C++程式設計(六)—— 繼承和派生
一、繼承和派生的基本概念
從一個或多個以前定義的類(基類)產生新類的過程稱為派生,這個新類稱為派生類。基類和派生類又可以分別叫做父類和子類。派生的新類可以增加或重新定義資料和操作,這就產生了類的層次性。
類的繼承是指派生類繼承基類的資料成員和成員函式。繼承通常用來表示類屬關係,不能將繼承理解為構成關係。當從現有類中派生出新類時,派生類可以有以下幾種變化:
① 增加新的成員;
② 重新定義已有的成員函式;
③ 改變基類成員的訪問許可權。
C++中有兩種繼承:單一繼承和多重繼承。
二、單一繼承
1、派生類的建構函式和解構函式
先看一個簡單示例:
#include <iostream> using std::cout; class Point { private: int x, y; public: Point(int a, int b) { x = a; y = b; cout << "正在初始化Point類的物件" << "\n"; } ~Point() { cout << "銷燬Point類的物件" << "\n"; } void showXY() { cout << "x=" << x << ",y=" << y << "\n"; } }; class Rectangle: public Point {//派生類繼承基類並使用訪問控制 private: int width, height; public: Rectangle(int a, int b, int c, int d) :Point(a, b) {//定義派生類的建構函式 width = c; height = d; cout << "正在初始化Rectangle類" << "\n"; } ~Rectangle() { cout << "銷燬Rectangle類的物件" << "\n"; } void showWH() { cout << "W=" << width << ",H=" << height << "\n"; } };
#include "Example1.h"
void example1();
int main() {
example1();
return 0;
}
void example1() {
Rectangle r(1, 2, 3, 4);
r.showWH(); //呼叫派生類的成員函式
r.showXY(); //呼叫基類的成員函式
// 正在初始化Point類的物件
// 正在初始化Rectangle類
// W=3,H=4
// x=1,y=2
// 銷燬Rectangle類的物件
// 銷燬Point類的物件
}
派生類的宣告要在類名後新增一個冒號,冒號後面是訪問控制關鍵字(public、protected、private),之後是基類名。 由於建構函式是不被繼承的,所以一個派生類只能呼叫它的直接基類的建構函式,對基類成員進行初始化,然後執行派生類的建構函式。如果一個基類依是一個派生類,則這具過程遞迴進行,當該物件消失時,解構函式的執行順序和執行建構函式時的順序正好相反。
2、類的保護成員
關鍵字protected之後宣告的是類的保護成員,保護成員具有私有成員和公有成員的雙重角色,對派生類的成員函式而言,它是公有成員可以被訪問;對其他函式而言則仍是私有成員,不能被訪問。示例如下:
#include<iostream>
using std::cout;
class Point2{
protected:
int x,y;
public:
Point2(int a,int b){
x = a;
y = b;
}
void show(){
cout << "x=" << x << ",y=" << y << "\n";
}
};
class Rectangle2: public Point2{
protected:
int width,height;
public:
Rectangle2(int a,int b,int c,int d):Point2(a,b){
width = c;
height = d;
}
void show(){
cout << "x=" << x << ",y=" << y << ",width=" << width << ",height=" << height << "\n";
}
};
#include "Example2.h"
void example2();
int main() {
example2();
return 0;
}
void example2(){
Point2 a(1,2);
Rectangle2 b(10,20,30,40);
a.show();
b.show();
// x=1,y=2
// x=10,y=20,width=30,height=40
}
上例中,派生類雖然繼承了基類的成員函式show(),但它改造了這個函式,使函式能顯示所有資料,這並不會影響基類函式原來的功能。
3、訪問許可權和賦值相容規則
⑴ 公有派生和賦值相容規則
在公共派生情況下,基類成員的訪問許可權在派生類中保持不變。所謂相容規則是指在公有派生情況下,一個派生類的物件可以作為基類的物件來使用的情況。示例如下:
#include <iostream>
#include <string>
using std::cout;
using std::string;
class Dog{
protected:
int age;
public:
Dog(int a){
age = a;
}
void show(){
cout << "狗狗" << age << "歲了" << "\n";
}
};
class WhiteDog : public Dog{
private:
string color;
public:
WhiteDog(int a,string b):Dog(a){
color = b;
}
void show(){
cout << color << "狗狗" << age << "歲了" << "\n";
}
};
#include "Example3.h"
void example3();
int main() {
example3();
return 0;
}
void example3() {
WhiteDog wd(1, "白色");
Dog d(2);
wd.show();//白色狗狗1歲了
d.show();//狗狗2歲了
//派生類的物件初始化基類的引用
Dog &d1 = wd;
d1.show();//狗狗1歲了,實際呼叫的是基類的show函式
//派生類物件的地址賦給指向基類的指標
Dog *d2 = &wd;
d2->show();//狗狗1歲了,實際呼叫的是基類的show函式
WhiteDog *wd1 = &wd;
wd1->show();//白色狗狗1歲了,呼叫的是派生類的show函式
//派生類的物件賦給基類的物件
d = wd;
d.show();//狗狗1歲了,派生類物件的屬性值更新基類物件的屬性值
}
注意:靜態成員可以被繼承,這時基類物件和派生類物件共享該靜態成員。
⑵ is-a和has-a的區別
前者屬於繼承和派生問題,後者是一個類使用另一個類的問題,即把一個類的物件作為自己的資料成員或者成員函式的引數。
⑶ 私有派生
通過私有派生,基類的私有和不可訪問的成員在派生類中是不可訪問的,而公有和保護成員這時就成了派生類的私有成員,派生類的物件不能訪問繼承的基類成員,必須定義公有的成員函式作為介面。更重要的是,雖然派生類的成員函式可通過自定義的函式訪問基類的成員,但將該派生類作為基類再進行派生時,這時即使使用公有派生,原基類公有成員在新的派生類中也是不可訪問的。示例如下:
#include<iostream>
using std::cout;
class Point3{
protected:
int x,y;
public:
Point3(int a,int b){
x = a;
y = b;
}
void show(){
cout << "x=" << x << ",y=" << y << "\n";
}
};
class Rectangle3: private Point3{
private:
int width,height;
public:
Rectangle3(int a,int b,int c,int d):Point3(a,b){
width = c;
height = d;
}
void show(){
cout << "x=" << x << ",y=" << y << ",width=" << width << ",height=" << height << "\n";
}
};
class Test3:public Rectangle3{
public:
Test3(int a,int b,int c,int d):Rectangle3(a,b,c,d){
}
void show(){
//Point3::show();//私有派生時,不能使用此方式呼叫該函式
Rectangle3::show();
}
};
#include "Example4.h"
void example4();
int main() {
example4();
return 0;
}
void example4(){
//例項化一個Test3類的物件
Test3 t(1,2,3,4);
t.show();//x=1,y=2,width=3,height=4
}
⑷ 保護派生
派生也可以使用protected,這種派生使原來的許可權都降一級使用,即private變為不可訪問,protected變為private,public變為protected。
三、多重繼承
繼承的規則是一樣的,只不過繼承的時候需要用逗號分隔。示例如下:
#include<iostream>
using std::cout;
class A{
private:
int a;
public:
void setA(int i){
a = i;
}
void showA(){
cout << "a=" << a << "\n";
}
};
class B{
private:
int b;
public:
void setB(int t){
b = t;
}
void showB(){
cout << "b=" << b << "\n";
}
};
class C:public A,private B{//多繼承使用逗號分隔
private:
int c;
public:
void setC(int x,int y){
c = x ;
setB(y);
}
void showC(){
cout << "c=" << c << "\n";
showB();
}
};
#include "Example5.h"
void example5();
int main() {
example5();
return 0;
}
void example5(){
C c;
c.setA(10);
c.showA();//a=10
c.setC(20,30);
c.showC();//c=20 b=30
}
四、二義性及其支配規則
對基類成員的訪問必須是無二義性的,如果使用一個表示式的含義能解釋為訪問多個基類中的成員,則這種對基類成員的訪問就是不確定的,稱這種訪問具有二義性。
1、作用域分辨符和成員名限定
從類中派生其他類可能導致幾個類使用同一個成員函式名或資料成員名時,程式必須確切的告訴編譯器使用哪個版本的資料成員或成員函式。示例如下:
#include <iostream>
using std::cout;
class A1{
public:
void show(){
cout << "a1.show" << "\n";
}
};
class B1{
public:
void show(){
cout << "b1.show" << "\n";
}
void display(){
cout << "b1.display" << "\n";
}
};
class C1:public A1,public B1{
public:
void display(){
cout << "c1.display" << "\n";
}
void reveal(){
//show();//具有二義性
}
void reveal1(){
A1::show();//使用基類的函式
}
void reveal2(){
B1::show();//使用基類的函式
}
};
#include "Example7.h"
void example7();
int main() {
example7();
return 0;
}
void example7(){
C1 obj;
obj.A1::show();//a1.show
obj.B1::show();//b1.show
obj.B1::display();//b1.display
obj.C1::display();//c1.display
obj.display();//c1.display
obj.reveal1();//a1.show
obj.reveal2();//b1.show
}
由上例可知,如果基類中的名字在派生類中再次宣告,則派生類中的名字就隱藏了基類中的相應名字,C++可以使用作用域分辨運算子“::”來存取那些被隱藏的名字,這一過程叫做作用域分辨。
2、派生類支配基類的同名函式
基類的成員和派生類新增的成員都具有類作用域,基類在外層,派生類在內層。如果這時派生類定義了一個和基類成員函式同名的新成員函式(同名的非過載函式),派生類的新成員函式就覆蓋了外層的同名成員函式。在這種情況下,直接使用成員名只能訪問派生類的成員函式,只有使用作用域分辨,才能訪問基類的同名成員函式。
由於二義性原因,一個類不能從同一個類中直接繼承一次以上,如果必須要這樣做,可以使用一箇中間類,二義性檢查是在訪問許可權檢查之前進行的,因此成員的訪問許可權不能解決二義性問題。如果涉及幾層繼承關係,對於任一基類中可以存取的成員,都可以通過作用域分辨進行存取。一般只有派生類中使用的識別符號與基類中的識別符號同名時,才有必要使用作用域分辨符進行存取。
五、友元與派生
友元宣告與訪問控制無關,友元宣告在私有區域進行或在公有區域進行是沒有太大區別的,對友元函式的唯一限制是該函式必須出現在類宣告內的某一部分。
友元關係是無等級的,友元可以訪問任何一個類成員函式可以訪問的物件,這比一個派生類可以訪問的物件還多。友元關係不能繼承,一個派生類的友元只能訪問該派生類的直接基類的公有和保護成員,不能訪問私有成員。當友元訪問直接基類的靜態保護成員時,只能使用物件名而不能使用成員名限定。