C++ Primer學習 《函數-Functions》

函數-Functions

說到函數,很多人覺得很簡單,但如果問你重載函數的判別原理,函數返回指針的注意事項,指針函數的定義等等,很多人就頭大了。函數是編程的基礎,這塊一定要打紮實。

函數基本知識

實參(Arguments)。形參(Parameters)。

形參初始化的順序

儘管我們知道哪個實參初始化了哪個形參,但我們並不知道這個初始化的順序!編譯器可以以任意順序初始化各個形參!因此,實參中不用包括改變自身的運算(如:++)。

初始化時的轉換

如果我們定義了一個函數

int fact(int);
那麼,在調用該函數時,實參一定要能轉變成形參的type。

fact("hello");//error,string不能轉成int
fact(3.14);//ok,float可以轉成int

形參列表

在函數的最外層定義的變量,不能和形參重複。即:

int fact(int a)
{
    int a;//error!
    {
        int a;//ok!
    }
}

因爲要使用的形參必須是有名字的,所以形參一般都會有名字。然而,如果我們在不斷更新代碼後,有的形參不再使用,那麼我們將該形參設爲沒有名字的,以示區分。注意的是,即便這個形參沒有名字,我們還是要通過實參給其賦值!

函數返回值

一個函數的返回值不能是數組或是函數,但是我們可以返回一個指針指向數組,或是指針指向函數。


局部變量

局部變量主要有兩種,一種是automatic objects,另一種是local static objects。

automatic objects

automatic objects在函數調用時生成,在函數結束時便會消亡(每一次重新調用該函數,這個object的值不會被保留)。所有傳遞來的參數都是automatic objects。

local static objects

而local static objects則不同,它在函數第一次執行時,還未執行到定義該object的語句之前就被初始化,它的值在每一次調用函數時都會被保留。其定義形式爲:

static type name;//ex: static int b;可以只聲明不初始化,也可以直接初始化


參數傳遞

參數傳遞有兩種:值傳遞(passing argument by value)和引用傳遞(passing argument by reference)。

如果函數定義時,形參是一個reference,那麼參數傳遞便是引用傳遞;否則,是值傳遞。

簡單地說,引用傳遞下,函數內對該參數的改變,在函數返回時依然有效,因爲函數內改變的是一個對原參數的引用,它和原參數指向同一個內存地址;而值傳遞下,函數內對參數的改變,在函數返回後不會影響原參數,因爲值傳遞是copy了一個一模一樣的備份,然後對該備份進行運算,這個備份和原來的參數的內存地址是不同的。

必須使用值傳遞的情況

一些類(包括IO 類),不能被複制(cannot be copied)。這種情況下,函數必須使用引用傳遞!例如:

void ioNotBeCopied(ifstream fin);
void ioNotBeCopied2(ifstream &fin);

void main()
{
    ifstream fin;
    ioNotBeCopied(fin);//error!
    ioNotBeCopied2(fin);//ok!
}
事實上IO類的三個頭文件:iostream,fstream,sstream定義的類都是不能複製的,不能直接作爲函數參數傳遞,也不能存儲在vector中。




Const 參數

top-level const

當我們初始化一個形參時,top-level const會被忽略(回顧top-level const:object本身不能被改變)。

對於一個top-level const的形參,我們可以用nonconst或top-level const object來初始化它。

對於一個nonconst形參,我們也可以用nonconst或top-level const object來初始化它。

注意:這裏的傳遞都是值傳遞,而非引用傳遞。

Pointer or Reference Parameters and const

回顧 low-level const:object指向或引用的對象不能被改變。

在pointer和reference中涉及const時,記住兩個原則:

1.我們能用nonconst初始化一個low-level const,但反之不行。

2.對於plain reference(即nonconst reference,別忘了reference只存在low-level const,不存在top-level const),必須使用同樣的類型(nonconst)來初始化。(這也解釋了上一部分top-level const的原則只適用於值傳遞)。

一些例子:

void reset(int &a);
void creset(const int &a);

int c =0;
const int cc = 0;
reset(&c);//error!非常量引用的初始值必須爲左值
creset(&cc);//error! const int*和const int&不兼容

