1. 程式人生 > >C++ Primer 第六章筆記

C++ Primer 第六章筆記

Chapter 6 Functions

6.1 函式基礎

6.1.1 區域性物件

​ C++ 中,名字具有作用域,物件具有生命週期。

  • 名字的作用域是程式文字的一部分,名字在其中可見。
  • 物件的生命週期是程式執行過程中該物件存在的一段時間。

​ 形參和函式體內部定義的變數統稱為區域性變數(local variable)。它們對函式而言是 “區域性” 的,僅在函式的作用域內可見,同時區域性變數還會隱藏(hide)在外層作用域中同名的其他所有宣告中。

​ 在所有函式體之外定義的物件存在於程式的整個執行過程中。此類物件在程式啟動時被建立,直到程式結束才會銷燬。區域性變數的生命週期依賴於定義的方式。

自動物件

​ 我們把只存在於塊執行期間的物件稱為自動物件(automatic object)。當塊的執行過程結束後,塊中建立的自動物件的值就變成未定義的了。

​ 形參是一種自動物件。函式開始時為形參申請儲存空間,因為形參定義在函式體作用域之內,所以一旦函式終止,形參也被銷燬。

區域性靜態物件

​ 某些時候,有必要令區域性變數的生命週期貫穿函式呼叫之後及以後的時間。可以將區域性變數定義成 static 型別從而獲得這樣的物件。區域性靜態物件(local static object)在程式的執行路徑第一次經過物件定義語句時初始化,並且直到程式終止才被銷燬,再次期間即使物件所在的函式結束後執行也不會對它有什麼影響。

6.1.2 函式宣告

​ 函式的宣告和函式的定義非常類似,唯一的區別時函式宣告無須函式體,用一個分號代替即可。

在標頭檔案中進行函式宣告

​ 建議在標頭檔案中宣告,在原始檔中定義。與之類似,函式也應該在標頭檔案中宣告而非在原始檔中定義。

6.1.3 分離式編譯

編譯和連結多個原始檔

​ 舉個例子,假設 fact 函式的定義位於一個名為 fact.cc 的檔案中,它的宣告位於名為 Chapter6.h 的標頭檔案中。fact.cc 應該包含 Chapter6.h 標頭檔案。另外,我們在名為 factMain.cc 的檔案中建立 main 函式,main 函式將呼叫 fact 函式。要生成可執行檔案(executable file),必須告訴編譯器我們用到的程式碼在哪裡。對於上述幾個檔案來說,編譯的過程如下:

$ CC factMain.cc fact.cc # generates factMain.exe or a.out
$ CC factMain.cc fact.cc -o main # generates main or main.exe

​ 其中,CC 時編譯器的名字,$ 是系統命令提示符,# 後面是命令列下的註釋語句。接下來執行可執行檔案,就會執行我們定義的 main 函式。

​ 如果我們修改了其中一個原始檔,那麼只需要重新編譯那個改動了的檔案。大多數編譯器提供了分離器編譯每個檔案的機制,這一過程通常會產生一個字尾名是 .obj(Windows)或 .o(UNIX)的檔案,字尾名的含義是該檔案包含物件程式碼(object code)。

​ 接下來編譯器負責把物件檔案連結在一起形成可執行檔案。我們的系統中,編譯的過程如下所示:

$ CC -c factMain.cc # generates factMain.o
$ CC -c fact.cc # generates fact.o
$ CC factMain.o fact.o # generates factMain.exe or a.out
$ CC factMain.o fact.o -o main # generates main or main.exe

6.2 引數傳遞

6.2.1 傳值函式

​ 當初始化一個非引用型別的變數時,初始值被拷貝給變數。此時,對變數的改動不會影響初始值。傳值引數的機理完全一樣,函式對形參做的所喲操作都不會影響實參。

指標形參

​ 當執行指標拷貝操作時,拷貝的是指標的值。拷貝之後,兩個指標是不同的指標。在 C++ 中,建議使用引用型別的形參來代替指標。

6.2.2 傳引用引數

