You are on page 1of 210

KỸ THUẬT LẬP TRÌNH

Chương 6. Lập trình hướng


đối tượng

277
Lập trình hướng đối tượng
C++

278
Tổng quan

279
Lập trình cấu trúc
• Lập trình cấu trúc (1960s)
– Phân chia vấn đề lớn thành các vấn đề con độc lập, từ
đó tạo thành thủ tục và hàm.
– Trọng tâm của lập trình truyền thống: dựa trên các chi
tiết của việc thực hiện.
– Phương pháp viết chương trình chặt chẽ
– Rõ ràng, dễ thử nghiệm và sửa lỗi, dễ thay đổi
– Khi chương trình lớn hơn  khó quản lý
– Dữ liệu đóng vai trò quan trọng
• Khi thêm 1 dữ liệu kiểu mới, phải thay đổi tất cả các công việc
và các hàm liên quan đến dữ liệu đó
• Không thể phân chia các phần cần che dấu thông tin trong
chương trình. 280
Các phương pháp lập trình

Lập trình tuyến tính Thời gian đầu

Lập trình có cấu trúc 1960 – 1970

Lập trình hướng đối


tượng 1980
281
(1) Lập trình tuyến tính

• Chương trình là một


dãy các lệnh
• Lập trình là viết các
lệnh trong dãy lệnh

• Không mang tính thiết kế

• Tiêu biểu là ngôn ngữ Basic, Fortran


282
(1) Lập trình tuyến tính: Đặc điểm

• Chương trình đơn giản, số dòng lệnh ít

• Thực hiện trình tự từ đầu đến cuối

• Không có cấu trúc

• Dùng các lệnh “goto/ gosub” để nhảy đến một


vị trí nào đó trong chương trình

283
(2) Lập trình có cấu trúc

• Chương trình là một hệ thống các thủ tục/


hàm. Mỗi thủ tục/ hàm là một dãy các lệnh

• Lập trình là xây dựng các thủ tục/ hàm

• Kết quả là hệ thống cấu trúc và mối quan hệ


giữa các hàm/ thủ tục

• Tiêu biểu là ngôn ngữ Pascal, C

284
(2) Lập trình có cấu trúc

• Khái niệm trừu tượng hóa (abstraction) theo


chức năng: Làm được gì, không quan tâm chi
tiết bên trong

• Phân chia công việc thành các chức năng đơn


giản hơn gọi là lập trình đơn thể (module)

285
(2) Lập trình có cấu trúc: Đặc điểm
Ưu điểm

• Đơn giản hóa quá trình thiết kế và cài đặt

• Dễ đọc chương trình, hiểu chi tiết bài toán

• Các chương trình con tương đối độc lập, dễ


phân công cho từng nhóm

286
(2) Lập trình có cấu trúc: Đặc điểm
Khuyết điểm

Khi thay đổi dữ liệu thì phải thực hiện các thay
đổi ở mọi đơn thể có tác động trên chúng

Hàm và dữ liệu không có quan hệ với nhau

Hàm không hạn chế truy nhập tới các biến toàn
cục

287
(2) Lập trình có cấu trúc: Đặc điểm
Khuyết điểm

• Sai sót trong việc trao đổi thông tin giữa các
thành viên trong nhóm có thể mất nhiều thời
gian để sửa chữa chương trình

• Phần xử lý nằm rải rác và phải hiểu rõ cấu


trúc dữ liệu

• Khó bảo trì chương trình


288
(3) Lập trình Hướng đối tượng
(Object-oriented Programming)
• Tư duy mới, tiếp cận theo hướng đối
tượng để giải các bài toán trên máy tính

• Thiết kế và phát triển phần mềm dựa trên


kiến trúc lớp và đối tượng

289
(3) Lập trình Hướng đối tượng
(Object-oriented Programming)
Mục tiêu

• Loại bỏ những thiếu sót của tiếp cận theo thủ tục

• Tiếp cận theo hướng trừu tượng hoá (abstraction)

• Dữ liệu được xem là phần trung tâm và được bảo


vệ

• Hàm gắn kết với dữ liệu

290
(3) Lập trình Hướng đối tượng
(Object-oriented Programming)

Mục tiêu

• Phân tách bài toán thành nhiều đối tượng và


yêu cầu chúng thực hiện hành động của mình

• Tăng cường khả năng sử dụng lại

291
(3) Lập trình Hướng đối tượng
(Object-oriented Programming)

Ưu điểm

• Cung cấp một cấu trúc module rõ ràng: Giao


diện định nghĩa rõ ràng và chi tiết cài đặt ẩn

• Duy trì và sửa đổi mã nguồn dễ dàng

• Cung cấp framework tốt với các thư viện mã


nguồn

292
Lập trình hướng đối tượng
• Object oriented programming (OOP)
– Chia bài toán thành các nhóm nhỏ có liên hệ với
nhau gọi là đối tượng.

293
Đối Tượng (Object)
• Đối tượng là
chìa khóa để hiểu
được kỹ thuật
hướng đối tượng
• Trong hệ thống
hướng đối tượng,
mọi thứ đều là đối
tượng
Viết một chương trình hướng đối tượng nghĩa là đang tạo
một mô hình của một vài bộ phận trong thế giới thực
294
Đối tượng (Object)
What is the – Hộp đen chứa các
salary of Jack?
lệnh (code) và dữ
Sales
liệu.
– Thông tin truyền
giữa các phần khác
nhau gọi là các
Jack's salary thông điệp giữa các
is $2000 đối tượng.
Accounts – Các thông điệp này
có thể được chuyển
thành lời gọi hàm
trong chương trình.
295
Đối Tượng

Đối tượng phần mềm Đối tượng phần mềm Xe Đạp

Đối tượng (object) là một Thuộc tính được xác định


thực thể phần mềm bao bởi giá trị cụ thể gọi là
bọc các thuộc tính và thuộc tính thể hiện.
các phương thức liên Một đối tượng cụ thể
quan. được gọi là một thể hiện.
296
Đối Tượng Thế Giới Thực
(Real Object)

• Một đối tượng thế giới thực là một thực


thể cụ thể mà thông thường bạn có thể sờ,
nhìn thấy hay cảm nhận được.
Dữ liệu thành viên Hàm thành viên

• Tất cả có
trạng thái
(state) và
hành động
(behaviour)
297
Đối Tượng Phần Mềm
(Software Object)
• Các đối tượng phần mềm có thể được
dùng để biểu diễn các đối tượng thế giới
thực.
• Cũng có trạng thái và
hành động
– Trạng thái: thuộc tính
(attribute; property)
– Hành động: phương thức
(method) 298
Lớp (Class)
• Nhóm các đối tượng có cùng thuộc tính (hay còn gọi là
trạng thái, hoặc Dữ liệu thành viên), hành vi (Hàm thành
viên) và mối quan hệ chung.
• Lớp là viết tắt của “lớp của đối tượng”.
• Một lớp là một thiết kế (blueprint) hay mẫu (prototype)
cho các đối tượng cùng kiểu
– Ví dụ: lớp XeDap là một thiết kế chung cho nhiều đối tượng xe
đạp được tạo ra
• Một đối tượng là một thể hiện (instance) cụ thể của một
lớp.
– Ví dụ: mỗi đối tượng xe đạp là một thể hiện của lớp XeDap
• Mỗi thể hiện có thể có những thuộc tính thể hiện khác
nhau
– Ví dụ: một cây mai có hoa 6 cánh, trong khi một cây khác có hoa
299
đến 12 cánh.
Thuộc Tính Lớp
& Phương Thức Lớp

Đối tượng đa giác Lớp đa giác

Thuộc tính
(properties):
Số cạnh
Trừu tượng hóa
Màu viền
Màu nền

Phương thức
(methode):
Vẽ
Xóa
Di chuyển

300
Thuộc Tính Lớp
& Phương Thức Lớp
• Thuộc tính lớp (class attribute): đặc trưng yêu cầu của
đối tượng hoặc thực thể được biểu diễn trong một lớp.
• Thuộc tính lớp được định nghĩa bên trong định nghĩa lớp
và được chia sẻ bởi tất cả các thể hiện của lớp.
– Ví dụ: Lớp đa giác gồm các thuộc tính như: số cạnh, màu viền,
màu chữ…
• Phương thức lớp (class method): phương thức (hành
động) yêu cầu của đối tượng hoặc thực thể được biểu
diễn trong một lớp.
• Tất cả các phương thức lớp ảnh hưởng đến toàn bộ lớp
chứ không ảnh hưởng đến một lớp riêng rẽ nào.
– Ví dụ: Lớp đa giác gồm các phương thức như: vẽ, xóa, di
chuyển…

301
Thuộc tính (Attribute) và Phương
thức (Method)
• Thuộc tính (attribute) là dữ liệu trình bày các đặc điểm
về một đối tượng.
• Phương thức (method) có liên quan tới những thứ mà
đối tượng có thể làm. Một phương thức đáp ứng một
chức năng tác động lên dữ liệu của đối tượng (thuộc
tính).

302
Thông Điệp
& Truyền Thông Điệp
• Thông điệp (message) là một lời yêu cầu
một hoạt động. Gồm có:
– Đối tượng nhận thông điệp
– Tên của phương thức thực hiện
– Các tham số mà phương thức cần
• Truyền thông điệp: một đối tượng triệu
gọi một hay nhiều phương thức của đối
tượng khác để yêu cầu thông tin.

303
Đặc Điểm Quan Trọng
• Nhấn mạnh trên dữ liệu hơn là thủ tục
• Các chương trình được chia thành các đối
tượng
• Dữ liệu được che giấu và không thể được
truy xuất từ các hàm bên ngoài
• Các đối tượng có thể giao tiếp với nhau
thông qua các hàm
• Dữ liệu hay các hàm mới có thể được
thêm vào khi cần
• Theo tiếp cận từ dưới lên 304
Thuận Lợi
• So với các tiếp cận cổ điển thì OOP có
những thuận lợi sau:
– OOP cung cấp một cấu trúc module rõ ràng
• Giao diện được định nghĩa tốt
• Những chi tiết cài đặt được ẩn
– OOP giúp lập trình viên duy trì mã và sửa đổi mã tồn
tại dễ dàng (các đối tượng được tạo ra với những
khác nhau nhỏ so với những đối tượng tồn tại).
– OOP cung cấp một cơ chế tốt với các thư viện mã mà
các thành phần có thể được chọn và sửa đổi bởi lập
trình viên.
305
Trừu Tượng Hóa
(Abstraction)
• Trừu tượng hóa
– Tiến trình xem xét các khía cạnh nào đó của
bài toán.
– Biểu diễn những đặc tính, bỏ qua những chi
tiết vụn vặt hoặc những giải thích.
• Các kỹ thuật trừu tượng
– Đóng gói (encapsulation)
– Ẩn thông tin (information hiding)
– Thừa kế (inheritance)
– Đa hình (polymorphism)
306
Tính đóng gói
(Encapsulation)
• Đóng gói (encapsulation):
– Cho phép truy cập đối tượng chỉ qua thông
điệp của nó trong khi giữ kín các chi tiết riêng
tư gọi là ẩn thông tin.
– Là tiến trình che giấu việc thực thi chi tiết của
một đối tượng.

307
Ẩn thông tin
(Information Hiding)
• Đóng gói  Thuộc tính được lưu trữ
hay phương thức được cài đặt như thế
nào  được che giấu đi từ các đối
tượng khác

Việc che giấu những chi


tiết thiết kế và cài đặt từ
những đối tượng khác
được gọi là ẩn thông tin

308
Tính Thừa Kế
(Inheritance)

• Hệ thống hướng đối tượng cho phép các


lớp được định nghĩa kế thừa từ các lớp
khác
– Ví dụ, lớp xe đạp leo núi và xe đạp đua là
những lớp con (subclass) của lớp xe đạp.
• Thừa kế nghĩa là các phương thức và các
thuộc tính được định nghĩa trong một lớp
có thể được thừa kế hoặc được sử dụng
lại bởi lớp khác.
309
Tính Đa Hình
(Polymorphism)
• Đa hình: “nhiều hình thức”, hành động
cùng tên có thể được thực hiện khác nhau
đối với các đối tượng/các lớp khác nhau.
• Ngữ cảnh khác  kết quả khác
Điểm Đường Thẳng Hình Tròn Hình Vuông

Vẽ

310
Chương trình học
• Chương 1. Tổng quan
• Chương 2. Biến, biểu thức và toán tử
• Chương 3. Cấu trúc điều khiển
• Chương 4. Hàm
• Chương 5. Mảng và con trỏ
• Chương 6. Lập trình hướng đối tượng
• Chương 7. Lớp
• Chương 8. Quá tải
• Chương 9: Thừa kế 311
KỸ THUẬT LẬP TRÌNH
Chương 7. Lớp

312
Khái niệm lớp
• Lớp: khái niệm trung tâm của OOP
• Định nghĩa: Lớp là nhóm của những đối
tượng (objects) có cùng chung thuộc tính
(properties) và có những mối quan hệ
chung
• Đối tượng: thể hiện một thực thể trong thế
giới thực.

313
Cú pháp khai báo lớp
class <Ten lop>
{ private: <Khai bao cac
thanh phan private>
public: <Khai bao cac class Xedap{
thanh phan public> };
protected: <Khai bao
cac thanh phan protected>
};

