Tải bản đầy đủ (.pdf) (43 trang)

Chương 6: Tính kế thừa docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (71.25 KB, 43 trang )





Chương 6


Tính kế thừa










Giới thiệu tính kế thừa


Điều khiển truy cập lớp cơ sở


Sử dụng các thành viên được bảo vệ


Hàm tạo, hàm hủy và tính kế thừa


Tính đa kế thừa




Lớp cơ sở ảo








Chöông 6 Tính keá thöøa

164









































Chương 6 Tính kế thừa

165
I/ Giới thiệu tính kế thừa
(inheritance)


Tính kế thừa
là cơ chế nhờ đó một lớp có thể kế thừa các đặc điểm của một lớp khác.

Tính kế thừa hổ trợ khái niệm
phân loại theo thứ bậc
(hierachical classification) của
lớp, ngoài ra còn hổ trợ
tính đa hình
(polymorphism).


Lớp cơ sở
(base class) là lớp được kế thừa bởi một lớp khác.
Lớp dẫn xuất
(derive class) là lớp kế thừa từ một lớp cơ sở.

Lớp cơ sở
xác đònh các tính chất mà sẽ trở nên thông dụng cho các lớp dẫn xuất.
Nghiã là lớp cơ sở hiển thò mô tả tổng quát nhất một tập hợp các đặc điểm.

Một lớp dẫn xuất kế thừa các đặc điểm tổng quát này và bổ sung thêm các tính chất
riêng của lớp dẫn xuất.

Cú pháp khai báo cho
lớp dẫn xuất


class
derived_class_name
:


access_specifier
base_class_name
{

// body of class
} ;

base_class_name Tên lớp cơ sở
derived_class_name Tên lớp dẫn xuất
access_specifier
chỉ đònh truy cập bao gồm :
public
,
private

protected



Từ khoá
public
báo cho trình biên dòch biết rằng lớp cơ sở sẽ được kế thừa sao
cho mọi thành viên chung của lớp cơ sở cũng sẽ là các
thành viên chung của lớp
dẫn xuất
. Tuy nhiên, mọi
thành viên riêng
của lớp cơ sở
vẫn còn riêng đối với nó


và không được truy cập trực tiếp bởi lớp dẫn xuất.


Ví dụ 1.1
// A simple example of inheritance.
#include <iostream.h.h>



B


D
Hình 6.1 Sự kế thừa đơn

Chöông 6 Tính keá thöøa

166
// Define base class.
class
base
{
int i;
public:
void set_i(int n);
int get_i();
};

// Define derived class.

class
derived
:
public

base
{
int j;
public:
void set_j(int n);
int mul();
};

// Set value i in base.
void
base::set_i
(int n)
{
i = n;
}

// Return value of i in base.
int
base::get_i
() { return i; }

// Set value of j in derived.
void
derived::set_j
(int n)

{
j = n;
}

// Return value of base's i times derived's j.
int
derived::mul
()
{
// derived class can call base class public member functions
return j * get_i();
}
Chương 6 Tính kế thừa

167
int main()
{
derived ob;

ob.set_i(10); // load i in base
ob.set_j(4); // load j in derived

cout << ob.mul(); // displays 40

return 0;
}


Một


lớp cơ sở là không thuộc riêng về một lớp dẫn xuất.
Lớp cơ sở có thể được kế
thừa bởi nhiều lớp khác
.

Ví dụ 1.2
Lớp cơ sở chung Fruit có 2 lớp dẫn xuất Apple và Orange
// An example of class inheritance.
#include <iostream.h.h>
#include <string.h>

enum yn {no, yes};
enum color {red, yellow, green, orange};

void out(enum yn x);
char *c[] = {"red", "yellow", "green", "orange"};

// Generic fruit class.
class
fruit
{
// in this base, all elements are public
public:
enum yn annual;
enum yn perennial;
enum yn tree;
enum yn tropical;
enum color clr;
char name[40];
};

Chöông 6 Tính keá thöøa

168
// Derive Apple class.
class
Apple
:
public

fruit
{
enum yn cooking;
enum yn crunchy;
enum yn eating;
public:
void seta(char *n, enum color c, enum yn ck, enum yn crchy, enum yn e);
void show();
};

// Derive orange class.
class
Orange
:
public

fruit
{
enum yn juice;
enum yn sour;
enum yn eating;

public:
void seto(char *n, enum color c, enum yn j, enum yn sr, enum yn e);
void show();
};

