《C++Primer》讀書筆記(六)函數

函數基礎

  • 我們用調用運算符來執行函數,它是一對圓括號,它作用於一個表達式,該表達式是函數或者指向函數的指針;括號之內是一個用逗號隔開的實參列表

(1)編寫函數

int fact(int val){
    int ret=1;
    while (val>1)
        ret*=val--;
    return ret;
}

(2)調用函數

int main(){
    int j=fact(5);
    cout<<"5! is "<<j<<endl;
    return 0;
}

函數的調用完成兩項工作:一是實參初始化函數對應的形參,二是將控制權轉移給被調用函數

(3)形參和實參

(4)函數的形參列表

(5)函數的返回類型

局部對象

在C++中,名字有作用域,對象有生命週期

  • 名字的作用域是程序文本的一部分,名字在其中可見
  • 對象的生命週期是程序執行中該對象存在的一段時間

(1)自動對象

(2)局部靜態對象

// 統計函數它自己被調用了多少次
size_t count_calls(){
    static size_t ctr=0;
    return ++ctr;
}
int main(){
    for(size_t i=0;i!=10;++i)
        cout<<count_calls()<<endl;
    retunr 0;
}
  • 在控制流第一次經過ctr的定義之前,ctr被創建並初始化爲0。
  • 每次調用將ctr加1並返回新值
  • 每次執行函數時,變量ctr的值都已經存在並且等於函數上一次退出時ctr的值

函數聲明

void print(vector<int>::const_iterator beg,
            vector<int>::const_iterator end);
  • 函數的三要素(返回類型、函數名、形參列表)描述了函數的接口,說明了調用該函數所需的全部信息。函數聲明也稱作函數原型

(1)在頭文件中進行函數聲明

分離式編譯

(1)編譯和鏈接多個源文件

參數傳遞

  • 但形參是引用類型時,我們說它對應的實參被引用傳遞或者函數被傳引用調用
  • 當實參被拷貝給形參時,形參和實參是兩個獨立的對象

傳值參數

(1)指針形參
- 當執行指針拷貝操作時,拷貝的是指針的值。拷貝之後,兩者爲不同的指針
- 因爲指針使我們可以間接地訪問它所指的對象,所以通過指針可以修改它所指對象的值

int n=0,i=42;
int *p=&n,*q=&i; //p指向n;q指向i
*p=42; //n的值改變;p不變
p=q; //p現在指向了i;但是i和n的值不變
  • 指針形參的行爲與之相似
void reset(int *ip){
    *ip=0; // 改變指針ip所指對象的值
    ip=0; // 只改變了ip的局部拷貝,實參並未改變
}
  • 調用該函數需要傳遞對象的地址
    熟悉C的程序員經常使用指針類型的形參來訪問函數外部的對象。在C++語言中,建議使用引用類型的形參替代指針

傳引用參數

// 該函數接受一個int對象的引用,然後將對象的值置爲0
void reset(int &i){  // i是傳給reset函數的對象的另一個名字
    i=0;  // 改變了i所引對象的值
}
  • 調用該函數時,我們直接傳入對象,而無須傳入對象地址

(1)使用引用避免拷貝

如果函數無須改變引用的形參的值,最好將其聲明爲常量引用

// 比較兩個string對象的長度
bool isShorter(const string &s1,const string &s2){
    return s1.size()<s2.size();
}

(2)使用引用形參返回額外信息
- 但我們需要獲得兩個或以上的返回數據時,函數只能有一個返回值,有兩種辦法
- 定義一個新的數據類型,讓它包含不同的成員,作爲返回值
- 給函數傳入一個額外的應用實參,令其保存另外的返回數據

// 返回s中c第一次出現的位置索引
// 引用形參occurs負責統計c出現的次數
string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
    auto ret=s.size();
    occurs=0; //設置表示出現次數的形參的值
    for(decltype(ret) i=0; i!=s.size() ; ++i){
        if(s[i]==c){
            if(ret==s.size()){
                ret=i; //記錄第一次出現的位置
            }
            ++occurs;
        }
    }
    return ret;
}