314
Sử dụng lớp đối tượng
• <Tên lớp> <Tên biến lớp>;
• Tên lớp: là tên lớp đối tượng đã được định nghĩa trước khi
khai báo biến.
• Tên biến lớp: là tên đối tượng cụ thể. Tên biến lớp sẽ được
sử dụng như các biến thông thường trong C++, ngoại trừ
việc nó có kiểu lớp đối tượng.

Xedap xedap; // đúng,


nhưng khai báo:
class Xedap xedap; ; // Lỗi cú pháp là
sai cú pháp.

315
Các thành phần của lớp
• Thuộc tính của lớp (Dữ liệu thành viên)
<Kiểu dữ liệu> <Tên thuộc tính>;
Các thành phần chỉ dữ liệu của lớp, được gọi
là thuộc tính của lớp
class Xedap
{ private: int tocdo;

public: string nhanhieu;

};

316
Các thành phần của lớp
• Phương thức của lớp (Hàm thành viên)
<Kiểu trả về> <Tên phương thức>([<Các
tham số>]);
Một phương thức là một thao tác thực hiện
một số hành động đặc trưng của lớp đối
tượng. class Xedap{
private:
int tocdo;
string nhanhieu;
public:
void show();
};
Code 317
Khái niệm lớp
private
• Lớp: kiểu dữ liệu trừu tượng. protected
public
Đặc tả class TÊNLỚP
TÊN LỚP
đối [: <Quyền truy xuất> LỚPCHA ]
tượng
{ <Quyền truy xuất > :
Dữ liệu DataType1 memberdata1;
thành viên DataType2 memberdata2;
…………….
< Quyền truy xuất > :
Tập các Hàm memberFunction1();
thao tác thành viên
memberFunction2();
…………..
};

318
Quyền truy xuất
• Các thành viên lớp được liệt kê vào một trong ba loại quyền truy
xuất khác nhau:
• Các thành viên (hàm thành viên và dữ liệu thành viên) có quyền
truy cập là chung: (public) có thể được truy xuất bởi tất cả các
thành phần sử dụng lớp.
• Các thành viên riêng (hàm thành viên và dữ liệu thành viên) có
quyền truy cập là riêng: (private) chỉ có thể được truy xuất bởi các
thành viên lớp.
• Các thành viên hàm thành viên và dữ liệu thành viên) có quyền
truy cập là chung: được bảo vệ (protected) chỉ có thể được truy
xuất bởi các thành viên lớp và các thành viên của một lớp dẫn xuất.

319
Lớp đơn giản
• Ví dụ: Tạo ra
đối tượng
class Point { thuộc lớp
int xVal, yVal; void main() { Point
public: Point pt;
Khai báo void SetPt (int, int);
void OffsetPt (int, int); pt.SetPt(10,20);
Lớp pt.OffsetPt(2,2);
}; Gọi hàm
……..
void Point::SetPt (int x, int y) { trên
xVal = x; đối tượng
Định nghĩa yVal = y;
} pt.xVal = 10; // Đúng hay sai?
các hàm
thành viên void Point::OffsetPt (int x, int y) {
xVal += x; Point pt1, pt2, pt3;
……….
yVal += y;
}
}
320
Quyền Lớp đơn giản
truy cập Đối tượng
1
Tên lớp
4 1 5
• Ví dụ: Dữ liệu thành viên Tạo ra
2 đối tượng
class Point { thuộc lớp
int xVal, yVal; void main() { Point
public: 3 Point pt;
void SetPt (int, int);
Khai báo
void OffsetPt (int, int);
Hàm thành viên
pt.SetPt(10,20);
Lớp pt.OffsetPt(2,2);
}; Gọi hàm
……..
void Point::SetPt (int x, int y) { trên
xVal = x; đối tượng
Định nghĩa yVal = y;
} pt.xVal = 10; // Đúng hay sai?
các hàm
thành viên void Point::OffsetPt (int x, int y) {
xVal += x; Point pt1, pt2, pt3;
……….
yVal += y;
}
}
321
Lớp đơn giản
Đối tượng
• Ví dụ: 1
Tạo ra
đối tượng
class Point { thuộc lớp
int xVal, yVal; void main() { Point
public: Point pt;
Khai báo void SetPt (int, int);
void OffsetPt (int, int); pt.SetPt(10,20);
Lớp pt.OffsetPt(2,2);
}; Gọi hàm
……..
void Point::SetPt (int x, int y) { trên
xVal = x; đối tượng
Định nghĩa yVal = y;
} pt.xVal = 10; // Đúng hay sai?
các hàm
thành viên void Point::OffsetPt (int x, int y) {
xVal += x; Point pt1, pt2, pt3;
……….
yVal += y;
}
}
322
Lớp đơn giản
Quyền truy cập của xVal là gì?
• Ví dụ: 1
Tạo ra
đối tượng
class Point { thuộc lớp
int xVal, yVal; void main() { Point
public: Point pt;
Khai báo void SetPt (int, int);
void OffsetPt (int, int); pt.SetPt(10,20);
Lớp pt.OffsetPt(2,2);
}; Gọi hàm
……..
void Point::SetPt (int x, int y) { trên
xVal = x; đối tượng
Định nghĩa yVal = y;
} pt.xVal = 10; // Đúng hay sai?
các hàm
thành viên void Point::OffsetPt (int x, int y) {
xVal += x; Point pt1, pt2, pt3;
……….
yVal += y;
}
}
323
Lớp đơn giản
• Ví dụ: Tạo ra
đối tượng
class Point { thuộc lớp
int xVal, yVal; void main() { Point
public: Point pt;
Khai báo void SetPt (int, int);
void OffsetPt (int, int); pt.SetPt(10,20);
Lớp pt.OffsetPt(2,2);
}; Gọi hàm
……..
void Point::SetPt (int x, int y) { trên
xVal = x; đối tượng
Định nghĩa yVal = y;
} pt.xVal = 10; // Đúng hay sai?
các hàm
thành viên void Point::OffsetPt (int x, int y) {
xVal += x; Point pt1, pt2, pt3;
……….
yVal += y;
}
}
324
Cơ bản về lớp
• Lớp gồm các thành viên:
– Dữ liệu thành viên (Member Data):
• Có cú pháp của định nghĩa biến
• Một đặc trưng của đối tượng
• Có thể là kiểu đã được định nghĩa hoặc tự định nghĩa
• Dữ liệu thành viên của lớp không thể có kiểu của lớp đó,
nhưng có thể là con trỏ kiểu lớp đó.
– Hàm thành viên (Member Function) hay phương thức
(Method)
• Có cú pháp của khai báo hàm
• Chỉ định các thao tác của lớp
• Một hoạt động, hành vi của đối tượng
• Đối tượng là “hộp đen” nhận và gửi thông điệp (message) 325
Cơ bản về lớp
• Đối tượng của lớp
– Đối tượng là một thể hiện cụ thể của một lớp
– Đối tượng được khai báo sau định nghĩa lớp
• Khai báo đối tượng hoặc mảng, con trỏ, tham chiếu đến đối
tượng
• Ví dụ:
Point pt;
Point polygon[9];
Point *pt1, pt2, pt3;
• Chỉ dữ liệu của đối tượng mới được cấp phát vùng nhớ,
các hàm thành viên không có vùng nhớ riêng.
• Sử dụng toán tử “.” để truy nhập vào các thành phần của
đối tượng
– Chú ý: phạm vi truy nhập (public/private)

326
Cơ bản về lớp

327
Cơ bản về lớp
• Dữ liệu thành viên:
– Nên được khai báo với từ khoá private.
– Không được khởi tạo giá trị của dữ liệu thành phần trong định
nghĩa lớp.
class Employee // khai báo tên lớp
{
private: // từ khóa cho biết không thể truy nhập từ ngoài lớp
unsigned int EmpID =1; // sai
char EmpName[30];
float EmpSalary;
public: // từ khóa cho biết có thể truy nhập từ ngoài lớp
void AddEmployee();
};

328
Cơ bản về lớp
• Các hàm thành viên định nghĩa ở ngoài lớp
– Sử dụng toán tử ::
• “Liên kết” tên hàm thành viên với tên lớp
• Các lớp khác nhau có thể có các hàm trùng tên
– Định dạng để định nghĩa các hàm thành viên
Kiểutrảvề Tênlớp::Tênhàmthànhviên( )
{ …
}
– Không phụ thuộc vào hàm public hay private
• Các hàm thành viên định nghĩa bên trong lớp
– Không cần toán tử :: và tên lớp
– Trình biên dịch sẽ cố thực hiện inline
• Ở ngoài lớp, ta chỉ định inline với từ khoá inline

329
Ví dụ
• Viết chương trình nhập từ bàn phím chiều
dài và chiều rộng của một hình chữ nhật
và hiển thị diện tích và chu vi ra màn hình.
• Phương pháp lập trình hướng đối tượng là
phương pháp giải quyết các bài toán lập
trình thông qua các đối tượng, do đó bài
này bạn cần tạo ra một đối tượng thuộc
lớp Rectangle (hình chữ nhật) giống như
sau:

330
Ví dụ
Trong đó:
•length và width là hai thuộc tính (Dữ liệu thành
viên) chỉ chiều dài và chiều rộng của hình
chữ nhật.
•getInformation() là phương thức nhập dữ liệu
(Hàm thành viên) cho thuộc
tính length và width từ bàn phím.
•display() là phương thức dùng để hiển thị thông
tin về chu vi và diện tích của hình chữ nhật ra
màn hình.
•getArea() và getPerimeter() là hai phương thức trả
về diện tích và chu vi của hình chữ nhật, hai
phương thức này được dùng trong phương
thức display().
331
Ví dụ
#include <iostream> void display()
using namespace std; { cout << "Area: " << getArea()
class Rectangle { << endl;
public: double width; cout << "Perimeter: " <<
double length; getPerimeter() << endl; } };
void getInformation() int main()
{ cin >> width; {
cin >> length; } Rectangle r1;
double getArea() r1.getInformation();
{ return width * length; r1.display();
} return 0; }
double getPerimeter()
{ return (width + length)
* 2; }
332
Điền chỗ thiếu vào các ô trống

333
Bài tập 2

Hãy viết chương trình sử dụng class gồm các thuộc tính a,
b, c.
a) Viết hàm tạo gán a=5,b=10,c=15
b) Tính trung bình cộng của 3 số
c) Cập nhật 3 số a, b và c từ bàn phím

334
Hàm inline
• Hàm không phải thành viên:
– Dùng từ khóa inline trong khai báo hàm và dòng đầu định nghĩa
hàm
• Hàm thành viên:
– Đặt mã định nghĩa hàm BÊN TRONG định nghĩa lớp  tự động
inline
• Chỉ dùng cho những hàm rất ngắn
• Mã thực sự sẽ được chèn vào nơi gọi
– Bớt “chi phí” gọi hàm
– Hiệu quả hơn, nhưng chỉ với hàm ngắn!

335
Hàm thành viên nội tuyến
• Hàm inline:
– Cải thiện tốc độ thực thi
– Tốn bộ nhớ (dành cho mã lệnh) khi thực thi.
Cách 1:
class Point { class Point {
thêm int xVal, yVal; int xVal, yVal;
Từ public: Cách 2: public:
khóa void SetPt (int, int); void SetPt (int x, int y) {
inline void OffsetPt (int, int); Định xVal = x;
}; nghĩa yVal = y;
bên }
inline void Point::SetPt (int x, int y) {
trong void OffsetPt (int x, int y) {
xVal = x;
lớp xVal += x;
yVal = y;
yVal += y;
} }
…………… };
336
Truy cập các thành phần
(dữ liệu và hàm)
• Các chế độ truy cập
– private
• Chế độ truy cập mặc định
• Chỉ có thể truy xuất được bởi các thành viên của lớp
• Các hàm thành phần và các hàm bạn, lớp bạn có thể truy
cập được
– public
• Bất kỳ hàm nào trong chương trình xử lý đối tượng đó cũng
có thể truy cập được (có thể được truy xuất bởi tất cả các
thành phần sử dụng lớp)
– protected
• Sẽ được học sau. Hiện tại giống với private
• Dữ liệu mặc định là private để đảm bảo giấu kín
• Hàm mặc định là public để có thể dùng mọi nơi
337
Các hàm truy cập và
các hàm hữu dụng
• Các hàm truy cập
– public
– Đọc/xuất dữ liệu
– Các hàm xác định tính chất
• Kiểm tra các điều kiện
• Các hàm hữu dụng (các hàm giúp đỡ)
– private
– Hỗ trợ hoạt động của các hàm thành phần public
– Không dành cho người dùng trực tiếp

338
Sự đóng gói (encapsulation)
• Sự truy xuất đến đối tượng thông qua các thông điệp
(message) mà vẫn giữ các thuộc tính private dưới
dạng thông tin ẩn
• Làm cho việc truy xuất đến dữ liệu của các lớp từ bên
ngoài lớp bị giới hạn, trở nên không cần thiết hoặc
không thể thực hiện được
• Người dùng không cần quan tâm đến cấu trúc và hoạt
động bên trong của lớp, chỉ quan tâm giải quyết vấn đề
lớn hơn
• Mục tiêu:
– tạo bức tường không thể thâm nhập được để bảo vệ, tránh
những hư hại vô tình hoặc cố ý do những lỗi chúng ta mắc phải
– Dễ dàng cô lập lỗi  dễ dàng tìm kiếm và sữa chữa

