C++函數(shù)參數(shù)傳遞

引用傳遞和值傳遞

1. 值傳遞

當形參是非引用類型時枫甲,實參的值會被拷貝給形參,實參和形參是兩個完全不同的對象揭芍,函數(shù)對形參做的所有操作都不會影響實參眨唬。

Tips:當形參是指針類型時,形參和實參也是兩個完全不同的指針拨黔,只不過他們指向同個對象蛔溃。因為指針使我們可以間接地訪問它所指向的對象,因此通過指針可以修改它所指對象的值篱蝇。

熟悉C語言的程序員常常使用指針類型的形參訪問函數(shù)外部的對象贺待,在C++語言中,建議使用引用類型的形參替代指針零截。

2. 引用傳遞

Tips:如果函數(shù)無須改變引用形參的值麸塞,那么最好將其聲明為常量引用。

當形參是引用類型時涧衙,我們說它對應的實參被引用傳遞哪工,使用引用傳遞的原因在于:

  • 拷貝大的類類型對象或者容器對象比較低效奥此,甚至有的類類型(包括IO類型和數(shù)組等)根本就不支持拷貝操作,因此只能通過引用形參來訪問該類型的對象
  • 引用形參可以實現(xiàn)多返回值(當然指針形參也可以實現(xiàn)這個功能)

const形參和實參

1. 忽略形參的頂層const

Tips:常量引用和非常量引用參數(shù)是可以重載的正勒,因為這個時候是底層const而非頂層const得院。

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

// 既可以給fcn傳入const int, 也可以傳入int
void fcn(const int i);

// 錯誤: fcn(const int i)忽略了頂層const, 相當于重復定義了fcn(int)
void fcn(int i);

2. 指針或引用形參與const

前面提到頂層const是不可以實現(xiàn)重載的祥绞,因為實參初始化形參時會忽略掉頂層const。由于我們可以用非常量初始化一個底層const對象鸭限,但是反過來不行蜕径,因此常量引用和非常量引用是可以重載的。

3. 形參盡量使用常量引用

Tips:一個普通的引用必須用同類型的對象初始化败京,我們不能將需要類型轉(zhuǎn)換的對象傳遞給普通的引用形參兜喻。

把函數(shù)不會改變的形參定義成普通的引用是一種比較常見的錯誤,這么做給函數(shù)的調(diào)用者一種誤導赡麦,即函數(shù)可以修改它的實參的值朴皆。另外使用引用而非常量引用也會極大地限制函數(shù)所能接受的實參類型(普通引用形參無法接受const對象、字面值或者需要類型轉(zhuǎn)換的對象)泛粹。

數(shù)組形參

1. 傳遞數(shù)組形參

數(shù)組的兩個特殊性質(zhì)對我們定義和使用作用在數(shù)組上的函數(shù)有影響:

  • 不允許拷貝數(shù)組:無法以值傳遞的方式使用數(shù)組參數(shù)
  • 使用數(shù)組時會將其轉(zhuǎn)換成指針:當我們?yōu)楹瘮?shù)傳遞一個數(shù)組時遂铡,實際上傳遞的是指向數(shù)組首元素的指針

盡管不能以值傳遞的方式傳遞數(shù)組,但是我們把形參寫成類似數(shù)組的形式:

// 下面三個函數(shù)等價, 都接受const int*類型的形參
void print(const int*);
void print(const int[]);
void print(const int[10]);  // 這里的維度表示我們期望數(shù)組含有多少個元素, 實際上不一定

2. 傳遞數(shù)組形參大小

由于數(shù)組是以指針的形式傳遞給函數(shù)的晶姊,所以函數(shù)并不知道數(shù)組的確切尺寸扒接,調(diào)用者一般需要提供一些額外的信息。管理數(shù)組形參通常有三種技術:

2.1 數(shù)組中包含結(jié)束標記(一般只有C風格字符串)

第一種方法要求數(shù)組本身包含一個結(jié)束標記们衙,最典型的例子是C風格字符串钾怔,接受C風格字符串的函數(shù)在遇到空字符時就會停止:

void print(const char *cp) {
    if (cp) {               // cp不是空指針
        while (*cp) {       // 指針所指字符不是空字符
            cout << *cp++;  // 輸出當前字符并將指針向前移動一個位置
        }
    }
}
2.2 使用標準庫規(guī)范

