You are on page 1of 70

Giới thiệu

MỞ RỘNG CỦA
C++ SO VỚI C
TS. LÊ THỊ MỸ HẠNH
Bộ môn Công nghệ Phần mềm
Khoa Công Nghệ Thông Tin n Ngôn ngữ C ra đời năm 1972.
Đại học Bách khoa – Đại học Đà Nẵng
n Phát triển thành C++ vào năm 1983.

Từ khóa Chú thích


n Để bổ sung các tính năng mới vào C, một số từ khóa n Chú thích trong C bằng /* ... */
(keyword) mới đã được đưa vào C++ ngoài các từ khóa
có trong C n C++ đưa thêm chú thích bắt đầu bằng //.
n Các chương trình bằng C nào sử dụng các tên trùng với ¨ kiểu chú thích /*...*/ được dùng cho các khối
các từ khóa cần phải thay đổi trước khi chương trình chú thích lớn gồm nhiều dòng,
được dịch lại bằng C++.
¨ còn kiểu // được dùng cho các chú thích trên
n Các từ khóa mới này là :
một dòng.
asm catch class delete friend inline
Ví dụ: /* Đây là
new operator private protected public
chú thích trong C */
template this throw try virtual
// Đây là chú thích trong C++

Biến Biến
n Biến: vùng bộ nhớ được dành riêng để lưu n Trong C tất cả các câu lệnh khai báo biến,
giá trị. mảng cục bộ phải đặt tại đầu khối.
n 3 loại: ¨ vị trí khai báo và vị trí sử dụng của biến có thể
¨ Biến giá trị; ở cách khá xa nhau, điều này gây khó khăn
¨ Biến tham chiếu; trong việc kiểm soát chương trình.
¨ Biến con trỏ. n C++ đã khắc phục nhược điểm này bằng
n Khai báo & Khởi tạo: cách cho phép các lệnh khai báo biến có
int x; <Kiểu Dữ Liệu> <Tên biến>;
thể đặt bất kỳ chỗ nào trong chương trình
x = 5; trước khi các biến được sử dụng.
Hoặc:
→ int x = 5; ¨ Phạm vi hoạt động của các biến kiểu này là
<Kiểu Dữ Liệu> <Tên biến> = <Giá trị>; khối trong đó biến được khai báo.
Biến Chuyển kiểu (CASTING)
n Phân loại theo phạm vi: n C:
¨ Biến cục bộ ¨ (new_type) expression;
¨ Biến toàn cục n C++:
n Toán tử định phạm vi (::) ¨ vẫn sử dụng như trên.
¨ Phép chuyển kiểu mới:
new_type (expression);
→ Phép chuyển kiểu này có dạng như một
hàm số chuyển kiểu đang được gọi.

Khai báo hằng Nhập xuất dữ liệu


n Có 2 cách định nghĩa: n Trong C:
¨ Sử dụng bộ tiền xử lý #define; ¨ Printf
¨ #define identifier value ¨ Scanf
n Sử dụng từ khóa const; ¨ Khai báo thư viện: # include <stdio.h>
¨ const type identifier = value;
¨ type const identifier = value;
n Trong C++:
¨ Biến dòng nhập cin và biến dòng xuất cout
¨ Khai báo thư viện: #include <iostream.h>
¨ Sử dụng câu lệnh nhập, xuất dữ liệu trong
C++ cần sử dụng thêm using namespace
std:

Xuất dữ liệu Định dạng xuất


n Cú pháp: cout << biểu_thức_1 << ... << biểu_thức_n; n Quy định số thực được hiển thị ra màn hình với p chữ số
n Trong đó cout được định sau dấu chấm thập phân, sử dụng đồng thời các hàm
nghĩa trước như một đối sau:
tượng biểu diễn cho thiết
bị xuất chuẩn của C++ là
¨ setiosflags(ios::showpoint): bật cờ hiệu showpoint(p).
màn hình;
¨ Định dạng trên sẽ có hiệu lực đối với tất cả các toán tử xuất tiếp
n cout được sử dụng kết theo cho đến khi gặp một câu lệnh định dạng mới.
hợp với toán tử chèn <<
để hiển thị giá trị các biểu
thức 1, 2, ..., n ra màn
hình;
n Sử dụng “\n” hoặc endl để
xuống dòng mới.
Định dạng xuất Nhập dữ liệu
n Để quy định độ rộng tối thiểu để hiển thị là k vị trí cho giá n Cú pháp: cin >> biến_1 >> ... >> biến_n;
trị (nguyên, thực, chuỗi) ta dùng hàm: n Toán tử cin được định nghĩa trước như một đối
setw(k); (#include <iomanip>). tượng biểu diễn cho thiết bị vào chuẩn của C++ là
n Hàm này cần đặt trong toán tử xuất và nó chỉ có hiệu lực bàn phím;
cho một giá trị được in gần nhất. Các giá trị in ra tiếp theo
sẽ có độ rộng tối thiểu mặc định là 0. n cin được sử dụng kết hợp với toán tử trích >> để
nhập dữ liệu từ bàn phím cho các biến 1, 2, ..., n.

Biến tham chiếu Hằng tham chiếu


n Biến tham chiếu là một tên khác (bí danh) cho biến đã định
nghĩa: n Cú pháp:
Kiểu_dữ_liệu &Biến_tham_chiếu = biến_hằng; const Kiểu_dữ_liệu &Hằng = Biến/Hằng;
n Biến tham chiếu có đặc điểm là nó được n Ví dụ:
sử dụng làm bí danh cho một biến (kiểu giá
trị) nào đó và sử dụng vùng nhớ của biến
này;
n Biến tham chiếu không được cung cấp
vùng nhớ (dùng chung địa chỉ vùng nhớ
với biến mà nó tham chiếu đến);
n Trong khai báo biến tham chiếu phải chỉ rõ
tham chiếu đến biến nào;
n Biến tham chiếu có thể tham chiếu đến một
phần tử mảng, nhưng không cho phép khai
báo mảng tham chiếu.

Hàm
n Khai báo nguyên mẫu hàm & Định nghĩa
CHƯƠNG 1:
hàm;

HÀM
TS. LÊ THỊ MỸ HẠNH
Bộ môn Công nghệ Phần mềm
Khoa Công Nghệ Thông Tin
Đại học Bách khoa – Đại học Đà Nẵng
Hàm Hàm
n Truyền tham số: tham trị & tham chiếu (biến tham n Đối số
chiếu hoặc biến con trỏ):

Hàm Hàm
n Đối số hằng: sử dụng khi không muốn n Đối số hằng tham chiếu:
thay đổi giá trị đối số truyền vào:

Hàm Hàm
n Đối số mặc định
¨ Định nghĩa các giá trị tham số mặc định cho các hàm; n Đối số mặc định:
¨ Các đối số mặc định cần là các đối số cuối cùng tính từ trái
qua phải;
¨ Nếu chương trình sử dụng khai báo nguyên mẫu hàm thì các
đối số mặc định cần được khởi gán trong nguyên mẫu hàm,
không được khởi gán lại cho các đối số mặc định trong dòng
đầu của định nghĩa hàm;
¨ Khi xây dựng hàm, nếu không khai báo nguyên mẫu hàm thì
các đối số mặc định được khởi gán trong dòng đầu của định
nghĩa hàm;
¨ Đối với các hàm có đối số mặc định thì lời gọi hàm cần viết
theo quy định: các tham số vắng mặt trong lời gọi hàm tương
ứng với các đối số mặc định cuối cùng (tính từ trái sang
phải).
Hàm Hàm
n Hàm trả là tham chiếu
n Hàm trả về là tham chiếu Kiểu &Tên_hàm(...) {
¨ Biểu thức được trả lại trong //thân hàm
câu lệnh return phải là biến return <biếntoàncục>;
toàn cục, khi đó mới có thể }
sử dụng được giá trị của hàm;
¨ Nếu trả về tham chiếu đến một biến cục bộ thì biến
cục bộ này sẽ bị mất đi khi kết thúc thực hiện hàm.
Do vậy tham chiếu của hàm sẽ không còn ý nghĩa
nữa. Vì vậy, nếu hàm trả về là tham chiếu đến biến
cục bộ thì biến cục bộ này NÊN khai báo static;
¨ Khi giá trị trả về của hàm là tham chiếu, ta có thể gặp
các câu lệnh gán hơi khác thường, trong đó vế trái là
một lời gọi hàm chứ không phải là tên của một biến.

Hàm Hàm nội tuyến (inline)


n Hàm trả về hằng tham chiếu n Hàm: làm chậm tốc độ thực hiện chương trình vì phải thực
hiện một số thao tác có tính thủ tục khi gọi hàm:
¨ Cấp phát vùng nhớ cho đối số và biến cục bộ;
¨ Truyền dữ liệu của các tham số cho các đối;
¨ Giải phóng vùng nhớ trước khi thoát khỏi hàm.
n C++ cho khả năng khắc phục được nhược điểm trên bằng
cách dùng hàm nội tuyến → dùng từ khóa inline trước khai
báo nguyên mẫu hàm.
n Chú ý:
¨ Hàm nội tuyến cần có từ khóa inline phải xuất hiện trước các
lời gọi hàm;
¨ Chỉ nên khai báo là hàm inline khi hàm có nội dung đơn giản;
¨ Hàm đệ quy không thể là hàm inline.

Hàm nội tuyến (inline) Đa năng hóa (Overloading)


n Với ngôn ngữ C++, chúng ta có thể đa năng hóa
các hàm và các toán tử (operator).
n Đa năng hóa là phương pháp cung cấp nhiều
hơn một định nghĩa cho tên hàm đã cho trong
cùng một phạm vi.
n Trình biên dịch sẽ lựa chọn phiên bản thích hợp
của hàm hay toán tử dựa trên các tham số mà
nó được gọi.
n Có hai hình thức đa năng hóa:
¨ Đa năng hóa hàm
¨ Đa năng hóa toán tử
Đa năng hóa hàm Đa năng hóa hàm
(Function Overloading) (Function Overloading)
n Trong ngôn ngữ C cũng như mọi ngôn ngữ máy tính khác, mỗi hàm — Trình biên dịch dựa vào sự khác nhau về số các tham số, kiểu
đều phải có một tên phân biệt. của các tham số để có thể xác định chính xác phiên bản cài
¨ Như trong ngôn ngữ C, vì cần thiết phải có tên phân biệt nên C phải có đặt nào của hàm MyAbs() thích hợp với một lệnh gọi hàm
hàm riêng cho mỗi kiểu dữ liệu số, được cho,
¨ do vậy có tới ba hàm khác nhau để trả về trị tuyệt đối của một tham số : MyAbs(-7); //Gọi hàm int MyAbs(int)
MyAbs(-7l); //Gọi hàm long MyAbs(long)
int abs(int i);
MyAbs(-7.5); //Gọi hàm double MyAbs(double)
long labs(long l);
double fabs(double d);
— Quá trình tìm hàm đa năng hóa :
— nếu tìm thấy một phiên bản định nghĩa nào đó của một hàm được
¨ Tất cả các hàm này đều cùng thực hiện một chứa năng nên chúng ta đa năng hóa mà có kiểu dữ liệu các tham số của nó trùng với
thấy điều này nghịch lý khi phải có ba tên khác nhau. kiểu các tham số đã gởi tới trong lệnh gọi hàm thì phiên bản hàm
n C++ giải quyết điều này bằng cách cho phép chúng ta tạo ra các đó sẽ được gọi.
hàm khác nhau có cùng một tên. Đây chính là đa năng hóa hàm. — Nếu không trình biên dịch C++ sẽ gọi đến phiên bản nào cho
phép chuyển kiểu dễ dàng nhất.
n Như vậy, trong C++ chúng ta có thể định nghĩa lại các hàm trả về trị MyAbs(‘c’); //Gọi int MyAbs(int)
tuyệt đối để thay thế các hàm trên như sau : MyAbs(2.34f); //Gọi double MyAbs(double)
int Myabs(int i); Þ Bất kỳ hai hàm nào trong tập các hàm đã đa năng phải có
long Myabs(long l); các tham số khác nhau.
double Myabs(double d);

Đa năng hóa toán tử Đa năng hóa toán tử


(Operators overloading) (Operators overloading)
— Trong C, khi tạo ra một kiểu dữ liệu mới, để thực hiện — Để khắc phục yếu điểm này, trong C++ cho phép chúng ta có
các thao tác liên quan đến kiểu dữ liệu đó thường thể định nghĩa lại chức năng của các toán tử đã có sẵn một
thông qua các hàm cách tiện lợi và tự nhiên hơn rất nhiều.
— Ví dụ: — Điều này gọi là đa năng hóa toán tử..
typedef struct Complex{ — Ví dụ:
double Real; Complex operator + (Complex C1,Complex C2);
Complex operator - (Complex C1,Complex C2);
double Imaginary; => C3 = C1 + C2;
}; C4 = C1 - C2;
Complex SetComplex(double R,double I); — Như vậy trong C++, các phép toán trên các giá trị kiểu số
Complex AddComplex(Complex C1,Complex C2); phức được thực hiện bằng các toán tử toán học chuẩn chứ
không phải bằng các tên hàm như trong C.
Complex SubComplex(Complex C1,Complex C2);
=> C3 = AddComplex(C1,C2); //Hơi bất tiện !!! — Chẳng hạn chúng ta có lệnh sau:
C4 = AddComplex(C3, SubComplex(C1,C2));
C4 = SubComplex(C1,C2);
thì ở trong C++, chúng ta có lệnh tương ứng như sau:
— Điều này trở nên không thoải mái vì thực chất thao tác C4 = C3 + C1 - C2;
cộng và trừ là các toán tử chứ không phải là hàm.

Đa năng hóa toán tử Đa năng hóa toán tử


(Operators overloading) (Operators overloading)
Cú pháp: — Các giới hạn của đa năng hóa toán tử:
— Chúng ta không thể định nghĩa các toán tử mới.
data_type operator oper_sym( parameters ){ — Hầu hết các toán tử của C++ đều có thể được đa năng hóa.

……………………………… — Các toán tử sau không được đa năng hóa là :


q :: Toán tử định phạm vi.
} q .* Truy cập đến con trỏ là trường của struct hay thành viên của class.
. Truy cập đến trường của struct hay thành viên của class.
Trong đó: q

q ?: Toán tử điều kiện


data_type: Kiểu trả về. q sizeof
operator_symbol: Ký hiệu của toán tử. — và chúng ta cũng không thể đa năng hóa bất kỳ ký hiệu tiền xử lý nào.
— Chúng ta không thể thay đổi thứ tự ưu tiên của một toán tử hay không thể thay đổi số các toán
parameters: Các tham số (nếu có). hạng của nó.

— Các toán tử được đa năng hóa sẽ được lựa chọn bởi trình — Chúng ta không thể thay đổi ý nghĩa của các toán tử khi áp dụng cho các kiểu có sẵn.
— Đa năng hóa các toán tử không thể có các tham số có giá trị mặc định.
biên dịch: — Các toán tử có thể đa năng hoá:
— khi gặp một toán tử làm việc trên các kiểu không phải là kiểu có sẵn, + - * / % ^ ! = < > += -=
trình biên dịch sẽ tìm một hàm định nghĩa của toán tử nào đó có các ^= &= |= << >> <<= <= >= && || ++ --
tham số đối sánh với các toán hạng để dùng. () [] new delete & | ~ *= /= %= >>= == != , -> ->*
Bài tập Bài tập
n Bài tập 1.1: n Bài tập 1.2:
Viết chương trình cho phép thực hiện các thao tác trên kiểu struct Viết chương trình cho phép thực hiện các thao tác trên kiểu struct
phân số: số phức:
n Hàm toán tử Nhập, xuất phân số (>>, <<) n Hàm toán tử Nhập, xuất số phức (>>, <<).

n Nghịch đảo, rút gọn phân số. n Hàm tính module số phức.

n Hàm toán tử +, -, *, /, <, >, ==, != giữa hai phân số. n Hàm toán tử +, -, *, /, <, >, ==, != giữa hai số phức.

37

Bài tập Bài tập


n Bài tập 1.3: n Bài tập 1.4:
Viết chương trình cho phép thực hiện các thao tác trên kiểu struct Viết chương trình cho phép thực hiện các thao tác trên kiểu struct
đơn thức: Date gồm ngày, tháng, năm:
n Hàm toán tử nhập, xuất đơn thức. n Hàm toán tử nhập, xuất 01 ngày Date (>>, <<)

n Tính giá trị, đạo hàm, nguyên hàm đơn thức. n Tính xác định thứ trong tuần.

n Hàm toán tử +, -, *, /, <, >, ==, != giữa hai đơn thức cùng n Hàm toán tử ++, -- để tang, giảm 01 ngày; toán tử <, >, ==, !=
bậc. giữa hai ngày.

Bài tập
n Bài tập 1.5:
Thông tin một học sinh bao gồm:
CHƯƠNG 2:
n Họ tên. CON TRỎ
n Điểm văn, toán.

Thực hiện các thao tác trên kiểu struct học sinh: &
n Hàm toán tử >>, << để nhập, xuất thông tin học sinh.

n Toán tử >, != để soa sánh 02 học sinh dựa vào điểm trung CẤP PHÁT ĐỘNG
bình
n Tính điểm trung bình của mỗi học sinh.
TS. LÊ THỊ MỸ HẠNH
Bộ môn Công nghệ Phần mềm
n Xếp loại theo tiêu chí:
Khoa Công Nghệ Thông Tin
¨ Giỏi (>= 8.0), Khá (>= 7.0). Đại học Bách khoa – Đại học Đà Nẵng
¨ Trung bình (>= 5.0), Yếu (< 5).
Viết chương trình thực hiện việc quản lý danh sách học sinh gồm
nhập xuất danh sách học sinh, in danh sách học sinh theo thứ tự
điểm trung bình giảm dần.
Con trỏ Con trỏ
n Khái niệm:
¨ Con trỏ là biến dùng để chứa địa chỉ của biến khác
¨ Cùng kiểu dữ liệu với kiểu dữ liệu của biến mà nó trỏ
tới.
n Cú pháp: <kiểudữliệu> *<têncontrỏ>;
Lưu ý:
¨ Có thể viết dấu * ngay sau kiểu dữ liệu;
¨ Ví dụ: int *a; và int* a; là tương đương.
¨ Thao tác với con trỏ:
n * là toán tử thâm nhập (dereferencing operator): *p là giá trị
nội dung vùng nhớ con trỏ đang trỏ đến;
n & là toán tử địa chỉ (address of operator): &x là địa chỉ của
biến x; nếu int *p = &x thì p ↔ x.

Con trỏ Con trỏ


n Con trỏ hằng và hằng con trỏ n Phép gán giữa các con trỏ:
¨ const int x = 1; và int const x = 1; là như
¨ Các con trỏ cùng kiểu có thể gán cho nhau
nhau; thông qua phép gán và lấy địa chỉ con trỏ
¨ Nếu int x = 1; thì
<tên con trỏ 1> = <tên con trỏ 2>;
n const int *p = &x; (CON TRỎ HẰNG)
n Lưu ý:
Không cho phép thay đổi giá trị vùng nhớ mà ¨ Bắt buộc phải dùng phép lấy địa chỉ của
con trỏ đang trỏ đến thông qua con trỏ (*p). biến do con trỏ trỏ tới mà không được dùng
n int* const p = &x; (HẰNG CON TRỎ) phép lấy giá trị của biến;
Không cho phép thay đổi vùng nhớ con trỏ ¨ Hai con trỏ phải cùng kiểu dữ liệu (nếu khác
đang trỏ tới, nhưng có thể thay đổi giá trị vùng kiểu phải sử dụng các phương thức ép kiểu).
nhớ đó thông qua con trỏ.

Con trỏ Con trỏ


n Phép gán giữa các n Sử dụng typedef
con trỏ:
¨ Ví dụ
Con trỏ Con trỏ và Mảng
n const_cast: thêm hoặc loại bỏ const khỏi n Khai báo & khởi tạo mảng:
một biến ¨ int A[5];
¨ intA[5] = {1, 2, 3, 4, 5};
¨ làcách duy nhất để thay đổi tính hằng của intA[] = {1, 2, 3, 4};
biến. int A[2][3];
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
¨ Ví dụ: int A[2][3] = {1, 2, 3, 4, 5, 6};
int A[][3] = {{1, 2, 3}, {4, 5, 6}};
n Không được bỏ trống cột với mảng 2 chiều.
n Truy cập phần tử trong mảng:
A[i] – giá trị phần tử thứ i; &A[i] – địa chỉ phẩn tử thứ i;
A[i][j] – giá trị phần tử hàng i, cột j;
&A[i] – địa chỉ hàng i;
&A[i][j] – địa chỉ phần tử hàng i, cột j.

Con trỏ và Mảng Con trỏ và Mảng


n Quan hệ giữa con trỏ và mảng 1 chiều: n Phép toán trên con trỏ và mảng 1 chiều:
n Tên mảng được coi như một con trỏ tới phần tử đầu tiên ¨ Khi con trỏ trỏ đến mảng, thì các phép toán
của mảng:
tăng hay giảm trên con trỏ sẽ tương ứng với
phép dịch chuyển trên mảng;
n Lưu ý:nếu int*p =A; thì p++và*p++ là
khác nhau
¨ p++ thao tác trên con trỏ: đưa con trỏ pa đến
địa chỉ phần tử tiếp theo;
n Do tên mảng và con trỏ là tương đương, ta có thể dùng
¨ *p++ là phép toán trên giá trị, tăng giá trị phần
P như tên mảng. Ví dụ:
tử hiện tại của mảng.
n P[3] = 7; tương đương với A[3] = 7;

Con trỏ và Mảng Con trỏ và Mảng


n Phép toán trên con trỏ và mảng 1 chiều: n Con trỏ & mảng 2 chiều
¨ Ví dụ ¨ Địa chỉ ma trận A:
A = A[0] = *(A+0) = &A[0][0];
¨ Địa chỉ của hàng thứ nhất:
A[1] = *(A+1) = &A[1][0];
¨ Địa chỉ của hàng thứ i:
A[i] = *(A+i) = &A[i][0];
¨ Địa chỉ phần tử:
&A[i][j] = (*(A+i)) + j;
¨ Giá trị phần tử:
A[i][j] = *((*(A+i)) + j);
→ int A[3][3]; tương đương int (*A)[3];.
Con trỏ và Mảng Con trỏ hàm
n Mỗi hàm đều có 1 địa chỉ xác định trên bộ
n Con trỏ tới con trỏ: nhớ → có thể sử dụng con trỏ để trỏ đến
¨ int A[3][3]; tương đương int (*A)[3]; địa chỉ của hàm.
¨ int A[3]; tương đương int *A;

→Mảng 2 chiều có thể thay thế bằng


một mảng các con trỏ tương đương một con
trỏ trỏ đến con trỏ.
n Ví dụ:
int A[3][3];
int (*A)[3]; int **A;

Con trỏ hàm Con trỏ hàm


n Khai báo: n Ví dụ
<kiểu dữ liệu trả về> (*<tên hàm>) ([<danh sách tham số>])
n Lưu ý:
¨ Dấu “()” bao bọc tên hàm để thông báo đó là con trỏ hàm;
¨ Nếu không có dấu “()” thì trình biên dịch sẽ hiểu ta đang khai báo
một hàm có giá trị trả về là một con trỏ.
n Ví dụ:
int (*Cal) (int a, int b) //Khai báo con trỏ hàm
int *Cal (int a, int b) //Khai báo hàm trả về kiểu con trỏ
n Đối số mặc định không áp dụng cho con trỏ hàm, vì đối số
mặc định được cấp phát khi biên dịch, còn con trỏ hàm
được sử dụng lúc thực thi.

Con trỏ hàm Con trỏ hàm


n Sử dụng con trỏ hàm làm đối số: n Sử dụng con trỏ hàm làm đối số:
¨ Vídụ: sắp xếp dữ liệu trong mảng số nguyên theo thứ
¨ Sử dụng khi cần gọi 1 hàm như là tham số tự tăng dần
của 1 hàm khác;
¨ Ví dụ: int (*Cal) (int a, int b);
n Thì có thể gọi các hàm có hai tham số kiểu int và
trả về cũng kiểu int:
int add(int a, int b);
int sub(int a, int b);
n Nhưng không được gọi các hàm khác kiểu tham
số hoặc kiểu trả về:
int add(float a, int b);
int add(int a);
char* sub(char* a, char* b); Nhưng nếu muốn sắp xếp theo thứ tự giảm dần ???
Con trỏ hàm Con trỏ hàm
n Sử dụng con trỏ hàm làm đối số: n • Sử dụng con trỏ hàm làm đối số:
¨ Thêm 2 hàm so sánh:

¨ ascending:sắp xếp tăng dần


¨ descending: sắp xếp giảm dần Lưu ý: Con trỏ hàm là đối số mặc định

Cấp phát động Cấp phát động


n Cấp phát tĩnh (static allocation): biến được cấp n Ví dụ
phát vùng nhớ khi biên dịch;
n Cấp phát động (dynamic allocation): biến được
cấp phát vùng nhớ lúc thực thi:
¨ Sử dụng từ khóa new;
¨ Dùng biến con trỏ (p) để lưu trữ địa chỉ vùng nhớ:
n Cấp phát bộ nhớ 1 biến: p = new type;

n Cấp phát bộ nhớ 1 mảng: p = new type[n];

n Kiểm tra vùng nhớ cấp phát có thành công hay không?
¨ Thành công: con trỏ chứa địa chỉ đầu vùng nhớ được cấp phát;
¨ Không thành công: p = NULL.

Cấp phát động Cấp phát động


n Giải phóng bộ nhớ trong C++: n Cấp phát động mảng đa chiều:
¨ delete biếncontrỏ ¨ Cấp phát động mảng hai chiều (N+1)(M+1) gồm các
¨ delete [] biếncontrỏ đối tượng IQ:
Cấp phát động Cấp phát động
n Huỷ mảng động bất hợp lệ: n Con trỏ lạc: khi delete P, ta cần chú ý
không xoá vùng bộ nhớ mà một con trỏ Q
khác đang trỏ tới:

n Sau đó

Cấp phát động Bài tập


n Rò rỉ bộ nhớ:
n Bài tập 1.5:
¨ Một vấn đề liên quan: mất mọi con trỏ đến một vùng Viết chương trình cho phép thực hiện các thao tác trên kiểu mảng:
bộ nhớ được cấp phát. Khi đó, vùng bộ nhớ đó bị mất n Hàm nhập, xuất mảng.
dấu, không thể trả lại cho heap được: n Lấy kích thước mảng.

n Lấy phần tử tại vị trí nào đó.

n Sắp xếp tăng, giảm (theo các phương pháp sắp xếp: Chọn
trực tiếp, Chèn trực tiếp, Nổi bọt BubbleSort, QuickSort,
HeapSort, ShellSort, RadixSort)
¨ Sau đó n Tìm phần tử nào đó trong mảng (tuần tự, nhị phân, nội suy).

Nội dung
n Định nghĩa cấu trúc
CHƯƠNG 2:
n Các thao tác trên cấu trúc
n Mảng cấu trúc, con trỏ cấu trúc
KIỂU DỮ LIỆU CẤU TRÚC
n Danh sách liên kết
n Ngăn xếp, Hàng đợi
TS. LÊ THỊ MỸ HẠNH
Bộ môn Công nghệ Phần mềm
Khoa Công Nghệ Thông Tin
Đại học Bách khoa – Đại học Đà Nẵng

12/09/2021
Cấu trúc và Hợp Cấu trúc và Hợp (2)
n Cấu trúc (Struct)
¨ Struct là một tập hợp các phần tử dữ liệu cùng kiểu hoặc khác kiểu được gộp n Ví dụ struct trong C++
chung thành một nhóm. Các phần tử này được gọi là thành viên của struct.
¨ Cú pháp ¨ Ví dụ về struct hình chữ nhật có hai chiều
Ví dụ:
struct structure_name {
<Kiểu dữ liệu thành viên số 1> <Tên thành viên số 1>; struct Student {
rộng và chiều cao:
<Kiểu dữ liệu thành viên số 2> <Tên thành viên số 2>; char name[20]; 1 #include <iostream>
… 2 using namespace std;
int id;
}; 3 struct Rectangle {
¨ Định nghĩa thể hiện của struct int age; 4 int width, height;
structure_name variable_name; }; 5 };
Student s; 6 int main(void) {
¨ Cách truy cập biến struct 7 struct Rectangle rec;
?

n Biến của struct có thể được truy cập bằng cách sử dụng thể hiện 8 rec.width = 8;
của struct theo sau bởi toán tử (.) Và sau đó là trường của struct. 9 rec.height = 5;
10 cout << "Dien tich hinh chu nhat la: "
n Ví dụ: s.name
11 << (rec.width * rec.height) << endl;
s.id
12 return 0;
s.age
13 }

12/09/2021 12/09/2021

Cấu trúc và Hợp (2) Cấu trúc và Hợp (2)


n Ví dụ struct trong C++ n Hợp (Union)
¨ Sử dụng phương thức và Constructor: khởi tạo dữ ¨ union là một tập hợp các phần tử dữ liệu cùng kiểu hoặc khác kiểu
được gộp chung thành một nhóm. Các phần tử này được gọi là
liệu và phương thức để tính diện tích hình chữ nhật. thành viên của union
1 #include <iostream> Ví dụ:
2 using namespace std; ¨ Cú pháp: // Khai báo 1 union
3 struct Rectangle { union Union_Name { union Hardware {
4 int width, height; float _cpu;
<Kiểu dữ liệu thành viên số 1> <Tên thành viên số 1>;
5 Rectangle(int w, int h) { short _ram;
6 width = w; <Kiểu dữ liệu thành viên số 2> <Tên thành viên số 2>;

int _ssd;
7 height = h;
8 }
};
};
9 void areaOfRectangle() { // Sử dụng union
10 cout << "Dien tich hinh chu nhat la: " << (width * height); int main() {
11 } Khi được định nghĩa như ví dụ bên, union Hardware có kích thước Hardware h;
12 }; của thuộc tính có kích thước lớn nhất, trong trường hợp này h._cpu = 3.2f;
13 int main(void) { float hoặc int là 4 bytes nên union này có kích thước là 4 bytes. h._ram = 256;
14 struct Rectangle rec = Rectangle(4, 10);
Với cách sử dụng (dùng chung vùng nhớ) như trên, 3 thuộc h._ssd = 1024;
15 rec.areaOfRectangle();
16 return 0; tính _cpu, _ram, _ssd ghi đè lên nhau trong quá trình sử dụng. return 0;
17 } }
12/09/2021 12/09/2021

Cấu trúc và Hợp (3) Cấu trúc và Hợp (4)


n Trường hợp sử dụng // Khai báo 1 union n Union không tên:
union union Hardware { ¨ C++ cho phép khai báo các union không tên:
short _ram; union {
¨ Cần tiết kiệm vùng nhớ <Kiểu dữ liệu thành viên số 1> <Tên thành viên số 1>;
int _ssd;
Tiết kiệm vùng nhớ sẽ <Kiểu dữ liệu thành viên số 2> <Tên thành viên số 2>;
};
hữu ích với các ứng dụng …
có bộ nhớ nhỏ như trong void func() {
};
lập trình nhúng, dung Hardware hw;
lượng bộ nhớ chỉ khoảng // (1) Đoạn code cần dùng ram → Khi đó các thành phần (khai báo trong union) sẽ dùng chung một vùng
2KB (2048 bytes). hw.ram = 2048; // MB nhớ → tiết kiệm bộ nhớ và cho phép dễ dàng tách các byte của một vùng
printf("RAM: %d", hw.ram); nhớ.
// (2) Đoạn code không cần dùng ram, ¨ Ví dụ: nếu các biến nguyên i , biến ký tự ch và biến thực x không đồng
// nhưng cần dùng hdd thời sử dụng thì có thể khai báo chúng trong một union không tên như
hw.ssd = 1024; // GB sau:
printf("SSD: %d", hw.ssd);
}

12/09/2021
Cấu trúc và Hợp (5) Cấu trúc và Hợp (5)
n Mảng cấu trúc
n Mảng cấu trúc struct SinhVien {
void NhapMang(SinhVien a[], int &n) {
do{
char ten[20]; printf("Cho biet so Sinh vien: ");
¨ Một sinh viên có các thông tin bao gồm: Tên char ma[10];
int namsinh;
scanf("%d", &n);
} while (n <= 0);
sinh viên, mã số sinh viên, năm sinh, điểm float dtb;
int songaynghi;
for (int i = 1; i <= n; i++) {
printf("Thong tin Sinh vien thu %d la: \n", i);
trung bình và số ngày nghỉ. Nhà trường cần };
void XuatMang(SinhVien a[], int n) {
printf("Ten: \n"); fflush(stdin); gets(a[i].ten);
printf("Ma so: \n"); fflush(stdin); gets(a[i].ma);
nhập thông tin của tất cả các sinh viên đang printf("Ten\t\t Ma\t\t NamSinh\t DTB\t \tSoNgayNghi");
for (int i = 1; i <= n; i++) {
printf("Nam sinh :\n");
scanf("%d", &a[i].namsinh);
theo học tại trường và lập ra danh sách các printf("\n%s %s \t %d \t\t %f \t%d\n",
a[i].ten,a[i].ma,a[i].namsinh,
printf("Diem Trung Binh: \n");
scanf("%f", &a[i].dtb);
học sinh có thành tích tốt trong học tập ( điểm }
a[i].dtb, a[i].songaynghi); printf("So ngay nghi: \n");
scanf("%d", &a[i].songaynghi);
trung bình lớn hơn 7.0) để khen thưởng và }
}
}

danh sách các học sinh có số ngày nghỉ lớn void main() {
SinhVien A[100];
hơn 3 để ra nhắc nhở sinh viên đó. int N;
NhapMang(A, N);
XuatMang(A, N);
getch();
12/09/2021 12/09/2021
}

Cấu trúc và Hợp (4) Danh sách


n Con trỏ cấu trúc #include <iostream> n Danh sách là cấu trúc dữ liệu tuyến tính, trong đó các
using namespace std;
¨ Một biến con trỏ có thể
struct Distance { phần tử dữ liệu được sắp xếp theo một thứ tự xác định
được tạo ra không chỉ các int feet;
kiểu cơ sở như n Danh sách thuần nhất: các phần tử cùng một kiểu
float inch;
(int, float, doube…) mà }; n Ví dụ
chúng còn có thể được tạo int main() {
cho các kiểu người dùng tự Distance *ptr, d; ¨ Danh sách sinh viên
định nghĩa như cấu trúc. ptr = &d; ¨ Danh sách điện thoại
cout << "Enter feet: "; cin >> (*ptr).feet;
¨ Truy xuất 01 trường của
cout << "Enter inch: "; cin >> (*ptr).inch;
¨ Danh sách môn học
con trỏ struct: có 02 cách cout << "Displaying information." << endl; ¨ Danh sách bài hát
cout << "Distance = " << (*ptr).feet
ptr->feet tương đương với (*ptr).feet << " feet " << (*ptr).inch << " inches";
¨ Danh sách công việc
ptr->inch tương đương với (*ptr).inch return 0;
}

12
12/09/2021

Trừu tượng hóa danh sách Trừu tượng hóa danh sách
n Đặc tả dữ liệu n Đặc tả dữ liệu
L = (a0, a1, …, an-1)
— Là một dãy hữu hạn các phần tử
L = (a0, a1, … , an-1) trong đó ai là phần tử thứ i+1 của danh sách L
Ví dụ:
n Đặc tả các phép toán L = (1, 2, 3, 3, 4, 5)
— Kiểm tra danh sách có rỗng hay không L = (‘Hùng’, ‘Cường’, ‘Sang’)
n Đặc tả các phép toán
— Đếm số phần tử của danh sách ¨ Kiểm tra danh sách có rỗng hay không: empty(L)
— Trả về phần tử ở vị trí thứ i của danh sách ¨ Đếm số phần tử của danh sách: length(L)

— Thêm phần tử x vào vị trí i trong danh sách ¨ Trả về phần tử ở vị trí thứ i của danh sách: element(L, i)
¨ Thêm phần tử x vào vị trí i trong danh sách: insert(L, i, x)
— Thêm phần tử x vào đuôi danh sách ¨ Thêm phần tử x vào đuôi danh sách: append(L, x)
— Loại phần tử ở vị trí thứ i trong danh sách ¨ Loại phần tử ở vị trí thứ i trong danh sách: erase(L, i)

13 14
Ví dụ Cài đặt danh sách bằng mảng
n L = (1, 2, 3, 3, 4, 5) n Mảng một chiều tĩnh
int dayso[100];
n empty(L) → false Phanso dayphanso[15];
n length(L) → 6 n Mảng một chiều động
¨ Cấp phát bộ nhớ bằng phép new
n element(L, 0) → 1 int *daysod = new int[100];
n element(L, 2) → 3 Phanso *dayphansod = new Phanso[15];
¨ Khi không dùng nữa, phải giải phóng bộ nhớ bằng phép delete
n insert(L, 2, 10) → L = (1, 2, 10, 3, 3, 4, 5) delete [] daysod;
n append(L, -5) → L = (1, 2, 10, 3, 3, 4, 5, -5) delete [] dayphansod;

n erase(L, 3) → L = (1, 2, 10, 3, 4, 5, -5) n L = (a0, a1, …, an-1)

n erase(L, 1) → L = (1, 10, 3, 4, 5, -5) a0 a1 … an-1 ? … ?


0 1 n-1 n MAX-1
15 16

Cài đặt danh sách bằng mảng Cài đặt danh sách bằng mảng
n Mảng (array) n insert(L, i, x)
¨ Tập hợp các phần tử (các biến) có cùng một kiểu a0 … ai-1 ai … an-1 ? ? … ?
¨ Một phần tử cụ thể trong mảng sẽ được xác 0 … i-1 i … n-1 n n+1 MAX-1
định và truy cập bởi một chỉ số
¨ Trong C/C++, các phần tử của mảng được đặt a0 … ai-1 x ai … an-1 ? … ?
cạnh nhau tạo thành một khối liên tục. Địa chỉ 0 … i-1 i i+1 … n n+1 MAX-1
thấp nhất tương ứng với phần tử đầu tiên, địa
chỉ cao nhất tương ứng với phần tử cuối cùng n Dồn tất cả các phần tử từ vị trí i tới vị trí n-1 về sau một vị trí
¨ Mảng thì có thể là một chiều hoặc nhiều chiều n Sau đó đặt giá trị x vào vị trí i
n Tăng số phần tử của danh sách lên 1
17 18

Cài đặt danh sách bằng mảng Danh sách


n erase(L, i)
Thành phần đồng
Mảng (Array)
a0 … ai-1 ai ai+1 … an-1 ? … ? Truy cập ngẫu
nhiên/trực tiếp
nhất

Thành phần không


Bản ghi (Record)
0 … i-1 i i+1 … n-1 n MAX-1 đồng nhất
Cấu trúc dữ liệu

Tuyến tính Danh sách liên kết


Tổng quát
(List)

Truy cập tuần tự Vào-trước-ra-trước Hàng đợi (Queue)


a0 … ai-1 ai+1 … an-1 ? ? … ?
0 … i-1 i … n-2 n-1 n MAX-1 Không tuyến tính Tập hợp (Set) Vào-sau-ra-trước Ngăn xếp (Stack)

n Dồn tất cả các phần tử từ vị trí i+1 tới vị trí n-1 lên trước một vị trí
n Giảm số phần tử của danh sách đi 1
19 20
Danh sách Danh sách
n Cấu trúc dữ liệu tĩnh n Cấu trúc dữ liệu động
¨ Một số đối tượng dữ liệu trong chu kỳ sống của nó có thể thay ¨ Cấp phát động lúc chạy chương trình
đổi về cấu trúc, kích thước, như danh sách sinh viên trong lớp ¨ Các phần tử được cấp phát vùng nhớ rải rác trong bộ nhớ
có thể tăng hoặc giảm số lượng ¨ Kích thước danh sách chỉ bị giới hạn bởi RAM
n Nếu dùng cấu trúc dữ liệu tĩnh như mảng để biểu diễn -> thao tác
¨ Thao tác thêm/xoá đơn giản
phức tạp, kém tự nhiên -> chương trình khó đọc, khó bảo trì, sử
dụng bộ nhớ kém hiệu quả -> dữ liệu chiếm vùng nhớ đã được cấp
phát trong suốt thời gian chương trình hoạt động
¨ Ví dụ: Mảng 1 chiều
n Kích thước cố định
n Chèn 1 phần tử hoặc xoá 1 phần tử rất khó
n Các phần tử được lưu trữ tuần tự với chỉ số 0, 1, .. N-1
n Truy cập phần tử ngẫu nhiên

12/09/2021 12/09/2021

Danh sách liên kết Danh sách liên kết (2)


n Danh sách liên kết là gì? n Các kiểu danh sách liên kết:
¨ Danh sách liên kết đơn là một tập hợp các Node được phân bố ¨ Danh sách liên kết đơn(Single linked list): Chỉ có sự
động, được sắp xếp theo cách sao cho mỗi Node chứa “một giá
trị”(Data) và “một con trỏ”(Next). Con trỏ sẽ trỏ đến phần tử kế tiếp kết nối từ phần tử phía trước tới phần tử phía sau.
của danh sách liên kết đó. Nếu con trỏ mà trỏ tới NULL, nghĩa là A B C D
đó là phần tử cuối cùng của linked list.
¨ hình ảnh mô phỏng một danh sách liên đơn kết đầy đủ
¨ Danh sách liên kết đôi(Double linked list): Có sự kết
nối 2 chiều giữa phần tử phía trước với phần tử phía
sau
A B C D
¨ một Node trong danh sách liên kết đơn: Data Next
10 pointer

12/09/2021 12/09/2021

Danh sách liên kết (2) Danh sách liên kết (3)
n Các kiểu danh sách liên kết: n Cài đặt danh sách liên kết đơn (linked list)
¨ Khai báo LinkedList struct LinkedList{
¨ Danh sách liên vòng: Phần tử cuối danh sách liên với int data;
n data lưu giữa giá trị
phần tử đầu danh sách struct LinkedList *next;
n next là con trỏ đến node kế tiếp };
¨ Danh sách liên kết đơn vòng
¨ Tại sao next lại là kiểu LinkedList của chính nó?
¨ Tạo mới 1 Node
A B C D
typedef struct LinkedList *node;
¨ Danh sách liên kết đôi vòng //Từ giờ dùng kiểu dữ liệu LinkedList có thể thay bằng node cho ngắn gọn

node CreateNode(int value){


node temp; // declare a node
A B C D temp = new node; // Cấp phát vùng nhớ
temp->next = NULL;// Cho next trỏ tới NULL
temp->data = value; // Gán giá trị cho Node
return temp;//Trả về node mới đã có giá trị
}
12/09/2021 12/09/2021
Danh sách liên kết (4) Danh sách liên kết (4)
n Thêm Node vào danh sách liên kết n Thêm Node vào danh sách liên kết
¨ Thêm vào đầu ¨ Thêm vào cuối
1 node AddTail(node head, int value){
1 node AddHead(node head, int value){ 2 node temp,p;// Khai báo 2 node tạm temp và p
2 node temp = CreateNode(value); // Khởi tạo node temp với data = value 3 //Gọi createNode để khởi tạo node temp có next trỏ tới NULL và giá trị là value
3 if (head == NULL){ 4 temp = CreateNode(value);
4 head = temp; // //Nếu linked list đang trống thì Node temp là head 5 if(head == NULL){
5 luôn 6 head = temp; //Nếu linked list đang trống thì Node temp là head luôn
7 }
6 }else{
8 else{
7 temp->next = head; // Trỏ next của temp = head hiện tại 9 p = head;// Khởi tạo p trỏ tới head
8 head = temp; // Đổi head hiện tại = temp(Vì temp bây giờ là head mới 10 while(p->next != NULL){
9 mà) 11 p = p->next;//Duyệt danh sách liên kết đến cuối. Node cuối là node có next = NULL
10 } 12 }
return head; 13 //Gán next của pt cuối = temp. Khi đó temp sẽ là pt cuối(temp->next = NULL mà)
} 14 p->next = temp; }
15 return head;
16 }

