1.基礎知識
1.1 類之間的關系
has-A,uses-A 和 is-A
has-A
包含關系,用以描述一個類由多個“部件類”構成。實現has-A關系用類成員表示,即一個類中的數據成員是另一種已經定義的類。
常和構造函數初始化列表一起使用
uses-A
一個類部分地使用另一個類。通過類之間成員函數的相互聯系,定義友元函數友元類或對象參數傳遞實現。
is-A
機制稱為繼承。關系具有傳遞性,不具有對稱性。
1.2 繼承概念
萬事萬物都有繼承
1.3 繼承示例代碼
注意:C++中的繼承方式(public、private、protected)會影響子類的對外訪問屬性。
示例代碼:
#include <iostream>
using namespace std;class Parent
{
public:void print(){a = 0;b = 0;cout<<"a"<<a<<endl;cout<<"b"<<b<<endl;}int b;
protected:
private:int a;
};//class Child : private Parent
//class Child : protected Parent public
class Child : public Parent
{
public:
protected:
private:int c;
};void main11()
{Child c1;//c1.c = 1;//err//c1.a = 2;//errc1.b = 3;c1.print();cout<<"hello..."<<endl;system("pause");return ;
}
1.4 重要說明
1、子類擁有父類的所有成員變量和成員函數
2、子類就是一種特殊的父類
3、子類對象可以當作父類對象使用
4、子類可以擁有父類沒有的方法和屬性
2.派生類的訪問控制
派生類繼承了基類的全部成員變量和成員方法(除了構造和析構之外的成員方法),但是這些成員的訪問屬性,在派生過程中是可以調整的。
2.1 單個類的訪問控制
1、類成員訪問級別(public、private、protected)
2、思考:類成員的訪問級別只有public和private是否足夠?
示例代碼:
#include <iostream>
using namespace std;//public 修飾的成員變量 方法 在類的內部 類的外部都能使用
//protected: 修飾的成員變量方法,在類的內部使用 ,在繼承的子類中可用 ;其他 類的外部不能被使用
//private: 修飾的成員變量方法 只能在類的內部使用 不能在類的外部//派生類訪問控制的結論
//1 protected 關鍵字 修飾的成員變量 和成員函數 ,是為了在家族中使用 ,是為了繼承
//2 項目開發中 一般情況下 是 public
//3class Parent
{
public:int a; //老爹的名字
protected:int b; //老爹的銀行密碼
private:int c; //老的情人
};//保護繼承
class Child3 : protected Parent
{
public:
protected:
private:
public: void useVar(){a = 0; // okb = 0; // ok //b這這里 寫在了子類Child3的內部 2看protected 3密碼===>protected//c = 0; //err}
};void main()
{Child3 c3;//c3.a = 10; //err//c3.b = 20; //err//c3.c = 30;//err}//私有繼承
class Child2 : private Parent
{
public:void useVar(){a = 0; //okb = 0; //ok//c = 0; // err}protected:
private:
};void main22()
{Child2 c2;//c1.a = 10; err//c2.b = 20; err//c3.b = 30;
}class Child : public Parent
{
public:void useVar(){a = 0; //okb = 0; //ok//c = 0; // err}protected:
private:
};/*
C++中的繼承方式(public、private、protected)會影響子類的對外訪問屬性 判斷某一句話,能否被訪問1)看調用語句,這句話寫在子類的內部、外部2)看子類如何從父類繼承(public、private、protected) 3)看父類中的訪問級別(public、private、protected)
*/
//共有繼承
void main21()
{Parent t1, t2;t1.a = 10; //ok//t1.b = 20; //err//t2.c = 30 ; //errcout<<"hello..."<<endl;system("pause");return ;
}
2.2 不同的繼承方式會改變繼承成員的訪問屬性
1)C++中的繼承方式會影響子類的對外訪問屬性
public繼承
父類成員在子類中保持原有訪問級別
private繼承
父類成員在子類中變為private成員
protected繼承
父類中public成員會變成protected
父類中protected成員仍然為protected
父類中private成員仍然為private
2)private成員在子類中依然存在,但是卻無法訪問到。不論種方式繼承基類,派生類都不能直接使用基類的私有成員 。
3)C++中子類對外訪問屬性表
2.3 “三看”原則
C++中的繼承方式(public、private、protected)會影響子類的對外訪問屬性
判斷某一句話,能否被訪問
1)看調用語句,這句話寫在子類的內部、外部
2)看子類如何從父類繼承(public、private、protected)
3)看父類中的訪問級別(public、private、protected)
2.4 派生類類成員訪問級別設置的原則
思考:如何恰當的使用public,protected和private為成員聲明訪問級別?
1、需要被外界訪問的成員直接設置為public
2、只能在當前類中訪問的成員設置為private
3、只能在當前類和子類中訪問的成員設置為protected,protected成員的訪問權限介于public和private之間。
示例代碼:
//類的繼承方式對子類對外訪問屬性影響#include <cstdlib>
#include <iostream>using namespace std;class A
{
private:int a;
protected:int b;
public:int c;A(){a = 0; b = 0; c = 0;}void set(int a, int b, int c){this->a = a; this->b = b; this->c = c;}
};
class B : public A
{
public:void print(){//cout<<"a = "<<a; //errcout<<"b = "<<b; //okcout<<"c = "<<endl; //ok}
};class C : protected A
{
public:void print(){//cout<<"a = "<<a; //errcout<<"b = "<<b; // okcout<<"c = "<<endl; //ok}
};class D : private A
{
public:void print(){//cout<<"a = "<<a; //errcout<<"b = "<<b<<endl; //okcout<<"c = "<<c<<endl; // ok}
};int main()
{A aa;B bb;C cc;D dd;aa.c = 100; //okbb.c = 100; // ok//cc.c = 100; // err 保護的//dd.c = 100; // erraa.set(1, 2, 3); //okbb.set(10, 20, 30); //ok//cc.set(40, 50, 60); // err//dd.set(70, 80, 90); //errbb.print(); //okcc.print(); //okdd.print(); ///**/system("pause");return 0;
}
3.繼承中的構造和析構
3.1 類型兼容性原則
類型兼容規則是指在需要基類對象的任何地方,都可以使用公有派生類的對象來替代。通過公有繼承,派生類得到了基類中除構造函數、析構函數之外的所有成員。這樣,公有派生類實際就具備了基類的所有功能,凡是基類能解決的問題,公有派生類都可以解決。類型兼容規則中所指的替代包括以下情況:
- 子類對象可以當作父類對象使用
- 子類對象可以直接賦值給父類對象
- 子類對象可以直接初始化父類對象
- 父類指針可以直接指向子類對象
- 父類引用可以直接引用子類對象
- 在替代之后,派生類對象就可以作為基類的對象使用,但是只能使用
基類繼承的成員。 - 類型兼容規則是多態性的重要基礎之一。
總結:子類就是特殊的父類 (base *p = &child;)
#include <iostream>
using namespace std;class Parent
{
public:void printP(){cout<<"我是爹..."<<endl;}Parent(){cout<<"parent構造函數"<<endl;}Parent(const Parent &obj){cout<<"copy構造函數"<<endl;}private:int a;
};class child : public Parent
{
public:void printC(){cout<<"我是兒子"<<endl;}
protected:
private:int c;
};/*
兼容規則中所指的替代包括以下情況:子類對象可以當作父類對象使用子類對象可以直接賦值給父類對象子類對象可以直接初始化父類對象父類指針可以直接指向子類對象父類引用可以直接引用子類對象*///C++編譯器 是不會報錯的 .....
void howToPrint(Parent *base)
{base->printP(); //父類的 成員函數 }void howToPrint2(Parent &base)
{base.printP(); //父類的 成員函數
}
void main()
{//Parent p1;p1.printP();child c1;c1.printC();c1.printP();//賦值兼容性原則 //1-1 基類指針 (引用) 指向 子類對象Parent *p = NULL;p = &c1;p->printP(); //1-2 指針做函數參數howToPrint(&p1);howToPrint(&c1); //1-3引用做函數參數howToPrint2(p1);howToPrint2(c1); //第二層含義//可以讓子類對象 初始化 父類對象//子類就是一種特殊的父類Parent p3 = c1;cout<<"hello..."<<endl;system("pause");return ;
}
3.2 繼承中的對象模型
- 類在C++編譯器的內部可以理解為結構體
- 子類是由父類成員疊加子類新成員得到的
問題:如何初始化父類成員?父類與子類的構造函數有什么關系
- 在子類對象構造時,需要調用父類構造函數對其繼承得來的成員進行初始化
- 在子類對象析構時,需要調用父類析構函數對其繼承得來的成員進行清理
#include <cstdlib>
#include <iostream>
using namespace std;class Parent04
{
public:Parent04(const char* s){cout<<"Parent04()"<<" "<<s<<endl;}~Parent04(){cout<<"~Parent04()"<<endl;}
};class Child04 : public Parent04
{
public:Child04() : Parent04("Parameter from Child!"){cout<<"Child04()"<<endl;}~Child04(){cout<<"~Child04()"<<endl;}
};void run04()
{Child04 child;
}int main_04(int argc, char *argv[])
{run04();system("pause");return 0;
}
3.3 繼承中的構造析構調用原則
1、子類對象在創建時會首先調用父類的構造函數
2、父類構造函數執行結束后,執行子類的構造函數
3、當父類的構造函數有參數時,需要在子類的初始化列表中顯示調用
4、析構函數調用的先后順序與構造函數相反
#include <iostream>
using namespace std;//結論
//先 調用父類構造函數 在調用 子類構造函數
//析構的順序 和構造相反/*
1、子類對象在創建時會首先調用父類的構造函數2、父類構造函數執行結束后,執行子類的構造函數3、當父類的構造函數有參數時,需要在子類的初始化列表中顯示調用4、析構函數調用的先后順序與構造函數相反
*/class Parent
{
public:Parent(int a, int b){this->a = a;this->b = b;cout<<"父類構造函數..."<<endl;}~Parent(){cout<<"析構函數..."<<endl;}void printP(int a, int b){this->a = a;this->b = b;cout<<"我是爹..."<<endl;}
private:int a;int b;
};class child : public Parent
{
public:child(int a, int b, int c) : Parent(a, b){this->c = c;cout<<"子類的構造函數"<<endl;}~child(){cout<<"子類的析構"<<endl;}void printC(){cout<<"我是兒子"<<endl;}
protected:
private:int c;
};void playObj()
{child c1(1, 2, 3);
}
void main()
{//Parent p(1, 2);playObj();cout<<"hello..."<<endl;system("pause");return ;
}
3.4 繼承與組合混搭情況下,構造和析構調用原則
- 先構造父類,再構造成員變量、最后構造自己
- 先析構自己,在析構成員變量、最后析構父類
#include <iostream>
using namespace std;class Object
{
public:Object(int a, int b){this->a = a;this->b = b;cout<<"object構造函數 執行 "<<"a"<<a<<" b "<<b<<endl;}~Object(){cout<<"object析構函數 \n";}
protected:int a;int b;
};class Parent : public Object
{
public:Parent(char *p) : Object(1, 2){this->p = p;cout<<"父類構造函數..."<<p<<endl;}~Parent(){cout<<"析構函數..."<<p<<endl;}void printP(int a, int b){cout<<"我是爹..."<<endl;}protected:char *p;};class child : public Parent
{
public:child(char *p) : Parent(p) , obj1(3, 4), obj2(5, 6){this->myp = p;cout<<"子類的構造函數"<<myp<<endl;}~child(){cout<<"子類的析構"<<myp<<endl;}void printC(){cout<<"我是兒子"<<endl;}
protected:char *myp;Object obj1;Object obj2;
};void objplay()
{child c1("繼承測試");
}
void main()
{objplay();cout<<"hello..."<<endl;system("pause");return ;
}
3.5 繼承中的同名成員變量處理方法
1、當子類成員變量與父類成員變量同名時
2、子類依然從父類繼承同名成員
3、在子類中通過作用域分辨符::進行同名成員區分(在派生類中使用基類的同名成員,顯式地使用類名限定符)
4、同名成員存儲在內存中的不同位置
總結:同名成員變量和成員函數通過作用域分辨符進行區分
#include <iostream>
using namespace std;class A
{
public:int a;int b;
public:void get(){cout<<"b "<<b<<endl;}void print(){cout<<"AAAAA "<<endl;}
protected:
private:
};class B : public A
{
public:int b;int c;
public:void get_child(){cout<<"b "<<b<<endl;}void print(){cout<<"BBBB "<<endl;}
protected:
private:
};void main()
{B b1;b1.print(); b1.A::print();b1.B::print(); //默認情況system("pause");
}//同名成員變量
void main71()
{B b1;b1.b = 1; //b1.get_child();b1.A::b = 100; //修改父類的bb1.B::b = 200; //修改子類的b 默認情況是Bb1.get();cout<<"hello..."<<endl;system("pause");return ;
}
3.6 派生類中的static關鍵字
繼承和static關鍵字在一起會產生什么現象哪?
理論知識
- 基類定義的靜態成員,將被所有派生類共享
- 根據靜態成員自身的訪問特性和派生類的繼承方式,在類層次體系中具有不同的訪問性質 (遵守派生類的訪問控制)
- 派生類中訪問靜態成員,用以下形式顯式說明:
類名 :: 成員
或通過對象訪問 對象名 . 成員
總結:
1> static函數也遵守3個訪問原則
2> static易犯錯誤(不但要初始化,更重要的顯示的告訴編譯器分配內存)
3> 構造函數默認為private
#include <iostream>
using namespace std;//單例
class A
{A(){cout<<"A的構造函數"<<endl;}
public:/*static int a;int b;*/
public:/*void get(){cout<<"b "<<b<<endl;}void print(){cout<<"AAAAA "<<endl;}*/
protected:
private:
};//int A::a = 100; //這句話 不是簡單的變量賦值 更重要的是 要告訴C++編譯器 你要給我分配內存 ,我再繼承類中 用到了a 不然會報錯../*
class B : private A
{public:int b;int c;
public:void get_child(){cout<<"b "<<b<<endl;cout<<a<<endl;}void print(){cout<<"BBBB "<<endl;}
protected:
private:
};
*///1 static關鍵字 遵守 派生類的訪問控制規則//2 不是簡單的變量賦值 更重要的是 要告訴C++編譯器 你要給我分配內存 ,我再繼承類中 用到了a 不然會報錯..//3 A類中添加構造函數 //A類的構造函數中 A的構造函數是私有的構造函數 ... //被別的類繼承要小心....//單例場景 .... UMLvoid main()
{A a1;//a1.print();//B b1;// b1.get_child();system("pause");
}void main01()
{// B b1;//b1.a = 200;system("pause");
}
4.多繼承
4.1 多繼承概念
一個類有多個直接基類的繼承關系稱為多繼承
- 多繼承聲明語法
class 派生類名 : 訪問控制 基類名1 , 訪問控制 基類名2 , … , 訪問控制 基類名n
{
數據成員和成員函數聲明
}; - 類 C 可以根據訪問控制同時繼承類 A 和類 B 的成員,并添加自己的成員
#include <iostream>
using namespace std;class Base1
{
public:Base1(int b1){this->b1 = b1;}void printB1(){cout<<"b1:"<<b1<<endl; }
protected:
private:int b1;
};class Base2
{
public:Base2(int b2){this->b2 = b2;}void printB2(){cout<<"b2:"<<b2<<endl; }
protected:
private:int b2;
};class B : public Base1, public Base2
{
public:B(int b1, int b2, int c): Base1(b1), Base2(b2){this->c = c;}void printC(){cout<<"c:"<<c<<endl; }
protected:
private:int c;
};void main()
{B b1(1, 2, 3);b1.printC();b1.printB1();b1.printB2();cout<<"hello..."<<endl;system("pause");return ;
}
4.2 多繼承的派生類構造和訪問
- 多個基類的派生類構造函數可以用初始式調用基類構造函數初始化數據成員
- 執行順序與單繼承構造函數情況類似。多個直接基類構造函數執行順序取決于定義派生類時指定的各個繼承基類的順序。
- 一個派生類對象擁有多個直接或間接基類的成員。不同名成員訪問不會出現二義性。如果不同的基類有同名成員,派生類對象訪問時應該加以識別。
4.3 簡單應用
4.4 虛繼承
如果一個派生類從多個基類派生,而這些基類又有一個共同的基類,則在對該基類中聲明的名字進行訪問時,可能產生二義性
分析如下:
#include <iostream>
using namespace std;class B
{
public:int b;
protected:
private:};class B1 : virtual public B
{
public:int b1;};class B2 : virtual public B
{
public:int b2;
};class C : public B1, public B2
{
public:int c;
};void main()
{C c1;c1.b1 = 100;c1.b2 = 200;c1.c = 300;c1.b = 500; //繼承的二義性 和 虛繼承解決方案//c1.B1::b = 500;//c1.B2::b = 500;cout<<"hello..."<<endl;system("pause");return ;
}
小結:
1)如果一個派生類從多個基類派生,而這些基類又有一個共同
的基類,則在對該基類中聲明的名字進行訪問時,可能產生二義性
2)如果在多條繼承路徑上有一個公共的基類,那么在繼承路徑的某處匯合點,這個公共基類就會在派生類的對象中產生多個基類子對象
3)要使這個公共基類在派生類中只產生一個子對象,必須對這個基類聲明為虛繼承,使這個基類成為虛基類。
4)虛繼承聲明使用關鍵字 virtual
4.5 總結
- 繼承是面向對象程序設計實現軟件重用的重要方法。程序員可以在已有基類的基礎上定義新的派生類。
- 單繼承的派生類只有一個基類。多繼承的派生類有多個基類。
- 派生類對基類成員的訪問由繼承方式和成員性質決定。
- 創建派生類對象時,先調用基類構造函數初始化派生類中的基類成員。 - 調用析構函數的次序和調用構造函數的次序相反。
- C++提供虛繼承機制,防止類繼承關系中成員訪問的二義性。
- 多繼承提供了軟件重用的強大功能,也增加了程序的復雜性。