C++ Primer 第六章 函數 6.5~6.6 練習和總結

6.5 特殊用途語言特性

6.5.1默認實參

我們可以給形參列表中的形參添加默認的實參。
如下形式

int func(int height,int width=123,char color='')

1。注意給一個形參賦予默認實參之後,其後面的所有形參都要賦予默認實參,所以在設計函數的時候,儘可能的需要手動傳入參數的形參放在形參列表的前面。

2.默認實參不可以是局部變量,表達式只要能夠轉化爲形參所定義的類型,都可以作爲默認實參。

int w = 123;
int h = 123;
int func(int height=h,int width=1+5,char color=''){
}

以上賦予默認實參的方式都是可以的。如果變量是全局變量,可以調用函數之前修改變量的值,調用函數之後,默認實參的取值爲全局變量最後一次修改的值。(大白話就是,如果默認實參是一個全局變量,那麼全局變量變化,默認實參也會變化,因爲形參的賦值,只發生在調用的時候,所以在這之前全局變量可以一直變化。)

3.C++中建議在頭文件中寫函數的聲明,在源文件中寫函數的定義。我們可以在聲明的時候爲函數賦予默認的實參,需要注意的是,如果同一個函數聲明瞭多次,那麼後續只能給沒有賦予默認實參的形參賦予默認實參

int func(int a,int b ,int c=1);
int func(int a,int b,int c =1);//錯誤c修改了默認實參
int func(int a,int b=1,int c=1)// 錯誤,c修改了默認實參

從上的代碼可以看到,就算是一模一樣的代碼同時聲明兩次,也算修改了之前已經賦予默認實參的形參。

練習
6.40
b。因爲有默認實參的形參後面所有的形參都要有默認形參

6.41
a。是非法的,因爲ht沒有默認實參,所以調用函數需要一個實參
b。合法
c。合法,但是和初衷不符,因爲調用函數時,實參是從左往右給形參賦值,所以init(14,’*’)是分別給ht,和wd賦值。

6.42

string make_plural(size_t ctr,const string &word,const string &ending="s") {
	return (ctr > 1) ? word + ending : word;
}

make_plural(2,"success","es");
make_plural(2,"failure");

6.5.2 內聯函數和constexpr函數

內聯函數

把重複使用的代碼封裝成爲函數,可以提高我們的開發效率,調用函數相對於直接使用表達式,調用函數的效率相對要低一些。

所以C++提供了inline關鍵字,inline可以將函數聲明爲內聯函數,內聯函數會在調用的地方展開成爲表達式,以此提高運行效率。

但是什麼時候將函數定義爲內聯函數是個值得商討的問題,C++ Primer中並沒說明,內聯和普通函數之間的區別。

而且如果一個函數太長,聲明爲inline,編譯器可以選擇忽略,如果沒有記錯的話,有些編譯器會將較短的普通函數變爲內聯函數

constexpr函數

我們可以用constexpr來修飾函數,表示函數會返回常量表達式,需要注意的是,constexpr函數不一定返回常量表達式.

只有當函數的返回值類型和所有形參類型都是字面值類型時時,返回常量表達式。

但是我在測試時,局部變量不是字面值,直接返回局部變量的結果,也可以作爲常量表達式返回,我使用的編譯器爲vs2017。

constexpr int func(){
return 1;//返回的是常量表達式
}
constexpr int func(){
	int a = 1;
	return a;//返回的常量表達式
}
constexpr int func(int a){
return a;
}

func(1);// 是常量表達式
int a= 10;
func(a);//不是常量表達式

constexpr函數會被隱式的指定爲inline函數

constexpr函數和inline函數可以被定義多次,但是他們通常在頭文件中定義

*爲什麼這樣,結合書上的原因就是,我個人覺得是這樣的,constexpr函數和inline函數需要被嵌入爲表達式。所以明確的需要知道函數體中的內容,而通常我們在頭文件中定義函數,在源文件中修改函數。如果包含了頭文件,編譯的時候代碼實際上只是調用了這個函數的聲明,具體函數體的內容需要等到鏈接的時候才知道。
而聲明在頭文件中的話,我們在編譯的時候就能將函數體的內容嵌入到調用處。
*

以上純屬個人猜測

練習

6.43
a。聲明和定義都在頭文件,因爲它是內聯函數,內聯函數在編譯時會在調用點替換爲函數體的內容。
b。聲明在頭文件,定義在源文件。

6.44


inline bool isShorter(const string &s1,const string &s2) {
	return s1.size() < s2.size();
}

6.45
一般函數體結構較爲簡單的函數都可以聲明爲內聯

6.46
不可以,因爲返回值類型不是字面值類型

6.5.3 調試幫助

C++提供了一些便於調試的功能

一個是assert,其實就是斷言,使用方法如下

assert(experssion);

如果表達式爲真則不會發生任何事情,如果表達式爲假,則會報錯。

