C++ 函數(shù)2

這章再深入的講些函數(shù)相關的踪宠。

1.C++內(nèi)聯(lián)函數(shù)

編譯器對內(nèi)聯(lián)的函數(shù)處理就是將相應的函數(shù)的代碼直接替換掉對應函數(shù)的調(diào)用窘茁。對于內(nèi)聯(lián)代碼,程序無需跳到另一個位置處執(zhí)行代碼壁涎,再調(diào)回來凡恍。因此,內(nèi)聯(lián)函數(shù)的運行速度比常規(guī)函數(shù)調(diào)用稍快怔球,但是更占用內(nèi)存嚼酝。如果程序在10個不同的位置調(diào)用同一個函數(shù),則該程序?qū)摵瘮?shù)代碼的10個備份竟坛。
應有選擇的地使用內(nèi)聯(lián)函數(shù)闽巩。如果執(zhí)行函數(shù)代碼的時間比處理函數(shù)調(diào)用機制的時間長,則節(jié)省的時間只占整個過程的很小一部分担汤。如果代碼執(zhí)行時間很短涎跨,則內(nèi)聯(lián)調(diào)用就可以節(jié)省非內(nèi)聯(lián)調(diào)用使用的大部分時間。另一方面崭歧,由于這個過程相當快隅很,因此盡管節(jié)省了該過程的大部分時間,但節(jié)省的時間的絕對值并不大率碾,除非該函數(shù)經(jīng)常被調(diào)用叔营。
要使用這項特別,必須采取如下措施之一:

  • 在函數(shù)聲明前加上關鍵字inline播掷;
  • 在函數(shù)定義前加上關鍵字inline审编。

通常的做法是省略原型撼班,將整個定義放在本該提供原型的地方歧匈。
當函數(shù)過大或者函數(shù)有遞歸自己的時候,不應該將此函數(shù)聲明為內(nèi)聯(lián)函數(shù)砰嘁。
下面簡單舉個栗子:

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

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

inline工具是C++新增的特性件炉。C語言使用預處理語句#define來提供宏——內(nèi)聯(lián)代碼的原始實現(xiàn)。例如:

#define SQUARE(X) X*X

這并不是通過傳遞參數(shù)實現(xiàn)的矮湘,而是通過文本替換來實現(xiàn)的斟冕。

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++中可以考慮使用將以前用宏定義的函數(shù)改為內(nèi)聯(lián)函數(shù)實現(xiàn)磕蛇。

2.引用變量
2.1 創(chuàng)建引用變量

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

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

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

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指針再扭,必須在創(chuàng)建時初始化氧苍,并且一旦關聯(lián)起來,就一直效忠于它泛范。

2.2 將引用用作函數(shù)參數(shù)

引用經(jīng)常被用作函數(shù)參數(shù)让虐,這種傳遞參數(shù)的方法被稱為按引用傳遞。按引用傳遞允許被調(diào)用的函數(shù)訪問調(diào)用函數(shù)中的變量敦跌。這種方式和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 引用的屬性和特別之處

首先如果意圖是不允許函數(shù)修改參數(shù),還想使用引用柠傍,則可以用常量引用麸俘,例子:

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

上面只是個例子惧笛,對于數(shù)據(jù)比較小的情況从媚,應該使用按值傳遞的方式,只有在數(shù)據(jù)較大的情況再使用常量引用(既不能通過引用修改原數(shù)據(jù)患整,又可以避免大量的數(shù)據(jù)創(chuàng)建拷貝)拜效。

以下是Primer原文的總結(jié),但在實際實驗結(jié)果和描述不一致各谚,可能是現(xiàn)代編譯器更智能了吧紧憾。

當函數(shù)參數(shù)為常量引用時,如果實參與引用參數(shù)類型不匹配昌渤,C++會生成臨時變量赴穗。注意的是參數(shù)必須是聲明為常量引用的時候,且類型不匹配才會創(chuàng)建臨時變量膀息,否則編譯器報錯般眉。例子:

#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);//不報錯,因為會創(chuàng)建臨時變量潜支,臨時變量是long型的10轉(zhuǎn)換為int型5
    square2(a);//報錯甸赃,類型不匹配
    
    return 0;
}

創(chuàng)建臨時變量的情況有

  • 實參的類型正確,但不是左值冗酿;
  • 實參的類型不正確埠对,但是可以轉(zhuǎn)換為正確的類型络断。
    什么是左值呢,很多项玛,所以就說什么不是左值吧妓羊,如101、“string”這種字面量就不是左值稍计,或者 1+1 這種表達式也不是左值躁绸。

    那為什么只有常量引用才可以創(chuàng)建臨時變量?還是例子:
#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;
}

實際打印結(jié)果:

a:12,b:10

==先說下原文的意思:如果允許創(chuàng)建臨時變量則引用就失效了臣嚣,臨時比那兩交換不代表原變量交換并扇。但是實際結(jié)果是的確交換了骗露,所以這塊還得再研究下蚕涤。我的測試環(huán)境是64位Mac OS噪裕,IDE是CLion。==
應該盡可能使用常量引用怎虫,原因如下:

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

