一,函數(shù)重載
在C語言中,同名函數(shù)是不能出現(xiàn)在同一作用域的,但是在C++中卻可以,但是要滿足函數(shù)重載的規(guī)則。
那什么是函數(shù)重載呢?它的規(guī)則是什么呢?
1.1 函數(shù)重載的定義
函數(shù)重載:是函數(shù)的一種特殊情況,C++允許在同一作用域中聲明幾個(gè)功能類似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個(gè)數(shù) 或 類型 或 類型順序)不同,常用來處理實(shí)現(xiàn)功能類似數(shù)據(jù)類型不同的問題。
舉幾個(gè)例子:
1.1.1.形參的類型不同
void Swap(int* pa, int* pb)
{
cout << "void Swap(int* pa, int* pb)" << endl;
}
void Swap(double* pa, double* pb)
{
cout << "void Swap(double* pa, double* pb)" << endl;
}
int main()
{
int a = 0, b = 1;
double c = 1.1, d = 2.2;
Swap(&a, &b);
Swap(&c, &d);
return 0;
}
1.1.2參數(shù)的個(gè)數(shù)不同
1.1.3.參數(shù)的順序不同
void f(int a, char b)
{
cout << "void f(int a, char b)" << endl;
}
void f(char c, int d)
{
cout << "void f(char c, int d)" << endl;
}
int main()
{
int a = 0, b = 1;
double c = 1.1, d = 2.2;
f(1,'c');
f('d',3);
return 0;
}
1.1.4.有一個(gè)是缺省參數(shù)構(gòu)成重載。但是調(diào)用時(shí)存在歧義
void f()
{
cout << "void f()" << endl;
}
void f(int a = 10)
{
cout << "void f(int a = 10)" << endl;
}
int main()
{
int a = 0, b = 1;
double c = 1.1, d = 2.2;
f();
f(5);
return 0;
}
1.1.5.返回值不同,不構(gòu)成重載。因?yàn)榉祷刂悼山邮?,可不接受,調(diào)用函數(shù)產(chǎn)生歧義。
int f()
{
return 0;
}
void f()
{
cout << "void f(char c, int d)" << endl;
}
int main()
{
int a = 0, b = 1;
double c = 1.1, d = 2.2;
int ret = f();
cout << ret << endl;
f();
return 0;
}
1.2 C++支持函數(shù)重載的原理 – 名字修飾
為什么C++存在重載,C語言不支持?C++為什么支持?
這兩個(gè)問題深究起來十分復(fù)雜。它們與預(yù)處理、編譯、匯編、鏈接有極大的關(guān)系,這里只是大致解釋,只要記住結(jié)論即可。
在一般的工程項(xiàng)目中,都會(huì)進(jìn)行多文件操作,那一個(gè)函數(shù)就一定會(huì)聲明與定義分離。
通過上圖我們需要知道的兩點(diǎn):
1.函數(shù)的地址:轉(zhuǎn)到反匯編時(shí),每個(gè)函數(shù)都有一堆要執(zhí)行的指令,函數(shù)的地址是第一句指令的地址。
2.函數(shù)的地址要依靠函數(shù)的定義生成,而不是函數(shù)聲明。
函數(shù)的重載發(fā)生在函數(shù)鏈接的時(shí)候。
C語言在鏈接時(shí),直接用函數(shù)名去查找,當(dāng)遇到同名函數(shù)時(shí),無法區(qū)分,就不支持重載。
C++在鏈接時(shí),直接用修飾后的函數(shù)名去查找,當(dāng)遇到同名函數(shù)時(shí),可以區(qū)分,支持重載。
其實(shí)不同的編譯器有不同的函數(shù)名修飾規(guī)則。
在Linux的g++編譯器下:
C語言不支持重載,直接就是函數(shù)名:
C++支持重載,有函數(shù)名的修飾:
上面的圖文看不懂沒關(guān)系。通過上面的分析??梢缘贸鼋Y(jié)論:
- 在項(xiàng)目工程中,使用多文件操作,函數(shù)的定義與聲明分離,而在鏈接的時(shí)候,要用函數(shù)名去找地址。C語言直接用函數(shù)名去查找,當(dāng)遇到同名函數(shù)時(shí),無法區(qū)分,而C++有函數(shù)名修飾規(guī)則,只要參數(shù)不同,修飾出來的名字就不一樣,可以區(qū)分 。并且不同編譯器的修飾規(guī)則不同。
二,引用
2.1 引用的概念
引用不是新定義一個(gè)變量,而是給已存在變量取了一個(gè)別名,編譯器不會(huì)為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間。也可以給別名再取別名。
舉個(gè)例子:
int main()
{
int a = 1;
//引用:& b是a的別名
int& b = a;
//也可以給別名取別名
int& c = b;
//值相同,地址也相同
cout << "a = " << a<< endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
cout << "&a = " << &a << endl;
cout << "&b = " << &b << endl;
cout << "&c = " << &c << endl;
return 0;
}
2.2 引用的使用場景
2.2.1 引用做參數(shù)
最典型的例子就是我們經(jīng)常使用的交換函數(shù):
//a是x的別名,b是y的別名。a,b的交換就是x,y的交換
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 1;
int y = 2;
Swap(x, y);//這里就不用傳地址過去了
cout << x << ":" << y << endl;
return 0;
}
2.2.2 引用做返回值
這里暫時(shí)不解釋,到后面學(xué)習(xí)了類和對象后再解釋,會(huì)更加清晰。
2.3 引用特性
1.引用在定義時(shí)必須初始化
2.一個(gè)變量可以有多個(gè)引用
3.引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體
第1,2條容易理解,這里說明第三條:
int main()
{
int x = 0;
int& y = x;//y是x的別名
int z = 1;
//這里y是z的別名,還是z賦值給y?
//是賦值。引用一旦引用一個(gè)實(shí)體,就不能再改變指向了!
y = z;
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl << endl;
return 0;
}
2.4 引用的權(quán)限問題
這里所說的權(quán)限有:權(quán)限放大,權(quán)限縮小,權(quán)限平移。
2.4.1 舉例1
int main()
{
//這里權(quán)限放大了。m是只讀,n變成m的別名后,可讀可寫。
const int m = 0;
//int& n = m; //err
int p = m;//可以,不是權(quán)限的放大。
//只是把m拷貝給p,p的修改不影響m。
//n也是只讀的,權(quán)限的平移
const int& n = m;//可以
return 0;
}
2.4.2 舉例2
int main()
{
const int m = 0;
// 權(quán)限的放大
//p1可以修改,*p1不行,const修飾的是p1指向的內(nèi)容,即*p1。
const int* p1 = &m;
p1++;//可以
//int* p2 = p1;//err. *p2是可以修改的,所以權(quán)限放大了。
return 0;
}
2.4.3 舉例3
int main()
{
//權(quán)限的縮小
int x = 0;
int& y = x;
const int& z = x;//可以
y++;//x,z都會(huì)修改
return 0;
}
2.4.4 總結(jié)
1.權(quán)限放大問題存在于引用和指針里,普通的賦值拷貝不會(huì)。
2.權(quán)限只能縮小,平移,不能放大。
2.5 常引用
2.5.1 類型轉(zhuǎn)換時(shí)
因?yàn)轭愋娃D(zhuǎn)換的過程中會(huì)生成臨時(shí)變量,這個(gè)臨時(shí)變量具有常性(相當(dāng)于被const修飾)。權(quán)限被放大了。
int main()
{
double d = 3.24;
//類型轉(zhuǎn)換
int i = d;
//int& r = d;err
const int& r = d;//可以。加了const就相當(dāng)于權(quán)限平移了。
return 0;
}
2.5.2 表達(dá)式運(yùn)算時(shí)
表達(dá)式運(yùn)算也會(huì)生成臨時(shí)變量,解釋同上。
int main()
{
int x = 0, y = 1;
//表達(dá)式運(yùn)算也會(huì)生成臨時(shí)變量
//int& r2 = x + y; //err
const int& r2 = x + y; //可以
return 0;
}
2.6 傳值、傳引用效率比較
其實(shí)這與函數(shù)的傳值,傳址調(diào)用類似。當(dāng)函數(shù)傳值調(diào)用時(shí),會(huì)在內(nèi)存中又開辟空間臨時(shí)拷貝,當(dāng)參數(shù)較大時(shí),效率低下。而傳址調(diào)用卻不會(huì),會(huì)提高效率。
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作為函數(shù)參數(shù)
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作為函數(shù)參數(shù)
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分別計(jì)算兩個(gè)函數(shù)運(yùn)行結(jié)束后的時(shí)間
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
注意:0毫米表示運(yùn)行時(shí)間小于1毫米。
2.7 引用和指針的區(qū)別
在語法概念上引用就是一個(gè)別名,沒有獨(dú)立空間,和其引用實(shí)體共用同一塊空間。
int main()
{
int a = 10;
int& ra = a;
cout << "&a = " << &a << endl;
cout << "&ra = " << &ra << endl;
return 0;
}
在底層實(shí)現(xiàn)上實(shí)際是有空間的,因?yàn)?strong>引用是按照指針方式來實(shí)現(xiàn)的。
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
我們來看下引用和指針的匯編代碼對比:
引用和指針的不同點(diǎn):
- 引用概念上定義一個(gè)變量的別名,指針存儲(chǔ)一個(gè)變量地址。
- 引用在定義時(shí)必須初始化,指針沒有要求。
- 引用在初始化時(shí)引用一個(gè)實(shí)體后,就不能再引用其他實(shí)體,而指針可以在任何時(shí)候指向任何一個(gè)同類型實(shí)體。
- 沒有NULL引用,但有NULL指針。
- 在sizeof中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個(gè)數(shù)(32位平臺下占4個(gè)字節(jié))。
- 引用自加即引用的實(shí)體增加1,指針自加即指針向后偏移一個(gè)類型的大小。
- 有多級指針,但是沒有多級引用。
- 訪問實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理。
- 引用比指針使用起來相對更安全。
三,內(nèi)聯(lián)函數(shù)
如果有一個(gè)頻繁調(diào)用的小函數(shù),例如Swap交換函數(shù),在頻繁調(diào)用時(shí)會(huì)有建立棧幀的消耗,我們想消除這樣的消耗,該怎么做呢?
C語言中是使用宏函數(shù)解決,C++中就可以使用內(nèi)聯(lián)函數(shù)。
3.1 內(nèi)聯(lián)函數(shù)的概念
以inline修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時(shí)C++編譯器會(huì)在調(diào)用內(nèi)聯(lián)函數(shù)的地方展開,沒有函數(shù)調(diào)用建立棧幀的開銷,內(nèi)聯(lián)函數(shù)提升程序運(yùn)行的效率。
3.2 inline 關(guān)鍵字
在沒有加 inline 之前:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int c = Add(1, 2);
cout << c << endl;
return 0;
}
如果在上述函數(shù)前增加inline關(guān)鍵字將其改成內(nèi)聯(lián)函數(shù),在編譯期間編譯器會(huì)用函數(shù)體替換函數(shù)的調(diào)用。
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
int c = Add(1, 2);
cout << c << endl;
return 0;
}
查看方式:
1.在release模式下,查看編譯器生成的匯編代碼中是否存在call Add。
2.在debug模式下,需要對編譯器進(jìn)行設(shè)置,否則不會(huì)展開(因?yàn)閐ebug模式下,編譯器默認(rèn)不會(huì)對代碼進(jìn)行優(yōu)化,以下給出vs2013的設(shè)置方式)。
轉(zhuǎn)到匯編中:
3.3 內(nèi)聯(lián)的特性
1.inline是一種以空間換時(shí)間的做法,如果編譯器將函數(shù)當(dāng)成內(nèi)聯(lián)函數(shù)處理,在編譯階段,會(huì)用函數(shù)體替換函數(shù)調(diào)用,缺陷:可能會(huì)使目標(biāo)文件變大,優(yōu)勢:少了調(diào)用開銷,提高程序運(yùn)行效率。
2.inline對于編譯器而言只是一個(gè)建議,不同編譯器關(guān)于inline實(shí)現(xiàn)機(jī)制可能不同,一般建議:將函數(shù)規(guī)模較小(即函數(shù)不是很長,具體沒有準(zhǔn)確的說法,取決于編譯器內(nèi)部實(shí)現(xiàn))、不是遞歸、且頻繁調(diào)用的函數(shù)采用inline修飾,否則編譯器會(huì)忽略inline特性。
3.inline不建議聲明和定義分離,分離會(huì)導(dǎo)致鏈接錯(cuò)誤。因?yàn)閕nline被展開,就沒有函數(shù)地址了,鏈接就會(huì)找不到。
四,auto關(guān)鍵字(C++11)
其實(shí) auto 關(guān)鍵字的介紹和使用內(nèi)容十分豐富,我們現(xiàn)在這個(gè)階段不需要了解太詳細(xì),在以后的學(xué)習(xí)中會(huì)邊學(xué)邊了解。
4.1 給類型取別名
隨著程序越來越復(fù)雜,程序中用到的類型也越來越復(fù)雜,經(jīng)常體現(xiàn)在:類型難于拼寫,含義不明確導(dǎo)致容易出錯(cuò)。
有人會(huì)想到 typedef 不也可以給類型取別名嗎?但是在某些場合下是有缺陷的:
typedef char* pstring;
int main()
{
//const pstring p1;//err. const修飾指針p1本身,
//而const修飾的指針要初始化
const pstring* p2; // 可以. const修飾 *p2
return 0;
}
4.2 可由初始化自動(dòng)推導(dǎo)類型
比如,下面的寫法是正確的:
int main()
{
int i = 0;
//根據(jù)右邊得到初始化自動(dòng)推導(dǎo)類型
auto j = 7;
return 0;
}
注意:下面的 typeid(變量名).name 判斷一個(gè)對象的類型
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
//
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 無法通過編譯,使用auto定義變量時(shí)必須對其進(jìn)行初始化
return 0;
}
- 使用auto定義變量時(shí)必須對其進(jìn)行初始化,在編譯階段編譯器需要根據(jù)初始化表達(dá)式來推導(dǎo)auto的實(shí)際類型。因此auto并非是一種“類型”的聲明,而是一個(gè)類型聲明時(shí)的“占位符”,編譯器在編譯期會(huì)將auto替換為變量實(shí)際的類型。
4.3 auto不能推導(dǎo)的場景
1.auto不能作為函數(shù)的參數(shù)。
2.auto不能直接用來聲明數(shù)組。
五,基于范圍的for循環(huán)
5.1 范圍for的語法
在C++98中如果要遍歷一個(gè)數(shù)組,可以按照以下方式進(jìn)行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
對于一個(gè)有范圍的集合而言,由程序員來說明循環(huán)的范圍是多余的,有時(shí)候還會(huì)容易犯錯(cuò)誤。因此C++11中引入了基于范圍的for循環(huán)。for循環(huán)后的括號由冒號“ :”分為兩部分:第一部分是范圍內(nèi)用于迭代的變量,第二部分則表示被迭代的范圍。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
//注意:這里如果要改變原數(shù)組元素的值,要加引用&
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
注意:與普通循環(huán)類似,可以用continue來結(jié)束本次循環(huán),也可以用break來跳出整個(gè)循環(huán)。
5.2 范圍for的使用條件
1.for循環(huán)迭代的范圍必須是確定的。
2.迭代的對象要實(shí)現(xiàn)++和==的操作。(關(guān)于迭代器這個(gè)問題,以后會(huì)講,現(xiàn)在提一下,沒辦法講清楚,現(xiàn)在大家了解一下就可以了)
六,nullptr關(guān)鍵字
在良好的C/C++編程習(xí)慣中,聲明一個(gè)變量時(shí)最好給該變量一個(gè)合適的初始值,否則可能會(huì)出現(xiàn)不可預(yù)料的錯(cuò)誤,比如未初始化的指針。
在C語言中,如果一個(gè)指針沒有合法的指向,我們基本都是按照如下方式對其進(jìn)行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL實(shí)際是一個(gè)宏,在傳統(tǒng)的C頭文件(stddef.h)中,可以看到如下代碼:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定義為字面常量0,或者被定義為無類型指針(void)的常量* 。不論采取何種定義,在使用空值的指針時(shí),都不可避免的會(huì)遇到一些麻煩,比如:
程序本意是想通過f(NULL)調(diào)用指針版本的f(int*)函數(shù),但是由于NULL被定義成0,所以會(huì)調(diào)用f(int)函數(shù),因此與程序的初衷相悖。
在C++98中,字面常量0既可以是一個(gè)整形數(shù)字,也可以是無類型的指針(void)常量,但是編譯器默認(rèn)情況下將其看成是一個(gè)整形常量,如果要將其按照指針方式來使用,必須對其進(jìn)行強(qiáng)轉(zhuǎn)(void )0。
注意:
1.在使用nullptr表示指針空值時(shí),不需要包含頭文件,因?yàn)閚ullptr是C++11作為新關(guān)鍵字引入的。
2 在C++11中,sizeof(nullptr) 與 sizeof((void)0)所占的字節(jié)數(shù)相同。*文章來源:http://www.zghlxwxcb.cn/news/detail-858192.html
3.為了提高代碼的健壯性,在后續(xù)表示指針空值時(shí)建議最好使用nullptr。文章來源地址http://www.zghlxwxcb.cn/news/detail-858192.html
到了這里,關(guān)于【C++】:函數(shù)重載,引用,內(nèi)聯(lián)函數(shù),auto關(guān)鍵字,基于范圍的for循環(huán),nullptr關(guān)鍵字的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!