C++基础知识-Day8
?
2.類的作用域運算符
shadow
在我們之前講的內(nèi)容中,我們會發(fā)現(xiàn)一種情況,就是在我們在不同類中的打印函數(shù)我們都是盡量讓其名字不同,那么為什么會有這種情況呢?首先我們來看一個函數(shù)
void func(){cout<<"B::void func()"<<endl;func();}運行程序會發(fā)現(xiàn)這是一個死循環(huán),因為其存在自己調(diào)用自己的情況,那么放在類中會是什么樣子的呢
#include <iostream>using namespace std; class A { public:void foo(){cout<<"A::void foo()"<<endl;} }; class B:public A { public:void foo(){cout<<"B::void foo()"<<endl;foo();//實際上這里是有一個this指針指向foo的 } }; int main() {B b;b.foo();return 0; }?這樣調(diào)用還是會出現(xiàn)死循環(huán)的情況,雖然其本意是在類B中的foo調(diào)用類A中的foo,但是由于this指針指向foo并且由于類中的兩個函數(shù)重名,因此會出現(xiàn)死循環(huán),為了解決這個問題,引入類的作用域運算符,將類B中的foo函數(shù)寫成如下形式
void foo(){cout<<"B::void foo()"<<endl;A::foo();}?shadow產(chǎn)生機理
(1)??在父子類中出現(xiàn)重名的標(biāo)識符(函數(shù)成員和數(shù)據(jù)成員),就會構(gòu)成shadow,如果想訪問被shadow的成員,加上父類的命名空間
(2)??shadow在父子類中的標(biāo)識符只有一個,就是重名,不論返回值,參數(shù)不同什么
?
?3. 繼承的方式詳解
繼承的方式有三種:public,protected和private,但是我們一般都用public
所有的繼承必須是public的,如果想私有繼承的話,應(yīng)該采用將基類實例作為成員的方式作為替代
一般情況下,在一個類中,public常用于接口,protected常用于數(shù)據(jù),private常用于隱私
那么為什么public是用的最多的呢
?如果多級派生中,均采用public,直到最后一級,派生類中均可訪問基類的public,protected,很好的做到了接口的傳承,保護數(shù)據(jù)以及隱私的保護
?protected:封殺了對外的接口,保護數(shù)據(jù)成員,隱私保護
public:傳承接口,間接地傳承了數(shù)據(jù)(protected)
protected:傳承數(shù)據(jù),間接封殺了對外接口(public)
private:統(tǒng)殺了數(shù)據(jù)和接口
4.?類的作用域運算符
shadow產(chǎn)生機理
(1)??在父子類中出現(xiàn)重名的標(biāo)識符(函數(shù)成員和數(shù)據(jù)成員),就會構(gòu)成shadow,如果想訪問被shadow的成員,加上父類的命名空間
(2)??shadow在父子類中的標(biāo)識符只有一個,就是重名,不論返回值,參數(shù)不同什么
5.?多重繼承
從繼承類別來說,繼承可以分為單繼承和多繼承
多繼承的意義:
俗話講,魚和熊掌不可兼得,而在計算機中可以實現(xiàn),生成一種新的對象,叫熊掌魚,多繼承自魚和熊掌即可
繼承語法:
派生類名:public?基類名1,public?基類名2,…,protected?基類名n
構(gòu)造器格式
派生類名:派生類名(總參列表)
???????:基類名1(參數(shù)表1),基類名2(參數(shù)名2),…基類名n(參數(shù)名n),
???????內(nèi)嵌子對象1(參數(shù)表1),內(nèi)嵌子對象2(參數(shù)表2)…內(nèi)嵌子對象n(參數(shù)表n)
{
???????派生類新增成員的初始化語句
}
多繼承可能存在的問題
(1)??三角問題
多個父類中重名的成員,繼承到子類中后,為了避免沖突,攜帶了各父類的作用域信息,子類中要訪問繼承下來的重名成員,則會產(chǎn)生二義性,為了避免沖突,訪問時需要提供父類的作用域信息
構(gòu)造器問題
下面我們用一個實際的例子來對其進行講解
1 #include <iostream> 2 3 using namespace std; 4 5 class X 6 { 7 public: 8 X(int d) 9 { 10 cout<<"X()"<<endl; 11 } 12 protected: 13 int _data; 14 }; 15 16 class Y 17 { 18 public: 19 Y(int d) 20 { 21 cout<<"Y()"<<endl; 22 } 23 protected: 24 int _data; 25 }; 26 27 class Z:public X,public Y 28 { 29 public: 30 Z() 31 :X(1),Y(2) 32 { 33 34 } 35 void dis() 36 { 37 cout<<Y_data<<endl;39 } 40 }; 41 42 int main() 43 { 44 Z z; 45 z.dis(); 46 return 0; 47 }直接這樣的話會報錯,因為_data會產(chǎn)生二義性,為了解決這個問題,我們可以在數(shù)據(jù)之前加上其父類作用域
1 void dis() 2 { 3 cout<<Y::_data<<endl; 4 cout<<X::_data<<endl; 5 }下面我們看一個有趣的情況
#include <iostream>using namespace std;class X { public:X(int d){cout<<"X()"<<endl;_data=d;}void setData(int d){_data=d;} protected:int _data; };class Y { public:Y(int d){cout<<"Y()"<<endl;_data=d;}int getData(){return _data;} protected:int _data; };class Z:public X,public Y { public:Z(int i,int j):X(i),Y(j){}void dis(){cout<<X::_data<<endl;cout<<Y::_data<<endl;} };int main() {Z z(100,200);z.dis();cout<<"================="<<endl;z.setData(1000000);cout<<z.getData()<<endl;cout<<"================="<<endl;z.dis();return 0; }在這里我們getData得到的數(shù)據(jù)仍然是200,并不是setData的1000000,原因如下
剛開始的時候,在類X和類Y中,都有一個_data,
?
?當(dāng)其繼承在類Z中后
?
由于是重名的問題,setData設(shè)置的是類X中的數(shù)據(jù),但是getData得到的是類Y中的數(shù)據(jù),所以說會出現(xiàn)問題
那么我們應(yīng)該怎么來解決這個問題呢
需要解決的問題:
數(shù)據(jù)冗余
訪問方便
由此引發(fā)了一個三角轉(zhuǎn)四角的問題
首先解決初始化問題,
祖父類的好處是,祖父類是默認(rèn)的構(gòu)造器,因此在父類中,并不需要顯示地調(diào)用,按道理說,Z中有類X,Y,只需要管X,Y的初始化就可以了
#include <iostream>using namespace std;//祖父類 class A { protected:int _data; }; //父類繼承祖父類 class X:virtual public A { public:X(int d){cout<<"X()"<<endl;_data=d;}void setData(int d){_data=d;}}; //各父類繼承祖父類 class Y:virtual public A//虛繼承 { public:Y(int d){cout<<"Y()"<<endl;_data=d;}int getData(){return _data;} };class Z:public X,public Y { public:Z(int i,int j):X(i),Y(j){}void dis(){cout<<_data<<endl;} };int main() {Z z(100,200);z.dis();cout<<"================="<<endl;z.setData(1000000);cout<<z.getData()<<endl;cout<<"================="<<endl;z.dis();return 0; }這樣就帶來了兩個好處,解決了數(shù)據(jù)冗余的問題,并且為訪問帶來了便利,虛繼承也是一種設(shè)計的結(jié)果,被抽象上來的類叫做虛基類。也可以說成:被虛繼承的類稱為虛基類
虛基類:被抽象上來的類叫做虛基類
虛繼承:是一種對繼承的擴展
那么虛繼承就有幾個問題需要我們來注意了,首先是初始化的順序問題,為了測試初始化的順序問題,因為上述都是構(gòu)造器的默認(rèn)情況,但是實際情況中,可能都會帶參數(shù),甚至是虛繼承的祖父類也會帶參數(shù),那么構(gòu)造器順序又將是如何的呢?我們利用如下代碼進行測試
1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 public: 8 A(int i) 9 { 10 _data=i; 11 cout<<"A(int i)"<<endl; 12 } 13 protected: 14 int _data; 15 }; 16 class B:virtual public A 17 { 18 public: 19 B(int i) 20 :A(i) 21 { 22 _data=i; 23 cout<<"B(int i)"<<endl; 24 } 25 }; 26 27 class C:virtual public A 28 { 29 public: 30 C(int i) 31 :A(i) 32 { 33 _data=i; 34 cout<<"C(int i)"<<endl; 35 } 36 }; 37 38 class D:public C,B 39 { 40 public: 41 D() 42 :C(1),B(1),A(1) 43 { 44 cout<<"D(int i)"<<endl; 45 } 46 void dis() 47 { 48 cout<<_data<<endl; 49 } 50 }; 51 int main() 52 { 53 D d; 54 d.dis(); 55 return 0; 56 }運行代碼后我們可以得知,構(gòu)造的順序是從祖父類的構(gòu)造器開始,按照順序執(zhí)行下來,最后到孫子類的構(gòu)造器為止的
當(dāng)然,上述只是一個測試,因為在實際過程中,祖父類是由父類抽象起來的,因此一般不會用祖父類生成對象?
在實際過程中,在父類的構(gòu)造器中我們常帶默認(rèn)參數(shù),這樣我們就可以不使得派生類的構(gòu)造器如此復(fù)雜
實際例子,沙發(fā)床,除了上述之外,我們還需要增加顏色和重量,除此之外,我們還需要用descript函數(shù)來對其進行描述
#include <iostream>using namespace std;class Furniture { public:void descript(){cout<<"_weight:"<<_weight<<endl;cout<<"_color :"<<_color<<endl;} protected:float _weight;int _color; }; class Sofa:virtual public Furniture { public:Sofa(float w=0,int c=1){_weight=w;_color=c;}void sit(){cout<<"take a sit and have a rest"<<endl;} };class Bed:virtual public Furniture { public:Bed(float w=0,int c=1){_weight=w;_color=c;}void sleep(){cout<<"have a sleep ......."<<endl;}};class SofaBed:public Sofa,public Bed { public:SofaBed(float w,int c){_weight=w;_color=c;} };int main() {SofaBed sb(1000,2);sb.sit();sb.sleep();sb.descript();return 0; }int main1() {Sofa sf;sf.sit();Bed bd;bd.sleep();return 0; }?6. 多態(tài)
(1)??生活中的多態(tài)
如果有幾個相似而不完全相同的對象,有時人們要求在向他們發(fā)出同一個消息時,他們的反應(yīng)各不相同,分別執(zhí)行不同的操作,這種情況就是多態(tài)現(xiàn)象
(2)??C++?中的多態(tài)
C++?中的多態(tài)是指,由繼承而產(chǎn)生的相關(guān)的不同的類,其對同一消息會做出不同的響應(yīng)
比如,Mspaint中的單擊不同圖形,執(zhí)行同一拖動動作而繪制不同的圖形,就是典型的多態(tài)應(yīng)用
多態(tài)性是面向?qū)ο蟪绦蛟O(shè)計的一個重要特征,能增加程序的靈活性,可以減輕系統(tǒng)的升級,維護,調(diào)試的工作量和復(fù)雜度
(3)??賦值兼容
賦值兼容是指,在需要基類對象的任何地方,都可以使用共有派生的對象來替代
只有在共有派生類中才有賦值兼容,賦值兼容是一種默認(rèn)行為,不需要任何的顯示的轉(zhuǎn)化步驟
賦值兼容總結(jié)起來有以下三種特點
| 派生類的對象可以賦值給基類對象 |
| 派生類的對象可以初始化基類的引用 |
| 派生類對象的地址可以賦給指向基類的指針 |
下面我們將分別對其進行說明
- 派生類的對象可以賦值給基類對象
觀察下面代碼
1 #include <iostream> 2 3 using namespace std; 4 5 class Shape 6 { 7 public: 8 Shape(int x=0,int y=0) 9 :_x(x),_y(y){} 10 void draw() 11 { 12 cout<<"draw shape from"<<"("<<_x<<","<<_y<<")"<<endl; 13 } 14 protected: 15 int _x; 16 int _y; 17 }; 18 class Circle:public Shape 19 { 20 public: 21 Circle(int x=0,int y=0,int r=1) 22 :Shape(x,y),_radius(r){} 23 void draw() 24 { 25 cout<<"draw shape from"<<"("<<_x<<","<<_y<<")"<<"radius:"<<_radius<<endl; 26 } 27 protected: 28 int _radius; 29 }; 30 int main() 31 { 32 Shape s(1,2); 33 s.draw(); 34 Circle c(4,5,6); 35 c.draw(); 36 s=c; //派生類對象可以賦值給基類對象 37 s.draw(); 38 return 0; 39 }?有上述例子可以看出,派生類的對象是可以復(fù)制給基類對象的
- 派生類的對象可以初始化基類的引用
- 派生類的對象的地址可以賦給指向基類的指針
?在這三種情況中,使用的最多的是第三種,即派生類對象的地址可以賦給指向基類的指針
就如圖示一樣,假設(shè)左邊的類是父類,右邊的類是子類,,左邊的指針是派生類的對象的地址賦給指向派生類的指針,那么其可訪問的范圍就是整個派生類,右邊的指針是派生類的對象的地址賦給指向基類的指針,那么其訪問范圍就只有基類的那一部分
?
7. 多態(tài)
多態(tài)分為靜多態(tài)和動多態(tài)
靜多態(tài),就是我們說的函數(shù)重載,表面上,是由重載規(guī)則來限定的,內(nèi)部實現(xiàn)卻是Namemangling,此種行為,發(fā)生在編譯期,故稱為靜多態(tài)
(動)多態(tài),不是在編譯階段決定,而是在運行階段決定,故稱動多態(tài),動多態(tài)的形成條件如下
多態(tài)實現(xiàn)的條件
?
| 父類中有虛函數(shù)(加virtual,是一個聲明型關(guān)鍵字,即只能在聲明中有,在實現(xiàn)中沒有),即公用接口 |
| ?子類override(覆寫)父類中的虛函數(shù) ? |
| 通過已被子類對象賦值的父類指針,調(diào)用共有接口 ? |
下面分別對這些條件進行講解
- 父類中有虛函數(shù)(加virtual,是一個聲明型關(guān)鍵字,即只能在聲明中有,在實現(xiàn)中沒有),即公用接口
virtual函數(shù)是一個聲明型關(guān)鍵字,只能在聲明中有,在實現(xiàn)中沒有
class A { public:A(){};virtual void draw(); private:int _x; } void A::draw() {cout<<_x<<endl; }假設(shè)在實現(xiàn)的過程中也加入virtual關(guān)鍵字,即
virtual void A::draw() {cout<<_x<<endl; }系統(tǒng)即會開始報錯
- ?子類覆寫父類中的虛函數(shù),子類中同名同參同函數(shù),才能構(gòu)成覆寫
- 通過已被子類對象賦值的父類指針,調(diào)用虛函數(shù),形成多態(tài)
可以看出,利用virtual,可以實現(xiàn)多態(tài)
通過父類的指針調(diào)用父類的接口指向其本來應(yīng)該指向的內(nèi)容
1 int main() 2 { 3 Circle c(3,4,5); 4 Shape *ps=&c;//父類指針指向子類的對象 5 ps->draw(); 6 7 Rect r(6,7,8,9); 8 ps=&r; 9 ps->draw(); 10 while(1) 11 { 12 int choice; 13 cin>>choice; 14 switch(choice) 15 { 16 case 1: 17 ps=&c; 18 break; 19 case 2: 20 ps=&r; 21 break; 22 } 23 ps->draw(); 24 } 25 return 0; 26 }一個接口呈現(xiàn)出不同的行為,其中virtual是一個聲明型關(guān)鍵字,用來聲明一個虛函數(shù),子類覆寫了的函數(shù),也是virtual?
虛函數(shù)在子函數(shù)中的訪問屬性并不影響多態(tài),要看子類
虛函數(shù)和多態(tài)總結(jié)
(1)virtual是聲明函數(shù)的關(guān)鍵字,他是一個聲明型關(guān)鍵字
(2)override構(gòu)成的條件,發(fā)生在父子類的繼承關(guān)系中,同名,同參,同返回
(3)虛函數(shù)在派生類中仍然為虛函數(shù),若發(fā)生覆寫,最好顯示的標(biāo)注virtual
(4)子類中覆寫的函數(shù),可以為任意的訪問類型,依子類需求決定
?
8. pure virtual function
純虛函數(shù),指的是virtual修飾的函數(shù),沒有實現(xiàn)體,被初始化為0,被高度抽象化的具有純接口類才配有純虛函數(shù),含有純虛函數(shù)的類稱為抽象基類
抽象基類不能實例化(不能生成對象),純粹用來提供接口用的
子類中若無覆寫,則依然為純虛,依然不能實例化
9. 總結(jié)
(1)純虛函數(shù)只有聲明,沒有實現(xiàn),被“初始化”為0
(2)含有純虛函數(shù)的類,稱為Abstract Base Class(抽象基類),不能實例化,即不能創(chuàng)造對象,存在的意義就是被繼承,而在派生類中沒有該函數(shù)的意義
(3)如果一個中聲明了純虛函數(shù),而在派生類中沒有該函數(shù)的定義,則該虛函數(shù)在派生類中仍然為虛函數(shù),派生類仍然為純虛基類
10. 析構(gòu)函數(shù)
含有虛函數(shù)的類,析構(gòu)函數(shù)也應(yīng)該聲明為虛函數(shù)
這是為了保證對象析構(gòu)的完整性,具體的情況就是父類的指針指向子類的堆對象,此時通過父類指針去析構(gòu)子類堆對象時就會虛構(gòu)不完整,為了保證析構(gòu)的完整性,含有虛函數(shù)的類將其析構(gòu)函數(shù)也聲明為虛函數(shù)(virtual)
對比棧對象和對對象在多態(tài)中銷毀的不同
首先我們來看位于棧上的對象
在這里,我們生成了幾個類,一個是抽象基類,一個是Dog類,一個是Cat類,我們分別在class中去構(gòu)造這幾個類
首先生成Animal類
其.h文件的內(nèi)容如下
1 #ifndef ANIMAL_H 2 #define ANIMAL_H 3 class Animal 4 { 5 public: 6 Animal(); 7 ~Animal(); 8 virtual void voice()=0; 9 }; 10 #endif // ANIMAL_H其.cpp文件中的內(nèi)容如下
1 #include "animal.h" 2 #include <iostream> 3 using namespace std; 4 Animal::Animal() 5 { 6 cout<<"Animal::Animal()"<<endl; 7 } 8 9 Animal::~Animal() 10 { 11 cout<<"Animal::~Animal()"<<endl; 12 }然后我們再生成Dog的.h文件
1 #ifndef DOG_H 2 #define DOG_H 3 #include "animal.h" 4 class Animal; 5 class Dog : public Animal 6 { 7 public: 8 Dog(); 9 ~Dog(); 10 11 virtual void voice(); 12 }; 13 #endif // DOG_H然后我們再生成Dog的.cpp文件
1 #include "dog.h" 2 #include "animal.h" 3 #include <iostream> 4 using namespace std; 5 Dog::Dog() 6 { 7 cout<<"Dog::Dog()"<<endl; 8 } 9 10 Dog::~Dog() 11 { 12 cout<<"Dog::~Dog()"<<endl; 13 } 14 15 void Dog::voice() 16 { 17 cout<<"wang wang wang"<<endl; 18 }然后我們生成Cat類
首先生成Cat的.h文件
1 #ifndef CAT_H 2 #define CAT_H 3 #include "animal.h" 4 class Cat : public Animal 5 { 6 public: 7 Cat(); 8 ~Cat(); 9 10 virtual void voice(); 11 }; 12 #endif // CAT_H然后再生成cat的.cpp文件
1 #include "cat.h" 2 #include "animal.h" 3 #include <iostream> 4 using namespace std; 5 Cat::Cat() 6 { 7 cout<<"Cat::Cat()"<<endl; 8 } 9 Cat::~Cat() 10 { 11 cout<<"Cat::~Cat()"<<endl; 12 } 13 void Cat::voice() 14 { 15 cout<<"miao miao miao"<<endl; 16 }最后,main函數(shù)如下
1 #include <iostream> 2 #include "animal.h" 3 #include "cat.h" 4 #include "dog.h" 5 using namespace std; 6 7 int main() 8 { 9 Cat c; 10 Dog d; 11 Animal *pa=&c; 12 pa->voice(); 13 return 0; 14 }生成的結(jié)果為
可以看出其是析構(gòu)完全了的
但是若為棧上的對象,即主函數(shù)改寫為如下
1 #include <iostream> 2 #include "animal.h" 3 #include "cat.h" 4 #include "dog.h" 5 using namespace std; 6 7 int main() 8 { 9 Animal *pa=new Dog; 10 pa->voice(); 11 delete pa; 12 return 0; 13 }得出的結(jié)果為
可以看出其是沒有析構(gòu)完全的,生成的Dog是沒有析構(gòu)的,因此對于堆上的對象,其是析構(gòu)器有問題的
我們只需要解決如下
但凡類中含有虛函數(shù)(包括純虛函數(shù)),將其虛構(gòu)函數(shù)置為virtual ,這樣即可以實現(xiàn)完整虛構(gòu)
12.設(shè)計模式的原則:依賴倒置原則-核心思想:面向接口編程
傳統(tǒng)的過程式設(shè)計傾向于使高層次的模塊依賴于低層次的模塊(自頂向下,逐步細(xì)化),而依據(jù)DIP的設(shè)計原則,將中間層抽象為抽象層,讓高層模塊和底層模塊依賴于中間層
以一個例子來進行舉例,用母親給給孩子講故事來進行舉例
原本母親給孩子講故事是依賴于故事書上的內(nèi)容,因此對于母親給孩子講故事我們可以寫成如下代碼
1 //Mother 依賴于 Book 依賴->耦合 -->低耦合 2 class Book 3 { 4 public: 5 string getContents() 6 { 7 return "從前有座山,山里有座廟,廟里有個小和尚." 8 "聽老和尚講故事,從前有座山"; 9 } 10 }; 11 class Mother 12 { 13 public: 14 void tellStory(Book &b) 15 { 16 cout<<b.getContents()<<endl; 17 } 18 };在這里,母親和書的關(guān)系是一種強耦合關(guān)系
即只要書的內(nèi)容發(fā)生改變,Book,Mother等都需要發(fā)生改變,這樣是很麻煩的
但是實際上,這種強耦合關(guān)系是我們所不希望的,為了解決這種強耦合關(guān)系,我們引入一個中間層
1 #include <iostream> 2 3 using namespace std; 4 5 //Mother 依賴于 Book 依賴->耦合 -->低耦合 6 7 class IReader 8 { 9 public: 10 virtual string getContents()=0; 11 }; 12 13 class Book:public IReader 14 { 15 public: 16 string getContents() 17 { 18 return "從前有座山,山里有座廟,廟里有個小和尚." 19 "聽老和尚講故事,從前有座山"; 20 } 21 }; 22 23 class NewsPaper:public IReader 24 { 25 public: 26 string getContents() 27 { 28 return "Trump 要在黑西哥邊境建一座墻"; 29 } 30 }; 31 class Mother 32 { 33 public: 34 void tellStory(IReader *pi) 35 { 36 cout<<pi->getContents()<<endl; 37 } 38 }; 39 int main() 40 { 41 Mother m; 42 Book b; 43 NewsPaper n; 44 m.tellStory(&b); 45 m.tellStory(&n); 46 return 0; 47 }這樣的話,書改變時,Mother是不會發(fā)生改變的,只需要加一個新類就是可以的了,用戶端接口不會發(fā)生改變
虛繼承和虛函數(shù)總結(jié)
虛繼承解決了多個父類中重名冗余的成員(包括數(shù)據(jù)成員和函數(shù)成員)
虛函數(shù)解決了多態(tài)的問題
被虛繼承的類稱為虛基類,含有純虛函數(shù)的類稱為抽象基類
?
轉(zhuǎn)載于:https://www.cnblogs.com/Cucucudeblog/p/10148671.html
總結(jié)
以上是生活随笔為你收集整理的C++基础知识-Day8的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python-函数递归调用
- 下一篇: github gitlab BitBuc