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