​ 對引用的操作實際上是作用在引用所引的物件上,引用形參的行為與之類似。通過使用引用形參,允許函式改變一個或多個實參的值。

使用引用避免拷貝

​ 拷貝大的類型別物件或者容器物件比較低微,甚至有的類型別(包括 IO 型別在內)根本就不支援拷貝操作。當某種型別不支援拷貝操作時,函式只能通過引用形參訪問該型別的物件。

​ 如果函式無須改變引用引數的值,最好將其宣告為常量引用。

使用引用形參返回額外資訊

​ 一個函式只能返回一個值,然而有時函式需要同時返回多個值,引用形參為我們一次返回多個結果提供了有效途徑。例如,我們定義一個名為 find_char 的函式,它返回在 string 物件中某個指定字元第一次出現的位置。同時,我們也希望能返回該字元出現的字元數。

​ 該如何定義函式使得它能夠既返回出現次數呢?一種方法時定義一個新的資料型別,讓它包含位置和數量兩個成員。另一種方法,我們可以給函式傳入一個額外的引用實參,令其儲存字元出現的次數:

// returns the index of the first occurrence of c in s
// the reference parameter occurs counts how often c occurs
string::size_type find_char(const string &s, char c, string::size_type &occurs)
{
	auto ret = s.size(); // position of the first occurrence, if any
	occurs = 0; // set the occurrence count parameter
	for (decltype(ret) i = 0; i != s.size(); ++i) {
		if (s[i] == c) {
			if (ret == s.size())
				ret = i; // remember the first occurrence of c
			++occurs; // increment the occurrence count
		}
	}
	return ret; // count is returned implicitly in occurs
}

6.2.3 const 形參和實參

​ 當形參是 const 時,必須要注意 2.4.3 節(p57)關於頂層 const 的討論,頂層 const 作用於物件本身:

const int ci = 42;  // we cannot change ci; const is top-level
int i = ci;  // ok: when we copy ci, its top-level const is ignored
int * const p = &i;  // const is top-level; we can't assign to p
*p = 0;  // ok: changes through p are allowed; i is now 0

​ 和其他初始化過程一樣,當使用實參初始化形參時會忽略掉頂層 const。換句話說,形參的頂層 const 被忽略掉了。當形參有頂層 const 時,傳給它常量物件或者非常量物件都是可以的。呼叫 fcn 函式時,既可以傳入 const int 也可以傳入 int。忽略掉形參的頂層 const 可能會產生意想不到的結果:

void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* . . . */ } // error: redefines fcn(int)

指標或引用形參與 const

​ 形參的初始化方式和變數的初始化方式是一樣的,我們可以用非常量初始化一個底層 const 物件,但是反過來不行;同時理解一盒普通的引用必須用同類型的物件初始化。

int i = 42;
const int *cp = &i;  // ok: but cp can't change i (§ 2.4.2 (p. 62))
const int &r = i;  // ok: but r can't change i (§ 2.4.1 (p. 61))
const int &r2 = 42;  // ok: (§ 2.4.1 (p. 61))
int *p = cp;  // error: types of p and cp don't match (§ 2.4.2 (p. 62))
int &r3 = r;  // error: types of r3 and r don't match (§ 2.4.1 (p. 61))
int &r4 = 42;  // error: can't initialize a plain reference from a literal (§ 2.3.1 (p.50))

​ 將同樣的初始化規則應用到引數傳遞上可得如下形式:

void reset(int &i) // i is just another name for the object passed to reset
{
	i = 0; // changes the value of the object to which i refers
}

int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i);  // calls the version of reset that has an int* parameter
reset(&ci);  // error: can't initialize an int* from a pointer to a const int object
reset(i);  // calls the version of reset that has an int& parameter
reset(ci);  // error: can't bind a plain reference to the const object ci
reset(42);  // error: can't bind a plain reference to a literal
reset(ctr);  // error: types don't match; ctr has an unsigned type
// ok: find_char's first parameter is a reference to const
find_char("Hello World!", 'o', ctr);