reset(42);//error! 不能用plain reference綁定literal
creset(42);//ok!
這裏對“非常量引用的初始值必須爲左值”做一點說明,這句話的意思是int &a是一個非常量引用,所以它的初始值一定要是一個左值(lvalue),畢竟nonconst reference只能綁定object。而&c其實是c 的地址,是一個右值(rvalue),你無法對&c再次取地址。因此出現了error。

儘可能使用const reference(Use Reference to Const When Possible)

這樣做有兩個好處:

1.明確告訴使用函數的人,哪些變量可能會被改變,哪些不會。

2.plain reference比const reference有更多的限制,如不能綁定數字(literal),不能綁定一個const object。




數組參數(Array Parameters)

我們不能將數組copy給一個函數(數組沒有拷貝構造函數),當我們使用數組時,它經常會被轉換成指針。如果我們向函數傳遞一個數組,實際上我們傳遞的是指向數組第一個元素的指針。

下面的三個聲明都是完全一樣的:

void print (const int*);
void print (const int[]);
void print (const int[10]);
而當編譯器檢查時,它只會檢查實參是否是一個指針:

int i=0;
print(&i);//ok!

確保數組傳遞的正確性

正是因爲編譯器只會檢查是否是指針,因此傳遞數組很容易出錯,例如我們希望得到一個int[10],而事實上傳遞的是一個int[5],那麼在遍歷數組時就會越界!

C++ Primer提供了三種方法,來避免這種錯誤。

1.用一個標記(marker)來表示數組的結束

在C-style string中,我們知道string的結尾是一個'\0‘,當函數讀到'\0'時,我們就知道數組結束了。這種方法對於有明顯的結尾標記(end marker)的情況很實用。但是對於int類型往往就不那麼好用了,因爲任何值都是有可能的。

2.傳遞頭元素(first element)和結尾元素(one past the last element)

藉助begin()和end()函數,我們可以輕鬆地獲得數組的起始和截止指針,將這兩個指針傳遞到函數內,則遍歷就不會出錯。例如:

void print(const int*beg,const int *end)
{
    while(beg!=end)
        cout<< *beg++ <<endl;
}

int j[2] = {0,1};
print(begin(j),end(j));
3.傳遞數組同時傳遞數組大小

這個思想非常容易想到。值得注意的是,數組的大小一般可以用size_t來表示。

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

Array Parameters and const

和之前提到的reference儘量聲明成const一樣,array和pointer都應該儘量聲明成const,除非需要改變其中的值。

Array Reference(對array的引用)

我們可以定義一個reference,其指向一個array,作爲參數。這樣做的好處是,我們可以用range for輕鬆地遍歷數組:

void print(int (&arr)[10])
{
    for(auto elem:arr)
        cout<<elem<<endl;
}
注意:這裏的(&arr)兩側的括號是很有必要的!如果沒有括號,則是一個由10個reference構成的array。

但是,這樣做的缺點是,我們在初始化形參時,必須明確傳遞一個int[10]:

int j[2] = {0,1};
int k[10];
print(j);//error!
print(k);//ok!
事實上,我們也有辦法傳遞任意大小的數組,這將在很以後學到。

Passing a Multidimensional Array

傳遞多維數組(本質是有數組構成的數組)時,第二維即以後的數組大小必須被明確定義。

void print(int (*matrix) [10]);//()不能省略!
void print(matrix[][10]);//equivalent defination




命令行參數處理

我們知道,main函數其實可以接受命令行的參數,基本形式如下:

int main(int argc,char *argv[]);
int main(int argc,char **argv);//equivalent
argc表示包括函數名和參數在內的總個數,argv則是包括了函數名和參數在內的具體內容,最後以0結尾。舉例說明:

program -d
argc = 2;
argv[0]="program";
argv[1] = "-d";
argv[2] = 0;





不知道參數個數的函數(Functions with Varying Parameters)

如果我們事先不確定具體的函數參數個數,那麼有兩種主要方法來解決。

1.如果參數的類型都相同,那麼可以使用initializer_list類來完成。