339
Hàm tạo (Constructor)
• Dùng để định nghĩa và khởi tạo đối tượng cùng 1 lúc.
• (1) Có tên trùng với tên lớp,
• (2) Không cần khai báo kiểu cho hàm tạo
• (3) Không có kiểu trả về.
• Không gọi trực tiếp, sẽ được tự động gọi khi khởi tạo đt.
• Gán giá trị, cấp vùng nhớ cho các dữ liệu thành viên.
class Point { void main() {
int xVal, yVal; Point pt1(10,20);
public: pt1.OffsetPt(2,2);
Point (int x, int y) { ……..
xVal = x; yVal = y; // Khai báo nào là sai ?
} Point pt2;
Point pt3(); ???
void OffsetPt (int x, int y) {
Point pt4 = Point(5,5);
xVal += x; yVal += y;
Point *pt5 = new Point(5,5);
}
……….
}; }
340
Vì cách khai báo này ứng với hàm tạo không tham số
Định nghĩa lớp Set

341
Định nghĩa lớp Set

342
Hàm tạo
class Set { Mềm
class Point { dẻo
private:
int xVal, yVal;
int *elems; hơn
public:
int maxCard;
Point () // Hàm tạo mặc định int card;
{ xVal = 0; yVal = 0; } public:
Point (int x, int y) { Set(const int size) {
xVal = x; yVal = y; elems = new int[size];
} maxCard = size;
Point (float len, float angle) { card = 0;
xVal = (int) (len * cos(angle)); }
yVal = (int) (len * sin(angle)); ……………
} };
void OffsetPt (int , int ); … Không cần
void main() {
}; Set s1(100);
phải nhớ
void main() { Set s2(20); gọi hàm
Point p1; Set s3(1000); … EmptySet()
Point p2(10,20); } khi khởi tạo
Point p3(60.3, 3.14);
}
343
Hàm tạo
class Set { Mềm
class Point { dẻo
private:
int xVal, yVal;
int *elems; hơn
public:
int maxCard;
Point () // Hàm tạo mặc định int card;
{ xVal = 0; yVal = 0; } public:
Point (int x, int y) { Set(const int size) {
xVal = x; yVal = y; elems = new int[size];
} maxCard = size;
Point (float len, float angle) { card = 0;
xVal = (int) (len * cos(angle)); }
yVal = (int) (len * sin(angle)); ……………
} };
void OffsetPt (int , int ); … Không cần
void main() {
}; Set s1(100);
phải nhớ
void main() { Set s2(20); gọi hàm
Point p1; Set s3(1000); … EmptySet()
Point p2(10,20); } khi khởi tạo
Point p3(60.3, 3.14);
}
344
Hàm tạo
class Set { Mềm
class Point { dẻo
private:
int xVal, yVal;
int *elems; hơn
public:
int maxCard;
Point () // Hàm tạo mặc định int card;
{ xVal = 0; yVal = 0; } public:
Point (int x, int y) { Set(const int size) {
xVal = x; yVal = y; elems = new int[size];
} maxCard = size;
Point (float len, float angle) { card = 0;
xVal = (int) (len * cos(angle)); }
yVal = (int) (len * sin(angle)); ……………
} };
void OffsetPt (int , int ); … Không cần
void main() {
}; Set s1(100);
phải nhớ
void main() { Set s2(20); gọi hàm
Point p1; Set s3(1000); … EmptySet()
Point p2(10,20); } khi khởi tạo
Point p3(60.3, 3.14);
}
345
Hàm tạo
• Hàm tạo sao chép khởi tạo đối tượng dựa trên một đối
tượng khác thuộc cùng lớp.
• Mỗi lớp có một hàm tạo sao chép mặc định – hàm này
có một tham số là đối tượng cùng lớp.
• Ta có thể định nghĩa lại hàm tạo sao chép.
Date(Date& d)
• Ví dụ
void main()
{
Date d1(12,4,1997);
Date d2(d1); // hàm tạo sao chép mặc định
Date d3=d1; // hàm tạo sao chép mặc định
}

346
Hàm hủy
• Giải phóng 1 đối tượng trước khi nó được thu hồi.
• Cú pháp: Có tên trùng với tên lớp, có dấu ~ phía trước
• ~TenLop() { ……... }
• Không gọi trực tiếp, sẽ được tự động gọi khi hủy bỏ đối
tượng.
• Thu hồi vùng nhớ cho các dữ liệu thành viên là con trỏ.
class Set { Set TestFunct1(Set s1) {
private: Set *s = new Set(50);
int *elems; return *s;
Tổng cộng
int maxCard; }
có bao
int card; nhiêu lần
void main() {
public:
Set s1(40), s2(50); hàm hủy
Set(const int size) { …… }
~Set() { delete[] elems; }
s2 = TestFunct1(s1); được gọi ?
}
….
}; 4

347
Hàm hủy
• Giải phóng 1 đối tượng trước khi nó được thu hồi.
• Cú pháp: ~TenLop() { ……... }
• Không gọi trực tiếp, sẽ được tự động gọi khi hủy bỏ đối
tượng.
• Thu hồi vùng nhớ cho các dữ liệu thành viên là con trỏ.

class Set { Set TestFunct1(Set s1) {


private: Set *s = new Set(50);
int *elems; return *s;
Tổng cộng
int maxCard; }
có bao
int card; nhiêu lần
void main() {
public:
Set s1(40), s2(50); hàm hủy
Set(const int size) { …… }
~Set() { delete[] elems; }
s2 = TestFunct1(s1); được gọi ?
}
….
}; 4

348
Hàm tạo và hàm hủy mặc định
• Hàm tạo mặc định:
– Thường tạo hàm tạo và hàm hủy khi lớp có
thành phần cấp phát động
– Nếu lớp không có hàm tạo thì trình biên dịch
sẽ cung cấp 1 hàm tạo mặc định không đối.
Hàm này chỉ cấp phát bộ nhớ, không khởi tạo
dữ liệu.
– Nếu lớp có hàm tạo thì sẽ không có hàm tạo
mặc định mới được phát sinh.
• Hàm hủy mặc định tương tự như trên.
349
Đối số mặc định

class Point { class Point {


int xVal, yVal; int xVal, yVal;
public: public:
Point (){}; Point (int x = 0, int y = 0);
Point (int x = 0, int y = 0); Point (float x=0, float y=0, int i=0);
//... //...
}; }; Tối nghĩa
Mơ hồ
void main() { void main() {
Point p1; // như là ??? Point p2(1.6, 5.0); // như là ???
Point p2(10); // như là ??? Point p3(10,20, 10); // như là ???
Point p3(10,20); Point p4; // ?????
Point p4(, 20); // ????? …..
….. }
}
350
Cho biết kết quả?
class Point {
int xVal, yVal;
public:
Point(int x = 0, int y = 0){
xVal = x ; yVal = y ;
}
void Print(){
cout<<" ("<<xVal <<","<<yVal<<" )";
}
};
void main() {
Point pt(5);
pt.Print();
}

Đáp án (5,0)
351
Đối số mặc định

class Point { class Point {


int xVal, yVal; int xVal, yVal;
public: public:
Point (){}; Point (int x = 0, int y = 0);
Point (int x = 0, int y = 0); Point (float x=0, float y=0, int i=0);
//... //...
}; }; Tối nghĩa
Mơ hồ
void main() { void main() {
Point p1; // như là ??? Point p2(1.6, 5.0); // như là ???
Point p2(10); // như là ??? Point p3(10,20, 10); // như là ???
Point p3(10,20); Point p4; // ?????
Point p4(, 20); // ????? …..
….. }
}
352
Đối số thành viên ẩn
• Con trỏ *this:
– Là 1 thành viên ẩn, có thuộc tính là private.
– Trỏ tới chính bản thân đối tượng.
void Point::OffsetPt (int x, int y) { void Point::OffsetPt (int x, int y) {
xVal += x; this->xVal += x;
yVal += y; this->yVal += y;
} }

• Có những trường hợp sử dụng *this là dư thừa (Ví dụ trên)


• Tuy nhiên, có những trường hợp phải sử dụng con trỏ *this

353
Đối số thành viên ẩn
• Con trỏ *this:
– Là 1 thành viên ẩn, có thuộc tính là private.
– Trỏ tới chính bản thân đối tượng.
void Point::OffsetPt (int x, int y) { void Point::OffsetPt (int x, int y) {
xVal += x; this->xVal += x;
yVal += y; this->yVal += y;
} }

• Có những trường hợp sử dụng *this là dư thừa (Ví dụ trên)


• Tuy nhiên, có những trường hợp phải sử dụng con trỏ *this

354
Sử dụng con trỏ this
• Các lời gọi hàm thành phần liên tiếp
– Nhiều hàm được gọi trong cùng 1 lệnh
– Hàm trả về 1 tham chiếu đến cùng đối tượng
đó
{ return *this; }
– Các hàm khác hoạt động trên con trỏ này
– Hàm không trả về tham chiếu phải được gọi
sau cùng

355
Sử dụng con trỏ this
1. #include <iostream>
2. class Time
3. { int hour, minute, second ;
4. public:
5. Time( int = 0, int = 0, int = 0 ); // default constructor
6. Time &setTime(int,int,int); // set hour,minute,second
7. Time &setHour( int ); // set hour
8. Time &setMinute( int ); // set minute
9. Time &setSecond( int ); // set second
10. int getHour() const; // return hour
11. int getMinute() const; // return minute
12. int getSecond() const; // return second
13. void printUniversal() const; // print universal time
14. void printStandard() const; // print standard time
15. }; // end class Time
356
16. Time::Time( int hr, int min, int sec )
17. { setTime( hr, min, sec );
18. } // end Time constructor
19. Time &Time::setTime( int h, int m, int s )
20. { setHour( h ); setMinute( m ); setSecond( s );
21. return *this; // enables cascading
22. } // end function setTime
23. Time &Time::setHour( int h )
24. { hour = ( h >= 0 && h < 24 ) ? h : 0;
25. return *this; // enables cascading
26. } // end function setHour
27. Time &Time::setMinute( int m )
28. { minute = ( m >= 0 && m < 60 ) ? m : 0;
29. return *this; // enables cascading
30. } // end function setMinute
357
31. Time &Time::setSecond( int s )
32. { second = ( s >= 0 && s < 60 ) ? s : 0;
33. return *this; // enables cascading
34. } // end function setSecond
35. int Time::getHour() const
36. { return hour;
37. } // end function getHour
38. int Time::getMinute() const
39. { return minute;
40. } // end function getMinute
41. int Time::getSecond() const
42. { return second;
43. } // end function getSecond
44. void Time::printUniversal() const
45. { cout << hour << ":“ << minute << ":“ << second;
46. } // end function printUniversal 358
47. void Time::printStandard() const
48. { cout << ((hour == 0 || hour == 12 ) ? 12 : hour % 12 )
49. << ":" << minute << ":" << second
50. << ( hour < 12 ? " AM" : " PM" );
51. } // end function printStandard
52. void main()
53. { Time t;
54. t.setHour( 18 ).setMinute( 30 ).setSecond( 22 );
55. cout << "Universal time: ";
56. t.printUniversal();
57. cout << "\nStandard time: ";
58. t.printStandard();
59. cout << "\n\nNew standard time: ";
60. t.setTime( 20, 20, 20 ).printStandard();
61. cout << endl;
62. } // end main 359
Bạn (Friend) – Đặt vấn đề
• Đôi khi chúng ta cần cấp quyền truy xuất cho một hàm
tới các thành viên không là các thành viên chung của
một lớp. Một truy xuất như thế được thực hiện bằng
cách khai báo hàm như là bạn của lớp.
• - Nếu một thành viên của lớp được quy định là private
hoặc protected thì chỉ có các hàm thành viên của lớp
mới có quyền truy cập đến nó.
• - Nếu một phương thức không phải là thành viên của lớp
muốn truy cập đến, thì nó phải là hàm bạn của lớp đó.
• - Phương thức bạn có thể được khai báo nhờ từ khóa
friend

360
Bạn (Friend) – Đặt vấn đề
Tập Các Hàm SetToReal
Số Nguyên dùng để chuyển
tập số nguyên
thành tập số thực
class IntSet {
public: void IntSet::SetToReal (RealSet &set) {
//... set.card = card;
private: for (register i = 0; i < card; ++i)
int elems[maxCard]; set.elems[i] = (float) elems[i];
int card; }
};
class RealSet {
public: Làm thế nào
//... để thực hiện
Tập Các private: được việc truy
Số Thực float elems[maxCard]; xuất
int card; đến thành viên
}; Private ?

361
Bạn (Friend) – Đặt vấn đề
Tập Các Hàm SetToReal
Số Nguyên dùng để chuyển
tập số nguyên
thành tập số thực
class IntSet {
public: void IntSet::SetToReal (RealSet &set) {
//... set.card = card;
private: for (register i = 0; i < card; ++i)
int elems[maxCard]; set.elems[i] = (float) elems[i];
int card; }
};
class RealSet {
public: Làm thế nào
//... để thực hiện
Tập Các private: được việc truy
Số Thực float elems[maxCard]; xuất
int card; đến thành viên
}; Private ?

