初始化列表(包括成員對象初始化)html
初始化列表 ( 推薦 ) :
能夠初始化任何類型的數據, 不論是不是普通類型仍是對象,都建議用.
再也不須要在構造器中賦值了, 並且初始化列表比構造函數要早執行.
成員初始化次序取決於成員在類中的聲明次序.
當類成員有其它對象時,構造器內給對象賦值會觸發成員對象的默認構造函數(無參數的),若是成員對象沒有默認構造函數編譯報錯.
因此有成員變量爲對象這種場景下,要用 initializer list.git
Source:https://github.com/farwish/unix-lab/blob/master/cpp/Initializer_list.ccgithub
繼承安全
複用的一種方式,還有上面介紹過的 "對象組合"(成員變量爲其餘對象)ide
私有屬性只能由父類本身訪問;受保護的屬性能夠由子類訪問,別人都沒法訪問.函數
當實例化子類時,會先調用父類的構造函數,當父類沒有默認構造函數時又沒有初始化本身的構造函數時,編譯報相似 "no matching function AA::AA( )",因此在子類中只能用 initializer list 對父類成員初始化.ui
析構的調用次序則反過來,先子類後父類.this
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Extends.ccspa
函數重載(Function overload)和默認參數(Default argument)設計
同名函數經過擁有不一樣的參數表實現重載. void print( ); void print ( int i )
默認參數是在頭文件中給原型的默認參數值,惟一的好處是某些狀況下少打字;可是在調用時容易形成閱讀困難,另外也不安全,若是咱們不 include 頭文件而是本身寫一個函數聲明,把默認參數值設爲其它的,那麼就和設計者的意圖不同。因此建議不使用 Default argument 如 void f (int i , int j = 10);
內聯函數(Inline functions)
當函數前面有 inline 時,它就是一個 declaration,而再也不是 defination,所以不須要擔憂重複定義的問題。
內聯函數的 body 放在頭文件裏就能夠了,不須要 .cpp 文件,和傳統的一個 .h 對應一個 .cpp 不一樣。
由於內聯函數有類型檢查,所以比作一樣事情的宏要好。
( 使用場合:函數只有2~3行的,須要重複調用的;不適合的:函數比較大,遞歸 )
成員函數在 class 聲明時若是給出了 body,那麼這些都是 inline 函數,只要有一個頭文件就夠了。
另外一種寫法是保持 class 聲明乾淨,而爲單獨實現的成員函數前面加 inline.
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Inline.h
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Inline_main.cc
const; 不可修改的對象(對象成員)
成員函數 const 的用法:
在聲明和定義的地方要一塊兒用.
int getData( ) const;
int getData( ) const { return data }
不修改數據的成員函數應該被定義爲 const.
若是類有 const 成員變量 或者 實例一個 const 對象,那麼必定要在 initialize list 裏面初始化變量,不然編譯沒法經過,由於後面沒法修改它 (成員變量)。
func( ) { } 和 func( ) const { } 是不同的,它們構成重載( overload ),由於它們至關因而 func( A* this ) 與 func( const A* this ),參數表不同。
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Const_class.cc
引用(C++數據類型)
char c; char* p = &c; char& r = c;
本地變量或全局變量,必須有初始值,type& name = 'name'
int x = 3;
int& y = x; # 賦初值
const int& z = x; # z 不能作左值,可是能夠經過修改 x 來修改 z
做爲參數和成員變量時,能夠沒有初始值,由於它們會在構造對象時被調用者初始化,type& name;
void f ( int& x )
f ( y ); # 在函數調用時初始化
指針和引用的區別:
引用不能爲 null. 指針能夠爲 null.
引用依賴另外一個變量,是一個變量的別名. 指針獨立於已存在的對象.
引用不能指向一個新的地址. 指針能夠更改指向不一樣的地址.
cpp內存模型的複雜性體如今:三個地方放對象(堆,棧,全局數據區),訪問對象的方式(變量放對象,指針訪問,引用訪問)。
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Reference.cc
引用再研究
引用做爲類的成員時,聲明時沒有辦法給初始值,由於它須要和另一個變量捆綁在一塊兒,做爲別名;因此必須在構造函數的 initializer list 裏初始化。
函數能夠返回一個引用,但不能引用本地變量。
參數前的 const 的引用,const 保證不被修改,引用使傳參高效,好處是函數中不用使用 * 號。
參數傳引用,這說明參數是一個能夠作左值的東西,傳參不能使用變量非const的表達式。
void func(int &); func(i * 3); // error:invalid initialization of non-const reference of type 'int&' from a temporary of type 'int' error: in passing argument 1 of 'void f (int &)'
void func(const int&); int i = 3; func(i * 3); // 區別僅在於參數是const的,正確輸出9
不能對函數返回的對象作左值,編譯會報錯,error: using temporary as lvalue [-fpermissive]。
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Reference_2.cc
向上造型(Upcasting)
子類的對象當作父類的對象來看,叫作向上造型,由於通常習慣把父類畫在上面;Upcasting 必定是安全的,最多子類擁有的被無視。
父類的對象當作子類的對象看,叫作向下造型,Downcasting 有風險,由於父類不必定擁有子類的東西。
類型轉換和造型的區別,類型轉換原來的值轉換完就變了,而造型數據沒變,子類的對象仍是子類的對象,只是看待的眼光不同。
Persion John('JOHN');
Animal* p = &John; // Upcast, 由於Person是Animal的一種, 但反過來就是 Downcast
Animal& q = John; // Upcast
多態性(polymorphism):Upcast 和 Dynamic binding 兩個條件構成多態性
Upcast: 把派生類當作基類使用。
Dynamic binding: 調用對象的函數。
(Static binding: 調用代碼寫明的函數)
/**
* 通用函數,對任何 Shape 和其子類都通用.
*
* 動態綁定,調用的 render 在運行時決定:
* p 有一個靜態類型和動態類型,若是 p 的 render 函數是 virtual 的,那麼是動態綁定,不是 virtual 則是靜態綁定。
* 因此動態綁定仍是靜態綁定取決於 render 函數,而不是對象 p;若是咱們調用的是 move 函數,那麼就是靜態綁定。
*/
void render(Shape* p)
{
p->render(); }
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Polymorphism.cc
虛 析構函數
Shape的析構不是 virtual 時,默認是靜態綁定,delete p 時,只有 Shape 的析構會被調用,Ellipse 的不會調用。
Shape的析構是 virtual 時,表示動態綁定,delete p 時,會先調子類的析構,在調父類的析構。
Shape* p = new Ellipse(100.0, 110.0);
delete p;
其它 OOP 語言默認就是 virtual 的,也就是動態綁定的,而C++默認是靜態綁定的,動態綁定須要手動加 virtual。
若是一個類裏有一個 virtual 函數,它的析構函數就必須是 virtual 的。
若是父類和子類有名字相同、參數表相同的 virtual 函數,那麼子類成員函數就對父類構成了重寫/覆蓋。
子類成員函數中調用父類的同名函數用 Base::func( ) 的方式。
父類裏有兩個 virtual 的重載(overload)函數,那麼子類裏也要實現兩個 overloaded 的函數,不然另外一個函數會發生 name hidden,只有 C++ 會發生函數的隱藏。
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Polymorphism.cc
拷貝構造
拷貝構造的惟一形式:T::T(const T&)
拷貝構造何時被調用?
1.用對象進行初始化時,Person p = p1 或 Person p(p1),這兩種寫法相同,注意它不是 assignment 而是 initialization (由於變量前有類型)。
2.調用一個函數,函數的參數是一個對象時,void func(Person p);
3.用返回對象的函數返回值進行初始化。
Construction vs Assignment
每一個對象只能構造一次,每一個對象應該被析構一次,
對象一旦被構造,它能夠是被賦值的目標,前頭有類型就是 initialization,沒有類型就是 assignment.
Copy constructor guidelines
寫一個類就先寫三個函數 default constructor, virtual distructor, copy constructor。
若是確實不須要拷貝構造,那麼就聲明爲私有,不建議這麼作,限制了不少事不能作。
Source: https://github.com/farwish/unix-lab/blob/master/cpp/Copy_constructor.cc
靜態對象
static兩個基本的含義:
靜態存儲,本地變量是static,這個本地變量具備持久存儲(事實上static的本地變量就是全局變量)。
名字可見性,全局變量、函數的static,那麼這個全局變量、函數只在當前文件中可用。
static 在 C++ 中的使用:
靜態本地變量 - 持久存儲。
靜態成員變量 - 全部對象間共享。
靜態成員函數 - 全部對象間共享,它只能訪問靜態成員變量。
對象是靜態的 - 除了遵照兩個基本法則(存儲、可見性),保證只構造析構一次。
靜態初始化的依賴
多個 cpp 文件都有本身的全局變量的狀況,沒人保證初始化順序前後;
若是一個變量的初始化依賴另外一個變量的值做爲參數,那麼須要先初始化那另外一個變量,可是跨文件的初始化是不存在的。因此解決方案是,1. 別這麼幹。 2. 邏輯上許可的話,把全部有依賴的全局變量放到一個地方。
Source:https://github.com/farwish/unix-lab/blob/master/cpp/Static_members.cc
運算符重載(Overloading Operators) - 基本規則
運算符容許經過本身定義 function 來重載。
只有已存在的運算符能夠被重載,不能對類和枚舉重載,必須保持操做數個數(如加法須要兩個操做數),必須保證優先級。
使用 operator 關鍵字做爲函數名字,如重載 * 號,就是 operator * (...)
能夠做爲成員函數,const String String::operator + (const String& that); 須要兩個操做數,由於有默認參數 this 做爲第一個,因此再只須要一個。
能夠是全局函數,const String operator + (const String& r, const String& l); 這時參數表須要兩個參數。
Integer x(1), y(5), z;
x + y; ====> x.operator+(y); 這裏的 x 就是receiver,receiver 決定 operator 用哪一個。
z = x + y; 能夠。
z = x + 3; 能夠。
z = 3 + y; 編譯通不過。
Tips:作成成員函數仍是函數?
單目的運算符應該作成是成員的,但非強制。
= ( ) [] -> ->* 必須是成員的。
賦值運算符應該作成是成員的。
全部其它二元操做符做爲非成員的。
原型
參數傳遞:若是是隻讀的,那麼傳 const 的「引用」,不修改算子的成員函數加 const,全局函數可能兩個都加或者又一個不加 const。
返回值:1.決定了是對本身進行了修改仍是返回了新對象; 2.製造出的新對象是否是能夠作左值;
運算符原型