​ 要想呼叫這兒的 reset ,只能使用 int 型別的物件,而不能使用字面值、求值結果為 int 的表示式、需要轉換物件或者 const int 型別的物件。類似的,要想呼叫指標版本的 reset(6.2.1 p188)只能使用 int*。

儘量使用常量引用

​ 把函式不會改變的形參定義成(普通的)引用是一種比較常見的錯誤,這麼做給函式的呼叫者一種錯誤,即函式可以修改它的實參的值。此外,使用引用而非常量引用也會極大地限制函式所能接受的實參型別。就像剛剛看到的,我們不能把 const 物件、字面值或者需要型別轉換的物件傳遞給普通的引用形參。

6.2.4 陣列形參

​ 陣列的兩個特殊性質對我們定義和使用作用在陣列上的函式有影響,這兩個性質分別是:不允許拷貝陣列以及使用陣列時通常會將其轉換成指標。因為不能拷貝陣列,所以我們無法以按值傳遞的方式使用陣列引數。因為陣列會被轉換成指標,所以當我進門為函式傳遞一個數組時,實際上傳遞的時指向陣列首元素的指標。

​ 儘管不能以按值傳遞的方式傳遞陣列,但是我們可以把形參寫成類似陣列的形式:

// despite appearances, these three declarations of print are equivalent
// each function has a single parameter of type const int*
void print(const int*);
void print(const int[]);  // shows the intent that the function takes an
array
void print(const int[10]);  // dimension for documentation purposes (at
best)

​ 如果我們傳給函式的是一個指標,則實參自動地i轉換成指向首元素的指標,陣列的大小對函式的呼叫沒有影響。

​ 管理指標形參有三種常見的技術。

使用標記指定陣列長度

​ 這種方法要求陣列本身包含一個結束標記,使用這種方法的典型事例是 C 風格字串。C 風格字串後面跟著一個空字元。函式在處理 C 風格字串時遇到空字元停止:

void print(const char *cp)
{
	if (cp)  // if cp is not a null pointer
		while (*cp)  // so long as the character it points to is not a null character
			cout << *cp++;  // print the character and advance the pointer
}

​ 這種方法適用於那些有明顯結束標記且該標記不會與普通資料混淆的情況,但是對於像 int 這樣所有取值都是合法值得就不太有效了。

使用標準庫規範

​ 第二種技術時傳遞指向陣列首元素和尾後元素的指標,例如:

void print(const int *beg, const int *end)
{
// print every element starting at beg up to but not including end
	while (beg != end)
		cout << *beg++ << endl; // print the current element
// and advance the pointer
}

​ 為了呼叫這個函式,我們需要傳入兩個指標:一個指向輸出的首元素,另一個指向尾元素的下一位置:

int j[2] = {0, 1};
// j is converted to a pointer to the first element in j
// the second argument is a pointer to one past the end of j
print(begin(j), end(j))// begin and end functions, see § 3.5.3 (p.118)

顯示傳遞一個表示陣列大小的形參

​ 第三種方法時專門定義一個表示陣列大小的形參,在過去的程式中常常使用這種方法。使用該方法,重寫 print 方法 如下:

// const int ia[] is equivalent to const int* ia
// size is passed explicitly and used to control access to elements of ia
void print(const int ia[], size_t size)
{
	for (size_t i = 0; i != size; ++i) {
		cout << ia[i] << endl;
	}
}

陣列形參和 const

​ 當函式不需要對陣列執行寫操作的時候,陣列形參應該是指向 const 的指標。只有當函式確實要改變元素值的時候,才把形參定義成指向非常量的指標。

陣列引用形參

​ C++ 允許將變數定義成陣列地引用,所以形參可以是陣列的引用。此時,引用形參繫結到對應的實參上,也就是繫結到陣列上:

// ok: parameter is a reference to an array; the dimension is part of the type
void print(int (&arr)[10])
{
	for (auto elem : arr)
		cout << elem << endl;
}

f(int &arr[10])  // error: declares arr as an array of references
f(int (&arr)[10])  // ok: arr is a reference to an array of ten ints