362
Bạn (Friend)
• Hàm bạn:
– Có quyền truy xuất đến tất cả các dữ liệu và
hàm thành viên (protected + private) của 1 lớp.
– Lý do:
• Cách định nghĩa hàm chính xác.
• Hàm cài đặt không hiệu quả.
• Lớp bạn:
– Tất cả các hàm trong lớp bạn: là hàm bạn.
class A; class IntSet { ……….. }
class B { // ………. class RealSet { // ……….
friend class A; friend class IntSet;
}; };
363
Các hàm thành viên của lớp A đều là bạn của lớp B
Hàm bạn và lớp bạn
• Tính chất của tình bạn
– Tình bạn được cấp, không tự dưng có được
• Lớp B là friend của lớp A
– Lớp A phải khai báo rõ ràng lớp B là friend
– Không đối xứng
• Lớp B là friend của lớp A
• Lớp A không nhất thiết là friend của lớp B
– Không bắt cầu
• Lớp A là friend của lớp B
• Lớp B là friend của lớp C
• Lớp A không nhất thiết là friend của lớp C

364
Hàm bạn (Friend)
• Cách 1 - Dùng từ khoá friend để xây dựng hàm trong
định nghĩa lớp.
• Cú pháp class A
{
private:
// Khai báo các thuộc tính
public: ...
// Xây dựng các hàm bạn của lớp A
friend void f1(...) { ... }
friend double f2(...) { ... }
friend A f3(...) { ... } ... } ;
365
Hàm bạn (Friend)
• Cách 1: Khai báo hàm thành viên của lớp
IntSet là bạn (friend) của lớp RealSet.
class IntSet {
Giữ nguyên định public:
nghĩa của lớp IntSet //...
private:
int elems[maxCard];
int card;
void SetToReal (RealSet &);
};
class RealSet {
Thêm dòng khai báo public:
Friend cho //...
hàm thành viên friend void IntSet::SetToReal (RealSet &);
SetToReal private:
float elems[maxCard];
int card;
}; 366
HÀM BẠN (Friend Function)
• Dùng từ khoá friend để khai báo hàm trong lớp và xây
dựng hàm bên ngoài như các hàm thông thường (không
dùng từ khoá friend).
• Ví dụ một lớp A có ba hàm bạn:
class A { // Xây dựng các hàm f1, f2, f3
void f1(...) { ... }
private: double f2(...) { ... }
// Khai báo các thuộc tính A f3(...) { ... }
public: ...
// Khai báo các hàm bạn của lớp A
friend void f1(...);
friend double f2(...);
friend A f3(...) ; ... } ;
367
HÀM BẠN (Friend Function)
• Hàm bạn của nhiều lớp
• Một hàm là bạn của nhiều lớp, thì nó có quyền truy
nhập tới tất cả các thuộc tính của các đối tượng trong
các lớp này.
// Định nghĩa lớp B
class A; // Khai báo trước lớp A
class B{
class B; // Khai báo trước lớp B
// Khai báo f là bạn của B
class C; // Khai báo trước lớp C
friend void f(...) ; } ; /
// Định nghĩa lớp A
/ Định nghĩa lớp C
class A {
class C{
// Khai báo f là bạn của A
// Khai báo f là bạn của C
friend void f(...) ;
friend void f(...) ; };
};
// Xây dựng hàm f
void f(...) { ... }