void
Apple::seta
(char *n, enum color c, enum yn ck, enum yn crchy,
enum yn e)
{
strcpy(name, n);
annual = no;
perennial = yes;
tree = yes;
tropical = no;
clr = c;
cooking = ck;
crunchy = crchy;
eating = e;
}

void
Orange::seto
(char *n, enum color c, enum yn j, enum yn sr, enum yn e)
{
strcpy(name, n);
Chöông 6 Tính keá thöøa

169
annual = no;

perennial = yes;
tree = yes;
tropical = yes;
clr = c;
juice = j;
sour = sr;
eating = e;
}
void
Apple::show
()
{
cout << name << " apple is: " << "\n";
cout << "Annual: "; out(annual);
cout << "Perennial: "; out(perennial);
cout << "Tree: "; out(tree);
cout << "Tropical: "; out(tropical);
cout << "Color: " << c[clr] << "\n";
cout << "Good for cooking: "; out(cooking);
cout << "Crunchy: "; out(crunchy);
cout << "Good for eating: "; out(eating); cout << "\n";
}

void
Orange::show
()
{
cout << name << " orange is: " << "\n";
cout << "Annual: "; out(annual);
cout << "Perennial: "; out(perennial);

cout << "Tree: "; out(tree);
cout << "Tropical: "; out(tropical);
cout << "Color: " << c[clr] << "\n";
cout << "Good for juice: "; out(juice);
cout << "Sour: "; out(sour);
cout << "Good for eating: "; out(eating); cout << "\n";
}



Chương 6 Tính kế thừa

170
void out(enum yn x)
{
if(x==no) cout << "no\n";
else cout << "yes\n";
}

int main()
{
Apple a1, a2;
Orange o1, o2;

a1.seta("Red Delicious", red, no, yes, yes);
a2.seta("Jonathan", red, yes, no, yes);
o1.seto("Navel", orange, no, no, yes);
o2.seto("Valencia", orange, yes, yes, no);

a1.show();

a2.show();

o1.show();
o2.show();
return 0;
}


Bài tập I

1. Cho lớp cơ sở sau
class area_cl {
public:
double height;
double width;
};

hãy tạo 2 lớp dẫn xuất box và isosceles kế thừa lớp area_cl. Với mỗi lớp hãy tạo
hàm area() lần lượt trả về diện tích của một hộp hay một tam giác cân. Dùng các
hàm tạo được tham số hoá để khởi đầu height và width.
Chương 6 Tính kế thừa

171
II/ Điều khiển truy cập lớp cơ sở

Chỉ đònh truy cập (
access_specifier
) xác đònh cách mà các phần tử của lớp cơ sở
được kế thừa bởi lớp dẫn xuất.



Từ khoá
private
chỉ đònh

các thành viên chung lớp cơ sở trở thành các
thành
viên riêng của lớp dẫn xuất,
nhưng những thành viên này vẫn còn được truy cập
bởi các hàm thành viên của lớp dẫn xuất.

Ví dụ 2.1
#include <iostream.h>

class base {
int x;
public:
void setx(int n) { x = n; }
void showx() { cout << x << '\n'; }
};

// Inherit as public.
class
derived
:
public

base
{
int y;

public:
void sety(int n) { y = n; }
void showy() { cout << y << '\n'; }
};

int main()
{
derived ob;

ob.setx(10); // access member of base class
ob.sety(20); // access member of derived class

ob.showx(); // access member of base class
ob.showy(); // access member of derived class

Chương 6 Tính kế thừa

172
return 0;
}


Ví dụ 2.2
Lớp dẫn xuất không thể truy cập đến các thành viên riêng của lớp cơ
sở

class base {
int x;
public:
void setx(int n) { x = n; }

void showx() { cout << x << '\n'; }
};

// Inherit as public - this has an error!
class derived :
public
base {
int y;
public:
void sety(int n) { y = n; }

/* Cannot access private member of base class.
x is a private member of base and not available within derived. */
void show_sum() { cout <<
x
+ y << '\n'; } // Error!
void showy() { cout << y << '\n'; }
};


Ví dụ 2.3
Lớp dẫn xuất kế thừa lớp cơ sở với chỉ đònh private
// This program contains an error.
#include <iostream.h>

class base {
int x;
public:
void setx(int n) { x = n; }
void showx() { cout << x << '\n'; }

};
Chương 6 Tính kế thừa

