C++ 函數2

這章再深入的講些函數相關的。

1.C++內聯函數

編譯器對內聯的函數處理就是將相應的函數的代碼直接替換掉對應函數的調用。對于內聯代碼,程序無需跳到另一個位置處執行代碼,再調回來。因此,內聯函數的運行速度比常規函數調用稍快,但是更占用內存。如果程序在10個不同的位置調用同一個函數,則該程序將包含該函數代碼的10個備份。
應有選擇的地使用內聯函數。如果執行函數代碼的時間比處理函數調用機制的時間長,則節省的時間只占整個過程的很小一部分。如果代碼執行時間很短,則內聯調用就可以節省非內聯調用使用的大部分時間。另一方面,由于這個過程相當快,因此盡管節省了該過程的大部分時間,但節省的時間的絕對值并不大,除非該函數經常被調用。
要使用這項特別,必須采取如下措施之一:

  • 在函數聲明前加上關鍵字inline;
  • 在函數定義前加上關鍵字inline。

通常的做法是省略原型,將整個定義放在本該提供原型的地方。
當函數過大或者函數有遞歸自己的時候,不應該將此函數聲明為內聯函數。
下面簡單舉個栗子:

inline int square(int a) {
    return a * a;
}

int main() {
    int aa = square(5);
    cout << "aa:" << aa << endl;
    return 0;
}

inline工具是C++新增的特性。C語言使用預處理語句#define來提供宏——內聯代碼的原始實現。例如:

#define SQUARE(X) X*X

這并不是通過傳遞參數實現的,而是通過文本替換來實現的。

a = SQUARE(5);// 實際是 a = 5*5
b = SQUARE(1+5);// 實際是 b = 1+5 * 1+5
c = SQUARE(a++);//實際是 a++ * a++

上面只有第一個能正常工作,即使通過改進宏定義

#define SQUARE(X) ((X)*(X))

對應第三個仍然是錯誤的。所以在c++中可以考慮使用將以前用宏定義的函數改為內聯函數實現。

2.引用變量
2.1 創建引用變量

引用是已定義的變量的別名。使用方法如下

int a = 10;
int &b = a;//b是a變量的別名

其中&不是地址運算符,而是類型標識符的一部分。比如char*表示char類型的指針,int&表示int類型的引用。上述聲明表示a和b指向相同的值和內存單元。舉個詳細的栗子:

int main() {
    int a = 10;
    int &b = a;

    //通過b修改變量
    b = 20;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;

    //通過a修改變量
    a = 30;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;

    //查看他倆的地址
    cout << "a addr:" << &a << endl;
    cout << "b addr:" << &b << endl;

    return 0;
}

輸出為

a:20
b:20
a:30
b:30
a addr:0x7fff59892938
b addr:0x7fff59892938

引用變量與指針的差別之一是,必須在聲明引用時將其初始化,如

int a = 10;
int *b;
b = &a;//可以
int &c = a;//可以
int &b;//不可以
b = a

因此引用更接近const指針,必須在創建時初始化,并且一旦關聯起來,就一直效忠于它。

2.2 將引用用作函數參數

引用經常被用作函數參數,這種傳遞參數的方法被稱為按引用傳遞。按引用傳遞允許被調用的函數訪問調用函數中的變量。這種方式和C的按指針傳遞類似。舉個例子:

#include <iostream>

using namespace std;

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void swap(int &a, int &b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int a = 10;
    int b = 5;

    //按指針傳遞
    swap(&a, &b);
    cout << "a:" << a << ",b:" << b << endl;


    a = 16;
    b = 25;

    //按引用傳遞
    swap(a, b);
    cout << "a:" << a << ",b:" << b << endl;

    return 0;
}
2.3 引用的屬性和特別之處

首先如果意圖是不允許函數修改參數,還想使用引用,則可以用常量引用,例子:

double refcube(const int & n);//n是一個引用,引用的是一個整形常量

上面只是個例子,對于數據比較小的情況,應該使用按值傳遞的方式,只有在數據較大的情況再使用常量引用(既不能通過引用修改原數據,又可以避免大量的數據創建拷貝)。

以下是Primer原文的總結,但在實際實驗結果和描述不一致,可能是現代編譯器更智能了吧。

當函數參數為常量引用時,如果實參與引用參數類型不匹配,C++會生成臨時變量。注意的是參數必須是聲明為常量引用的時候,且類型不匹配才會創建臨時變量,否則編譯器報錯。例子:

#include <iostream>

using namespace std;

int square(const int &n) {
    return n * n;
}

int square2(int &n) {
    return n * n;
}

int main() {
    long a = 10L;
    square(a);//不報錯,因為會創建臨時變量,臨時變量是long型的10轉換為int型5
    square2(a);//報錯,類型不匹配
    
    return 0;
}

創建臨時變量的情況有

  • 實參的類型正確,但不是左值;
  • 實參的類型不正確,但是可以轉換為正確的類型。
    什么是左值呢,很多,所以就說什么不是左值吧,如101、“string”這種字面量就不是左值,或者 1+1 這種表達式也不是左值。

    那為什么只有常量引用才可以創建臨時變量?還是例子:
#include <iostream>

using namespace std;

void swap(int &a, int &b) {
    int tmp = a;
    a = b;
    b = a;
}

int main() {
    long a = 10L;
    long b = 12L;
    swap(a, b);
    cout << "a:" << a << ",b:" << b << endl;
    return 0;
}

實際打印結果:

a:12,b:10

