第九天2017/04/18(3、重载/覆盖 PK 重写/重定义、父类子类混搭风、抽象类)
生活随笔
收集整理的這篇文章主要介紹了
第九天2017/04/18(3、重载/覆盖 PK 重写/重定义、父类子类混搭风、抽象类)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1、重載/覆蓋 PK 重寫/重定義
【預備知識】
函數重載必須在同一個類中發生子類無法重載父類的函數,父類同名的函數將會被名稱覆蓋重載是在編譯期間根據參數類型和個數決定函數調用重載只放在同一個類之中,在編譯期間就確定
函數重寫必須發生在父類與子類之間父類與子類中的函數必須有完全相同的函數原型使用virtual關鍵字聲明后能夠產生多態(如果沒有virtual,那叫重定義)多態是運行期間根據具體對象的類型決定函數調用重寫發生在父子類之間,
覆蓋父類和子類中有“同名”的函數,子類的函數會把父類的函數隱藏起來
重定義(是一種特殊的覆蓋)父類和子類中有“相同函數原型”的函數,子類的函數會把父類的函數隱藏起來
1、重載/覆蓋 PK 重寫/重定義重載:在“同一個類”中,函數名相同、函數原型不同,此時發生重載覆蓋:無virtual關鍵字,在父類、子類中,函數名相同、函數原型不同,此時在子類中的函數隱藏了父類中的函數。
---------------------------------------------------------------------------------------------重寫:有virtual關鍵字,在父類、子類中,函數名、函數原型都相同,此時發生重寫重定義:無virtual關鍵字,在父類、子類中,函數名、函數原型都相同,此時在子類中的函數隱藏了父類中的函數,類似于“覆蓋”。#include <iostream>
using namespace std;
class B
{
public:void f() { }virtual void f(int i) { } void f(int i,int j) { }
};
class D:public B
{
public://子類中沒有void f()函數void f(int i) { } //重寫void f(int i,int j) { } //發生名稱覆蓋void f(int i,int j,int k) { } //發生名稱覆蓋
};
void g(B& b)
{b.f(1);
}void main()
{D d;
/***************************************************************************/
//【重點】
//疑問:為什么子類對象不能調用父類中的f()函數?
//答:因為子類中有函數名為f的函數,有由于子類的函數不會重載父類的函數,所以
//子類中的f函數會把父類中的無參的f()函數給覆蓋,因此直接d.f()會發生編譯錯誤!//d.f();//error: 沒有重載函數接受0個參數的f()
//疑問:如果我就是想用子類對象去調用父類中的f()函數,應該怎么做?
//答:加上作用域符B::,此時d對象就會調用父類B中的f()函數。d.B::f(); //
//【結論】子類中的f()函數不會重載父類中的f()函數,父類同名的函數將被覆蓋!重載只發生在同一個類中!
/***************************************************************************/D dd;
//覆蓋//dd.f(); //編譯失敗,因為無virtual發生同名覆蓋dd.B::f(); //作用域B::,調用子類中的void f()
//覆蓋、重定義dd.B::f(1,2);//作用域B::,調用子類中的void f(int i,int j)dd.f(1,2); //覆蓋:調用子類中的void f(int i,int j)
//多態B &b = d;b.f(1); //多態:調用子類中的void f(int i)b.B::f(1); //作用域B::,調用父類中的void f(int i)
//重載
}
----------------------------------------------------------------------
2、父類對象、子類對象混搭風【該模塊中,隱藏了一個天大的Bug:“P++步長”】本質:由于步長的影響
【結論】不要用父類指針p指向子類對象的數組,通過p++,去遍歷這個子類對象的數組。
同理:也不要用子類指針p指向父類對象的數組,通過p++,去遍歷這個父類對象的數組
【為什么?】因為父類指針p指向了子類對象的數組,在進行p++的時候,
p增加的步長是sizeof(父類),但是由于sizeof(父類)不一定等于sizeof(子類),
因此p在進行加1后,指向的位置不一定是下一個子類對象的首地址。當p指向的位置
不是下一個子類對象的首地址的時候,如果進行訪問子類對象中的成員,程序必然會發生
崩潰。
//程序案例
#include <iostream>
using namespace std;
class A
{
public:virtual void f(){cout<<"A:f()"<<endl;}
};
class B:public A
{
public:int i; //為什么在子類中加上一個屬性i,程序就會運行崩潰:因為加了一個變量,對步長有影響
public:B(int i=0,int j=0){}virtual void f(){cout<<"B:f()"<<endl;}
};void howToF(A* pBase)
{pBase->f();
}int main()
{A *p = NULL;B *q = NULL;B c[3] = {B(1,1),B(1,1),B(1,1)};p = c; //父類指針p指向由子類對象構成的數組q = c; //子類指針q指向由子類對象構成的數組for(int i=0;i<3;i++){//p->f(); //因為步長的原因,訪問成員時,會發生程序崩潰q->f();p++; //p++增加的步長是sizeof(父類),而不是sizeof(子類)q++;}for(int i=0;i<3;i++){howToF(&c[i]); //形參:子類對象的地址,實參:父類指針//解釋:此處運行正確,為什么?
//此處沒有用p++形式,而是用了下標操作c[i],避開了p++的步長不一致導致的程序崩潰。}return 0;
}
----------------------------------------------------------------------
3、抽象類
#include <iostream>
using namespace std;
class A
{
public:virtual void ff() = 0;
};//A g1(); //不允許使用返回抽象類 "A" 的函數
A& g21(); //允許使用返回抽象類引用類型 "A&" 的函數
A& g22(A&);
A& g23(A*);
A* g31(); //允許使用返回抽象類指針類型 "A*" 的函數
A* g32(A*);
A* g32(A&);//void f1(A a);//不允許使用抽象類類型 "A" 的參數
void f2(A &a);//允許使用抽象類引用類型 "A&" 的參數
void f2(A *a);//允許使用抽象類指針類型 "A*" 的參數class B:public A
{
public:void ff() { }
};
int main()
{//A a1;//不能實例化抽象類//A a2 = new A;//不能實例化抽象類A *a4 = new B; //可以用抽象類的指針指向抽象類的子類對象B b1;A &a3 = b1;//可以用抽象類的引用指向抽象類的子類對象
}4、多重繼承與抽象類--->實現多繼承接口
【知識復習】
#include <iostream>
using namespace std;
class A1
{
public:virtual void ff() = 0; //純虛函數void gg() { cout<<"普通函數"<<endl; } //普通函數在A1中實現
};
class A2
{
public:virtual void ff() = 0; //純虛函數void gg() { cout<<"普通函數"<<endl; }//普通函數在A2中實現
};
class B:public A1,public A2//B多重繼承A1、A2時
{
public:virtual void ff() //純虛函數ff在B中實現{ cout<<"純虛函數"<<endl; }};
int main()
{B b;
//b訪問純虛函數ff,不會發生二義性b.ff();
//b訪問普通函數gg,會發生二義性//b.gg(); //編譯失敗
//防止二義性,應該加上作用域標識符A1::、A2::b.A1::gg();b.A2::gg();
}
【知識引出】
疑問:多重繼承會發生二義性,但是多重繼承在C++中有什么作用呢?
答:在項目開發過程中,多重繼承的作用主要是:多重繼承多個抽象類(接口),這樣會有效的避免二義性。多繼承接口案例
#include <iostream>
using namespace std;
class A
{
public:virtual void ff(){ cout<<"ff:A"<<endl; }
};class interface1 //抽象類接口1
{
public:virtual void gg1() = 0;
};
class interface2 //抽象類接口2
{
public:virtual void gg2() = 0;
};
class B:public A,public interface1,public interface2
{
//B類繼承了類A、接口interface1、接口interface2
public:void gg1() { cout<<"gg1"<<endl; }void gg2() { cout<<"gg2"<<endl; }void kk(){ cout<<"KK"<<endl; }
};
int main()
{B b;b.ff();b.gg1();b.gg2();b.kk();
}
總結
以上是生活随笔為你收集整理的第九天2017/04/18(3、重载/覆盖 PK 重写/重定义、父类子类混搭风、抽象类)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第九天2017/04/18(2、类的继承
- 下一篇: 第九天2017/04/18(4、非虚继承