傳遞多維陣列

​ 和所有陣列一樣,當將多維陣列傳遞給函式時,真正傳遞的是指向陣列首元素的指標。因為我們處理的是陣列的陣列,所以首元素本身就是一個數組,指標就是一個指向陣列的指標。陣列第二維(以及後面所有維度)的大小都是陣列型別的一部分,不能省略:

// matrix points to the first element in an array whose elements are arrays of ten ints
void print(int (*matrix)[10], int rowSize) { /* . . . */ }

// equivalent definition
void print(int matrix[][10], int rowSize) { /* . . . */ }

​ 也可以使用陣列的語法定義函式,此時編譯器會一如既往地忽略第一個維度,所以最好不要把它包括在形參列表了。matrix 的宣告看似是一個二維陣列,實際上形參是指向含有 10 個整數的指標。

6.2.5 main: 處理命令列選項

​ 有時我們需要給 main 傳遞實參,一種常見的情況是使用者通過設定一組選項來確定函式所要執行的操作。例如,假定 main 函式位於可執行檔案 prog 之內,我們可以向程式傳遞下面的選項:

prog -d -o ofile data0

這些命令列選項通過兩個(可選的)形參傳遞給 main 函式:

int main(int argc, char *argv[]) { ... }

第二個形參 argv 是一個數組,它的元素是指向 C 風格字串的指標;第一個形參 argc 表示陣列中字串的數量。因為第二個形參是陣列,所以 main 函式也可以定義成:

int main(int argc, char **argv) { ... }

其中 argv 指向 char*。

當實參傳給 main 函式之後,argv 的第一個元素指向程式的名字或者一個空字串,接下來的元素依次傳遞提供命令列提供的實參。最後一個指標之後的元素保證為 0。

以上面提供的命令列為例,argc 應該等於 5,argv 應該包含如下的 C 風格字串:

argv[0] = "prog";  // or argv[0] might point to an empty string
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;

當使用 argv 中的例項時,一定要記得可選的實參從 argv[1] 開始;argv[0] 儲存程式的名字,而非使用者輸入。

6.2.6 含有可變形參的函式

為了編寫能處理不同數量的函式,C++11 新標準提供了兩種主要的方法:如果所有的實參型別相同,可以傳遞一個名為 initializer_list 的標準庫型別;如果實參的型別不同,我們可以編寫一種特殊的函式,也就是可變引數模板。C++ 還有一種特殊的形參型別(即省略符),可以用它傳遞可變數量的實參。

initializer_list 形參

如果函式的實引數量位置但是所有的實參型別相同,我們可以使用 initializer_list 型別的實參。 initializer_list 是一種標準庫型別,用於表示某種 特定型別的值的陣列。 initializer_list 型別定義在同名的標頭檔案中,它提供的操作如表 6.1 所示:

16

和 vector 一樣,initializer_list 也是一種模板型別,定義 initializer_list 物件時,必須說明列表中所含元素的型別;和 vector 不一樣的是,initializer_list 物件中的元素永遠是常量值,我們無法改變 initializer_list 物件中元素的值。

省略符形參

省略符形參是為了便於 C++ 程式訪問某些特殊的 C 程式碼而設定的,這些程式碼使用了名為 varags 的 C 標準庫功能。省略符形參只能出現在形參列表的最後一個位置,它的形式無外乎以下兩種:

void foo(parm_list, ...);
void foo(...);

6.3 返回型別和 return 語句

​ return 語句終止當前正在執行的函式並將控制權返回到呼叫該函式的地方。return 語句有兩種形式:

return;
return expression;

6.3.1 無返回值函式

​ 沒有返回值的 return 語句只能在返回型別是 void 的函式中。返回 void 的函式不要求非得有 return 語句,因為在這類函式的最後一句後面會隱式地執行 return。

通常情況下,void 函式如果想在它的中間位置提前退出,可以使用 return 語句。一個返回型別是 void 的函式也能使用 return 語句的第二種形式,不過此時 return 語句的 expression 必須是另一個返回 void 的函式。強行令 void 函式返回其他型別的表示式將產生編譯錯誤。