173
// Inherit base as private.
class derived :
private
base {
int y;
public:
void sety(int n) { y = n; }
void showy() { cout << y << '\n'; }
};

int main()
{
derived ob;

ob.setx(10); // ERROR - now private to derived class
ob.sety(20); // access member of derived class - OK
ob.showx(); // ERROR - now private to derived class
ob.showy(); // access member of derived class - OK

return 0;
}

@ Lưu ý
Trong hàm main vẫn có thể truy cập các thành viên chung cuả lớp cơ
sở
base base_ob;

base_ob.setx(1); // is legal because base_ob is of type base


Ví dụ 2.4
Lớp dẫn xuất kế thừa lớp cơ sở với chỉ đònh private, các thành viên vẫn
còn được
truy cập bên trong lớp dẫn xuất
.
// This program is fixed.
#include <iostream.h>

class base {
int x;
public:
void setx(int n) { x = n; }
void showx() { cout << x << '\n'; }
};
Chöông 6 Tính keá thöøa

174
// Inherit base as private.
class derived :
private
base {
int y;
public:
// setx is accessible from within derived
void setxy(int n, int m) {
setx(n)
; y = m; }

// showx is accessible from within derived
void showxy() {
showx()
; cout << y << '\n'; }
};

int main()
{
derived ob;

ob.setxy(10, 20);
ob.showxy();
return 0;
}




















Chương 6 Tính kế thừa

175
Bài tập II

1. Xét đoạn chương trình sau
#include <iostream.h>

class mybase {
int a, b;
public:
int c;
void setab(int i, int j) { a = i; b = j; }
void getab(int &i, int &j) { i = a; j = b; }
};

class derived1 :
public
mybase {
//
};

class derived2 :
private
mybase {
//
};


int main()
{
derived1 o1;
derived2 o2;
int i, j;

//
}

Trong hàm main() câu lệnh nào sau đây là hợp lệ :
A. o1.getab(i,j) ;
B. o2.getab(i,j) ;
C. o1.c = 10 ;
D. o2.c = 10 ;
2. Điều gì xảy ra khi một thành viên riêng được kế thừa như một thành viên chung ?
Điều gì xảy ra khi nó được kế thừa như một thành viên riêng ?
Chương 6 Tính kế thừa

176
III/ Sử dụng các thành viên được bảo vệ
(protected members)


Với các chỉ đònh truy cập
public

private
, một lớp dẫn xuất không truy cập được
các thành viên riêng của lớp cơ sở. Tuy nhiên, sẽ có những lúc muốn một thành viên

của lớp cơ sở vẫn là riêng nhưng vẫn cho phép lớp dẫn xuất truy cập tới nó.


Chỉ đònh truy cập
protected
, tương đương với chỉ đònh
private
với một ngoại lệ
duy nhất là các
thành viên được bảo vệ
(protected members
)
của lớp cơ sở có thể
được truy cập đối với các thành viên của một lớp dẫn xuất từ lớp cơ sở đó.

Bên ngoài lớp cơ sở hoặc lớp dẫn xuất, các
thành viên được bảo vệ
không thể
được truy cập.

Dạng tổng quát của khai báo lớp :

class
class_name
{

// private members
protected :
// protected members
public :

// public members
};

Khi một
thành viên được bảo vệ
của một lớp cơ sở được kế thừa bởi lớp dẫn xuất với
chỉ đònh
public
, nó trở thành
thành viên được bảo vệ của lớp dẫn xuất
.

Nếu lớp dẫn xuất được kế thừa với chỉ đònh
private
, thì một thành viên được bảo vệ
của lớp cơ sở trở thành
thành viên riêng của lớp dẫn xuất
.

Lớp cơ sở có thể được kế thừa với chỉ đònh
protected
bởi lớp dẫn xuất, khi đó các
thành viên chung và được bảo vệ của lớp cơ sở trở thành các thành viên được bảo vệ
của lớp dẫn xuất.

Còn các thành viên riêng của lớp cơ sở vẫn còn riêng đối với lớp cơ sở và không
được truy cập bởi lớp dẫn xuất.

Chương 6 Tính kế thừa


177
Ví dụ 3.1
Truy cập các thành viên chung, riêng và được bảo vệ của lớp cơ sở
#include <iostream.h>

class samp {
// private by default
int a;
protected: // still private relative to samp
int b;
public:
int c;

samp(int n, int m) { a = n; b = m; }
int geta() { return a; }
int getb() { return b; }
};

