C++系統學習之六:函數

1、函數基礎

  典型的函數定義包括:返回類型、函數名、由0個或多個形參組成的列表以及函數體。

2、參數傳遞

  形參初始化的機理和變量初始化一樣。

  有兩種方式:引用傳遞和值傳遞

2.1 傳值參數

  當形參是非引用類型時,形參初始化和變量初始化一樣,將實參的值拷貝給形參。

指針形參

  當執行指針拷貝操作時,拷貝的是指針的值,拷貝之後,兩個指針是不同的指針。但通過指針可以修改它所指的對象。

2.2 傳引用參數

使用引用避免拷貝

  拷貝大的類類型對象或者容器對象比較低效,甚至有的類類型根本就不支持拷貝操作。當某種類型不支持拷貝操作時,函數只能通過引用形參訪問該類型的對象。

使用引用形參返回額外信息

2.3 const形參和實參

  當用實參初始化const形參時會忽略頂層const。因此,當形參有頂層const時,傳給它常量對象或者非常量對象都是可以的。

void fun1(const int i){.......}

void fun2(int i){.....}

  上述兩個函數不能算是重載,兩個函數是一樣的,程序會報錯,fun2重複定義了fun1.

指針或引用形參與const

  可以使用非常量初始化一個底層const對象,但是反過來不行。同時一個普通的引用必須用同類型的對象初始化。

儘量使用常量引用

2.4 數組形參

數組有兩個重要的特性:

  • 不允許拷貝
  • 使用數組時會轉換成指針

儘管不能以值傳遞的方式傳遞數組,但是可以將形參寫成類似數組的形式

void print(const int*);
void print(const int[]);
void print(const int[10]);

以上三種形式的聲明等價

NOTE:當函數不需要對數組元素執行寫操作的時候,數組形參應該是指向const的指針。只有當函數確實要改變元素值的時候,才把形參定義成指向非常量的指針。

當數組作爲函數形參時,因此應該提供一些額外信息來確定數組的確切尺寸,管理數組形參有三種常用的技術:

使用標記指定數組長度

  要求數組本身包含一個結束標記。例如C風格字符串以空字符結尾。

使用標準庫規範

  傳遞指向數組首元素和尾元素的指針。

void print(const int *beg,const int *end)
{
     while(beg!=end)
    {
         cout<<*beg++<<endl;   
    }  
}    
int arr[2]={0,1};
print(begin(arr),end(arr));

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

  專門定義一個表示數組大小的形參。

void print(const int ia[], size_t size);

int j[]={0,1};

print(j, end(j)-begin(j));

數組引用形參

  形參可以是數組的引用,此時,引用形參綁定到對應的實參上,也就是綁定到數組上。

void print(int (&arr)[10])
{
    for(auto elem:arr)
    {   
        cout<<elem<<endl;
    }
}
形參是數組的引用,維度是類型的一部分

NOTE:arr兩端的括號必不可少

f(int &arr[10]);    //錯誤,將arr聲明成了引用的數組
f(int (&arr)[10]);    //正確,arr是具有10個整數的整型數組的引用

傳遞多維數組

  數組第二維的大小都是數組類型的一部分,不能省略。傳遞多維數組傳遞的是指向數組的指針,實際還是指向首元素的指針。(多維數組就是數組的數組,數組的首元素還是數組,所以是指向數組的指針)。

void print(int (*matrix)[10],int size);  matrix是一個指針,指向有10個整數的數組

也可以用:

void print(int matrix[][10],int size);  matrix和上面一樣的意義

2.5 含有可變形參的函數

C++提供兩種方法:

實參類型相同,可以傳遞一個名爲initializer_list的標準庫類型

initializer_list形參

lnitializer_list和vector一樣都是模板類型,不同的是initializer_list對象中的元素永遠是常量值,不能改變。

void error_msg(initializer_list<string> ls)
{
	for (auto beg = ls.begin(); beg != ls.end(); ++beg)
	{
		cout << *beg << "   ";
	}
	cout << endl;
}

error_msg({ "hello" });
error_msg({ "hello!", "world!!" });  //注意值的傳遞要放在花括號裏

省略符形參

  省略符形參是爲了便於C++程序訪問某些特殊的C代碼而設置的。通常,省略符形參不應用於其他目的。省略符形參只能出現在形參列表的最後一個位置。

實參類型不同,使用可變參數模板

3、返回類型和return語句

3.1 無返回值函數

  返回類型是void類型的函數

3.2 有返回值函數

值是如何被返回的

  返回一個值的方式和初始化一個變量或形參的方式完全一樣:返回的值用於初始化調用點的一個臨時量,該臨時量就是函數調用的結果。

 不要返回局部對象的引用或指針

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

auto sz=getstring().size();    //getstring返回的string對象再調用size函數

 引用返回左值

調用一個返回引用的函數得到左值,其他返回類型得到右值。

 列表初始化返回值

函數可以返回花括號包圍的值的列表。

vector<string>  process()
{
    return {"ni","hao"};
}

 遞歸

如果一個函數調用了它自身,不管這種調用是直接還是間接的,都稱該函數爲遞歸函數。

int factorial(int val)
{
    if(val>1)
        return factorial(val-1)*val;
    return 1;
}
求1x2x3x4......

 3.3 返回數組指針

因爲數組不能被拷貝,所以函數不能返回數組。不過,函數可以返回數組的指針或引用。

最直接的方法是使用類型別名