6.3.2 有返回值函式

​ return 語句的第二種形式提供了函式的結果。只要函式的返回型別不是 void,則該函式內的每條 return 語句必須返回一個值。return 語句返回值的型別必須與函式的返回型別相同,或者能夠隱式地轉換成函式的返回型別。

儘管 C++ 無法確保結果的正確性,但是可以保證每個 return 語句的結果型別正確。也許無法估計所有情況,但是編譯器仍然儘量確保具有返回值的函式只能通過一條有效的 return 語句退出,例如:

// incorrect return values, this code will not compile
bool str_subrange(const string &str1, const string &str2)
{
	// same sizes: return normal equality test
	if (str1.size() == str2.size())
		return str1 == str2; // ok: == returns bool
	// find the size of the smaller string; conditional operator, see § 4.7 (p. 151)
	auto size = (str1.size() < str2.size())? str1.size() : str2.size();
	// look at each element up to the size of the smaller string
	for (decltype(size) i = 0; i != size; ++i) {
		if (str1[i] != str2[i])
			return; // error #1: no return value; compiler should detect this error
	}
	// error #2: control might flow off the end of the function without a return
	// the compiler might not detect this error
}

​ 在含有 return 語句的迴圈後面應該也有一條 return 語句,如果沒有的話該程式就是錯誤的。

值是如何被返回的

返回一個值的方式和初始化一個變數或形參的方式完全一樣:返回的值用於初始化呼叫點的一個臨時量,該臨時量就是函式呼叫的結果。

不要返回區域性物件的引用或指標

函式完成後,它所佔用的儲存空間也隨之被釋放掉,因此,函式終止意味著區域性變數的引用將指向不再有效的記憶體區域:

// disaster: this function returns a reference to a local object
const string &manip()
{
	string ret;
	// transform ret in some way
	if (!ret.empty())
		return ret;  // WRONG: returning a reference to a local object!
	else
		return "Empty";  // WRONG: "Empty" is a local temporary string
}

上面的兩條 return 語句都將返回未定義的值。對第一條 return 來說,顯然它返回的是區域性物件的引用。在第二條 return 語句中,字串字面值轉換成一個區域性臨時 string 物件,對於 manip 來說,該物件和 ret 一樣都是區域性的。當函式結束時臨時物件佔用的空間也就隨之釋放掉了,所以兩條 return 語句都指向了不再可用的記憶體空間。

如前所述,返回區域性物件的引是錯誤的;同樣,返回區域性物件的指標也是錯誤的。一旦函式完成,區域性物件被釋放,指標將會指向一個不存在的物件。

引用返回左值

函式的返回型別絕地給函式呼叫是否是左值。呼叫一個返回引用的函式得到左值,其他返回型別得到右值。可以像使用其他左值那樣來使用返回引用的函式的呼叫,特別是,我們能為返回型別是非常量引用的函式的結果賦值:

char &get_val(string &str, string::size_type ix)
{
	return str[ix]; // get_val assumes the given index is valid
}
int main()
{
	string s("a value");
	cout << s << endl; // prints a value
	get_val(s, 0) = 'A'; // changes s[0] to A
	cout << s << endl; // prints A value
	return 0;
}

主函式 main 的返回值

如果函式的返回型別不是 void,那麼它必須返回一個值。但是我們允許 main 函式沒有 return 語句直接結束。如果控制達到了 main 函式的結尾處而沒有 return 語句,編譯器將隱式地插入一條返回 0 的 return 語句。

main 函式的返回值可以看作是狀態指示器。返回 0 表示執行成功,返回其他值表示執行失敗,其中非 0 值的具體含義依機器而定。為了使返回值與機器無關,cstdlib 標頭檔案定義了兩個預處理變數,我們可以使用兩個變量表示成功和失敗:

int main()
{
	if (some_failure)
		return EXIT_FAILURE;  // defined in cstdlib
	else
		return EXIT_SUCCESS;  // defined in cstdlib
}

