1. 程式人生 > 其它 >《C++ primer》(第5版) chapter6 讀書筆記

《C++ primer》(第5版) chapter6 讀書筆記

技術標籤:C++c++指標

文章目錄

函式基礎

  • 一個典型的函式定義包括以下部分:返回型別函式名稱由0個或多個形參組成的列表以及函式體
  • 函式的呼叫完成兩項工作:
    • 一是用實參初始化函式對應的形參
    • 二是將控制權轉移給被呼叫函式,此時主調函式的執行被暫時中斷,被調函式開始執行
  • return語句也完成兩項工作:
    • 一是返回return語句中的值(如果有的話)
    • 二是將控制權從被調函式轉移回主調函式
  • 區域性靜態物件在程式的執行路徑第一次經過物件定義語句時初始化,並且直到程式終止才被銷燬,在此期間即使物件所在的函式結束執行也不會對它有影響
  • 如果區域性靜態變數沒有顯示的初始值,它將執行值初始化,內建型別的區域性靜態變數初始化為0
  • 和其他名字一樣,函式的名字也必須在使用之前宣告
    • 函式的宣告和函式的定義非常類似,唯一的區別是函式宣告無需函式體,用一個分號替代即可
    • 因為函式的宣告不包含函式體,所以就無需形參的名字
    • 函式只能定義一次,但可以宣告多次
  • 建議在標頭檔案中宣告變數和函式,在原始檔中定義變數和函式
    • 定義函式的原始檔應該把含有函式宣告的標頭檔案包含進來,編譯器負責驗證函式的定義和宣告是否匹配

引數傳遞

  • 當形參是引用型別時,它對應的實參是被引用傳遞或者函式被傳引用呼叫. 當實參的值被拷貝給形參時,形參和實參是兩個相互獨立的物件,這樣的實參被值傳遞
    或者函式被傳值呼叫
  • 如果函式無需改變引用形參的值,最好將其宣告為常量引用
  • 當用實參初始化形參時會忽略掉頂層const;同時,當形參有頂層const時,傳給它常量物件或者非常量物件都是可以的
  • 因為陣列會被轉換為指標,所以當我們為函式傳遞一個數組時,實際上傳遞的是指向陣列首元素的指標
  • int main(int argc,char *argv[]):第一個形參argc表示陣列中字串的數量,第二個形參argv是一個數組,它的第一個元素指向程式的名字,接下來的元素依次傳遞命令列提供的實參,最後一個指標之後的元素值保證為0
    • 當使用argv中的實參時,一定要記得可選的實參從argv[1]開始;argv[0]儲存程式的名字,而非使用者輸入
  • 為了編寫能處理不同數量實參的函式,c++11新標準提供了兩種主要的方法:
    • 一是如果所有的實參型別相同,可以傳遞一個名為initializer_list的標準庫型別(initializer_list提供的操作見例子四)
    • 二是如果實參的型別不同,可以編寫一種特殊的函式,也就是所謂的可變引數模板,關於它的細節將在16章介紹
  • initializer_lsit物件中的元素永遠是常量值,我們無法改變其中的元素值
    • 在範圍for迴圈中使用initializer_list物件時,應該使用常量引用型別
//例子一:const形參和實參
//這種函式過載的形式是錯誤的,因為第二個fcn實際上與第一個沒有區別
void fcn(const int i)
void fcn(int i)

//例子二:陣列形參
//儘管形式不同,但這三個print引數是等價的
void print(const int*);
void print(const int[]);
void print(const int[10]);          //這裡的維度表示我們期望陣列含有多少元素,實際不一定

//例子三:管理指標形參的三種技術

//使用標記指定陣列長度
void print(const char *cp){
    if(cp)
        while(*cp)  //處理c風格字串遇到空字元停止
            cout<<*cp++;
}

//使用標準庫規範
void print(const int *begin,const int *end){
    while(beg!=end)
        cout<<*beg++;
}