int main()
{
samp ob(10, 20);

// ob.b = 99; Error! b is protected and thus private
ob.c = 30; // OK, c is public

cout << ob.geta() << ' ';
cout << ob.getb() << ' ' << ob.c << '\n';

return 0;
}



Ví dụ 3.2
Các
thành viên được bảo vệ
được kế thừa như thành viên chung
#include <iostream.h>

class
base
{
protected :
// private to base
int a, b; // but still accessible by derived
public:
Chương 6 Tính kế thừa

178
void setab(int n, int m) { a = n; b = m; }
};

class
derived
:
public

base
{
int c;
public:

void setc(int n) { c = n; }

// this function has access to a and b from base
void showabc() {
cout << a << ' ' << b << ' ' << c << '\n';
}
};

int main()
{
derived ob;

// a and b are not accessible here because they are private to both base and
derived.
ob.setab(1, 2);
ob.setc(3);

ob.showabc();
return 0;
}


Ví dụ 3.3
Các
thành viên được bảo vệ
được kế thừa bởi chỉ đònh
protected

// This program will not compile.
#include <iostream.h>


class base {
protected :
// private to base
int a, b; // but still accessible by derived
public:

Chöông 6 Tính keá thöøa

179
void setab(int n, int m)
{ a = n;
b = m;
}
};


class
derived
:
protected

base
{ // inherit as protected
int c;
public:
void setc(int n) { c = n; }

// this function has access to a and b from base
void showabc() {

cout << a << ' ' << b << ' ' << c << '\n';
}
};

int main()
{
derived ob;

// ERROR: setab() is now a protected member of base.
ob.setab(1, 2); // setab() is not accessible here.
ob.setc(3);
ob.showabc();

return 0;
}








Chương 6 Tính kế thừa

180
Bài tập III

1. Lập bảng tổng kết về quyền truy cập của lớp dẫn xuất với sự kế thừa đơn


Thuộc tính thành viên
trong lớp cơ sở
Chỉ đònh truy cập của lớp
dẫn xuất
Quyền truy cập của lớp
dẫn xuất
Public Public
Private
Protected
?
?
?
Private Public
Private
Protected
?
?
?
Protected Public
Private
Protected
?
?
?
2. Trong bài tập II/ 1. chương 6, nếu a và b bên trong lớp mybase được thực hiện trở
thành những thành viên được bảo vệ thay vì các thành viên riêng (theo mặc đònh) thì
kết quả thế nào ? Tại sao ?




















Chương 6 Tính kế thừa

181
IV/ Hàm tạo, hàm hủy và tính kế thừa
Lớp cơ sở, lớp dẫn xuất hoặc cả hai có thể có các hàm tạo và/hoặc hàm hủy.


Khi cả lớp cơ sở lẫn lớp dẫn xuất có các hàm tạo và hàm hủy, các hàm tạo được
thi hành theo thứ tự dẫn xuất. Cáùc hàm hủy được thi hành theo thứ tự ngược lại.

Cú pháp truyền đối số từ lớp dẫn xuất đến lớp cơ sở
:

derived_constructor(arg_list) : base(arg_list) {

// body of derived class constructor
}


Ví dụ 4.1
#include <iostream.h>

class base {
public:
base() { cout << "Constructing base class \n"; }
~base() { cout << "Destructing base class \n"; }
};

class derived :
public
base {
public:
derived() { cout << "Constructing derived class \n"; }
~derived() { cout << "Destructing derived class \n"; }
};

int main()
{
derived o;

return 0;
}


Giải thích kết quả của chương trình ?

Chương 6 Tính kế thừa

182
Constructing base class
Constructing derived class
Destructing derived class
Destructing base class


Ví dụ 4.2
Truyền một đối số cho hàm tạo của lớp dẫn xuất
#include <iostream.h>

class base {
public:

base()
{ cout << "Constructing base class\n"; }
~base() { cout << "Destructing base class\n"; }
};

class derived :
public
base {
int j;
public:

derived(int n)
{
cout << "Constructing derived class\n";

j = n;
}
~derived() { cout << "Destructing derived class\n"; }
void showj() { cout << j << '\n'; }
};

int main()
{
derived o(10); // truyền theo cách thông thường

o.showj();
return 0;
}

Kết quả của chương trình ?

Ví dụ 4.3
Hàm tạo của lớp dẫn xuất lẫn của lớp cơ sở nhận một đối số,
Chương 6 Tính kế thừa

183
cả hai sử dụng đối số giống nhau