​因為它們是預處理變數,所以既不能在前面加上 std::,也不能在 using 宣告中出現。

6.3.3 返回陣列和指標

因為陣列不能被拷貝,所以函式不能返回陣列。不過,函式可以返回陣列的指標或引用。雖然從語法上來說,要想定義一個返回陣列的指標或引用的函式比較煩瑣,但是有一些方法可以簡化成這一任務,其中最直接的方法是適用類型別名:

typedef int arrT[10];  // arrT is a synonym for the type array of ten ints
using arrT = int[10];  // equivalent declaration of arrT; see § 2.5.1 (p. 68)
arrT* func(int i);  // func returns a pointer to an array of five ints

其中 arrT 是含有 10 個整數的陣列的別名。因為我們無法返回陣列,所以將返回型別定義成陣列的指標。因此,func 函式接受一個 int 實參,返回一個指向 10 個整數的陣列的指標。

宣告一個返回陣列指標的函式

要想在宣告 func 時不使用類型別名,我們必須牢記被定義的名字後面陣列的維度:

int arr[10];  // arr is an array of ten ints
int *p1[10];  // p1 is an array of ten pointers
int (*p2)[10] = &arr;  // p2 points to an array of ten ints

和這些生命一樣,如果我們想定義一個返回陣列指標的函式,則陣列的維度必須跟在函式名字之後。然而,函式的形參列表也跟在函式名字後面且形參列表應該先於陣列的維度。因此,返回陣列指標的函式形式如下所示:

Type (*function(parameter_list))[dimension];

int (*func(int i))[10];

可以按照以下的順序來逐層理解該宣告的含義:

  • func(int i) 表示呼叫 func 函式時需要一個 int 型別的實參。
  • (*func(int i)) 意味著我們可以對函式呼叫的結果執行解引用的操作。
  • (*func(int i))[10] 表示解引用 func 的呼叫將得到一個大小是 10 的陣列。
  • int (*func(int i))[10] 表示陣列中的元素是 int 型別。

使用尾置返回型別

​ C++11 新標準中可以使用尾置返回型別(trailing return type)。任何函式的定義都能使用尾置返回,但是這種形式對於返回型別比較複雜的函式最有效,比如返回型別是陣列的指標或者陣列的引用。尾置返回型別跟在形參列表後面並以一個 -> 符號開頭。為了表示函式真正的返回型別跟在形參列表之後,我們本應該出現返回型別的地方放置一個 auto:

// fcn takes an int argument and returns a pointer to an array of ten ints
auto func(int i) -> int(*)[10];

使用 decltype

如果我們知道函式返回的指標將指向哪個陣列,就可以使用 decltype 關鍵字宣告返回型別。例如,下面的函式返回一個指標,該指標根據引數 i 的不同指向兩個已知陣列中的某一個:

int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
// returns a pointer to an array of five int elements
decltype(odd) *arrPtr(int i)
{
	return (i % 2) ? &odd : &even; // returns a pointer to the array
}

arrPtr 使用關鍵字 decltype 表示它的返回型別是個指標並且該指標所指的物件與 odd 一致。因為 odd 是陣列,所以 arrPtr 返回一個指向返回含有 5 個整數的陣列的指標。有一個地方需要注意:decltype 並不負責把陣列型別轉換成對應的指標,所以 decltype 的結果是個陣列,要想表示 arrPtr 返回指標還必須在函式宣告時加一個 * 符號。

6.4 函式過載

過載和 const 形參

頂層 const 不影響傳入函式的物件。一個擁有頂層 const 的形參無法和另一個沒有頂層 const 的形參區分開來:

Record lookup(Phone);
Record lookup(const Phone);  // redeclares Record lookup(Phone)
Record lookup(Phone*);
Record lookup(Phone* const);  // redeclares Record lookup(Phone*)

另一方面,如果形參時某種型別的指標或引用,則通過區分其指向的時常量物件還是非常量物件可以實現函式過載,此時的 const 是底層的:

