這章再深入的講些函數(shù)相關的踪宠。
1.C++內(nèi)聯(lián)函數(shù)
編譯器對內(nèi)聯(lián)的函數(shù)處理就是將相應的函數(shù)的代碼直接替換掉對應函數(shù)的調(diào)用窘茁。對于內(nèi)聯(lián)代碼,程序無需跳到另一個位置處執(zhí)行代碼壁涎,再調(diào)回來凡恍。因此,內(nèi)聯(lián)函數(shù)的運行速度比常規(guī)函數(shù)調(diào)用稍快怔球,但是更占用內(nèi)存嚼酝。如果程序在10個不同的位置調(diào)用同一個函數(shù),則該程序?qū)摵瘮?shù)代碼的10個備份竟坛。
應有選擇的地使用內(nèi)聯(lián)函數(shù)闽巩。如果執(zhí)行函數(shù)代碼的時間比處理函數(shù)調(diào)用機制的時間長,則節(jié)省的時間只占整個過程的很小一部分担汤。如果代碼執(zhí)行時間很短涎跨,則內(nèi)聯(lián)調(diào)用就可以節(jié)省非內(nèi)聯(lián)調(diào)用使用的大部分時間。另一方面崭歧,由于這個過程相當快隅很,因此盡管節(jié)省了該過程的大部分時間,但節(jié)省的時間的絕對值并不大率碾,除非該函數(shù)經(jīng)常被調(diào)用叔营。
要使用這項特別,必須采取如下措施之一:
- 在函數(shù)聲明前加上關鍵字inline播掷;
- 在函數(shù)定義前加上關鍵字inline审编。
通常的做法是省略原型撼班,將整個定義放在本該提供原型的地方歧匈。
當函數(shù)過大或者函數(shù)有遞歸自己的時候,不應該將此函數(shù)聲明為內(nèi)聯(lián)函數(shù)砰嘁。
下面簡單舉個栗子:
inline int square(int a) {
return a * a;
}
int main() {
int aa = square(5);
cout << "aa:" << aa << endl;
return 0;
}
inline工具是C++新增的特性件炉。C語言使用預處理語句#define來提供宏——內(nèi)聯(lián)代碼的原始實現(xiàn)。例如:
#define SQUARE(X) X*X
這并不是通過傳遞參數(shù)實現(xiàn)的矮湘,而是通過文本替換來實現(xiàn)的斟冕。
a = SQUARE(5);// 實際是 a = 5*5
b = SQUARE(1+5);// 實際是 b = 1+5 * 1+5
c = SQUARE(a++);//實際是 a++ * a++
上面只有第一個能正常工作,即使通過改進宏定義
#define SQUARE(X) ((X)*(X))
對應第三個仍然是錯誤的缅阳。所以在c++中可以考慮使用將以前用宏定義的函數(shù)改為內(nèi)聯(lián)函數(shù)實現(xiàn)磕蛇。
2.引用變量
2.1 創(chuàng)建引用變量
引用是已定義的變量的別名。使用方法如下
int a = 10;
int &b = a;//b是a變量的別名
其中&不是地址運算符,而是類型標識符的一部分秀撇。比如char*表示char類型的指針超棺,int&表示int類型的引用。上述聲明表示a和b指向相同的值和內(nèi)存單元呵燕。舉個詳細的栗子:
int main() {
int a = 10;
int &b = a;
//通過b修改變量
b = 20;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
//通過a修改變量
a = 30;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
//查看他倆的地址
cout << "a addr:" << &a << endl;
cout << "b addr:" << &b << endl;
return 0;
}
輸出為
a:20
b:20
a:30
b:30
a addr:0x7fff59892938
b addr:0x7fff59892938
引用變量與指針的差別之一是棠绘,必須在聲明引用時將其初始化,如
int a = 10;
int *b;
b = &a;//可以
int &c = a;//可以
int &b;//不可以
b = a
因此引用更接近const指針再扭,必須在創(chuàng)建時初始化氧苍,并且一旦關聯(lián)起來,就一直效忠于它泛范。
2.2 將引用用作函數(shù)參數(shù)
引用經(jīng)常被用作函數(shù)參數(shù)让虐,這種傳遞參數(shù)的方法被稱為按引用傳遞。按引用傳遞允許被調(diào)用的函數(shù)訪問調(diào)用函數(shù)中的變量敦跌。這種方式和C的按指針傳遞類似澄干。舉個例子:
#include <iostream>
using namespace std;
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 10;
int b = 5;
//按指針傳遞
swap(&a, &b);
cout << "a:" << a << ",b:" << b << endl;
a = 16;
b = 25;
//按引用傳遞
swap(a, b);
cout << "a:" << a << ",b:" << b << endl;
return 0;
}
2.3 引用的屬性和特別之處
首先如果意圖是不允許函數(shù)修改參數(shù),還想使用引用柠傍,則可以用常量引用麸俘,例子:
double refcube(const int & n);//n是一個引用,引用的是一個整形常量
上面只是個例子惧笛,對于數(shù)據(jù)比較小的情況从媚,應該使用按值傳遞的方式,只有在數(shù)據(jù)較大的情況再使用常量引用(既不能通過引用修改原數(shù)據(jù)患整,又可以避免大量的數(shù)據(jù)創(chuàng)建拷貝)拜效。
以下是Primer原文的總結(jié),但在實際實驗結(jié)果和描述不一致各谚,可能是現(xiàn)代編譯器更智能了吧紧憾。
當函數(shù)參數(shù)為常量引用時,如果實參與引用參數(shù)類型不匹配昌渤,C++會生成臨時變量赴穗。注意的是參數(shù)必須是聲明為常量引用的時候,且類型不匹配才會創(chuàng)建臨時變量膀息,否則編譯器報錯般眉。例子:
#include <iostream>
using namespace std;
int square(const int &n) {
return n * n;
}
int square2(int &n) {
return n * n;
}
int main() {
long a = 10L;
square(a);//不報錯,因為會創(chuàng)建臨時變量潜支,臨時變量是long型的10轉(zhuǎn)換為int型5
square2(a);//報錯甸赃,類型不匹配
return 0;
}
創(chuàng)建臨時變量的情況有
實參的類型正確,但不是左值冗酿;-
實參的類型不正確埠对,但是可以轉(zhuǎn)換為正確的類型络断。
什么是左值呢,很多项玛,所以就說什么不是左值吧妓羊,如101、“string”這種字面量就不是左值稍计,或者 1+1 這種表達式也不是左值躁绸。
那為什么只有常量引用才可以創(chuàng)建臨時變量?還是例子:
#include <iostream>
using namespace std;
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = a;
}
int main() {
long a = 10L;
long b = 12L;
swap(a, b);
cout << "a:" << a << ",b:" << b << endl;
return 0;
}
實際打印結(jié)果:
a:12,b:10
==先說下原文的意思:如果允許創(chuàng)建臨時變量則引用就失效了臣嚣,臨時比那兩交換不代表原變量交換并扇。但是實際結(jié)果是的確交換了骗露,所以這塊還得再研究下蚕涤。我的測試環(huán)境是64位Mac OS噪裕,IDE是CLion。==
應該盡可能使用常量引用怎虫,原因如下:
- 使用const引用可以避免無意中修改數(shù)據(jù)的編程錯誤暑认;
- 使用const引用使函數(shù)能夠處理const和非const實參,否則將只能接受非const實參大审;
- 使用const引用使函數(shù)能夠正確生成并使用臨時變量蘸际。
2.4 將引用用于結(jié)構
沒啥太多說的,返回引用時要注意一點徒扶,應避免函數(shù)終止時不再存在內(nèi)存單元引用粮彤。
2.5 何時使用引用參數(shù)
使用引用參數(shù)的主要原因有兩個:
- 程序員能夠修改調(diào)用函數(shù)中的數(shù)據(jù)對象;
- 通過傳遞引用而不是整個數(shù)據(jù)對象,可以提高程序的運行速度姜骡。
對于使用傳遞的值而不作修改的函數(shù):
- 如果數(shù)據(jù)對象很小导坟,如內(nèi)置數(shù)據(jù)類型或小型結(jié)構,則按值傳遞圈澈。
- 如果數(shù)據(jù)對象是數(shù)組惫周,則使用指針,因為這是唯一的選擇康栈,并將指針聲明為指向const的指針递递。
- 如果數(shù)據(jù)對象是較大的結(jié)構,則使用const指針或const引用谅将,以提高程序的效率漾狼。這樣可以節(jié)省復制結(jié)構所需的時間和空間重慢。
- 如果數(shù)據(jù)對象是類對象饥臂,則使用const引用。類設計的語義常常要求使用引用似踱,這是C++新增這項特性的主要原因隅熙。因此稽煤,傳遞類對象參數(shù)的標準方式是按引用傳遞。
對于修改調(diào)用函數(shù)中數(shù)據(jù)的函數(shù):
- 如果數(shù)據(jù)對象是內(nèi)置數(shù)據(jù)類型囚戚,則使用指針酵熙。如果看到諸如fixit(&x)這樣的代碼(其中x是int),則很明顯,該函數(shù)將修改X。
- 如果數(shù)據(jù)對象是數(shù)組驰坊,則只能使用指針匾二。
- 如果數(shù)據(jù)對象是結(jié)構,則使用引用或指針拳芙。
- 如果數(shù)據(jù)對象是類對象察藐,則使用引用。
3.默認參數(shù)
栗子:
#include <iostream>
using namespace std;
int processInt(int a, int b = 1) {
return a * b;
}
int main() {
cout << "result1:" << processInt(10) << endl;
cout << "result2:" << processInt(10, 3) << endl;
return 0;
}
輸出結(jié)果為:
result1:10
result2:30
需要注意的是:==對于帶參數(shù)列表的函數(shù)舟扎,必須從右向左添加默認值==分飞。
4.函數(shù)重載
概念不說了。函數(shù)重載的掛件是函數(shù)的參數(shù)列表——也成為函數(shù)特征標睹限。跟返回值沒關系譬猫。注意下兩個函數(shù)原型
double process(int a,int b);
double process(int &a,int &b);
這兩個函數(shù)是沖突的。
函數(shù)匹配時羡疗,并不區(qū)分const和非const變量染服。
5.函數(shù)模板
就是泛型的使用,栗子:
template<typename _T>
void swap(_T &a, _T &b);
int main() {
using std::cout;
using std::endl;
int a = 10, b = 5;
float c = 11.1f, d = 12.2f;
swap(a, b);
cout << "a:" << a << ",b:" << b << endl;
swap(c, d);
cout << "c:" << c << ",d:" << d << endl;
return 0;
}
template<typename _T>
void swap(_T &a, _T &b) {
_T tmp = a;
a = b;
b = tmp;
}
輸出
a:5,b:10
c:12.2,d:11.1
==注意叨恨,在mac clion上使用的時候肌索,不要在執(zhí)行swap的之前寫類似using namespace std;這種代碼,因為std空間中自帶了個swap的函數(shù)特碳,參數(shù)列表也是兩個引用類型的模板诚亚。==
5.1 重載的模板
很簡單,看栗子:
#include <iostream>
template<typename _T>
void swap(_T &a, _T &b);
template<typename T>
void swap(T *a, T *b);
int main() {
using std::cout;
using std::endl;
int a = 10, b = 5;
float c = 11.1f, d = 12.2f;
swap(a, b);
cout << "a:" << a << ",b:" << b << endl;
swap(&c, &d);
cout << "c:" << c << ",d:" << d << endl;
return 0;
}
//傳遞引用的swap
template<typename _T>
void swap(_T &a, _T &b) {
_T tmp = a;
a = b;
b = tmp;
}
//傳遞指針的swap
template<typename T>
void swap(T *a, T *b) {
T tmp = *a;
*a = *b;
*b = tmp;
}
輸出
a:5,b:10
c:12.2,d:11.1
5.2 模板的局限性
還是看上面swap的栗子:
template<typename _T>
void swap(_T a, _T b);
{……}
如果傳入函數(shù)的是兩個數(shù)組午乓,則在函數(shù)能不能執(zhí)行a=b這種站宗,也不能執(zhí)行a>b,a*b等等。
5.3 顯示具體化
這個話題比較有意思益愈,從來沒想過梢灭,舉個栗子,假設有如下結(jié)構體
struct Person{
int age;
char name[40];
char id[18];
}
例外需要有個交換的函數(shù)swap蒸其,則swap和之前寫的一樣敏释,會交換兩個Person的所有屬性,但是如果我們希望有個函數(shù)值交換id和name摸袁,不交換age钥顽,則同樣需要聲明一樣的函數(shù)swap(T &a,T &b),就產(chǎn)生沖突了。這個時候可以提供一個具體化函數(shù)定義——稱為顯式具體化靠汁,其中包含所需的代碼蜂大。
C++98有如下定義和規(guī)則:
- 對于給定的函數(shù)名闽铐,可以有非模板函數(shù)、模板函數(shù)和顯式具體化模板函數(shù)以及他們的重載版本奶浦。
- 顯式具體化的原型和定義應該以template<>開頭兄墅,并通過名稱來指出類型。
- 具體化優(yōu)先于常規(guī)模板澳叉,非模板函數(shù)優(yōu)先于具體化和模板隙咸。
舉個栗子,都是原型:
void swap(Person &,Person &);//非模板函數(shù)
//模板函數(shù)
template <Typename T>
void swap(T &,T &);
//具體化模板函數(shù)
template <> void swap<Person>(Person &,Person &);
//具體化也可以這么寫,省略函數(shù)名后的Person,因為參數(shù)列表已經(jīng)表明了
template <> void swap(Person &,Person &);