– Doug McIlroy Giới thiệu Kiến thức cơ bản về Class + Menber Funtions Hàm thành phần; Default Copying Bản sao mặc định; AccessControl Kiểm soát truy cập; class and struct Lớp và cấu
Trang 1TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI PHÂN HIỆU TẠI TP HỒ CHÍ MINH
BỘ MÔN CÔNG NGHỆ THÔNG TIN
BÁO CÁO BÀI TẬP LỚN NỘI DUNG: DỊCH TÀI LIỆU The C++ Programming Language Fourth Edition
Giảng viên hướng dẫn: TRẦN THỊ DUNG
Sinh viên thực hiện: NGUYỄN ĐÌNH TRINH ĐẠT
Lớp : CÔNG NGHỆ THÔNG TIN K62
Khoá : K62
Tp Hồ Chí Minh, năm 2022
Trang 2TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI PHÂN HIỆU TẠI TP HỒ CHÍ MINH
BỘ MÔN CÔNG NGHỆ THÔNG TIN
BÁO CÁO BÀI TẬP LỚN NỘI DUNG: DỊCH TÀI LIỆU The C++ Programming Language Fourth Edition
Giảng viên hướng dẫn: TRẦN THỊ DUNG
Sinh viên thực hiện: NGUYỄN ĐÌNH TRINH ĐẠT
Lớp : CÔNG NGHỆ THÔNG TIN K62
Khoá : K62
Tp Hồ Chí Minh, năm 2022
Trang 3NHẬN XÉT CỦA GIÁO VIÊN HƯỚNG DẪN
………
………
………
………
………
………
………
………
………
………
………
………
………
………
………
………
………
………
………
………
Tp Hồ Chí Minh, ngày ….… tháng 09 năm 2022
Giáo viên hướng dẫn
Trần Thị Dung
Trang 4Chương 16: CLASSES
Those types are not “abstract”;
they are as real as int and float.
– Doug McIlroy
Giới thiệu
Kiến thức cơ bản về Class
+ Menber Funtions (Hàm thành phần); Default Copying (Bản sao mặc định); AccessControl (Kiểm soát truy cập); class and struct (Lớp và cấu trúc); Constructors( Khởitạo); explicit Constructors( khởi tạo tường minh); In-Class Initializers (Bộ khởi tạotrong Class); In-Class Function Definitions(Định nghĩa hàm trong Class);Mutability(Tính đột biến); Self-Refer-ence(Tự tham khảo); Member Access(Truy cậpthành phần); static Members(Thành phần tĩnh ); Member Types(Các loại thành phần).+ Concrete Classes (Lớp khởi tạo đối tượng)
- Menber Funtions (Hàm thành phần); Helper Functions (Người trợ giúp); OverloadedOperators (Nạp chồng toán tử); The Significance of Concrete (Tầm quan trọng của lớp
cụ thể hay lớp không trừu tượng)
Lời khuyên
16.1 Tổng quan
C++ classes là một công cụ cho việc tạo ra các kiểu mới có thể sử dụng thuận tiện nhưnhững kiểu đã được tích hợp sẵn Ngoài ra, Lớp dẫn xuất (Derived classes) (§3.2.4,Chương 20) và temblates (§3.4, Chương 23) cho phép các lập trình viên thể hiện (phâncấp và tham số) các mối quan hệ giữa các class và tận dụng các mối quan hệ đó
Mỗi kiểu là đại diện cụ thể cho một khái niệm (ý tưởng, khái niệm, …) Ví dụ, kiểu floattích hợp trong C++ với các phép toán +, -, *, … của nó, cung cấp một sự gần đúng cụ thểcủa khái niệm toán học về một số thực Một Class là một kiểu do người dùng định nghĩa.Chúng ta thiết kế ra một kiểu mới để tạo ra một định nghĩa mà chưa được tích hợp sẵn Vídụ: chúng ta có thể cung cấp loại Trunk_line trong chương trình xử lý điện thoại, loạiExplosion cho trò chơi điện tử hoặc loại danh sách <Paragraph> cho chương trình xử lý
Trang 5văn bản Một chương trình tạo ra với các kiểu tương thích với các khái niệm của chươngtrình đó thì sẽ dễ hiểu hơn, dễ lập luận hơn, dễ sửa đổi hơn so với một chương trìnhngược lại Việc xác định chính xác các kiểu cũng giúp cho chương trình trở nên ngắn gọnhơn Ngoài ra, nó làm cho nhiều loại phân tích code có thể thực hiện được Đặc biệt, nócho phép trình biên dịch phát hiện ra việc sử dụng các đối tượng bất hợp pháp mà nếukhông thì chỉ được tìm thấy thông qua kiểm tra toàn diện
Ý tưởng trong việc xác định một kiểu mới là tách các chi tiết ngẫu nhiên của việc triểnkhai (ví dụ: bố cục của dữ liệu được sử dụng để lưu trữ một đối tượng của kiểu) khỏi cácthuộc tính cần thiết nhằm sử dụng nó một cách chính xác (ví dụ: danh sách đầy đủ của cáchàm có thể truy cập dữ liệu) Sự tách biệt như vậy được thể hiện tốt nhất bằng cách phânluồng tất cả các mục đích sử dụng cấu trúc dữ liệu và các quy trình ‘’ vệ sinh nội bộ`` của
nó thông qua một giao diện cụ thể
Chương này tập trung vào các kiểu tương đối đơn giản '' cụ thể '' do người dùng xác định
mà về mặt logic không khác nhiều so với các kiểu tích hợp sẵn:
§16.2: Khái niệm cơ bản về lớp, Cơ sở cơ bản để xác định một lớp và các thành phần của
nó
§16.3: Lớp cụ thể: thảo luận về việc trình bày các lớp cụ thể đẹp và hiệu quả.
Các chương sau đi sâu vào chi tiết hơn và trình bày các lớp trừu tượng và cấu trúc phâncấp lớp:
Chương 17: Xây dựng, Hủy bỏ, Sao chép và Di chuyển trình bày nhiều cách khác nhau để
kiểm soát việc khởi tạo các đối tượng của một lớp, cách sao chép và di chuyển các đốitượng cũng như cách cung cấp '' các hành động dọn dẹp '' sẽ được thực hiện khi một đốitượng bị phá hủy (ví dụ: vượt ra khỏi phạm vi)
Chương 18: Nạp chồng toán tử, giải thích cách xác định các toán tử đơn phân và nhị
phân (VD: +, ∗, ! ) cho các loại do người dùng xác định và cách sử dụng chúng
Chương 19: Toán tử đặc biệt, cách xác định và sử dụng các toán tử (VD: [], (), ->, new),
“Đặc biệt” ở chỗ chúng được sử dụng bằng những cách khác với toán tử số học và logic.Đặc biệt, chương này chỉ ra cách định nghĩa một String Class
Trang 6Chương 20: Lớp dẫn xuất (Derived classes) giới thiệu các tính năng cơ của của ngôn ngữ
hỗ trợ lập trình hướng đối tượng Các lớp cơ sở và lớp dẫn xuất, các hàm ảo, kiểm soáttruy cập được bảo đảm
Chương 21: Lớp kế thừa (Class Hierarchies) sử dụng các lớp cơ sở và lớp dẫn xuất để
xây dựng code xung quanh khái niệm lớp kế thừa Phần lớn chương này nói về các kỹthuật lập trình, nhưng vẫn đề cập đến kỹ thuật đa kế thừa (Các lớp có nhiều hơn một lớp
cơ sở)
Chương 22: Run-time type information (RTTI) mô tả các kỹ thuật để điều hướng một
cách rõ ràng các cấu trúc lớp kế thừa Cụ thể, các loại thay đổi kiểu dynamic_cast vàstatic_cast được nêu rõ, cũng như xác định kiểu của một đối tượng cho một trong các lớp
cơ sở của nó (typeid)
16.2 Lớp cơ sở
Nội dung cơ bản:
Một lớp là một kiểu do người dùng định nghĩa
Một lớp bao gồm một tập hợp các thành phần Như là dữ liệu hay các hàm thànhphần của nó
Các hàm thành phần có thể được định nghĩa sự khởi tạo (creation), sao chép, dichuyển, và hủy bỏ
Các thành phần được truy cập bằng dấu chấm “.” cho các đối tượng và dấu mũi tên
“->” cho con trỏ
Các toán tử, chẳng hạn như +, !, và [], có thể được định nghĩa cho một lớp
Một lớp là không gian để chứa các thành phần của nó
Các thành phần Pulic cung cấp các “class’s interface” hay thường được gọi là các nguyên mẫu hàm còn các thành phần Private triển khai các chi tiết
Struct là một lớp (class) mà các thành phần bên trong nó mặc định được công khai
Trang 7int m;
public: // thông báo thàn phần sau là public
X(int i =0) :m{i} { } // khởi tạoint mf(int i) // hàm thành phần{
int old = m;
m = i; // gán giá trị mớireturn old; // trả về giá trị cũ}
};
X var {7}; // một biến kiểu X được khởi tạo thành 7
int user(X var, X∗ ptr)
{
int x = var.mf(7); // truy cập sử dụng dấu chấm “.”
int y = ptr−>mf(9); // truy cập sử dụng dấu mũi tên “->”
int z = var.m; // lỗi: không thể truy cập thành viên private
Trang 8int d, m, y;
};
void init_date(Date& d, int, int, int); // khởi tạo d
void add_year(Date& d, int n); // nhập năm cho d
void add_month(Date& d, int n); // nhập tháng cho d
void add_day(Date& d, int n); // nhập ngày cho d
Ở đây có thể dễ dàng nhìn thấy là không thể nào kết nối các thành phần một cách rõ ràng
Để cho rõ ràng thì cần khai báo nguyên mẫu hàm trước như sau:
struct Date {
int d, m, y;
void init(int dd, int mm, int yy); // khởi tạovoid add_year(int n); // nhập nămvoid add_month(int n); // nhập thángvoid add_day(int n); // nhập ngày};
Các hàm được khai báo trong một lớp (struct cũng được coi là một lớp) được gọi là cáchàm thành phần và chỉ có thể dược gọi cho một cho một biến cụ thể của kiểu thích hợpbằng cách sử dụng các cú pháp để truy cập các thành phần của struct Ví dụ:
Date my_bir thday;
void f()
{
Date today;
today.init(16,10,1996);
Trang 9sẽ gán cho my.birthday.m Một hàm thành phần trong một lớp “hiểu” đối tượng mà mà nó
đã gọi Để biết thêm chi tiết về thành phần static ở phần sau
Trang 10Mặc định, bản sao của một đối tượng lớp là bản sao của mỗi thành phần và các đối tượngcủa lớp có thể được sao chép theo phép gán Ví dụ:
void f(Date& d)
{
d = my_birthday;
}
16.2.3 Kiếm soát truy cập
Ở phần trước nó không chỉ rõ những hàm nào là hàm duy nhất trực tiếp vào việc thể hiệnDate và những hàm duy nhất để truy cập trực tiếp vào các đối tượng của lớp Date Đểkhắc phục hạn chế này thì ta sử dụng thay thế struct bằng một class:
Trang 11Việc bảo vệ dữ liệu một cách private dựa trên việc hạn chế sử dụng tên riêng của cácthành viên trong lớp đó Do đó, nó có thể bị truy cập bằng thao tác địa chỉ và chuyển đổikiểu Nhưng điều này tất nhiên là một sự gian lận C++ bảo vệ chống lại sự cố thay vì cố
ý pháp vỡ (gian lận) Chỉ phần cứng mới có thể cung cấp khả năng bảo vệ hoàn hảo chống
Trang 12lại việc sử dụng ác ý một ngôn ngữ đa dụng và thậm chí điều đó khó có thể thực hiệnđược trong các hệ thống thực tế.
16.2.4 class and struct
Cấu trúc:
class X { };
được gọi là định nghĩa lớp; nó định nghĩa một kiểu X
Theo định nghĩa, struct là một lớp trong đó các thành viên được mặc định là public; đó là,
Date1(int dd, int mm, int yy);
Trang 13int d, m, y;
public:
Date2(int dd, int mm, int yy);
void add_year(int n); // add n years};
Ở đây Date1 và Date2 là tương đương nhau
Không bắt buộc phải khai báo dữ liệu trước trong một lớp Trên thực tế, thường thì hợp lýnhất khi đặt các dữ liệu private ở cuối cùng để nhấn mạnh các chức năng đưuọc cung cấpbởi các nguyên mẫu hàm ở trên.Ví dụ:
class Date3 {
public:
Date3(int dd, int mm, int yy);
void add_year(int n); // add n yearsprivate:
Trang 14Date today = Date(23,6,1983);
Date xmas(25,12,1990); // dạng viết tắt
Date my_bir thday; // lỗi: Thiếu giá trị khởi tạo
Trang 15Date release1_0(10,12); // lỗi: Thiếu giá trị thứ 3(yy)
Khi khởi tạo có thế sử dụng kí hiệu {} thay thế:
Date today = Date {23,6,1983};
Date xmas {25,12,1990}; // dạng viết tắt
Date release1_0 {10,12}; // lỗi: Thiếu giá trị thứ 3 (yy)
Có một lời khuyên cho bạn là nên dùng {} thay cho () vì nó sẽ cụ thể những gì đang thựchiện (khởi tạo), nhằm tránh một số lỗi tiềm ẩn Có những trường hợp bắt buộc phải dùng() những rất hiếm Có nhiều cách khởi tạo các đối tượng của một kiểu Ví dụ:
Date(const char∗); // Date được thể hiện thông qua chuỗi};
Các hàm tạo tuân theo các quy tắc nạp chồng giống như các hàm thông thường Miễn làcác hàm tạo có đủ khác biệt về kiểu đối số của chúng, trình biên dịch có thể chọn đúng để
sử dụng:
Date today {4}; // 4, today.m, today.y
Date july4 {"July 4, 1983"};
Trang 16Date guy {5,11}; // 5, 11, today.y
Date now; // mặc định khởi tạo như hôm nay
Date start {}; // mặc định khởi tạo như hôm nay
16.2.6 Khởi tạo tường minh
Theo mặc định, một hàm tạo được gọi bởi một đối số hoạt động như một chuyển đổingầm định từ kiểu đối số sang kiểu của nó Ví dụ:
complex<double> d {1}; // d== {1,0} (§5.6.2)
Những chuyển đổi ngầm như vậy có thể cực kỳ hữu ích Số phức là một ví dụ: nếu chúng
ta bỏ đi phần ảo, chúng ta sẽ nhận được một số phức trên trục thực Đó chính xác lànhững gì toán học yêu cầu Tuy nhiên, trong nhiều trường hợp, những chuyển đổi như vậy
có thể là một nguồn gây nhầm lẫn và sai sót đáng kể Thử xét với ví dụ Date:
Trang 17Date d2 = Date{15}; // OK: rõ ràng
Date d3 = {15}; // lỗi : = Khởi tạo không thể thực hiện ngầmDate d4 = 15; // lỗi : = Khởi tạo không thể thực hiện ngầmvoid f()
{
my_fct(15); // lỗi : truyền đối số không thể thực hiện ngầmmy_fct({15}); // lỗi:truyền đối số không thể thực hiện ngầm my_fct(Date{15}); // OK: rõ ràng
//
}
Một khởi tạo với dâu “=” được coi là một sự khởi tạo sao chép
16.2.7 Khởi tạo trong class
Khi ta sử dụng một số hàm khởi tạo, việc khởi tạo thành phần có thể lặp lại Ví dụ:
class Date {
int d, m, y;
Trang 18//
Date(int, int, int); // ngày, tháng, nămDate(int, int); // ngày, tháng, năm của hôm nayDate(int); // ngày, tháng và năm của hôm nayDate(); // mặc định Date: hôm nay
Date(const char∗); // Date được thể hiện thông qua chuỗi};
Có thể giải quyết vấn đề đó bằng cách khai báo các đối số mặc định để giảm số lượnghàm tạo Ngoài ra, chúng ta có thể thêm trình khởi tạo vào các biến trong class:
Trang 19Một hàm thành viên được định nghĩa trong định nghĩa lớp - thay vì chỉ được khai báo ở
đó - được coi là một hàm thành viên nội tuyến Có nghĩa là, định nghĩa trong lớp của cáchàm thành viên dành cho các hàm nhỏ, hiếm khi được sửa đổi, được sử dụng thườngxuyên Giống như định nghĩa lớp mà nó là một phần, một hàm thành viên được địnhnghĩa trong lớp có thể được sao chép trong một số đơn vị dịch bằng cách sử dụng
#include Giống như bản thân lớp, ý nghĩa của hàm thành viên phải giống nhau ở bất kỳđâu #include
Một thành phần có thể tham chiếu đến một thành viên khác trong lớp của mình một cáchđộc lập với nơi thành viên đó được xác định Xem xét:
class Date {
public:
void add_month(int n) { m+=n; } // cộng thêm tháng của Date
Trang 20Kiểu sau này thường được sử dụng để giữ cho các định nghĩa lớp đơn giản và dễ đọc Nócũng cung cấp sự phân tách văn bản về giao diện và triển khai của một lớp
16.2.9 Khả năng thay đổi
Chúng ta có thể định nghĩa một đối tượng được đặt tên như một hằng số hoặc một biến.Nói cách khác, tên có thể đề cập đến một đối tượng chứa một giá trị không thể thay đổi
Trang 21hoặc có thể thay đổi Vì thuật ngữ chính xác có thể hơi vụng về, chúng tôi kết thúc việc đềcập đến một số biến là hằng số hoặc ngắn gọn vẫn là biến const.
Để trở nên có tác dụng khác ngoài định nghĩa về hằng số đơn giản của các kiểu dựng sẵn,
ta phải có khả năng xác định các hàm hoạt động trên các đối tượng hằng của kiểu dongười dùng định nghĩa Đối với các hàm tự do có nghĩa là các hàm nhận const T & đối số.Đối với các lớp, điều đó có nghĩa là chúng ta phải có khả năng xác định các hàm thànhviên hoạt động trên các đối tượng const
16.2.9.1 Hàm thành viên hằng
Date được định nghĩa cho đến nay cung cấp các hàm thành viên để cung cấp giá trị choDate Nhưng ta chưa có cách kiểm tra giá trị của Date Có thể dễ dàng sửa lỗi này bằngcách thêm các chức năng đọc ngày, tháng và năm:
Đương nhiên, trình biên dịch sẽ phát hiện những vi phạm sự “const” này Ví dụ:
int Date::year() const
Trang 22void f(Date& d, const Date& cd)
{
int i = d.year(); // OKd.add_year(1); // OKint j = cd.year(); // OKcd.add_year(1); // lỗi : không thể thay đổi giá trị của một const Date}
16.2.9.2 Hằng số vật lý và logic
Đôi khi, một hàm thành viên về mặt logic là const, nhưng nó vẫn cần thay đổi giá trị củamột thành viên Có nghĩa là, đối với người dùng, hàm dường như không thay đổi trạngthái của đối tượng của nó, nhưng một số chi tiết mà người dùng không thể quan sát trực
Trang 23tiếp được cập nhật Điều này thường được gọi là hằng số logic Ví dụ, lớp Date có thể cómột hàm trả về biểu diễn chuỗi Việc xây dựng biểu diễn này có thể là một hoạt độngtương đối tốn kém Do đó, bạn nên giữ một bản sao để các yêu cầu lặp đi lặp lại sẽ chỉ trảlại bản sao đó, trừ khi giá trị của Date đã được thay đổi Lưu trữ các giá trị như vậy phổbiến hơn đối với các cấu trúc dữ liệu phức tạp hơn, nhưng hãy xem cách có thể đạt đượcgiá trị đó cho một Date:
Theo người dùng, string_rep không thay đổi trạng thái Date của nó, vì vậy rõ ràng nó phải
là một hàm thành viên const Mặt khác, các thành viên cache và cache_valid thỉnh thoảngphải thay đổi để thiết kế có ý nghĩa
Những vấn đề như vậy có thể được giải quyết thông qua bạo lực bằng cách sử dụng épkiểu, ví dụ, một const_cast (§11.5.2) Tuy nhiên, cũng có những giải pháp thanh lịch hợp
lý mà không liên quan đến việc gây rối với các quy tắc loại
16.2.9.3 Có thể thay đổi (Mutable)
Chúng ta có thể định nghĩa một thành viên của một lớp là có thể thay đổi, nghĩa là nó cóthể được sửa đổi ngay cả trong một đối tượng const:
Trang 24mutable bool cache_valid;
mutable string cache;
void compute_cache_value() const; // fill (mutable) cache//
};
Bây giờ ta có thể định nghĩa string_rep () một cách rõ ràng:
string Date::string_rep() const
}
Bây giờ ta có thể sử dụng string_rep () cho cả đối tượng const và không phải const Ví dụ:
void f(Date d, const Date cd)
Trang 2516.2.9.4 Tính đột biến thông qua hướng dẫn
Khai báo một thành phần có thể thay đổi là thích hợp nhất khi chỉ một phần nhỏ của biểudiễn của một đối tượng nhỏ được phép thay đổi Các trường hợp phức tạp hơn thườngđược xử lý tốt hơn bằng cách đặt dữ liệu thay đổi vào một đối tượng riêng biệt và truy cập
nó một cách gián tiếp Nếu kỹ thuật đó được sử dụng, ví dụ string-with-cache sẽ trởthành:
Trang 2616.2.10 Tự quy (Self-Reference)
Các hàm cập nhật trạng thái add_year (), add_month () và add_day ()được định nghĩakhông trả về giá trị Đối với một tập hợp các hàm cập nhật liên quan như vậy, thường hữuích khi trả về một tham chiếu đến đối tượng được cập nhật để các hoạt động có thể đượcxâu chuỗi Ví dụ, ta muốn viết:
Trang 27//
Date& add_year(int n); // add n yearsDate& add_month(int n); // add n monthsDate& add_day(int n); // add n days};
Mỗi hàm thành phần (không phải static) biết đối tượng nào được gọi và có thể tham chiếuđến nó một cách rõ ràng Ví dụ:
∗this đề cập đến đối tượng mà hàm thành phần được gọi
Trong một hàm thành phần (không phải static), từ khóa này là một con trỏ đến đối tượng
mà hàm được gọi Trong một hàm thành viên không phải hằng số của lớp X, kiểu củahàm này là X ∗ Tuy nhiên, điều này được coi là không hợp lệ, vì vậy không thể lấy địachỉ của điều này hoặc gán cho điều này Trong một hàm thành viên const của lớp X, kiểunày là const X ∗ để ngăn việc sửa đổi chính đối tượng
Hầu hết việc sử dụng điều này là ẩn Đặc biệt, mọi tham chiếu đến thành phần (khôngphải static) từ bên trong một lớp dựa vào việc sử dụng ngầm định điều này để lấy thành
Trang 28viên của đối tượng thích hợp Ví dụ: hàm add_year có thể tương đương, nhưng thật tệ,hav e được định nghĩa như thế này:
Trang 29if (pre) pre−>suc = suc;
if (suc) suc−>pre = pre;
delete this;
}//
};
16.2.11 Quyền truy cập thành phần
Một thành viên của lớp X có thể được truy cập bằng cách áp dụng toán tử “.” (dấu chấm)cho một đối tượng của lớp X hoặc bằng cách áp dụng toán tử “->” (mũi tên) cho một contrỏ đến một đối tượng của lớp X Ví dụ:
Trang 30Rõ ràng, có một chút dư thừa ở đây: trình biên dịch biết liệu một tên liên quan đến X hay
X ∗, vì vậy một toán tử duy nhất sẽ là đủ Tuy nhiên, một lập trình viên có thể bị nhầmlẫn, vì vậy từ những ngày đầu tiên của C, quy tắc đã được sử dụng các toán tử riêng biệt
Từ bên trong một lớp không cần toán tử Ví dụ:
Trang 31int X::sm {7}; // X’s static member sm (§16.2.12)int (S::∗) pmf() {&S::f}; // X’s member f
Bây giờ chúng ta có thể xác định hàm tạo Date để sử dụng default_date như sau:
Date::Date(int dd, int mm, int yy)
Trang 32Sử dụng set_default (), chúng ta có thể thay đổi ngày mặc định khi thích hợp Một thànhviên tĩnh có thể được gọi như bất kỳ thành viên nào khác Ngoài ra, một thành phần static
có thể được tham chiếu mà không cần đề cập đến một đối tượng Thay vào đó, tên của nó
đủ điều kiện theo tên của lớp của nó Ví dụ:
Date Date::default_date {16,12,1770}; // định nghĩa Date :: default_date
void Date::set_default(int d, int m, int y) // định nghĩa Date::set_default