368
Hàm bạn (Friend)
• Cách 2:
– Chuyển hàm SetToReal ra ngoài (độc lập).
– Khai báo hàm đó là bạn của cả 2 lớp.
class IntSet {
public: void SetToReal (IntSet& iSet,
//... RealSet& rSet )
friend void SetToReal (IntSet &, RealSet&);
private: {
int elems[maxCard]; rSet.card = iSet.card;
int card; for (int i = 0; i < iSet.card; ++i)
}; rSet.elems[i] =
class RealSet { (float) iSet.elems[i];
public:
//... }
friend void SetToReal (IntSet &, RealSet&);
private: Hàm độc lập
float elems[maxCard]; là bạn(friend)
int card;
}; của cả 2 lớp.
369
Hàm bạn (Friend)
• Cách 2:
– Chuyển hàm SetToReal ra ngoài (độc lập).
– Khai báo hàm đó là bạn của cả 2 lớp.
class IntSet {
public: void SetToReal (IntSet& iSet,
//... RealSet& rSet )
friend void SetToReal (IntSet &, RealSet&);
private: {
int elems[maxCard]; rSet.card = iSet.card;
int card; for (int i = 0; i < iSet.card; ++i)
}; rSet.elems[i] =
class RealSet { (float) iSet.elems[i];
public:
//... }
friend void SetToReal (IntSet &, RealSet&);
private: Hàm độc lập
float elems[maxCard]; là bạn(friend)
int card;
}; của cả 2 lớp.
370
HÀM BẠN (Friend Function)
• Tính chất của hàm bạn
• - Trong thân hàm bạn của một lớp có thể truy nhập tới
các thuộc tính (kể cả thuộc tính riêng) của các đối tượng
thuộc lớp này. Đây là sự khác nhau duy nhất giữa hàm
bạn và hàm thông thường.
• - Chú ý rằng hàm bạn không phải là phương thức của
lớp. Phương thức của lớp có một đối ẩn (ứng với con trỏ
this) và lời gọi của phương thức phải gắn với một đối
tượng nào đó (địa chỉ đối tượng này được truyền cho
con trỏ this). Lời gọi của hàm bạn giống như lời gọi của
hàm thông thường.
•  Lý do dùng hàm bạn
 Cách định nghĩa hàm chính xác.
 Hàm cài đặt không hiệu quả.
371
Hàm bạn
class <Tên lớp>{
// Khai báo các thành phần lớp như thông thường
// Khai báo hàm bạn
friend <Kiểu trả về> <Tên hàm bạn>([<Các tham số>]);
};

Khi đó, định nghĩa chi tiết hàm bạn được thực hiện như
định nghĩa một hàm tự do thông thường:
<Kiểu trả về> <Tên hàm bạn>([<Các tham số>]){
// Có thể truy nhập trực tiếp các thành phần private
// của lớp đã khai báo
}
372
Hàm bạn
• Một hàm tự do là hàm bạn của một lớp
– Mặc dù hàm bạn được khai báo khuôn mẫu hàm trong phạm vi
lớp, nhưng hàm bạn tự do lại không phải là một phương thức
của lớp. Nó là hàm tự do, việc định nghĩa và sử dụng hàm này
hoàn toàn tương tự như các hàm tự do khác.
– Việc khai báo khuôn mẫu hàm bạn trong phạm vi lớp ở vị trí
nào cũng được: hàm bạn không bị ảnh hưởng bởi các từ khóa
private, protected hay public trong lớp.
– Trong hàm bạn, có thể truy nhập trực tiếp đến các thành phần
private và protected của đối tượng có kiểu lớp mà nó làm bạn
(truy nhập thông qua đối tượng cụ thể).

373
Hàm bạn
#include <iostream> int HienThiChieuDai(HinhChuNhat hcn) {
using namespace std; hcn.chieuDai += 10;
class HinhChuNhat { return hcn.chieuDai;
private: }
int chieuDai;
int chieuRong; int main() {
public: HinhChuNhat hcn = HinhChuNhat(10,
HinhChuNhat(int chieuDai, int 20);
chieuRong) { cout << "Chieu dai cua hinh chu nhat la:
this->chieuDai = chieuDai; " << HienThiChieuDai(hcn)<<endl;
this->chieuRong = chieuRong; return 0;
} }
HinhChuNhat(): chieuDai(0) { }
friend int
HienThiChieuDai(HinhChuNhat); //friend
function
};

Đáp án: 20
374
Ví dụ về lớp bạn

375
Hàm thành viên và hàm độc lập
class SP class SP
{ double r; { double r;
double i; double i;
public: public:
SP cong(SP s2) friend SP cong(SP s1,SP s2)
{ SP temp; { SP temp;
temp.r = r+ s2.r ; temp.r = s1.r+ s2.r ;
temp.i = i+ s2.i ; temp.i = s1.i+ s2.i ;
return temp; return temp;
} }
}; };
Cách dùng: Cách dùng:
SP s, s1, s2; SP s, s1, s2;
s=s1.cong(s2); s=cong(s1,s2);
376
1. // Use a friend function.
2. #include <iostream>
3. const int IDLE=0;
4. const int INUSE=1;
5. class C2; // note : forward declaration
6. class C1
7. { int status; // IDLE if off, INUSE if on screen
8. // ...
9. public:
10. void set_status(int state);
11. friend int idle(C1 a, C2 b);
12. };
13.class C2
14. { int status; // IDLE if off, INUSE if on screen
15. // ...
16. public:
17. void set_status(int state);
18. friend int idle(C1 a, C2 b); 377

19. };
20.void C1::set_status(int state) { status = state; }
21.void C2::set_status(int state) { status = state; }
22.// idle( ) is a friend of C1 and C2.
23.int idle(C1 a, C2 b)
24. { if(a.status || b.status) return 0; else return 1; }
25.void main()
26. { C1 x;
27. C2 y;
28. x.set_status(IDLE);
29. y.set_status(IDLE);

30. if(idle(x, y)) cout << "Screen Can Be Used.\n";


31. else cout << "Pop-up In Use.\n";

32. x.set_status(INUSE);

33. if(idle(x, y)) cout << "Screen Can Be Used.\n";


34. else cout << "Pop-up In Use.\n"; 378

35. }
Hàm bạn và lớp bạn
• Chú ý: Hàm bạn không có con trỏ this
• Lợi ích của hàm bạn và lớp bạn:
– Quá tải toán tử
• Sẽ được học sau
– Đơn giản một vài hàm I/O
• Sẽ được học sau
– Các thành phần của 2 lớp (hoặc nhiều hơn)
có liên quan đến phần khác của chương trình
• Ví dụ: Hàm print cần xuất thông tin của 2 đối
tượng thuộc 2 lớp khác nhau
379
Hàm bạn và lớp bạn
• Thuận lợi:
– Cung cấp một sự tự do trong việc thiết kế giao diện
• Khó khăn:
– Đi ngược lại nguyên lý OOP
• Phá vỡ encapsulation
• Có thể hạn chế sự vi phạm tính toàn vẹn trong một
chừng mực nào đó
– Khi 1 hàm là hàm bạn của 1 lớp, nó có thể truy cập
dữ liệu của lớp đó. Người lập trình cần biết mã nguồn
 hạn chế thay đổi lớp đó đến mức có thể.
– Không lạm dụng friend

380
Toán tử new
• Xem xét lệnh sau
Time *timePtr;
timePtr = new Time;
• Toán tử new
• Tạo đối tượng với kích thước vùng nhớ kiểu Time
– Thành công : trả về con trỏ trỏ đến vùng nhớ đó
– Lỗi (nếu không đủ bộ nhớ, …) : trả về 0
• Gọi hàm tạo mặc định cho đối tượng
• Trả về con trỏ kiểu Time
• Cung cấp bộ khởi tạo
double *ptr = new double( 3.14159 );
Time *timePtr = new Time( 12, 0, 0 );
• Cấp phát mảng
int *gradesArray = new int[ 10 ];
381
Toán tử delete
• Giải phóng vùng nhớ được cấp phát động cho đối tượng
– Gọi hàm hủy cho đối tượng
– Giải phóng vùng nhớ dành cho đối tượng
• Vùng nhớ đó có thể dùng để cấp phát lại cho các
đối tượng khác
– Giải phóng mảng
• delete [] gradesArray;
• Giải phóng mảng gradesArray đang trỏ tới
• Nếu là mảng đối tượng
• Đầu tiên gọi hàm hủy cho từng đối tượng
• Sau đó giải phóng vùng nhớ
• Ví dụ
delete timePtr;
382
Toán tử phạm vi
• Toán tử :: dùng để xác định chính xác hàm
(thuộc tính) được truy xuất thuộc lớp nào.
• Câu lệnh: pt.OffsetPt(2,2);
<=> pt.Point::OffsetPt(2,2);
• Cần thiết trong một số trường hợp:
– Cách gọi hàm trong thừa kế.
– Tên thành viên bị che bởi biến cục bộ.
Ví dụ: Point(int xVal, int yVal) {
Point::xVal = xVal;
Point::yVal = yVal;
}
383
Mảng các đối tượng
• Sử dụng hàm tạo không đối số (hàm tạo mặc
nhiên - default constructor).
VD: Point pentagon[5];
• Sử dụng bộ khởi tạo mảng:
VD: Point triangle[3] =
{ Point(4,8), Point(10,20), Point(35,15) };
Ngắn gọn:
Set s[4] = { 10, 20, 30, 40 };
tương đương với:
Set s[4] = { Set(10), Set(20), Set(30), Set(40) };

384
Mảng các đối tượng
• Sử dụng dạng con trỏ:
– Cấp vùng nhớ:
VD: Point *pentagon = new Point[5];
– Thu hồi vùng nhớ:
delete[] pentagon;
delete pentagon; // Thu hồi vùng nhớ đầu
class Polygon {
public: Không cần biết kích
//... thước mảng.
private:
Point *vertices; // các đỉnh
int nVertices; // số các đỉnh
};

385
Mảng các đối tượng
• Sử dụng dạng con trỏ:
– Cấp vùng nhớ:
VD: Point *pentagon = new Point[5];
– Thu hồi vùng nhớ:
delete[] pentagon;
delete pentagon; // Thu hồi vùng nhớ đầu
class Polygon {
public: Không cần biết kích
//... thước mảng.
private:
Point *vertices; // các đỉnh
int nVertices; // số các đỉnh
};

386
Mảng các đối tượng
• Sử dụng dạng con trỏ:
– Cấp vùng nhớ:
VD: Point *pentagon = new Point[5];
– Thu hồi vùng nhớ:
delete[] pentagon;
delete pentagon; // Thu hồi vùng nhớ đầu
class Polygon {
public: Không cần biết kích
//... thước mảng.
private:
Point *vertices; // các đỉnh
int nVertices; // số các đỉnh
};

387
Ví dụ mảng các đối tượng
• Nhập điểm số lượng sinh viên từ bàn phím
• Nhập mã sv, điểm số từng sinh viên
• Tính điểm trung bình
• Gợi ý:
Tạo class Sinhvien
void getinfo ()
void display ()
stulist[i].getinfo(); // ten-mang-doi-tuong[chi-
so].hamthanhvien()
total = total + stulist[i].getmarks();
// hàm main
Student stulist[100];
388
float total, average;
Ví dụ mảng các đối tượng
int main()
#include <iostream> {
using namespace std; Student stulist[100];
class Student float total, average;
{ int no, i;
private: int rollno;
int marks; total = 0.0;
public: cout << "\n Nhap vao so luong sinh vien: ";
void getinfo () cin >> no ;
{cout << "Nhap vao ma so sinh vien:
";
for (i=0; i< no ; i++)
cin >> rollno ;
{
cout << "Nhap vao diem: ";
stulist[i].getinfo();
cin >> marks ;
total = total + stulist[i].getmarks();
} //ket thuc ham getinfo
}
void display ()
cout << "Ma so Diem"<< endl;
{cout<<rollno<<'\t'<<marks<< endl;
for (i=0; i< no; i++)
} // ket thuc ham display
{
stulist[i].display();
int getmarks()
}
{return marks;
average = total / no;
} // ket thuc ham getmarks
cout << "Diem trung binh cac sinh vien = " <<
average;
};// ket thuc lop Student
}// ket thuc ham main
389
Phạm vi lớp
• Thành viên trong 1 lớp:
– Che các thực thể trùng tên trong phạm vi.
// ………
int fork (void); // fork hệ thống
class Process {
int fork (void); // fork thành viên fork thành viên
//... che đi fork toàn cục
}; trong phạm vi lớp
Process
// ………
int Process::func1 (void)
{ int x = fork(); // gọi fork cục bộ
int pid = ::fork(); // gọi hàm fork hệ thống
//...
}

390
Phạm vi lớp
• Thành viên trong 1 lớp:
– Che các thực thể trùng tên trong phạm vi.
// ………
int fork (void); // fork hệ thống
class Process {
int fork (void); // fork thành viên fork thành viên
//... che đi fork toàn cục
}; trong phạm vi lớp
Process
// ………
int Process::func1 (void)
{ int x = fork(); // gọi fork cục bộ
int pid = ::fork(); // gọi hàm fork hệ thống
//...
}

391
Phạm vi lớp
• Lớp toàn cục: đại đa số lớp trong C++.
• Lớp lồng nhau: lớp chứa đựng lớp.
• Lớp cục bộ: trong 1 hàm hoặc 1 khối.
class Rectangle { // Lớp lồng nhau void Render (Image &i)
public: {
Rectangle (int, int, int, int); class ColorTable {
//.. public:
private: ColorTable () { /* ... */ }
class Point { AddEntry (int r, int g, int b)
public: { /* ... */ }
Point(int a, int b) { … } //...
private:
int x, y; };
}; ColorTable colors;
Point topLeft, botRight; //...
}; }
Rectangle::Point pt(1,1); // sd ở ngoài ColorTable ct; // SAI
392
Cấu trúc và hợp
• Cấu trúc (structure):
– Bắt nguồn từ ngôn ngữ C.
– Tương đương với class với các thuộc tính là public.
– Sử dụng như class.

struct Point { class Point {


Point (int, int); public:
Point(int, int);
void OffsetPt(int, int);
void OffsetPt(int, int);
int x, y; int x, y;
}; };

Point p = { 10, 20 }; Có thể khởi tạo dạng này


nếu không có định nghĩa
hàm xây dựng
393
Cấu trúc và hợp
• Hợp (union):
– Tất cả thành viên ánh xạ đến cùng 1 địa chỉ bên trong đối tượng
chính nó (không liên tiếp).
– Kích thước = kích thước của dữ liệu lớn nhất.
union Value { class Object {
long integer; private:
double real; enum ObjType {intObj, realObj,
char *string; strObj, listObj};
Pair list; ObjType type; // kiểu đối tượng
//... Value val; // giá trị của đối tượng
}; //...
};
class Pair {
Value *head;
Value *tail;
//... Kích thước của Value là
}; 8 bytes = sizeof(double)
394
Các trường bit
• Điều khiển đối tượng ở mức bit.
VD: Truy xuất các bit trong header của gói tin.
typedef unsigned int Bit;
class Packet {
Bit type : 2; // rộng 2 bit
Bit acknowledge : 1; // rộng 1 bit
Bit channel : 4; // rộng 4 bit
Bit sequenceNo : 4; // rộng 4 bit
Bit moreData : 1; // rộng 1 bit
//...
}; // …………
enum PacketType { dataPacket, controlPacket, Packet p;
supervisoryPacket }; p.type = controlPacket;
enum Bool { false, true }; p.acknowledge = true;

395
Danh sách khởi tạo thành viên
Member Initialization List

• Tương đương việc gán giá trị dữ liệu thành viên.


class Point { class Image {
int xVal, yVal; public:
Image(const int w, const int h);
public: private:
Point (int x, int y) { int width;
xVal = x; int height;
yVal = y; //...
} };
Image::Image(const int w, const int h) {
// ……………………
width = w;
}; height = h;
//.....................
}
Point::Point (int x, int y)
: xVal(x), yVal(y)
Image::Image (const int w, const int h)
{ } : width(w), height(h)
{ //............... }
396
Danh sách khởi tạo thành viên
• Tương đương việc gán giá trị dữ liệu thành viên.
class Point { class Image {
int xVal, yVal; public:
Image(const int w, const int h);
public: private:
Point (int x, int y) { int width;
xVal = x; int height;
yVal = y; //...
} };
Image::Image(const int w, const int h) {
// ……………………
width = w;
}; height = h;
//.....................
}
Point::Point (int x, int y)
: xVal(x), yVal(y)
Image::Image (const int w, const int h)
{ } : width(w), height(h)
{ //............... }
397
Thành viên hằng (Constant
Members)
• Hằng dữ liệu thành viên:
class Image {
public:
Image(const int w, const int h); Khai báo bình thường
private: như dữ liệu thành viên
const int width;
const int height;
//...
};

class Image {
const int width = 256;
Khởi tạo const int height = 168;
SAI //...
};

Image::Image (const int w, const int h) Khởi tạo ĐÚNG


: width(w), height(h) thông qua danh sách
{ //................ } khởi tạo thành viên 398
Thành viên hằng
• Hằng đối tượng: không được thay đổi giá trị.
• Hàm thành viên hằng:
– Được phép gọi trên hằng đối tượng.
– Không được thay đổi giá trị dữ liệu thành viên.
class Set { void main() {
public: const Set s;
Set(void){ card = 0; } s.AddElem(10); // SAI
bool Member(const int) const;
s.Member(10); // ok
void AddElem(const int);
//...
}; }
bool Set::Member (const int elem) const
{ //...
}

399
Thành viên tĩnh (Static
Members)
• Dữ liệu thành viên tĩnh:
– Dùng chung 1 bản sao chép (1 vùng nhớ) chia sẻ
cho tất cả đối tượng của lớp đó.
– Sử dụng: <TênLớp>::<TênDữLiệuThànhViên>
– Thường dùng để đếm số lượng đối tượng.
class Window {
// danh sách liên kết tất cả Window
static Window *first; Khai báo
// con trỏ tới window kế tiếp
Window *next;
//...
}; Khởi tạo
dữ liệu
Window *Window::first = &myWindow; thành viên
// ……………. tĩnh
400
Thành viên tĩnh
• Dữ liệu thành viên tĩnh
– Dữ liệu “cấp độ lớp”
• Thuộc tính của lớp, không phải của 1 đối tượng
nào đó của lớp.
– Hiệu quả khi chỉ cần 1 biến cho lớp là đủ
– Có vẻ như biến toàn cục, nhưng chỉ có phạm vi lớp
• Chỉ các đối tượng thuộc lớp đó mới có thể truy cập
– Cần khởi tạo 1 lần duy nhất
– Tồn tại ngay cả khi không có đối tượng nào của lớp
đó tồn tại, không phụ thuộc vào bất kỳ đối tượng nào
– Có thể là public, private hoặc protected

401
Thành viên tĩnh
• Hàm thành viên tĩnh:
– Tương đương với hàm toàn cục.
– Gọi thông qua: <TênLớp>::<TênHàm>
class Window {
Khai báo
// ……….
Định nghĩa
static void PaintProc (Event *event) { ….. }
hàm thành
// ………
viên tĩnh
};

void main() {
// …………….
Window::PainProc(); Truy xuất
hàm thành
} viên tĩnh

402
Thành viên tĩnh
• Hàm thành viên tĩnh
– Các hàm static không có con trỏ this
• Các dữ liệu và hàm thành viên static tồn
tại mà không phụ thuộc vào các đối tượng
– Có thể gọi hàm static ngay cả khi không
có đối tượng nào của lớp đó tồn tại
– Có thể sử dụng một hàm thành viên
static để truy vấn một đối tượng để tìm ra
nó là đối tượng nào.
– Có ích trong việc gỡ rối một chương trình
giữa những tình huống khác nhau
403
Ví dụ về thành viên tĩnh
#include <iostream>
using namespace std;
class Teacher {
public:
static int n;
public :
Teacher (){ cout<<" "<<n++ ; }
};
int Teacher::n = 0;
int main() {
Teacher t1;
Teacher t2;
Teacher t3;
cout<<" "<<t1.n ;
return 0;
} 404
Truy xuất các dữ liệu static
• Ví dụ:
class NGUOI
{ float tn; //thu nhập từng người
static int tongsn; //tổng số người có trong class
static float tongtn;//tổng thu nhập của các object
public:

} nguoi1, nguoi2;
• Truy xuất tongtn:
– nguoi1.tongtn
– nguoi2.tongtn
– NGUOI::tongtn // có thể dùng cả khi chưa có đối tượng nào
• nguoi1.tn, nguoi2.tn sẽ chiếm 2 vùng nhớ khác nhau
• nguoi1.tongtn, nguoi2. tongtn chiếm cùng 1 vùng nhớ
405
Thành phần static của lớp
1. #include <iostream>
2. #include <new.h> // C++ standard new operator
3. #include <string.h>
4. class Employee
5. { char *firstName;
6. char *lastName;
7. // static data member
8. static int count; // number of objects instantiated
9. public:
10. Employee( const char *, const char * ); // constructor
11. ~Employee(); // destructor
12. const char *getFirstName() const; // return first name
13. const char *getLastName() const; // return last name
14. // static member function
15. static int getCount(); // return # objects instantiated
16. }; // end class Employee 406
17. int Employee::count = 0;
18. int Employee::getCount()
19. { return count;
20. }
21. Employee::Employee( const char *first, const char *last )
22. { firstName = new char[ strlen( first ) + 1 ];
23. strcpy( firstName, first );
24. lastName = new char[ strlen( last ) + 1 ];
25. strcpy( lastName, last );
26. ++count; // increment static count of employees
27. cout << "Employee constructor for " << firstName
28. << ' ' << lastName << " called." << endl;
29. } // end Employee constructor
30. Employee::~Employee()
31. { cout << "~Employee() called for " << firstName
32. << ' ' << lastName << endl;
33. delete [] firstName; delete [] lastName;
34. --count; // decrement static count of employees
35. } 407
36. const char *Employee::getFirstName() const
37. { // const before return type prevents client from
// modifying private data; client should copy returned
// string before destructor deletes storage to prevent
// undefined pointer
38. return firstName;
39. } // end function getFirstName
40. const char *Employee::getLastName() const
41. { // const before return type prevents client from
// modifying private data; client should copy returned
// string before destructor deletes storage to prevent
// undefined pointer
42. return lastName;
43. } // end function getLastName

408
void main()
{ cout << "Number of employees before instantiation is "
<< Employee::getCount() << endl; // use class name
Employee *e1Ptr = new Employee( "Susan", "Baker" );
Employee *e2Ptr = new Employee( "Robert", "Jones" );
cout << "Number of employees after instantiation is "
<< e1Ptr->getCount();
cout << "\n\nEmployee 1: “ << e1Ptr->getFirstName()
<< " " << e1Ptr->getLastName()
<< "\nEmployee 2: “ << e2Ptr->getFirstName()
<< " " << e2Ptr->getLastName() << "\n\n";
delete e1Ptr; // recapture memory
e1Ptr = 0; // disconnect pointer from free-store space
delete e2Ptr; // recapture memory
e2Ptr = 0; // disconnect pointer from free-store space
cout << "Number of employees after deletion is "
<< Employee::getCount() << endl;
409
} // end main
• Number of employees before instantiation is 0
• Employee constructor for Susan Baker called.
• Employee constructor for Robert Jones called.
• Number of employees after instantiation is 2

• Employee 1: Susan Baker
• Employee 2: Robert Jones

• ~Employee() called for Susan Baker
• ~Employee() called for Robert Jones
• Number of employees after deletion is 0

410
Thành viên tham chiếu
• Tham chiếu dữ liệu thành viên:
class Image {
int width; Khai báo bình thường
int height; như dữ liệu thành viên
int &widthRef;
//...
};

class Image {
int width;
int height;
Khởi tạo
int &widthRef = width;
SAI
//...
};

Image::Image (const int w, const int h) Khởi tạo ĐÚNG


: widthRef(width) thông qua danh sách
{ //……………... } khởi tạo thành viên
411
Thành viên tham chiếu
• Tham chiếu dữ liệu thành viên:
class Image {
int width; Khai báo bình thường
int height; như dữ liệu thành viên
int &widthRef;
//...
};

class Image {
int width;
int height;
Khởi tạo
int &widthRef = width;
SAI
//...
};

Image::Image (const int w, const int h) Khởi tạo ĐÚNG


: widthRef(width) thông qua danh sách
{ //……………... } khởi tạo thành viên
412
Thành viên là đối tượng của 1 lớp
• Dữ liệu thành viên có thể có kiểu:
– Dữ liệu (lớp) chuẩn của ngôn ngữ.
– Lớp do người dùng định nghĩa (có thể là chính lớp đó).
class Point { ……. };
class Rectangle {
public:
Rectangle (int left, int top, int right, int bottom);
//... Khởi tạo cho các
private: dữ liệu thành viên
Point topLeft; qua danh sách khởi
Point botRight; tạo thành viên
};
Rectangle::Rectangle (int left, int top, int right, int bottom)
: topLeft(left,top), botRight(right,bottom)
{}

413
Thành viên là đối tượng của 1 lớp
• Dữ liệu thành viên có thể có kiểu:
– Dữ liệu (lớp) chuẩn của ngôn ngữ.
– Lớp do người dùng định nghĩa (có thể là chính lớp đó).
class Point { ……. };
class Rectangle {
public:
Rectangle (int left, int top, int right, int bottom);
//... Khởi tạo cho các
private: dữ liệu thành viên
Point topLeft; qua danh sách khởi
Point botRight; tạo thành viên
};
Rectangle::Rectangle (int left, int top, int right, int bottom)
: topLeft(left,top), botRight(right,bottom)
{}

414
Bài tập
1. tạo lớp điểm, nhập 3 điểm bất kỳ trong mặt phẳng,
tính chu vi, diện tích tam giác tạo ra từ 3 điểm đó.
2. Nhập dãy các điểm. Tìm:
– 3 điểm tạo tam giác có diện tích lớn nhất / nhỏ nhất
– 2 điểm xa nhau nhất / gần nhau nhất
3. Nhập mảng hình chữ nhật có diện tích / chu vi lớn
nhình chữ nhật, tìm hất.
4. tạo lớp danh sách : mã số, họ tên, thu nhập. Nhập
danh sách cán bộ, sắp xếp danh sách theo thu
nhập, xuất danh sách và tổng thu nhập
5. tạo lớp : họ tên, điểm toán, lý, hoá. Nhập thông tin
N thí sinh, sắp xếp theo tổng số điểm, xuất các thí
sinh trúng tuyển
6. Tìm số lớn nhất / nhỏ nhất trong mảng 1 chiều các
số nguyên
415
7. tạo lớp ngày gồm ngày, tháng và năm và các hàm:
– Hàm tạo với 3 tham số mặc định
– Hàm xuất thông tin ở dạng dd-mm-yy
– Hàm tăng / giảm từng ngày một
– Hàm tính khoảng cách giữa 2 ngày trong 1 năm
8. tạo lớp phân số với các phương thức nhập, xuất, rút gọn,
cộng trừ nhân chia, đảo dấu
9. tạo lớp đa thức với các phương thức nhập, xuất, cộng trừ
nhân, đảo dấu
10. Viết chương trình cộng trừ nhân các số nguyên rất lớn
11. tạo lớp số phức với các phương thức nhập, xuất, cộng trừ
nhân chia, đảo dấu, liên hiệp phức, luỹ thừa, khai căn,
xuất tọa độ cực
12. tạo lớp vector và ma trận.
13. Tìm phần tử lớn nhất / nhỏ nhất của từng hàng / cột của
ma trận
14. Tìm phần tử lớn nhất trong số n phần tử nhỏ nhất của n
cột của ma trận m * n
416
15. tạo lớp lý lịch
Chương 8. Quá tải
(Overloading)
Quá tải hàm
Quá tải toán tử

417
Qúa tải hàm (Function
Overloading)
• Định nghĩa các hàm cùng tên
• Đối số phải khác nhau:
– Số lượng – Kiểu
– Thứ tự
class Time { void main() {
//... int h, m, s;
long GetTime (void); // số giây tính từ nửa đêm long t = GetTime(); // Gọi hàm ???
void GetTime (int &hours, GetTime(h, m, s); // Gọi hàm ???
int &minutes, }
int &seconds);
};

• Có thể dùng đối số mặc định.


418
Qúa tải hàm
• Định nghĩa các hàm cùng tên
• Đối số phải khác nhau:
– Số lượng – Kiểu
– Thứ tự
class Time { void main() {
//... int h, m, s;
long GetTime (void); // số giây tính từ nửa đêm long t = GetTime(); // Gọi hàm ???
void GetTime (int &hours, GetTime(h, m, s); // Gọi hàm ???
int &minutes, }
int &seconds);
};

• Có thể dùng đối số mặc định.


419
Toán tử 1 ngôi
• Toán tử một ngôi (unary operator) hay còn gọi là toán tử
đơn, có thể được dùng làm toán tử trước (prefix operator)
và toán tử sau (postfix operator). Ví dụ phép tăng (++) hay
phép giảm (–)
• Ví dụ:
• prefix operator: ++A;
• postfix operator: A++;

420
Toán tử 2 ngôi

• Toán tử 2 ngôi hay còn gọi là toán tử


đôi (binary operator).
• Ví dụ: như A+B, A*B, hay toán tử
chỉ mục “[…]” cũng là toán tử đôi.

421
Quá tải toán tử
• Định nghĩa lại các phép toán trên đối tượng.
• Các phép toán có thể tái định nghĩa (quá tải):

+ - * ! ~ & ++ -- () -> ->*


Một ngôi
new delete

+ - * / % & | ^ << >>

Hai ngôi = += -= /= %= &= |= ^= <<= >>=

== != < > <= >= && || [] () ,

• Các phép toán không thể tái định nghĩa (quá tải):
. .* :: ?: sizeof
422
Quá tải toán tử
• Bằng hàm thành viên:
class Point {
public:
Point (int x, int y) { Point::x = x; Point::y = y; }
Point operator + (Point &p) { return Point(x + p.x,y + p.y); }
Point operator - (Point &p) { return Point(x - p.x, y - p.y); }
private:
int x, y;
};
Có 1 tham số
(Nếu là toán tử hai ngôi)
void main() {
Point p1(10,20), p2(30,40);
Point p3 = p1 + p2; Point p4 = p1 - p2;
Point p5 = p3.operator + (p4); Point p6 = p3.operator – (p4);
};

423
Quá tải toán tử
• Bằng hàm độc lập: thường khai báo friend
class Point {
public:
Point (int x, int y) { Point::x = x; Point::y = y; }
friend Point operator + (Point &p, Point &q)
{return Point(p.x + q.x,p.y + q.y); }
friend Point operator - (Point &p, Point &q)
{return Point(p.x - q.x,p.y - q.y); }
private:
int x, y; Có 2 tham số
}; (Nếu là toán tử hai ngôi)

void main() {
Point p1(10,20), p2(10,20);
Point p3 = p1 + p2; Point p4 = p1 - p2;
Point p5 =operator + (p3, p4); Point p6 = operator – (p3, p4);
};
424
Chuyển kiểu
• Muốn thực hiện các phép cộng:
void main() {
Point p1(10,20), p2(30,40), p3, p4, p5;
p3 = p1 + p2;
p4 = p1 + 5; p5 = 5 + p1;
};

Có thể định nghĩa thêm 2 toán tử:


class Point {
//...
friend Point operator + (Point, Point);
friend Point operator + (int, Point);
friend Point operator + (Point, int);
};

425
Chuyển kiểu (tt)
• Chuyển đổi kiểu: ngôn ngữ định nghĩa sẵn.
void main() {
Point p1(10,20), p2(30,40), p3, p4, p5;
p3 = p1 + p2;
p4 = p1 + 5; // tương đương p1 + Point(5)
p5 = 5 + p1; // tương đương Point(5) + p1
}

Định nghĩa phép chuyển đổi kiểu


class Point {
//... Chuyển kiểu
Point (int x) { Point::x = Point::y = x; } 5  Point(5)
friend Point operator + (Point, Point);
};

426
Quá tải toán tử xuất <<
• Định nghĩa hàm toàn cục:
ostream& operator << (ostream&, Class&);
class Point { void main() {
public: Point p1(10,20), p2;
Point (int x=0, int y=0) cout<<“Diem P1: “<< p1 << endl;
{ Point::x = x; Point::y = y; } cout<<“Diem P2: “<< p2 << endl;
friend ostream& operator << }
(ostream& os, Point& p)
{ os<< “(“ << p.x << “,” << p.y << “)”; }
// …..
private: Kết quả
int x, y; trên
}; màn hình ?

427
Quá tải toán tử nhập >>
• Định nghĩa hàm toàn cục:
istream& operator >> (istream&, Class&);
class Point { void main() {
public: Point p1, p2;
Point (int x=0, int y=0) cout<<“Nhap thong tin cho P1: \n“;
{ Point::x = x; Point::y = y; } cin>>p1;
friend istream& operator >> cout<<“Nhap thong tin cho P2: \n“;
(istream& is, Point& p) cin>>p2;
{ cout<<“Nhap x: “; is>>p.x; }
cout<<“Nhap y: “; is>>p.y;
}
// …..
private:
int x, y;
};
428
Quá tải toán tử [ ]
• Thông thường để xuất ra giá trị của 1 phần tử tại
vị trí cho trước trong đối tượng.
• Định nghĩa là hàm thành viên.
class StringVec { char* StringVec::operator [] (int i) {
public: if ( i>=0 && i<used) return elems[i];
StringVec (const int dim); return “”;
~StringVec (); }
char* operator [] (int);
int add(char* ); void main() {
// ……….. StringVec sv(100);
private: sv.add(“PTPhi”);sv.add(“BQThai”);
char **elems; // cac phan tu sv.add(“LVLam”); sv.add(“NCHuy”);
int dim; // kich thuoc cua vecto cout<< sv[2]<<endl;
int used; // vi tri hien tai cout<<sv[0];
}; }

429
Quá tải toán tử ()
• Định nghĩa là hàm thành viên.
class Matrix { double& Matrix::operator ()
public: (const short row, const short col)
Matrix (const short rows, const short cols); {
~Matrix (void) {delete []elems;} static double dummy = 0.0;
double& operator () (const short row, return (row >= 1 && row <= rows
const short col); && col >= 1 && col <= cols)
friend ostream& operator << (ostream&, Matrix&); ? elems[(row - 1)*cols
+ (col - 1)]
friend Matrix& operator + (Matrix&, Matrix&);
: dummy;
friend Matrix& operator - (Matrix&, Matrix&); }
friend Matrix& operator * (Matrix&, Matrix&); void main() {
private: Matrix m(3,2);
const short rows; // số hàng m(1,1) = 10; m(1,2) = 20;
const short cols; // số cột m(2,1) = 30; m(2,2) = 40;
double *elems; // các phần tử m(3,1) = 50; m(3,2) = 60;
}; cout<<m<<endl;
} 430
Quá tải toán tử ++ & --
• Toán tử ++ (hoặc toán tử --) có 2 loại:
– Tiền tố: ++n
– Hậu tố: n++
class PhanSo { void main() {
int tuso, mauso; PhanSo p1(3,4), p2;
public:
cout<< p1++;
// …..
PhanSo(int x=0 , int y=1) cout<<++p2;
{tuso=x; mauso=y;} cout<<++(p1++) + (++p2)++;
friend PhanSo operator ++ (PhanSo&); }
friend PhanSo operator ++ (PhanSo&, int);
};
PhanSo operator ++ (PhanSo& p) {
return (p = PhanSo(p.tuso+p.mauso, p.mauso));
}
PhanSo operator ++ (PhanSo& p, int notused) {
PhanSo p2 = PhanSo(p.tuso+p.mauso, p.mauso);
return p2; 431
}
Quá tải new & delete
• Hàm new và delete mặc định của ngôn ngữ:
– Nếu đối tượng kích thước nhỏ, có thể sẽ gây ra quá
nhiều khối nhỏ => chậm.
– Không đáng kể khi đối tượng có kích thước lớn.
=> Toán tử new và delete ít được quá tải.
• Định nghĩa theo dạng hàm thành viên:
class Point { void main() {
public: Point *p = new Point(10,20);
//... Point *ds = new Point[30];
void* operator new (size_t bytes); //………………
void operator delete (void *ptr, size_t bytes); delete p;
private: delete []ds;
int xVal, yVal; }
}; 432
Các toán tử được quá tải
• Tương tự như tái định nghĩa hàm thành viên:
– Che giấu đi toán tử của lớp cơ sở.
– Hàm tạo bản sao:
Y::Y (const Y&)
– Phép gán:
Y& Y::operator = (const Y&)
• Nếu không định nghĩa, sẽ tự động có hàm tạo
bản sao và phép gán do ngôn ngữ tạo ra.
=> SAI khi có con trỏ thành viên.
• Cẩn thận với toán tử new và delete.
433
Chương 9
Thừa kế
(Inheritance)

434
Khái niệm
• Kế thừa từ các lớp có từ trước.
• Ích lợi: có thể tận dụng lại
– Các thuộc tính chung
– Các hàm có thao tác tương tự
Lớp cơ sở LỚP CHA
STUDENT
(Base class) (Super class)

Lớp dẫn xuất LỚP CON


ENT_STUDENT
(Derived class) (Sub class)
435
Thuật ngữ
• inheritance: tính thừa kế
• derive: dẫn xuất / thừa kế
• base/parent class: lớp cơ sở / lớp cha
• derived/child class: lớp dẫn xuất / lớp con
• multiple inheritance: đa thừa kế

436
Khái niệm
• Kế thừa từ các lớp có từ trước.
• Ích lợi: có thể tận dụng lại
– Các thuộc tính chung
– Các hàm có thao tác tương tự
Lớp cơ sở LỚP CHA
STUDENT
(Base class) (Super class)

Lớp dẫn xuất LỚP CON


ENT_STUDENT
(Derived class) (Sub class)
437
Ưu điểm của việc kế thừa
– Tiết kiệm thời gian và công sức
– Tái sử dụng lại những lớp có sẵn
– Giảm lượng code phải thiết kế, viết, kiểm tra
– Tránh trùng lắp code
– Rút ngắn thời gian giúp LTV tập trung vào
mục tiêu
– Giúp phân loại và thiết kế lớp dễ dàng, dễ
quản lý

438
Cú pháp kế thừa
class TenLopCoSo
{
//Cac thanh vien cua lop co so
};
class TenLopDanXuat : public TenLopCoSo
{
//Cac thanh vien cua lop dan xuat
};
439
Cú pháp kế thừa
class TenLopCoSo
{
//Cac thanh vien cua lop co so
};
class TenLopDanXuat :
public/private/protected TenLopCoSo
{
//Cac thanh vien cua lop dan xuat
}; 440
Ví dụ minh họa
Ký hiệu n
composition ContactDir Contact

#include <iostream> class ContactDir {


#include <string.h> private:
class Contact { int Lookup(const char *name);
private:
Contact **contacts; // ds cac doi tac
char *name; // ten doi tac
int dirSize; // kich thuoc thu muc hien tai
char *address; // dia chi doi tac
char *tel; // so dien thoai int maxSize; // kich thuoc thu muc toi da
public: public:
Contact (const char *name, ContactDir (const int maxSize);
const char *address, const char *tel); ~ContactDir();
~Contact (); void Insert(const Contact&);
const char* Name () const { return name;} void Delete(const char *name);
const char* Address() const { return address;} Contact* Find(const char *name);
const char* Tel() const { return tel;} friend ostream& operator <<
friend ostream& operator << (ostream&, ContactDir&);
( ostream&, Contact& ); // …………
}; };
441
Ví dụ minh họa (tt)
n
ContactDir Contact
Ký hiệu
Thừa kế
SmartDir

class SmartDir : public ContactDir { Contact* SmartDir::Recent (void) {


private: return recent == 0 ? 0 :
char *recent; // ten duoc tim gan nhat ContactDir::Find(recent);
public: }
SmartDir(const int max) : ContactDir(max) Contact* SmartDir::Find (const char *name) {
{ recent = 0; } Contact *c = ContactDir::Find(name);
Contact* Recent (void); if (c != 0)
Contact* Find (const char *name); recent = (char*) c->Name();
// ……………. return c;
}; }
442
Ví dụ minh họa (tt)
n
ContactDir Contact
Ký hiệu
Thừa kế
SmartDir

class SmartDir : public ContactDir { Contact* SmartDir::Recent (void) {


private: return recent == 0 ? 0 :
char *recent; // ten duoc tim gan nhat ContactDir::Find(recent);
public: }
SmartDir(const int max) : ContactDir(max) Contact* SmartDir::Find (const char *name) {
{ recent = 0; } Contact *c = ContactDir::Find(name);
Contact* Recent (void); if (c != 0)
Contact* Find (const char *name); recent = (char*) c->Name();
// ……………. return c;
}; }
443
Ví dụ minh họa (tt)
n
ContactDir Contact
Ký hiệu
Thừa kế
SmartDir

class SmartDir : public ContactDir { Contact* SmartDir::Recent (void) {


private: return recent == 0 ? 0 :
char *recent; // ten duoc tim gan nhat ContactDir::Find(recent);
public: }
SmartDir(const int max) : ContactDir(max) Contact* SmartDir::Find (const char *name) {
{ recent = 0; } Contact *c = ContactDir::Find(name);
Contact* Recent (void); if (c != 0)
Contact* Find (const char *name); recent = (char*) c->Name();
// ……………. return c;
}; }
444
Ví dụ về kế thừa
#include <iostream> int main() {
//#include <bits/stdc++.h> lopCon obj1;
using namespace std; obj1.id_con = 10;
obj1.id_cha = 30;
//Lop Cha cout << "lopCon id is " << obj1.id_con
<< endl;
class lopCha {
cout << "lopCha id is " << obj1.id_cha
public:
<< endl;
int id_cha;
};
return 0;
}
// Lop con ke thua tu lop cha
class lopCon : public lopCha {
public:
int id_con;
}; 445
Phân loại
• Phân loại
– Đơn kế thừa (single inheritance): chỉ có một
lớp cha
– Đa kế thừa (multiple inheritance): có nhiều
lớp cha

446
Từ khóa chỉ định quyền truy cập: protected

• Phân loại mới cho thành viên của lớp


• Cho phép lớp dẫn xuất truy cập trực tiếp thành viên
của lớp cơ sở “bằng tên”
– Nhưng với những chỗ khác, chúng giống như private
• Trong lớp cơ sở  coi chúng là private
• Chúng được coi là "protected" trong lớp dẫn xuất
– Để cho phép các dẫn xuất cháu chắt
• Nhiều người cho rằng cơ chế này vi phạm nguyên lý
che giấu thông

447
Các phạm vi kế thừa
Có 3 loại chính đó là:
• public: Nếu kế thừa ở dạng public, sau khi kế thừa,
tất cả các thành viên dạng public lớp cha
sẽ public ở lớp con, dạng protected ở lớp cha vẫn
sẽ là protected ở lớp con.
• protected: Nếu dùng protected thì sau khi kế thừa,
tất cả các thành viên dạng public lớp cha sẽ trở
thành protected tại lớp con.
• private: Trường hợp ta sử dụng private, thì sau khi
kế thừa, tất cả các thành viên
dạng public và protected ở lớp cha sẽ
thành private tại lớp con. 448
Đơn kế thừa
• Đơn kế thừa (Single
Inheritance): nghĩa là một
lớp chỉ được kế thừa từ
đúng một lớp khác. Hay
nói cách khác, lớp con chỉ
có duy nhất một lớp cha.

449
Ví dụ
#include <iostream> // Lop con ke thua tu lop cha
using namespace std; class Lopxedap : public Lopxe
{
// Lop cha };
class Lopxe
{ // main function
public: int main()
Lopxe() {
{ Lopxedap xe1;
cout << "This is a bycicle" << endl; return 0;
} }
};

450
Ví dụ (tt) - mô tả trong bộ nhớ
**contacts **contacts
dirSize dirSize
maxSize maxSize
ContactDir *recent
SmartDir
*contacts 1 *contacts 1
Contact 1 *contacts 2 Contact 1 *contacts 2
*contacts 3 *contacts 3
… …
Contact 2 Contact 2
*contacts i *contacts i
… …
… …
*contacts n *contacts n

Contact i Contact i

451
Cây kế thừa
• Các quan hệ kế thừa luôn được biểu
diễn với các lớp dẫn xuất đặt dưới lớp
cơ sở để nhấn mạnh bản chất phả hệ
của quan hệ

452
Cây kế thừa
Kế thừa phần lớn các thành viên dữ liệu và phương thức
của lớp cơ sở (ngoại trừ constructor, destructor)

Lớp dẫn xuất

Có thể bổ sung thêm các thành viên dữ liệu mới và các


phương thức mới
Lớp cơ sở trực tiếp Class A

Lớp cơ sở Class B

Lớp cơ sở gián tiếp 453


Class C
Hàm tạo và hàm hủy
• Trong thừa kế, khi khởi tạo đối tượng:
– Hàm tạo của lớp cha sẽ được gọi trước
– Sau đó mới là hàm tạo của lớp con.
• Trong thừa kế, khi hủy bỏ đối tượng:
– Hàm hủy của lớp con sẽ được gọi trước
– Sau đó mới là hàm hủy của lớp cha.
A

C 454
Hàm tạo và hàm hủy
• Trong thừa kế, khi khởi tạo đối tượng:
– Hàm tạo của lớp cha sẽ được gọi trước
– Sau đó mới là hàm tạo của lớp con.
• Trong thừa kế, khi hủy bỏ đối tượng:
– Hàm hủy của lớp con sẽ được gọi trước
– Sau đó mới là hàm hủy của lớp cha.
A

C 455
Hàm tạo và hàm hủy (tt)
class SmartDir : public ContactDir {
private:
char *recent; // ten duoc tim gan nhat
Gọi hàm
public: tạo của
SmartDir(const int max) : ContactDir(max) lớp cha

{ recent = 0; }
SmartDir(const SmartDir& sd): ContactDir(sd)
{ recent = 0; }
~SmartDir() { Thu hồi vùng nhớ
của con trỏ thành viên
delete recent; của lớp con nếu đã
} cấp vùng nhớ trong
hàm tạo.
// …………….
};
456
Thành viên lớp được bảo vệ
• Thừa kế:
– Có tất cả các dữ liệu và hàm thành viên.
– Không được truy xuất đến thành viên private.
• Thuộc tính truy cập protected:
– Cho phép lớp con truy xuất.
class Foo {
class ContactDir { public:
//... // cac thanh vien chung...
private:
protected: // cac thanh vien rieng...
int Lookup (const char *name); protected:
// cac thanh vien duoc bao ve...
Contact **contacts; // ds cac doi tac public:
int dirSize; // kich thuoc hien tai // cac thanh vien chung nua...
protected:
int maxSize; // kich thuoc toi da
// cac thanh vien duoc bao ve nua...
}; }; 457
Thành viên lớp được bảo vệ
• Thừa kế:
– Có tất cả các dữ liệu và hàm thành viên.
– Không được truy xuất đến thành viên private.
• Thuộc tính truy cập protected:
– Cho phép lớp con truy xuất.
class Foo {
class ContactDir { public:
//... // cac thanh vien chung...
private:
protected: // cac thanh vien rieng...
int Lookup (const char *name); protected:
// cac thanh vien duoc bao ve...
Contact **contacts; // ds cac doi tac public:
int dirSize; // kich thuoc hien tai // cac thanh vien chung nua...
protected:
int maxSize; // kich thuoc toi da
// cac thanh vien duoc bao ve nua...
}; }; 458
Lớp private, publish và
protected
Thuộc tính truy xuất Kiểu kế thừa
thành viên của lớp
public protected private
cơ sở
public trong lớp protected trong private trong
public
dẫn xuất lớp dẫn xuất lớp dẫn xuất
protected trong protected trong private trong
protected
lớp dẫn xuất lớp dẫn xuất lớp dẫn xuất
Dấu trong lớp Dấu trong lớp dẫn Dấu trong lớp
private dẫn xuất xuất dẫn xuất

protecte
public private
d 459
Lớp private, publish và protected

class A { class B : A { // Thừa kế dạng private


private: …….
int x; };
class C : private A { // A là lớp cơ sở riêng của B
void Fx (void);
………
public: };
int y; class D : public A { // A là lớp cơ sở chung của C
void Fy (void); ………
protected: };
int z; class E : protected A { // A: lớp cơ sở được bảo vệ
void Fz (void); ……….
}; };
460
Lớp private, publish và protected

class A { class B : A { // Thừa kế dạng private


private: …….
int x; };
class C : private A { // A là lớp cơ sở riêng của B
void Fx (void);
………
public: };
int y; class D : public A { // A là lớp cơ sở chung của C
void Fy (void); ………
protected: };
int z; class E : protected A { // A: lớp cơ sở được bảo vệ
void Fz (void); ……….
}; };
461
Đa kế thừa
• Đa kế thừa (Multiple
Inheritance): là một tính
năng của ngôn ngữ C++.
Trong đó một lớp có thể kế
thừa từ nhiều hơn một lớp
khác. Nghĩa là một lớp con
được kế thừa từ nhiều hơn
một lớp cơ sở.

462
Đa thừa kế
OptionList Window class OptionList { class Window {
public: public:
OptionList (int n); Window (Rect &);
~OptionList (); ~Window (void);
Menu //... //...
}; };

class Menu
OptionList object Window object Menu object : public OptionList, public Window {
OptionList public:
Window OptionList
data members
data members data members Menu (int n, Rect &bounds);
~Menu (void);
Window
//...
data members };
Menu Menu::Menu (int n, Rect &bounds) :
data members
OptionList(n), Window(bounds)
{ /* ... */ }
463
Sự mơ hồ trong đa thừa kế
class OptionList { class Window {
public: public:
// …… // ……
void Highlight (int part); void Highlight (int part);
}; };

class Menu : public OptionList,


Hàm cùng tên Chỉ rõ hàm
public Window của lớp nào
{ ……. };

void main() {
void main() {
xử lý Menu m1(….);
Menu m1(….);
m1.OptionList::Highlight(10);
m1.Highlight(10);
m1.Window::Highlight(20);
….
….
}
}
464
Hàm ảo
• Liên kết tĩnh (static binding):
– Xác định khi biên dịch chương trình.
– Dùng hàm thành viên.
– Gọi hàm của lớp cơ sở (lớp cha).
class ContactDir { class SortedDir : public ContactDir {
//... public:
public:
int Lookup (const char *name); SortedDir(const int max) : ContactDir(max) {}
//... int Lookup(const char *name);
}; };

void
voidmain()
main(){ { cout<<p->Lookup(“ABC”);
ContactDir Gọi
ContactDir c1(10);
*p; ….
SortedDir hàm
SortedDir *p; p = &c1;
c1(10); p = &c1; } 465
nào ?
Hàm ảo (tt)
• Liên kết động (dynamic binding)
– Xác định khi thực thi chương trình.
– Dùng hàm ảo (virtual function).
– Gọi hàm của lớp dẫn xuất (lớp con).
– Thể hiện tính đa hình của OOP.
class ContactDir { void main() {
//... ContactDir c1(10);
public:
SortedDir *p1; p1 = &c1;
virtual int Lookup (const char *name);
}; p1->Lookup(“ABC”); Kết quả
trên
class SortedDir : public ContactDir { SortedDir c2(20); màn hình
//…. Bỏ từ khóa là gì?
ContactDir *p2; p2 = &c2;
public: virtual thì kết
int Lookup(const char *name);
quả là gi? p2->Lookup(“ABC”);
}; } 466
Hàm ảo (tt)
• Khi khai báo hàm ảo trong lớp cơ sở thì hàm đó sẽ được
định nghĩa lại trong lớp dẫn xuất.
• Sử dụng hàm ảo?
– Lớp Parent và Child cùng có phương thức f
– Khai báo một con trỏ thuộc kiểu của lớp Parent
• Parent* p;
– Con trỏ này trỏ đến đối tượng của lớp Child
• p = new Child;
– Sau đó, thực hiện lời gọi
• p->f;
– Kết quả: f của lớp Parent sẽ được gọi
– Nếu f được khai báo là hàm ảo trong lớp Parent thì f của lớp
Child sẽ được gọi.

467
#include <iostream>
#include <iostream>
using namespace std;
using namespace std;
class Hinh
class Hinh
{
{
public:
public:
virtual void ve()
void ve()
{
{
cout << "ve hinh" << endl;
cout << "ve hinh" << endl;
}
}
};
};
class Hinhtron : public Hinh
class Hinhtron : public Hinh
{
{
public:
public:
void ve()
void ve()
{
{
cout << "ve hinh tron" << endl;
cout << "ve hinh tron" << endl;
}
}
};
};
int main ()
int main ()
{
{
Hinh *h = new Hinhtron;
Hinh *h = new Hinhtron;
h->ve();
h->ve();
}
}

468
#include <iostream>
class base{
public:
int i;
base(int x) {i=x;}
virtual void func() {
cout <<"Ham func() cua lop co so, i= "<<i<<endl;
}
};
class derived1:public base{
public:
derived1(int x):base(x) {}
virtual void func() {
cout <<"Ham func() cua lop dan suat 1, i= "<<2*i<<endl;
}
};
class derived2:public base{
public:
derived2(int x):base(x) {}
};
main() { Kết quả là
base *b; base ob(10); gi?
derived1 d_ob1(10); derived2 d_ob2(10);
b=&ob; b->func();
b=&d_ob1; b->func();
b=&d_ob2; b->func();
getchar(); 469
}
Hàm thuần ảo
• Lớp cơ sở trừu tượng
– chỉ cung cấp bộ khung gồm có danh sách các biến và
các hàm cho các lớp khác kế thừa.
Thông báo
– không có đối tượng nào, không tồn tại độc lậpcho trình
– các lớp dẫn xuất định nghĩa phần còn lại. biên dịch
biết thân
• Hàm thuần ảo được khai báo trong lớp sẽ hàm làm
không
liên quan
cho lớp đó trở thành lớp cơ sở trừu tượng. đến lớp cơ
virtual kiểu_trả_về tên_hàm(danh sách tham số) = 0; sở

• Tất cả các lớp dẫn xuất đều phải định nghĩa


hàm thuần ảo.
470
Hàm thuần ảo
• Lớp cơ sở trừu tượng
– chỉ cung cấp bộ khung gồm có danh sách các biến và
các hàm cho các lớp khác kế thừa.
– không có đối tượng nào, không tồn tại độc lập
– các lớp dẫn xuất định nghĩa phần còn lại.
• Hàm thuần ảo được khai báo trong lớp sẽ làm
cho lớp đó trở thành lớp cơ sở trừu tượng.
virtual kiểu_trả_về tên_hàm(danh sách tham số) = 0;
• Tất cả các lớp dẫn xuất đều phải định nghĩa hàm
thuần ảo.
471
Hàm thuần ảo
#include <iostream>
using namespace std;
class Square : public Shape
// lớp trừu tượng {
class Shape public:
{ float calculateArea()
{ return l*l; }
protected:
};
float l;
public: class Circle : public Shape
void getData() {
{ public:
float calculateArea()
cin >> l;
{ return 3.14*l*l; }
} };

// hàm thuần ảo
virtual float calculateArea() = 0;
472
};
int main()
{
Square s;
Circle c;

cout << "Enter length to calculate the area of a square: ";


s.getData();
cout<<"Area of square: " << s.calculateArea();
cout<<"\nEnter radius to calculate the area of a circle: ";
c.getData();
cout << "Area of circle: " << c.calculateArea();

return 0;
}
473
Ví dụ: hàm thuần ảo

class Mammal
{
public:
virtual void Move() = 0;
};
class Dog : public Mammal
{
public:
void Move() {cout << "Dog moves 1 step";}
};
void main()
{
Dog p;
p.Move(); // “Dog moves 1 step”
Mammal m; // ”Lỗi”
m.Move();
}
474
Lớp cơ sở ảo
• Sự mơ hồ - dư thừa dữ liệu

class OptionList
: public Widget, List
{ /*...*/ };
class Window
: public Widget, Port
{ /*...*/ };
class Menu
: public OptionList,
public Window
{ /*...*/ };

Đối tượng Menu


475
Lớp cơ sở ảo (tt)
• Cách xử lý: dùng lớp cơ sở ảo.
class OptionList
: virtual public Widget,
public List
{ /*...*/ };
class Window
: virtual public Widget,
Chỉ có 1
public Port đối tượng Widget
{ /*...*/ };
class Menu
: public OptionList,
Menu::Menu (int n, Rect &bounds) :
public Window Widget(bounds), OptionList(n), Window(bounds)
{ /*...*/ }; { //... }
476
#include <iostream> #include <iostream>
class base{ class base{
public: public:
int i; int i;
}; };
class derived1:public base{ class derived1: virtual public base{
public: public:
int j; int j;
}; };
class derived2:public base{ class derived2: virtual public base{
public: public:
int k; int k;
}; };
class derived3:public derived1, public class derived3:public derived1, public
derived2{ derived2{
public: public:
int tich() { return i*j*k; } int tich() { return i*j*k; }
}; };
main() { main() {
derived3 ob; derived3 ob;
Mơ hồ, ? i của
ob.i=10; ob.i=10;
ob.j=20; lớp nào? ob.j=20;
ob.k=30; ob.k=30;
cout <<"tich so la : "<<ob.tich ()<<endl; cout <<"tich so la : "<<ob.tich ()<<endl;
getchar(); getchar();
} } 477
Khuôn hình

478
Định nghĩa khuôn hình
• Template cho phép định nghĩa một hàm có đặc điểm
chung.
• C++ sử dụng template để tạo ra những thể hiện cụ thể
của hàm khi cần thiết.
• Ví dụ: định nghĩa hàm max
template <class kind>
kind max(kind d1, kind d2)
{
if (d1 > d2)
return (d1);
return (d2);
}
– Cấu trúc <class kind> báo cho C++ biết rằng kind có thể được
thay thế bởi bất kỳ kiểu nào.
– Khai báo hàm phải nằm ngay sau khai báo template
479
Định nghĩa khuôn hình…
• Ví dụ: Sử dụng template
main()
{
float f = max(3.5, 8.7);
int i = max(100, 800);
char ch = max('A', 'Q');
int i2 = max(600, 200);
}
– Khi C++ phát hiện câu lệnh: float f = max(3.5, 8.7); nó
sẽ kiểm tra để xác định xem hàm max(float, float) đã
có mã lệnh hay chưa và nó sẽ tự động tạo ra mã lệnh
nếu chưa có.
– Chú ý rằng khi gặp câu lệnh int i2 = max(600, 200);
thì nó sẽ không tạo ra mã lệnh cho hàm max(int,int)
nữa vì nó đã tạo ra từ trước.
480
Chuyên biệt hoá hàm
• Khi sử dụng hàm max để so sánh hai string như
sau:
main()
{
char *namel = "Able";
char *name2 = "Baker";
cout << max(namel, name2) << '\n';
}
– Vì string được biểu diễn bởi một con trỏ (char *), nên
câu lệnh if (dl > d2) sẽ so sánh giá trị của con trỏ chứ
không phải nội dung của con trỏ.
– Nếu muốn C++ vẫn sử dụng cách so sánh với những
dữ liệu thông thường, riêng đối với string thì phải sử
dụng strcmp. 481
Chuyên biệt hoá hàm …
• Thực hiện quá trình chuyên biệt hoá (specialization).
• Định nghĩa lại hàm max chỉ dùng cho kiểu string.
char *max(char *dl, char *d2)
{
if (strcmp(dl, d2) < 0)
return (dl);
return (d2);
}
– Khi C++ phát hiện ra câu lệnh cout << max(namel, name2) << '\n'; thì
nó sẽ tìm những hàm thông thường có dạng max(char *, char *) ,
sau đó nó mới tìm đến template max(kind d1, kind d2).

482
Khuôn hình lớp
#include <stdlib.h>
#include <iostream>
const int STACK_SIZE = 100; // số lượng phần tử lớn nhất trong Stack
template<class kind>
class stack {
private:
int count; // Số lượng phần tử của Stack
kind data[STACK_SIZE]; // Mảng chứa các phần tử của Stack
public:
stack (void) {count = 0; }// Stack ban đầu rỗng
void push(const kind item) {
data[count] = item;
++count;
}
kind pop(void) {
--count;
return (data[count]);
}
};
483
Khuôn hình lớp …
• Nếu ta khai báo:
stack a_stack; // câu lệnh không hợp lệ
– Stack là một template chung.
– Khi C++ nhìn thấy lệnh khai báo này, nó sẽ hỏi “stack nào?”.
– Phải xác định một kiểu dữ liệu cụ thể được lưu trong stack.
– Phải khai báo:
stack<int> a_stack; // Các phần tử trong stack là kiểu số nguyên
• Sau đó, ta có thể sử dụng stack như sau:
a_stack.push(I);
x = a_stack.pop();

484
Khuôn hình lớp …
• Định nghĩa các hàm thành viên bên ngoài
định nghĩa lớp như sau:
template<class kind>
inline void stack<kind>::push(const kind item)
{
data[count] = item;
++count;
}

485
Chuyên biệt hoá lớp
template <class kind>stack { ...}
– Giới thiệu cho C++ biết cách tạo ra một tập các lớp có tên là stack<int>,
stack<double>, stack<float>,…
– C++ sẽ tự động tạo ra các hàm thành viên như: stack<int>: :push,
stack<double>: :push, và stack<float>::push.
• Nếu khai báo một hàm thành viên một cách tường minh thì C++ sẽ
sử dụng định nghĩa này trước.
inline void stack<char *>::push(const char * item)
{
data[count] = strdup(item);
++count;
}
– Từ khoá template nói cho C++ biết ”Đây là class chung, hãy tạo ra một
phiên bản tương ứng với nó”.
– Nếu không có từ khoá template, C++ sẽ hiểu đây là hàm chính thức và
phải sử dụng nó trước.

486

You might also like