天天看點

【C++ Prime Plus】第八章學習筆記

内聯函數 inline

可以用來修飾簡短的函數,在編譯時建議編譯器不作為函數,而展開編譯。

内聯函數無法遞歸。

引用變量

引用用來為一個其他變量起一個别名。

建立引用變量

不同于指針,在生命引用時必須對其進行初始化,指明是哪一個變量的别名。

int num = 5;
int &rnum1 = num;
int &rnum2;//報錯
           

随後,rnum1永遠指向num,所有對rnum的修改将作用于num。

rnum1 = 10;
cout<<rnum1<<endl;//10
cout<<num<<endl;//10
           

将引用用作函數參數

我們可以将引用作為參數傳遞給函數,這樣函數就可以對引用的内容進行修改。

void setNum(int& num){
    num = 15;
    return;
}
...
setNum(num);
cout<<num<<endl;//15
           

引用的屬性與特别之處

非const引用不支援類型轉換

按值傳遞的函數在接受參數時能夠進行類型轉換,例如

void func1(float arg);
int arg1 = 1;
func1(arg1);//不報錯
           

但是非const引用則會報錯,例如

void func1(float& arg);

int arg1 = 1;
func1(arg1);//報錯
           

設計的思路是,如果我們對非const的參數進行修改,則會按照float的方式修改int變量,一定會導緻不可預期的錯誤(float與int編碼方式不同),是以無法編譯。

const引用

以非const引用作為參數的函數無法接受const引用,因為const int& 無法用來初始化int& ,數值會被修改。

void func1(int& arg);
int num = 1;
const int& cr_num = num;
func1(cr_num);//報錯
           
int num = 1;
const int& cr_num = num;
int& r_num = cr_num;//報錯
           

左值與非左值

左值指的是可以被引用的對象,例如變量(const也可以),數組元素,被解引用的指針,非左值包括字面量,符号運算的結果,函數運算的結果(傳回值非引用)。

前面我們提及了,以非const引用作為參數的函數無法進行類型轉換,但是把const類型引用作為參數的函數可以在這兩種程度上進行類型轉換,并建立一個臨時變量:

1.參數類型正确,但不是左值。

void func1(const int &arg);
...
int num = 5;
func1(num+5);//通過
           

很顯然,num+5的類型是int,但是是右值。因為函數中不會對arg進行修改,是以可以進行類型轉換。

2.參數的類型不正确,但可以轉換為正确的類型。

void func1(const int &arg);
...
float f_num = 5.0f;
func1(num+2.0);//通過
           

總的來說,如果一個函數的目的是修改作為參數的引用,那麼它就無法建立臨時變量。

在編寫函數的時候,應該盡可能地多使用const參數,避免對引用的修改,還能夠相容const與非const參數。

将引用用于結構與類

用引用傳遞結構與類能夠提高效率。

一個函數能夠傳回引用,引用作為右值時有兩種用法,一種是初始化其他引用,另一種是單獨作為數值。

int& func1(int &arg){
    return arg;
};
...
int num = 5;
int& r_num = func1(num);
r_num = 10;
cout<<num<<endl;//10
           

我們可以觀察到,傳回的引用指派給了r_num,等價于

int& r_num = num;
           

函數傳回引用是有要求的,必須傳回一個參數中的引用,或者是全局變量中的引用或者是在函數中new的對象的引用,總的來說,被引用的對象不能在函數結束後被銷毀。

int& func1(int &arg){
    int result = 5;
    return result;//運作後崩潰,因為result在函數結束後銷毀。
};
           

函數傳回值類型也可以聲明為const引用

const int& func1(int &arg);
...
int& r_num = func1(...);//報錯
const int& rc_num = func1(...);//通過
           

因為const引用不能被指派給非const引用,反之可以。

對象,繼承與引用

對于一般變量來講,引用無法進行類型轉換,但是衍生類的引用可以直接複制給基類的引用。

函數傳參,結果傳回的本質都是指派

預設參數

比較簡單 不過多贅述。

函數重載

參數類型不同的函數之間構成重載,但參數名字不同不行,傳回值類型不同也不行。

//構成重載
int func1(int arg1);
int func1(float arg1);

//不構成重載
float func1(int arg1);
int func1(int arg1);

//不構成重載
int func1(int arg1);
int func1(int arg2);
           

判斷的原理是,編譯器需要能夠根據參數的類型來分别調用哪個函數,如果編譯器不能分辨就會報錯。

這條原理可以用于解釋這種現象

//不構成重載
int func1(int arg1);
int func1(int& arg1);
           

