You are on page 1of 8

Chươ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); Access
Control(Kiểm soát truy cập); class and struct (Lớp và cấu trúc); Constructors( Khởi tạo); explicit
Constructors( khởi tạo tường minh); In-Class Initializers (Bộ khởi tạo trong 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ập thà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); Overloaded
Operators (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 Introduction
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ân cấ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 float tí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ại Explosion cho trò chơi điện tử hoặc loại danh sách <Paragraph> cho chương
trình xử lý vă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ương trì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ình ngượ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ọn hơ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ếu khô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ển khai (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ác thuộ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ác hà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ân luồ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 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ân cấ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 đối tượ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 đối tượ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.
Chươ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át truy 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ành phầ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, di chuyể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 Public.
Ví dụ:
class X {
private: // thông báo thàn phần sau là private
int m;
public: // thông báo thàn phần sau là public
X(int i =0) :m{i} { } // khởi tạo
int mf(int i) // hàm thành phần
{
int old = m;
m = i; // gán giá trị mới
return 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
}
Các phần sau đây nghiên cứu sâu về phần này và các cơ sở lý luận liên quan.
16.2.1 Hàn thành phần
Triển khai một struct date để xác định cách thể hiện thông tin Data (ngày ,tháng, năm) và các hàm để thực
hiện việc này:
struct Date {
int 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ạo
void add_year(int n); // nhập năm
void add_month(int n); // nhập tháng
void 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ác hà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ợp bằ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);
my_bir thday.init(30,12,1950);

Date tomorrow = today;


tomorrow.add_day(1);
// ...
}
Bởi vì các struct khác nhau có thể có các hàm thành phần trùng tên nhau, nên ta phải chỉ định tên struct
khi định nghĩa một hàm thành phần.
void Date::init(int dd, int mm, int yy)
{
d = dd;
m = mm;
y = yy;
}
Trong một hàm thành phần, tên của các thành phần có thể sử dụng mà không cần phải tham chiếu rõ ràng
đến một đối tượng nào. Trong trường hợp đó, tên truy cập đến thành phần của các đối tượng thứ mà đã
được gọi lại. Ví dụ: khi Date::init() được gọi cho today, m=mm sẽ gán cho today.m. Mặt khác, khi
Date::init() được gọi cho my_birthday, m=mm sẽ 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.
16.2.2 Hàm mặc định sao chép
Mặc định các đối tượng có thể được sao chép. Đặc biệt, một đối tượng của lớp có thể được khởi tạo bằng
bản sao của một đối tượng thuộc lớp của nó. Ví dụ:
Date d1 = my_birthday;
Date d2 {my_birthday};
Mặ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ượng củ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ện Date 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:
class Date {
int d, m, y;
public:
void init(int dd, int mm, int yy);
void add_year(int n);
void add_month(int n);
void add_day(int n);
};
“public” tách nội dung của class thành 2 phần riêng biệt. Trước public là các thành phần private và chỉ có
thể được sử dụng bởi các hàm thành phần. Còn lại là thành phần public có các nguyên mẫu hàm. Struct
đơn giản là một lớp gồm các thành phần public; các chức năng bên trong có thể được định nghĩa và sử
dụng chính xác như phía trên. Ví dụ:
void Date::add_year(int n)
{
y += n;
}
Tuy nhiên, các hàm không phải hàm thành phần thì bị cấm sử dụng các thành phần private. Ví dụ:
void timewarp(Date& d)
{
d.y −= 200; // Lỗi : Date::y is private
}
Hàm init() bây giờ rất cần thiết vì viêc đặt dữ liệu ở chế độ private buộc chúng ta phải cung cấp cách khởi
tạo các thành phần. Ví dụ:
Date dx;
dx.m = 3; // lỗi : m is private
dx.init(25,3,2011); // OK
Một số lợi ích thu được từ việc hạn ché quyền truy cập vào cấu trúc dữ liệu đối với danh sách các hàm
đươc báo rõ ràng. Ví dụ: Bất kỳ lỗi nào khiến Date nhận một giá trị bất hợp pháp (ví dụ: ngày 36 tháng 12
năm 2016) mà nguyên nhân bởi code của hàm thành phần. Điều này có thể cho biết rằng đây là giai đoạn
đầu tiên của quá trình debug được được hoàn thành trước khi chương trình có thể chạy được. Đây là một
trường hợp đặc biệt của nhận xét chung rằng bất kỳ thay đổi nào đối với sự thay đổi của Date đều có thể
và phải được thực hiện bằng các sự thay dổi đối với các thành phần của nó. Đặc biệt, nếu ta thay đổi
nguyên mẫu hàm của một lớp, chúng ta chỉ cần thay đổi hàm thành viên để tận dụng dược sự tích hợp của
nguyên mẫu hàm mới.
Việ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ác thành viên trong lớp
đó. Do đó, nó có thể bị truy cập bằng thao tác địa chỉ và chuyển đổi kiể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 lạ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à,
struct S { /* ... */ };
chỉ đơn giản là viết tắt của
class S { public: /* ... */ };
Hai định nghĩa này của S có thể thay thế cho nhau mặc dù thông thường sẽ chỉ sử dụng một kiểu. Bạn sử
dụng phong cách nào là tùy thuộc vào hoàn cảnh và sở thích.
Theo mặc đinh, các thành phần của một lớp là riêng tư như sau:
class Date1 {
int d, m, y; // private là mặc định
public:
Date1(int dd, int mm, int yy);
void add_year(int n);
};
Tuy nhiên, ta có thể sử dụng các công cụ xác định quyền truy cập private: để nói rằng phần sau đây là
private cũng giống như public:
struct Date2 {
private:
int 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ấp bở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 years
private:
int d, m, y;
};
Khi code thực tế, cả public và private thường nhiều hơn các ví dụ hướng dẫn ở đây. Các chỉ định truy cập
có thể được sử dụng nhiều lần trong một class duy nhất. Ví dụ:
class Date4 {
public:
Date4(int dd, int mm, int yy);
private:
int d, m, y;
public:
void add_year(int n); // add n years
};
Tuy nhiên, có nhiều hơn một lần public, nhưng trong Date 4 có vẻ hơi lộn xộn và có thể ảnh hưởng đến
bố cục của đối tượng.
16.2.5 Khởi tạo
Việc sử dụng các hàm như init () để cung cấp khởi tạo cho các đối tượng lớp là không phù hợp và dễ xảy
ra lỗi. Bởi vì không có chỗ nào nói rằng một đối tượng phải được khởi tạo, một lập trình viên có thể quên
làm như vậy - hoặc làm như vậy hai lần (thường cho kết quả thảm hại như nhau). Một cách tiếp cận tốt
hơn là cho phép lập trình viên khai báo một hàm với mục đích rõ ràng là khởi tạo các đối tượng. Bởi vì
một hàm như vậy xây dựng các giá trị của một kiểu nhất định, nó được gọi là một hàm tạo. Một phương
thức khởi tạo được nhận dạng bằng cách có cùng tên với chính lớp đó. Ví dụ:

You might also like