2.如果參數的類型也不同,那麼我們用一種叫做variadic模板的特殊函數來完成(將在很以後介紹)。

另外,有一種參數類型叫做ellipsis(省略)也能完成這一功能,但只有當我們的程序需要和C語言兼容時我們才應該使用它,即在C++中不推薦使用ellipsis參數!

initializer_list參數

C++ 11 新引進一個類:initializer_list類,它用來表示特定類型的數組。
一些操作:
initializer_list<T> lst;//empty list of elements of type T
initializer_list<T> lst{a,b,c...};//elements are copies of the corresponding initializer!!!elements are const!!!
lst2(lst);//copy
lst2 = lst;//assign. Warning:copy or assign an initializer_list does not copy the elements in the list! The original and the copy share the elements!
lst.size();
lst.begin();
lst.end();
注意的是,initializer_list內的元素都是const,無法改變。

我們可以用{}包括起來的內容,直接當做實參傳遞給一個initiazer_list。
void error_msg(initializer_list<string>il);
//expected,actual are strings
error_msg({"functionX",expected,actual});//ok!

其實,我沒有完全想明白initializer_list和vector的差別。initializer_list全是const,這一點vector可以很容易做到。唯一的差別是,initializer_list的copy和assign都是相當於別名(alias),這樣的好處是什麼呢?疑惑。

我想,可能的解釋是:initializer_list的構造只能通過{},這就限制了它的使用範圍。正如它的名字的意思,這就是用來初始化一個函數的特殊類,用vector也能實現,但是用initializer_list實現感覺分工更明確。

ellipsis parameters

使用"..."來表示省略的參數:
void foo(parm_list, ...);
具體如何使用我並不清楚,大家可以自己上網學習,但如果是C++程序員,ellipsis parameter不那麼重要。




Function Return Types

Never Return a Reference or Pointer to a Local Object

當一個函數運行結束,其佔用的內存空間都會被釋放。因此,如果你的指針是一個在函數內創建的指針,那麼該指針指向的空間將在函數結束時被釋放,因此返回後,你將無法獲得這些值;同樣的,如果你的函數返回類型是一個引用,而這個引用指向的是函數內部創建的object,那麼函數結束後,你也將無法獲得該值。

const string& manip()
{
    string ret;
    if(!ret.empty())
        return ret;//WRONG!ret是一個local object
    else
        return "EMPTY";//WRONG!"EMPTY"也是一個local object。當然,如果函數返回的是const string,而非const string&,那麼這個是可以的
}

Reference Returns Are Lvalues

函數的返回類型,只有當是reference時是lvalue,其它情況都是rvalue。

如果函數的返回參數是一個reference(當然該reference不指向local object),那麼我們可以把這個返回參數當做一般的lvalue使用。

char& getValue(string &str,int ix)
{
    return str[ix];
}
int main()
{
    string s("a value");
    get_val(s,0) = 'A';
}

List Initializing the Return Value(使用list initializer來定義返回變量)

這個很好理解,當我們的return type是vector等類型時,我們可以使用list initializer的形式定義返回變量。

vector<int> process()
{
    return {1,2,3,4};
}
注意的是:如果返回的是一個built-int type(如int,float等),那麼{}中只能有一個值,且這個值的類型和返回類型必須完全一樣,不能有任何轉換。而如果返回一個類,那麼{}內的內容由該類決定。




返回數組的指針(Return a Pointer to an Array)

因爲數組不能被複制,因此我們無法直接返回一個數組。然而,我們可以返回一個指向數組的指針。

要返回這樣的一個指針,有四種辦法:

1.用type alias

typedef int arrT[10];
using arrT = int[10];//equivalent
arrT* func();//func()返回一個指向int[10]的指針

2.直接定義

基本格式是:

Type (*function(parameter_list))[dimension]
一個例子:

int (*func())[10];//()不能省略!和上面的func等價

3.使用Trailing Return Type(拖尾返回類型)

C++ 11新特性,trailing return type可以定義任何函數,但是在函數的返回類型很複雜時尤其有用。

基本格式是:

