Giới thiệu
 Các khái niệm cơ bản
 Lớp và đối tượng 
 Kỹ thuật thừa kế và tính đa hình
 Thiết kế chương trình hướng đối tượng 
 Khuôn mẫu (template) 
 Luồng nhập xuất
 Các mẫu thiết kế hướng đối tượng
              
                                            
                                
            
 
            
                 418 trang
418 trang | 
Chia sẻ: Mr Hưng | Lượt xem: 1017 | Lượt tải: 0 
              
            Bạn đang xem trước 20 trang nội dung tài liệu Lập trình hướng đối tượng, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
NULL;}
List(Item a) { last = new Link(a, NULL); last-
>next = last; }
~List() { CleanUp(); }
bool Empty() const {return last == NULL;}
bool IsMember(Item x) const;
int Count() const;
void View() const;
};
49 of 111
Không dùng kế thừa private
class Stack {
List l;
public:
Stack():l() {}
bool Push(Item x) {l.Insert(x); return true;}
bool Pop(Item *px);
bool Empty() const {return l.Empty();}
};
inline bool Stack::Pop(Item *px) {
Item *p = l.GetFirst();
if (!p) return false;
*px = *p;
return true;
}
50 of 111
Không dùng kế thừa private
class Set {
List l;
public:
Set():l(){}
void Add(Item x) {if (!l.IsMember(x)) Insert(x);}
bool Empty() const {return l.Empty();}
bool IsMember(Item x) const {return 
l.IsMember(x);}
int Count() const {return l.Count();}
void View() const {l.View();}
};
51 of 111
Không dùng kế thừa private
class HinhTron {
double r;
Diem tam;
public:
HinhTron(double tx, double ty, double rr): 
tam(tx,ty),r(rr){}
void Ve(int color) const;
void TinhTien(double dx, double dy) const;
};
52 of 111
Không dùng kế thừa private
Diem dd[] = {Diem(100,100), Diem(200,200), 
Diem(200,50)};
class DaGiac {
MangDiem md;
public:
DaGiac():md(3,3,dd){}
DaGiac(int sd, Diem *pd):md(sd,sd,pd){}
DaGiac(const DaGiac &d):md(d.md){}
DaGiac& operator = (const DaGiac &d);
void Ve(int color) const;
void Quay(Diem Tam, double goc);
void TinhTien(double dx, double dy) const;
Diem TrongTam() const;
//...
};
53 of 111
Phương thức thiết lập và huỷ bỏ
• Phương thức thiết lập và huỷ bỏ là các hàm thành phần đặc 
biệt dùng để tự động khởi động đối tượng khi nó được tạo ra 
và tự động dọn dẹp đối tượng khi nó bị hủy đi. 
• Một đối tượng thuộc lớp con có chứa các thành phần dữ liệu 
của các lớp cơ sở. Có thể xem lớp con có các thành phần 
ngầm định ứng với các lớp cơ sở. Vì vậy khi một đố tượng 
thuộc lớp con được tạo ra, các thành phần cơ sở cũng được 
tạo ra, nghĩa là phương thức thiết lập của các lớp cơ sở phải 
được gọi.
• Trình biên dịch tự động gọi phương thức thiết lập của các lớp 
cơ sở cho các đối tượng (cơ sở) nhúng vào đối tượng đạng 
được tạo ra.
• Đối với phương thức thiết lập của một lớp con, công việc 
đầu tiên là gọi phương thức thiết lập của các lớp cơ sở.
54 of 111
Phương thức thiết lập và huỷ bỏ
• Nếu mọi phương thức thiết lập của lớp cơ sở
đều đòi hỏi phải cung cấp tham số thì lớp con 
bắt buộc phải có phương thức thiết lập để
cung cấp các tham số đó.
class Diem {
double x,y;
public:
Diem(double x, double y):x(xx),y(yy){}
//...
};
class HinhTron: private Diem
{
double r;
public:
void Ve(int color) const;
};
HinhTron t; // SAI
55 of 111
Cung cấp tham số cho phương thức thiết lập của lớp 
cha
• Trong trường hợp đó, lớp con bắt buộc phải có phương 
thức thiết lập để cung cấp tham số cho phương thức thiết 
lập của lớp cơ sở. Cú pháp để gọi phương thức thiết lập 
của lớp cơ sở tương tự như cú pháp thiết lập đối tượng 
thành phần, bản thân tên lớp cơ sở được quan điểm như
đối tượng thành phần nhúng vào lớp con.
class HinhTron: private Diem {
double r;
public:
HinhTron(double tx, double ty, double 
rr):Diem(tx,ty),r(rr){}
void Ve(int color) const;
void TinhTien(double dx, double dy) const;
};
HinhTron t(200,200,50); // Dung
56 of 111
Phương thức thiết lập và huỷ bỏ
class Nguoi {
protected:
char *HoTen;
int NamSinh;
public:
Nguoi(char *ht, int ns):NamSinh(ns) {
HoTen = strdup(ht);
}
//...
};
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : 
Nguoi(ht,ns) { MaSo = strdup(ms);
}
void Xuat() const;
};
57 of 111
Phương thức thiết lập và huỷ bỏ
• Sau khi phương thức thiết lập của các lớp cơ sở được 
gọi, mã chương trình trong bản thân phương thức 
của lớp con sẽ được thực hiện. Nội dung của 
phương thức thiết lập ở lớp con chỉ nên thao tác 
trên dữ liệu của riêng lớp con, việc khởi động dữ 
liệu thuộc lớp cha do phương thức thiết lập ở lớp 
cha đảm nhiệm với các tham số cung cấp bởi lớp 
con.
class SinhVien : public Nguoi
{
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : 
Nguoi(ht,ns) { MaSo = strdup(ms); }
void Xuat() const;
};
58 of 111
Phương thức thiết lập và huỷ bỏ
• Ta có thể khởi động các thành phần của lớp 
cha bên trong phương thức thiết lập của lớp 
con. Trong trường hợp này đối tượng thuộc 
lớp cha phải có khả năng tự khởi động:
class Complex {
protected:
double re, im;
public:
Complex(double r = 0, double i = 0):re(r), im(i){}
Complex operator +(Complex b);
Complex operator -(Complex b);
Complex operator *(Complex b);
Complex operator /(Complex b);
double Norm() const {return sqrt(re*re + im*im);}
};
59 of 111
Phương thức thiết lập và huỷ bỏ
• Hai cách thiết lập đối tượng thuộc lớp con 
sau đây tương đương:
// Cach 1
class Imag: public Complex
{
public:
Imag(double i = 0):Complex(0, i){}
//...
};
// Cach 2
class Imag: public Complex
{
public:
Imag(double i = 0){re = 0; im = i;){}
//...
};
60 of 111
Phương thức thiết lập và huỷ bỏ
class Nguoi {
protected:
char *HoTen;
int NamSinh;
public:
Nguoi(char *ht = "Ng Van A", int ns = 1980)
:NamSinh(ns) {HoTen = strdup(ht);}
~Nguoi() {delete [] HoTen;}
//...
};
61 of 111
Phương thức thiết lập và huỷ bỏ
// Cach 1
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : 
Nguoi(ht,ns) { MaSo = strdup(ms); }
void Xuat() const;
};
// Cach 2
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) { 
HoTen = strdup(ht); MaSo = strdup(ms);
NamSinh = ns; 
}
};
62 of 111
Phương thức thiết lập và huỷ bỏ
• Phương thức thiết lập sao chép là cần 
thiết trong trường hợp đối tượng có nhu 
cầu cấp phát tài nguyên.
class Nguoi {
protected:
char *HoTen;
int NamSinh;
public:
Nguoi(char *ht, int ns):NamSinh(ns) {
HoTen = strdup(ht);
}
Nguoi(const Nguoi &n):NamSinh(n.NamSinh) {
HoTen = strdup(n.HoTen);
}
~Nguoi() { delete [] HoTen;}
//...
};
63 of 111
Phương thức thiết lập và huỷ bỏ
• Ở lớp con liệu có cần phương thức thiết lập 
sao chép ?
// Khong dung pttl sao chep : D hay S
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : 
Nguoi(ht,ns) { MaSo = strdup(ms);}
~SinhVien() {delete [] MaSo;}
//...
};
void main() {
SinhVien s1("Vo Vien Sinh", "200002541",1984);
SinhVien s2(s1);
}
64 of 111
Phương thức thiết lập và huỷ bỏ
• Ở lớp con liệu có cần phương thức thiết 
lập sao chép ?
// Lop con co pttl sao chep : co can thiet ? 
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : 
Nguoi(ht,ns) { MaSo = strdup(ms);}
SinhVien(const SinhVien &s) : Nguoi(s) 
{ MaSo = strdup(s.MaSo);}
//...
};
void main() {
SinhVien s1("Vo Vien Sinh", 
"200002541",1984);
SinhVien s2(s1);
}
65 of 111
Phương thức thiết lập và huỷ bỏ
• Khi một đối tượng bị huỷ đi, phương thức huỷ bỏ của nó sẽ 
được gọi, sau đó phương thức huỷ bỏ của các lớp cơ sở sẽ 
được gọi một cách tự động. Vì vậy lớp con không cần và
cũng không được thực hiện các thao tác dọn dẹp cho các 
thành phần thuộc lớp cha.
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns) { 
MaSo = strdup(ms);}
SinhVien(const SinhVien &s) : Nguoi(s) 
{ MaSo = strdup(s.MaSo);}
~SinhVien() {delete [] MaSo;}
//...
};
66 of 111
Câu Hỏi
• Khi lớp có nhu cầu cấp phát tài nguyên thì
cần có bộ tứ phương thức gì? Cho một ví dụ
bằng ngôn ngữ C++.
• Giả sử đã có lớp PhânSố có các phương thức 
khởi tạo, set/get, các phương thức Cộng và
Trừ. Hãy xây dựng lớp MyPhanSo có thêm 
phương thức Nhân và Chia.
• Khi xây dựng lớp con kế thừa từ lớp cha thì
cần phải khai báo, cài đặt những phương thức 
gì ở lớp con.
67 of 111
Con trỏ và kế thừa
Con trỏ trong kế thừa hoạt động theo nguyên tắc sau:
• Con trỏ đến đối tượng thuộc lớp cơ sở thì có thể trỏ
đến các đối tượng thuộc lớp con.
• Điều ngược lại không đúng, con trỏ đến đối tượng 
thuộc lớp con thì không thể trỏ đến các đối tượng 
thuộc lớp cơ sở.
• Ta có thể ép kiểu để con trỏ đến đối tượng thuộc 
lớp con có thể trỏ đến đối tượng thuộc lớp cơ sở. 
Tuy nhiên thao tác này có thể nguy hiểm.
• Sử dụng ép kiểu đúng cách có thể giải quyết bài 
toán quản lý một danh sách các đối tượng khác 
kiểu.
68 of 111
Con trỏ và kế thừa
void main() {
clrscr();
Nguoi n("Nguyen Van Nhan", 1970);
SinhVien s("Vo Vien Sinh", "200002541",1984);
Nguoi *pn;
SinhVien *ps;
pn = &n;
ps = &s;
pn = &s;
ps = &n; // Sai
ps = pn; // Sai
ps = (SinhVien *)&n; // Sai logic
ps = (SinhVien *)pn;
}
Phương thức ảo và tính đa 
hình
70 of 111
Bài toán quản lý một danh sách các đối tượng khác kiểu
- Giả sử ta cần quản lý một danh sách các đối tượng có
kiểu có thể khác nhau, ta cần giải quyết hai vấn đề: 
Cách lưu trữ và thao tác xử lý.
- Xét trường hợp cụ thể, các đối tượng có thể là người, 
sinh viên hoặc công nhân.
- Về lưu trữ: Ta có thể dùng union, trong trường hợp này 
mỗi đối tượng phải có kích thước chứa được đối tượng 
có kích thước lớn nhất. Điều này gây lãng phí không 
gian lưu trữ. Một cách thay thế là lưu trữ đối tượng bằng 
đúng kích thước của nó và dùng một danh sách (mảng, 
dslk,...) các con trỏ để quản lý các đối tượng. 
- Về thao tác, phải thoả yêu cầu đa hình: Thao tác có
hoạt động khác nhau ứng với các loại đối tượng khác 
nhau. Có hai cách giải quyết là vùng chọn kiểu và
phương thức ảo.
71 of 111
Dùng vùng chọn kiểu
• Về lưu trữ: Ta sẽ dùng một mảng các con trỏ
đến lớp cơ sở để có thể trỏ đến các đối tượng 
thuộc lớp con. 
• Xét lớp Người và các lớp kế thừa sinh viên 
và công nhân. Thao tác ta quan tâm là xuat. 
Ta cần bảo đảm thao tác xuất áp dụng cho 
lớp sinh viên và lớp công nhân khác nhau.
72 of 111
Dùng vùng chọn kiểu
class Nguoi {
protected:
char *HoTen;
int NamSinh;
public:
Nguoi(char *ht, int ns):NamSinh(ns) {HoTen 
= strdup(ht);}
~Nguoi() {delete [] HoTen;}
void An() const { cout << HoTen << " an 3 
chen com";}
void Ngu() const { cout << HoTen << " ngu 
ngay 8 tieng";}
void Xuat() const { cout << "Nguoi, ho 
ten: " << HoTen << " sinh " << NamSinh; }
};
73 of 111
class SinhVien : public Nguoi
{
protected:
char *MaSo;
public:
SinhVien(char *n, char *ms, int ns) : 
Nguoi(n,ns) { MaSo = strdup(ms);}
~SinhVien() {delete [] MaSo;}
void Xuat() const { cout << "Sinh vien " 
<< HoTen << ", ma so " << MaSo;}
};
74 of 111
class NuSinh : public SinhVien
{
public:
NuSinh(char *ht, char *ms, int ns) : 
SinhVien(ht,ms,ns) {}
void An() const { cout << HoTen << " ma so 
" << MaSo << " an 2 to pho";}
};
75 of 111
Dùng vùng chọn kiểu
class CongNhan : public Nguoi
{
protected:
double MucLuong;
public:
CongNhan(char *n, double ml, int ns) : 
Nguoi(n,ns), MucLuong(ml) { }
void Xuat() const { cout << "Cong nhan, 
ten " << HoTen << " muc luong: " << 
MucLuong;}
};
76 of 111
void XuatDs(int n, Nguoi *an[])
{
for (int i = 0; i < n; i++)
{
an[i]->Xuat();
cout << "\n";
}
}
77 of 111
Dùng vùng chọn kiểu
const int N = 4;
void main()
{
Nguoi *a[N];
a[0] = new SinhVien("Vien Van Sinh", 
”200001234", 1982);
a[1] = new NuSinh("Le Thi Ha Dong", 
”200001235", 1984);
a[2] = new CongNhan("Tran Nhan 
Cong", 1000000, 1984);
a[3] = new Nguoi("Nguyen Thanh 
Nhan", 1960);
XuatDs(4,a);
}
78 of 111
Dùng vùng chọn kiểu
• Xuất liệu cho đoạn chương trình trên như sau:
– Nguoi, ho ten: Vien Van Sinh sinh 1982
– Nguoi, ho ten: Le Thi Ha Dong sinh 1984
– Nguoi, ho ten: Tran Nhan Cong sinh 1984
– Nguoi, ho ten: Nguyen Thanh Nhan sinh 1960
• Tất cả mọi đối tượng đều được quan điểm như người vì thao 
tác được thực hiện thông qua con trỏ đến lớp Người.
• Để bảo đảm xuất liệu tương ứng với đối tượng, phải có cách 
nhận diện đối tượng, ta thêm một vùng dữ liệu vào lớp cơ sở
để nhận diện, vùng này có giá trị phụ thuộc vào loại của đối 
tượng và được gọi là vùng chọn kiểu.
• Các đối tượng thuộc lớp người có cùng giá trị cho vùng chọn 
kiểu, các đối tượng thuộc lớp sinh viên có giá trị của vùng 
chọn kiểu khác của lớp người.
79 of 111
Dùng vùng chọn kiểu
class Nguoi
{
public:
enum LOAI {NGUOI, SV, CN};
protected:
char *HoTen;
int NamSinh;
public:
LOAI pl;
Nguoi(char *ht, int ns):NamSinh(ns), pl(NGUOI) {HoTen = 
strdup(ht);}
~Nguoi() {delete [] HoTen;}
void An() const { cout << HoTen << " an 3 chen com";}
void Ngu() const { cout << HoTen << " ngu ngay 8 
tieng";}
void Xuat() const { cout << "Nguoi, ho ten: " << HoTen 
<< " sinh " << NamSinh; }
};
80 of 111
Dùng vùng chọn kiểu
class SinhVien : public Nguoi
{
protected:
char *MaSo;
public:
SinhVien(char *n, char *ms, int ns) : 
Nguoi(n,ns) { 
MaSo = strdup(ms); pl = SV;
}
~SinhVien() {delete [] MaSo;}
void Xuat() const { cout << "Sinh vien " 
<< HoTen << ", ma so " << MaSo;}
};
81 of 111
class NuSinh : public SinhVien
{
public:
NuSinh(char *ht, char *ms, int ns) : 
SinhVien(ht,ms,ns) {}
void An() const { cout << HoTen << " ma so 
" << MaSo << " an 2 to pho";}
};
82 of 111
Dùng vùng chọn kiểu
class CongNhan : public Nguoi
{
protected:
double MucLuong;
public:
CongNhan(char *n, double ml, int ns) : 
Nguoi(n,ns), MucLuong(ml) { pl = CN;}
void Xuat() const { cout << "Cong nhan, 
ten " << HoTen << " muc luong: " << 
MucLuong;}
};
83 of 111
Dùng vùng chọn kiểu
•Khi thao tác ta phải căn cứ vào giá trị của vùng chọn kiểu 
để “ép kiểu” phù hợp.
void XuatDs(int n, Nguoi *an[]) {
for (int i = 0; i < n; i++) {
switch(an[i]->pl) {
case Nguoi::SV:
((SinhVien *)an[i])->Xuat();
break;
case Nguoi::CN:
((CongNhan *)an[i])->Xuat();
break;
default: 
an[i]->Xuat(); 
break;
}
cout << "\n";
}
}
84 of 111
Dùng vùng chọn kiểu
const int N = 4;
void main() {
Nguoi *a[N];
a[0] = new SinhVien("Vien Van Sinh", 
"200001234", 1982);
a[1] = new NuSinh("Le Thi Ha Dong", 
"200001235", 1984);
a[2] = new CongNhan("Tran Nhan Cong", 
1000000, 1984);
a[3] = new Nguoi("Nguyen Thanh Nhan", 
1960);
XuatDs(4,a);
}
Sinh vien Vien Van Sinh, ma so 200001234
Sinh vien Le Thi Ha Dong, ma so 200001235
Cong nhan, ten Tran Nhan Cong muc luong: 
1000000
Nguoi, ho ten: Nguyen Thanh Nhan sinh 1960
85 of 111
Dùng vùng chọn kiểu
• Cách tiếp cận trên giải quyết được vấn để: 
Lưu trữ được các đối tượng khác kiểu nhau 
và thao tác khác nhau tương ứng với đối 
tượng. Tuy nhiên nó có các nhược điểm sau:
– Dài dòng với nhiều switch, case.
– Dễ sai sót, khó sửa vì trình biên dịch bị cơ 
chế ép kiểu che mắt.
– Khó nâng cấp ví dụ thêm một loại đối 
tượng mới, đặc biệt khi chương trình lớn.
• Các nhược điểm trên có thể được khắc phục 
nhờ phương thức ảo.
86 of 111
Phương thức ảo
• Con trỏ thuộc lớp cơ sở có thể trỏ đến lớp con:
– Nguoi* pn = new SinhVien(“Le Vien Sinh”, 
200001234, 1982);
• Ta mong muốn thông qua con trỏ thuộc lớp cơ sở có thể
truy xuất hàm thành phần được định nghĩa lại ở lớp con:
– pn->Xuat(); // Mong muon: goi Xuat cua lop sinh 
vien, 
– // thuc te: goi Xuat cua lop Nguoi
• Phương thức ảo cho phép giải quyết vấn đề. Ta qui định 
một hàm thành phần là phương thức ảo bằng cách thêm 
từ khoá virtual vào trước khai báo hàm.
• Trong ví dụ trên, ta thêm từ khoá virtual vào trước khai 
báo của hàm xuat.
87 of 111
Phương thức ảo
class Nguoi {
protected:
char *HoTen;
int NamSinh;
public:
Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = 
strdup(ht);}
~Nguoi() {delete [] HoTen;}
void An() const { cout << HoTen << " an 3 chen com";}
void Ngu() const { cout << HoTen << " ngu ngay 8 
tieng";}
virtual void Xuat() const { 
cout << "Nguoi, ho ten: " << HoTen 
<< " sinh " << NamSinh; 
}
};
88 of 111
Phương thức ảo
class SinhVien : public Nguoi
{
protected:
char *MaSo;
public:
SinhVien(char *n, char *ms, int ns) : 
Nguoi(n,ns) { MaSo = strdup(ms);}
~SinhVien() {delete [] MaSo;}
void Xuat() const { 
cout << "Sinh vien " << HoTen 
<< ", ma so " << MaSo;
}
};
89 of 111
class NuSinh : public SinhVien
{
public:
NuSinh(char *ht, char *ms, int ns) : 
SinhVien(ht,ms,ns) {}
void An() const { 
cout << HoTen << " ma so " 
<< MaSo << " an 2 to pho";
}
};
90 of 111
Phương thức ảo
class CongNhan : public Nguoi
{
protected:
double MucLuong;
public:
CongNhan(char *n, double ml, int ns) : 
Nguoi(n,ns), MucLuong(ml) { }
void Xuat() const { 
cout << "Cong nhan, ten " 
<< HoTen << " muc luong: " << MucLuong;
}
};
91 of 111
void XuatDs(int n, Nguoi *an[])
{
for (int i = 0; i < n; i++)
{
an[i]->Xuat();
cout << "\n";
}
}
92 of 111
Phương thức ảo
const int N = 4;
void main()
{
Nguoi *a[N];
a[0] = new SinhVien("Vien Van Sinh", "200001234", 1982);
a[1] = new NuSinh("Le Thi Ha Dong", "200001235", 1984);
a[2] = new CongNhan("Tran Nhan Cong", 1000000, 1984);
a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);
XuatDs(4,a);
}
• Phương thức ảo xuat được khai báo ở lớp Nguoi cho 
phép sử dụng con trỏ đến lớp cơ sở (Nguoi) nhưng 
trỏ đến một đối tượng thuộc lớp con (Sinh viên, 
công nhân) gọi đúng thao tác ở lớp con:
93 of 111
Phương thức ảo
• Nguoi *pn;
• pn = new SinhVien("Vien Van Sinh", 
"200001234", 1982);
• pn->Xuat(); // Goi thao tac xuat cua lop Sinh vien
• Con trỏ pn thuộc lớp Nguoi nhưng trỏ đến đối tượng sinh 
viên, vì vậy pn->Xuat() thực hiện thao tác xuất của lớp sinh 
viên.
• Trở lại ví dụ trên, khi i a[i] lần lượt trỏ đến các đối tượng 
thuộc các loại khác nhau, thao tác tương ứng với lớp sẽ được 
gọi.
• Dùng phương thức ảo khắc phục được các nhược điểm của 
cách tiếp cận dùng vùng chọn kiểu:
• Thao tác đơn giản không phải dùng switch/case vì vậy khó
sai, dễ sửa.
94 of 111
Thêm lớp con mới
• Dùng phương thức ảo, ta dễ dàng nâng cấp sửa chữa. 
Việc thêm một loại đối tượng mới rất đơn giản, ta 
không cần phải sửa đổi thao tác xử lý (hàm XuatDs). 
Qui trình thêm chỉ là xây dựng lớp con kế thừa từ lớp cơ 
sở hoặc các lớp con đã có và định nghĩa lại phương thức 
(ảo) ở lớp mới tạo nếu cần
class CaSi : public Nguoi
{
protected:
double CatXe;
public:
CaSi(char *ht, double cx, int ns) : 
Nguoi(ht,ns), CatXe(cx) {}
void Xuat() const { cout << "Ca si, " << HoTen 
<< " co cat xe " << CatXe;}
};
95 of 111
Thêm lớp con mới
void XuatDs(int n, Nguoi *an[])
{
for (int i = 0; i < n; i++)
{
an[i]->Xuat();
cout << "\n";
}
}
• Hàm XuatDs không thay đổi, nhưng nó có thể hoạt động cho 
các loại đối tượng ca sĩ thuộc lớp mới ra đời.
• Có thể xem như thao tác XuatDs được viết trước cho các lớp 
con cháu chưa ra đời.
96 of 111
Các lưu ý khi sử dụng phương thức ảo
• Phương thức ảo chỉ hoạt động thông qua con trỏ.
• Muốn một hàm trở thành phương thức ảo có hai 
cách: Khai báo với từ khoá virtual hoặc hàm 
tương ứng ở lớp cơ sở đã là phương thức ảo.
• Phương thức ảo chỉ hoạt động nếu các hàm ở lớp 
cơ sở và lớp con có nghi thức giao tiếp giống hệt 
nhau.
• Nếu ở lớp con không định nghĩa lại phương thức 
ảo thì sẽ gọi phương thức ở lớp cơ sở (gần nhất 
có định nghĩa).
97 of 111
Cơ chế thực hiện phương thức ảo
• Khi gọi một thao tác, khả năng chọn đúng phiên bản tuỳ
theo đối tượng để thực hiện thông qua con trỏ đến lớp 
cơ sở được gọi là tính đa hình (polymorphisms).
• Cơ chế đa hình được thực hiện nhờ ở mỗi đối tượng có
thêm một bảng phương thức ảo. Bảng này chứa địa chỉ
của các phương thức ảo và nó được trình biên dịch khởi 
tạo một cách ngầm định khi thiết lập đối tượng.
• Khi thao tác được thực hiện thông qua con trỏ, hàm có
địa chỉ trong bảng phương thức ảo sẽ được gọi.
• Trong ví dụ trên, mỗi đối tượng thuộc lớp cơ sở Người 
có bảng phương thức ảo có một phần tử là địa chỉ hàm 
Nguoi::Xuat. Mỗi đối tượng thuộc lớp SinhVien có bảng 
tương tự nhưng nội dung là địa chỉ của hàm 
SinhVien::Xuat.
98 of 111
Cơ chế thực hiện phương thức ảo
• Trong ví dụ 2, mỗi đối tượng thuộc các lớp 
Mamal, Dog, Cat, Horse, Pig đều có bảng 
phương thức ảo với hai phần tử, địa chỉ của 
hàm Speak và của hàm Move.
99 of 111
100 of 111
Phương thức huỷ bỏ ảo
• Trong ví dụ quản lý danh sách các đối tượng 
thuộc các lớp Nguoi, SinhVien, CongNhan, 
 Thao tác dọn dẹp đối tượng là cần thiết.
const int N = 4;
void main()
{
Nguoi *a[N];
a[0] = new SinhVien("Vien Van Sinh", "20001234", 1982);
a[1] = new NuSinh("Le Thi Ha Dong", "20001235", 1984);
a[2] = new CongNhan("Tran Nan Cong", 1000000, 1984);
a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);
XuatDs(4,a);
for (int i = 0; i < 4; i++)
delete a[i];
} 
101 of 111
Phương thức huỷ bỏ ảo
• Thông qua con trỏ thuộc lớp cơ sở Nguoi, chỉ
có phương thức huỷ bỏ của lớp Nguoi được 
gọi.
• Để bảo đảm việc dọn dẹp là đầy đủ, ta dùng 
phương thức huỷ bỏ ảo.
102 of 111
class Nguoi
{
protected:
char *HoTen;
int NamSinh;
public:
Nguoi(char *ht, int ns):NamSinh(ns) {HoTen 
= strdup(ht);}
virtual ~Nguoi() {delete [] HoTen;}
virtual void Xuat(ostream &os) const { os 
<< "Nguoi, ho ten: " << HoTen << " sinh " 
<< NamSinh; }
void Xuat() const { Xuat(cout); }
};
103 of 111
Phương thức thiết lập ảo
• C++ không cung cấp cơ chế thiết lập đối 
tượng có khả năng đa hình theo cơ chế hàm 
thành phần ảo. 
• Tuy nhiên ta có thể “thu xếp” để có thể tạo 
đối tượng theo nghĩa “ảo”. 
• Phương thức thiết lập ảo cũng có thể được 
hiện thực bằng cách dùng hàm thành phần 
tĩnh để tạo đối tượng. 
104 of 111
Phương thức thiết lập ảo
enum FILETYPE {UNKNOWN, BMP, GIF, JPG};
class CGBmp;
typedef CGBmp * CGBmpPtr;
class CGBmp
{
protected:
// ...
public:
virtual ~CGBmp(){Release();}
virtual void Release() = 0;
static CGBmpPtr NewBmp(const String 
&pathName);
virtual void Draw() const;
};
105 of 111
Phương thức thiết lập ảo
class CBmp : public CGBmp
{
//...
public:
CBmp(const String &pathName);
void Release();
void Draw() const;
//...
};
106 of 111
class CGif: public CGBmp
{
//..
public:
CGif(const String &pathName);
void Release();
void Draw() const;
//...
};
107 of 111
Phương thức thiết lập ảo
class CJpg: public CGBmp
{
//..
public:
CJpg(const String &pathName);
void Release();
void Draw() const;
//...
};
108 of 111
FILETYPE GetFileType(const String &pathName)
{
String FileExt = GetFileExt(pathName);
if (FileExt == ".DIB" || FileExt == 
".BMP")
return BMP;
else if (FileExt == ".JPG")
return JPG;
else if (FileExt == ".GIF")
return GIF;
else return UNKNOWN;
}
109 of 113
Phương thức thiết lập ảo
CGBmpPtr CGBmp::NewBmp(const String &pathName)
{
switch (GetFileType(pathName))
{
case BMP:
return new CBmp(pathName);
case GIF:
return new CGif(pathName);
case JPG:
return new CJpg(pathName);
default:
return NULL;
}
}
void main()
{
CGBmpPtr p = CGBmp::NewBmp("Lena.jpg");
p->Draw();
}
110 of 113
Phương thức thuần ảo và lớp cơ sở trừu tượng
• Lớp cơ sở trừu tượng là lớp cơ sở không có đối 
tượng nào thuộc chính nó. Một đối tượng thuộc lớp 
cơ sở trừu tượng
            Các file đính kèm theo tài liệu này:
 oop_tom_tat_bai_giang_1_slides_per_page_2826.pdf oop_tom_tat_bai_giang_1_slides_per_page_2826.pdf