typedef int arrT[10];
using arrT=int[10];

 聲明一個返回數組指針的函數

int arr[10];    //arr是一個含有10個整數的數組
int *p1[10];    //p1是一個含有10個整型指針的數組
int (*p2)[10]=&arr;    //p2是一個指針,其指向一個有10個整數的數組

 如果要定義一個返回數組指針的函數,則數組的維度必須跟在函數名字之後,並且函數的形參列表應該先於數組的維度。

int (*func(int a,int b))[10];

 此函數返回的是一個指向有10個整數數組的指針。

 使用尾置返回類型

任何函數的定義都能使用尾置返回,但是這種形式對於返回類型比較複雜的函數最有效,比如返回類型是數組的指針或引用。

尾置返回類型跟在形參列表後面並以一個->符號開頭。爲了表示函數真正的返回類型跟在形參列表之後,我們在本應該出現返回類型的地方放置一個auto。

auto func(int i)->int (*)[10];

 使用decltype

4、函數重載

如果同一作用域內的幾個函數名字相同但形參列表不同,稱爲函數重載。注意必須是形參列表不同,僅僅只是返回類型不同不可以稱爲重載。

重載和const形參

頂層const不影響傳入函數的對象。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來。

int f1(int i);
int f1(const int i);    //不構成重載,重複聲明瞭f1

int f2(int *i);
int f2(int *const i);    //不構成重載,重複聲明瞭f2

但底層const不同,可以構成重載

int f1(int &i);
int f1(const int &i);    //重載,新函數

int f2(int *i);
int f2(const int *i);    //重載,新函數

NOTE:最好只重載那些確實非常相似的操作。

const_cast和重載

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);
}

4.1 重載與作用域

如果在內層作用域中聲明名字,它將隱藏外層作用域中聲明的同名實體。

void func()
{    
}

int main()
{    
    int func=0;
    func();    //錯誤,此時func是int類型的變量,不是函數,隱藏了外層的函數定義
    return 0;
}

5、特殊用途語言特性

  默認實參、內聯函數和constexpr函數。

5.1 默認實參

一旦某個形參被賦予了默認值,它後面的所有形參都必須有默認值。

string  screen(int i=10, int a=1, stirng s=" ");

使用默認實參調用函數

在調用函數的時候省略該實參就可以。

默認實參聲明

在給定的作用域中一個形參只能被賦予一次默認實參。

默認實參初始值

局部變量不能作爲默認實參。除此之外,只要表達式的類型能轉換成形參所需的類型,該表達式就能作爲默認實參。

5.2 內聯函數和constexpr函數

調用函數一般比求等價表達式的值要慢一些。

內聯函數可避免函數調用的開銷

在函數的返回類型前面加上關鍵字inline。

一般來說,內聯機制用於優化規模較小、流程直接、頻繁調用的函數。

constexpr函數

constexpr函數是指能用於常量表達式的函數。

定義constexpr函數要遵循:函數的返回類型及所有形參的類型都得是字面值類型,而且函數體中必須有且只有一條return語句。

constexpr int new_sz()
{    
    return 42;
}

constexpr int foo=new_sz();    //foo是一個常量表達式

爲了能在編譯過程中隨時展開,constexpr函數被隱式地指定爲內聯函數。

NOTE:constexpr函數不一定返回常量表達式。

把內聯函數和constexpr函數放在頭文件內

和其他函數不一樣,內聯函數和constexpr函數可以在程序中多次定義。不過,對於某個給定的內聯函數或者constexpr函數來說,它的多個定義必須完全一致。因此,內聯函數和constexpr函數通常定義在頭文件中。

5.3 調試幫助

兩項預處理功能:assert和NDEBUG

assert預處理宏

assert宏常用於檢查“不能發生”的條件。

assert(expr);

 如果expr爲假,assert輸出信息並終止程序執行,如果爲真,assert什麼也不做。

 NDEBUG預處理變量

assert的行爲依賴於一個名爲NDEBUG的預處理變量的狀態。如果定義了NDEBUG,則assert什麼也不做,默認情況下沒有定義NDEBUG。

可以使用#define語句定義NDEBUG,從而關閉調試狀態。

 6、函數指針

函數指針指向的是函數而非對象。和其他指針一樣,函數指針指向某種特定類型。函數的類型由它的返回類型和形參類型共同決定,與函數名無關。

int func(int a, string s);

該函數的類型是int(int , string).要想聲明一個可以指向該函數的指針,只需要用指針替換函數名即可。

int (*p)(int ,string )  //未初始化

 NOTE:*p的括號必須加上

使用函數指針

當把函數名作爲一個值使用時,該函數自動地轉換成指針。

int (*p)(int ,string )=func;

 可以使用函數指針直接調用該函數,而不需要解引用該指針。

指向不同函數類型的指針之間不存在相互轉換,可以給函數指針賦值nullptr和0,表示指針沒指向任何一個函數。

 重載函數的指針

如果定義了指向重載函數的指針,編譯器通過指針類型決定選用哪個函數,指針類型必須與重載函數中的某一個精確匹配。

函數指針形參

和數組類似,雖然不能定義函數類型的形參,但是形參可以是指向函數的指針。可以直接把函數作爲實參使用,此時它會自動轉換成指針。

返回指向函數的指針

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

注意將decltype用於函數名時,返回的是函數類型,而非指針類型,如果要表示函數指針,需要自己加上*。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章