==先說下原文的意思:如果允許創建臨時變量則引用就失效了,臨時比那兩交換不代表原變量交換。但是實際結果是的確交換了,所以這塊還得再研究下。我的測試環境是64位Mac OS,IDE是CLion。==
應該盡可能使用常量引用,原因如下:

  • 使用const引用可以避免無意中修改數據的編程錯誤;
  • 使用const引用使函數能夠處理const和非const實參,否則將只能接受非const實參;
  • 使用const引用使函數能夠正確生成并使用臨時變量。
2.4 將引用用于結構

沒啥太多說的,返回引用時要注意一點,應避免函數終止時不再存在內存單元引用。

2.5 何時使用引用參數

使用引用參數的主要原因有兩個:

  • 程序員能夠修改調用函數中的數據對象;
  • 通過傳遞引用而不是整個數據對象,可以提高程序的運行速度。

對于使用傳遞的值而不作修改的函數:

  • 如果數據對象很小,如內置數據類型或小型結構,則按值傳遞。
  • 如果數據對象是數組,則使用指針,因為這是唯一的選擇,并將指針聲明為指向const的指針。
  • 如果數據對象是較大的結構,則使用const指針或const引用,以提高程序的效率。這樣可以節省復制結構所需的時間和空間。
  • 如果數據對象是類對象,則使用const引用。類設計的語義常常要求使用引用,這是C++新增這項特性的主要原因。因此,傳遞類對象參數的標準方式是按引用傳遞。

對于修改調用函數中數據的函數:

  • 如果數據對象是內置數據類型,則使用指針。如果看到諸如fixit(&x)這樣的代碼(其中x是int),則很明顯,該函數將修改X。
  • 如果數據對象是數組,則只能使用指針。
  • 如果數據對象是結構,則使用引用或指針。
  • 如果數據對象是類對象,則使用引用。
3.默認參數

栗子:

#include <iostream>

using namespace std;

int processInt(int a, int b = 1) {
    return a * b;
}

int main() {
    cout << "result1:" << processInt(10) << endl;
    cout << "result2:" << processInt(10, 3) << endl;
    return 0;
}

輸出結果為:

result1:10
result2:30

需要注意的是:==對于帶參數列表的函數,必須從右向左添加默認值==。

4.函數重載

概念不說了。函數重載的掛件是函數的參數列表——也成為函數特征標。跟返回值沒關系。注意下兩個函數原型

double process(int a,int b);
double process(int &a,int &b);

這兩個函數是沖突的。
函數匹配時,并不區分const和非const變量。

5.函數模板

就是泛型的使用,栗子:

template<typename _T>
void swap(_T &a, _T &b);


int main() {
    using std::cout;
    using std::endl;

    int a = 10, b = 5;
    float c = 11.1f, d = 12.2f;

    swap(a, b);
    cout << "a:" << a << ",b:" << b << endl;

    swap(c, d);
    cout << "c:" << c << ",d:" << d << endl;

    return 0;
}

template<typename _T>
void swap(_T &a, _T &b) {
    _T tmp = a;
    a = b;
    b = tmp;
}

輸出

a:5,b:10
c:12.2,d:11.1

==注意,在mac clion上使用的時候,不要在執行swap的之前寫類似using namespace std;這種代碼,因為std空間中自帶了個swap的函數,參數列表也是兩個引用類型的模板。==

5.1 重載的模板

很簡單,看栗子:

#include <iostream>

template<typename _T>
void swap(_T &a, _T &b);

template<typename T>
void swap(T *a, T *b);

int main() {
    using std::cout;
    using std::endl;

    int a = 10, b = 5;
    float c = 11.1f, d = 12.2f;

    swap(a, b);
    cout << "a:" << a << ",b:" << b << endl;

    swap(&c, &d);
    cout << "c:" << c << ",d:" << d << endl;

    return 0;
}

//傳遞引用的swap
template<typename _T>
void swap(_T &a, _T &b) {
    _T tmp = a;
    a = b;
    b = tmp;
}

//傳遞指針的swap
template<typename T>
void swap(T *a, T *b) {
    T tmp = *a;
    *a = *b;
    *b = tmp;
}

輸出

a:5,b:10
c:12.2,d:11.1
5.2 模板的局限性

還是看上面swap的栗子:

template<typename _T>
void swap(_T a, _T b);
{……}

如果傳入函數的是兩個數組,則在函數能不能執行a=b這種,也不能執行a>b,a*b等等。

5.3 顯示具體化

這個話題比較有意思,從來沒想過,舉個栗子,假設有如下結構體

struct Person{
    int age;
    char name[40];
    char id[18];
}

例外需要有個交換的函數swap,則swap和之前寫的一樣,會交換兩個Person的所有屬性,但是如果我們希望有個函數值交換id和name,不交換age,則同樣需要聲明一樣的函數swap(T &a,T &b),就產生沖突了。這個時候可以提供一個具體化函數定義——稱為顯式具體化,其中包含所需的代碼。
C++98有如下定義和規則:

  • 對于給定的函數名,可以有非模板函數、模板函數和顯式具體化模板函數以及他們的重載版本。
  • 顯式具體化的原型和定義應該以template<>開頭,并通過名稱來指出類型。
  • 具體化優先于常規模板,非模板函數優先于具體化和模板。
    舉個栗子,都是原型:
void swap(Person &,Person &);//非模板函數

//模板函數
template <Typename T>
void swap(T &,T &);

//具體化模板函數
template <> void swap<Person>(Person &,Person &);
//具體化也可以這么寫,省略函數名后的Person,因為參數列表已經表明了
template <> void swap(Person &,Person &);
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容