Ch ng 4: K th aươ ế ừ
Phần 2: Ngôn ngữ lập trình C++
Các nội dung chính
Các khái niệm cơ bản
Phân loại kế thừa
Kế thừa đơn
Tính đa hình
Lớp trừu tượng
Kế thừa bội
Các khái niệm cơ bản
Kế thừa (inheritance):
Lớp B kế thừa lớp A, tức là B sẽ có thể tái sử
dụng các thành phần dữ liệu và các thao tác
trong A
A
B
Lớp cha (lớp cơ sở)
Lớp con (lớp dẫn xuất)
Ví dụ về sự kế thừa
Lớp Số
+, -, *, /
<,>,==,…
Giá trị
Lớp Số Thực
+, -, *, /
<,>,==,…
Giá trị thực
Lớp Số Nguyên
+, -, *, /
<,>,==,…
Giá trị nguyên
Lớp Phân Số
+, -, *, /
<,>,==,…
Giá trị mẫu số
Lớp Số Phức
+, -, *, /
<,>,==,…
Giá trị phức
Phân loại kế thừa
Có hai cách phân loại
Theo mức độ kế thừa: có 2 mức
Public: bảo toàn mức độ che dấu của các thành phần của
lớp cơ sở trong lớp dẫn xuất
Private: đưa các mức che dấu của các thành phần trong lớp
cơ sở lên mức private trong lớp dẫn xuất
Theo số lượng lớp cơ sở: có 2 loại
Kế thừa đơn
Kế thừa bội
Cú pháp khai báo kế thừa
Kế thừa đơn
Trong đó E có thể
là:
public
private
Kế thừa bội
class B: <E> A {
//Đ/n phần thân của lớp dẫn xuất
};
class C: <E
A
> A, <E
B
> B {
//Đ/n phần thân của lớp dẫn xuất
};
Kế thừa đơn
Chương trình 4.1: xây dựng hai lớp số Real và
Complex, trong đó lớp Complex kế thừa lớp
Real
class Real {
protected:
float r;
public:
Real(float a=0){
r = a;
}
//tiếp ở hình bên
};
class Real {
//tiếp từ hình bên
Real Add(Real x){
Real z;
z.r = x.r +r;
return z;
}
void Print(){
cout<<r;
}
};
Chương trình 4.1 (tiếp)
class Complex: public Real {
float i; //Phan ao
public:
Complex(float rp=0, float
ip=0):Real(rp){
i=ip;
}
//tiếp ở hình bên
};
class Complex: public Real {
//tiếp từ hình bên
Complex Add(Complex x)
{
Complex a;
a.r = x.r +r;
a.i = x.i +i;
return a;
}
void Print(){
Real::Print();
cout<<"+j"<<i;
}
};
Chương trình 4.1 (tiếp và hết)
main(){
Real x, y(10.5),z(25.5);
x = y.Add(z);
cout<<"x="; x.Print();
cout<<endl;
Complex c, d(5,6), e(6,9);
c = d.Add(e);
cout<<"c="; c.Print();
cout<<endl;
return system("PAUSE"),EXIT_SUCCESS;
}
Kết quả chạy
Tính đa hình và hàm ảo
Khái niệm đa hình:
Ta thấy trong chương trình ở trên, trong hàm main
có các đối tượng của cả lớp Real và Complex. Các
đối tượng này đều có hàm thành viên Print() không
có tham số để in ra nội dung của từng đối tượng. Do
đó, khi hàm main gửi thông báo Print cho các đối
tượng, yêu cầu hiển thị nội dung của chúng thì các
đối tượng đều gọi hàm Print để in nội dung của
chúng ra màn hình. Do các đối tượng có các nội
dung khác nhau nên các nội dung in ra cũng khác
nhau. Đây được gọi là sự đa hình, và hàm thành viên
giống nhau cho nhiều lớp khác nhau được gọi là hàm
đa hình.
Tính đa hình
main()
:a Real
Print()
:a Complex
Print()
Print
Print
Vấn đề với tính đa hình
main(){
Real x(10.5);
cout<<"x="; x.Print(); cout<<endl;
Complex c(6,9);
cout<<"c="; c.Print(); cout<<endl;
Real *pr = &x;
cout<<"x=";pr->Print();cout<<endl;
pr=&c;
cout<<"c=";pr->Print();cout<<endl; //Vấn đề ở đây
return system("PAUSE"),EXIT_SUCCESS;
}
Kết quả chạy
Vấn đề với tính đa hình
Vấn đề nằm ở dòng in ra c = 6, tức là chỉ in ra
phần thực của c, mặc dù con trỏ pr đã trỏ vào c
Nguyên nhân:
Do bên trong mỗi đối tượng thuộc lớp Complex có 2
hàm Print(), một kế thừa từ lớp Real, và một được
định nghĩa lại trong lớp Complex, nên khi gọi pr-
>Print(), hệ thống đã liên kết và gọi đến hàm Print
của lớp Real, vì nó chỉ xét kiểu của con trỏ pr là kiểu
Real, chứ không xét đến bản thân kiểu của đối tượng
mà pr đang trỏ đến. Đây là hình thức liên kết sớm
(early binding), được thực hiện vào lúc dịch chương
trình
Giải pháp cho vấn đề đa hình
Sử dụng hàm ảo
(virtual)
Thêm từ khóa virtual
vào khai báo của hàm
Print trong lớp Real
Khi đó hàm Print trong
lớp Complex cũng là
hàm virtual, mặc dù ta
không cần lặp lại từ
khóa này trong đ/n của
hàm này
class Real {
//tiếp từ hình bên
Real Add(Real x){
Real z;
z.r = x.r +r;
return z;
}
virtual void Print(){
cout<<r;
}
};
Kết quả chạy lại
Hàm ảo và cơ chế liên kết muộn
Cơ chế liên kết muộn (late binding)
Khi gặp hàm ảo, thì hệ thống không thực hiện liên
kết sớm khi dịch chương trình, mà chờ đến khi chạy
chương trình, sau khi xác định rõ kiểu của đối tượng
mà thi hành hàm đó, thì việc liên kết mới được thực
hiện, nhằm tìm đúng loại hàm cần thiết để thực hiện.
Lớp trừu tượng và hàm ảo thuần túy
Hàm ảo thuần túy (pure virtual function):
Là hàm ảo không có phần định nghĩa (không
có phần thân hàm)
Lớp trừu tượng (abstract class):
Là lớp có ít nhất một hàm thành viên là ảo
thuần túy
Ví dụ
//Đ/n lớp trừu tượng
class Number {
//Khai báo hàm ảo thuần túy
public:
virtual void Print()=0;
};
class Integer: public Number {
public:
void Print();
};
class Real: public Number {
public:
void Print();
};
class Integer
Print()
class Real
Print()
Abs class
Number
virtual Print()=0
Lớp trừu tượng
Một số quy tắc:
Lớp trừu tượng không có đối tượng, nó được dùng
để khái quát các thành phần chung nhất của các lớp
Có thể khai báo con trỏ kiểu lớp trừu tượng
Một hàm ảo thuần túy trong lớp trừu tượng cần phải
được định nghĩa lại trong lớp dẫn xuất; hoặc tiếp tục
là hàm ảo trong lớp đó, khi đó lớp dẫn xuất này lại là
trừu tượng.
Kế thừa bội
Bài tập về nhà nghiên cứu
Tóm tắt
Khái niệm cơ bản về kế thừa
Cách phân loại kế thừa
Tính đa hình và cách cài đặt
Sự khác nhau giữa hàm ảo và hàm thông thường
Lớp trừu tượng và hàm ảo thuần túy
Kế thừa bội
Bài tập
Bài 1: Xây dựng cây các lớp số: gồm các lớp Number,
Integer, Fraction, Real, Complex có nút gốc là lớp
Number. Mỗi lớp gồm các thao tác:
Số học: +,-,*,/
So sánh: <,>,==
Phép xuất ra màn hình <<
Bài 2: Xây dựng một cây các lớp hình: gồm các lớp
Shape, Rectangle, Square, Triangle, Quadrangle. Mỗi
lớp gồm các thao tác:
Thank you!