Tips:標準庫begin和end函數(shù)可以返回數(shù)組的首元素指針和尾后元素指針。

第二種方式是傳遞指向數(shù)組首元素和尾后元素的指針:

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

int j[2] = {0, 1};
print(begin(j), end(j));
2.3 顯式傳遞一個表示數(shù)組大小的形參

第三種方法是專門定義一個表示數(shù)組大小的形參:

// const int ia[]等價于const int *ia
// size表示數(shù)組的大小
void print(const int ia[], size_t size) {
    for (size_t i = 0; i != size; ++i) {
        cout << ia[i] << endl;
    }
}

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

3. 數(shù)組形參與const

當函數(shù)不需要對數(shù)組元素執(zhí)行寫操作時蒙挑,數(shù)組形參應該是指向const的指針宗侦。只有當函數(shù)確實要改變元素值的時候,才把形參定義成指向常量的指針忆蚀。

4. 數(shù)組引用形參

Tips:當形參是數(shù)組的引用時凝垛,維度也是類型的一部分。

C++語言允許將變量定義為數(shù)組的引用:

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

注意arr兩邊的括號是必不可少的:

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

由于數(shù)組的大小是構(gòu)成數(shù)組類型的一部分蜓谋,所以只要不超過維度梦皮,在函數(shù)體內(nèi)我們可以放心地使用數(shù)組。但是這一用法也無形中限制了print函數(shù)的可用性桃焕,我們只能將函數(shù)作用于維度為10的數(shù)組剑肯。

5. 傳遞多維數(shù)組

前面我們提到過C++中并沒有真正的多維數(shù)組,所謂的數(shù)組其實是數(shù)組的數(shù)組观堂。和所有的數(shù)組一樣让网,當我們把多維數(shù)組傳遞給函數(shù)時呀忧,實際上傳遞的是指向數(shù)組首元素的指針,即一個指向數(shù)組的指針溃睹。

Tips:由于數(shù)組第二維以及后面的維度的大小都是數(shù)組類型的一部分而账,因此傳遞多維數(shù)組時不能省略。

// matrix是指向含有10個整數(shù)的數(shù)組的指針
void print(int (*matrix)[10], int rowSize);

// 等價定義
// 由于編譯器會忽略掉第一個維度, 因此最好不要把它包含在形參列表內(nèi)
void print(int matrix[][10], int rowSize);

main函數(shù)處理命令行選項

假設我們的可執(zhí)行文件名為prog因篇,我們可以向程序傳遞如下選項:

prog -d -o ofile data0

這些選項會通過兩個可選的形參傳遞給main函數(shù):

int main(int argc, char *argv[]);

// 等價
int main(int argc, char** argv);

其中第二個形參argv是一個數(shù)組泞辐,它的元素是指向C風格字符串的指針,第一個形參argc表示數(shù)組中字符串的數(shù)量竞滓。

當實參傳遞給main函數(shù)之后咐吼,argv第一個元素指向程序的名字或者一個空字符串,接下來的元素依次傳遞命令行提供的實參商佑。最后一個指針之后的元素值保證為0锯茄。

在前面的例子中,argc等于5茶没,argv指向的類型如下:

argv[0] = "prog";  // 或者一個空字符串
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

可變形參

1. 支持可變形參的三種方法

有時候我們無法知道應該向函數(shù)提供幾個實參肌幽,為了編寫能處理不同數(shù)量實參的函數(shù),C++11新標準提供了兩種主要的方法:

  • 如果所有的實參類型相同抓半,傳遞名為initializer_list的標準庫類型
  • 如果實參的類型不同喂急,可以編寫可變參數(shù)模板(TODO:p618頁介紹)

C++還提供了一種特殊的形參類型(即省略符),可以用于傳遞可變數(shù)量的實參琅关,不過這種功能一般只用于與C函數(shù)交互的接口程序。

2. initializer_list形參

Tips:initializer_list對象中的元素永遠都是常量讥蔽。

如果函數(shù)的實參數(shù)量未知但是全部實參的類型都相同涣易,我們可以使用initializer_list類型的形參。和vector一樣冶伞,initializer_list也是一種模板類型新症,但是initializer_list對象中的元素永遠都是常量值,我們是無法改變的响禽。