auto function(parameter_list) -> return_type
一個例子:

auto func() -> int(*)[10];//和上面的func等價

4.使用decltype

記住,decltype不會把一個array轉成對應的pointer類型。
int odd[] = {1,3,5,7,9};
decltype(odd) *func();//和上面的func不同,返回一個int(*)[5]。decltype(odd)得到的是一個int[5]


返回數組的引用(Return a Reference to an Array)

和上一節完全一樣,四種方法可以分別使用到數組的引用上。




函數重載

重載函數必須在參數的個數,或者類型上有所區別!如果兩個函數僅僅是返回類型不同,那麼重載這樣兩個函數將是錯誤的。

Overloading and const Parameters

由於top-level const對object的傳遞不起作用,因此,如果兩個函數僅僅是top-level const的差別,那麼編譯器無法區分兩個函數,也就是說這樣兩個函數不能重載。
void lookup(phone);
void lookup(const phone);//error!redeclaration of lookup(phone)

void lookup(phone*);
void lookup(phone* const);//error!redeclaration of lookup(phone*);
另一方面,如果兩個函數是low-level const的差別,那麼就可以重載。
void lookup(account&);
void lookup(const account&);//new function

void lookup(account*);
void lookup(const account*);//new function

const_cast and Overloading

有時候,我們會有這樣的情況:對於一個函數,如果我們給它nonconst參數,那麼希望得到nonconst返回;而如果我們給它const參數,那麼希望得到const返回。或者其他情況,總之,兩個重載函數,根據參數的差別,一個返回const,一個返回nonconst。這時候,當然我們可以寫兩個差不多的函數,實現功能。但如果兩個函數高度類似,我們可以使用const_cast來簡化我們的代碼。
假設我們已經擁有以下函數:
const string& shorterString(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}
這時,如果我們向函數傳遞兩個nonconst string,那麼得到的結果依舊是const string。現在,我們希望在這種情況下,能得到一個nonconst string,除了重新寫一個新的函數外,我們可以這麼做:
string& shorterString(string&s1,string&s2)
{
    auto &r = shorterString(const_cast<const string&>(s1),
                            const_cast<const string&>(s2));
    return const_cast<string&>(r);
}
這裏auto &r中的&的作用是保證返回的內容就是s1或s2中的一個,而不是重新創造的string。
在這個函數中,我們並沒有重新編寫一個新的shorterString,而是藉助現有的函數,通過const_cast轉換,實現了功能。
應該注意的是,在這裏,我們知道用const_cast去掉const是安全的,因爲我們知道原來的string並非const string。這也是爲什麼const_cast在重載函數中很有用,但是在一般情況下作用並不大的原因,因爲一般來說,去掉const將會導致很危險的結果!

Calling an Overloaded Function

當我們調用一個重載函數時,我們應該明白三點:
1.編譯器會找到一個最佳匹配的重載函數(a best match)。
2.如果沒有重載函數能匹配調用的參數,那麼將無法匹配(no match)並報錯。
3.如果有多於一個重載函數能匹配,而且編譯器無法決定哪個是最佳的,這種情況被稱作模糊調用(ambiguous call)並報錯。
具體編譯器如何決定哪個函數是最優的,在後面小節中將講到。

Overloading and Scope

一般來說,在函數內部聲明一個函數是非常不好的做法(還真從來沒見過有人這麼做...),但是爲了說明函數重載和scope的關係,我們將打破這一習慣。
我們看一個例子來了解一下:
string read();
void print(const string &);
void print(double);
void foo(int ival)
{
    string s = read();//ok!
    bool read = false;//hides the outer declaration of read
    string s = read();//error!read is a bool variable,not a function
    
    void print(int);//hides previous instances of print
    print("Value: ");//error!
    print(ival);//ok!print(int) is visible
    print(3.14);//ok!calls print(int);print(double) is hidden
}
這個例子很全面地告訴我們,函數內部重新定義的內容(新的函數或是新的變量),將覆蓋函數外的內容。重新定義之後的代碼裏,只能對新定義的函數或變量操作。






缺省參數(Default Arguments)