//顯式傳遞一個表示陣列大小的形參
void print(const int ia[],size_t size){
    //size表示陣列的大小,將它顯式地傳遞給函式用於控制對ia元素的訪問
    for(size_t i=0;i!=size;++i)
        cout<<ia[i];
}

//例子四:initializer_list提供的操作
initializer_list<T> lst;            //預設初始化,T型別元素的空列表
initializer_lsit<T> lst{a,b,c...};  //lst的元素數量和初始值一樣多
lst2(lst);                          //拷貝lst給lst2
lst2=lst;                           //拷貝lst給lst2
lst.size();                         //類別中的元素數量
lst.begin();                        //返回指向lst中首元素的指標
lst.end();                          //返回指向lst中尾元素下一位置的指標

返回型別和return語句

  • 在含有return語句的迴圈後面應該也有一條return語句,如果沒有的話該程式就是錯誤的,很多編譯器都無法發現此類錯誤
  • 永遠不要返回區域性物件的引用或指標
  • 呼叫一個返回引用的函式得到左值,其他返回型別得到右值
  • main函式的返回值可以看做是狀態指示器:返回0表示執行成功,返回其他值表示執行失敗,其中非0值的具體含義依機器而定
    • main函式不能呼叫它自己
  • 因為陣列不能被拷貝,所以函式不能返回陣列,不過函式可以返回陣列的指標或引用(見例子一)
//例子一:返回陣列指標

//方式一
typedef int arrT[10];
using arrT=int[10];

arrT* func(int i);                  //func返回一個指向含有10個整數的陣列的指標

//方式二
//Type (*function(parameter_list))[demension]     !!!!!! 宣告一個返回陣列指標的函式 !!!!!!!
int (*func(int i))[10];             //宣告一個返回指向10個整數的陣列的指標的函式

//方式三
auto func(int i) -> int(*) [10];            //使用尾置返回型別宣告

//方式四
int odd[]={1,3,5,7,9};
int even[]={0,2,4,6,8};
//返回一個指標,該指標指向含有5個整數的陣列
//decltype並不負責把陣列型別轉換成對應的指標,所以decltype的結果是個陣列,所以在函式宣告時還需加一個*fuhao
decltype(odd) *arrPtr(int i)
{
    return (i%2)?&odd;&even;
}

函式過載

  • 對於過載的函式來說,它們應該在形引數量或形參型別上有所不同
    • 不允許兩個函式除了返回型別外其他所有的要素都相同
    • 一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來(見例子一)
    • 擁有底層const的形參可以和沒有底層const的形參區分開來(見例子二)
  • 如果我們在內層作用域中宣告名字(包括變數,函式名等),它將隱藏外層作用域中宣告的同名實體
//例子一:頂層const形參無法區分
Record lookup(Phone);
Record lookup(const Phone);         //報錯:無法過載

Record lookup(Phone*);
Record lookup(Phone *const);        //報錯:無法過載

//例子二:底層const形參可以區分
Record lookup(Account&);
Record lookup(const Account&);        //正確:新函式

Record lookup(Account*);
Record lookup(const Account*);        //正確:新函式

特殊用途語言特性

  • 一旦某個形參被賦予了預設值,它後面的所有形參都必須有預設值
    • 當設計含有預設實參的函式時,其中一項任務就是合理設定形參的順序,儘量讓不怎麼使用預設值的形參出現在前面,而讓那些經常使用預設值的形參出現在後面
  • 內聯說明只是向編譯器發出的一個請求,編譯器可以選擇忽略這個請求
  • assert是一種預處理巨集,定義在cassert標頭檔案中,assert(expr)首先對expr求值,如果表示式為假,assert輸出資訊並終止程式的執行.如果表示式為真,assert什麼也不做
  • assert的行為依賴於一個名為NDEBUG的預處理變數的狀態。如果定義了NDEBUG,則assert什麼也不做。預設狀態下沒有定義NDEBUG,此時assert將執行執行時檢查
    • 除了用於assert外,也可以使用NDEBUG編寫自己的條件除錯程式碼(見例子一)
