1、為什麼需要最好将析構函數設定為虛函數
構造函數的調用順序: 先構造基類 再構造子類 析構函數的調用順序: 析構的時候先析構子類在析構基類
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
};
class Child :public Base
{
public:
Child()
{
cout << "Child()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
};
int main(void)
{
Base *pb = new Child;
delete pb;
return 0;
}
輸出結果為:
Base()
Child()
~Base()
現在将析構函數改為虛函數
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
virtual ~Base()
{
cout << "~Base()" << endl;
}
};
輸出結果為:
Base()
Child()
~Child()
~Base()
分析:上面未加virtual的析構函數,在對象析構 delete pb的時候 少調用了一次~Child(),這個原因是c++的靜态機制導緻的,在未加virtual的情況 執行delete b的時候根據Base *b = new Child 左邊的類型決定目前對象的類型,進而決定調用子類還是基類的析構函數,是以這裡先執行基類的析構函數,導緻子類的析構函數沒有執行。反之在前方加上了virtual關鍵字後,執行delete b的時候根據Base *b = new Base右邊的類型決定目前對象的類型,進而決定調用子類還是基類的析構函數,是以在執行delete b的時候先執行子類的析構函數,基類的析構函數自動執行。(隻對加上了virtual關鍵字的函數有用,未加上virtual關鍵字的函數,相當于沒有加入虛函數表,不存在這種動态機制,仍以左邊的類型來決定調用子類還是基類的函數,如果目前子類的新增的函數,該指針無法調用子類的方法)
在有Base *b = new Child基類指針指向子類對象的情況,如何判斷目前調用的是子類的方法還是基類的方法
首先看基類的該方法是否有virtal關鍵字,有則根據Base *b = new Child等式右邊,調用的是Child類中的方法,否則則根據右邊,調用的是Base 裡面的方法。
2、為什麼有時候要将虛函數設定為private權限
class Base
{
public:
Base();
private:
~Base();
};
int main(void) {
Base *b = new Base;
delete b; //報錯 不可通路
return 0;
}
為了解決上面這個問題,同時不改變析構函數的通路權限,我們需要自定定義一個對象清理函數。正好利用報錯這個性質,可以讓提醒使用該類的使用者,調用你提供的清理函數進行清理。
class Base
{
public:
Base();
void Clear(); //自己提供的類清理函數
private:
~Base();
};
int main(void) {
Base *b = new Base;
delete b; //報錯 不可通路
return 0;
}
3、模闆類和類模闆的差別
類模闆是一個模闆,模闆類是一個類。(模闆類是類模闆執行個體化後的一個産物,類模闆比作是一個作餃子的模子,而模闆類就是用這個模子做出來的餃子,至于餃子什麼餡兒的就需要你自己去執行個體化自己的内容。)
4.、為什麼有時候要禁止拷貝構造 指派函數
關于拷貝構造函數的禁用原因,主要是兩個原因。第一是淺拷貝問題(也就是說的雙殺問題),第二 個則是基類拷貝問題。
先看看第一個淺拷貝的原因(雙殺)
class Base
{
public:
Base()
{
cout << "Base()" << endl;
_buf = new char[20];
}
~Base()
{
cout << "~Base()" << endl;
delete[] _buf;
}
private:
char * _buf;
};
int main(void)
{
//
//Base b; //調用一次構造函數
//Base b2(b); //預設拷貝構造函數函數
//return 0; //return 0 執行結束後報錯
//
Base*b = new Base;
Base*b2 = new Base(*b);
delete b;
delete b2; //執行到這步出錯
return 0;
}
分析:導緻上面程式執行出錯的原因:在執行Base b2(b)或 Base*b2 = new Base(*b),程式會預設執行Base的拷貝構造函數
實際上預設的拷貝構造函數 會直接複制
buf_這個指針給要拷貝的對象
導緻兩個
Base
對象的_buf指向同一個記憶體區,這會導緻析構的時候兩次删除同一片區域的問題(這個問題也就是
雙殺
問題)。
要解決這個問題我們有兩個辦法
1、禁止拷貝構造函數和指派函數 當然這樣的類不允許進行拷貝 指派操作
class Base
{
public:
Base()
{
cout << "Base()" << endl;
_buf = new char[20];
}
Base(const Base &)=delete; //禁用
Base& operator=(cosnt Base &) = delete;//禁用
~Base()
{
cout << "~Base()" << endl;
delete[] _buf;
}
private:
//或設定權限為private
Base(const Base &); //禁用
Base& operator=(cosnt Base &) ;//禁用
char * _buf;
};
第二種辦法 就需要我們自己定義拷貝構造函數和指派函數
在自己實作的拷貝構造函數和指派函數中對涉及到記憶體配置設定分指針進行記憶體配置設定後将要拷貝對象中指針指向的記憶體的那内容拷貝到新對象中;
Base::Base(const Base& base)
{
int length = strlen(base._buf);
_buf= new char[length+1];
strcpy_s(m_pData, length + 1, base._buf);
}
5、為什麼有時候基類類型的指針指向子類對象 但無法掉用子的類的新增方法
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
void kill()
{
}
virtual ~Base()
{
cout << "~Base()" << endl;
}
};
class Child :public Base
{
public:
Child()
{
cout << "Child()" << endl;
}
virtual void set() //新增方法
{
cout << " Child Set()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
};
int main(void)
{
Base *pb = new Child;
pb->set();//這裡報錯 "Base"沒有成員 set();
delete pb;
return 0;
}
分析 :C++在對沒有virtual關鍵字申明的成員函數進行編譯時,預設處理方式為靜态機制, Base *pb = new Child; 這裡對set()函數而言,目前對象的類型實際為Base 是以會報錯 。實際的類型Base *pb = new Child 預設都是左邊 隻用在調用的函數為虛函數時候,才看Base *pb = new Child 右邊對象類型,這裡注意的是對象實際類型跟基類的函數是否加入動态機制有關,否則都是以等号左邊的為主;
解決辦法:
1、在基類定義該方法 2、在基類定義該方法生命為純虛函數 3、在基類定義該方法申明為虛函數