assert的行爲依賴於預處理變量NDEBUG,如果開啓了NDEBUG,則所有的assert都不會被觸發。

我們可以通過定義宏,來手動的寫一些調試

#ifndef NDEBUG
cout<<123<<endl;
#endif

除此之外,C++還預定義了一些變量,便於我們調試。


int func() {
	cout << __func__ << endl;//函數名
	cout << __LINE__ << endl;//當前行號
	cout << __TIME__ << endl;//文件編譯時間
	cout << __FILE__ << endl;//文件名
	cout << __DATE__ << endl;//文件編譯日期
	return 1;
}

可以利用這些東西爲我們的代碼,提供更多的錯誤信息。

練習
6.47

vs2017中定義了NDEBUG也沒有反應。。。。

6.48

不正確,cin是一個對象,永遠爲true

6.6函數匹配

同名的函數可以有多個,在調用的時候就必然需要匹配一個合適函數。

匹配的流程如下:

1.首先是選取候選函數,候選函數是同調用的函數同名且可見的函數。因爲如果在局部作用域中,聲明瞭和調用函數一樣的名字的函數或者變量,那麼外部作用域的同名函數會被屏蔽。

2.從候選函數中,根據調用函數的實參數量和類型確定可行函數。可行函數的特徵是形參類型和實參的類型一樣或者可以相互轉換,形參和實參的數量一樣,如果有默認實參,那麼默認實參的數量是可以忽略的。

3.從可行的函數中選出最佳匹配函數。

最佳的匹配函數一般是可行函數中形參和實參的類型完全一樣的函數。

但是有時實參的類型並沒有那麼理想,實參需要經過轉換才能變爲形參的類型。所以這個時候就涉及到了函數匹配的機制問題。

這個機制總的來說就是,

如果形參只有一個
實參轉換爲形參的類型時,優先挑選挑選那些類型轉換後精度不會出現損失的函數

void func(double i) {
	cout<<"double"<<endl;
}
void func(int i) {
	cout<<"int"<<endl;
}

float i = 1.0f;
func(i);//輸出double

如果形參有多個。那麼有且只有一個函數滿足下面的條件,纔算匹配成果。

1.該函數的每一個實參的匹配都不劣於其他可行函數需要的匹配
2.至少有一個實參的匹配,優於其他可行函數的匹配。

總結起來一句話就是,每一個實參的匹配都要>=其他函數的匹配,且至少有一個實參的匹配要>其他的函數的實參匹配。

所以下面的代碼具有二義性,最佳匹配的函數不唯一

1.int func(int,int);
2.int func(double,double);


func(2,3.14);

從第一個參數來看,函數1更加匹配,但是從參數2來看,函數2更加匹配,沒有任何一個函數可以完全勝過其他的函數,所以具有二義性。

練習

6.49
在之前的總結中已經寫了,不再贅述

6.50
除了a不合法,其餘的都合法,是因爲匹配就有二義性。有多個函數可以匹配

6.6.1 實參類型轉換

之前只是大概的說了一下匹配的規則,實參類型轉換提供了實參類型轉化的優先級。
在這裏插入圖片描述
我覺得第1點的第三小點和第二點有點像。

類型提升是,在計算時,小整型都會轉化爲int或者比int大的整型

算術類型轉化是,在計算算術表達式的時候,運算對象會進行轉換,
比如

char a  = 'a';
double b=  3.14;
int c = a+b;

a首先被提升爲int,然後再轉化爲double,右側表達式計算的結果再轉化爲int。

void func(int );
void func(short);
char a='a''
func(a);//調用的是int,因爲char整型提升之後變成了int
void func(long);
void func(float);
func(4.14);//有二義性,因爲doulbe可以轉爲float和long

關於算術類型的轉化這裏比較難理解,我的猜想是,雙精度轉化爲單精度是會損失精度,double轉long也會損失精度,所以這兩個函數都可以轉,但是沒有最優。

函數匹配和const實參

這個之前就說過了,如果函數有引用和常量引用兩種,

void func(const int &i);
void func(int &i);

這兩個函數在調用的時候選誰,卻決於實參是不是const,如果實參是const則選擇將const形參的函數。如果實參不是常量,則選擇普通函數。

void f(int& ,int &) {

}
void f(const int &,const int &){

}

const int a = 1;
	int b = 1;
f(a, b);

如果是實參既有const又有非const類型,那麼選擇形參聲明爲常量的函數,其實這裏b已經通過類型轉化轉化爲const類型了。

練習

6.52
a。等級3 類型提升
b。等級4, 算術類型轉換

6.53
a。如果掉函數時,實參有常量類型,則調用第二條語句
b。如果調用函數時,實參有常量類型指針,則調用第二條語句
c。重複定義,指針的頂層const,不算重載。

發佈了46 篇原創文章 · 獲贊 6 · 訪問量 3109
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章