//例子一:使用NDEBUG編寫條件除錯程式碼
/*
除了c++定義幾個對除錯有用的名字:
    __func__:輸出當前除錯的函式的名字
    __FILE__:存放檔名的字串字面值
    __LINE__:存放當前行號的整型字面值
    __TIME__:存放檔案編譯時間的字串字面值
    __DATE__:存放檔案編譯日期的字串字面值
*/

if(word.size()<threshold)
    cerr<<"Error:"<<__FILE__
        <<" : in function "<<__func__
        <<" at line "<<__LINE__
        <<" Compliled on "<<__DATE__
        <<" at "<<__TIME__<<endl
        <<" Word read was \""<<word
        <<"\":Length too shoart "<<endl;

函式匹配

  • 函式匹配有三步:確定候選函式->從候選函式中確定可行函式->從可行函式中尋找最佳匹配(如果有的話)
    • 候選函式具有兩個特徵:一是與被呼叫的函式同名,二是其宣告在呼叫點可見
    • 可行函式也有兩個特徵:一是其形引數量與本次呼叫提供的實引數量相同,二是每個實參的型別與對應的形參型別吸納共同,或者能轉換成形參的型別
  • 為了確定最佳匹配,編譯器將實參型別到形參型別的轉換劃分成幾個等級,具體排序如下所示(類型別轉換等級最低)
    1. 精確匹配,包括以下情況:
      • 實參型別和形參型別相同
      • 實參從陣列型別或函式型別轉換成對應的指標型別
      • 向實參新增頂層const或從實參中刪除頂層const
    2. 通過const轉換實現的匹配
    3. 通過型別提升實現的匹配
    4. 通過算術型別轉換或指標轉換實現的匹配
    5. 通過類型別轉換實現的匹配
  • 如果有且只有一個函式滿足下列條件(即“最匹配”),則匹配成功,否則編譯器將報告二義性呼叫的資訊:
    • 該函式每個實參的匹配都不劣於其他可行函式需要的匹配
    • 至少有一個實參的匹配優於其他可行函式提供的匹配

函式指標

  • 函式指標指向的是函式而非物件,函式指標指向某種特定型別,函式的型別由它的返回型別和形參型別共同決定,與函式名無關(見例子一)
    • 使用函式指標作為形參時,可以使用函式指標或函式名(見例子二)
    • 函式型別和函式指標的宣告需要特別注意(見例子三)
    • 當定義一個返回指向函式型別的指標的函式時,我們必須把返回型別寫成指標形式,編譯器不會自動地將函式返回型別當成對應的指標型別處理(見例子四)
//例子一:函式指標
bool (*pf)(const string&,const string&);            //未初始化的函式指標

//例子二:函式指標作為形參
void useBigger(const string &s1,const string &s2,
                bool pf(const string&,const string&));
//等價的宣告:顯示地將形參定義成指向函式的指標
void useBigger(const string &s1,const string &s2,
                bool (*pf)(const string&,const string&));

//例子三

//Func是函式型別
typedef bool Func(const string&,const string&);
//FuncP是指向函式的指標
typedef bool(*FuncP)(cosnt string&,const string&);

//例子四:返回指向函式的指標
//和返回指向陣列的指標的函式的宣告一樣,這裡也有四種方式宣告返回指向函式的指標的函式

//方式一
using F=int(int*,int);          //F是函式型別,不是指標
using PF=int(*)(int*,int);      //PF是指標型別

PF f1(int);                     //正確
F f1(int);                      //錯誤:F是函式型別,f1不能返回一個函式
F *f1(int);                     //正確:顯示地指定返回型別是指向函式的指標

//方式二

int (*f1(int))(int*,int);


//方式三

auto f1(int) -> int(*)(int*,int);

//方式四
string::size_type sumLength(const string&,const string&);
decltype(sumLength) *getFcn(const string&);     //這裡decltype得到的函式型別,所以需要顯示地指定返回型別是指向函式的指標