#include <iostream.h>

class base {
int i;
public:

base(int n)

{
cout << "Constructing base class\n";
i = n;
}
~base() { cout << "Destructing base class\n"; }
void showi() { cout << i << '\n'; }
};

class derived :
public
base {
int j;
public:

derived(int n) : base(n)
{ // pass arg to base class
cout << "Constructing derived class\n";
j = n;
}
~derived() { cout << "Destructing derived class\n"; }
void showj() { cout << j << '\n'; }
};

int main()
{
derived o(10);

o.showi();
o.showj();


return 0;
}

Kết quả của chương trình ?
Chương 6 Tính kế thừa

184


Trong hầu hết các trườnghợp, các hàm tạo đối với lớp cơ sở và lớp dẫn xuất
sẽ
không dùng đối số giống nhau
.

Nếu truyền một hay nhiều đối số cho mỗi lớp, cần phải truyền cho hàm tạo của lớp
dẫn xuất
tất cả các đối số
mà cả hai lớp dẫn xuất và lớp cơ sở cần đến. Sau đó lớp
dẫn xuất chỉ truyền cho lớp cơ sở những đối số nào mà lớp cơ sở cần đến.

Ví dụ 4.4

#include <iostream.h>

class base {
int i;
public:

base(int n)
{

cout << "Constructing base class\n";
i = n;
}
~base() { cout << "Destructing base class\n"; }
void showi() { cout << i << '\n'; }
};

class derived :
public
base {
int j;
public:

derived(int n, int m) : base(m)
{ // pass arg to base class
cout << "Constructing derived class\n";
j = n;
}
~derived() { cout << "Destructing derived class\n"; }
void showj() { cout << j << '\n'; }
};

int main()
{
derived o(10, 20);

Chương 6 Tính kế thừa

185
o.showi();

o.showj();

return 0;
}

Kết quả của chương trình ?



Đối với hàm tạo của lớp dẫn xuất không cần phải nhận một số để truyền cho
lớp cơ sở. Nếu lớp dẫn xuất không cần đối số, nó bỏ qua đối số và chỉ truyền
cho lớp cơ sở.

Ví dụ 4.5

class base {
int i;
public:

base(int n)
{
cout << "Constructing base class\n";
i = n;
}
~base() { cout << "Destructing base class\n"; }
void showi() { cout << i << '\n'; }
};

class derived :
public

base {
int j;
public:

derived(int n) : base(n)
{ // pass arg to base class
cout << "Constructing derived class\n";
j = 0; // n not used here
}
~derived() { cout << "Destructing derived class\n"; }
void showj() { cout << j << '\n'; }
};
Bài tập IV
Chương 6 Tính kế thừa

186

1. Cho đoạn chương trình sau, hãy bổ sung hàm tạo cho lớp myderived. Cho
myderived truyền cho lớp mybase một con trỏ về một chuỗi khởi đầu. Hãy cho hàm
tạo myderived() khởi đầu len với độ dài chuổi.

#include <iostream.h>
#include <string.h>

class mybase {
char str[80];
public:
mybase(char *s) { strcpy(str, s); }
char *get() { return str; }
};


class myderived : public mybase {
int len;
public:
// add myderived() here
int getlen() { return len; }
void show() { cout << get() << '\n'; }
};

int main()
{
myderived ob("hello");

ob.show();
cout << ob.getlen() << '\n';

return 0;
}

2. Lập các hàm tạo car() và truck() trong đoạn chương trình sau. Mỗi hàm truyền các
đối số thích hợp cho lớp vehicle. Ngoài ra, cho car() khởi đầu passengers và truck()
khởi đầu loadlimit như được chỉ rõ khi đối tượng được tạo ra. Khai báo các đối tượng
:
Chöông 6 Tính keá thöøa

187
car ob(passengers, wheels, range) ;
truck ob(loadlimit, wheels, range) ;
#include <iostream.h>


// A base class for various types of vehicles.
class vehicle {
int num_wheels;
int range;
public:
vehicle(int w, int r)
{
num_wheels = w; range = r;
}
void showv()
{
cout << "Wheels: " << num_wheels << '\n';
cout << "Range: " << range << '\n';
}
};

class car : public vehicle {
int passengers;
public:
// insert car() constructor here
void show()
{
showv();
cout << "Passengers: " << passengers << '\n';
}
};

class truck : public vehicle {
int loadlimit;
public:

// insert truck() constructor here
void show()
{

×