#include <initializer_list>
#include <string>
#include <iostream>

void print(std::initializer_list<std::string> list) {
    for (auto it = list.begin(); it != list.end(); ++it) {
        std::cout << *it << std::endl;
    }
}

int main() {
    print({"tomo", "cat", "tomocat"});
}


3. 省略符形參

Tips:省略符形參只能出現(xiàn)在形參列表的最后一個位置徒爹,并且僅僅用于C和C++通用的類型。

省略符形參是為了便于C++程序訪問某些特殊的C代碼而設置的芋类,這些代碼使用了名為varargs的C標準庫功能隆嗅。

4. 可變參數(shù)函數(shù)模板

可變參數(shù)函數(shù)模板指的是接收可變數(shù)目參數(shù)的模板函數(shù)『罘保可變數(shù)目的參數(shù)被稱為參數(shù)包胖喳,包括兩種參數(shù)包:

  • 模板參數(shù)包:表示零個或多個模板參數(shù)
  • 函數(shù)參數(shù)包:表示零個或多個函數(shù)參數(shù)
// Args: 模板參數(shù)包
// rest: 函數(shù)參數(shù)包
template <typename T, typename... Args>
void foo(const T &t, const Args&... rest);
4.1 sizeof...運算符

我們可以使用sizeof...運算符來獲取參數(shù)包中元素個數(shù):

template <typename... Args> void bar(Args... args) {
    cout << sizeof...(Args) << endl;  // 類型參數(shù)的數(shù)目
    cout << sizeof...(args) << endl;  // 函數(shù)參數(shù)的數(shù)目
}
4.2 編寫可變參數(shù)函數(shù)模板

Tips:可變參數(shù)函數(shù)模板通常是遞歸的。當定義可變參數(shù)版本的print時贮竟,非可變參數(shù)版本的聲明必須在作用域中丽焊,否則可變參數(shù)版本會無限遞歸较剃。

// 用來終止遞歸并打印最后一個元素的函數(shù), 必須在可變參數(shù)版本的print定義之前聲明
template <typename T>
ostream &print(ostream &os, const T &t) {
    return os << t;
}

// 包中除最后一個元素之外的其他元素都會調(diào)用這個版本的print
template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest) {
    os << t << ", ";
    return print(os, rest...);
}

Reference

[1] C++ Primer

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市技健,隨后出現(xiàn)的幾起案子写穴,更是在濱河造成了極大的恐慌,老刑警劉巖雌贱,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啊送,死亡現(xiàn)場離奇詭異,居然都是意外死亡帽芽,警方通過查閱死者的電腦和手機删掀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來导街,“玉大人披泪,你說我怎么就攤上這事“峁澹” “怎么了款票?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長泽论。 經(jīng)常有香客問我艾少,道長,這世上最難降的妖魔是什么翼悴? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任缚够,我火速辦了婚禮,結(jié)果婚禮上鹦赎,老公的妹妹穿的比我還像新娘谍椅。我一直安慰自己,他們只是感情好古话,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布雏吭。 她就那樣靜靜地躺著,像睡著了一般陪踩。 火紅的嫁衣襯著肌膚如雪杖们。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天肩狂,我揣著相機與錄音摘完,去河邊找鬼。 笑死傻谁,一個胖子當著我的面吹牛描焰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼荆秦,長吁一口氣:“原來是場噩夢啊……” “哼篱竭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起步绸,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤掺逼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓤介,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吕喘,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年刑桑,在試婚紗的時候發(fā)現(xiàn)自己被綠了氯质。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡闻察,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琢锋,到底是詐尸還是另有隱情辕漂,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布跋涣,位于F島的核電站遣臼,受9級特大地震影響揍堰,放射性物質(zhì)發(fā)生泄漏之碗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一峰尝、第九天 我趴在偏房一處隱蔽的房頂上張望伦意。 院中可真熱鬧熏矿,春花似錦缆八、人聲如沸吊趾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽识颊。三九已至,卻和暖如春抠艾,著一層夾襖步出監(jiān)牢的瞬間首懈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工脸狸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留最仑,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓炊甲,卻偏偏與公主長得像泥彤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子卿啡,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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