我們可以將函數中的若干個參數定義爲缺省參數,但是如果我們定義了一個參數爲缺省參數,那麼其後的所有參數都必須具有缺省值。

Default Argument Declaration

一般來說,一個函數的聲明應該處處是一樣的。下面的說明,只是表明C++的規則,強烈不建議任何人這麼做。
對於有default arguments的函數,在不同地方聲明時,可以有所不同。關鍵在於,同一個缺省參數不能有不同的缺省值,而我們可以在另一個函數聲明中追加一些之前沒確定的缺省值。例如:
<pre name="code" class="cpp">string screen(int,int,char=' ');//ok
string screen (int,int,char='*');//error! redeclaration
string screen (int = 24,int =80,char);//ok! adds default arguments,現在兩個screen的缺省值是一樣的!都是int=24,int=80,char=' '
string screen(int =24,int=80,char=' ');//error! 最後一個char不能再次定義!

注意:我在VS2013測試時,輸入如下:
string screen(int, int=20);
string screen(int = 10, int);
在編輯器內,第二行的screen下面會有紅色波浪線,顯示“錯誤:默認實參不在形參列表的結尾”,但是編譯過程沒有報錯,也可以正常運行。詭異。。

Default Argument Initializers

當一個函數具有缺省參數時,這些缺省參數並不需要是常量!事實上,我們可以改變這些缺省參數,以改變函數本身!
對缺省參數的唯一限制就是:它們不能是local variable,必須是全局變量。
而正因爲是全局變量,所以我們能改變它們,以改變函數。
int a=1;
int b();
string screen(int =a,int =b());//現在的默認調用是screen(1,b());

void func()
{
    a=2;
    screen();//調用screen(2,b());
}







Inline和constexpr函數

inline Functions

之前我們定義過這個函數:
const string& shorterString(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}
定義這種短小的函數有如下四個好處:
1.比起復雜的比較來說更直觀易懂。
2.統一的函數行爲,不同函數內調用都得到一樣的結果。
3.易於修改。
4.方便其他函數調用。
然而,這樣做也有一個顯而易見的缺點:函數調用比起僅僅是比較來說,要慢非常多!因此對於追求高效率的代碼,需要做一些調整。

inline Functions Avoid Function Call Overhead

如果我們將一個函數定義爲inline函數,就能避免無謂的函數調用。事實上,編譯器在編譯時,會將inline函數的內容完全替代調用這個函數的地方,這樣我們的代碼簡潔高效,同時運行速度也沒有受到任何影響。
inline函數定義很簡單:
inline const string& shorterString(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}
應該注意的是:inline函數只應該用來定義那些簡短小巧的代碼。inline只是一個request(申請),編譯器可以選擇忽略這個申請。大部分編譯器對遞歸函數的inline將忽略,對於超過75行的函數將幾乎肯定被忽略。

constexpr Functions

這是C++ 11的新特性。一個constexpr函數可以被用在常量表達式中(constant expression)。constexpr函數的返回值和所有參數,都必須是literal或const!!而且函數只允許有一個return語句。
由於constexpr代表的一定是一個常值,因此爲了儘快展開這個函數已得到結果,constexpr函數默認都是inline的。
一個constexpr也可以包含其他語句,只要這些語句在運行過程中不產生行爲(generate no actions at run time),例如:空語句,type aliases和using語句。
一個constexpr並不一定要返回const類型變量,例如:
constexpr int scale(int cnt){return 3*cnt;}//只要cnt是一個const,那麼scale返回的便是一個常量
再看個列子:
int arr[scale(2)];//ok!scale(2)是常表達式
int i=2;             //i is not a constant expression
int a2[scale(i)];//error!
最後要說的是!constexpr目前還沒有被Visual Studio所接受!暈,具體原因涉及到編譯器的實現細節,和類模板似乎有一定關係。我也不是很懂,大家可以參考這篇文章:
http://blog.csdn.net/hikaliv/article/details/4484789

Put inline and constexpr Functions in Header Files

