C++匿名物件與建構函式
前言: 不得不承認,我是一個內心比較躁動的人。如果沒有一個純粹的學習環境,大部分時間我很難靜下心來,但是我還要說,學習使我感到快樂。對我而言,大部分埋頭啃書的時間都是枯燥乏味的,但是總有那麼一些恍然大悟的瞬間,一些雲開月明的瞬間,讓我興奮不已,那種無比充實而快樂的感覺,真好! 但是,學到的知識就像捧在手裡的雪球,如果不把它攥實或者變大,就會很容易的化掉,最終只剩手心的一點水漬。我曾經無數次想記錄下一些學習上的心得與體會,但是最終都沒有做到。希望這篇文章是一個轉折點。
1,匿名物件 為什麼在這篇文章要把匿名物件和建構函式放一起?如果你覺得他們之間沒有任何關係,那我覺得你一定要看一下下邊的內容。我們先來看一下C++匿名物件的語法。
//這是本文的示例類,下文只談結果,執行效果省略。
class Person {
public:
Person() {
cout << "no param constructor!" << endl;
mAge = 0;
}
Person(int age) {
cout << "1 param constructor!" << endl;
mAge = age;
}
Person(const Person& person) {
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
void PrintPerson() {
cout << "Age:" << mAge << endl;
}
~Person() {
cout << "解構函式已呼叫" << endl;
}
private:
int mAge;
};
1.1,匿名物件的構造和析構:
Person p1; //1,預設構造
Person(); //2,預設構造
Person(100); //3,有參構造
Person(Person(1)); //4,有參構造
Person(Person(p1)); //5,拷貝構造
Person(p1); //6,error,p1重定義
//匿名物件可以呼叫類方法
Person(100).PrintPerson();
從1,2,3行我們很容易發現,匿名函式當代碼執行走完它所在的那一行,就呼叫解構函式釋放掉,這是因為這個物件是匿名了,我們後續的操作中已經無法使用這個物件,所以編譯器(走完 ;這個序列點之後)就自動幫我們釋放掉了。
然而 4,5行是什麼操作?可能好多人都以為 4 是先呼叫有參構造,再呼叫拷貝構造,而 5 行應該是呼叫了兩次拷貝構造,如果你也感覺是這樣,那麼你就非常有必要看一看後邊的內容了。
建構函式的語法相當複雜,我覺得稱之為智障也不為過,雖然這只是開始。我們如果嘗試這寫一些很複雜的巢狀層級很多的程式碼,你會發現這裡會和指標一樣很容易把人繞懵,所以我們只討論有實用意義的語法。
上邊的4,5行疑問暫時保留,這裡先解釋一下6行那裡的錯誤:
易錯點1: b為A的例項化物件,A a = A(b) 和 A(b)的區別? 當A(b)有變數來接的時候,那麼編譯器認為他是一個匿名物件,當沒有變數來接的時候,編譯器認為你A(b) 等價於 A b,所以就造成了重定義錯誤。
2,預設構造和拷貝構造 因為這是兩種相對簡單的建構函式,我們把他們拿到一起來說。
2.1,預設構造
Person P1; //1,預設構造 (隱式呼叫)
//預設構造不存在 括號法 呼叫
Person P1(); //2,error,編譯器會認為這是一個函式宣告
Person P1 = Person();//3,預設構造(顯式呼叫)
是不是感覺第3行和上邊示例中的4,5行似曾相識,為啥之呼叫了預設構造,應該是 = 右邊呼叫依次無參構造,= 左邊呼叫一次拷貝構造才對啊,事實並不是這樣的。 結合上文,我們不妨做出猜想: 猜想1:
A:匿名物件是建構函式的一個橋樑 B:當匿名物件出現在 = 號右邊時會被左邊的物件接管,這個接管會省去基於 = 號 的拷貝構造。 C:當匿名物件出現在 = 左邊,就會很快被釋放掉。
易錯點2:
2.2,拷貝構造 Person P1() 是一個錯誤的寫法,編譯器會認為這是一個返回值為Person型別,函式名為 P1 的函式宣告。
Person p1;
Person p2(p1); //1,括號法 (隱式呼叫)最常用
Person p2 = p1; //2,等號法 (隱式呼叫)隱式轉換
Person p2 = Person(p1); //3,匿名法 (顯式呼叫)
又沒又發現一個簡單的拷貝構造可以寫出來這麼多花樣來,哈哈。還有就是第3行只調用了一次拷貝構造,不是大家想的兩次拷貝構造( = 號左右兩邊各一次)。 這裡是不是已經驗證了我們的猜想?現在我們需要做的就是把所有的形式都分析一下,看一看我們猜想能不能在每種情況下站得住腳。如果能,那我們就記住它!
2.3,顯式呼叫和隱式呼叫 然後我們在這裡講一下顯式呼叫和隱式呼叫。我們可以看到顯式呼叫只有一種,大家都知道建構函式名和類名一樣,匿名法也就是我們說的顯式呼叫法,這裡驗證了匿名物件作為建構函式的一個橋樑。 猜想2: D:顯式呼叫其實就是在類外呼叫了類的建構函式 E:如果等號右邊的匿名物件是一個非物件引數構造,呼叫了有參構造 F:如果右邊的匿名物件是一個物件,呼叫了拷貝構造。
還有就是為什麼隱式呼叫有兩種?一個括號法,一個等號法,其實只有當一個類的建構函式只需要一個傳參的時候(可以又其他有預設值的引數)才會有等號法。等號法的本質,C++語法上,更喜歡稱之為隱式轉換,這個我們後續文章會詳細介紹。
在這裡我要扯一些閒話,如果你是編譯器,你更喜歡顯式呼叫還是隱式呼叫?我覺得大部分人看到顯式呼叫會更容易一點,也就是說隱式呼叫最終會被解析為顯式呼叫。這裡可能是語法層次上的一些優化,隱式呼叫的優勢在於程式碼更緊湊一些,更有利於程式設計師的工作效率。
3.有參構造
Person p1(100); //(隱式呼叫)最常用
Person p1 = Person(100);//匿名法 (顯式呼叫)
Person p1 = 100; //等號法 (隱式呼叫)隱式轉換
//相信我,你老闆或者同時看到你寫下邊這種程式碼,一定會削你的!
Person p1(Person(100)); //等價於 Person p1 = Person(100);
這裡再次驗證了我們上邊的猜想,所以我們最後再來熟悉一下上邊的每條猜想: 猜想1:
A:匿名物件是建構函式的一個橋樑 B:當匿名物件出現在等號右邊時會被左邊的物件接管,會省去基於等號的拷貝構造。 C:當匿名物件出現在 = 左邊,就會很快被釋放掉。
猜想2:
D:顯式呼叫其實就是在類外呼叫了類的建構函式 E:如果等號右邊的匿名物件是由一個非物件引數構造,呼叫了有參構造 F:如果右邊的匿名物件是由一個物件構造,呼叫了拷貝構造。