引用傳遞和值傳遞
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