// functions taking const and nonconst references or pointers have different parameters
// declarations for four independent, overloaded functions
Record lookup(Account&);  // function that takes a reference to Account
Record lookup(const Account&);  // new function that takes a const reference
Record lookup(Account*);  // new function, takes a pointer to Account
Record lookup(const Account*); // new function, takes a pointer to const

最好只過載那些確實非常相似的操作。

6.5 特殊用途語言特性

6.5.1 預設實參

在函式的很多次呼叫中,某種形參都被賦予一個相同的值,此時,我們把這個反覆出現的值稱為預設實參(default argument)。呼叫含有預設實參的函式時,可以包含該實參,也可以省略該實參。

當設計含有預設實參的函式時,其中一項任務就是合理設定形參的順序,儘量讓不怎麼使用預設值的形參出現在前面,而讓那些經常使用預設值的形參出現在後面。

6.5.2 行內函數和 constexpr 函式

行內函數可避免函式呼叫的開銷

在函式的返回型別前面加上關鍵字 inline,這樣就可以將它宣告成內聯函數了。一般來說,內聯機制用於優化規模較小、流程直接、頻繁呼叫的函式。很多編譯器不支援內聯遞迴函式,而且一個 75 行的函式也不大可能在呼叫點內聯地展開。

constexpr 函式

constexpr 函式是值能夠用於常量表達式的函式。定義 constexpr 函式的方法與其他函式的方法類似,不過要遵循幾項規定:函式的返回型別及所有形參的型別都得是字面值型別,而且函式體中必須且只有一條 return 語句:

constexpr int new_sz() { return 42; }
constexpr int foo = new_sz();  // ok: foo is a constant expression

執行該初始化任務時,編譯器把對 constexpr 函式的呼叫替換成其結果值。為了能在編譯過程中隨時展開,constexpr 函式被隱式地指定為行內函數。

constexpr 函式體內也可以包含其他語句,只要這些語句在執行時不執行任何操作就行。例如,constexpr 函式中可以有空語句、類型別名及 using 宣告。

我們允許 constexpr 函式的返回值並非一個常量。
// scale(arg) is a constant expression if arg is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }

當 scale 的實參是常量是常量表達式時,它的返回值也時常量表達式;反之則不然:

nt arr[scale(2)];  // ok: scale(2) is a constant expression
int i = 2;  // i is not a constant expression
int a2
            
           

相關推薦

C++ Primer 筆記

Chapter 6 Functions 6.1 函式基礎 6.1.1 區域性物件 ​ C++ 中,名字具有作用域,物件具有生命週期。 名字的作用域是程式文字的一部分,名字在其中可見。 物件的生命週期是程式執行過程中該物件存在的一段時間。 ​ 形參

C++primer《學習》

