c++學習總結(五)——繼承
一、心得體會
之前的程式碼在定義類時,如果需要多次用到某一個類,都需要反覆定義使用它,但繼承的學習卻彌補這一缺點。整合式面向物件程式設計中軟重用的關鍵技術。繼承機制使用已經定義的類作為基礎建立新的類定義,新的類時原有類的資料及操作與新類所增加的資料及操作的組合。新的類把原有類作為基類引用,而不需要修改原有類的定義。新定義的類作為派生類引用。這種課擴充、可重用技術大大降低了大型軟體的開發難度。
二、內容及例項
8.1類之間的關係
繼承:在已有類的繼承上建立新類的過程
一個B類繼承A類,或稱從類A派生類B。類A稱為基類(父類),類B稱為派生類(子類)
8.2基類和派生類
C++中,描述類繼承關係的語句格式為:
class 派生類名:基類名錶
{
資料成員和成員函式說明
};
基類名錶 構成
訪問控制 基類名1,訪問控制 基類名2,...,訪問控制 基類名n
訪問控制表示派生類對基類的繼承方式,使用關鍵字:
public 公有繼承
private 私有繼承
protected 保護繼承
8.2.1訪問控制
不論哪種方式繼承基類,派生類都不能直接使用基類的私有成員
1.派生類的生成過程
(1)吸收基類成員
在c++的繼承機制中,派生類吸收基類中除解構函式和解構函式之外的全部成員。
(2)改造基類成員
通過在派生類中定義同名成員(包括成員函式和資料成員)來遮蔽(隱藏)在派生類中不起作用的部分基類成員。
(3)新增新成員
僅僅繼承基類的成員是不夠的,需要在派生類中新增新成員,以保證派生類自身特殊屬性和行為的實現。
例:
#include<iostream>
using namespace std;
class A
{
public:
int a;
int b;
private:
int c;
protected:
int d;
};
class B:public A
{
int c;
};
int main()
{
cout<<"size of A is"<<sizeof(A);
cout<<"size of B is"<<sizeof(B);
}
1.公有繼承
以公有方式繼承的派生類,基類的public和protected成員在派生類中的性質不變。即派生類中可以使用基類中定義的public和protected成員;並且,基類的公有成員也是派生類物件的藉口,可以在類模組之外被訪問。
2.私有繼承
以私有方式繼承的派生類,基類的public和protected成員會成為派生類的私有成員,即基類中定義的public和protected成員只能在私有繼承的派生類中可見,而不能再類外使用。
3.保護繼承
保護把基類的公有成員和保護成員作為派生類的保護成員,使其在派生類中被遮蔽。保護繼承和私有繼承方式在程式設計中應用較少。原因是,繼承的目的是軟體重用,如果有需要遮蔽的成員,通常在類中被定義為私有的或保護的成員。
4.訪問宣告
C++提供一種訪問調節機制,是一些本來在派生類中不可見的成員變為可訪問的,稱為訪問宣告。
訪問宣告的格式為:
基類名::成員
8.2.2 重名成員
C++允許派生類的成員與基類成員重名。在派生類中訪問重名成員是,遮蔽基類的同名成員。如果要在派生類中使用基類的同名成員,可以顯式地使用作用域符指定,格式如下:
類名::成員
1.重名資料成員
如果在派生類中定義了與基類相同的名字的資料成員,根據繼承規則,在建立派生類物件時,系統會分別建立不同的儲存空間。
例:
class base
{
public :
int a , b ;
} ;
class derived : public base
{
public :
int b , c ;
} ;
void f ()
{ derived d ;
d.a = 1 ;
d.base :: b = 2 ;
d.b = 3 ;
d.c = 4 ;
};
2.重名成員函式
在派生類中定義與基類同名的成員函式,稱為在派生類中過載基類的成員函式。由呼叫形式指示this指標的不同型別,呼叫不同版本的成員函式。
8.2.3 派生類中訪問靜態成員
如果在基類中定義了靜態成員,這些靜態成員將在類體系中被共享,根據靜態成員自身的訪問特性和派生類的繼承方式,在類層次體系中具有不同的訪問性質。
1.基類定義的靜態成員,將被所有派生類共享(基類和派生類共享基類中靜態成員)
2.根據靜態成員自身的方法特性和派生類的繼承方式,在類層次體系中具有不同的訪問性質
3.派生類中訪問靜態成員,用以下形式顯式說明:
類名::成員
或通過物件訪問 物件名.成員
例:
#include<iostream>
using namespace std;
class B
{
public:
static void Add() { i++ ; }
static int i;
void out() { cout<<"static i="<<i<<endl; }
};
int B::i=0;
class D : private B
{
public:
void f()
{ i=5;
Add();
B::i++;
B::Add();
}
};
int main()
{ B x;
D y;
x.Add();
x.out();
y.f();
cout<<"static i="<<B::i<<endl;
cout<<"static i="<<x.i<<endl;
//cout<<"static i="<<y.i<<endl;
}
8.3 基類的初始化
1.在建立派生類物件時用指定引數呼叫基類的建構函式來初始化派生類繼承基類的資料
2.派生類建構函式宣告為
派生類建構函式(變元素):基類(變元素),物件成員(變元素)
...物件成員n(變元素);
3.建構函式執行順序:基類 物件成員 派生類
例:
#include<iostream>
using namespace std ;
class parent_class
{ int data1 , data2 ;
public :
parent_class ( int p1 , int p2 ) { data1 = p1; data2 = p2; }
int inc1 () { return ++ data1; }
int inc2 () { return ++ data2 ; }
void display () {cout << "data1=" << data1 << " , data2=" << data2 << endl ; }
};
class derived_class : private parent_class
{ int data3 ;
parent_class data4 ;
public:
derived_class ( int p1 , int p2 , int p3 , int p4 , int p5 ): parent_class ( p1 , p2 ) , data4 ( p3 , p4 )
{ data3 = p5 ; }
int inc1 ( )
{
return parent_class :: inc1 ( ) ;
}
int inc3 ( )
{
return ++ data3 ;
}
void display ( )
{
parent_class :: display ( ) ; data4.display ( ) ;
cout << "data3=" << data3 << endl ;
}
} ;
int main ( )
{
derived_class d1 ( 17 , 18 , 1 , 2 , -5 ) ;
d1 . inc1 ( ) ;
d1 . display ( ) ;
}
4.派生類建構函式和解構函式的定義規則
派生類建構函式和解構函式的使用原則
1.基類的建構函式和解構函式不能被繼承
2.如果基類沒有定義建構函式或有無參的建構函式,派生類也可以不用定義建構函式
3.如果派生類無無參的建構函式,派生類必須定義建構函式
4.如果派生類的基類也是派生類,則每個派生類只負責直接基類的構造
5.派生類是否定義解構函式與所屬的基類無關
5.派生類建構函式的定義
派生類的資料成員既包括基類的資料成員,也包括派生類新增資料成員。
在C++中,派生類建構函式的一般格式為:
派生類::派生類名(引數總表):基類名(引數表)
{
// 派生類新增成員的初始化語句
}
注意:這是基類有建構函式且含有引數時使用
6.派生類解構函式
(1)當派生類中不含物件成員時
在建立派生類物件時,建構函式的執行順序是:基類的建構函式→派生類的建構函式;
在撤消派生類物件時,解構函式的執行順序是:派生類的解構函式→基類的解構函式。
(2)當派生類中含有物件成員時
在定義派生類物件時,建構函式的執行順序:基類的建構函式→物件成員的建構函式→派生類的建構函式;
在撤消派生類物件時,解構函式的執行順序:派生類的解構函式→物件成員的解構函式→基類的解構函式。
例:
class B
{
public:
B() { cout<<"B()"<<endl; }
~B() { cout<<"~B()"<<endl; }
};
class D : public B
{
public:
D(){ cout<<"D()"<<endl; }
~D() { cout<<"~D()"<<endl; }
};
int main()
{
D d;
return 0;
}
8.4 繼承的應用例項
例 考察一個點、圓、圓柱體的層次結構 。首先定義點類Point,然後從Point類派生圓類Circle,最後從Circle類派生圓柱體類Cylinder。
class Point
{ friend ostream &operator<< (ostream &, const Point &);
public:
Point( int = 0, int = 0 ) ; // 帶預設引數的建構函式
void setPoint( int, int ) ; // 對點座標資料賦值
int getX() const { return x ; }
int getY() const { return y ; }
protected:
int x, y; // Point類的資料成員
};
class Circle : public Point
{ friend ostream &operator<< (ostream &, const Circle &); // 友元函式
public:
Circle(double r=0.0, int x=0, int y=0); // 建構函式
void setRadius(double); /*置半徑*/
double getRadius() const; /*返回半徑*/
double area() const; // 返回面積
protected: double radius; // 資料成員,半徑
};
class Cylinder:public Circle
{ friend ostream & operator<<(ostream &, const Cylinder &); // 友元函式
public:
Cylinder(double h=0.0, double r=0.0, int x=0, int y=0); // 建構函式
void setHeight(double); /* 置高度值*/
double getHeight() const; /* 返回高度值*/
double area() const; /* 返回面積*/
double volume() const; /* 返回體積*/
protected:
double height; // 資料成員,高度
};
// Point 類的成員函式
// 建構函式,呼叫成員函式對 x,y作初始化
Point::Point ( int a, int b )
{ setPoint ( a , b ) ; }
// 對資料成員置值
void Point :: setPoint ( int a, int b )
{
x = a ;
y = b ;
}// 過載插入算符,輸出物件資料
ostream &operator<< ( ostream &output , const Point &p )
{
output << '[' << p.x << "," << p.y << "]" ;
return output ;
}
// Circle 類的成員函式
// 帶初始化式建構函式,首先呼叫基類建構函式
Circle::Circle( double r, int a, int b ): Point( a, b )
{
setRadius ( r );
}// 對半徑置值
void Circle::setRadius ( double r )
{
radius = ( r >= 0 ? r : 0 );
}// 返回半徑值
double Circle::getRadius() const { return radius; }
// 計算並返回面積值
double Circle::area() const { return 3.14159 * radius * radius ; }
// 輸出圓心座標和半徑值
ostream & operator<< ( ostream &output, const Circle &c)
{
output << "Center = " << '[' << c.x << "," << c.y << "]" << "; Radius = "<< setiosflags(ios::fixed|ios::showpoint) << setprecision(2) << c.radius ;
return output ;
}
Cylinder::Cylinder(double h, double r, int x, int y):Circle(r,x,y)
{
setHeight(h);
}
void Cylinder::setHeight(double h)
{
height = ( h >= 0 ? h : 0 );
}
double Cylinder::getHeight() const { return height; }
double Cylinder::area()const{ return 2*Circle::area()+2*3.14159*radius*height; }
double Cylinder::volume() const { return Circle::area()*height; }
ostream &operator<< ( ostream &output, const Cylinder &cy )
{
output << "Center = " << '[' << cy.x << "," << cy.y << "]" << "; Radius = "
<< setiosflags(ios::fixed|ios::showpoint) << setprecision(2) << cy.radius
<< "; Height = " << cy.height << endl ;
return output;
}
8.5 多繼承
1.一個類有多個直接基類的繼承關係稱為多繼承
2.多繼承宣告語法
class 派生類名 : 訪問控制 基類名1 , 訪問控制 基類名2 , … , 訪問控制 基類名n
{
資料成員和成員函式宣告
};
3.類 C 可以根據訪問控制同時繼承類 A 和類 B 的成員,並新增自己的成員
8.5.1多繼承的派生類構造和訪問
(1).多個基類的派生類建構函式可以用初始式呼叫基類建構函式初始化資料成員。
(2).執行順序與單繼承建構函式情況類似。多個直接基類建構函式執行順序取決於定義派生類時指定的各個繼承基類的順序。
(3).一個派生類物件擁有多個直接或間接基類的成員。不同名成員訪問不會出現二義性。如果不同的基類有同名成員,派生類物件訪問時應該加以識別。
1.多繼承的簡單應用
class Base1
{ public:
Base1(int x) { value = x ; }
int getData() const { return value ; }
protected:
int value;
};
class Base2
{ public:
Base2(char c) { letter=c; }
char getData() const { return letter;}
protected:
char letter;
};
class Derived : public Base1, public Base2
{ friend ostream &operator<< ( ostream &, const Derived & ) ;
public :
Derived ( int, char, double ) ;
double getReal() const ;
private :
double real ;
};
int main()
{ Base1 b1 ( 10 ) ;
Base2 b2 ( 'k' ) ;
Derived d ( 5, 'A', 2.5 ) ;
:
return ;
}
2.多繼承的建構函式
派生類名(引數總表):基類名1(引數表1),基類名2(引數表2),…,基類名n(引數表n)
{
// 派生類新增成員的初始化語句
}
3.多繼承方式下建構函式的執行順序:
先執行所有基類的建構函式
再執行物件成員的建構函式
最後執行派生類的建構函式
處於同一層次的各基類建構函式的執行順序取決於定義派生類時所指定的基類順序與派生類建構函式中所定義的成員初始化列表順序沒有關係。內嵌物件成員的建構函式執行順序與物件在派生類中宣告的順序一致。
4.多繼承的解構函式
●解構函式名同樣與類名相同,無返回值、無引數,而且其定義方式與基類中的解構函式的定義方式完全相同。
●功能是在派生類中對新增的有關成員進行必要的清理工作。
●解構函式的執行順序與多繼承方式下建構函式的執行順序完全相反,首先對派生類新增的資料成員進行清理,再對派生類物件成員進行清理,最後才對基類繼承來的成員進行清理。
5.賦值相容規則
●賦值相容規則指在程式中需要使用基類物件的任何地方,都可以用公有派生類的物件來替代。
賦值相容規則中所指的替代包括以下的情況:
a 派生類的物件可以賦給基類物件
b 派生類的物件可以初始化基類的引用
c 派生類的物件的地址可以賦給基類型別的指標
6.賦值相容的可行性
(1)通過公有繼承,派生類得到了除了構造、解構函式以外的所有成員且這些成員的訪問控制屬性也和基類完全相同。這樣,它便具備了基類的所有功能。
(2)利用賦值相容規則
a 派生類的物件可以賦給基類物件(強制型別轉換)
b 派生類的物件可以初始化基類的引用
c 派生類的物件的地址可以賦給基類型別的指標
例如,下面宣告的兩個類:
class Base{
…
};
class Derived:public Base{
…
};
根據賦值相容規則, 以下幾種情況是合法的:
(1) 可以用派生類物件給基類物件賦值。例如:
Base b;
Derived d;
b=d;
這樣賦值的效果是,物件b中所有資料成員都將具有物件d中對應資料成員的值。
(2) 可以用派生類物件來初始化基類的引用。例如:
Derived d;
Base &br=d;
(3) 可以把派生類物件的地址賦值給指向基類的指標。例如:
Derived d;
Base *bptr=&d;
這種形式的轉換,是在實際應用程式中最常見到的。
(4) 可以把指向派生類物件的指標賦值給指向基類物件的指標。例如:
Derived *dptr,obj; dptr=&obj;
Base *bptr=dptr;
7.賦值相容規則的特點
在替代之後,派生類物件就可以作為基類的物件使用,但只能使用從基類繼承的成員。
一個派生類物件也是一個基類物件,一個基類物件可派上用場的地方,派生類物件一樣可派上用場。反之則不然。
class Person {…};
class Student : public Person { … };
void eat(Person &p){};
void study(Student &s){};
void main( )
{
Person p;
Student s;
eat(p); // OK
eat(s); // OK 學生也要吃飯
study(s); // OK
study(p); // error C2664: “study”: 不能將引數 1 從“Person”轉換Student &”
}
8.賦值相容應注意的問題
(1)宣告為指向基類的指標可以指向它的公有派生類的物件,但不允許指向它的私有派生類的物件。例如:
class B {…};
class D:private B {…};
B b1,*pbl;D d1;
pb1=&b1; //合法,基類B的物件b1和B類的指標
pb1=&d1; //非法,不允許將基類指標指向它的私有派生類物件
(2)允許將一個宣告為指向基類的指標指向其公有派生類物件,但是不能將一個宣告為指向派生類物件的指標指向其基類的一個物件。
(3) 宣告為指向基類物件的指標,當其指向公有派生類物件時,只能用它來直接訪問派生類中從基類繼承來的成員,而不能直接訪問公有派生類的定義的成員。