const形參和實參

  • 關於頂層const作用於對象本身:
const int ci=42; //不能改變ci,const時頂層的
int i=ci; //正確:當拷貝ci時,忽略了它的頂層const
int * const p=&i; //const是頂層的,不能給p賦值
*p=0; //正確:通過p改變對象的內容是允許的,現在i變成了0
  • 當用實參初始化形參時會忽略掉頂層const
  • 即當形參有頂層const時,傳給它常量對象或者非常量對象都是可以的
void fcn(const int i){
    // fcn能夠讀取i,但是不能向i寫值
}

(1)指針或引用形參與const
- 形參的初始化方式和變量的初始化方式時一樣的
- 我們可以使用非常量初始化一個底層const對象,當反過來不行
- 一個普通的引用必須用同類型對象來初始化

int i =42;
const int *cp=&i; //正確:但是cp不能改變i
const int &r=i; //正確:但是r不能改變i

const int &r2=42; //正確

int *p=cp; //錯誤:p的類型與c的類型不匹配
int &r3=r; //錯誤:r3的類型和r的類型不匹配
int &r4=r2; //錯誤:不能用字面值初始化一個非常量引用

(2)儘量使用常量引用

數組形參

  • 數組的兩個特殊性質對我們定義和使用作用在數組上的函數又影響,分別是:
    • 不允許拷貝數組
    • 使用數組會將其轉換成指針
  • 儘管不能以值傳遞的方式傳遞數組,當可以把形參寫成類似數組的形式
void print(const int*);
void print(const int[]);
void print(const int[10]);

和其他使用數組的代碼一樣,以數組作爲形參的函數也必須確保使用數組時不會越界

  • 因爲數組是以指針的形式傳遞給函數的,所以一開始函數並不知道數組的尺寸,調用者應該爲此提供額外信息。管理指針形參有三種常用技術
    (1)使用標記指定數組長度
void print(const char *cp){
    if(cp) //若cp不是一個空指針
        while(*cp) //只要指針不是空字符
            cout<<*cp++;
}

(2)使用標準庫規範
- 傳遞指向數組首元素和尾後元素的指針

void print(const int *beg,consst int *end){
    //輸出beg到end之間全部元素
    while(beg!=end)
        cout<<*beg++<<endl;
}

上述函數的調用可以通過:

int j[2]={0,1};
print(begin(j),end(j));

(3)顯式傳遞一個表示數組大小的形參

void print(cosnt int ia[],size_t size){
    for(size_t i=0 ; i!=size() ; ++i){
        cout<<ia[i]<<endl;
    }
}

(4)數組形參和const

(5)數組引用形參

void print(int (&arr)[10]){
    for(auto elem:arr)
        cout<<elem<<endl;
}

(6)傳遞多維數組

void print(int (*matrix)[10] ,int rowSize){}

/*等價定義*/
void print(int matrix[][10],int rowSize){}

上述語句將matrix聲明成指向含有10個整數的數組的指針

mian:處理命令行選項

含有可變形參的函數

(1)initializer_list形參
- 如果函數的實參數量未知但是全部實參的類型都相同,可以使用initializer_list類型的形參

