引用傳遞和值傳遞
1. 值傳遞
當(dāng)形參是非引用類型時(shí),實(shí)參的值會(huì)被拷貝給形參,實(shí)參和形參是兩個(gè)完全不同的對(duì)象,函數(shù)對(duì)形參做的所有操作都不會(huì)影響實(shí)參。
Tips:當(dāng)形參是指針類型時(shí),形參和實(shí)參也是兩個(gè)完全不同的指針,只不過他們指向同個(gè)對(duì)象。因?yàn)橹羔樖刮覀兛梢蚤g接地訪問它所指向的對(duì)象,因此通過指針可以修改它所指對(duì)象的值。
熟悉C語言的程序員常常使用指針類型的形參訪問函數(shù)外部的對(duì)象,在C++語言中,建議使用引用類型的形參替代指針。
2. 引用傳遞
Tips:如果函數(shù)無須改變引用形參的值,那么最好將其聲明為常量引用。
當(dāng)形參是引用類型時(shí),我們說它對(duì)應(yīng)的實(shí)參被引用傳遞,使用引用傳遞的原因在于:
- 拷貝大的類類型對(duì)象或者容器對(duì)象比較低效,甚至有的類類型(包括IO類型和數(shù)組等)根本就不支持拷貝操作,因此只能通過引用形參來訪問該類型的對(duì)象
- 引用形參可以實(shí)現(xiàn)多返回值(當(dāng)然指針形參也可以實(shí)現(xiàn)這個(gè)功能)
const形參和實(shí)參
1. 忽略形參的頂層const
Tips:常量引用和非常量引用參數(shù)是可以重載的,因?yàn)檫@個(gè)時(shí)候是底層const而非頂層const。
當(dāng)用實(shí)參初始化形參時(shí)會(huì)忽略形參的頂層const,即當(dāng)形參有頂層const時(shí),傳給它常量對(duì)象或者非常量對(duì)象都是可以的:
// 既可以給fcn傳入const int, 也可以傳入int
void fcn(const int i);
// 錯(cuò)誤: fcn(const int i)忽略了頂層const, 相當(dāng)于重復(fù)定義了fcn(int)
void fcn(int i);
2. 指針或引用形參與const
前面提到頂層const是不可以實(shí)現(xiàn)重載的,因?yàn)閷?shí)參初始化形參時(shí)會(huì)忽略掉頂層const。由于我們可以用非常量初始化一個(gè)底層const對(duì)象,但是反過來不行,因此常量引用和非常量引用是可以重載的。
3. 形參盡量使用常量引用
Tips:一個(gè)普通的引用必須用同類型的對(duì)象初始化,我們不能將需要類型轉(zhuǎn)換的對(duì)象傳遞給普通的引用形參。
把函數(shù)不會(huì)改變的形參定義成普通的引用是一種比較常見的錯(cuò)誤,這么做給函數(shù)的調(diào)用者一種誤導(dǎo),即函數(shù)可以修改它的實(shí)參的值。另外使用引用而非常量引用也會(huì)極大地限制函數(shù)所能接受的實(shí)參類型(普通引用形參無法接受const對(duì)象、字面值或者需要類型轉(zhuǎn)換的對(duì)象)。
數(shù)組形參
1. 傳遞數(shù)組形參
數(shù)組的兩個(gè)特殊性質(zhì)對(duì)我們定義和使用作用在數(shù)組上的函數(shù)有影響:
- 不允許拷貝數(shù)組:無法以值傳遞的方式使用數(shù)組參數(shù)
- 使用數(shù)組時(shí)會(huì)將其轉(zhuǎn)換成指針:當(dāng)我們?yōu)楹瘮?shù)傳遞一個(gè)數(shù)組時(shí),實(shí)際上傳遞的是指向數(shù)組首元素的指針
盡管不能以值傳遞的方式傳遞數(shù)組,但是我們把形參寫成類似數(shù)組的形式:
// 下面三個(gè)函數(shù)等價(jià), 都接受const int*類型的形參
void print(const int*);
void print(const int[]);
void print(const int[10]); // 這里的維度表示我們期望數(shù)組含有多少個(gè)元素, 實(shí)際上不一定
2. 傳遞數(shù)組形參大小
由于數(shù)組是以指針的形式傳遞給函數(shù)的,所以函數(shù)并不知道數(shù)組的確切尺寸,調(diào)用者一般需要提供一些額外的信息。管理數(shù)組形參通常有三種技術(shù):
2.1 數(shù)組中包含結(jié)束標(biāo)記(一般只有C風(fēng)格字符串)
第一種方法要求數(shù)組本身包含一個(gè)結(jié)束標(biāo)記,最典型的例子是C風(fēng)格字符串,接受C風(fēng)格字符串的函數(shù)在遇到空字符時(shí)就會(huì)停止:
void print(const char *cp) {
if (cp) { // cp不是空指針
while (*cp) { // 指針?biāo)缸址皇强兆址? cout << *cp++; // 輸出當(dāng)前字符并將指針向前移動(dòng)一個(gè)位置
}
}
}
2.2 使用標(biāo)準(zhǔn)庫規(guī)范
Tips:標(biāo)準(zhǔn)庫begin和end函數(shù)可以返回?cái)?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 顯式傳遞一個(gè)表示數(shù)組大小的形參
第三種方法是專門定義一個(gè)表示數(shù)組大小的形參:
// const int ia[]等價(jià)于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
當(dāng)函數(shù)不需要對(duì)數(shù)組元素執(zhí)行寫操作時(shí),數(shù)組形參應(yīng)該是指向const的指針。只有當(dāng)函數(shù)確實(shí)要改變?cè)刂档臅r(shí)候,才把形參定義成指向常量的指針。
4. 數(shù)組引用形參
Tips:當(dāng)形參是數(shù)組的引用時(shí),維度也是類型的一部分。
C++語言允許將變量定義為數(shù)組的引用:
// 形參是數(shù)組的引用, 維度是類型的一部分
void print(int (&arr)[10]) {
for (auto elem : arr) {
cout << elem << endl;
}
}
注意arr兩邊的括號(hào)是必不可少的:
f(int &arr[10]); // 錯(cuò)誤: 將arr聲明成了引用的數(shù)組
f(int (&arr)[10]); // 正確: arr是具有10個(gè)整數(shù)的整型數(shù)組的引用
由于數(shù)組的大小是構(gòu)成數(shù)組類型的一部分,所以只要不超過維度,在函數(shù)體內(nèi)我們可以放心地使用數(shù)組。但是這一用法也無形中限制了print函數(shù)的可用性,我們只能將函數(shù)作用于維度為10的數(shù)組。
5. 傳遞多維數(shù)組
前面我們提到過C++中并沒有真正的多維數(shù)組,所謂的數(shù)組其實(shí)是數(shù)組的數(shù)組。和所有的數(shù)組一樣,當(dāng)我們把多維數(shù)組傳遞給函數(shù)時(shí),實(shí)際上傳遞的是指向數(shù)組首元素的指針,即一個(gè)指向數(shù)組的指針。
Tips:由于數(shù)組第二維以及后面的維度的大小都是數(shù)組類型的一部分,因此傳遞多維數(shù)組時(shí)不能省略。
// matrix是指向含有10個(gè)整數(shù)的數(shù)組的指針
void print(int (*matrix)[10], int rowSize);
// 等價(jià)定義
// 由于編譯器會(huì)忽略掉第一個(gè)維度, 因此最好不要把它包含在形參列表內(nèi)
void print(int matrix[][10], int rowSize);
main函數(shù)處理命令行選項(xiàng)
假設(shè)我們的可執(zhí)行文件名為prog,我們可以向程序傳遞如下選項(xiàng):
prog -d -o ofile data0
這些選項(xiàng)會(huì)通過兩個(gè)可選的形參傳遞給main函數(shù):
int main(int argc, char *argv[]);
// 等價(jià)
int main(int argc, char** argv);
其中第二個(gè)形參argv是一個(gè)數(shù)組,它的元素是指向C風(fēng)格字符串的指針,第一個(gè)形參argc表示數(shù)組中字符串的數(shù)量。
當(dāng)實(shí)參傳遞給main函數(shù)之后,argv第一個(gè)元素指向程序的名字或者一個(gè)空字符串,接下來的元素依次傳遞命令行提供的實(shí)參。最后一個(gè)指針之后的元素值保證為0。
在前面的例子中,argc等于5,argv指向的類型如下:
argv[0] = "prog"; // 或者一個(gè)空字符串
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
可變形參
1. 支持可變形參的三種方法
有時(shí)候我們無法知道應(yīng)該向函數(shù)提供幾個(gè)實(shí)參,為了編寫能處理不同數(shù)量實(shí)參的函數(shù),C++11新標(biāo)準(zhǔn)提供了兩種主要的方法:
- 如果所有的實(shí)參類型相同,傳遞名為
initializer_list的標(biāo)準(zhǔn)庫類型 - 如果實(shí)參的類型不同,可以編寫可變參數(shù)模板(TODO:p618頁介紹)
C++還提供了一種特殊的形參類型(即省略符),可以用于傳遞可變數(shù)量的實(shí)參,不過這種功能一般只用于與C函數(shù)交互的接口程序。
2. initializer_list形參
Tips:
initializer_list對(duì)象中的元素永遠(yuǎn)都是常量。
如果函數(shù)的實(shí)參數(shù)量未知但是全部實(shí)參的類型都相同,我們可以使用initializer_list類型的形參。和vector一樣,initializer_list也是一種模板類型,但是initializer_list對(duì)象中的元素永遠(yuǎn)都是常量值,我們是無法改變的。
#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)在形參列表的最后一個(gè)位置,并且僅僅用于C和C++通用的類型。
省略符形參是為了便于C++程序訪問某些特殊的C代碼而設(shè)置的,這些代碼使用了名為varargs的C標(biāo)準(zhǔn)庫功能。
4. 可變參數(shù)函數(shù)模板
可變參數(shù)函數(shù)模板指的是接收可變數(shù)目參數(shù)的模板函數(shù)??勺償?shù)目的參數(shù)被稱為參數(shù)包,包括兩種參數(shù)包:
- 模板參數(shù)包:表示零個(gè)或多個(gè)模板參數(shù)
- 函數(shù)參數(shù)包:表示零個(gè)或多個(gè)函數(shù)參數(shù)
// Args: 模板參數(shù)包
// rest: 函數(shù)參數(shù)包
template <typename T, typename... Args>
void foo(const T &t, const Args&... rest);
4.1 sizeof...運(yùn)算符
我們可以使用sizeof...運(yùn)算符來獲取參數(shù)包中元素個(gè)數(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ù)模板通常是遞歸的。當(dāng)定義可變參數(shù)版本的print時(shí),非可變參數(shù)版本的聲明必須在作用域中,否則可變參數(shù)版本會(huì)無限遞歸。
// 用來終止遞歸并打印最后一個(gè)元素的函數(shù), 必須在可變參數(shù)版本的print定義之前聲明
template <typename T>
ostream &print(ostream &os, const T &t) {
return os << t;
}
// 包中除最后一個(gè)元素之外的其他元素都會(huì)調(diào)用這個(gè)版本的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