和其他函數不同,inline和constexpr函數可以被多次定義(be defined multiple times)。所有對inline和constexpr的定義都必須完全一樣(all of the definitions of a given inline or constexpr must match exactly),因此我們一般將inline和constexpr定義在頭文件中,而非源文件中!
我個人不是非常明白多次定義的意思。我測試下來的結果是:inline和constexpr定義在頭文件中,頭文件必須有#ifndef...#define...#endif,否則編譯報錯。而如果定義好了宏,那麼在多個源文件中多次引用該頭文件確實是不會報錯的。






Aids for Debugging

我們在開發代碼的過程,需要一些特殊的工具來幫助我們debug,而一旦開發完成,我們希望直接輸出一個去掉這些debug代碼的release版本。我們通過assert和NDEBUG兩個宏來實現這個功能。

the assert Preprocessor Macro

assert定義在cassert頭文件中。基本形式是:
assert(expression);
如果語句運算的結果爲真,那麼assert不做任何動作;如果結果爲假,那麼將中斷程序,在終端上會顯示debug結果(在哪個具體位置遇到了assert失敗的情況)。

the NDEBUG Preprocessor Variable

NDEBUG,顧名思義就是NO DEBUG!
如果NDEBUG被定義了,那麼assert就不會執行;否則assert會被執行。默認情況下,NDEBUG是沒有被定義的。
定義NDEBUG的方法並不是定義一個叫NDEBUG的變量,我們需要定義一個叫NDEBUG的預處理變量(Preprocessor Variable)!
一種方法是使用命令行定義:
$ CC -D NDEBUG main.C  #linux
$ CC /D NDEBUG main.C  #microsoft
配合NDEBUG,我們還可以實現比assert更復雜的debug功能。例如:
void print()
{
#ifndef NDEBUG
cerr<<__FILE__<<endl;
#endif
}
即通過宏定義來控制cerr<<__FILE__<<endl這段代碼是否執行。
這裏我們用了__FILE__這個編譯器預先定義好的變量,還有一些其他類似變量。
__FILE__:string, name of file
__LINE__:int, current line number
__TIME__:string,time the file was compiled
__DATE__:string,date the file was compiled







Function Matching

前面我們講到對於重載函數,編譯器會尋找最佳匹配的重載函數來執行,那麼如何做呢?這一節我們會講明白。
考慮以下四個函數,和我們的調用。
void f();
void f(int);
void f(int,int);
void f(double,double=3.14);
f(5.6);//calls void f(double,double)

Candidate and Viable Functions

我們把只要名稱一樣的所有重載函數,都看做候選函數(Candidate Functions)。這裏有四個candidate functions
我們把候選函數中,能夠被提供的實參調用的函數叫做可行函數(Viable Functions)。這裏有兩個viable functions

尋找最佳匹配函數

對於最佳匹配,總的思想是:argument和parameter的type越接近,那麼匹配越好。
在上一個例子中,如果f(5.6)調用f(int),那麼需要進行double到int的轉換,然而如果調用f(double,double = 3.14),那麼無需進行任何轉換,這是一個精準匹配(exact match)!

更準確的最佳匹配的定義是,存在一個且僅存在一個最佳匹配函數,使得:
1.調用這個函數的話,任何實參都不比調用其它可行函數來的差。(好和差的定義將在後面給出)
2.至少有一個實參調用,比其它可行函數來得好。

對於上面這個例子,如果我們調用f(42,2.56),會發型有兩個viable functions,分別是f(int,int)和f(double,double=3.14),然而無論調用哪一個,都需要轉換一個參數。因此,不存在最優的匹配函數,這種情況下,編譯器就會報錯!

Argument Type Conversions

爲了判斷匹配的好壞,編譯器對各種類型轉換進行了排序。
1.精準匹配:
形參和實參類型完全一樣(identical)
形參從數組轉變成指針
top-level const從實參添加或去除
2.經過const轉換
3.經過整形提升(integral promotion),從比int小的整形編程int。
4.經過算數轉換(如浮點數和整數的轉換,或是char到short,int到unsigned int)或指針轉換(void*和其他指針的轉換)
5.經過類轉換(以後會講到)

Matches Requiring Promotion or Arithmetic Conversion

