4. BM MTT&TT 4
Giới thiệu
Một số khái niệm
Thừa kế đơn
Hàm xây dựng
Hàm xây dựng sao chép
Hàm hủy
Tái định nghĩa...
Liên kết tĩnh và liên kết động
Đa thừa kế
Nội dung bài giảng
5. BM MTT&TT 5
Thừa kế
Là một trong những đặc tính mạnh nhất của OOP.
Là quá trình tạo ra các lớp mới từ những lớp đã có.
Về mặt ngữ nghĩa, lớp mới phải “là” lớp đã có với một vài
đặc điểm hoặc tính năng mới.
Mục đích
Xây dựng lớp mới từ những lớp đã có.
Dùng lại mã (code reusability)
Sử dụng sự tương tự và sự khác nhau để mô hình hóa các
lớp đối tượng có liên quan với nhau.
Là một cách để tổ chức thông tin.
Giới thiệu
6. BM MTT&TT 6
Xây dựng lớp mới từ lớp đã có
void Thisinh2008::hienthi()
{
Thisinh::hienthi();
cout << "Noi thi: ";
cout << noithi;
}
void Thisinh2008::nhap()
{
Thisinh::nhap();
cout << "Nhap noi thi: ";
cin >> noithi;
}
Tận dụng lại các đoạn
mã đã viết sẵn
7. BM MTT&TT 7
Các loại hình dạng đều có một số thuộc tính, phương thức
giống nhau. Các loài bò sát, động vật có vú, cá, … đều là
động vật, đều cùng một loài.
Sự tương tự và sự khác nhau
8. BM MTT&TT 8
Lớp cha và lớp con
Lớp được thừa kế: Lớp cha (parent class), lớp cơ sở (base
class).
Lớp thừa kế: Lớp con (child class), lớp dẫn xuất (derived
class).
Các loại thừa kế
Thừa kế đơn: Lớp con thừa kế từ một lớp cha.
Đa thừa kế: Lớp con thừa kế từ nhiều lớp cha.
Một số khái niệm
9. BM MTT&TT 9
Một số đặc điểm
Quan hệ giữa lớp con và lớp cha là quan hệ “là”.
Lớp con, mặc nhiên sẽ có tất cả các thành phần của lớp
cha. Tuy nhiên, nó chỉ truy xuất được các thành phần
public hoặc protected.
Lớp con được tạo ra bằng cách
Thêm vào các thuộc tính mới.
Thêm vào các phương thức mới.
Thể hiện hóa một số thuộc tính của lớp cha.
Đổi kiểu dữ liệu của thuộc tính của lớp cha.
Tái định nghĩa, chồng các phương thức của lớp cha.
Một số khái niệm
10. BM MTT&TT 10
Lớp con chỉ thừa kế từ một lớp cha.
Cú pháp:
Dạng thừa kế: dùng để chỉ định các thành phần của lớp
cha sẽ xuất hiện trong lớp con với thuộc tính truy xuất gì.
Thừa kế đơn
class <tên lớp con> : [dạng thừa kế] <tên lớp cha> {
//các thành ph n (thu c tính/ph ng th c) c a l p conầ ộ ươ ứ ủ ớ
...
};
Dạng t.kế
Lớp cha
public protected
private
(mặc nhiên)
public public protected private
protected protected protected private
private private private private
11. BM MTT&TT 11
Dạng thừa kế
Thừa kế đơn
class A {
int x;
void Fx ();
public:
int y;
void Fy ();
protected:
int z;
void Fz ();
};
class B : A { // Thừa kế dạng private
…….
};
class C : private A { // A là lớp cơ sở riêng của
C
………
};
class D : public A { // A là lớp cơ sở chung
của D
………
};
class E : protected A { // A: lớp cơ sở được
bảo vệ
……….
};
12. BM MTT&TT 12
Chú ý: L p con th a k (có) t t c các thành ph n c a l p cha nh ng ch ớ ừ ế ấ ả ầ ủ ớ ư ỉ
đ c truy xu t các thành ph n ượ ấ ầ public ho c ặ protected
class Diem {
int x, y;
public:
void ganDiem(int h, int t) {
x = h; y = t;
}
void hienthi() {
cout << "(" << x
<< ", "<< y << ")";
}
void doiDiem(int dx, int dy) {
x += dx; y += dy;
}
...
};
class Diemmau : public Diem {
int mau; //b sung thêm thu c tínhổ ộ
“màu”
public:
void ganDM(int h, int t, int m) {
ganDiem(h, t); mau = m;
}
void hienthiDM() {
hienthi();
cout << ", mau: " << mau;
}
};
void main() {
Diemmau a;
a.ganDM(0, 0, 7);
a.hienthiDM();
cout << endl;
a.doiDiem(2, 1);
a.hienthiDM();
}
a
x, y, mau
ganDiem()
hienthi()
doiDiem()
ganDM()
hienthiDM()
Thừa kế đơn
13. Thứ tự khởi tạo: Thành phần lớp cha => Thành phần lớp
con => Hàm xây dựng của lớp cha sẽ được gọi trước,
sau đó là hàm xây dựng của lớp con.
Khi gọi hàm xây dựng: phải gọi hàm xây dựng của lớp
cha trước khi khởi tạo các dữ liệu của lớp con.
Nếu hàm XD của lớp con không gọi hàm XD lớp cha một
cách tường minh thì hàm xây dựng không đối số sẽ được
gọi.
Thừa kế đơn - Hàm xây dựng
14. Ví dụ
Thừa kế đơn - Hàm xây dựng
int main()
{
B objB;
return 0;
}
class A {
public:
A() {
cout << "A's constructorn";
}
};
class B : public A {
public:
B() {
cout << "B's constructorn";
}
};
15. Cú pháp gọi hàm xây dựng của lớp cha:
Thừa kế đơn - Hàm xây dựng
<phần khai báo hàm XD lớp con> : <tên lớp cha>([DS đối số])
{
//thân hàm XD c a l p conủ ớ
}
Tùy vào danh sách đối số mà hàm
xây dựng tương ứng của lớp cha
sẽ được gọi
class X {
int a;
public:
//X’s constructors
X() { a = 0; }
X(int aa) { a = aa; }
//. . .
};
class Y : public X {
int b;
public:
//Y’s constructors
Y() : X() { b = 0; }
Y(int aa, int bb) : X(aa) {
b = bb;
}
//. . .
};
Gọi hàm XD của lớp cha
16. Thừa kế đơn - Hàm xây dựng
mau:
void main()
{
Diemmau a;
Diemmau b(1, 1, 3);
}
x:
y:
0
0
0
a
//th a k t l p Diemừ ế ừ ớ
Diem()
mau = 0;
mau:
x:
y:
1
1
3
b
//th a k t l p Diemừ ế ừ ớ
Diem(1, 1)
mau = 3;
class Diem {
int x, y;
public:
Diem() { x = y = 0; }
Diem(int h, int t) {
x = h; y = t;
}
};
class Diemmau : public Diem {
int mau;
public:
Diemmau() : Diem() { mau = 0; }
Diemmau(int h, int t, int m)
:Diem(h, t) {
mau = m;
}
}; Gọi hàm XD của lớp cha
17. Hàm xây dựng sao chép
Bên trong mỗi đối tượng của lớp con, luôn có các thành
phần dữ liệu của lớp cha trong hàm XDSC của lớp con
phải gọi hàm XDSC của lớp cha để đảm bảo các dữ liệu
này cũng được sao chép.
Cú pháp gọi hàm XDSC của lớp cha
Thừa kế đơn - Hàm XD sao chép
<tên lớp con>(const <tên lớp con>& obj) : <tên lớp cha>(obj)
{
//thân hàm XDSC c a l p conủ ớ
}
Prototype hàm XDSC lớp con Gọi hàm XDSC lớp cha
Các thành phần dữ liệu của lớp cha bên trong ĐT obj
sẽ được sử dụng để sao chép cho các t.phần dữ liệu
của lớp cha của ĐT mới.
18. 18
//constructors
Emp::Emp () : Person() {
dept = new char[30];
strcpy(dept, "");
salary = 0.0;
}
Emp::Emp (char* n, char* d,
float s) : Person(n) {
dept = strdup(d);
salary = s;
}
//copy constructor
Emp::Emp(const Emp& e)
:Person(e) {
dept = strdup(e.dept);
salary = e.salary;
}
//Constructors
Person::Person() {
name = new char[50];
strcpy(name, "");
}
Person::Person(char* n) {
name = strdup(name);
}
//copy constructor
Person::Person(const Person& p)
{
name = strdup(p.name);
} Gọi hàm XDSC cho lớp cha
Thừa kế đơn - Hàm XD sao chép
19. Thuộc tính của lớp con trùng tên thuộc tính của lớp cha.
Trong lớp con, nếu ta truy xuất đến dữ liệu trùng tên đó thì
sẽ truy xuất đến dữ liệu của lớp con.
Truy xuất dữ liệu lớp cha : <Tên lớp cha>::<dữ liệu>
Thừa kế đơn - Tái ĐN dữ liệu TV
class LopCha {
public:
int a;
LopCha()
{ a= 0; }
};
class LopCon : public LopCha {
public:
int a;
LopCon();
void Hien();
};
LopCon::LopCon() : LopCha()
{ a = 1; }
void LopCon::Hien()
{ cout << a << LopCha::a; }
void main() {
LopCon x;
x.Hien();
cout << x.a
<< x.LopCha::a;
}
20. Hàm thành viên của lớp con trùng tên với hàm thành viên
của lớp cha.
Gọi hàm trùng tên => gọi hàm của lớp con.
Gọi hàm của lớp cha : <Tên lớp cha>::<tên hàm>(…)
Thừa kế đơn - Tái ĐN dữ liệu TV
class Diem {
int x, y;
public:
...
void Hien()
{ cout<<“(”<<x<<“,”<<y<<“)”;}
};
class DiemMau : public Diem {
int mau;
public:
void Hien();
...
};
void DiemMau :: Hien()
{ Diem::Hien();
cout<<“ mau ”<<mau;
}
void main() {
DiemMau a(2,5,4);
a.Hien();
a.DiemMau::Hien();
DiemMau b=a;
b.Diem::Hien();
}
21. Cũng tương tự như hàm XDSC, hàm tái định nghĩa toán tử
gán ‘=’ của lớp con cũng phải gọi đến hàm tái định nghĩa
tác tử ở lớp cha để thực hiện sự sao chép cho các thành
phần của lớp cha.
Thừa kế đơn - Tái ĐN phép gán
//assignment operator overloading
Person& Person::operator= (const Person& p) {
delete[] name;
name = strdup(p.name);
return *this;
}
Emp& Emp::operator= (const Emp& e) {
Person::operator=(e);
delete[] dept;
dept = strdup(e.dept);
salary = e.salary;
}
Gọi hàm tái ĐN phép gán
cho lớp cha
22. Hủy đối tượng, hàm hủy:
Khi một đối tượng thuộc lớp con bị hủy, hàm hủy của lớp
cha sẽ tự động được gọi (nếu có), ngay cả trong trường
hợp lớp con không có hàm hủy.
Hàm hủy của lớp con (nếu có) không cần gọi đến
hàm hủy của lớp cha.
Thứ tự: hàm hủy lớp con => hàm hủy lớp cha.
Thừa kế đơn - Hàm hủy
class Person {
char* name;
public:
//. . .
~Person() {delete[] name; }
//. . .
};
class Emp {
char* dept;
float salary;
public:
//. . .
~Emp() {delete[] dept; }
//. . .
};
23. Thừa kế đơn - Hàm hủy
class X {
int* x_data; int x_size;
public:
//...constructors + member functions
//destructor
~X() {
cout << "X's destructorn";
delete[] x_data;
}
};
class X : public Y {
int* x_data; int x_size;
public:
//...constructors + member functions
//destructor
~Y() {
cout << “Y's destructorn";
delete[] y_data;
}
};
int main()
{
Y yObj;
return 0;
}
Trường hợp Y không có hàm hủy
24. Sự tương thích giữa đối tượng của lớp con và đối
tượng của lớp cha
Liên kết tĩnh và liên kết động
•
Một đối tượng thuộc lớp
con có thể được gán cho
một đối tượng thuộc lớp
cha.
•
Một con trỏ thuộc lớp
cha có thể trỏ đến một
đối tượng thuộc lớp con.
•
Không có chiều ngược
lại.
24
void main() {
LopCha a;
LopCon b;
a = b;
b = a; // Sai
LopCha *pa;
pa = &a;
LopCon *pb;
pb = &b;
pa = &b;
pb = &a; // Sai
}
Con trỏ của lớp cha
có thể trỏ đến
đối tượng
của lớp con
Có thể gán Cha = Con
Hổn
25. Liên kết tĩnh
Sự liên kết giữa con trỏ đối tượng và hàm thành viên
được thực hiện lúc biên dịch => Con trỏ thuộc lớp
nào sẽ gọi hàm thành viên của lớp đó.
Liên kết tĩnh và liên kết động
class LopCha {
...
public:
…
void HamThanhVien() {
cout<<“Ham cua Lop Cha”;
}
};
class LopCon : public LopCha {
...
public:
…
void HamThanhVien() {
cout<<“Ham cua Lop Con”;
}
};
void main() {
LopCha a;
LopCha *pa=&a;
pa->HamThanhVien(); //Lớp cha
LopCon b;
LopCon *pb=&b;
pb->HamThanhVien(); //Lớp con
pa= &b;
pa->HamThanhVien(); //Lớp cha
pb= &a; //Báo lỗi
}
27. Liên kết động
Sự liên kết giữa con trỏ đối
tượng và hàm thành viên
được thực hiện lúc thực thi
chương trình, con trỏ đang
trỏ tới đối tượng thuộc lớp
nào thì hàm thành viên của
lớp đó được gọi.
Chỉ thực hiện được khi hàm
thành viên được khai báo là
hàm ảo (virtual function)
trong lớp cha.
Thể hiện cho tính đa hình của
lập trình Hướng đối tượng:
cùng một thông điệp nhưng
có thể được ứng xử khác
nhau tùy vào từng “hoàn
cảnh” cụ thể.
Liên kết tĩnh và liên kết động
class LopCha {
...
public:
…
virtual void HamAo() {
cout<<“Ham cua Lop Cha”;
}
};
class LopCon : public LopCha {
...
public:
…
void HamAo() {
cout<<“Ham cua Lop Con”;
}
};
void main() {
LopCha a;
LopCha *pa=&a;
pa->HamAo(); //Lớp cha
LopCon b;
LopCon *pb=&b;
pb->HamAo(); //Lớp con
pa= &b;
pa->HamAo(); //Hàm lớp con
pb= &a; //Báo lỗi
}
28. Hàm ảo
Cú pháp: khai báo thêm từ khóa virtual trước hàm.
Dùng chung với tính liên kết động => tính đa hình
Hàm hoàn toàn ảo
Là hàm ảo chỉ có khai báo, không có định nghĩa
Cú pháp: chỉ khai báo tên trong lớp cha và gán=0.
Hàm hoàn toàn ảo phải định nghĩa lại trong lớp con.
Liên kết tĩnh và liên kết động
class Base {
public:
virtual void draw() { ... }
};
class Base {
public:
virtual void draw() = 0;
};
class Base {
public:
virtual void draw() = 0;
};
29. Liên kết tĩnh và liên kết động
class Base {
public:
void virtual draw() { ... }
};
class Derived : public Base {
public:
void draw() { . . .}
};
Base bObj, *bp;
Derived dObj;
bp = &bObj;
bp->draw(); //Goi ham draw() cua lop Base
bp = &dObj;
bp->draw(); //Goi ham draw() cua lop Derived
bObj
draw()
dObj
draw()
Base
draw()
Base* bp
30. Liên kết tĩnh và liên kết động
class Shape {
//. . .
public:
virtual void draw() = 0;
};
class Circle : public Shape {
//. . .
public:
void draw() {
cout << "Circle's draw()n";
}
};
class Triangle : public Shape {
//. . .
public:
void draw() {
cout << "Triangle's draw(n)";
}
};
class Rectangle : public Shape {
//. . .
public:
void draw() {
cout << "Rectangle's draw()n";
}
};
void main() {
Shape* shapeArr[4];
shapeArr[0] = new Circle();
shapeArr[1] = new Triangle();
shapeArr[2] = new Circle();
shapeArr[3] = new Rectangle();
for (int i=0; i<4; i++)
shapeArr[i]->draw();
}
31. Hàm hủy
Hàm hủy của lớp cơ sở nên được khai báo là hàm ảo.
Lý do: Sử dụng tính liên kết động để hủy đối tượng.
Lớp ảo
Là lớp có chứa ít nhất một hàm ảo.
Không thể tạo đối tượng thuộc lớp ảo.
Tuy nhiên, có thể tạo con trỏ thuộc lớp ảo.
Ví dụ: Lớp Shape trong VD trước là lớp ảo.
Liên kết tĩnh và liên kết động
32. Lớp con thừa kế từ nhiều hơn một lớp cha.
Cú pháp:
Đa thừa kế
class <tên lớp con> : {[dạng thừa kế] <tên lớp cha>} {,… n}
{
//các thành ph n (thu c tính/ph ng th c) c a l p conầ ộ ươ ứ ủ ớ
...
};
class Z : public X, public Y
{
//...
};
class X {
//...
};
class Y {
//...
};
33. Ưu điểm
Tận dụng được những thành phần đã có sẵn của lớp cha:
Dữ liệu thành viên
Hàm thành viên
Thuận lợi khi sử dụng kết hợp với hàm ảo.
Vấn đề cần lưu ý khi sử dụng thừa kế bội
Cạnh tranh trong thừa kế bội.
Thiết kế sơ đồ thừa kế phải đúng ý nghĩa.
Nên hạn chế và cẩn thận khi sử dụng thừa kế bội.
Đa thừa kế
SinhViên
SinhViênTạiChức
– mã số
NhânViên
– mã số
Dữ liệu của SinhViên
Dữ liệu của NhânViên
Dữ liệu của SinhViênTạiChức
Tạo ra
đối
tượng
34. class XeDien {
int loaibinhdien;
int thoigian;
float tocdo;
public:
XeDien() { … }
XeDien(int b, int tg, float td) { … }
…
};
class XeDapDien : public XeDap, public XeDien {
…
public:
XeDapDien();
XeDapDien( char*, char*, int , int , int , float );
…
};
XeDapDien::XeDapDien () : XeDap(), XeDien() { … }
XeDapDien::XeDapDien ( char* a, char* b, int c, int d, int e, float f )
: XeDap(a, b, c), XeDien(d,e,f) { … }
…
class XeDap {
char loai[10];
char* mau;
int chieucao;
public:
XeDap() { … }
XeDap(char* l, char* m, int c) { … }
…
};
Khai báo lớp trong
thừa kế bội
Gọi hàm xây dựng
của các lớp cha
Đa thừa kế
36. Thứ tự khởi tạo: giống như trong Thừa kế đơn, các thành
phần dữ liệu của lớp cha sẽ được khởi tạo trước, sau đó
tới lớp con.
Hàm XD/XDSC của lớp con cần gọi đến hàm XD/XDSC thích
hợp ở lớp cha.
Cú pháp: tương tự trong Thừa kế đơn.
Đa thừa kế - Khởi tạo
class X {
// data
public:
//methods
};
class Y {
// data
public:
//methods
};
class Z : public X, private Y {
//data
public:
//constructor
Z(...) : X(...), Y(...) {
//...
}
//copy constructor
Z(const Z& obj) : X(obj), Y(obj) {
//...
}
};
37. Trong Đa thừa kế, ta thường gặp phải một số vấn đề
Sự trùng lặp dữ liệu của lớp cơ sở chung trong lớp kế thừa.
Xung đột tên của các hàm thành viên hoặc dữ liệu thành
viên giữa các lớp cơ sở.
Đa thừa kế - Một số vấn đề
SaleManger
SalePerson Manager
EmployeeEmployee
38. Cách giải quyết các vấn đề trên
Sự trùng lặp dữ liệu của lớp cơ sở chung: Dùng lớp cơ sở
ảo.
Đa thừa kế - Một số vấn đề
class A {
// . . .
};
class X : public A {
// . . .
};
class Y : public A {
// . . .
};
class X : virtual public A {
// . . .
};
class Y : virtual public A {
// . . .
};
class Z : public X, public Y {
//. . .
};
Z
X Y
AA
Z
X Y
A
(Dùng chung bởi X và Y)
39. Cách giải quyết các vấn đề trên
Dùng tên lớp cơ sở một cách tường minh trong truy xuất
các thành phần trùng tên trong lớp cơ sở.
Đa thừa kế - Một số vấn đề
class X {
public:
void print()
{ cout << "X's print()"; }
};
class Y : virtual public X {
public:
void print()
{ cout << "Y's print"; }
};
class T : virtual public X {
public:
void print()
{ cout << "T's print"; }
};
class U : public Y, public T {
public:
void print() {
cout << "U's print";
}
};
int main()
{
U u;
u.print(); // U's print invoked
u.X::print(); // X's print invoked
u.Y::print(); // Y's print invoked
u.T::print(); // T's print invoked
return 0;
}