6.7 前面已經實現的統計母音的程式存在一個問題:不能統計大寫的母音字母。編寫程式統計大寫小寫的母音字母,也就是說,你的程式計算出來的aCnt,既包括’a’也包括’A’出現的次數,其他四個母音也一樣。 int main() { ifstream infile; infile.open("tes

C++ Primer 筆記

Chapter 3 Strings, Vectors, and Arrays 3.1 名稱空間的 using 宣告 ​ 目前為止,我們用到的庫函式基本上都屬於名稱空間 std,而程式也顯式地將這一點標註出來。例如,std::cin 表示從標準輸入中讀取內容。此處

C++ Primer 筆記

Chapter 4 Expressions 4.1 基礎 4.1.1 基本概念 左值和右值 ​ 一個左值表示式的求職結果是一個物件或者一個函式,然而以常量物件為代表的某些左值實際上不能作為賦值語句的左側運算物件。當一個物件用作右值的時候,用的是物件的值(內容);

C++ Primer 筆記

Chapter 5 Statements 5.6 try 語句塊和異常處理 異常是指存在於執行時的反常行為,這些行為超出了函式正常功能的範圍。異常處理包括: throw 表示式(throw expression),異常檢測部分使用 throw 表示式來表示它

C++ Primer 筆記

Chapter 7 Classes 7.1 定義抽象資料型別 7.1.2 定義改進的 Sales_data 類 ​ 定義和宣告成員函式的方式與普通函式差不多。成員函式的宣告在類的內部,它的定義既可以在類的內部也可以在類的外部。作為介面部分的非成員函式,它們的定義

c++ primer習題

#include <iostream> #include <cctype> #include <string> #include <array> #include <iomanip> #include <fstream> us

C++ primer 5th 筆記

總結: 6.1 基礎 : 陣列 和 函式不能充當返回值, 可以使用指向他們的指標. 自動變數: 生存週期 = 作用域 區域性靜態變數: 生存週期 > 作用域 函式宣告 = 函式原型 6.2 引數傳遞:

深度探索c++物件模型筆記

執行期語意學(Runtime Semantics) 有下面的程式碼 if(yy==xx.getValue())........... 其中xx 和yy定義為: X xx; Y yy; class Y定義為: class Y { public: Y(); ~Y

c++ primer(標準庫型別)學習筆記

1.在使用標準庫提供的string物件的size方法獲取字串長度時,為了避免溢位,儲存一個string對像size的最安全方法就是    使用標準庫型別string::size_type,處於同樣的道理在定義索引變數時也要使用string::size_type。 2.stri

C++ Primer 標準庫型別 筆記

C++ Primer 第三章 標準庫型別 標準庫型別是我之前沒有接觸過內容,不僅是這一章,整本書有很多東西對我來說都是新的,譚伯伯那本介紹的東西只是C++中的皮毛罷了。感覺到學習C++將是個無底洞。

C++ Primer 版》

C艹的變數名的幾種簡單的規則 1.     在名稱中只能使用字母字元、數字和下劃線 2.     名稱的第一個字元不能是數字 3.     區分大寫字元與小寫字元 4.     不能將C艹關鍵字用作名稱 5.     一兩個下劃線或下劃線和大寫字母打頭的名稱被保留給實現(編

C++ Primer 泛型算法 筆記

size_t string 引用捕獲 list 字典序排序 變量 字符串 space ifstream C++ Primer 第十章 泛型算法 練習題 10.1 概述 叠代器令算法不依賴於容器,但算法依賴於元素類型的操作。 10.1 vector<int>vi;

c++ primer 10 泛型算法學習筆記

用戶 pan class 抽象 添加元素 叠代器 因此 定義 標準 一、泛型算法概述 標準庫容器定義的操作很少,標準庫並未給每種容器添加大量的功能,而是提供一些算法,這些算法孤立與容器種類,是容器所通用的,或稱泛型的,泛型算法適用於各種各種容器,容器中可以有各種元

C++ Primer2

detail 取地址 算術 nbsp sig nic http 花括號 如果 今天學到的 30頁~49頁。 1.算術類型的選擇,一般只用int,double。超出int則用long long,而不用long。浮點型用double而不用float,二者計算成本差別不大。 2.

c++primer 編程練習答案

答案 c++ nal world mint fin blog logs eas 3.7.1 #include<iostream> int main() { using namespace std; const int unit = 12;

c++primer 編程練習答案

float enter put rand out har lin score ring 4.13.1 #include<iostream> struct students { char firstname[20]; char lastname

c++primer 編程練習答案

rime factor 1.0 9.1 sin cin ria tor don 5.9.1 #include<iostream> int main() { using namespace std; int one, two, temp, sum

筆記

birt 單詞 使用 point 錯誤 永遠 一個 表達式 學習 循環結構(二) 學習本章有道的單詞: rate:速度,比率 young:年輕的,年少 schedule:時間表,調度 neggtive:消極的;否定 customer:顧客,觀眾 birthday:生日 po

c語言-循環結構II

while循環 程序 如果 初始化 應該 con class 語句 優先 for( 表達式1 ; 表達式2 ; 表達式3 ){ 語句;}for 循環與 while 循環類似,屬於先判斷後執行執行順序是:表達式1、表達式2、語句、表達式3-->表達