12/09/2021 12/09/2021

Danh sách liên kết (5) Danh sách liên kết (6)
n Xóa Node khỏi danh sách liên kết
n Thêm Node vào danh sách liên kết
¨ Thêm vào vị trí bất kỳ ¨ Xóa đầu
1 node AddAt(node head, int value, int position){
2 if(position == 0 || head == NULL){
3 head = AddHead(head, value); // Nếu vị trí chèn là 0, tức là thêm vào đầu
4
5
}else{
// Bắt đầu tìm vị trí cần chèn. Ta sẽ dùng k để đếm cho vị trí 1 node DelHead(node head){
6
7
int k = 1;
node p = head;
2 if(head == NULL){
8
9
while(p != NULL && k != position){
p = p->next;
3 printf("\nCha co gi de xoa het!");
10 ++k; 4 }else{
11 }
12 5 node p = head;
13 if(k != position){
14 // Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định chèn cuối 6 head = head->next;
15
16
// Nếu bạn không muốn chèn, hãy thông báo vị trí chèn không hợp lệ
head = AddTail(head, value);
7 delete p;
17
18
// printf("Vi tri chen vuot qua vi tri cuoi cung!\n");
}else{
8 }
19 node temp = CreateNode(value); return head;
20 temp->next = p->next;
21 p->next = temp; }
22 }
23 }
24 return head;
25 }
12/09/2021 12/09/2021

Danh sách liên kết (6) Danh sách liên kết (7)
n Xóa Node khỏi danh sách liên kết n Xóa Node khỏi danh sách liên kết
¨ Xóa cuối ¨ Xóa ở vị trí bất kỳ
1 node DelTail(node head){ 1 node DelAt(node head, int position){
2 if (head == NULL || head->next == NULL){ 2
3
if(position == 0 || head == NULL || head->next == NULL){
head = DelHead(head); // Nếu vị trí chèn là 0, tức là thêm vào đầu
3 return DelHead(head); 4
5
}else{
// Bắt đầu tìm vị trí cần chèn. Ta sẽ dùng k để đếm cho vị trí
4 } 6 int k = 1;
7 node p = head;
5 node p = head; 8 while(p->next->next != NULL && k != position){
9 p = p->next;
6 while(p->next->next != NULL){ 10 ++k;
7 p = p->next; 11
12
}

8 } 13
14
if(k != position){
// Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định xóa cuối
9 node q = p->next; 15 // Nếu bạn không muốn xóa, hãy thông báo vị trí xóa không hợp lệ
16 head = DelTail(head);
10 p->next = p->next->next; // Cho next bằng NULL 17 // printf("Vi tri xoa vuot qua vi tri cuoi cung!\n");
18 }else{
11 // Hoặc viết p->next = NULL cũng được 19 node q = p-next;
12 delete q; 20
21
p->next = p->next->next;
delete q;
return head; 22
23 }
}

} }
return head;