因為當傳入一個int型變量時候,編譯器也不知道要使用哪個函數。

當然也有特殊的情況,編譯器能夠根據引用是否是const來決定選擇哪個函數,例如

int func1(const int& arg1);
int func1(int& arg1);
...
int num = ;
const int& cr_num = num;
int& r_num = num;
func1(cr_num);//int func1(const int& arg1);
func2(r_num);//int func1(int& arg1);
           

同樣的例子是否适用于指針,可能需要因編譯器而議。

函數模闆

函數模闆允許使用泛型來定義函數。

重載的模闆

當同一個算法不能同時滿足多種參數類型時,我們可以對模闆也進行重載

template<class T>
void func1(T a){
	cout << a << endl;
}

template<class T>
void func1(T a[],int n){
	for (int i = ;i<n;i++){
		cout << a[i] << endl;
	}
}
           

模闆的局限性

以下情況模闆無法處理

template <class T>
void func1(T a, T b);

int arg1 = 1;
float arg2 = 1.0f;
func1(arg1, arg2);
           

也就是說,隻有模闆是無法處理類型轉換的并對特殊的類型執行特殊的算法的,需要用接下來介紹的具體化,執行個體化等方法解決。

顯式具體化

template <class T>
void func1(T a, T b){
	cout << "template" << endl;
};

template <>
void func1<int>(int a, int b){
	cout << "explicit specialization" << endl;
};

int arg1 = 1,arg2 = 2;
func1(arg1,arg2);//explicit specialization
           

可以觀察到,顯式具體化後優先級高于模闆。

值得一提的是,顯示具體化後仍然不支援float到int的類型轉換。

具體化與執行個體化

首先聲明,模闆本身并不是函數的定義,他隻是一個生成函數定義的方案。

編譯器在編譯時,檢測到了需要用的funct1的int類型的執行個體化,就把函數執行個體化在代碼區。

這就可以解釋隻有單純的模闆不能處理類型轉換的問題,因為編譯器不知道應該生成int類的代碼還是float類的代碼。

顯式具體化也不支援類型轉化,因為編譯器仍然不知道是需要在最初的模闆上生成float函數還是使用具體化的int函數。

除了顯示具體化之外,我們還可以顯示執行個體化

template
void func1<int>(int a, int b);
           

與顯式具體化的差別是,顯式執行個體化不能聲明自己的函數内容。

我們也可以這樣使用顯式執行個體化。

int num_i = 5;
float num_f = 5.0;
func1<int>(num_i,num_f);
           

因為我們指定了需要編譯器生成int類型的參數,是以這次可以支援類型轉換,通過編譯。

編譯器選擇使用哪個版本的函數

細節比較麻煩,這裡隻說一些細節。

1.完全比對,但正常函數優先于模闆,具體化的模闆優先于隐式生成的模闆。

2.提升轉換 short->int,float->double

3.标準轉換 int->char,long->double

4.使用者自定義的轉換

在轉換的過程中,例如像TYPE 到const TYPE,TYPE 到TYPE&這樣的轉化被視為無關緊要的轉換,不會影響優先級。

int可以同時作為Type與Type,編譯器傾向于後者,因為做的轉換更少。

更細節的内容見書。

絕大多數情況下,我們需要自己選擇重載函數。

func1<>(arg1,arg2);//強制使用模闆函數
func1<int>(arg1,arg2);//顯式執行個體化int
           

函數模闆的發展

我們需要一些更加自定義的功能。

template <class T1,class T2>
void func1(T1 a, T2 b){
	? type ? xpy = x + y;
};
           

我們需要指明一種更加細化的類型,例如int+long long時應該標明long long,double+float時選的double,c++11的decltype提供了這樣的功能。

template <class T1,class T2>
void func1(T1 a, T2 b){
        ...
	decltype(x+y) xpy = x + y;
        ...
};
           

那麼如何判斷

decltype(express)

的類型呢?

1.如果express不是被括号包括的辨別符,那麼就等于表達式本身的類型,即使帶有const,引用,指針等。

2.如果express是一個函數調用,則等于函數的傳回類型,但函數并不會被執行。

3.如果express被函數括起來的左值,則等于括起來的類型的引用。

double xx = 5.0;
decltype((xx)) r_d = xx; //double &
decltype((xx+2)) r_d = xx; //double 因為不是左值,見第四種情況
           
int j = 3;
int &k = j;
int &n = j;
decltype(j + 6) i1;//int7
decltype(100L) i2;//long
decltype(k + n) i3;//int 因為k+n不是zuo值
           
typedef decltype(x+y) xytype; 
           

繼續閱讀