SLIDE1

Saturday, June 20, 2015

[oop c++] Kế thừa - định nghĩa, cú pháp, sử dụng

Kế thừa

 là một đặc điểm của ngôn ngữ dùng để biểu diễn mối quan hệ đặc biệt hóa – tổng quát hóa giữa các lớp. Các lớp được trừu tượng hóa và được tổ chức thành một sơ đồ phân cấp lớp.
Sự kế thừa là một mức cao hơn của trừu tượng hóa, cung cấp một cơ chế gom chung các lớp có liên quan với nhau thành một mức khái quát hóa đặc trưng cho toàn bộ các lớp nói trên.



Các lớp với các đặc điểm tương tự nhau có thể được tổ chức thành một sơ đồ phân cấp kế thừa (cây kế thừa).
Quan hệ “là 1”: Kế thừa được sử dụng thông dụng nhất để biểu diễn quan hệ “là 1”.
Một sinh viên là một người
Một hình tròn là một hình ellipse
Một tam giác là một đa giác

Lợi ích kế thừa

Kế thừa cho phép xây dựng lớp mới từ lớp đã có.
Kế thừa cho phép tổ chức các lớp chia sẻ mã chương trình chung, nhờ vậy có thể dễ dàng sửa chữa, nâng cấp hệ thống.
Trong C++, kế thừa còn định nghĩa sự tương thích, nhờ đó ta có cơ chế chuyển kiểu tự động.

Đặc tính Kế thừa

Cho phép định nghĩa lớp mới từ lớp đã có.
Lớp mới gọi là lớp con (subclass) hay lớp dẫn xuất (derived class)
Lớp đã có gọi là lớp cha (superclass) hay lớp cơ sở (base class).
Thừa kế cho phép:
Nhiều lớp có thể dẫn xuất từ một lớp cơ sở
Một lớp có thể là dẫn xuất của nhiều lớp cơ sở
Thừa kế không chỉ giới hạn ở một mức: Một lớp dẫn xuất có thể là lớp cơ sở cho các lớp dẫn xuất khác

Cú pháp khai báo kế thừa

class SuperClass{
//Thành phần của lớp cơ sở
};

class DerivedClass : public/protected/private SusperClass{
//Thành phần bổ sung của lớp dẫn xuất
};

Kế thừa đơn

Xét hai khái niệm Người và Sinh viên với mối quan hệ tự nhiên: Một Sinh viên là một Người. Trong C++, ta có thể biểu diễn khái niệm trên, một sinh viên là một người có thêm một số thông tin và một số thao tác (riêng biệt của sinh viên).
Như vậy, ta tổ chức lớp Sinh viên kế thừa từ lớp Người.

Ta có thể tổ chức hai lớp Nam sinh và Nữ sinh là hai lớp con (lớp dẫn xuất) của lớp Sinh viên. Trường hợp này, lớp Sinh viên trở thành lớp cha (lớp cơ sở) của hai lớp trên.

Kế thừa đơn – Ví dụ

class Nguoi {
char *HoTen;
int NamSinh;
public:
Nguoi();
Nguoi( char *ht, int ns):NamSinh(ns) {HoTen=strdup(ht);}
~Nguoi() {delete [ ] HoTen;}
void An() const { cout<<HoTen<<" an 3 chen com \n";}
void Ngu() const { cout<<HoTen<< " ngu ngay 8 tieng \n";}
void Xuat() const;
friend ostream& operator << (ostream &os, Nguoi& p);
};
class SinhVien : public Nguoi {
char *MaSo;
public:
SinhVien();
SinhVien( char *ht, char *ms, int ns) : Nguoi(ht,ns) {
MaSo = strdup(ms);
}
~SinhVien() {
delete [ ] MaSo;
}
void Xuat() const;
};
void Nguoi::Xuat() const 
{
cout << "Nguoi, ho ten: " << HoTen;
cout << " sinh " << NamSinh;
cout << endl;
}
void SinhVien::Xuat() const {
cout << "Sinh vien, ma so: " << MaSo;
//cout << ", ho ten: " << HoTen;
//cout << ", nam sinh: " << NamSinh;
cout << endl;
}
void main() {
Nguoi p1("Le Van Nhan",1980);
SinhVien s1("Vo Vien Sinh", "200002541",1984);
cout << "1.\n";
p1.An(); s1.An();
cout << "2.\n";
p1.Xuat(); s1.Xuat();
s1.Nguoi::Xuat();
cout << "3.\n";
cout << p1 << "\n";
cout << s1 << "\n";
}


Kế thừa đặc tính của lớp cha

Khai báo
 class SinhVien : public Nguoi {
//...
 };
Cho biết lớp Sinh viên kế thừa từ lớp Người. Khi đó Sinh viên thừa hưởng các đặc tính của lớp Người.
Về mặt dữ liệu: Mỗi đối tượng Sinh viên tự động có thành phần dữ liệu họ tên, năm sinh của người.

Về mặt thao tác: Lớp Sinh viên được tự động kế thừa các thao tác của lớp cha. Đây chính là khả năng sử dụng lại mã chương trình.
Riêng phương thức thiết lập không được kế thừa.
Khả năng thừa hưởng các thao tác của lớp cơ sở có thể được truyền qua “vô hạn mức”.

Định nghĩa lại thao tác ở lớp con

Ta có thể định nghĩa lại các đặc tính ở lớp con đã có ở lớp cha, việc định nghĩa chủ yếu là thao tác.

class SinhVien : public Nguoi {
char *MaSo;
public:
//...
void Xuat() const;
};
void SinhVien::Xuat() const {
cout << "Sinh vien, ma so: " << MaSo << ", ho ten: " << HoTen;
}

Ràng buộc ngữ nghĩa ở lớp con

Có thể áp dụng quan hệ kế thừa mang ý nghĩa ràng buộc, đối tượng ở lớp con là đối tượng ở lớp cha nhưng có dữ liệu bị ràng buộc:
Hình tròn là Ellipse với ràng buộc bán kính ngang dọc bằng nhau.
Số ảo là số phức với ràng buộc phần ảo bằng 0

Lớp số ảo sau đây là một ví dụ minh họa.
Ví dụ
class Complex {
friend ostream& operator <<(ostream&, Complex);
friend class Imag;
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);}
};
class Imag: public Complex {
public:
Imag(double i = 0):Complex(0, i){ }
Imag(const Complex &c) : Complex(0, c.im){ }
Imag& operator = (const Complex &c){
re = 0; im = c.im;
return *this;
}
double Norm() const {
return fabs(im);
}
};
void main() 
{
Imag i = 1;
Complex z1(1,1)
Complex z3 = z1 - i; // z3 = (1,0)
i = Complex(5,2); // i la so ao (0,2)
Imag j = z1; // j la so ao (0,1)
cout << "z1 = " << z1 << "\n";
cout << "i = " << i << "\n";
cout << "j = " << j << "\n";
}

Trong ví dụ trên, lớp số ảo (Imag) kế thừa hầu hết các thao tác của lớp số phức (Complex).
Tuy nhiên, ta muốn ràng buộc mọi đối tượng thuộc lớp số ảo đều phải có phần thực bằng 0. Vì vậy, phải định nghĩa lại các hàm thành phần có thể vi phạm điều này.
Ví dụ phép toán gán phải được định nghĩa lại để đảm bảo ràng buộc này.