操作 結果
initializer_list lst; 默認初始化:T類型元素的空列表
initializer_list lst{a,b,c…> lst的元素數量和初始值一樣多,lst是初始值的副本,列表種的元素是const
lst2(lst) 拷貝或賦值一個initalizer_list對象不會拷貝列表中的元素;拷貝後兩列表共享與元素
lst2=lst 同上
lst.size() 列表中的元素數量
lst.begin() 返回首元素指針
lst.end() 返回尾元素下一位置指針
void error_msg(initalizer_list<string> il){
    for(auto beg=il.begin() ; beg!=il.end() ; ++beg)
        cout<<*beg<<"";
    cout<<endl;
}
  • 如果想向initialzer_list形參中傳遞一個值的序列,必須把序列放進花括號中
error_msg( {"functionX",expected,actual} );

(2)省略符形參

返回類型和return語句

無返回值函數

  • 可以直接執行return語句處推出
void swap(int &v1,int &v2){
    if(v1==v2)
        return;
    int tmp=v2;
    v2=v1;
    v1-tmp;
}

有返回值函數

(1)值是如何被返回的

string make_plural(size_t str,const string &word,const string &ending){
    return (ctr>1)?word+ending:word;
}

(2)不要返回局部對象的引用或指針
- 函數完成後,它說佔用的空間也隨之被釋放。因此,局部變量的引用不再有效

(3)返回類類型的函數和調用運算符

// shorterString函數返回istring,訪問其size成員
auto sz=shorterString(s1,s2).size();

(4)引用返回左值

(5)列表初始化返回值
- 如果返回值是引用,它是左值;其它則是右值
- 如果返回值是常量引用,就不能給調用的結果賦值

(5)列表初始化返回值

vectot<sting> process(){
    ...
    ...
    return {"funtionX","OKay"};
}

(6)主函數main的返回值
- return EXIT_FALLURE或者return EXIT_SUCCESS 這兩個預處理變量定義再頭文件cstdlib頭文件中

(7)遞歸

返回數組指針

  • 最直接的辦法就是使用類型別名
typedef int arrT[10]; // arrT是一個類型別名,表示含有10個整數的數組
using arrT=int[10]; // arrT的等價聲明

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

(1)聲明一個返回數組指針的函數
- 要想再聲明func時不適用類型別名

int arr[10];
int *p1[10];
int (*p2)[10]=&arr; //p2時一個指針,它指向含有10個整數的數組
  • 返回數組指針的形式如下所示
Type (*function(parameter_list))[10];

// 例子
int (*func(int i))[10];

(2)使用尾置返回類型

// func接受一個int類型的實參,返回一個指針,該指針指向含有10個整數的數組
auto func(int i) -> int(*)[10];

(3)使用decltype

int odd[]={1,3,5,7,9};
int even[]={0,2,4,6,8};
// 返回一個指針,該指針指向含有5個整數的數組
decltype(odd) *arrPtr(int i){
    return (i%2)?&odd:&even; //返回一個指向數組的指針
}
  • 其中,decltype表示它的返回類型是一個指針,並且所指對象類型與odd的類型一致
  • decltype並不負責把數組轉換成對應指針,所以decltype的結果是個數組,要想表示返回指針,還必須再函數聲明時加一個符號

函數重載

(1)定義重載函數和

Record lookup(const Account&);
Record lookup(const Phone&);

(2)判斷兩個形參的類型是否相同

(3)重載和const形參
- 一個擁有頂層const的形參無法和另一個沒有頂層const的形參區別開來
- 如果形參時某種類型的指針或引用,則區分其指向是否常量可以實現函數重載

(4)const_cast和重載

const string &shorterString(const string &s1,const string &s2){
    return s1.size()<=s2.size()?s1:s2;
}
  • 上述程序,當它的實參不是常量時,得到的結果是一個普通的引用
string &shorterString(string &s1,string &s2){
    auto &r=shorterString(const_cast<const string&>(s1),const_cast<const string&>(s2));
    return const_cast<string&>(r)
}

(5)調用重載的函數

重載與作用域

特殊用途語言特性

默認實參

typedef string::size_type sz;
string screen(sz ht=24,sz wid=80,char backgrnd='');
  • 其中,爲每個形參都提供了默認實參
  • 注意:只能給全部形參同時提供默認值

(1)使用默認實參調用函數
- 直接再調用時省略該實參即可
- 注意:只能省略尾部的實參,或者省略全部實參

(2)默認實參聲明
- 通常在頭文件中,進行函數實參的聲明

通常,應該在函數聲明中指定默認實參,並將該聲明放在合適的頭文件中

(3)默認實參初始值
- 除局部變量之外,表達式能實現對應轉化則可以作爲默認實參

內聯函數和constexpr函數

(1)內聯函數可避免函數調用的開銷
- 在函數的返回類型前面加上關鍵字inline,就聲明成爲內聯函數了

很多編譯器都不支持遞歸內聯函數

(2)constexpr函數
- constexpr函數的全部形參都必須時字面值類型,而且函數體中必須有且只有一條return

調試幫助

(1)assert預處理宏
- assert是一種預處理變量,它的行爲類似於內聯函數

assert(expr);
  • 首先,對expr求值,如果爲假(0),assert輸出信息並終止程序的執行;如果爲真,assert聲明也不做
  • 常常用來檢查“不能發生的事”

(2)NDEBUG預處理變量
- assert的行爲依賴於一個名爲NDEBUG的預處理變量狀態。如果定義了NDEBUG,則assert什麼也不做。默認狀態下沒有定義NDEBUG,此時assert將執行檢查
- 定義NDEBUG能避免檢查各種條件所需的運行時開銷

  • 小例子:
void print(const int ia[],size_t size){
    #ifndef NDEBUG
        cerr<<__func__<<":array size is "<<size<<endl;
    #endif
}
  • 如果NDEBUG未定義,將執行 #ifndef 和 #endif之間代碼,如果定義了NDEBUG,將被忽略

  • 編譯器定義的用於調試的名字

代碼 結果
_ _ func _ _ 存放當前調試的函數的名字的const char靜態數組
_ _ FILE _ _ 存放文件名的字符串字面值
_ _ LINE _ _ 存放當前行號的整型字面值
_ _ TIME _ _ 存放文件編譯時間的字符串字面值
_ _ DATE _ _ 存放文件編譯日期的字符串字面值
if(word.size() < threshold)
    cerr<<"Error:"<<_ _FILE_ _
        <<":in function"<<_ _func_ _
        <<"at line"<<_ _LINE_ _<<endl
        <<"     Compiled on"<<_ _DATE_ _
        <<"at"<<_ _TIME_ _<<endl
        <<"     Word read was \""<<word
        <<"\":Length too short"<<endl;

函數匹配

(1)確定候選函數

(2)尋找最佳匹配(如果有的話)

(3)含有多個形參的函數匹配

實參類型轉換

(1)需要類型提升和算術類型轉換的匹配

(2)函數匹配和const實參

函數指針

bool (*pf)(const string &,const string &);

(1)使用函數指針

pf=lengthCompare; //pf指向名爲lengthCompare的函數
pf=&lengthCompare; //等價的賦值語句

另外,還可以直接使用指向函數的指針條用函數

bool b1=pf("hello","goodbye");
bool b2=(*pf)("hello","goodbye"); //一個等價的調用

(2)重載函數指針

void ff(int*);
void ff(unsigned int);

void (*pf1)(unsigned int)=ff; //pf1指向ff(unsigned)

編譯器通過指針類型決定選用那個函數,指針類型必須與重載函數中的某一個精確匹配

(3)函數指針參數
- 函數雖然不能定義數組、函數類型的形參,但是形參也可以是指向

void useBigger(const string &s1,const string &s2,bool pf(const string &,const &));

// 等價的聲明
void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &));

(4)返回指向函數的指針
- 最簡單的辦法是聲明一個類型別名作爲返回類型

using F=int(int*,int); //F是函數類型,不是指針
using PF=int(*)(int*,int); //PF是指針類型
  • 將F定義成函數類型,將PF定義成指向函數類型的指針
PF f1(int); //正確:PF是指向函數的指針,f1返回指向函數的指針
F=f1(int); //錯誤:F是函數類型,f1不能返回一個函數
F *f1(int); //正確:顯式地指定返回類型是指向函數的指針
  • 處於完整性考慮,可以使用尾置返回類型的方式
auto f1(int)->int(*)(int*,int);

(5)將auto和decltype用於函數指針類型

string::size_type sumLength(const string&,const string&);
string::size_type larger(const string&,const string&);
// 根據其形參的取值,getFcn函數返回指向sumLength或者largerLength的指針
decltype(sumLength) *getFcn(const string &);
發佈了34 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章