SLIDE1

Saturday, June 20, 2015

[oop c++] phạm vi truy xuất trong kế thừa

Truy xuất theo chiều dọc:
Hàm thành phần của lớp con có quyền truy xuất các thành phần của lớp cha hay không?
Truy xuất theo chiều ngang:
Các thành phần của lớp cha, sau khi kế thừa xuống lớp con, thì thế giới bên ngoài có quyền truy xuất thông qua đối tượng của lớp con hay không?



Truy xuất theo chiều dọc

Lớp con có quyền truy xuất các thành phần của lớp cha hay không, hoàn toàn do lớp cha quyết định. Điều đó được xác định bằng thuộc tính kế thừa.
Trong trường hợp lớp Sinh viên kế thừa lớp Người, Sinh viên có quyền truy xuất họ tên của chính mình (được khai báo ở lớp Người) hay không?

class A{
private:
int a;
void f();
protected:
int b;
void g();
public:
int c;
void h();
};

void A::f()
{
a = 1; b = 2; c = 3;
}
void A::g()
{
a = 4; b = 5; c = 6;
}
void A::h(){
a = 7; b = 8; c = 9;
}

Thuộc tính public:

Thành phần nào có thuộc tính public thì có thể truy xuất từ bất cứ nơi nào.

Thuộc tính private:

 Thành phần có thuộc tính private
Là riêng tư của lớp đó
Chỉ có hàm thành phần của lớp và ngoại lệ các hàm bạn được phép truy xuất.
Các lớp con cũng không có quyền truy xuất

Thuộc tính protected:

Cho phép qui định một vài thành phần nào đó của lớp là bảo mật, theo nghĩa thế giới bên ngoài không được phép truy xuất, nhưng tất cả các lớp con, cháu… đều được phép truy xuất.

Ví dụ Thuộc tính private
class Nguoi {
char *HoTen;
int NamSinh;
public:
//...
};
class SinhVien : public Nguoi {
char *MaSo;
public:
//...
void Xuat() const;
};
Trong ví dụ trên, không có hàm thành phần nào của lớp SinhVien có thể truy xuất các thành phần HoTen, NamSinh của lớp Nguoi.
Ví dụ, đoạn chương trình sau đây sẽ gây ra lỗi:
void SinhVien::Xuat() const {
cout << "Sinh vien, ma so: "<<MaSo<<",ho ten:"<<HoTen;
}

Ta có thể khắc phục lỗi trên nhờ khai báo lớp SinhVien là bạn của lớp Nguoi như trong ví dụ ban đầu:
class Nguoi {
friend class SinhVien;
char *HoTen;
int NamSinh;
public:
//...
};
Khai báo lớp bạn như trên, lớp SinhVien có thể truy xuất các thành phần private của lớp Nguoi.
Cách làm trên chỉ giải quyết được nhu cầu của người sử dụng khi muốn tạo lớp con có quyền truy xuất các thành phần dữ liệu private của lớp cha.
Tuy nhiên, cần phải sửa lại lớp cha và tất cả các lớp ở cấp cao hơn mỗi khi có một lớp con mới.

class Nguoi {
friend class SinhVien;
friend class NuSinh;
char *HoTen; int NamSinh;
public:
//...
void An() const { cout << HoTen << " an 3 chen com";}
};
class SinhVien : public Nguoi {
friend class NuSinh;
char *MaSo;
public:
//...
};

Trong ví dụ trước, khi cài đặt lớp NuSinh ta phải thay đổi lớp cha SinhVien và cả lớp cơ sở Nguoi ở mức cao hơn.
class Nguoi {
protected:
char *HoTen;
int NamSinh;
public:
//...
};
class SinhVien : public Nguoi {
protected:
char *MaSo;
public:
SinhVien(char *ht, char *ms, int ns) : Nguoi(ht,ns){ 
MaSo = strdup(ms);
}
~SinhVien(){
delete [ ] MaSo;
}
void Xuat() const; 
};
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";
}
};
// Co the truy xuat Nguoi::HoTen va
// Nguoi::NamSinh va SinhVien::MaSo

void Nguoi::Xuat() const {
cout << "Nguoi, ho ten: " << HoTen << " sinh " << NamSinh;
}
void SinhVien::Xuat() const {
cout << "Sinh vien, ma so: " << MaSo << ", ho ten: " << HoTen; 
// Ok: co quyen truy xuat, Nguoi::HoTen, Nguoi::NamSinh
}
void SinhVien::Xuat() const {
cout << "Sinh vien, ma so: " << MaSo 
cout << ", ho ten: " << HoTen;
}

Là cách để tránh phải sửa đổi lớp cơ sở khi có lớp con mới hình thành Đảm bảo tính đóng gói.
Thông thường ta dùng thuộc tính protected cho thành phần dữ liệu và public cho thành phần phương thức.
Tóm tại, thành phần có thuộc tính protected chỉ cho phép những lớp con kế thừa được phép sử dụng.

Truy xuất theo chiều ngang

Thành phần protected và public của lớp khi đã kế thừa xuống lớp con thì thế giới bên ngoài có quyền truy xuất thông qua đối tượng thuộc lớp con hay không?
Điều này hoàn toàn do lớp con quyết định bằng phạm vi kế thừa: Kế thừa public, Kế thừa protected, Kế thừa private

Phương thức thiết lập

Phương thức thiết lập của lớp cơ sở luôn luôn được gọi mỗi khi có một đối tượng của lớp dẫn xuất được tạo ra.

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 A {
   public:
A ( )
 {  cout<< “A:default”<<endl;  }
A (int a){
     cout<<“A:parameter”<<endl;
}
};

class A {
   public:
A ( )
 {  cout<< “A:default”<<endl; }
A (int a){
  cout<<“A:parameter”<<endl;
}
};

Định nghĩa các thành phần riêng

Ngoài các thành phần được kế thừa, lớp dẫn xuất có thể định nghĩa thêm các thành phần riêng
class HinhTron : 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);

Lớp dẫn xuất cũng có thể override các phương thức đã được định nghĩa ở trong lớp cha.

Phương thức hủy bỏ

Khi một đối tượng bị hủy đi, phương thức hủy bỏ của nó sẽ được gọi. Sau đó, các phương thức hủy bỏ của 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;}
//...
};

Con trỏ và kế thừa

Con trỏ trong kế thừa hoạt động theo nguyên tắc sau:
Con trỏ 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.
Nhưng con trỏ 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ở.
Có thể ép kiểu để con trỏ 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.