例1: C++ 中的空類默認產生哪些類成員函數?ios
對於一個空類,編譯器默認產生4個成員函數:默認構造函數、 析構函數、 拷貝構造函數和賦值函數。程序員
例2: structure是否能夠擁有constructor / destructor及成員函數?若是能夠, 那麼structure和class還有區別麼?編程
區別是class中變量默認是private, struct中的變量默認是public。struct能夠有構造函數,析構函數,之間也能夠繼承,等等。 C++中的struct其實和class意義同樣, 惟一不一樣的就是struct裏面默認的訪問控制是public, class中默認的訪問控制是 private。 C++中存在struct關鍵字的惟一意義就是爲了讓C程序員們有個歸屬感, 是爲了讓C++編譯器兼容之前用C開發的項目。數組
例3: 現有如下代碼, 則編譯時會產生錯誤的是?ide
struct Test { Test(int) {} Test() {} ~Test() {} void fun() {} }; int main() { Test a(1); a.fun(); Test b(); b.fun(); return 0; }
Test b()
這個語法等同於聲明瞭一個函數,函數名爲b, 返回值爲Test, 傳入參數爲空。可是實際上,代碼做者是但願聲明一個類型爲Test,變量名爲b的變量,應該寫成Test b;
, 但程序中這個錯誤在編譯時是檢測不出來的。出錯的是b.fun()
,它是編譯不過去的。函數
例4: 下面程序的打印出的結果是什麼?spa
#include <iostream> using namespace std; class Base { public: Base(int i) : m_j(i), m_i(m_j) {} Base() : m_j(0), m_i(m_j) {} ~Base() {} int get_i() const { return m_i; } int get_j() const { return m_j; } private: int m_i; int m_j; }; int main() { Base obj(98); cout << obj.get_i() << endl; cout << obj.get_j() << endl; return 0; }
本題想要獲得的結果是"98,98"。 可是成員變量的聲明是先m_i, 而後是m_j;初始化列表的初始化變量順序是根據成員變量的聲明順序來執行的, 所以m_i會被賦予一個隨機值。更改一下成員變量的聲明順序能夠獲得預想的結果。 若是要獲得 "98,98"的輸出結果,程序須要修改以下.net
int m_j; int m_i;
補充變量未初始化的值翻譯
#include <iostream> using namespace std; int i; char c; float f; double d; int* p; int main() { cout << i << endl; // 0 cout << c << endl; // '\0' cout << f << endl; // 0 cout << d << endl; // 0 cout << p << endl; // 0 return 0; }
#include <iostream> using namespace std; int main() { int i; char c; float f; double d; int* p; cout << i << endl; cout << c << endl; cout << f << endl; cout << d << endl; cout << p << endl; return 0; }
類的成員變量未初始化的值指針
對於類類型成員按照其自身的默認構造函數進行初始化。
<span style="color:red;">若是類顯式提供了帶參數的構造函數,則編譯器不會再爲其生成空參數的構造函數。這時候就不能用空參數來定義類類型變量</span>
class A{ public: int value; A (int i):value(i){} }; int main(){ A a; // error return 0; }
參考代碼:
#include <iostream> using namespace std; class A { public: int i; float f; double d; char c; int* p; void print() { cout << i << endl; cout << c << endl; cout << f << endl; cout << d << endl; cout << p << endl; cout << "-------------------" << endl; } }; A a0; int main() { a0.print(); A a; a.print(); return 0; }
例5: MFC類庫中,CObject類的重要性不言自明。在CObject的定義中,咱們看到一個有趣的現象,即CObject的析構函數是虛擬的。爲何MFC的編寫者認爲虛擬的析構函數是必要的?
#include <iostream> using namespace std; class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } }; class Derived : public Base { public: Derived() { cout << "Derived" << endl; } ~Derived() { cout << "~Derived" << endl; } }; int main() { Base *b = new Derived(); delete b; return 0; }
咱們將一個父類的指針指向子類的對象,當咱們使用delete b
去釋放b指向的內存,會調用父類的析構函數(靜態聯編),可是卻不會調用子類的析構函數。
若是子類對象在構造函數內申請了一塊堆內存,最後根據上述狀況則會形成內存泄漏,咱們在 ~Base() { cout << "~Base" << endl; }
前加上 virtual
, delete b
會先調用子類析構,再調用父類析構,天然就把子類申請的內存釋放掉啦!
補充知識: C++靜態聯編和動態聯編
根據: https://blog.csdn.net/erlian1... 所修改
聯編是指一個程序自身彼此關聯的一個過程。 按照聯編所進行的階段不一樣,可分爲靜態聯編和動態聯編兩種。
靜態聯編: 靜態聯編是指在程序編譯連接階段進行聯編。這種聯編又稱爲早期聯編,這是由於這種聯編工做是在程序運行以前完成的。
編譯時所進行的聯編又稱爲靜態束定。束定是指肯定所調用的函數與執行該函數代碼之間的關係。 其優勢是效率高,但靈活性差。
下面來看一個靜態聯編的程序例題:
#include <iostream> using namespace std; class Point { public: Point(double i,double j)//基類的構造函數 { x=i;y=j; } double Area() const//定義的常成員函數 { return 0.0; } private: double x,y; }; class Rectangle:public Point//公有繼承的派生類 { public: Rectangle(double i,double j,double k,double l);//聲明派生類的構造函數 double Area() const { return w*h; } private: double w,h; }; Rectangle::Rectangle(double i,double j,double k,double l):Point(i,j)//派生類構造函數的函數體 { w=k; h=l; } void fun(Point &s)//定義的類外函數 { cout<<s.Area()<<endl; } int main() { Rectangle rec(3.5,15.2,5.0,28.0);//定義的派生類的帶參數的對象 fun(rec);//調用函數fun() }
輸出的結果爲:0
程序分析:從輸出的結果來看,該程序執行了Point::Area()的結果。派生類Rectangle的對象rec做爲函數fun()的實參,而該函數的形參是類Point()的對象的引用s。在程序編譯階段,對象引用s所執行的Area()操做被聯編到Point類的函數上。所以在執行函數fun()中的s.Area()操做時,返回值爲0。
經過對象指針進行的普通成員函數的調用,僅僅與指針的類型有關,而與此刻指針正指向什麼對象無關。要想實現當指針指向不一樣對象時執行不一樣的操做,就必須將基類中相應的成員函數定義爲虛函數,進行動態聯編。
動態聯編
動態聯編是指在程序運行時進行的聯編,這種聯編又稱爲晚期聯編。 動態聯編的優勢是靈活性強,但效率低。
動態聯編要求在運行時解決程序中的函數調用與執行該函數代碼間的關係,又稱爲動態束定。在上述的例題中,因爲是靜態聯編,函數fun()中的參數s所引用的對象被聯編到類Point上。那麼實行動態聯編,則s所引用的對象將被聯編到類Rectangle上。因而可知,對於同一個對象的引用,採用不一樣的聯編方式將會被聯編到不一樣類的對象上,即不一樣聯編能夠選擇不一樣的實現,這即是多態性。其實是對於函數fun()的參數的多態性選擇。聯編是一種重要的多態性選擇。
實現動態聯編須要同時知足如下三個條件:
上述的例題能夠改成:
#include <iostream> using namespace std; class Point { public: Point(double i,double j)//基類的構造函數 { x=i;y=j; } virtual double Area() const//定義的虛函數 { return 0.0; } private: double x,y; }; class Rectangle:public Point//公有繼承的派生類 { public: Rectangle(double i,double j,double k,double l);//聲明派生類的構造函數 virtual double Area() const//派生類的虛函數 { return w*h; } private: double w,h; }; Rectangle::Rectangle(double i,double j,double k,double l):Point(i,j)//派生類構造函數的函數體 { w=k; h=l; } void fun(Point &s)//定義的類外函數 { cout<<s.Area()<<endl; } int main() { Rectangle rec(3.5,15.2,5.0,28.0);//定義的派生類的帶參數的對象 fun(rec);//調用函數fun() }
輸出的結果爲:140.
程序分析:該程序中說明了虛函數,fun()函數的對象引用參數s被動態聯編,該函數體內調用的Area()函數是在運行中肯定的,而不是在編譯時肯定的,所以在運行時,實參rec爲類rectangle的對象,因而Area()函數被肯定爲類Rectangle的Area()函數。
例6: 析構函數能夠爲virtual型,構造函數則不能。那麼爲何構造函數不能爲虛呢?
虛函數採用一種虛調用的辦法。虛調用是一種能夠在只有部分信息的狀況下工做的機制,特別容許咱們調用一個只知道接口而不知道其準確對象類型的函數。可是若是要建立一個對象,你勢必要知道對象的準確類型,所以構造函數不能爲虛。
例7: 若是虛函數是很是有效的,咱們是否能夠把每一個函數都聲明爲虛函數?
不行,這是由於虛函數是有代價的:因爲每一個虛函數的對象都必須維護一個虛表,所以在使用虛函數的時候都會產生個系統開銷。若是僅是個很小的類,且不想派生其餘
類,那麼根本不必使用虛函數。
例8: 析構函數能夠是內聯函數嗎?
析構函數能夠是內聯函數。
例9: 重載和覆蓋有什麼不一樣?
override是指派生類重寫基類的虛函數, overload約定成俗地被翻譯爲 「重載",是指編寫一個與已有函數同名,可是參數表不一樣的函數。 例如一個函數既能夠接收整型數做爲參數,也能夠接收浮點數做爲參數。 重載不是一 種面向對象的編程, 而只是一種語法規則, 重載與多態沒有什麼直接關係。
例10: 在C++中, 你不該該從如下哪一個拋出異常?
A. Constructor (構造函數) B. Destructor (析構函數) C. Virtual function (虛方法) D. None of the above (以上答案都不對)
構造函數中拋出異常是有必定必要的,試想以下狀況:
構造函數中 有兩次new操做,第一次成功了,返回了有效的內存,而第二次失敗,此時由於對象構造還沒有完成,析構函數是不會調用的,也就是delete語句沒有被執行,第一次new出的內存就懸在那兒了(發生內存泄露), 因此異常處理程序能夠將其暴露出來。
構造函數中遇到異常是不會調用析構函數的, 一個對象的父對象的構造函數執行完畢,不能稱之爲構造完成,對象構造是不可分割的,要麼徹底成功, 要麼徹底失敗,C++保證這一點。 對於成員變量,C++遵循這樣的規則,即會從異常的發生點按照成員變量的初始化的逆序釋放成員。 舉例來講,有以下初始化列表:
A:: A() : ml(), m2(), m3(), m4(), m5() () {};
假定m3的初始化過程當中拋出異常,則會按照m2,ml的順序調用這兩個成員的析構函數。 在{}之間發生的未捕捉異常,最終會致使在棧的開解時析構全部的數據成員。
處理這樣的問題, 使用智能指針是最好的, 這是由於auto_ptr
成員是一個對象而不是指針。 換句話說,只要不使用原始的指針,那麼就沒必要擔憂構造函數拋出異常而致使資源泄露。
因此在C++中,資源泄露的問題通常都用RAII(資源獲取即初始化)的辦法:把須要打開/關閉的資源用簡單的對象封裝起來(這種封裝能夠同時有多種用處,好比隱藏底層API細節,以利於移植)。 這能夠省去不少的麻煩。
若是不用RAII,即便當前構造函數裏獲取的東西在析構函數裏都釋放了,若是某天對類有改動,要新增長一種資源,構造函數裏通常能適當地獲取,但記不記得要在析構函數裏相應地釋放呢?失誤的比例很大。 若是考慮到構造函數裏拋出異常,就更復雜了。 隨着項目的不斷擴大和時間的推移,這些細節不可能都記得住,並且,有可能會由別人來實施這樣的改動。
析構函數拋異常
Effective C++建議,析構函數儘量地不要拋出異常。 設想若是對象出了異常, 如今異常處理模塊爲了維護系統對象數據的一致性,避免資源泄漏,有責任釋放這個對象的資源,調用對象的析構函數,可如今假如析構過程又再出現異常,那麼請問由誰來保證這個對象的資源釋放呢?並且這新出現的異常又由誰來處理呢?不要忘記前面的一個異常目前都尚未處理結束,所以陷入了一個矛盾之中,或者說處於無限的遞歸嵌套之中。 因此C++標準就作出了這種假設。