沒啥太多說的,返回引用時要注意一點徒扶,應避免函數(shù)終止時不再存在內(nèi)存單元引用粮彤。

2.5 何時使用引用參數(shù)

使用引用參數(shù)的主要原因有兩個:

  • 程序員能夠修改調(diào)用函數(shù)中的數(shù)據(jù)對象;
  • 通過傳遞引用而不是整個數(shù)據(jù)對象,可以提高程序的運行速度姜骡。

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

  • 如果數(shù)據(jù)對象很小导坟,如內(nèi)置數(shù)據(jù)類型或小型結(jié)構,則按值傳遞圈澈。
  • 如果數(shù)據(jù)對象是數(shù)組惫周,則使用指針,因為這是唯一的選擇康栈,并將指針聲明為指向const的指針递递。
  • 如果數(shù)據(jù)對象是較大的結(jié)構,則使用const指針或const引用谅将,以提高程序的效率漾狼。這樣可以節(jié)省復制結(jié)構所需的時間和空間重慢。
  • 如果數(shù)據(jù)對象是類對象饥臂,則使用const引用。類設計的語義常常要求使用引用似踱,這是C++新增這項特性的主要原因隅熙。因此稽煤,傳遞類對象參數(shù)的標準方式是按引用傳遞。

對于修改調(diào)用函數(shù)中數(shù)據(jù)的函數(shù):

  • 如果數(shù)據(jù)對象是內(nèi)置數(shù)據(jù)類型囚戚,則使用指針酵熙。如果看到諸如fixit(&x)這樣的代碼(其中x是int),則很明顯,該函數(shù)將修改X。
  • 如果數(shù)據(jù)對象是數(shù)組驰坊,則只能使用指針匾二。
  • 如果數(shù)據(jù)對象是結(jié)構,則使用引用或指針拳芙。
  • 如果數(shù)據(jù)對象是類對象察藐,則使用引用。
3.默認參數(shù)

栗子:

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

輸出結(jié)果為:

result1:10
result2:30

需要注意的是:==對于帶參數(shù)列表的函數(shù)舟扎,必須從右向左添加默認值==分飞。

4.函數(shù)重載

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

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

這兩個函數(shù)是沖突的。
函數(shù)匹配時羡疗,并不區(qū)分const和非const變量染服。

5.函數(shù)模板

就是泛型的使用,栗子:

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上使用的時候肌索,不要在執(zhí)行swap的之前寫類似using namespace std;這種代碼,因為std空間中自帶了個swap的函數(shù)特碳,參數(shù)列表也是兩個引用類型的模板诚亚。==

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

如果傳入函數(shù)的是兩個數(shù)組午乓,則在函數(shù)能不能執(zhí)行a=b這種站宗,也不能執(zhí)行a>b,a*b等等。

5.3 顯示具體化

這個話題比較有意思益愈,從來沒想過梢灭,舉個栗子,假設有如下結(jié)構體

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

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

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

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

//具體化模板函數(shù)
template <> void swap<Person>(Person &,Person &);
//具體化也可以這么寫,省略函數(shù)名后的Person,因為參數(shù)列表已經(jīng)表明了
template <> void swap(Person &,Person &);
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末成洗,一起剝皮案震驚了整個濱河市扎瓶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泌枪,老刑警劉巖概荷,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碌燕,居然都是意外死亡误证,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門修壕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愈捅,“玉大人,你說我怎么就攤上這事慈鸠±督鳎” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵青团,是天一觀的道長譬巫。 經(jīng)常有香客問我,道長督笆,這世上最難降的妖魔是什么芦昔? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮娃肿,結(jié)果婚禮上咕缎,老公的妹妹穿的比我還像新娘。我一直安慰自己料扰,他們只是感情好凭豪,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晒杈,像睡著了一般嫂伞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天末早,我揣著相機與錄音,去河邊找鬼说庭。 笑死然磷,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的刊驴。 我是一名探鬼主播姿搜,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捆憎!你這毒婦竟也來了舅柜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤躲惰,失蹤者是張志新(化名)和其女友劉穎致份,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體础拨,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡氮块,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诡宗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滔蝉。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖塔沃,靈堂內(nèi)的尸體忽然破棺而出蝠引,到底是詐尸還是另有隱情,我是刑警寧澤蛀柴,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布螃概,位于F島的核電站,受9級特大地震影響鸽疾,放射性物質(zhì)發(fā)生泄漏谅年。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一肮韧、第九天 我趴在偏房一處隱蔽的房頂上張望融蹂。 院中可真熱鬧,春花似錦弄企、人聲如沸超燃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽意乓。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間届良,已是汗流浹背笆凌。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留士葫,地道東北人乞而。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像慢显,于是被迫代替她去往敵國和親爪模。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容