12/09/2021 12/09/2021
1 node DelAt(node head, int position){
2
3
if(position == 0 || head == NULL || head->next == NULL){
head = DelHead(head); // Nếu vị trí chèn là 0, tức là thêm vào đầu
Danh sách liên kết (7)
4 }else{
5 // Bắt đầu tìm vị trí cần chèn. Ta sẽ dùng k để đếm cho vị trí n Lấy giá trị ở vị trí bất kỳ
6 int k = 1; ¨
7 node p = head;
8 while(p->next->next != NULL && k != position){
1 int Get(node head, int index){
9 p = p->next; 2 int k = 0;
10 ++k; 3 node p = head;
11 } 4 while(p->next != NULL && k != index){
12
13 if(k != position){
5 ++k;
14 // Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định xóa cuối 6 p = p->next;
15 // Nếu bạn không muốn xóa, hãy thông báo vị trí xóa không hợp lệ 7 }
16 head = DelTail(head); 8 return p->data;
17 // printf("Vi tri xoa vuot qua vi tri cuoi cung!\n");
18 }else{
9 }
19 node q = p-next;
20 p->next = p->next->next;
21 delete q;
22 }
23 }
return head;
}
12/09/2021 12/09/2021

Danh sách liên kết (8) Danh sách liên kết (9)
n Tìm kiếm trong danh sách liên kết n Duyệt danh sách liên kết
¨ Hàm tìm kiếm này sẽ trả về chỉ số của Node đầu tiên có giá trị 1 void Traverser(node head){
bằng với giá trị cần tìm. Nếu không tìm thấy, chúng ta trả về -1. 2 for(node p = head; p != NULL; p = p->next){
3 cout<<setw(5)<< p->data;
1 int Search(node head, int value){ 4 }
2 int position = 0; 5 cout<<endl;
3 for(node p = head; p != NULL; p = p->next){ 6 }
4 if(p->data == value){
5 return position;
6 } n Xoá tất cả các phần tử trong danh sách
7 ++position; 1 node DelByVal(node head, int value){
8 } 2 int position = Search(head, value);
9 return -1; 3 while(position != -1){
10 } 4 DelAt(head, position);
5 position = Search(head, value);
6 }
7 return head;
8 }
12/09/2021 12/09/2021

Danh sách liên kết (10) Ngăn xếp


n Một số hàm khác 1 node InitHead(){
¨ Hàm khởi tạo Node head 2 node head; n Ngăn xếp là gì?
3 head = NULL;
4 return head; ¨ Là một danh sách nhưng các phép toán chỉ được
5 }
thực hiện ở một đỉnh của danh sách.
¨ Hàm lấy số phần tử của DSLK
1 int Length(node head){
n Tính chất
2 int length = 0;
3 for(node p = head; p != NULL; p = p->next){ ¨ Vào trước ra sau (First In Last Out: FILO)
4 ++length;
5 }
6 return length;
7 }

38
12/09/2021
Ngăn xếp Ngăn xếp
— Trừu tượng hóa cấu trúc ngăn xếp n Ứng dụng
— Đặc tả dữ liệu ¨ Trực tiếp
A = (a0, a1, …, an-1)
trong đó an-1 là đỉnh ngăn xếp n Nhật trình lướt web lưu trong trình duyệt
— Đặc tả các phép toán n Chuỗi undo trong một trình soạn thảo văn bản
1. Thêm phần tử x vào đỉnh ngăn xếp: push(x) n Việc lưu trữ các biến cục bộ khi một hàm gọi hàm
2. Loại phần tử ở đỉnh ngăn xếp: pop() khác và hàm này lại gọi tới hàm khác nữa, …
3. Kiểm tra ngăn xếp có rỗng hay không: isEmpty()
¨ Gián tiếp
4. Kiểm tra ngăn xếp có đầy hay không: isFull()
5. Đếm số phần tử của ngăn xếp: size() n Cấu trúc dữ liệu phụ trợ cho các thuật toán
6. Trả về phần tử ở đỉnh ngăn xếp: top() n Một phần của CTDL khác

39 40

Ngăn xếp Ngăn xếp


n Ngăn xếp chạy chương trình của n Cài đặt ngăn xếp
C++
n Hệ thống chạy chương trình của C++ ¨ Sử dụng mảng
dùng một ngăn xếp để quản lý một n Kích thước tối đa của ngăn xếp phải được chỉ định trước và
chuỗi các hàm đang thực thi không thể thay đổi
n Khi một hàm được gọi, hệ này push vào n Cố push phần tử mới vào ngăn xếp đã đầy sẽ sinh ngoại lệ
ngăn xếp một frame chứa: do cài đặt (implementation-specific exception)
¨ các biến cục bộ và giá trị trả về
¨ con đếm chương trình (program counter)
¨ Sử dụng danh sách liên kết
để theo dõi câu lệnh đang được thực hiện các nút
n Khi một hàm trả về gì đó, frame của nó
bị pop khỏi ngăn xếp và quyền điều t Æ
khiển được chuyển cho hàm ở đỉnh
ngăn xếp.

41 12/09/2021 các phần tử

Ngăn xếp Ngăn xếp


n Ứng dụng
n Ứng dụng: n Kiểm tra biểu thức dấu ngoặc cân xứng
¨ Kiểm tra biểu thức dấu ngoặc cân xứng ¨ Thuật toán
Algorithm ParenMatch(X,n):
n Mỗi ngoặc mở “(“, “[“, “{“ phải được cặp với một Input: An array X of n tokens, each of which is either a grouping symbol, a

ngoặc đóng “)“, “]“, “}“ tương ứng. variable, an arithmetic operator, or a number
Output: true if and only if all the grouping symbols in X match
n Ví dụ Let S be an empty stack
for i=0 to n-1 do
¨ cân xứng: ( )(( )){([( )])} if X[i] is an opening grouping symbol then
¨ không cân xứng: ((( )(( )){([( )])} S.push(X[i])
else if X[i] is a closing grouping symbol then
¨ không cân xứng: )(( )){([( )])} if S.isEmpty() then
¨ không cân xứng: ({[ ])} return false {nothing to match with}
if S.pop() does not match the type of X[i] then
¨ không cân xứng: ( return false {wrong type}
if S.isEmpty() then
return true {every symbol matched}
else
43
return false {some symbols were never matched}
44
Ngăn xếp Ngăn xếp
n Ứng dung n Bài tập
¨ Kiểm tra thẻ HTML cân xứng
n Mỗi thẻ mở <name> phải được cặp với một thẻ đóng </name> tương ứng 1. Viết chương trình cài đặt ngăn xếp bằng
mảng.
<body>
<center> The Little Boat 2. Viết chương trình cài đặt ngăn xếp bằng
<h1> The Little Boat </h1>

danh sách liên kết.


</center>
The storm tossed the little boat
<p> The storm tossed the little
like a cheap sneaker in an old
boat like a cheap sneaker in an
washing machine. The three
3. Với mỗi phép toán trong câu 1, 2 tính độ
old washing machine. The three
drunken fishermen were used to drunken fishermen were used to
such treatment, of course, but such treatment, of course, but not
not the tree salesman, who even as
a stowaway now felt that he
the tree salesman, who even as
a stowaway now felt that he had
phức tạp.
had overpaid for the voyage. </p> overpaid for the voyage.
<ol>
<li> Will the salesman die? </li> 4. Viết chương trình kiểm tra tính hợp lệ các
1. Will the salesman die?
cặp ngoặc ( ) [ ] { } cho một chương trình
<li> What color is the boat? </li>
<li> And what about Naomi? </li> 2. What color is the boat?
</ol> 3. And what about Naomi?
45 46
</body>
C++.

Hàng đợi KDLTT hàng đợi


n Hàng đợi là gì?
¨ Là một danh sách nhưng các phép toán chỉ được thực hiện ở hai đỉnh của danh
— Trừu tượng hóa cấu trúc hàng đợi
sách. Một đỉnh gọi là đầu hàng, đỉnh còn lại gọi là cuối hàng. — Đặc tả dữ liệu
n Tính chất A = (a0, a1, …, an)
¨ Vào trước ra trước (First In First Out: FIFO) trong đó a0 là đầu hàng đợi, an là cuối hàng đợi
— Đặc tả các phép toán
1. Thêm phần tử x vào cuối hàng đợi: enqueue(x)
2. Loại phần tử ở đầu hàng đợi: dequeue()
3. Kiểm tra hàng đợi có rỗng hay không: isEmpty()
4. Kiểm tra hàng đợi hết chỗ hay chưa: isFull()
5. Đếm số phần tử của hàng đợi: size()
6. Trả về phần tử ở đầu hàng đợi: front()

47 48

Hàng đợi Hàng đợi


n Ứng dụng n Cài đặt hàng đợi bởi mảng
¨ Dùng một mảng cỡ N theo kiểu vòng tròn
¨ Trực tiếp ¨ Dùng 2 biến để theo dõi đầu (front) và đuôi (rear) hàng đợi
n f là chỉ số của phần tử front
n Danh sách hàng đợi n r là chỉ số của ô liền sau phần tử rear
n Quản lý truy cập tới các tài nguyên dùng chung (ví ¨ Ô r trong mảng sẽ luôn rỗng
dụ máy in)
n Multiprogramming
cấu hình bình thường
¨ Gián tiếp Q
n Cấu trúc dữ liệu phụ trợ cho các thuật toán 0 1 2 f r
cấu hình bọc vòng quanh
n Một phần của CTDL khác
Q
0 1 2 r f
49 50
Hàng đợi Hàng đợi
n Các thao tác
n Các thao tác
¨ Ta sử dụng phép chia lấy dư ¨ Thao tác enqueue ném một ngoại lệ nếu mảng đã đầy
Algorithm size() ¨ Đây là ngoại lệ do cài đặt
return (N - f + r) mod N Algorithm enqueue(o)
if size() = N - 1 then
Algorithm isEmpty() throw FullQueueException
return (f = r) else
Q[r] ¬ o
Q r ¬ (r + 1) mod N
0 1 2 f r
Q
Q 0 1 2 f r
0 1 2 r f
Q
51 52
0 1 2 r f

Hàng đợi Hàng đợi


n Các thao tác n Ta có thể cài đặt hàng đợi bởi một danh sách liên kết
¨ Thao tác dequeue ném ngoại lệ nếu hàng đợi rỗng đơn
¨ Đây là ngoại lệ xác định cho KDLTT hàng đợi ¨ Phần tử front được lưu ở nút đầu
Algorithm dequeue() ¨ Phần tử rear được lưu ở nút cuối
if isEmpty() then n Không gian sử dụng là O(n) và mỗi thao tác thực hiện
throw EmptyQueueException trong thời gian O(1) r
else
o ¬ Q[f] các nút
f ¬ (f + 1) mod N
return o t Æ
Q
0 1 2 f r
Q
0 1 2 r f 12/09/2021 các phần tử
53

Hàng đợi Bài tập


n Ứng dụng: Lập lịch quay vòng 1. Viết chương trình cài đặt hàng đợi bằng
(Round Robin Schedulers)
mảng.
n Có thể cài đặt một bộ lập lịch quay vòng bằng một hàng đợi,
Q, bằng việc lặp lại các bước sau: 2. Viết chương trình cài đặt hàng đợi bằng
e = Q.dequeue()
1.
2. Service element e
danh sách liên kết đơn.
3. Q.enqueue(e) 3. Cài đặt hàng đợi bằng mảng vòng.
The Queue

1. Deque the 2. Service the 3. Enqueue the


next element next element serviced element

Shared
Service

56
55
Nội dung
n Lớp – Quyền truy xuất
Lập trình C/C++ n Khai báo, định nghĩa 1 lớp đơn giản
LỚP và ĐỐI TƯỢNG n Hàm thành viên nội tuyến (inline)
n Hàm xây dựng (constructor)
(CLASS & OBJECT) n Hàm hủy (destructor)
Khoa Công Nghệ Thông Tin Đại học n Hàm bạn (friend) – Lớp bạn
Bách khoa – Đại học Đà Nẵng n Đối số mặc định
n Đối số thành viên ẩn (con trỏ this)

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 1 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 2

Nội dung (tt) Khái niệm lớp


private
n Toán tử phạm vi n Lớp: kiểu dữ liệu trừu tượng. protected
public
n Danh sách khởi tạo thành viên Đặc tả TÊN LỚP class Classname {
đối
n Thành viên hằng - Thành viên tĩnh tượng <Quyền truy xuất > :
DataType1 memberdata1;
Dữ liệu
n Thành viên tham chiếu thành viên
DataType2 memberdata2;
…………….
n Thành viên là đối tượng của 1 lớp
< Quyền truy xuất > :
n Mảng các đối tượng Tập các Hàm memberFunction1();
thao tác thành viên memberFunction2();
n Phạm vi lớp …………..
n Cấu trúc (structure) và hợp (union) }; class Point {
int xVal, yVal;
n Các trường bit public:
void SetPt (int, int);
void OffsetPt (int, int);
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 3 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng }; 4

Đối tượng Đóng gói trong C++


n Đối tượng(Object): là một thể hiện thuộc lớp, một thực thể n Khái niệm đóng gói có sẵn trong C++ class: ta có thể hạn
có thực chế quyền truy nhập đến các thành viên của đối tượng
¨ Khai báo: n Sử dụng một bộ từ khoá để mô tả quyền truy nhập:
<Classname> <Objectname>;
n private
¨ Để truy xuất đến một thành phần của đối tượng, truy xuất giống ¨ nếu một thành viên của một lớp được khai báo là private, nó chỉ được truy nhập đến
từ bên trong lớp đó
như kiểu struct ¨ Mặc định: mọi thành viên của class là private, do đó nhấn mạnh khái niệm đóng gói
n Thành viên dữ liệu: của lập trình hướng đối tượng.

Objectname.datamember n Public
¨ Các thành viên được khai báo là public có thể được truy nhập từ bên ngoài đối
n Hàm thành viên: tượng
¨ là mặc định đối với các thành viên của struct
Objectname. Memberfunction(parameter)
¨ Ví dụ: n protected
Point pt; n friend
pt.SetPt(10,20);
pt.OffsetPt(2,2);
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 5 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 6
Đóng gói trong C++ Ví dụ: Lớp đơn giản
Tạo ra
n Khi nào sử dụng quyền nào? đối tượng
class Point { thuộc lớp
¨ Theo phong cách lập trình hướng đối tượng tốt, ta sẽ int xVal, yVal; void main() { Point
giữ mọi thành viên dữ liệu ở dạng private (che dấu dữ public: Point pt;
Khai báo void SetPt (int, int);
liệu). Lớp void OffsetPt (int, int); pt.SetPt(10,20);
}; pt.OffsetPt(2,2);
¨ Các phương thức thường khai báo là public để có thể …….. Gọi hàm
void Point::SetPt (int x, int y) { trên
liên lạc được với đối tượng từ bên ngoài(giao diện của Định nghĩa xVal = x; đối tượng
đối tượng). các hàm yVal = y;
thành viên } pt.xVal = 10; // Đúng hay sai?
¨ Các phương thức tiện ích chỉ được dùng bởi các void Point::OffsetPt (int x, int y) {
xVal += x; Point pt1, pt2, pt3;
phương thức khác trong cùng lớp nên được khai báo ……….
yVal += y;
private. }
}

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 7 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 8

Khai báo các phương thức Khai báo các phương thức
n Giao diện của phương thức luôn đặt trong định nghĩa
lớp, cũng như các khai báo thành viên dữ liệu.
n Hàm inline:
n Phần cài đặt (định nghĩa phương thức) có thể đặt trong ¨ Cải thiện tốc độ thực thi
định nghĩa lớp hoặc đặt ở ngoài. ¨ Tốn bộ nhớ (dành cho mã lệnh) khi thực thi.
n Hai lựa chọn: Cách 2:
class Point { class Point {
class Point { class Point { int xVal, yVal; thêm int xVal, yVal;
Từ
int xVal, yVal; int xVal, yVal; Cách 1: public: public:
public: public: void SetPt (int x, int y) { khóa void SetPt (int, int);
void SetPt (int, int); void SetPt (int x, int y) { Định xVal = x; inline void OffsetPt (int, int);
void OffsetPt (int, int); xVal = x; nghĩa yVal = y; };
}; yVal = y; bên }
inline void Point::SetPt (int x, int y) {
void Point:: SetPt (int x, int y) { } trong void OffsetPt (int x, int y) {
xVal = x;
xVal = x; yVal = y; void OffsetPt (int, int); lớp xVal += x;
yVal = y;
yVal += y;
} }; } }
}; ……………
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 9 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 10

Ví dụ - Lớp Set (tập hợp) Ví dụ - Lớp Set (tt)


Bool Set::IsMember (const int elem) { void Set::Copy (Set &set) { ………
#include <iostream.h>
for (register i = 0; i < card; ++i) for (register i = 0; i < card; ++i) int main (void) {
const maxCard = 100;
if (elems[i] == elem) set.elems[i] = elems[i]; Set s1, s2;
enum Bool {false, true}; return true; set.card = card; s1.EmptySet(); s2.EmptySet();
class Set { return false; } s1.AddElem(10); s1.AddElem(20);
private: } Bool Set::Equal (Set &set) {
void Set::AddElem (const int elem) { s1.AddElem(30); s1.AddElem(40);
int elems[maxCard]; if (card != set.card)
if (IsMember(elem)) s2.AddElem(30); s2.AddElem(50);
int card; return false;
return; s2.AddElem(10); s2.AddElem(60);
public: if (card < maxCard) for (register i = 0; i < card; ++i)
if (!set.IsMember(elems[i])) cout << "s1 = "; s1.Print();
void EmptySet(){ card = 0; } elems[card++] = elem;
return false; cout << "s2 = "; s2.Print();
Bool IsMember (const int); else
cout << "Set overflow“<<endl; return true; s2.RmvElem(50);
void AddElem (const int);
} } cout << "s2 - {50} = ";
void RmvElem (const int); void Set::RmvElem (const int elem) { Kết
void Set::Print (void) { s2.Print();
void Copy (Set&); for (register i = 0; i < card; ++i) cout << "{"; if (s1.IsMember(20))
quả ?
Bool Equal (Set&); if (elems[i] == elem) { for (int i = 0; i < card-1; ++i)
for (; i < card-1; ++i) // Dịch cout << "20 is in s1\n";
void Intersect (Set&, Set&); cout << elems[i] << ","; if (!s1.Equal(s2))
elems[i] = elems[i+1];
void Union (Set&, Set&); --card; if (card > 0)
cout << "s1 <> s2\n";
} cout << elems[card-1];
void Print (); return 0;
} cout << "}“<<endl;
}; } }

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 11 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 12
Đặt khai báo lớp ở đâu? File header car.h
n Để đảm bảo tính đóng gói, ta thường đặt khai // car.h
#ifndef CAR_H
báo của lớp trong file header #define CAR_H
¨ tên file thường trùng với tên lớp. Ví dụ khai báo lớp class Car {
Car đặt trong file “car.h” public:
//...
n Phần cài đặt (định nghĩa) đặt trong một file void drive(int speed, int distance);
nguồn tương ứng //...
void stop();
¨ “car.cpp” hoặc “car.cc” //...
void turnLeft();
n Quy ước đặt khai báo/định nghĩa của lớp trong private:
file trùng tên lớp được chấp nhận rộng rãi trong int vin; //...
C++ string make; //...
string model; //...
¨ là quy tắc bắt buộc đối với các lớp của Java string color; //...
};
#endif

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 13 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 14

Định nghĩa các phương thức Định nghĩa các phương thức
n Định nghĩa của các phương thức cần đặt trong 1 file n Khi định nghĩa một phương thức, ta cần sử dụng toán tử
nguồn trùng tên với tên lớp phạm vi để trình biên dịch hiểu đó là phương thức của
n File bắt đầu với các lệnh #include và có thể có các khai một lớp cụ thể chứ không phải một hàm thông thường
báo using cho các namespace khác
n Bên cạnh việc include các thư viện C++ cần thiết, ta con ¨ Ví dụ, định nghĩa phương thức drive của lớp Car được viết
như sau Toán tử định phạm vi
phải include header file chứa khai báo lớp
// car.cpp
// car.cpp ...
#include <iostream> void Car::drive(int speed, int distance)
#include <string> {
#include “car.h” //method definition
using namespace std; Tên lớp }
...
Tên phương thức

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 15 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 16

Định nghĩa các phương thức Đối số mặc định


n Vậy cấu trúc của file nguồn lớp Car có thể n Đối số mặc định tính từ bên phải.
như sau: class Point { class Point {
int xVal, yVal; int xVal, yVal;
// car.cpp public: public:
#include <iostream.h> Point (int x = 0, int y = 0); Point (int x = 0, int y = 0);
//... Point (float x=0, float y=0);
#include <string.h> }; //...
#include “car.h” }; Tối nghĩa
void main() { Mơ hồ
void Car::drive(int speed, int distance) {…} Point p1; // như là ??? void main() {
void Car::stop() {…} Point p2(10); // như là ??? Point p2(1.6, 5.0); // như là ???
Point p3(10,20); Point p3(10,20); // như là ???
void Car::turnLeft() {…} Point p4(, 20); // ????? Point p4; // ?????
….. …..
} }

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 17 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 18
Đối số thành viên ẩn Con trỏ this
n Con trỏ *this: n Tuy không bắt buộc sử dụng tường minh con trỏ this, ta
có thể dùng nó để giải quyết vấn đề tên trùng và phạm vi
¨ Là 1 thành viên ẩn, có thuộc tính là private.
void Foo::bar()
¨ Trỏ tới chính bản thân đối tượng. {
int x;
x = 5; // local x
void Point::OffsetPt (int xValL, int yVal) { void Point::OffsetPt (int x, int y) { this->x = 6; // this instance’s x
this->xVal += xVal; this->xVal += x; }
this->yVal += yVal; this->yVal += y;
} } hoặc
void Foo::bar(int x)
{
this->x = x;
• 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

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 19 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 20

Con trỏ this Toán tử phạm vi


n Con trỏ this được các phương thức tự động n Toán tử :: dùng để xác định chính xác hàm
sử dụng, nên việc ta có sử dụng nó một cách (thuộc tính) được truy xuất thuộc lớp nào.
tường minh hay bỏ qua không ảnh hưởng đến n Câu lệnh: pt.OffsetPt(2,2);
tốc độ chạy chương trình
<=> pt.Point::OffsetPt(2,2);
n Nhiều lập trình viên sử dụng this một cách
n Cần thiết trong một số trường hợp:
tường minh mỗi khi truy nhập các thành viên dữ
¨ Cách gọi hàm trong thừa kế.
liệu
¨ Tên thành viên bị che bởi biến cục bộ.
¨ để đảm bảo không có rắc rối về phạm vi
Ví dụ: Point(int xVal, int yVal) {
¨ ngoài ra, còn để tự nhắc rằng mình đang truy nhập
Point::xVal = xVal;
thành viên Point::yVal = yVal;
n Lựa chọn có dùng hay không là tuỳ ở mỗi người }
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 21 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 22

Hàm xây dựng (Constructor) Hàm xây dựng (tt)


n Dùng để định nghĩa và khởi tạo đối tượng cùng 1 lúc. class Set { Mềm
class Point {
n Có tên trùng với tên lớp, không có kiểu trả về. private: dẻo
int xVal, yVal;
n Không gọi trực tiếp, sẽ được tự động gọi khi khởi tạo đt. int *elems; hơn
public:
n Gán giá trị, cấp vùng nhớ cho các dữ liệu thành viên. int maxCard;
Point(){xVal=yVal=0;} int card;
n Constructor có thể được khai báo chồng (đa năng hoá) như các Point (int x=0, int y=0) { public:
hàm C++ thông thường khác xVal = x; yVal = y; Set(const int size) {
¨ cung cấp các kiểu khởi tạo khác nhau tuỳ theo các đối số được cho khi } elems = new int[size];
tạo thể hiện Point (float len, float angle) { maxCard = size;
xVal = (int) (len * cos(angle)); card = 0;
class Point { void main() { yVal = (int) (len * sin(angle)); }
int xVal, yVal; Point pt1(10,20); } ……………
public: pt1.OffsetPt(2,2); void OffsetPt (int , int ); … };
…….. Không cần
Point (int x, int y) { }; void main() {
xVal = x; yVal = y; // Khai báo nào là sai ?
void main() {
phải nhớ
Point pt2; Set s1(100);
}
Point p1; Set s2(20); gọi hàm
Point pt3();
void OffsetPt (int x, int y) {
Point pt4 = Point(5,5); Point p2(10,20); Set s3(1000); … EmptySet()
xVal += x; yVal += y; Point p3(60.3, 3.14); khi khởi tạo
}
Point pt5 = new Point(5,5); }
………. }
}; }

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 23 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 24
Hàm xây dựng Copy constructor
n Đối với constructor mặc định, nếu ta không cung n Copy constructor là constructor đặc biệt được gọi khi ta tạo
đối tượng mới là bản sao của một đối tượng đã có sẵn
cấp một phương thức constructor nào, C++ sẽ MyClass x(5);
tự sinh constructor mặc định là một phương MyClass y = x; hoặc MyClass y(x);
thức rỗng (không làm gì) n C++ cung cấp sẵn một copy constructor, nó chỉ đơn giản
¨ mục đích để luôn có một constructor nào đó để gọi copy từng thành viên dữ liệu từ đối tượng cũ sang đối
khi không có tham số nào tượng mới.
n Tuy nhiên, trong nhiều trường hợp, ta cần thực hiện các
n Tuy nhiên, nếu ta không định nghĩa constructor công việc Khởi tạo khác trong copy constructor
mặc định nhưng lại có các constructor khác, ¨ Thí dụ: lấy giá trị cho một ID duy nhất từ đâu đó, hoặc thực
trình biên dịch sẽ báo lỗi không tìm thấy hiện sao chép “sâu” (chẳng hạn khi một trong các thành viên
constructor mặc định nếu ta không cung cấp là con trỏ giữ bộ nhớ cấp phát động)
tham số khi tạo thể hiện. n Trong trường hợp đó, ta có thể định nghĩa lại copy
constructor
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 25 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 26

Copy constructor Hàm hủy (Destructor)


n Dọn dẹp 1 đối tượng trước khi nó được thu hồi.
n Khai báo cho copy constructor của lớp Foo: n Destructor không có giá trị trả về, và không thể định nghĩa lại (nó không bao
giờ có tham số)
Foo(const Foo& existingFoo); n
¨ mỗi lớp chỉ có 1 destructor
Cú pháp: ~TenLop() { ……... }
n Không gọi trực tiếp, sẽ được tự động gọi khi hủy bỏ đt.
n Thu hồi vùng nhớ cho các dữ liệu thành viên là con trỏ.
n nếu ta không cung cấp destructor, C++ sẽ tự sinh một destructor rỗng(không
tham số là đối tượng làm gì cả)
được sao chép class Set { Set TestFunct1(Set s1) {
private: Set *s = new Set(50); Tổng cộng
Kiểu tham số là tham chiếu return *s;
đến đối tượng kiểu Foo int *elems; có bao
int maxCard; } nhiêu lần
int card; hàm hủy
void main() {
public: được gọi ?
từ khoá const được dùng để đảm bảo đối Set s1(40), s2(50);
Set(const int size) { …… }
tượng được sao chép sẽ không bị sửa đổi s2 = TestFunct1(s1);
~Set() { delete[] elems; }
}
….
};

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 27 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 28

Bạn (Friend) – Đặt vấn đề Hàm bạn (Friend)


Tập Các Hàm SetToReal
Số Nguyên dùng để chuyển n Cách 1: Khai báo hàm thành viên của lớp
tập số nguyên
thành tập số thực IntSet là bạn (friend) của lớp RealSet.
class IntSet {
public: void IntSet::SetToReal (RealSet &set) { class IntSet {
//... set.card = card; public:
for (register i = 0; i < card; ++i) Giữ nguyên định
void SetToReal (RealSet&); //...
nghĩa của lớp IntSet
private: set.elems[i] = (float) elems[i]; void SetToReal (RealSet&);
int elems[maxCard]; } private:
int card; int elems[maxCard];
}; int card;
};
class RealSet { Làm thế nào
public: để thực hiện class RealSet {
Tập Các //... được việc truy Thêm dòng khai báo public:
Số Thực private: xuất Friend cho //...
float elems[maxCard]; đến thành viên hàm thành viên friend void IntSet::SetToReal (RealSet&);
int card; Private ? SetToReal private:
}; float elems[maxCard];
int card;
};
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 29 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 30
Hàm bạn (Friend) Bạn (Friend)
n Cách 2: n Hàm bạn:
¨ Chuyển hàm SetToReal ra ngoài (độc lập). ¨ Có quyền truy xuất đến tất cả các dữ liệu và
¨ Khai báo hàm đó là bạn của cả 2 lớp. hàm thành viên (protected + private) của 1 lớp.
class IntSet {
¨ Lý do:
public:
//...
void SetToReal (IntSet& iSet, n Cách định nghĩa hàm chính xác.
RealSet& rSet )
friend void SetToReal (IntSet &, RealSet&);
{ n Hàm cài đặt không hiệu quả.
private:
int elems[maxCard];
int card;
rSet.card = iSet.card;
for (int i = 0; i < iSet.card; ++i) n Lớp bạn:
}; rSet.elems[i] =
class RealSet { (float) iSet.elems[i]; ¨ Tất cả các hàm trong lớp bạn: là hàm bạn.
public:
//... }
friend void SetToReal (IntSet &, RealSet&); class A; class IntSet { ……….. }
private: Hàm độc lập class B { // ………. class RealSet { // ……….
float elems[maxCard]; là bạn(friend) friend class A; friend class IntSet;
int card; của cả 2 lớp.
}; }; };

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 31 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 32

friend – khai báo forward friend – khai báo forward


n Giải pháp: sử dụng khai báo forward (forward
n Một điều cần phải chú ý khi khai báo phương thức đơn
lẻ là friend: declaration) cho lớp cấp quan hệ friend (trong ví
¨ Nhớ lại cách ta đã khai báo phương thức SetToReal dụ là RealSet)
(RealSet&) là friend của RealSet n Ta khai báo các lớp trong ví dụ như sau:
class RealSet {
public: Class RealSet; // Forward declaration
friend void IntSet::SetToReal (RealSet&); class IntSet {
public:
private:
void SetToReal (RealSet&);

private:
}; …
¨ Khi xử lý phần này, trình biên dịch cần phải biết là đã có };
lớp IntSet class RealSet {
¨ Tuy nhiên các phương thức của IntSet lại dùng đến public:
RealSet nên phải có lớp RealSet trước khi định nghĩa IntSet friend void IntSet::SetToReal (RealSet&);
n Cho nên ta không thể tạo IntSet khi chưa tạo RealSet và private:
không thể tạo RealSet khi chưa tạo IntSet …
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 33 }; Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng
Khoa Công 34

friend – khai báo forward friend – khai báo forward


n Tuy nhiên, không thể làm ngược lại (khai n Lý do: trình biên dịch phải nhìn thấy khai báo phương
thức trong lớp nhận trước khi tạo mối quan hệ friend tại
báo forward cho lớp IntSet) lớp cho (granting class)
class IntSet; // Forward declaration ¨ Trong ví dụ, trình biên dịch phải biết khai báo IntSet::SetToReal
class RealSet { (RealSet&) tại khai báo của IntSet trước khi có thể tạo mối quan
public:
hệ friend của IntSet::SetToReal (RealSet&) với RealSet
friend void IntSet::SetToReal (RealSet&);
private: ¨ Khai báo forward cho một lớp chỉ cho trình biên dịch biết về sự
… có mặt của lớp mà không cho biết về các thành viên của lớp đó
Trình biên dịch chưa biết SetToReal
};
class IntSet {
n Vậy: cần khai báo forward cho lớp cấp quyền friend
public: ¨ trong ví dụ trên là RealSet
void SetToReal (RealSet&);
private:

};

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 35 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 36
friend – Ví dụ friend – Ví dụ
n Khai báo hàm nhân ma trận với vecto n Khai báo hàm nhân ma trận với vecto không dùng hàm bạn
const int N = 4;
class Vector
Vector Multiply(const Matrix &m, const Vector &v)
{
double a[N]; {
public: Vector r;
double Get(int i) const {return a[i];} for (int i = 0; i < N; i++)
void Set(int i, double x) {a[i] = x;} {
}; r.Set(i, 0);
class Matrix for (int j = 0; j < N; j++)
{ r.Set(i, r.Get(i)+ m.Get(i,j)*v.Get(j));
double a[N][N]; }
public: return r;
double Get(int i, int j) const {return a[i][j];}
}
void Set(int i, int j, double x) {a[i][j] = x;}
//...
};
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 37 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 38

friend – Ví dụ friend – Ví dụ
n Khai báo hàm nhân ma trận với vecto có dùng hàm bạn
const int N = 4; Vector Multiply(const Matrix &m, const Vector &v)
class Matrix; // khai báo forward {
class Vector {
double a[N]; Vector r;
public: for (int i = 0; i < N; i++)
double Get(int i) const {return a[i];} {
void Set(int i, double x) {a[i] = x;}
friend Vector Multiply(const Matrix &m, const Vector &v); r.a[i] = 0;
}; for (int j = 0; j < N; j++)
class Matrix { r.a[i] += m.a[i][j]*v.a[j];
double a[N][N];
public: }
double Get(int i, int j) const {return a[i][j];} return r;
void Set(int i, int j, double x) {a[i][j] = x;} }
friend Vector Multiply(const Matrix &m, const Vector &v);
};
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 39 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 40

Danh sách khởi tạo thành viên Thành viên hằng


n Thành viên dữ liệu hằng:
n Tương đương việc gán giá trị dữ liệu thành viên. ¨ Khi một thành viên dữ liệu được khai báo là const, thành viên đó sẽ giữ nguyên giá trị
trong suốt thời gian sống của đối tượng chủ.

class Image {
class Point { class Image { public:
int xVal, yVal; public: Image(const int w, const int h);
Image(const int w, const int h); Khai báo bình thường
public: private: như dữ liệu thành viên
private: const int width;
Point (int x, int y) { int width; const int height;
xVal = x; int height; //...
yVal = y; //... };
} };
Image::Image(const int w, const int h) { class Image {
// ……………………
width = w; const int width = 256;
}; height = h; Khởi tạo const int height = 168;
//..................... SAI //...
} };
Point::Point (int x, int y)
: xVal(x), yVal(y) Image::Image (const int w, const int h)
{ } Image::Image (const int w, const int h) Khởi tạo ĐÚNG
: width(w), height(h)
{ //............... }
: width(w), height(h) thông qua danh sách
{//………..} khởi tạo thành viên

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 41 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 42
Thành viên hằng Thành viên hằng
n Hằng đối tượng: không được thay đổi giá trị. inline char *strdup(const char *s){
n Hàm thành viên hằng: return strcpy(new char[strlen(s) + 1], s);
¨ Được phép gọi trên hằng đối tượng.(đảm bảo không thay đổi giá
trị của đối tượng chủ) }
¨ Không được thay đổi giá trị dữ liệu thành viên. class string{
n nên khai báo mọi phương thức truy vấn là hằng, vừa để char *p;
báo với trình biên dịch, vừa để tự gợi nhớ. public:
class Set { void main() { string(char *s = "") {p = strdup(s);}
public: const Set s;
Set(void){ card = 0; } s.AddElem(10); // SAI ~string() {delete [] p;}
Bool Member(const int) const; string(const string &s2) {p = strdup(s2.p);}
s.Member(10); // OK
void AddElem(const int);
//... void Output() const {cout << p;}
}; }
void ToLower() {strlwr(p);}
Bool Set::Member (const int elem) const
{ //... };
}

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 43 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 44

Thành viên hằng Thành viên tĩnh


void main() n Thành viên dữ liệu tĩnh:
{ ¨ Dùng chung 1 bản sao chép (1 vùng nhớ) chia sẻ
const string Truong("DH BC TDT"); cho tất cả đối tượng của lớp đó.
string s("ABCdef"); ¨ Sử dụng: <TênLớp>::<TênDữLiệuThànhViên>
s.Output(); ¨ Thường dùng để đếm số lượng đối tượng.
s.ToLower(); class Window {
// danh sách liên kết tất cả Window
s.Output(); static Window *first; Khai báo
// con trỏ tới window kế tiếp
Truong.Output(); Window *next;
//...
Truong.ToLower(); // Bao loi }; Khởi tạo
dữ liệu
} thành viên
Window *Window::first = &myWindow;
// ……………. tĩnh

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 45 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 46

Thành viên tĩnh - Ví dụ Thành viên tĩnh - Ví dụ


n Đếm số đối tượng MyClass n Cài đặt các phương thức
¨ khai báo lớp MyClass int MyClass::count=0;
MyClass::MyClass() {
class MyClass { this->count++; // Increment the static count
public: }
MyClass(); // Constructor MyClass::~MyClass() {
~MyClass(); // Destructor this->count--; // Decrement the static count
void printCount();//Output current value of count }
private: void MyClass::printCount() {
static int count;//static member to store cout << "There are currently " << this->count
//number of instances of MyClass << " instance(s) of MyClass.\n";
}; }

Khởi tạo biến đếm bằng 0 vì ban đầu không có đối tượng nào

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 47 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 48
Thành viên tĩnh Thành viên tĩnh
Định nghĩa và khởi tạo n chương trình demo sử dụng MyClass
n thành viên tĩnh được lưu trữ độc lập với các thể int main()
{
hiện của lớp, do đó, các thành viên tĩnh phải MyClass* x = new MyClass;
được định nghĩa x->PrintCount();
int MyClass::count; MyClass* y = new MyClass;
x->PrintCount();
n ta thường định nghĩa các thành viên tĩnh trong y->PrintCount();
file chứa định nghĩa các phương thức delete x;
There are currently 1 instance(s) of MyClass.
y->PrintCount();
n nếu muốn khởi tạo giá trị cho thành viên tĩnh ta }
There are currently 2 instance(s) of MyClass.
There are currently 2 instance(s) of MyClass.
cho giá trị khởi tạo tại định nghĩa There are currently 1 instance(s) of MyClass.
int MyClass::count = 0;
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 49 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 50

Thành viên hằng tĩnh Thành viên hằng tĩnh


n Kết hợp hai từ khoá const và static, ta có n Tóm lại, ta nên khai báo:
hiệu quả kết hợp ¨ static
¨ một thành viên dữ liệu được định nghĩa là static
const là một hằng được chia sẻ giữa tất cả các đối n đối với các thành viên dữ liệu ta muốn dùng chung
tượng của một lớp. cho mọi thể hiện của một lớp
n Không như các thành viên khác, các thành viên ¨ const
static const phải được khởi tạo khi khai báo n đối với các thành viên dữ liệu cần giữ nguyên giá
class MyClass { int main() { trị trong suốt thời gian sống của một thể hiện
public: MyClass x; ¨ static const
MyClass(); MyClass y;
~MyClass(); MyClass z; n đối với các thành viên dữ liệu cần giữ nguyên
private: }
cùng một giá trị tại tất cả các đối tượng của một
static const int thirteen=13; x, y, z dùng chung một thành viên
}; thirteen có giá trị không đổi là 13
lớp
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 51 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 52

Thành viên tĩnh Thành viên tĩnh


n Hàm thành viên tĩnh: n Hàm thành viên tĩnh: ví dụ
class ¨
MyClass {
¨ Tương đương với hàm toàn cục. public:
¨ Phương thức tĩnh không được truyền con trỏ this làm tham số MyClass(); // Constructor
~MyClass(); // Destructor
ẩn. static void printCount();//Output current value of count
¨ Không thể sửa đổi các thành viên dữ liệu từ trong phương thức private:
tĩnh. static int count; // count
};
¨ Gọi thông qua: <TênLớp>::<TênHàm> int main()
class Window { {
Khai báo
// ………. MyClass::printCount(); There are currently 0 instance(s) of MyClass.
Định nghĩa
static void PaintProc () { ….. } MyClass* x = new MyClass; There are currently 1 instance(s) of MyClass.
hàm thành
// ……… x->printCount(); There are currently 2 instance(s) of MyClass.
viên tĩnh
}; MyClass* y = new MyClass; There are currently 2 instance(s) of MyClass.
void main() { x->printCount(); There are currently 1 instance(s) of MyClass.
// ……………. y->printCount();
Truy xuất
Window::PainProc(); delete x;
hàm thành
} MyClass::printCount();
viên tĩnh
}
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 53 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 54
Thành viên tĩnh Thành viên tĩnh
int CDate::DayOfMonth(int m, int y){
dayTab[2]= LeapYear(y)?29:28;
typedef int bool;
return dayTab[m];
const bool false = 0, true = 1; }
class CDate{ bool betw(int x, int a, int b){
static int dayTab[13]; return x >= a && x <= b;
int day, month, year; }
bool CDate::ValidDate(int d, int m, int y){
public: return betw(m,1,12) && betw(d,1,DayOfMonth(m,y));
CDate(int d=1, int m=1, int y=2010); }
static bool LeapYear(int y) {return y%400 == 0 || void CDate::Input(){
y%4==0 && y%100 != 0;} int d,m,y;
static int DayOfMonth(int m, int y); cin >> d >> m >> y;
static bool ValidDate(int d, int m, int y); while (!ValidDate(d,m,y))
{
void Input(); cout << "Please enter a valid date: ";
}; cin >> d >> m >> y;
int CDate::dayTab[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; }
CDate::CDate(int d=1, int m=1, int y=2010){ day = d; month = m; year = y;
if (ValidDate(d,m,y)){day=d;month=m;year=y;} }
}
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 55 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 56

Thành viên tham chiếu Thành viên là đối tượng của 1 lớp
n Tham chiếu dữ liệu thành viên: n Dữ liệu thành viên có thể có kiểu:
class Image { ¨ Dữ liệu (lớp) chuẩn của ngôn ngữ.
int width; Khai báo bình thường
int height; như dữ liệu thành viên
¨ Lớp do người dùng định nghĩa (có thể là chính lớp đó).
int &widthRef;
//... class Point { ……. };
}; class Rectangle {
public:
class Image { Rectangle (int left, int top, int right, int bottom);
int width; //... Khởi tạo cho các
int height; private: dữ liệu thành viên
Khởi tạo Point topLeft; qua danh sách khởi
int &widthRef = width;
SAI Point botRight; tạo thành viên
//...
}; };
Rectangle::Rectangle (int left, int top, int right, int bottom)
Image::Image (const int w, const int h) Khởi tạo ĐÚNG :topLeft(left, top), botRight(right, bottom)
: widthRef(width) thông qua danh sách {
{ //……………... } khởi tạo thành viên }

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 57 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 58

Thành viên là đối tượng của 1 lớp Thành viên là đối tượng của 1 lớp
class Diem { class Diem {
double x,y;
double x,y;
public:
public:
Diem(double xx, double yy) {x = xx; y = yy;}
Diem(double xx, double yy) {x = xx; y = yy;} // ...
// ... };
}; class TamGiac {
class TamGiac { Diem A,B,C;
Diem A,B,C; public:
public: TamGiac(double xA, double yA, double xB, double yB,
void Ve() const; double xC, double yC):A(xA,yA),(xB,yB),C(xC,yC){}
// ... void Ve() const;
// ...
};
};
TamGiac t; // Bao sai TamGiac t(100,100,200,400,300,300);

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 59 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 60
Mảng các đối tượng Mảng các đối tượng
n Sử dụng hàm xây dựng không đối số (hàm n Sử dụng dạng con trỏ:
xây dựng mặc nhiên - default constructor). ¨ Cấp vùng nhớ:
VD: Point pentagon[5]; VD: Point *pentagon = new Point[5];
n Sử dụng bộ khởi tạo mảng: ¨ Thu hồi vùng nhớ:
delete[] pentagon;
VD: Point triangle[3] =
delete pentagon; // Thu hồi vùng nhớ đầu
{ Point(4,8), Point(10,20), Point(35,15) };
Ngắn gọn: class Polygon {
Không cần biết kích
public:
Set s[4] = { 10, 20, 30, 40 }; //... thước mảng.
tương đương với: private:
Point *vertices; // các đỉnh
Set s[4] = { Set(10), Set(20), Set(30), Set(40) }; int nVertices; // số các đỉnh
};

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 61 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 62

Phạm vi lớp Phạm vi lớp


n Thành viên trong 1 lớp: n Lớp toàn cục: đại đa số lớp trong C++.
¨ Che các thực thể trùng tên trong phạm vi. n Lớp lồng nhau: lớp chứa đựng lớp.
// ……… n Lớp cục bộ: trong 1 hàm hoặc 1 khối.
int fork (void); // fork hệ thống
class Process { class Rectangle { // Lớp lồng nhau void Render (Image &i)
int fork (void); // fork thành viên public: {
fork thành viên Rectangle (int, int, int, int); class ColorTable {
//... che đi fork toàn cục //.. public:
}; trong phạm vi lớp private: ColorTable () { /* ... */ }
Process class Point { AddEntry (int r, int g, int b)
public: { /* ... */ }
// ……… Point(int a, int b) { … } //...
int Process::func1 (void) private:
int x, y; };
{ int x = fork(); // gọi fork cục bộ ColorTable colors;
};
int pid = ::fork(); // gọi hàm fork hệ thống Point topLeft, botRight; //...
//... }; }
}
Rectangle::Point pt(1,1); // sd ở ngoài ColorTable ct; // SAI

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 63 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 64

Cấu trúc và hợp Cấu trúc và hợp


n Cấu trúc (structure): n Hợp (union):
¨ Bắt nguồn từ ngôn ngữ C. ¨ Tất cả thành viên ánh xạ đến cùng 1 địa chỉ bên
¨ Tương đương với class với các thuộc tính là public. trong đối tượng chính nó (không liên tiếp).
¨ Sử dụng như class. ¨ Kích thước = kích thước của dữ liệu lớn nhất.
union Value { class Object {
struct Point { class Point { long integer; private:
Point (int, int); public: double real; enum ObjType {intObj, realObj,
Point(int, int); char *string; strObj, listObj};
void OffsetPt(int, int);
void OffsetPt(int, int); Pair list; ObjType type; // kiểu đối tượng
int x, y; int x, y; //... Value val; // giá trị của đối tượng
}; }; }; //...
};
class Pair {
Point p = { 10, 20 }; Value *head;
Có thể khởi tạo dạng này Value *tail;
nếu không có định nghĩa //... Kích thước của Value là
hàm xây dựng }; 8 bytes = sizeof(double)
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 65 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 66
Nội dung
n Khái niệm
Lập trình C/C++
n Lớp dẫn xuất đơn giản

THỪA KẾ & ĐA HÌNH n Ký hiệu các thứ bậc


n Hàm xây dựng và hàm hủy
(INHERITANCE) n Quyền truy nhập

Bộ môn Công nghệ Phần mềm n Các đối tượng được thừa kế
Khoa Công Nghệ Thông Tin ¨ Downcast
CHƯƠNG
Đại học Bách khoa – Đại học Đà Nẵng ¨ Upcast
6 n Đa thừa kế - Sự mơ hồ
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 1 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 2

Sử dụng lại Sử dụng lại


n Tồn tại nhiều loại đối tượng có các thuộc
tính và hành vi tương tự hoặc liên quan n Copy mã nguồn
đến nhau ¨ Tốn công, dễ nhầm
¨ Person, Student, Manager,… ¨ Khó sửa lỗi do tồn tại nhiều phiên bản

n Xuất hiện nhu cầu sử dụng lại các mã n Quan hệ has_a


nguồn đã viết ¨ Sử dụng lớp cũ như là thành phần của lớp
¨ Sử dụng lại thông qua copy mới
¨ Sử dụng lại thông qua quan hệ has_a ¨ Sử dụng lại cài đặt với giao diện mới
¨ Sử dụng lại thông qua cơ chế “kế thừa” n Phải viết lại giao diện
n Chưa đủ mềm dẻo

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 3 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 4

Ví dụ: has_a Kế thừa


class Person {
private: n Dựa trên quan hệ is_a
String name;
Date bithday; n Thừa hưởng lại các thuộc tính và phương
public:
String getName() { return name; }
thức đã có
...
n Chi tiết hóa cho phù hợp với mục đích sử
};
class Employee { dụng mới
private:
Person me; ¨ Thêm các thuộc tính mới
double salary;
public:
¨ Thêm hoặc hiệu chỉnh các phương thức
String getName() { return me.getName(); }
...
};

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 5 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 6
Kế thừa Kế thừa
n Ích lợi: có thể tận dụng lại n Lớp cha – superclass (hoặc lớp cơ sở - base class)
¨ lớp tổng quát hơn trong một quan hệ “là”
¨ Các thuộc tính chung ¨ các đối tượng thuộc lớp cha có cùng tập thuộc tính và
¨ Các hàm có thao tác tương tự hành vi S
n Lớp con – subclass (hoặc lớp dẫn xuất – derived class)
¨ lớp cụ thể hơn trong một quan hệ “là”
Lớp cơ sở LỚP CHA ¨ các đối tượng thuộc lớp con có cùng tập thuộc tính và
(Base class) (Super class) hành vi S (do thừa kế từ lớp cha), kèm thêm tập thuộc tính
và hành vi S’ của riêng lớp con
n Quan hệ thừa kế - Inheritance hay còn gọi là quan hệ “là”
n Ta nói rằng lớp con “thừa kế từ” lớp cha, hoặc lớp con
Lớp dẫn xuất LỚP CON “được dẫn xuất từ” lớp cha.
(Derived class) (Sub class)

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 7 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 8

Sơ đồ quan hệ đối tượng Sơ đồ quan hệ đối tượng


Object Relationship Diagram - ORD
n Khi mô tả các quan hệ thừa kế giữa các lớp trong ORD, n Biểu diễn một quan hệ thừa
mục đích là để chỉ rõ sự khác biệt giữa các lớp tham gia kế giữa hai lớp bằng một mũi
quan hệ đó tên trỏ từ lớp con đến lớp cha
¨ một lớp con khác lớp cha của nó ở chỗ nào?
¨ các lớp con khác nhau ở chỗ nào?
n Có thể biểu diễn quan hệ với
nhiều lớp con theo một trong
hai kiểu sau:

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 9 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 10

Biểu diễn sơ đồ quan hệ Sơ đồ quan hệ đối tượng


n Biểu diễn các thuộc tính và hành vi n Ta có sơ đồ n mỗi xe ca đều có các
¨ Giả sử lớp MotorVehicle có các thuộc thuộc tính vin, make,
tính vin (số đăng ký xe), make (hãng), model, và hành vi
model (kiểu), và hành vi drive (lái) drive, kèm theo thuộc
tính passengers
n Ta có sơ đồ quan hệ
¨ mọi xe tải, xe ca
n mỗi xe tải đều có các
đều có các thuộc thuộc tính vin, make,
tính vin, make, model, và hành vi
model, và hành vi drive, kèm theo thuộc
drive tính maximum payload
và các hành vi load,
unload
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 11 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 12
Cây thừa kế Định nghĩa lớp con
class MyDerivedClass :<keyword> MyBaseClass{
n Các quan hệ thừa kế luôn ...
được biểu diễn với các lớp };
con đặt dưới lớp cha để nhấn mạnh
bản chất phả hệ của quan hệ n Mô tả một lớp con cũng giống như biểu diễn nó
trong ORD, ta chỉ tập trung vào những điểm khác
n Ta cũng có thể có nhiều tầng thừa kế, tại mỗi tầng,
các lớp con tiếp tục thừa kế từ lớp cha với lớp cha
¨ một xe chở rác (dump truck) là xe tải, và cũng là xe chạy n Ích lợi
bằng động cơ ¨ đơn giản hoá khai báo lớp,
n Nghĩa là các lớp con được thừa kế các thuộc tính ¨ hỗ trợ nguyên lý đóng gói của hướng đối tượng
và hành vi của mọi lớp cơ sở bên trên nó ¨ hỗ trợ tái sử dụng code (sử dụng lại định nghĩa của các
¨ một xe chở rác có mọi thuộc tính và hành vi của xe động thành viên dữ liệu và phương thức)
cơ, kèm theo mọi thuộc tính và hành vi của xe tải, kèm
¨ việc che dấu thông tin cũng có thể có vai trò trong việc
theo các thuộc tính và hành vi của riêng xe rác.
tạo cây thừa kế
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 13 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 14

Ví dụ: is_a Ví dụ MotorVehicle


class Person {
n Bắt đầu bằng định nghĩa lớp cơ sở, MotorVehicle
private:
String name;
Date bithday;
public:
String getName() { return name; }
... class MotorVehicle {
}; public:
class Employee: public Person { MotorVehicle(int vin, string make, string model);
private: ~MotorVehicle();
double salary; void drive(int speed, int distance);
public: private:
... int vin;
}; string make;
string model;
};

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 15 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 16

Ví dụ MotorVehicle Ví dụ MotorVehicle
n Ta định nghĩa constructor, destructor, và hàm
n Tạo lớp con Car
drive() (ở đây, ta chỉ định nghĩa tạm drive())
Chỉ rõ quan hệ giữa lớp con Car
MotorVehicle::MotorVehicle(int vin, string make, string và lớp cha MotorVehicle
model)
{
this->vin = vin; class Car : public MotorVehicle
this->make = make; {
this->model = model; public:
} Car (int passengers);
MotorVehicle::~MotorVehicle(){}// We could actually use ~Car();
// the default destructor private:
void MotorVehicle::drive(int speed, int distance) int passengers;
{ };
cout << “Dummy drive() of MotorVehicle.” << endl;
}

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 17 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 18
Định nghĩa lớp con Định nghĩa lớp con
n Hiện giờ constructor của lớp Car chỉ nhận 1 tham số n Tối thiểu, ta sẽ định nghĩa constructor và (có thể cả)
passengers, trong khi các đối tượng Car cũng có tất destructor
cả các thành viên được thừa kế từ MotorVehicle ¨ Các lớp con không thừa kế constructor và
destructor của lớp cha, do việc khởi tạo và huỷ các lớp
Car (int passengers); khác nhau là khác nhau
n Do vậy, trừ khi ta muốn dùng giá trị mặc định cho các n Phiên bản constructor đầu tiên mà ta có thể nghĩ tới:
thành viên được thừa kế, ta nên truyền thêm tham số
cho constructor để khởi tạo vin, make, model. Car::Car(int vin, string make, string model, int passengers)
Quy ước: đặt các tham số {
class Car : public MotorVehicle { cho lớp cha lên đầu danh sách. this->vin = vin;
public: this->make = make;
Car (int vin, string make, string model, int passengers); this->model = model;
~Car(); this->passengers = passengers;
private: }
int passengers; Car::~Car() {}
};
Car a(…);
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 19 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 20

Định nghĩa lớp con Định nghĩa lớp con


n Nhược điểm
n Để sử dụng constructor của lớp cơ sở, ta dùng
¨ trực tiếp truy nhập các thành viên dữ liệu của lớp cơ sở
danh sách khởi tạo của constructor (tương
n thiếu tính đóng gói : phải biết sâu về chi tiết lớp cơ sở và phải

can thiệp sâu


tự như khi khởi tạo các hằng thành viên)
n không tái sử dụng mã khởi tạo của lớp cơ sở
¨ cách duy nhất để tạo phần thuộc về thể hiện của lớp
n không thể khởi tạo các thành viên private của lớp cơ sở do
cha tạo trước nhất
không có quyền truy nhập n Ta sửa định nghĩa constructor như sau:
n Nguyên tắc: một đối tượng thuộc lớp con bao gồm một đối Car::Car(int vin, string make, string model, int passengers)
tượng lớp cha cộng thêm các tính năng bổ sung của lớp :MotorVehicle(vin,make,model)
{
con this->passengers = passengers;
¨ một thể hiện của lớp cơ sở sẽ được tạo trước, sau đó "gắn" thêm }
Ta không cần khởi tạo các thành
các tính năng bổ sung của lớp dẫn xuất viên vin, make, model từ bên Gọi constructor của MotorVehicle với
trong constructor của Car nữa các tham số vin, make, model
n Vậy, ta sẽ sử dụng constructor của lớp cơ sở.
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 21 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 22

Định nghĩa lớp con Hàm xây dựng và hàm hủy


n Để đảm bảo rằng một thể hiện của lớp cơ sở n Trong thừa kế, khi khởi tạo đối tượng:
luôn được tạo trước, nếu ta bỏ qua lời gọi ¨ Hàm xây dựng của lớp cha sẽ được gọi trước
constructor lớp cơ sở tại danh sách khởi tạo của ¨ Sau đó mới là hàm xây dựng của lớp con.
lớp dẫn xuất, trình biên dịch sẽ tự động chèn n Trong thừa kế, khi hủy bỏ đối tượng:
thêm lời gọi constructor mặc định của lớp cơ sở ¨ 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.
n Tuy ta cần gọi constructor của lớp cơ sở một
A
cách tường minh, tại destructor của lớp dẫn
xuất, lời gọi tượng tự cho destructor của lớp
cơ sở là không cần thiết B

¨ việc này được thực hiện tự động


C
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 23 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 24
Quyền truy nhập (Access privilege) Quyền truy nhập
n Các quyền truy nhập có vai trò gì trong
class Car : public MotorVehicle
n Từ khoá được dùng để chỉ rõ “kiểu” thừa
quan hệ thừa kế?
n Có hai kiểu quyền truy nhập cho các
{
public: kế được sử dụng
Car (...);
thành viên dữ liệu và phương thức ~Car(); ¨ nó quy định những ai có thể "nhìn thấy" quan
¨ public - thành viên/phương thức có private:
int passengers; hệ thừa kế đó
thể được truy nhập từ mọi đối };
tượng C++ thuộc phạm vi n Thừa kế public (loại thông dụng nhất):
¨ private - thành viên/phương thức mọi đối tượng C++ khi tương tác với một
chỉ có thể được truy nhập từ bên
trong chính lớp đó thể hiện của lớp con đều có thể coi nó
n Ta có thể sử dụng các từ khoá quyền
class Car : public MotorVehicle { ... };
như một thể hiện của lớp cha
truy nhập trong khai báo lớp để chỉ ¨ mọi thành viên/phương thức public của lớp
kiểu thừa kế
cha cũng là public trong lớp con

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 25 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 26

Quyền truy nhập Quyền truy nhập


n Quay lại cây thừa kế với
n Thừa kế private: chỉ có chính thể hiện MotorVehicle là lớp cơ sở
class MotorVehicle {
...
đó biết nó được thừa kế từ lớp cha ¨ Mọi thành viên dữ liệu đều được khai private:
báo private, do đó chúng chỉ có thể int vin;
¨ các đối tượng bên ngoài không thể tương tác được truy nhập từ các thể hiện của string make;
string model;
với lớp con như với lớp cha, vì mọi thành viên MotorVehicle
};
được thừa kế đều trở thành private trong ¨ tất cả các lớp dẫn xuất không có quyền
truy nhập các thành viên private của
lớp con MotorVehicle
void Truck::Load() {

n Có thể dùng thừa kế private để tạo lớp n Vậy, đoạn mã sau sẽ có lỗi: if (this->make == “Ford”) {
...
}
con có mọi chức năng của lớp cha nhưng }

lại không cho bên ngoài biết về các chức Lớp Truck không có quyền truy
nhập thành viên private make
năng đó. của lớp cơ sở MotorVehicle

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 27 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 28

Quyền truy nhập Quyền truy nhập


n Tuy quy định quyền truy nhập như trên có vẻ kỳ
cục, nhưng nó đảm bảo tính đóng gói n Từ khoá protected dùng để cấp quyền truy
nhập tới thành viên/phương thức của một lớp
¨ nếu không, ta có thể lấy được quyền truy nhập vào
cấu trúc bên trong của một lớp chỉ bằng cách tạo một cho các lớp dẫn xuất (và chỉ các lớp dẫn xuất)
lớp dẫn xuất của lớp đó của lớp đó
n Trường hợp nếu ta xây dựng lớp cơ sở và cố ý n Đối với mọi đối tượng khác của C++, các thành
muốn “cấp” cho lớp dẫn xuất quyền truy nhập viên/phương thức protected được coi như
tới một số thành viên/phương thức, private (chúng đều không thể truy nhập được)
¨ từ khoá protected quy định quyền truy nhập cho n Tuy tiêu chí đóng gói khuyên nên để mọi thứ
các thành viên/phương thức sẽ được sử dụng bởi lớp private, nhưng khi tạo các cây thừa kế, người
dẫn xuất ta thường hay cấp quyền truy nhập protected
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 29 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 30
Quyền truy nhập Quyền truy nhập
n Giả sử ta muốn các lớp con của class MotorVehicle { n Quay lại việc sử dụng các từ khoá truy nhập để
MotorVehicle có thể truy nhập dữ ... quy định “kiểu” thừa kế
protected:
liệu của nó int vin; class MotorVehicle {…};
n Thay từ khoá private bằng string make; class Car : protected MotorVehicle {…};
protected ta có khai báo: string model;
}; n Khi dùng kiểu thừa kế protected cho lớp con
Car, quan hệ “Car thừa kế MotorVehicle” sẽ
void Truck::Load() {

n Vậy, đoạn mã sau sẽ không có lỗi if (this->make == “Ford”) { được nhìn thấy từ
...
n Tuy nhiên truy nhập từ bên ngoài vẫn } ¨ mọi phương thức bên trong Car,
}
sẽ bị cấm ¨ mọi phương thức thuộc các lớp con của Car
Lớp Truck có quyền truy
nhập thành viên protected make n Tuy nhiên, mọi đối tượng khác của C++ không
của lớp cơ sở MotorVehicle
nhìn thấy quan hệ này
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 31 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 32

Quyền truy nhập Overiding


Lớp cơ sở Thừa kế public Thừa kế private Thừa kế protected n Lớp dẫn xuất có thể định nghĩa lại một hàm thành
private _ _ _ viên của lớp cơ sở mà nó được thừa kế.
public public private protected n Khi đó nếu tên hàm được gọi đến trong lớp dẫn
protected protected private protected
xuất thì trình biên dịch sẽ tự động gọi đến phiên
class A { class B : A { // Thừa kế dạng private bản hàm của lớp dẫn xuất.
private: …….
int x; }; n Muốn truy cập đến phiên bản hàm của lớp cơ sở
class C : private A { // A là lớp cơ sở riêng của B
public:
void Fx (void);
……… từ lớp dẫn xuất thì sử dụng toán tử định phạm vi
};
int y; class D : public A { // A là lớp cơ sở chung của C và tên lớp cơ sở trước tên hàm.
void Fy (void); ………
protected: };
int z; class E : protected A { // A: lớp cơ sở được bảo vệ
void Fz (void); ……….
}; };
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 33 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 34

Overiding – Ví dụ Các đối tượng được thừa kế trong C++


class DaGiac { class SinhVien : public Nguoi{
// ... char *MaSo; n Trong cây thừa kế MotorVehicle, giả sử mọi thành
void Ve() const; public:
void ToMau() const; //... viên dữ liệu đều được khai báo protected, và ta sử
}; void Xuat() const; dụng kiểu thừa kế public
class HCN :public Dagiac{ };
void ToMau(); void SinhVien::Xuat() const { n Lớp cơ sở MotorVehicle
…. cout << "Sinh vien, ma so: " << MaSo
}; << ", ho ten: " << HoTen;
class Ellipse{ } class MotorVehicle {
//... public:
public: MotorVehicle(int vin, string make, string model);
//... ~MotorVehicle();
void rotate(double rotangle){ //...} void Drive(int speed, int distance);
}; protected:
class Circle:public Ellipse { int vin;
public: string make;
//... string model;
void rotate(double rotangle){/* do nothing */} };
};
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 35 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 36
Các đối tượng được thừa kế trong C++ Các đối tượng được thừa kế trong C++
n Các lớp dẫn xuất Car và Truck: n Ta có thể khai báo các thể hiện của các lớp đó
class Car : public MotorVehicle {
public:
và sử dụng chúng như thế nào?
Car(int vin, string make, string model, int passengers); ¨ Thí dụ, khai báo các con trỏ tới 3 lớp:
~Car();
protected: MotorVehicle* mvPointer;
int passengers; Car* cPointer;
}; Truck* tPointer;

class Truck : public MotorVehicle { ¨ Sử dụng các con trỏ để khai báo các đối tượng thuộc
public: các lớp tương ứng
Truck(int vin,string make, string model, int maxPayload);
~Truck(); ...
void Load(); mvPointer = new MotorVehicle(10, “Honda”, “S2000”);
void Unload(); cPointer = new Car(10, “Honda”, “S2000”, 2);
protected: tPointer = new Truck(10, “Toyota”, “Tacoma”, 5000);
int maxPayload; ...
};
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 37 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 38

Các đối tượng được thừa kế trong C++ Upcast


n Trong cả ba trường hợp, ta có thể truy nhập các phương n Các thể hiện của lớp con thừa kế public có thể
thức của lớp cha, do ta đã sử dụng kiểu thừa kế public
¨ Thí dụ:
được đối xử như thể nó là thể hiện của lớp cha.
¨ từ một thể hiện của lớp con, ta có quyền truy nhập
mvPointer->Drive(); // Method defined by this class các thành viên và phương thức public mà ta có thể
cPointer->Drive(); // Method defined by base class
tPointer->Drive(); // Method defined by base class
truy nhập trên một thể hiện của lớp cha.
n Do đó, C++ cho phép dùng con trỏ được khai
n Tuy nhiên, các phương thức định nghĩa tại một lớp dẫn
xuất chỉ có thể được truy nhập bởi lớp đó báo thuộc loại con trỏ tới lớp cơ sở để chỉ tới thể
¨ xét phương thức Load() của lớp Truck hiện của lớp dẫn xuất
¨ ta có thể thực hiện các lệnh sau:
mvPointer->Load(); // Error
cPointer->Load(); // Error MotorVehicle* mvPointer2;
tPointer->Load(); // Method defined by this class mvPointer2 = mvPointer; // Point to another MotorVehicle
mvPointer2 = cPointer; // Point to a Car
mvPointer2 = tPointer; // Point to a Truck

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 39 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 40

Upcast Upcast
n Điều đáng lưu ý là ta thực hiện tất cả các lệnh n Upcast là quá trình tương tác với thể hiện của lớp dẫn
gán đó mà không cần đổi kiểu tường minh xuất như thể nó là thể hiện của lớp cơ sở.
¨ do mọi lớp con của MotorVehicle đều chắc chắn có n Cụ thể, đây là việc đổi một con trỏ (hoặc tham chiếu) tới
lớp dẫn xuất thành một một con trỏ (hoặc tham chiếu) tới
mọi thành viên và phương thức có trong một
MotorVehicle, việc tương tác với thể hiện của các lớp cơ sở
¨ ta đã thấy ví dụ về upcast đối với con trỏ
lớp này như thể chúng là MotorVehicle không có
MotorVehicle* mvPointer2 = cPointer;
chút rủi ro nào
¨ ví dụ về upcast đối với tham chiếu
¨ Ví dụ, lệnh sau đây là hợp lệ, bất kể mvPointer2
// Refer to the instance pointed to by cPointer
đang trỏ tới một MotorVehicle, một Car, hay một MotorVehicle& mvReference = *cPointer;
Truck // Refer to an automatically-allocated instance c
Car c(10, “Honda”, “S2000”, 2);
mvPointer2->Drive(); MotorVehicle& mvReference2 = c;

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 41 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 42
Upcast Upcast
n Upcast thường gặp tại các định nghĩa hàm, khi
n Nếu ta dùng một con trỏ tới lớp cơ sở để
một con trỏ/tham chiếu đến lớp cơ sở được yêu trỏ tới một thể hiện của lớp dẫn xuất, trình
cầu, nhưng con trỏ/tham chiếu đến lớp dẫn xuất biên dịch sẽ chỉ cho ta coi đối tượng như
cũng được chấp nhận thể nó thuộc lớp cơ sở
¨ xét hàm sau ¨ Như vậy, ta không thể làm như sau
void sellMyVehicle(MotorVehicle& myVehicle){...} mvPointer2->Load(); // Error
¨ có thể gọi sellMyVehicle một cách hợp lệ với tham n Đó là vì trình biên dịch không thể đảm bảo
số là một tham chiếu tới một MotorVehicle, một Car,
hoặc một Truck. rằng con trỏ thực ra đang trỏ tới một thể
hiện của Truck.

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 43 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 44

Upcast Downcast
n Chú ý rằng khi gắn một con trỏ/tham chiếu lớp cơ sở với một thể hiện
của lớp dẫn xuất, ta không hề thay đổi bản chất của đối tượng được n Upcast là đổi con trỏ/tham chiếu tới lớp
trỏ tới
¨ Ví dụ, lệnh dẫn xuất thành con trỏ/tham chiếu tới lớp
MotorVehicle* mvPointer2 = tPointer; cơ sở.
mvPointer2->Load();////error
//Point to a Truck n Downcast là quy trình ngược lại: đổi kiểu
không làm một thể hiện của Truck suy giảm thành một
n
MotorVehicle, nó chỉ cho ta một cách nhìn khác đối với đối tượng
con trỏ/tham chiếu tới lớp cơ sở thành con
Truck và tương tác với đối tượng đó. trỏ/tham chiếu tới lớp dẫn xuất.
¨ Do vậy, ta vẫn có thể truy nhập tới các thành viên và phương thức
của lớp dẫn xuất ngay cả sau khi gán con trỏ lớp cơ sở tới nó: n downcast là quy trình rắc rối hơn và có
tPointer = new Truck(…);
mvPointer2 = tPointer; // Point to a Truck nhiều điểm không an toàn
tPointer->Load(); // We can still do this
mvPointer2->Load(); // Even though we can’t do this (error)

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 45 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 46

Downcast Downcast
n Trước hết, downcast không phải là một quy trình tự động - nó n Nếu ta biết chắc chắn rằng một con trỏ lớp cơ sở quả thực
luôn đòi hỏi đổi kiểu tường minh (explicit type cast)
đang trỏ tới một lớp con, ta có thể tự đổi kiểu cho con trỏ
lớp cơ sở bằng cách sử dụng static_cast
n Điều đó là hợp lý
Car* cPointer = new Car(10,”Honda”,”S2000”,2);
¨ nhớ lại rằng: không phải “mọi xe chạy bằng máy đều là xe tải” MotorVehicle *mv=cPointer;//Upcast
Car* cPointer2;
¨ do đó, rắc rối sẽ nảy sinh nếu trình biên dịch cho ta đổi một con cPointer2 = static_cast<Car *> ( mv); //(car*) mv
trỏ bất kỳ tới MotorVehicle thành một con trỏ tới Truck, trong
khi thực ra con trỏ đó đang trỏ tới một đối tượng Car. n Ta có thể thấy mối nguy hiểm khi làm việc này - chuyện gì
xảy ra nếu đối tượng ta đang cố đổi kiểu thực ra không
n Ví dụ, đoạn mã sau sẽ gây lỗi biên dịch: thuộc lớp mà ta nghĩ?
MotorVehicle* mvPointer3;
Truck* tPointer = new Truck(10, “Toyota”, “Tacoma”, 5000);

MotorVehicle* mv = tPointer; // Upcast
Car* cPointer2 = mvPointer3; // Error Car* cPointer2;
Truck* tPointer2 = mvPointer3; // Error cPointer2 = static_cast<Car*>(mv); // Explicit downcast
MotorCycle mcPointer2 = mvPointer3; // Error
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 47 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 48
Downcast Đa thừa kế
Car* cPointer = new Car(10,”Honda”,”S2000”,2);
MotorVehicle *mv=cPointer;//Upcast OptionList Window class OptionList { class Window {
Truck* tPointer = static_cast<Truck*>(mv); // Explicit downcast public: public:
tPointer->Load(); OptionList (int n); Window (Rect &);
~OptionList (); ~Window (void);
Menu //... //...
n Đoạn mã trên hoàn toàn hợp lệ và sẽ được trình }; };
biên dịch chấp nhận
class Menu
n Tuy nhiên, nếu chạy đoạn trình trên, chương OptionList object Window object Menu object : public OptionList, public Window {
trình có thể bị đổ vỡ (thường là khi lần đầu truy OptionList
Window OptionList
public:
data members Menu (int n, Rect &bounds);
nhập đến thành viên/phương thức được định data members data members
~Menu (void);
nghĩa của lớp dẫn xuất mà ta đổi tới) Window //...
data members };
n Ví dụ: Point-Circle Menu
Menu::Menu (int n, Rect &bounds) :
data members
OptionList(n), Window(bounds)
{ /* ... */ }
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 49 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 50

Sự mơ hồ trong đa thừa kế Chuyển kiểu


class OptionList { class Window { n Có sẵn 1 phép chuyển kiểu không tường minh:
public: public:
// …… // ……
¨ Đối tượng lớp cha = Đối tượng lớp con;
void Highlight (int part); void Highlight (int part); ¨ Áp dụng cho cả đối tượng, tham chiếu và con trỏ.
}; };
m
Menu m(n, bounds); win
OptionList data members
class Menu : public OptionList, Chỉ rõ hàm Window win = m;
Hàm cùng tên Window
public Window của lớp nào Window &wRef = m; data members Window data members
{ ……. }; Window *wPtr = &menu; Menu data members

void main() {
void main() { n Không được thực hiện phép gán ngược:
Gọi Menu m1(….);
hàm Menu m1(….); xử lý
¨ Đối tượng lớp con = Đối tượng lớp cha; // SAI
m1.OptionList::Highlight(10);
của lớp m1.Highlight(10);
m1.Window::Highlight(20); Nếu muốn thực hiện
class Menu : public OptionList, public Window {
nào ? ….
phải tự định nghĩa
public:
…. //...
} phép ép kiểu
} Menu (Window&);
};
Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 51 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 52

Lớp cơ sở ảo Lớp cơ sở ảo (tt)


n Sự mơ hồ - dư thừa dữ liệu n Cách xử lý: dùng lớp cơ sở ảo.
class OptionList class OptionList
: public Widget, List : virtual public Widget,
{ /*...*/ }; public List
{ /*...*/ };
class Window
class Window
: public Widget, Port : virtual public Widget,
Chỉ có 1
{ /*...*/ }; public Port đối tượng Widget
class Menu { /*...*/ };
class Menu
: public OptionList,
: public OptionList, Menu::Menu (int n, Rect &bounds) :
public Window public Window Widget(bounds), OptionList(n), Window(bounds)
{ /*...*/ }; Đối tượng Menu { /*...*/ }; { //... }

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 53 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 54
Các toán tử được tái định nghĩa
n 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 xây dựng sao chép:
Y::Y (const Y&)
¨ Phép gán:
Y& Y::operator = (const Y&)
n Nếu không định nghĩa, sẽ tự động có hàm xây ĐA HÌNH
dựng sao chép và phép gán do ngôn ngữ tạo ra.
=> SAI khi có con trỏ thành viên.
(POLYMORPHISM)
n Cẩn thận với toán tử new và delete.

Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 55 Khoa Công Nghệ Thông Tin - Đại Học Bách khoa Đà Nẵng 56

Nội dung Đa hình là gì?


n Đa hình là gì? n “polymorphism” có nghĩa “nhiều hình thức”, hay “nhiều dạng
n Đa hình hàm – function polymorphism sống”
n một vật có tính đa hình (polymorphic) là vật có thể xuất hiện dưới
n Đa hình và Hướng đối tượng nhiều dạng
n So sánh Overloading và Overriding n Ta đã gặp một dạng của đa hình từ trước khi làm quen với khái
n Method Overidding niệm hướng đối tượng: Đa hình hàm
n Đa hình tĩnh – static polymorphism là gì? ¨ đa hình hàm – function polymorphism, hay còn gọi là hàm
chồng – function overloading, trong đó, một hàm có nhiều dạng
n Liên kết động – dynamic binding
myFunction() {... }
n Đa hình động – dynamic polymorphism
myFunction(int x) {... }
n Hàm ảo – virtual function
myFunction(int x, int y) {... }
n Destructor ảo n đó là khi một tên hàm có thể được dùng để chỉ các định nghĩa
n Lớp trừu tượng – Abstract class hàm khác nhau dựa trên danh sách tham số

Đa hình và Hướng đối tượng Đa hình và Hướng đối tượng


n Định nghĩa: Đa hình là hiện tượng các đối tượng
n Trong phạm vi mô hình hướng đối tượng, thuật ngữ thuộc các lớp khác nhau có khả năng hiểu cùng một
đa hình có ý nghĩa cụ thể và mạnh hơn thông điệp theo các cách khác nhau
n Ta đã thấy một chút về đa hình trong phần về thừa n Ta có thể có định nghĩa tương tự cho đa hình hàm:
kế một thông điệp (lời gọi hàm) được hiểu theo các
¨ lấy một con trỏ tới lớp cơ sở và dùng nó để truy nhập các cách khác nhau tuỳ theo danh sách tham số của
đối tượng của lớp dẫn xuất thông điệp.
n Còn nhớ: ta liên lạc với các đối tượng bằng các n Ví dụ: nhận được cùng một thông điệp “nhảy”, một
thông điệp (các lời gọi hàm) để yêu cầu đối tượng con kangaroo và một con cóc nhảy theo hai kiểu
thực hiện một hành vi nào đó. khác nhau: chúng cùng có hành vi “nhảy” nhưng
các hành vi này có nội dung khác nhau.
n Việc hiểu các thông điệp đó như thế nào chính là
nền tảng cho tính chất đa hình của hướng đối tượng

chú ý rằng kangaroo và cóc thuộc hai nhánh trong cây phả hệ động vật
Overloading vs. Overriding Overloading vs. Overriding
n Function overloading - Hàm chồng: dùng một tên n Đa hình được cài đặt bởi một khái niệm tương tự
hàm cho nhiều định nghĩa hàm, khác nhau ở danh nhưng hơi khác: method overriding
¨ “override” có nghĩa “vượt quyền”
sách tham số
n Method overriding: nếu một phương thức của lớp
n Method overloading – Phương thức chồng: tương cơ sở được định nghĩa lại tại lớp dẫn xuất thì định
tự nghĩa tại lớp cơ sở có thể bị “che” bởi định nghĩa tại
void jump(int howHigh); lớp dẫn xuất.
void jump(int howHigh, int howFar);
n Với method overriding, toàn bộ thông điệp (cả tên
¨ hai phương thức jump trùng tên nhưng có danh sách và tham số) là hoàn toàn giống nhau - điểm khác
tham số khác nhau nhau là lớp đối tượng được nhận thông điệp.
n Tuy nhiên, đây không phải đa hình hướng đối tượng Kangaroo k; Chú ý, Kangaroo và Frog đều là các lớp
Hai thông điệp Frog f; dẫn xuất từ lớp cơ sở gián tiếp Animal
mà ta đã định nghĩa, vì đây thực sự là hai thông giống nhau,
k.jump(10); // the kangaroo jumps 10m high
nhưng đích là hai
điệp jump khác nhau. lớp khác nhau f.jump(10); // the frog jumps 10m far

Method Overriding Method Overriding


Xét hành vi “draw” của các n Với đặc điểm đa hình của method overriding, ta sẽ có được
điều trên.
lớp trong cây phả hệ bên.
¨ định nghĩa lại hành vi draw tại các lớp dẫn xuất
¨ thông điệp “draw” gửi cho class Circle : public Point {
class Point {
một thể hiện của mỗi lớp public:
Point(int x,int y,string color);
public:
Circle (int x, int y,
trên sẽ yêu cầu thể hiện ~Point(); string color,int radius);
void draw(); ~Circle();
đó tự vẽ chính nó. protected: void draw();
int x; private:
int y; int radius;
1. khai báo lại tại lớp dẫn xuất
string color; };
};
2. khai báo lại tại lớp dẫn xuất
... ...
¨ một thể hiện của Point phải vẽ một điểm, một void Point::draw() { void Circle::draw() {
// Draw a point // Draw a circle
thể hiện của Circle phải vẽ một đường tròn, và } }

một thể hiện của Rectangle phải vẽ một hình


chữ nhật

Method Overriding Method Overriding


n Lưu ý :
n Kết quả: khi tạo các thể hiện của các lớp ¨ để override một phương thức của một lớp cơ sở, phương thức tại
lớp dẫn xuất phải có cùng tên, cùng danh sách tham số,cùng kiểu
khác nhau và gửi thông điệp “draw”, các giá trị trả về, cùng là const hoặc cùng không là const
phương thức thích hợp sẽ được gọi. ¨ Nếu lớp cơ sở có nhiều phiên bản overload của cùng một phương
thức, việc override một trong các phương thức đó sẽ che tất cả các
phương thức còn lại
Point p(0,0,”white”);
Circle c(100,100,”blue”,50); class A() {
void foo(); B override foo(), không override foo(x)
void foo(int x); Þ tại B, foo(x) bị che, nếu gọi foo(x) từ
p.draw(); // Draws a white point at (0,0) ... một đối tượng B sẽ gây lỗi biên dịch
c.draw(); // Draws a blue circle of radius 50 at (100,100)
class B:public A(){
void foo();
//no foo(int x);
...
Function call binding
Method Overriding (Liên kết lời gọi hàm)
n Khi tạo thể hiện của các lớp khác
nhau và gọi cùng một hành vi,
phương thức thích hợp sẽ được gọi n C++ làm thế nào để tìm đúng hàm cần chạy mỗi khi
Point p(0,0,”white”); ta có một lời gọi hàm hoặc gọi phương thức?
Circle c(100,100,”blue”,50); n Function call binding là quy trình xác định khối mã
hàm cần chạy khi một lời gọi hàm được thực hiện
p.draw();//Draws a white point at (0,0)
n Xem xét
c.draw();//Draws a blue circle of radius 50
at(100,100) ¨ function call binding
n Ta cũng có thể tương tác với các thể hiện lớp dẫn xuất như thể n C, C++
chúng là thể hiện của lớp cơ sở ¨ method call binding
Circle *pc = new Circle(100,100,”blue”,50); n static binding
Point* pp = pc; n dynamic binding
n Nhưng nếu có đa hình, lời gọi sau sẽ làm gì?
pp->draw(); // Draw what???

Function call binding


(Liên kết lời gọi hàm)
Static binding
n Static function call binding (hoặc static binding – liên kết Point P(10,20,”brow”);
Circle C(5,10,2.0, “yellow”);
tĩnh) là quy trình liên kết một lời gọi hàm với một định nghĩa hàm
P.draw(); //in điểm
tại thời điểm biên dịch. C.draw(); //in hình tròn
Kiểu tĩnh : Circle& rC = C;
¨ do đó còn gọi là “compile-time binding” – liên kết khi biên dịch,
Circle rC.draw(); //hình tròn
hoặc “early binding” – liên kết sớm
Circle* pC = &C;
pC->draw(); //hình tròn
Point& rP = C; Ta muốn in
Kiểu tĩnh : rP.draw(); // in điểm hình tròn
Point
Point* pP = &C;
pP->draw(); // in điểm

Static binding Static binding


n Ta muốn: n Liên kết tĩnh – Static binding: liên kết một tên hàm
rP.draw() và pP->draw()
với phương thức thích hợp được thực hiện bởi việc
phân tích tĩnh mã chương trình tại thời gian dịch
sẽ gọi Point::draw() hay Circle::draw() dựa vào kiểu tĩnh (được khai báo) của đối tượng
tuỳ theo rP và pP đang trỏ tới đối tượng Point hay Circle được dùng trong lời gọi hàm.
¨ Liên kết tĩnh không quan tâm đến chuyện con trỏ (hoặc
n Thực tế xảy ra: với liên kết tĩnh, khi trình biên dịch sinh lời gọi tham chiếu) có thể trỏ tới một đối tượng của một lớp dẫn
hàm, nó thấy kiểu tĩnh của rP là Point&, nên gọi Point::draw() xuất

và kiểu tĩnh của pP là Point* nên gọi Point::draw(). n Với liên kết tĩnh, địa chỉ đoạn mã cần chạy cho một
lời gọi hàm cụ thể là không đổi trong suốt thời gian
chương trình chạy
Static polymorphism Static polymorphism
n Static polymorphism – đa hình tĩnh là kiểu n Đa hình tĩnh thích hợp cho các phương thức:
được định nghĩa tại một lớp, và được gọi từ một thể hiện
đa hình được cài đặt bởi liên kết tĩnh ¨
của chính lớp đó (trực tiếp hoặc gián tiếp qua con trỏ)
n Đối với đa hình tĩnh, trình biên dịch xác ¨ được định nghĩa tại một lớp cơ sở và được thừa kế public
nhưng không bị override tại lớp dẫn xuất, và được gọi từ
định trước định nghĩa hàm/phương thức một thể hiện của lớp dẫn xuất đó
nào sẽ được thực thi cho một lời gọi n trực tiếp hoặc gián tiếp qua con trỏ tới lớp dẫn xuất, hoặc
hàm/phương thức nào. n qua một con trỏ tới lớp cơ sở
¨ được định nghĩa tại một lớp cơ sở và được thừa kế public
và bị override tại lớp dẫn xuất, và được gọi từ một thể hiện
của lớp dẫn xuất đó (trực tiếp hoặc gián tiếp qua con trỏ
tới lớp dẫn xuất)

Static polymorphism Dynamic Binding – liên kết động


n Ta gặp rắc rối với đa hình tĩnh (như trong trường hợp Circle), khi n Dynamic function call binding (dynamic binding – liên kết động)
ta muốn gọi định nghĩa đã được override tại lớp dẫn xuất của là quy trình liên kết một lời gọi hàm với một định nghĩa hàm tại
một phương thức qua con trỏ tới lớp cơ sở. thời gian chạy
Cirlce C(5,10,2.0, “yellow”); ¨ còn gọi là “run-time” binding hoặc “late binding”
Với trình biên dịch, ...
pP là con trỏ tới Point n Với liên kết động, quyết định chạy định nghĩa hàm nào được đưa
Point* pP = &C;
pP->draw(); // in điểm ra tại thời gian chạy, khi ta biết chắc con trỏ đang trỏ đến đối
tượng thuộc lớp nào.
n Ta muốn trình biên dịch hoãn quyết định gọi phương thức nào Khi chạy đến đây,
cho lời gọi hàm trên cho đến khi chương trình thực sự chạy. chương trình nhận ra Cirlce C(5,10,2.0, “yellow”);
pP đang trỏ tới ...
n Cần một cơ chế cho phép xác định kiểu động tại thời gian chạy Point* pP = &C;
Circle, nên gọi
(tại thời gian chạy, chương trình có thể xác định con trỏ đang Cirlce::draw() pP->draw(); // in hình tròn
thực sự trỏ đến cái gì)
n Đa hình động (dynamic polymorphism) là loại đa hình được cài
n Vậy, ta cần đa hình động – dynamic polymorphism
đặt bởi liên kết động.

Virtual function – hàm ảo Hàm ảo


class Point {
n Hàm/phương thức ảo – virtual function/method public:
Point(int x,int y,string color); Khai báo hàm ảo,
là cơ chế của C++ cho phép cài đặt đa hình động ~Point(); yêu cầu trình biên dịch
virtual void draw(); dùng liên kết động
n Nếu khai báo một hàm thành viên (phương thức) là protected:
virtual, trình biên dịch sẽ đẩy lùi việc liên kết các lời int x;
gọi phương thức đó với định nghĩa hàm cho đến khi int y; draw đã được khai báo là hàm ảo,
string color; Khi chạy, pP trỏ tới Circle, nên
chương trình chạy. }; Circle::draw() được gọi
¨ nghĩa là, ta bảo trình biên dịch sử dụng liên kết động thay int main() {
cho liên kết tĩnh đối với phương thức đó Cirlce C(5,10,2.0, “yellow”);
...
n Để một phương thức được liên kết tại thời gian Point* pP = &C;
chạy, nó phải khai báo là phương thức ảo (từ khoá pP->draw(); // in hình tròn
virtual) tại lớp cơ sở ...
}
Các hàm draw()

Hàm ảo
của Point và các lớp dẫn
xuất đều là hàm ảo
class Point{
public:
Hàm ảo
n Hàm ảo là phương thức được khai
báo với từ khoá virtual trong định ... n đa hình động dễ cài và cần thiết. Vậy tại sao lại cần đa hình
virtual void draw(); tĩnh?
nghĩa lớp (nhưng không cần tại định ...
nghĩa hàm, nếu định nghĩa hàm nằm };
n Trong Java, mọi phương thức đều mặc định là phương thức ảo,
ngoài định nghĩa lớp) ... tại sao C++ không như vậy?
n Một khi một phương thức được khai void Point::draw() n Có hai lý do:
{... } 1. tính hiệu quả
báo là hàm ảo tại lớp cơ sở, nó sẽ
tự động là hàm ảo tại mọi lớp dẫn class Circle: public Point { ¨ C++ cần chạy nhanh, trong khi liên kết động tốn thêm phần xử lý
xuất trực tiếp hoặc gián tiếp. public: phụ, do vậy làm giảm tốc độ
... ¨ Ngay cả những hàm ảo không được override cũng cần xử lý phụ
n Tuy không cần tiếp tục dùng từ khoá virtual void draw();
virtual trong các lớp dẫn xuất, nhưng Þ vi phạm nguyên tắc của C++: chương trình không phải chạy
... chậm vì những tính năng không dùng đến
vẫn nên dùng để tăng tính dễ đọc }; Không cần
của các file header. ... nhưng nên có 2. tính đóng gói
void Circle::draw() ¨ khi khai báo một phương thức là phương thức không ảo, ta có ý
¨ nhắc ta và những người dùng lớp
{... } rằng ta không định để cho phương thức đó bị override.
dẫn xuất của ta rằng phương thức
đó sử dụng liên kết động

Hàm ảo – constructor và destructor Destructor ảo


n Không thể khai báo các constructor ảo n Nhớ lại cây thừa kế MotorVehicle, nếu ta
tạo và huỷ một đối tượng Car qua một con
¨ constructor không được thừa kế, bao giờ
trỏ tới Car, mọi việc đều xảy ra như mong
cũng phải định nghĩa lại nên chuyện ảo không đợi
có nghĩa
Car* c = new Car(10, “Suzuki”, “RSX-R1000”, 4);

n Có thể (và rất nên) khai báo destructor là ...

delete c; // This works correctly – it will trigger


hàm ảo. // the Car destructor and then works
// up to the MotorVehicle destructor

Destructor ảo Destructor ảo
n Còn nếu ta dùng một con trỏ tới MotorVehicle thay cho n Chú ý: việc gọi nhầm destructor không ảnh hưởng
con trỏ tới Car, chỉ có destructor của MotorVehicle đến việc thu hồi bộ nhớ
được gọi. ¨ trong mọi trường hợp, phần bộ nhớ của đối tượng sẽ
được thu hồi chính xác
Car* c = new Car(10, “Suzuki”, “RSX-R1000”, 4); ¨ Trong ví dụ trước, kể cả nếu chỉ có destructor của
MotorVehicle được gọi, phần bộ nhớ của toàn bộ đối
MotorVehicle* mv = c; // Upcasting tượng Car vẫn được thu hồi
delete mv; // With static polymorphism, the compiler n Tuy nhiên, nếu không gọi đúng destructor, các đoạn
// thinks that this is referring to an mã dọn dẹp quan trọng có thể bị bỏ qua
// instance of MotorVehicle, so only the ¨ chẳng hạn xoá các thành viên được cấp phát động
// base class (MotorVehicle) destructor
// is invoked
Destructor ảo
n Quy tắc chung: mỗi khi tạo một lớp để được dùng
làm lớp cơ sở, ta nên khai báo destructor là hàm ảo.
¨
¨
kể cả khi destructor của lớp cơ sở rỗng (không làm gì)
Vậy ta sẽ sửa lại lớp MotorVehicle như sau:
Lớp trừu tượng
class MotorVehicle { - Abstract class
public:
MotorVehicle(int vin, string make, string model);
virtual ~MotorVehicle();
...
}

Tạo thể hiện của lớp Tạo thể hiện của lớp
n Khi mới giới thiệu khái niệm đối tượng, ta nói rằng n Tuy nhiên, với một số lớp, có
chúng có thể được nhóm lại thành các lớp, trong đó thể không hợp lý khi nghĩ đến
chuyện tạo thể hiện của các
mỗi lớp là một tập các đối tượng có cùng thuộc tính lớp đó
và hành vi. n Ví dụ, một hệ thống quản lý tài
n Ta cũng nói theo chiều ngược lại rằng ta có thể định sản (asset): chứng khoán
nghĩa một đối tượng như là một thể hiện của một (stock), ngân khoản (bank
account), bất động sản (real estate), ô tô (car), v.v..
lớp ¨ một đối tượng tài sản chính xác là cái gì?
n Nghĩa là: lớp có thể tạo đối tượng ¨ phương thức computeNetWorth() (tính giá trị) sẽ tính theo kiểu
n Hầu hết các lớp ta đã gặp đều tạo được thể hiện gì nếu không biết đó là ngân khoản, chứng khoán, hay ô tô?
¨ ta có thể nói rằng một đối tượng ngân khoản là một thể hiện của
¨ ta có thể tạo thể hiện của Point hay Circle tài sản, nhưng thực ra không phải, nó là một thể hiện của lớp
¨ ta có thể tạo thể hiện của MotorVehicle hay Car dẫn xuất của tài sản

Lớp trừu tượng – abstract class Lớp trừu tượng


n Chúng ta có thể tạo ra các lớp cơ sở để tái sử dụng mà không
muốn tạo ra đối tượng thực của lớp n Giả sử Asset là lớp trừu
¨ các lớp Point, Circle, Rectangle chung nhau khái niệm cùng là hình vẽ tượng, nó có các ích lợi gì?
Shape n Mọi thuộc tính và hành vi
n lớp trừu tượng (Abstract Base Class – ABC) là một lớp không thể của lớp cơ sở (Asset) có
tạo thể hiện mặt trong mỗi thể hiện của các
lớp dẫn xuất (BankAccount, Car, Stock,...)
n Thực tế, ta thường phân nhóm các đối tượng theo kiểu này
¨ chim và ếch đều là động vật, nhưng một con động vật là con gì?
n Ta vẫn có thể nói về quan hệ anh chị em giữa các thể hiện của
các lớp dẫn xuất qua lớp cơ sở.
¨ bia và rượu đều là đồ uống, nhưng một thứ đồ uống chính xác là
cái gì? n nói về một cái ngân khoản cụ thể và một cái xe cụ thể nào đó
như đang nói về tài sản
n Có thể xác định xem một lớp có phải là lớp trừu tượng hay
¨ ta có thể tạo một hàm tính tổng giá trị tài sản của một người,
không khi ta không thể tìm được một thể hiện của lớp này mà lại
không phải là thể hiện của một lớp con trong đó tính đa hình
¨ được thể hiện khi gọi hành vi “compute net worth” của các đối
¨ có con động vật nào không thuộc một nhóm nhỏ hơn không?
tượng tài sản
¨ có đồ uống nào không thuộc một loại cụ thể hơn không?
Lớp trừu tượng Lớp trừu tượng
n Các lớp trừu tượng chỉ có ích khi chúng là các lớp
cơ sở trong cây thừa kế
¨ Ví dụ, nếu ta cho BankAccount là lớp trừu tượng, và nó
không có lớp dẫn xuất nào, vậy nó để làm gì?
n Các lớp cơ sở trừu tượng có thể được đặt tại các
tầng khác nhau của một cây thừa kế.
¨ có thể coi BankAccount là lớp trừu tượng với các lớp con
(chẳng hạn tài khoản tiết kiệm có kỳ hạn và tài khoản
thường...)
¨ cây phả hệ động vật
n Yêu cầu duy nhất là mỗi lớp trừu tượng phải có ít
nhất một lớp dẫn xuất không trừu tượng

Cài đặt lớp trừu tượng Hàm thuần ảo


n Để tạo các lớp trừu tượng, C++ đưa ra khái niệm n Nếu một lớp có một phương thức không có định
hàm “thuần” ảo – pure virtual function nghĩa, C++ sẽ không cho phép tạo thể hiện từ lớp
n Hàm thuần ảo (hay phương thức thuần ảo) là một đó, nhờ vậy, lớp đó trở thành lớp trừu tượng
phương thức ảo được khai báo nhưng không được ¨ các lớp dẫn xuất của lớp đó nếu cũng không cung cấp
định nghĩa cho phương thức đó thì cũng trở thành lớp trừu
định nghĩa. tượng
n Cú pháp hàm thuần ảo: n nếu một lớp dẫn xuất muốn tạo thể hiện, nó phải
¨ đặt “ = 0” vào cuối khai báo phương thức cung cấp định nghĩa cho mọi hàm thuần ảo mà nó
Ví dụ: virtual void MyMethod()= 0; thừa kế
¨ không cần định nghĩa phương thức n Do vậy, bằng cách khai báo một số phương thức là
¨ nếu không có “ = 0” và không định nghĩa, trình biên dịch sẽ thuần ảo, một lớp trừu tượng có thể “bắt buộc” các
báo lỗi lớp con phải cài đặt các phương thức đó

Hàm thuần ảo Hàm thuần ảo


Khai báo work()
n
là hàm thuần ảo n Cũng có thể cung cấp định nghĩa cho hàm
Þ Monkey trở thành thuần ảo
lớp trừu tượng
n Điều này cho phép lớp cơ sở cung cấp mã
class Monkey{
public: mặc định cho phương thức, trong khi vẫn
Monkey(...);
virtual ~Monkey(); cấm lớp trừu tượng tạo thể hiện
virtual void work()=0; //pthức thuần ảo
void eatABanana(); n Để sử dụng đoạn mã mặc định này, lớp
...
}; con cần cung cấp một định nghĩa gọi trực
n Như vậy, nếu các lớp LazyMonkey, CircusMonkey, tiếp phiên bản định nghĩa của lớp cơ sở
MusicalMonkey... muốn tạo thể hiện thì phải tự cài đặt phương
thức work() cho riêng mình một cách tường minh
Hàm thuần ảo Nhận xét
n Ví dụ, trong file chứa định nghĩa Monkey (.cpp), ta có thể có định n Sử dụng hàm thuần ảo là một cách xây dựng chặt
nghĩa phương thức thuần ảo work() như sau: chẽ các cây thừa kế. Nó cho phép người tạo lớp cơ
void Monkey::work()
{
sở quy định chính xác những gì mà các lớp dẫn
cout << “No.” << endl; xuất cần làm, mặc dù ta chưa biết rõ chi tiết
} ¨ Ta có thể khai báo mọi phương thức mà ta muốn các lớp
n Tuy nhiên, vì đây là phương thức thuần ảo nên lớp Monkey con cài đặt (quy định về các tham số và kiểu trả về), ngay
không thể tạo thể hiện cả khi ta không biết chính xác các phương thức này cần
n Nếu muốn lớp LazyMonkey tạo được thể hiện và sử dụng định hoạt động như thế nào.
nghĩa mặc định của work(), ta có thể định nghĩa phương thức n Do làm việc với các hàm ảo, nên ta có đầy đủ ích lợi
work() của LazyMonkey như sau: của đa hình động
void LazyMonkey::work() n Nên khai báo các lớp cơ sở là trừu tượng mỗi khi có
{
Monkey::work();
thể, điều đó làm thiết kế rõ ràng mạch lạc hơn
} ¨ đặc biệt là khi làm việc với các cây thừa kế lớn hoặc các
cây thừa kế được thiết kế bởi nhiều người

Thông tin môn học


n Tên môn: Lập trình C/C++
LẬP TRÌNH C/C++ ¨ C/C++ Programming Language

n Số tín chỉ:
GIỚI THIỆU MÔN HỌC ¨ Lý thuyết: 1 đvht
¨ Bài tập: 2 đvht

n Giáo viên lý thuyết: Lê Thị Mỹ Hạnh


¨ ltmhanh@dut.udn.vn
TS. LÊ THỊ MỸ HẠNH
Khoa Công Nghệ Thông Tin ¨ 0905737577
Đại Học Bách khoa – Đại học Đà Nẵng
08/09/2020 2

Mô tả học phần Mục tiêu học phần


n Học phần cung cấp cho sinh viên kiến thức cơ bản về n Kiến thức:
ngôn ngữ lập trình C/C++ và phương pháp lập trình ¨ Định nghĩa được các khái niệm cơ bản của OOP và các C/C++.
¨ Áp dụng được tính chất của OOP để giải quyết vấn đề bằng
hướng đối tượng; C++.
¨ định hướng cho sinh viên trong việc phân tích thiết kế và triển n Kỹ năng:
khai một chương trình theo phương pháp hướng đối tượng, ¨ Rèn luyện cho sinh viên kỹ năng tư duy logic về cách tổ chức, kỹ
năng phân tích và kỹ năng giải quyết vấn đề.
¨ Hiểu và vận dụng các khái niệm: kiểu dữ liệu trừu tượng, kế
¨ Rèn luyện cho sinh viên kỹ năng sử dụng ngôn ngữ lập trình
thừa trong việc phát triển các kiểu dữ liệu, đa hình,…
C/C++ để cài đặt giải quyết các bài toán cụ thể.
n Học phần này thuộc khối kiến thức chung của ngành n Thái độ:
CNTT và được giảng dạy sau khi sinh viên đã học về Tin ¨ Giúp sinh viên phát triển được tính chuyên nghiệp trong môi
trường công việc và trong phát triển phần mềm, có ý thức chủ
học đại cương và các học phần cơ sở khác của ngành động, sáng tạo và trách nhiệm trong các hoạt động nghề nghiệp.
Công nghệ Thông tin 08/09/2020 08/09/2020
Điều kiện tiên quyết
Chuẩn đầu ra học phần
n Đã hoàn thành môn “Tin học đại cương”

n Hiểu và mô tả được các khái niệm và đặc trưng n Kiến thức cơ bản về C/C++
hướng đối tượng, thành phần, kỹ thuật lập trình n Kiến thức cơ bản về lập trình,
hướng đối tượng với C++
¨ các cấu trúc dữ liệu cơ bản: mảng, xâu, con trỏ
n Áp dụng các kỹ thuật để phân tích, thiết kế và
¨ giải thuật cơ bản: sắp xếp, tìm kiếm
cài đặt chương trình theo tiếp cận hướng đối
tượng n Phong cách lập trình (đặt tên, chú thích, lùi đầu dòng,
n Đánh giá các thiết kế và chương trình hướng đối tách dòng…) sẽ được yêu cầu trong các bài tập, bài thi.
tượng.

08/09/2020 6
08/09/2020

Nội dung môn học


¢ Giới thiệu học phần và ôn tập về C
Tài liệu tham khảo
¢ Chương 1. Hàm
n Sách tham khảo chính (có thể chọn một trong hai)
¨ Eckel, Bruce. Thinking in C++, 2nd Ed. Vol 1. Vol.2. (*)
¢ Truyền Tham số, Hàm nội tuyến (inline), Hàm với đối số mặc định, Hàm trả về tham
¨ Dietel & Dietel. C++ How to Program, 3rd Ed. 2003 (*)
chiếu, Con trỏ hàm, Đa năng hoá
n Sách đọc thêm
¢ Chương 2. Mảng và con trỏ
¨ Stroustrup, Bjarne. The C++ Programming Language, 3rd Ed. (*)
¢ Chương 3. Kiểu dữ liệu cấu trúc n tốt cho việc tra cứu

¢ Chương 4. Vào ra trên tệp n Lê Đăng Hưng, Đặng Tuấn Anh, Nguyễn Hữu Đức,
Nguyễn Thanh Thuỷ,
¢ Chương 5. Lớp và đối tượng
¨ Lập trình hướng đối tượng với C++. NXB Khoa học kỹ thuật
¢ Chương 6. Tính thừa kế và đa hình ¨ chỉ khuyên dùng trong trường hợp không thể đọc sách tiếng
¢ Chương 7. Một số kỹ thuật nâng cao trong C++ Anh,
¨ nội dung không cập nhật lắm.

08/09/2020 7 08/09/2020 8

Đánh giá kết quả học tập BÀI TẬP

n Thi giữa kỳ: (30%)


¨ 02 bài kiểm tra quá trình ¨ Bài tập 0: Sử dụng các mở rộng của C++, con trỏ trong C++ và các
thuật toán tìm kiếm, sắp xếp (không được sử dụng các hàm có sẵn).
¨ Bài tập 1: Sử dụng lớp và đối tượng.
n Thi học kỳ: (70%)
¨ Bài tập 2: Đa năng hóa hàm và toán tử.
¨ Bài thi trắc nghiệm trên máy tính + 03 bài kiểm tra quá trình ¨ Bài tập 3: Áp dụng danh sách liên kết, stack và hướng đối tượng (kế
thừa, đa hình, template, exception) viết các ứng dụng quản lý.
n Bài tập:
¨ Dự tính 3 bài tập lập trình (2 bài tại lớp)

¨ Bài trắc nghiệm nhỏ trên Kahoot

¨ Chuyên cần
08/09/2020 9 08/09/2020 10
BÀI KIỂM TRA Chuẩn và quy ước lập trình
n Test 1: Các mở rộng của C++ & Con trỏ trong C++
n Test 2: Lớp và đối tượng & Đa năng hóa
n Test 3: Kế thừa & Đa hình
n Test 4: Template & Exception

n Các bài test sẽ làm trắc nghiệm trên máy tính sau
khi kết thúc phần kiến thức dạy trên lớp. (thường
sẽ test vào cuối tuần, tại phòng thực hành Khoa
CNTT).

08/09/2020 11

Chuẩn và quy ước lập trình Chuẩn và quy ước lập trình
n Vì sao phải có chuẩn và quy ước? n Không có chuẩn chung toàn thế giới!!
n Làm việc một mình: n Quy ước đặt tên (Naming Convention):
¨ Tự làm tự hiểu. ¨ Quy tắc vàng: tên phải thể hiện ý nghĩa.
¨ Mình luôn hiểu mình? n x, y, f, g, … ?!
n total, rate, create, run, … !!
n Làm việc nhóm: ¨ Quy tắc đặt tên theo kiểu “lạc đà” (Camel Case)
¨ Mỗi người một việc. n Dùng để viết các từ dính liền nhau.
¨ Ráp nối công việc. n Viết hoa chữ cái đầu mỗi từ.
¨ Mọi người luôn hiểu nhau? n UpperCamelCase (thường gọi là PascalCase) nếu ký tự đầu
tiên của câu được viết hoa.
¨ Ví dụ: TheQuickBrownFoxJumpsOverTheLazyDog
Phối hợp công việc hiệu quả Áp đặt kỷ luật!! n lowerCamelCase (thường gọi là camelCase) nếu ký tự đầu
tiên của câu được viết thường.
¨ Ví dụ: theQuickBrownFoxJumpsOverTheLazyDog

Chuẩn và quy ước lập trình


n Quy ước viết câu lệnh:
¨ Quy tắc vàng: viết câu lệnh rộng rãi, rõ ràng.
n x=a+b-c*d; for(int i=0;i<n;i++);
n x = a + b – c * d;
Các thuật toán sắp xếp
for (int i = 0; i < n; i++); và tìm kiếm
¨ Viết mỗi câu lệnh một dòng.
¨ Viết cách khoảng giữa hai đoạn lệnh.
THAM KHẢO
n Quy ước viết chú thích:
¨ Quy tắc vàng: viết chú thích đầy đủ, dễ hiểu.
¨ Viết chú thích cho từng hàm.
¨ Dùng dấu // thay cho /* */.
LINEAR SREACH LINEAR SREACH
n Thường sử dụng với các mảng chưa được sắp xếp. n Thuật toán:
n Ý tưởng: tiến hành so sánh số x với các phần tử trong mảng cho
đến khi gặp được phần tử có giá trị cần tìm.

n Input:
¨ Mảng A[n] và giá trị cần tìm x

n Output:
¨ True: nếu tìm thấy;
¨ False: nếu không tìm thấy.

BINARY SREACH BINARY SREACH


n Thường dùng cho mảng đã sắp xếp thứ tự;
n Ý tưởng:
¨ Tìm kiếm kiểu tra “từ điển”;
¨ Tìm cách giới hạn phạm vi tìm kiếm sau mỗi lần
so sánh x với một phần tử trong mảng đã được sắp
xếp;
¨ Tại mỗi bước, so sánh x với phần tử nằm ở vị trí
giữa của mảng tìm kiếm hiện hành:
n Nếu x nhỏ hơn thì sẽ tìm kiếm ở nửa mảng trước;
n Ngược lại, tìm kiếm ở nửa mảng sau.

BINARY SREACH BINARY SREACH


n Thuật toán:
INTERPOLATION SREACH INTERPOLATION SREACH
n Biến thể của Binary Search, sử dụng với mảng đã được
sắp xếp;
n Ý tưởng:
¨ Step 1:
§ Binary Search: pos = left + (right – left)/2;
§ Cải tiến:
pos = left + (X- T[left]) * (right – left)/(T[right] – T[left])
¨ Step 2:
§ Kiểm A[pos] nếu bằng X thì pos là vị trí cần tìm;
§ Nếu nhỏ hơn X thì ta tăng left lên một đơn vị và tiếp tục
thực hiện lại bước 1;
§ Nếu lớn hơn X thì ta giảm right một đơn vị và tiếp tục thực
hiện lại bước 1.

INTERPOLATION SREACH BUBBLE SORT


n Sắp xếp nổi bọt;
n Ý tưởng: xuất phát từ đầu mảng, so sánh 2 phần tử cạnh nhau để đưa
phần tử nhỏ hơn lên trước; sau đó lại xét cặp tiếp theo cho đến khi tiến
về đầu mảng. Nhờ vậy, ở lần xử lý thứ i sẽ tìm được phần tử ở vị trí
đầu mảng là i.
¨ Step 1: i = 0
¨ Step 2: j = n – 1
n Trong khi j > i thực hiện: nếu a[j] < a[j – 1]: hoán vị a[j] & a[j– 1], j++;
¨ Step 3: i++
n Nếu i > n + 1 thì dừng, ngược lại, lặp lại step 2.

BUBBLE SORT BUBBLE SORT


BUBBLE SORT BUBBLE SORT
n Lưu đồ giải thuật

SELECTION SORT SELECTION SORT


n Sắp xếp chọn;
n Ý tưởng:
n Chọn phần tử nhỏ nhất trong n phần tử ban đầu của
mảng, đưa phần tử này về vị trí đầu tiên của mảng;
sau đó loại nó ra khỏi danh sách sắp xếp;
n Mảng hiện hành còn n – 1 phần tử của mảng ban đầu,
bắt đầu từ vị trí thứ 2; lặp lại quá trình trên cho mảng
hiện hành đến khi mảng hiện hành còn 1 phần tử.

SELECTION SORT SELECTION SORT


n Giải thuật:
INSERTION SORT INSERTION SORT
n Sắp xếp chèn
¨Ý tưởng:

INSERTION SORT MEGRE SORT


n Giải thuật: n Sắp xếp trộn;
¨Ý tưởng:
n Chia mảng lớn thành những mảng con nhỏ hơn;
n Tiếp tục chia cho đến khi mảng con nhỏ nhất là 1
phần tử;
n Tiến hành so sánh 2 mảng con có cùng mảng cơ
sở (vừa so sánh, vừa sắp xếp & ghép) cho đến khi
gộp thành 1 mảng duy nhất

MEGRE SORT MEGRE SORT


MEGRE SORT SHELL SORT
n Ý tưởng:
n Sử dụng Selection Sort trên các phần tử có khoảng cách xa nhau, sau đó
sắp xếp các phần tử có khoảng cách hẹp hơn;
n Khoảng cách này là interval: là số vị trí từ phần tử này đến phần tử khác.
n h = h * 3 + 1 (h là interval với giá trị ban đầu là 1).

SHELL SORT SHELL SORT


•h=1
n h=4 n Ví dụ:

n h=2

QUICK SORT QUICK SORT


n Sắp xếp nhanh;
¨ Ý tưởng:
n QuickSort chia mảng thành hai danh sách bằng cách so sánh từng
phần tử của danh sách với một phần tử được chọn được gọi là phần
tử chốt. Những phần tử nhỏ hơn hoặc bằng phần tử chốt được đưa về
phía trước và nằm trong danh sách con thứ nhất, các phần tử lớn hơn
chốt được đưa về phía sau và thuộc danh sách con thứ hai. Cứ tiếp
tục chia như vậy tới khi các danh sách con đều có độ dài bằng 1.
¨ Cách chọn phần tử chốt:
n Chọn phần tử đứng đầu hoặc đứng cuối;
n Chọn phần tử đứng giữa mảng;
n Chọn phần tử trung vị trong 3 phần tử đứng đầu, đứng giữa và đứng
cuối;
n Chọn phần tử ngẫu nhiên.
QUICK SORT HEAP SORT
n Ví dụ: n Sắp xếp vun đống;
n Ý tưởng:
n Phiên bản cải tiến của Selection Sort khi chia các
phần tử thành 2 mảng con, 1 mảng các phần tử đã
được sắp xếp và mảng còn lại các phần tử chưa
được sắp xếp. Trong mảng chưa được sắp xếp, các
phần tử lớn nhất sẽ được tách ra và đưa vào mảng
đã được sắp xếp. Điều cải tiến ở Heapsort so với
Selection sort ở việc sử dụng cấu trúc dữ liệu heap
thay vì tìm kiếm tuyến tính (linear-time search) như
Selection sort để tìm ra phần tử lớn nhất.

HEAP SORT
n Ví dụ: CHƯƠNG 7:

Template
(Khuôn mẫu)
TS. LÊ THỊ MỸ HẠNH
Bộ môn Công nghệ Phần mềm
Khoa Công Nghệ Thông Tin
Đại học Bách khoa – Đại học Đà Nẵng

Nội dung Lập trình tổng quát


n Lập trình tổng quát (generic programming) n Lập trình tổng quát là phương pháp lập
n Lập trình tổng quát trong C
trình độc lập với chi tiết biểu diễn dữ liệu
¨ Tư tưởng là ta định nghĩa một khái niệm
n C++ template
không phụ thuộc một biểu diễn cụ thể nào, và
n Khuôn mẫu hàm sau đó mới chỉ ra kiểu dữ liệu thích hợp làm
n Khuôn mẫu lớp tham số

n Các tham số template khác


n Template sử dụng template

Khoa Công nghệ Thông tin 1 Khoa Công nghệ Thông tin 2
Lập trình tổng quát Lập trình tổng quát
n Ta đã quen với ý tưởng có một phương thức được n Ví dụ, xét hàm sau:
định nghĩa sao cho khi sử dụng với các lớp khác void swap(int& a, int& b) {
nhau, nó sẽ đáp ứng một cách thích hợp int temp;
¨ Khi nói về đa hình, nếu phương thức "draw" được gọi cho temp = a; a = b; b = temp;
một đối tượng bất kỳ trong cây thừa kế Shape, định nghĩa }
tương ứng sẽ được gọi để đối tượng được vẽ đúng ¨ Hàm trên chỉ cần hoán đổi giá trị chứa trong hai biến int.
¨ Trong trường hợp này, mỗi hình đòi hỏi một định nghĩa ¨ Nếu ta muốn thực hiện việc tương tự cho một kiểu dữ liệu
phương thức hơi khác nhau để đảm bảo sẽ vẽ ra hình khác, chẳng hạn float?
đúng ¨ Có thực sự cần đến cả hai phiên bản không?
n Nhưng nếu định nghĩa hàm cho các kiểu dữ liệu void swap(float& a, float& b) {
khác nhau nhưng không cần phải khác nhau thì float temp;
sao? temp = a; a = b; b = temp;
}
Khoa Công nghệ Thông tin 3 Khoa Công nghệ Thông tin 4

class Stack {

Lập trình tổng quát public:


Stack(); Lập trình tổng quát
~Stack();
n Ví dụ khác: ta định nghĩa void push(const int& i);
void pop(int& i);
n Ta thấy, trong một số trường hợp, đưa chi tiết về kiểu dữ liệu
một lớp biểu diễn cấu trúc bool isEmpty() const; vào trong định nghĩa hàm hoặc lớp là điều không có lợi
...
ngăn xếp cho kiểu int private: ¨ Trong khi ta cần các định nghĩa khác nhau cho "draw" của Point
n Ta thấy khai báo và định int *data; hay Circle, vấn đề khác hẳn với trường hợp một hàm chỉ có
...
nghĩa của Stack phụ thuộc }; nhiệm vụ hoán đổi hai giá trị

tại một mức độ nào đó vào kiểu dữ liệu int n Thực ra, khái niệm lập trình tổng quát học theo sự sử dụng một
¨ Một số phương thức lấy tham số và trả về kiểu int phương pháp của lớp cơ sở cho các thể hiện của các lớp dẫn
¨ Nếu ta muốn tạo ngăn xếp cho một kiểu dữ liệu khác thì xuất
sao? ¨ Ví dụ, trong cây thừa kế, ta muốn cùng một phương thức draw()
¨ Ta có nên định nghĩa lại hoàn toàn lớp Stack (kết quả sẽ được thực thi, bất kể con trỏ/tham chiếu đang chỉ tới một Point
tạo ra nhiều lớp chẳng hạn IntStack, FloatStack, …) hay hay Circle
không? n Với lập trình tổng quát, ta tìm cách mở rộng sự trừu tượng hoá
ra ngoài địa hạt của các cây thừa kế
Khoa Công nghệ Thông tin 5 Khoa Công nghệ Thông tin 6

Lập trình tổng quát trong C C++ template


n Sử dụng trình tiền xử lý của C n Template (khuôn mẫu) là một cơ chế thay thế mã
¨ Trình tiền xử lý thực hiện thay thế text trước khi dịch cho phép tạo các cấu trúc mà không phải chỉ rõ kiểu
¨ Do đó, ta có thể dùng #define để chỉ ra kiểu dữ liệu và dữ liệu
thay đổi tại chỗ khi cần n Từ khoá template được dùng trong C++ để báo cho
#define TYPE int trình biên dịch rằng đoạn mã theo sau sẽ thao tác
void swap(TYPE & a, TYPE & b) {
TYPE temp; trên một hoặc nhiều kiểu dữ liệu chưa xác định
Trình tiền xử lý sẽ thay
temp = a; a = b; b = temp; ¨ Từ khoá template được theo sau bởi một cặp ngoặc nhọn
} mọi "TYPE" bằng "int"
trước khi thực hiện biên dịch chứa tên của các kiểu dữ liệu tuỳ ý được cung cấp
n Hai hạn chế:
¨ nhàm chán và dễ lỗi
¨ chỉ cho phép đúng một định nghĩa trong một chương trình
Chú ý: Một lệnh template chỉ có hiệu quả đối với khai báo ngay sau nó
Khoa Công nghệ Thông tin 7 Khoa Công nghệ Thông tin 8
C++ template Khuôn mẫu hàm
n Hai loại khuôn mẫu cơ bản: n Khuôn mẫu hàm là dạng khuôn mẫu đơn giản nhất
cho phép ta định nghĩa các hàm dùng đến các kiểu
¨ Function template – khuôn mẫu hàm cho
dữ liệu tuỳ ý
phép định nghĩa các hàm tổng quát dùng đến
n Định nghĩa hàm swap() bằng khuôn mẫu:
các kiểu dữ liệu tuỳ ý
¨ Class template – khuôn mẫu lớp cho phép template <typename T>
void swap(T & a, T & b) {
định nghĩa các lớp tổng quát dùng đến các T temp;
kiểu dữ liệu tuỳ ý temp = a; a = b; b = temp;
}
n Ta sẽ mô tả từng loại trước khi đi bàn đến
n Phiên bản trên trông khá giống với phiên bản swap()
những phức tạp của lập trình khuôn mẫu bằng C sử dụng #define, nhưng nó mạnh hơn nhiều

Khoa Công nghệ Thông tin 9 Khoa Công nghệ Thông tin 10

Khuôn mẫu hàm Khuôn mẫu hàm


n Thực chất, khi sử dụng template, ta đã định nghĩa một n Hãy xem xét hoạt động của trình biên dịch khi gặp
tập vô hạn các hàm chồng nhau với tên swap() lời gọi swap() thứ nhất (với hai tham số int)
n Để gọi một trong các phiên bản này, ta chỉ cần gọi nó với ¨ Trước hết, trình biên dịch tìm xem có một hàm swap()
kiểu dữ liệu tương ứng được khai báo với 2 tham số kiểu int hay không
int x = 1, y = 2; n Nó không tìm thấy một hàm thích hợp, nhưng tìm thấy một
float a = 1.1, b = 2.2; template có thể dùng được
swap(x, y); // Invokes int version of swap() ¨ Tiếp theo, nó xem xét khai báo của template swap() để
swap(a, b); // Invokes float version of swap() xem có thể khớp được với lời gọi hàm hay không
n Biên dịch mã? n Lời gọi hàm cung cấp hai tham số thuộc cùng một kiểu (int)
n Trình biên dịch thấy template chỉ ra hai tham số thuộc cùng
¨ Trước hết, sự thay thế "T" trong khai báo/định nghĩa hàm swap()
không phải thay thế text đơn giản và cũng không được thực hiện kiểu T, nên nó kết luận rằng T phải là kiểu int
bởi trình tiền xử lý n Do đó, trình biên dịch kết luận rằng template khớp với lời gọi
hàm
¨ Việc chuyển phiên bản mẫu của swap() thành các cài đặt cụ thể
cho int và float được thực hiện bởi trình biên dịch
Khoa Công nghệ Thông tin 11 Khoa Công nghệ Thông tin 12

Khuôn mẫu hàm Khuôn mẫu hàm


n Cuối quy trình biên dịch đoạn mã trong ví dụ, sẽ có hai
n Khi đã xác định được template khớp với lời gọi hàm,
phiên bản của swap() được tạo (một cho hai tham số kiểu
trình biên dịch kiểm tra xem đã có một phiên bản int, một cho hai tham số kiểu float) với các lời gọi hàm của
của swap() với hai tham số kiểu int được sinh ra từ ta được liên kết với phiên bản thích hợp
template hay chưa ¨ Có chi phí phụ về thời gian biên dịch đối với việc sử dụng template
¨ Nếu đã có, lời gọi được liên kết (bind) với phiên bản đã ¨ Có chi phí phụ về không gian liên quan đến mỗi cài đặt của swap()
được tạo trong khi biên dịch
được sinh (lưu ý: khái niệm liên kết này giống với khái
¨ Tuy nhiên, tính hiệu quả của các cài đặt đó cũng không khác với khi
niệm ta đã nói đến trong đa hình tĩnh)
ta tự cài đặt chúng.
¨ Nếu không, trình biên dịch sẽ sinh một cài đặt của swap()
int x = 1, y = 2;
lấy hai tham số kiểu int (thực ra là viết đoạn mã mà ta sẽ float a = 1.1, b = 2.2;
tạo nếu ta tự mình viết) – và liên kết lời gọi hàm với phiên ...
swap<int>(x, y); // Invokes int version of Swap()
bản vừa sinh. swap<float>(a, b); // Invokes float version of Swap()
Khoa Công nghệ Thông tin 13 Khoa Công nghệ Thông tin 14
Khuôn mẫu lớp Khuôn mẫu lớp
n Ví dụ, cấu trúc Pair gồm một cặp giá trị thuộc kiểu tuỳ ý
n Tương tự với khuôn mẫu hàm với tham số thuộc các
struct Pair {
kiểu tuỳ ý, ta cũng có thể định nghĩa khuôn mẫu lớp int first;
n Trước hết, xét khai báo Pair
(class template) sử dụng các thể hiện của một hoặc cho một cặp giá trị kiểu int:
int second;
};
nhiều kiểu dữ liệu tuỳ ý
template <typename T>
¨ Ta cũng có thể định nghĩa template cho struct và union n Ta có thể sửa khai báo trên thành struct Pair {
¨ Ví dụ: tạo 1 struct Pair như sau: một khuôn mẫu lấy kiểu tuỳ ý: T first;
Tuy nhiên hai thành viên first và second T second;
n Khai báo Pair cho 1 cặp giá trị kiểu int; };
phải thuộc cùng kiểu
n Ta có thể sửa khai báo trên thanh 1 khuôn mẫu lấy
kiểu tùy ý. Tuy nhiên 2 thành viên first & second phải n Hoặc ta có thể cho phép hai template <typename T, typename U>
thuộc cùng kiểu; struct Pair {
thành viên nhận các kiểu dữ T first;
n Hoặc ta có thể cho phép 2 thành viên nhận các kiểu liệu khác nhau: U second;
dữ liệu khác nhau. };
Khoa Công nghệ Thông tin 15 Khoa Công nghệ Thông tin 16

Khuôn mẫu lớp Khuôn mẫu lớp


n Để tạo các thể hiện của template Pair, ta phải dùng ký n Cũng như khuôn mẫu hàm, không có struct Pair mà
hiệu cặp dấu < > chỉ có các struct có tên Pair<int, int>, Pair<int,float>,
¨ Khác với khuôn mẫu hàm khi ta có thể bỏ qua kiểu dữ liệu cho Pair<int,char>,…
các tham số, đối với khuôn mẫu class/struct/union, chúng phải
n Quy trình tạo các phiên bản struct Pair từ khuôn mẫu
được cung cấp tường minh
cũng giống như đối với khuôn mẫu hàm
n Khi trình biên dịch lần đầu gặp khai báo dùng Pair<int,
int>, nó kiểm tra xem struct đó đã tồn tại chưa, nếu
n Tại sao đòi hỏi kiểu tường minh?
chưa, nó sinh một khai báo tương ứng.
¨ Các lệnh trên làm gì? - cấp phát bộ nhớ cho đối tượng
¨ Đối với các khuôn mẫu cho class, trình biên dịch sẽ sinh cả
¨ Nếu không biết các kiểu dữ liệu được sử dụng, trình biên dịch
các định nghĩa phương thức cần thiết để khớp với khai báo
làm thế nào để biết cần đến bao nhiêu bộ nhớ?
class
Khoa Công nghệ Thông tin 17 Khoa Công nghệ Thông tin 18

Khuôn mẫu lớp Khuôn mẫu lớp


n Một khi đã tạo được một thể hiện của một khuôn n Khi thiết kế khuôn mẫu (cho lớp hoặc hàm), thông
mẫu class/struct/union, ta có thể tương tác với nó thường, ta nên tạo một phiên bản cụ thể trước, sau đó
như thể nó là thể hiện của một class/struct/union mới chuyển nó thành một template
thông thường. ¨ Ví dụ, ta sẽ bắt đầu bằng việc cài đặt hoàn chỉnh Stack cho
số nguyên
Pair<int, int> q;
Pair<int, float> r; n Điều đó cho phép phát hiện các vấn đề về khái niệm
q.first = 5;
q.second = 10;
trước khi chuyển thành phiên bản cho sử dụng tổng
r.first = 15; quát
r.second = 2.5; ¨ khi đó, ta có thể test tương đối đầy đủ lớp Stack cho số
nguyên để tìm các lỗi tổng quát mà không phải quan tâm đến
n Tiếp theo, ta sẽ tạo một template cho lớp Stack đã các vấn đề liên quan đến template
được mô tả trong các slice trước

Khoa Công nghệ Thông tin 19 Khoa Công nghệ Thông tin 20
Khuôn mẫu lớp Khuôn mẫu lớp
n Khai báo và định nghĩa lớp Stack cho kiểu int
¨ Bắt đầu bằng một ngăn xếp đơn giản

Khoa Công nghệ Thông tin 21 Khoa Công nghệ Thông tin 22

Khuôn mẫu lớp Khuôn mẫu lớp


n Chuyển khai báo và định nghĩa trước thành một phiên bản tổng Mỗi phương thức cần một
lệnh template đặt trước
quát:
Thêm lệnh template để Mỗi khi dùng toán tử phạm vi, cần một ký
nói rằng một phần của kiểu hiệu ngoặc nhọn kèm theo tên kiểu
sẽ được chỉ rõ sau Ta đang định nghĩa một lớp Stack<type>,
chứ không địnhnghĩa lớp Stack

Thay thế kiểu của đối tượng được lưu trong


ngăn xếp (trước là int) bằng kiểu tuỳ ý T

Khoa Công nghệ Thông tin 23 Khoa Công nghệ Thông tin 24

Khuôn mẫu lớp Template Stack


n Sau đó, ta có thể tạo và sử dụng các thể hiện của
các lớp được định nghĩa bởi template của ta:

int x = 5, y;
char c = 'a', d;

Stack<int> s;
Stack<char> t;

s.push(x);
t.push(c);
s.pop(y);
t.pop(d);

Khoa Công nghệ Thông tin 25 Khoa Công nghệ Thông tin 26
Các tham số khuôn mẫu khác Các tham số khuôn mẫu khác
Nhớ lại rằng trong cài đặt Stack, ta có một hằng max quy định số
n Ta mới nói đến các lệnh template với n
lượng tối đa các đối tượng mà ngăn xếp có thể chứa
tham số thuộc "kiểu" typename ¨ Như vậy, mỗi thể hiện sẽ có cùng kích thước đối với mọi kiểu
của đối tượng được chứa
n Tuy nhiên, còn có hai "kiểu" tham số khác
n Nếu ta không muốn đòi hỏi mọi Stack đều có kích thước tối đa
¨ Kiểu thực sự (ví dụ: int) như nhau?
n Ta có thể thêm một tham số vào lệnh template chỉ ra một số int
¨ Các template
(giá trị này sẽ được dùng để xác định giá trị cho max)

template <typename T, int I>


// Specifies that one arbitrary type T and one int I
// will be parameters in the following statement

n Lưu ý: ta khai báo tham số int giống như trong các khai báo khác

Khoa Công nghệ Thông tin 27 Khoa Công nghệ Thông tin 28

Các tham số khuôn mẫu khác Các tham số khuôn mẫu khác
template <typename T, int I>
n Sửa khai báo và định nghĩa trước để sử dụng tham số mới:
Stack<T,I>::Stack() {
this->current = 0;
template <typename T, int I> Sửa các lệnh
}
class Stack { template
public: Khai báo tham số mới Sửa tên template <typename T, int I>
Stack(); lớp dùng Stack<T, I>::~Stack() {}
~Stack(); cho các
void push(const T& i) throw (logic_error); toán tử template <typename T, int I>
void pop(T& i) throw (logic_error); phạm vi void Stack<T, I>::push(const T& i) {
if (this->current < this->max) {
bool isEmpty() const;
this->contents[this->current++] = i;
bool isFull() const; Sử dụng tham số mới để
}
private: xác định giá trị max của else {
static const int max = I; một lớp thuộc một kiểu throw logic_error(“Stack is full.”);
T contents[max]; nào đó }
int current; }
}; ...

Khoa Công nghệ Thông tin 29 Khoa Công nghệ Thông tin 30

Các tham số khuôn mẫu khác Các tham số khuôn mẫu khác
n Giờ ta có thể tạo các thể hiện của các lớp n Các ràng buộc khi sử dụng các kiểu thực
Stack với các kiểu dữ liệu và kích thước sự làm tham số cho lệnh template:
đa dạng ¨ Chỉ có thể dùng các kiểu số nguyên, con trỏ,
hoặc tham chiếu
Stack<int, 5> s; // Creates an instance of a Stack
// class of ints with max = 5 ¨ Không được gán trị cho tham số hoặc lấy địa
Stack<int, 10> t; // Creates an instance of a Stack
// class of ints with max = 10
chỉ của tham số
Stack<char, 5> u; // Creates an instance of a Stack
// class of chars with max = 5

¨ Lưu ý rằng các lệnh trên tạo thể hiện của 3


lớp khác nhau
Khoa Công nghệ Thông tin 31 Khoa Công nghệ Thông tin 32
Các tham số khuôn mẫu khác Các tham số khuôn mẫu khác
n Một loại tham số thứ ba cho lệnh template chính là n Ta có thể khai báo lớp Map:
một template
n Ví dụ, xét thiết kế khuôn mẫu cho một lớp Map (ánh
xạ) ánh xạ các khoá tới các giá trị
¨ Lớp này cần lưu các ánh xạ từ khoá tới giá trị, nhưng ta
không muốn chỉ ra kiểu của các đối tượng được lưu trữ
ngay từ đầu n Sau đó có thể tạo các thể hiện của Map như sau:
¨ Ta sẽ tạo Map là một khuôn mẫu sao cho có thể sử dụng Map< string, int, Stack> wordcount;
các kiểu khác nhau cho khoá và giá trị n Lệnh trên tạo một thể hiện của lớp Map<string, int, Stack> chứa các
¨ Tuy nhiên, ta cần chỉ ra lớp chứa (container) là một thành viên là một tập các string và một tập các int (giả sử còn có các
template, để nó có thể lưu trữ các khoá và giá trị là các đoạn mã thực hiện ánh xạ mỗi từ tới một số int biểu diễn số lần xuất
kiểu tuỳ ý hiện của từ đó)
¨ Ta đã dùng template Stack để làm Container lưu trữ các thông tin trên
Khoa Công nghệ Thông tin 33 Khoa Công nghệ Thông tin 34

Các tham số khuôn mẫu khác Bài tập


n Như vậy, khi trình biên dịch sinh các khai báo và n Bài tập 7.1:
Định nghĩa lớp template mảng động với những phương thức sau:
định nghĩa thực sự cho các lớp Map, nó sẽ đọc các (Nhóm tạo hủy)
n Khởi tạo mặc định mảng kích thước = 0.
tham số mô tả các thành viên dữ liệu
n Khởi tạo với kích thước cho trước, các phần tử = 0.

n Khi đó, nó sẽ sử dụng khuôn mẫu Stack để sinh mã n Khởi tạo từ một mảng int [ ] với kích thước cho trước.

n Khởi tạo từ một đối tượng IntArray khác.


cho hai lớp Stack<string> và Stack<int> n Hủy đối tượng mảng, thu hồi bộ nhớ.

(Nhóm toán tử)


n Đến đây, ta phải hiểu rõ tại sao container phải là n Toán tử gán: =.

một khuôn mẫu, nếu không, làm thế nào để có thể n Toán tử lấy phần tử: [ ].

n Toán tử nhập, xuất: >>, <<.


dùng nó để tạo các loại stack khác nhau? ¨ Ứng dụng quản lý danh sách HỌC SINH
Khoa Công nghệ Thông tin 35 Khoa Công nghệ Thông tin 36

Ngoại lệ
n Xử lý lỗi
CHƯƠNG 8: n Xử lý lỗi theo kiểu truyền thống

Ngoại lệ
n Ý tưởng về ngoại lệ trong C++
n Giới thiệu về ngoại lệ
n Cú pháp

(Exception) n
n
Ném ngoại lệ
try-catch
n Khớp ngoại lệ
TS. LÊ THỊ MỸ HẠNH
n Chuyển tiếp ngoại lệ
Bộ môn Công nghệ Phần mềm
Khoa Công Nghệ Thông Tin n Chuyện hậu trường
Đại học Bách khoa – Đại học Đà Nẵng n Lớp exception
n Khai báo ngoại lệ

Khoa Công nghệ Thông tin 2


Xử lý lỗi Xử lý lỗi
double MyDivide(double numerator, double denominator) {
n Chương trình nào cũng có khả năng gặp phải các if (denominator == 0.0) {
// Do something to indicate that an error occurred
tình huống không mong muốn }
¨ người dùng nhập dữ liệu không hợp lệ else { xử lý lỗi
rturn numerator / denominator; như thế nào
¨ đĩa cứng bị đầy, } bây giờ?
¨ file cần mở bị khóa }

¨ đối số cho hàm không hợp lệ


n Xử lý như thế nào? n Mã thư viện (nơi gặp lỗi) thường không có
¨ Một chương trình không quan trọng có thể dừng lại đủ thông tin để xử lý lỗi
¨ Chương trình điều khiển không lưu? điều khiển máy bay? ¨ Cần có cơ chế để mã thư viện báo cho mã
ứng dụng rằng nó vì một lý do nào đó không
thể tiếp tục chạy được, để mã ứng dụng xử lý
Khoa Công nghệ Thông tin 3
tùy theo tình huống
Khoa Công nghệ Thông tin 4

Xử lý lỗi truyền thống Xử lý lỗi truyền thống – Hạn chế


n Xử lý lỗi truyền thống thường là mỗi hàm n phải có lệnh kiểm tra lỗi sau mỗi lời gọi hàm
lại thông báo trạng thái thành công/thất bại ¨ code trông rối rắm, dài, khó đọc
qua một mã lỗi n lập trình viên ứng dụng quên kiểm tra, hoặc cố
¨ biến toàn cục (chẳng hạn errno) tình bỏ qua
¨ bản chất con người
¨ giá trị trả về
¨ lập trình viên ứng dụng thường không có kinh nghiệm
n int remove ( const char * filename );
bằng lập trình viên thư viện
¨ tham số phụ là tham chiếu n rắc rối khi đẩy thông báo lỗi từ hàm được gọi
n double MyDivide(double numerator,
sang hàm gọi vì từ một hàm ta chỉ có thể trả về
double denominator,
một kiểu thông báo lỗi
int &status);

Khoa Công nghệ Thông tin 5 Khoa Công nghệ Thông tin 6

C++ exception Các kiểu ngoại lệ


n Exception – ngoại lệ là cơ chế thông báo và xử lý lỗi n Một ngoại lệ là một đối tượng chứa thông
giải quyết được các vấn đề kể trên tin về một lỗi và được dùng để truyền
¨ Tách được phần xử lý lỗi ra khỏi phần thuật toán chính
¨ cho phép 1 hàm thông báo về nhiều loại ngoại lệ
thông tin đó tới cấp thực thi cao hơn
¨ Không phải hàm nào cũng phải xử lý lỗi n Ngoại lệ có thể thuộc kiểu dữ liệu bất kỳ
nếu có một số hàm gọi thành chuỗi, ngoại lệ chỉ cần được xử lý tại
n
một hàm là đủ của C++
¨ không thể bỏ qua ngoại lệ, nếu không, chương trình sẽ kết ¨ có sẵn, chẳng hạn int, char* …
thúc.
¨ hoặc kiểu người dùng tự định nghĩa (thường
n Tóm lại, cơ chế ngoại lệ mềm dẻo hơn kiểu xử lý lỗi
dùng)
truyền thống
¨ các lớp ngoại lệ trong thư viện <exception>

Khoa Công nghệ Thông tin 7 Khoa Công nghệ Thông tin 8
Cơ chế ngoại lệ Cơ chế ngoại lệ
Quy trình gọi hàm và trả về trong trường hợp bình thường
n Quá trình truyền ngoại lệ từ ngữ cảnh n

thực thi hiện hành tới mức thực thi cao


hơn gọi là ném một ngoại lệ (throw an
exception)
¨ vị trí trong mã của hàm nơi ngoại lệ được ...
ném được gọi là điểm ném (throw point) int main() {
int x, y;
n Khi một ngữ cảnh thực thi tiếp nhận và // Prompt user for two numbers
cout << “Enter two integers, separated by a space: ”;
truy nhập một ngoại lệ, nó được coi là bắt cin >> x >> y;

ngoại lệ (catch the exception) cout << “The first number divided by the second is: “
<< MyDivide(x, y) << endl;
}
Khoa Công nghệ Thông tin 9 Khoa Công nghệ Thông tin 10

Cơ chế ngoại lệ Cơ chế ngoại lệ


n Quy trình ném và bắt ngoại lệ n Nếu một hàm không thể bắt ngoại
¨ giả sử người dùng nhập mẫu số lệ Program
bằng 0
¨ giả sử hàm MyLazyDivide() không thể calls
¨ Mã chương trình trong MyDivide() catches
exception bắt ngoại lệ do MyDivide() ném
catches
tạo một ngoại lệ (bằng cách nào main()
n Không phải hàm nào bắt được exception
đó) và ném calls
ngoại lệ cũng có thể bắt được mọi
¨ Khi một hàm ném một ngoại lệ, nó
throws loại ngoại lệ MyLazyDivide()
lập tức kết thúc thực thi và gửi exception
¨ Chẳng hạn hàm f() bắt được các ngoại calls
ngoại lệ đó cho nơi gọi nó
lệ loại E1 nhưng không bắt được các throws
¨ Nếu main() có thể xử lý ngoại lệ, MyDivide()
ngoại lệ loại E2. exception
nó sẽ bắt và giải quyết ngoại lệ
n Ngoại lệ đó sẽ được chuyển lên
n Chẳng hạn yêu cầu người dùng
mức trên cho main() bắt
nhập lại mẫu số

Khoa Công nghệ Thông tin 11 Khoa Công nghệ Thông tin 12

Cơ chế ngoại lệ Cú pháp xử lý ngoại lệ


n Nếu không có hàm nào bắt được ngoại lệ?
n Ta đã có các khái niệm cơ bản về xử lý ngoại lệ, sử
¨ Hiện giờ ví dụ của ta vẫn chưa có đoạn mã
bắt ngoại lệ nào, nếu có một ngoại lệ được
dụng cơ chế đó trong C++ như thế nào?
ném, nó sẽ được chuyển qua tất cả các n Cơ chế xử lý ngoại lệ của C++ có 3 tính năng chính
hàm
¨ khả năng tạo và ném ngoại lệ (sử dụng từ khoá throw)
n Tại mức thực thi cao nhất, chương trình tổng
¨ khả năng bắt và giải quyết ngoại lệ (sử dụng từ khoá
(nơi gọi hàm main()) sẽ bắt mọi ngoại lệ còn
catch)
sót lại mà nó nhìn thấy
n Khi đó, chương trình lập tức kết thúc ¨ khả năng tách lôgic xử lý ngoại lệ trong một hàm ra khỏi
phần còn lại của hàm (sử dụng từ khoá try)
¨ Quy trình này không hoàn toàn giống với
các quy trình "bắt" thông thường và ta nên
tránh không để chúng xảy ra.

Khoa Công nghệ Thông tin 13 Khoa Công nghệ Thông tin 14
throw – Ném ngoại lệ throw – Ném ngoại lệ
n Để ném một ngoại lệ, ta dùng từ khoá throw, kèm theo đối tượng mà ta n Trường hợp cần cung cấp nhiều thông tin hơn cho
định ném hàm gọi, ta tạo một class dành riêng cho các ngoại lệ
throw <object>;
//Throws an exception (replace “<object>” n Ví dụ, ta cần cung cấp cho người dùng 2 số nguyên
//with whatever type of object is to be thrown) ¨ Ta có thể tạo một
n Ta có thể dùng mọi thứ làm ngoại lệ, kể cả giá trị thuộc kiểu có sẵn lớp ngoại lệ:
throw 15; // Throws the int 15 as an exception
throw MyObj(…); // Throws an instance of MyObj as an exception

n Ví dụ, MyDivide() ném ngoại lệ là một string


double MyDivide(double numerator, double denominator) {
if (denominator == 0.0) {
throw string(“The denominator cannot be 0.”); ¨ Sau đó, dùng thể hiện của lớp vừa tạo để làm ngoại lệ
} else {
return numerator / denominator;
}
}
Khoa Công nghệ Thông tin 15 Khoa Công nghệ Thông tin 16

Khối try – catch Khối try – catch


Khối try – catch dùng để:
n
n Có thể có nhiều khối catch, mỗi khối chứa
¨ Tách phần giải quyết lỗi ra khỏi phần có thể sinh lỗi
¨ Quy định các loại ngoại lệ được bắt tại mức thực thi hiện hành
mã để giải quyết một loại ngoại lệ cụ thể
n Cú pháp chung cho khối try – catch: try {
// Code that could generate an exception
try { }
// Code that could generate an exception catch (<Exception type1>) {
} // Code that resolves a type1 exception
catch (<Type of exception>) { }
// Code that resolves an exception of that type Dấu chấm phảy đánh };
catch (<Exception type2>) {
}; dấu kết thúc của // Code that resolves a type2 exception
toàn khối try-catch }
¨ Mã liên quan đến thuật toán nằm trong khối try ...
¨ Mã giải quyết lỗi đặt trong (các) khối catch catch (<Exception typeN>) {
// Code that resolves a typeN exception
n Việc tách mã làm cho chương trình dễ hiểu, dễ bảo trì hơn
};

Khoa Công nghệ Thông tin 17 Khoa Công nghệ Thông tin 18

Khối try – catch Khối try – catch


n Ta viết lại hàm main() ban đầu để sử dụng khối try-catch: int main() { Có thể dùng một biến bool làm cờ báo hiệu thành
int main() { int x, y; công hay thất bại và đưa khối try-catch vào trong
int x, y; double result; một vòng do..while để thực hiện nhiệm vụ cho
double result; bool success; đến khi thành công
// Prompt user for two numbers do {
cout << “Enter two integers, separated by a space: ”; success = true; // Set success to true
cin >> x >> y; cout << “Enter two integers, separated by a space: ”; cin >> x >> y;
try {
try {
result = MyDivide(x, y);
result = MyDivide(x, y);//Use temporary variable to separate
}
call
catch (string& s) {
// to MyDivide() from output code cout << s << endl;
} Chú ý: MyDivide() ném success = false; // An exception occurred - set success to false
catch (string) { };
ngoại lệ là một string
// resolve error } while (success == false); // If success == false, repeat loop
}; cout<<“The first number divided by the second is:“<< result<<endl;
cout<<“The first number divided by the second is:“<< result<< }
endl; Giải quyết lỗi như thế nào?
}

Khoa Công nghệ Thông tin 19 Khoa Công nghệ Thông tin 20
Exception Matching
Khối try – catch (so khớp ngoại lệ)
n Nhận xét về slide trước n Khi một ngoại lệ được ném từ trong một khối try, hệ thống xử lý
ngoại lệ sẽ kiểm tra các kiểu được liệt kê trong khối catch theo
¨ Tại lệnh catch, có hai thay đổi so với phiên thứ tự liệt kê
bản trước: ¨ Khi tìm thấy kiểu ăn khớp, ngoại lệ xem như là được giải quyết,
không cần tiếp tục tìm kiếm.
n Ta đã sửa kiểu thành tham chiếu đến string thay
¨ Nếu không tìm thấy, mức thực thi hiện hành bị kết thúc và ngoại
vì string. Cách này hiệu quả hơn và tránh được lệ sẽ được chuyển lên mức cao hơn.
slicing nếu ta làm việc với các ngoại lệ là thể hiện n Chú ý: khi tìm các kiểu dữ liệu khớp với ngoại lệ, trình biên dịch
của các lớp dẫn xuất nói chung sẽ không thực hiện đổi kiểu tự động
¨ Nếu một ngoại lệ kiểu float được ném, nó sẽ không khớp với
n Ngoại lệ bắt được được đặt tên ("s") để ta có thể
một khối catch cho ngoại lệ kiểu int
truy nhập nó từ bên trong khối catch ¨ Tuy nhiên, một đối tượng hoặc tham chiếu kiểu dẫn xuất sẽ khớp với một
lệnh catch dành cho kiểu cơ sở
n Nếu một ngoại lệ kiểu Car được ném, nó sẽ khớp với một khối catch cho
ngoại lệ kiểu MotorVehicle

Khoa Công nghệ Thông tin 21 Khoa Công nghệ Thông tin 22

Exception Matching Exception Matching


n Nếu ta muốn bắt tất cả các ngoại lệ được ném (kể cả các ngoại
n Do vậy, trong đoạn mã sau, mọi ngoại lệ là đối tượng được sinh từ cây
lệ ta không thể giải quyết)
MotorVehicle sẽ khớp với lệnh catch đầu tiên (các lệnh còn lại sẽ
¨ Ví dụ, ta có thể cần chạy một số đoạn mã dọn dẹp trước khi
không bao giờ chạy)
hàm kết thúc (chẳng hạn dọn dẹp các vùng bộ nhớ cấp phát
try { … }
động)
// Matches everything from the motor vehicle hierarchy
n Để có một lệnh catch bắt được mọi ngoại lệ, ta đặt dấu ba
catch (MotorVehicle& mv){…} catch (Car& c) {…} chấm bên trong lệnh catch
catch (Truck& t) {…}; catch (...) {...}; // This will catch any exception
n Nếu muốn bắt các ngoại lệ dẫn xuất tách khỏi ngoại lệ cơ sở, ta phải n Do tham số ba chấm bắt được mọi ngoại lệ, ta chỉ nên sử dụng
xếp lệnh catch cho lớp dẫn xuất lên trước: nó cho lệnh catch cuối cùng trong một khối try-catch (nếu
try { … } không, nó sẽ vô hiệu hoá các lệnh catch đứng sau)
catch (Car& c) {…} try {...}
catch (Truck& t) {…} catch (<exception type1>){...}
catch (<exception type2>){...}
catch (MotorVehicle& mv) {…} ; ...
catch (<exception typeN>){...}
catch (…) {...};
Khoa Công nghệ Thông tin 23 Khoa Công nghệ Thông tin 24

Re-Throwing Exception
(chuyển tiếp ngoại lệ)
Chuyển tiếp ngoại lệ
n Khi một ngoại lệ được chuyển tiếp, mọi lệnh catch
n Trong một số trường hợp, ta có thể muốn chuyển tiếp một ngoại
còn lại trong khối sẽ bị bỏ qua, ngoại lệ được chuyển
lệ mà ta đã bắt - thường là khi ta không có đủ Thông tin để giải
quyết trọn vẹn ngoại lệ đó
lên mức thực thi tiếp theo (như khi ném một ngoại lệ
mới)
¨ Thông thường, ta sẽ chuyển tiếp một ngoại lệ sau khi thực hiện
một số công việc dọn dẹp bên trong một lệnh catch ba chấm n Do ngoại lệ được ném lại mà không bị sửa đổi, ta có
¨ Để chuyển tiếp, ta dùng từ khoá throw, không kèm tham số, từ thể thấy được tầm quan trọng của việc sử dụng tham
bên trong lệnh catch. chiếu
catch (...) { ¨ Nếu một lệnh catch cho lớp cơ sở với tham số không phải
cout << “An exception was thrown.” << endl;
tham chiếu bắt được một ngoại lệ thuộc lớp dẫn xuất, nó sẽ
slice (thay đổi vĩnh viễn) đối tượng để đối tượng đó chỉ
...
còn chứa các thuộc tính của lớp cơ sở
throw; // Re-throw exception
¨ Bằng cách sử dụng tham chiếu, ta có thể đảm bảo rằng
};
ngoại lệ được chuyển tiếp mà không bị thay đổi.
Khoa Công nghệ Thông tin 25 Khoa Công nghệ Thông tin 26
Quản lý bộ nhớ Quản lý bộ nhớ
n Ta đã biết: khi một ngoại lệ được ném, hàm hiện đang n Ví dụ
chạy sẽ kết thúc, điều khiển được trả về cho mức thực thi bool FlightList::contains(Flight *f) const throw(char*){
tiếp theo cao hơn cho đến khi gặp điểm bắt ngoại lệ. stack Flight flight;
Airport *a = new Airport(“SFO”, “San Francisco”);
sẽ được cuốn (unwind) cho đến khi gặp điểm bắt ngoại lệ
...
n Do đó quy trình dọn dẹp tự động xảy ra giống như khi hàm throw “Out of Bounds”;
kết thúc bình thường, đó là: ...
}
¨ Các đối tượng được cấp phát tự động bên trong hàm sẽ ...
được thu hồi try {
if (flightList->contains(flight))
¨ Trong các đối tượng đó, đối với đối tượng bất kỳ mà ...
constructor của nó đã được thực hiện hoàn chỉnh, }
Khi ngoại lệ được ném,
destructor của nó sẽ được gọi catch(char* str) {
destructor cho flight
// Handle the error
¨ Các đối tượng còn lại phải được hủy một cách tường }
được tự động gọi, nhưng
destructor của a thì không.
minh.
Khoa Công nghệ Thông tin 27 Khoa Công nghệ Thông tin 28

Quản lý bộ nhớ Lớp exception


n Nếu không tìm thấy lệnh catch tương ứng, sau khi mọi hàm đã n Để tích hợp hơn nữa các ngoại lệ vào ngôn ngữ
kết thúc, một hàm thư viện đặc biệt terminate() sẽ được chạy C++, lớp exception đã được đưa vào thư viện
(có thể coi đây là nơi bắt ngoại lệ cuối cùng) chuẩn
n Trường hợp mặc định, terminate() gọi hàm abort(), hàm này ¨ sử dụng #include <exception> và namespace std
sẽ lập tức kết thúc chương trình
n Sử dụng thư viện này, ta có thể ném các thể hiện
¨ Trong trường hợp này, quy trình dọn dẹp không xảy ra, như vậy,
destructor của các đối tượng tĩnh và toàn cục sẽ không được gọi
của exception hoặc tạo các lớp dẫn xuất từ đó
¨ Lưu ý rằng terminate() cũng được gọi ngay khi có một ngoại n Lớp exception có một hàm ảo what(), có thể định
lệ được ném trong quy trình dọn dẹp (nghĩa là một destructor nghĩa lại what() để trả về một xâu ký tự
cho một đối tượng cấp phát tự động ném ngoại lệ trong quá trình
unwind) try {...}
n Có thể thay đổi hoạt động của terminate() bằng cách sử dụng catch (exception e) {
hàm set_terminate() cout << e.what();
}

Khoa Công nghệ Thông tin 29 Khoa Công nghệ Thông tin 30

Lớp exception Lớp exception


n Một số lớp ngoại lệ chuẩn khác được dẫn xuất từ n runtime_error dùng để đại diện cho các lỗi trong
lớp cơ sở exception thời gian chạy (các lỗi là kết quả của các tình huống
n File header <stdexcept> (cũng thuộc thư viện không mong đợi, chẳng hạn: hết bộ nhớ)
chuẩn C++) chứa một số lớp ngoại lệ dẫn xuất từ n logic_error dùng cho các lỗi trong lôgic chương
exception trình (chẳng hạn truyền tham số không hợp lệ)
¨ File này cũng đã #include <exception> nên khi dùng n Thông thường, ta sẽ dùng các lớp này (hoặc các
không cần #include cả hai lớp dẫn xuất của chúng) thay vì dùng trực tiếp
n Trong đó có hai lớp quan trọng được dẫn xuất trực exception
tiếp từ exception: ¨ Một lý do là cả hai lớp này đều có constructor nhận tham
¨ runtime_error số là một string mà nó sẽ là kết quả trả về của hàm
¨ logic_error what()

Khoa Công nghệ Thông tin 31 Khoa Công nghệ Thông tin 32
Lớp exception Lớp exception
n runtime_error có các lớp dẫn xuất sau: n Ta có thể viết lại hàm MyDivide() để sử dụng các ngoại lệ chuẩn
¨ range_error điều kiện sau (post-condition) bị vi phạm tương ứng như sau:
¨ overflow_error xảy ra tràn số học
double MyDivide(double numerator, double denominator)
¨ bad_alloc không thể cấp phát bộ nhớ {
n logic_error có các lớp dẫn xuất sau: if (denominator == 0.0) {
throw invalid_argument(“The denominator cannot be 0.”);
¨ domain_error điều kiện trước (pre-condition) bị vi
}
phạm else {
¨ invalid_argument tham số không hợp lệ được truyền return numerator / denominator;
cho hàm }
¨ length_error tạo đối tượng lớn hơn độ dài cho }
phép
¨ out_of_range tham số ngoài khoảng (chẳng hạn n Ta sẽ phải sửa lệnh catch cũ để bắt được ngoại lệ kiểu
invalid_argument (thay cho kiểu string trong phiên bản trước)
chỉ số không hợp lệ

Khoa Công nghệ Thông tin 33 Khoa Công nghệ Thông tin 34

Lớp exception Khai báo ngoại lệ


int main() {
int x, y;
n Vậy, làm thế nào để user biết được một hàm/phương
double result; thức có thể ném những loại ngoại lệ nào?
do {
cout << “Enter two integers, separated by a space: ”; n Đọc chú thích, tài liệu?
cin >> x >> y; ¨ không phải lúc nào cũng có tài liệu và tài liệu đủ thông tin
try {
result = MyDivide(x, y); ¨ không tiện nếu phải kiểm tra cho mọi hàm
}
catch (invalid_argument& e) { n C++ cho phép khai báo một hàm có thể ném những
cout << e.what() << endl;
continue; // “Restart” the loop
loại ngoại lệ nào hoặc sẽ không ném ngoại lệ
}; ¨ một phần của giao diện của hàm
} while (0); // If we reach here, exit loop
cout<<“The first number divided by the second is:“<<result<<endl;
¨ Ví dụ: hàm MyDivide có thể ném ngoại lệ
} invalid_argument
double MyDivide(double numerator, double denominator) throw (invalid_argument);

Khoa Công nghệ Thông tin 35 Khoa Công nghệ Thông tin 36

Khai báo ngoại lệ Khai báo ngoại lệ


n Cú pháp: từ khoá throw ở cuối lệnh khai báo hàm, tiếp n Chuyện gì xảy ra nếu ta ném một ngoại lệ thuộc
theo là cặp ngoặc đơn chứa một hoặc nhiều tên kiểu
(tách nhau bằng dấu phảy) kiểu không có trong khai báo?
void MyFunction(...) throw (type1, type2,…,typeN) {...} ¨ Nếu một hàm ném một ngoại lệ không thuộc các kiểu
đã khai báo, hàm unexpected() sẽ được gọi
¨ hàm không bao giờ ném ngoại lệ:
void MyFunction(...) throw () {...} ¨ Theo mặc định, unexpected() gọi hàm
terminate() mà ta đã nói đến
n Cú pháp tương tự đối với phương thức
¨ Tương tự terminate(), hoạt động của
bool FlightList::contains(Flight *f)const throw(char*){...}
unexpected() cũng có thể được thay đổi bằng cách
n Nếu không có khai báo throw, hàm/phương thức có thể
sử dụng hàm set_unexpected()
ném bất kỳ loại ngoại lệ nào

Khoa Công nghệ Thông tin 37 Khoa Công nghệ Thông tin 38
Khai báo ngoại lệ Khai báo ngoại lệ
n Ta phải đặc biệt thận trọng khi làm việc với các cây thừa n Nhớ lại rằng một khai báo phương thức về cốt yếu là để tuyên
bố những gì người dùng có thể mong đợi từ phương thức đó
kế và các khai báo ngoại lệ
¨ Ta đã nói rằng đưa ngoại lệ vào khai báo hàm/phương thức hạn
n Giả sử ta có lớp cơ sở B chứa một phương thức ảo foo() chế các loại đối tượng có thể được ném từ hàm/phương thức
¨ Ta khai báo rằng foo() có thể ném hai loại ngoại lệ e1 n Khi có sự có mặt của thừa kế và đa hình, điều trên cũng phải áp
và e2 dụng được
class B { n Do vậy, nếu một lớp dẫn xuất override một phương thức của lớp
void foo() throw (e1, e2); cơ sở, nó không thể bổ sung các kiểu ngoại lệ mới vào phương
... thức
}; ¨ Nếu không, ai đó truy nhập phương thức qua một con trỏ tới lớp
n Giả sử D là lớp dẫn xuất của B,D định nghĩa lại foo() cơ sở có thể gặp phải một ngoại lệ mà họ không mong đợi (do
nó không có trong khai báo của lớp cơ sở)
¨ Cần có những hạn chế nào đối với khả năng ném ngoại
n Tuy nhiên, một lớp dẫn xuất được phép giảm bớt số loại ngoại
lệ của D? lệ có thể ném
Khoa Công nghệ Thông tin 39 Khoa Công nghệ Thông tin 40

Khai báo ngoại lệ Khai báo ngoại lệ


n Ví dụ, nếu phiên bản foo() của lớp B có thể n Các ràng buộc tương tự cũng áp dụng khi loại
ném ngoại lệ thuộc kiểu e1 và e2, phiên bản đối tượng được ném thuộc về một cây thừa kế
override của lớp D có thể chỉ được ném ngoại lệ
¨ giả sử ta có cây thừa kế thứ hai gồm các lớp ngoại
thuộc loại e1
lệ, trong đó BE là lớp cơ sở và DE là lớp dẫn xuất
¨ Không vi phạm quy định của lớp cơ sở
n Tuy nhiên, D sẽ không thể bổ sung kiểu ngoại lệ n Nếu phiên bản foo() của B chỉ ném đối tượng
mới, e3, cho các ngoại lệ mà foo() của D có thể thuộc lớp DE (lớp dẫn xuất), thì phiên bản foo()
ném của lớp D không thể ném thể hiện của BE (lớp
¨ Do việc đó vi phạm khẳng định rằng thể hiện của D cơ sở)
"là" thể hiện của B

Khoa Công nghệ Thông tin 41 Khoa Công nghệ Thông tin 42

constructor và ngoại lệ destructor và ngoại lệ


n Cách tốt để thông báo việc khởi tạo không n Không nên để ngoại lệ được ném từ destructor
thành công n Nếu destructor trực tiếp hoặc gián tiếp ném
¨ constructor không có giá trị trả về ngoại lệ, chương trình sẽ kết thúc
¨ một hậu quả: chương trình nhiều lỗi có thể kết thúc
n Cần chú ý để đảm bảo constructor bất ngờ mà ta không nhìn thấy được nguồn gốc có
không bao giờ để một đối tượng ở trạng thể của lỗi
thái khởi tạo dở n Vậy, destructor cần bắt tất cả các ngoại lệ có thể
¨ dọn dẹp trước khi ném ngoại lệ được ném từ các hàm được gọi từ đây

Khoa Công nghệ Thông tin 43 Khoa Công nghệ Thông tin 44

You might also like