記住:一個良好的代碼很少包含類型非常近的參數的重載函數!

在整形promotion中,整形會更傾向於適應int類型。
void ff(int);
void ff(long);
void ff(short);
void ff(long long);
ff('a');//調用ff(int)
另一方面,如果沒有int類型,可能就無法判斷。
void ff(long);
void ff(short);
void ff(long long);
ff('a');//報錯!無法判斷

所有的算數轉換都是一樣的!從int到unsigned int不比從int到double來的優先級更高。另外,literal中,整數一般默認是int,浮點數默認是double。
void manip(int);
void manip(float);
manip(3.14);//error!無法判斷
一個特殊的例子:
void ff(int);
void ff(long long);
ff(999999999999999);//調用ff(long long)

Function Matching and Const Arguments

如果兩個重載函數差別在於low-level const,那麼編譯器會傾向調用const類型匹配的那個函數。
void lookup(account&);
void lookup(const account&);
const account a;
account b;
lookup(a);//調用lookup(const account&)
lookup(b);//調用lookup(account&);



函數指針(Pointers to Functions)

Define a Function Pointer

一個函數的類型由函數的返回值和其所有參數類型共同決定!
bool lengthCompare(const string&,const string&);//該函數的類型是bool(const string&,const string&)
bool (*pf)(const string&,const string&);//定義的函數指針
注意定義函數指針的()是非常必要的!否則你就定義了一個函數!

Using a Function Pointer

我們可以直接將函數的地址賦給函數指針,具體的:
pf = lengthCompare;
pf = &lengthCompare;//equivalent!
我們也可以直接用函數指針來調用該函數,具體的:
bool b1 = pf("hello","goodbye");
bool b2 = (*pf)("hello","goodbye");//equivalent!
bool b3 = lengthCompare("hello","goodbye");//equivalent!
任意兩個不同的function type之間不存在轉換方法(廢話麼。。。),當然我們可以對任何function pointer賦值nullptr或0,來表示他們沒有指向任何函數。

Function Pointer Parameter

和數組一樣,函數的參數不能是一個函數,但卻可以是一個指向函數的指針。
void f(int,bool pf(int));//ok!第二個參數被認爲是函數指針
void f(int,bool (*pf)(int));//equivalent!顯式表明第二個參數是函數指針
使用type aliases和decltype能幫我們簡化代碼。
bool lengthCompare(int);

//Func,Func2是函數類型
typedef bool Func(int);
type decltype(lengthCompare) Func2;//equivalent

//FuncP,FuncP2是函數指針類型
type bool (*FuncP)(int);
type decltype(lengthCompare) *FuncP2;//equivalent
void f(int,Func);//這四個定義和之前的f()完全一樣
void f(int,Func2);
void f(int,FuncP);
void f(int,FuncP2);

Returning a Pointer to Function

和數組一樣,函數的返回類型不能是一個函數,但可以是一個函數指針。
using F = int(int*,int);//F是函數類型,不是指針
using PF = int(*)(int*,int);//PF函數指針類型
記住和參數列表中不同,參數列表中一個函數類型會被自動轉換成函數指針類型,但是返回類型卻不會!我們必須使用一個顯式的函數指針類型來定義函數的返回類型。
PF f(int);//ok
F f(int);//error!
F* f(int);//ok!
當然,我們也可以顯式地定義這樣的函數。
int (*f(int)) (int*,int);
另外,也可以用拖尾返回類型(trailing return)來定義。
auto f(int) ->int(*)(int*,int);

Using auto or decltype for Function Pointer Types

最後,如果我們已知一個函數,然後想使用這個函數的類型作爲函數指針,那麼可以使用decltype或auto來幫助完成。
使用decltype:
int sumLength(string,string);
decltype(sumLength) *f(int);
需要注意的是,decltype返回的是函數類型,所以我們需要在函數名前加一個*來表示返回函數指針,上面例子中如果缺少了*,則會報錯。
或者使用auto和decltype,不過似乎有些多此一舉:
auto f(int) -> decltype(sumLength)*;
同樣,這裏最後的*也不能省略。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章