首先我們先要知道,C++是在C的基礎(chǔ)之上,容納進(jìn)去了面向?qū)ο缶幊趟枷?,并增加了許多有用的庫(kù)。本章將會(huì)帶大家了解,C++是補(bǔ)充C語(yǔ)言語(yǔ)法的不足,以及C++是如何對(duì)C語(yǔ)言設(shè)計(jì)不合理的地方進(jìn)行優(yōu)化的。
一、命名空間
1. namespace
在C/C++中,變量、函數(shù)等等都是大量存在的,這些變量、函數(shù)和類(lèi)的名稱將都存在于全局作用域中,可能會(huì)導(dǎo)致很多沖突。使用命名空間的目的是對(duì)標(biāo)識(shí)符的名稱進(jìn)行本地化,以避免命名沖突,namespace 關(guān)鍵字的出現(xiàn)就是針對(duì)這種問(wèn)題的。
例如,我們想定義一個(gè)變量 sqrt ,直接定義在全局變量然后編譯是可以通過(guò)的,例如下圖:
但是,我們知道 sqrt 其實(shí)是一個(gè)庫(kù)函數(shù),它包含在 math.h 的頭文件中,假設(shè)我們加上 math.h 的頭文件,還能編譯過(guò)嗎?答案是不能,因?yàn)樗鼈冎孛耍绻?math.h 的頭文件,編譯不會(huì)通過(guò),會(huì)報(bào)下圖中的錯(cuò)誤:
那么有沒(méi)有好的解決方案呢,答案是有的,C++中就增加了 namespace 這樣的關(guān)鍵字解決這樣的問(wèn)題。例如我們可以將我們需要定義的變量放入 namespace 的命名空間中,然后在使用讓編譯器在指定的命名空間中尋找;如果不指定編譯器,編譯器優(yōu)先會(huì)在全局域中尋找變量;namespace 的使用:
#include <stdio.h>
#include <math.h>
// 命名空間的名字
namespace Young
{
int sqrt = 10;
}
int main()
{
printf("%d\n", Young::sqrt);
return 0;
}
上述代碼的使用就是讓編譯器在指定的命名空間 Young 中去尋找變量 sqrt 然后使用這個(gè)變量,這樣就不會(huì)與庫(kù)函數(shù)中的 sqrt 函數(shù)有命名沖突了;Young 是一個(gè)可以自己命名的命名空間的名字,可以取任意名字,不一定是 Young.
像 printf("%d\n", Young::sqrt);
中,sqrt 前面的 :: 符號(hào),叫做域作用限定符,意思是讓編譯器使用域作用限定符前面的命名空間中定義的東西。
2. namespace 的使用場(chǎng)景
除了上面我們使用 namespace 在命名空間中定義變量外,還可以定義函數(shù)、結(jié)構(gòu)體等;除此之外,還可以嵌套使用。例如以下代碼:
namespace Young
{
//變量
int sqrt = 10;
// 函數(shù)
int Add(int a, int b)
{
return a + b;
}
// 結(jié)構(gòu)體
struct ListNode
{
int data;
struct ListNode* next;
};
// 嵌套使用
namespace Y
{
int a = 10;
}
}
int main()
{
int ret = Young::Add(1, 2);
printf("%d\n", ret);
struct Young::ListNode node;
printf("%d\n", Young::Y::a);
return 0;
}
上述代碼中主函數(shù)部分,結(jié)構(gòu)體中的域作用限定符是要在 ListNode 前使用,而不是在 struct 前使用;嵌套使用 namespace 是從右往左看,到指定的命名空間中去尋找;
雖然這種方法可以有效避免命名沖突問(wèn)題,但是每次用的時(shí)候都要在前面加上域作用限定符,是不是很麻煩呢?確實(shí)是,但是還有一種方法可以解決,將命名空間展開(kāi);以上面的命名空間為例,例如以下代碼:
// 將命名空間展開(kāi)
using namespace Young;
using namespace Y;
int main()
{
int ret = Add(1, 2);
printf("%d\n", ret);
struct ListNode node;
printf("%d\n",a);
return 0;
}
上面的代碼就將 Young 和 Y 兩個(gè)命名空間中的內(nèi)容展開(kāi),就不用再使用域作用限定符了;除此之外,我們還可以展開(kāi)部分命名空間中的內(nèi)容,例如,我只展開(kāi) Add 函數(shù)出來(lái):
// 展開(kāi)部分
using Young::Add;
int main()
{
int ret = Add(1, 2);
printf("%d\n", ret);
struct Young::ListNode node;
printf("%d\n", Young::Y::a);
return 0;
}
以上就是展開(kāi)部分的命名空間,通常在做項(xiàng)目的時(shí)候,我們都不會(huì)將命名空間展開(kāi),因?yàn)檎归_(kāi)就會(huì)變得不安全;但是在平常我們?cè)趯?xiě)代碼練習(xí)的時(shí)候,可以將命名空間展開(kāi),更有利于我們練習(xí)。
二、了解 C++ 中的輸入和輸出
首先我們先要知道,C++中引入了不同于C語(yǔ)言的輸入和輸出,在C語(yǔ)言中我們使用 scanf 和 printf 作為輸入和輸出,但是在C++中了 cout 標(biāo)準(zhǔn)輸出對(duì)象(控制臺(tái))和 cin 標(biāo)準(zhǔn)輸入對(duì)象(鍵盤(pán));我們先看看它們的使用:
我們可以了解到,上述代碼中的 cout 和 cin 分別叫做流插入運(yùn)算符和流提取運(yùn)算符,關(guān)于這兩個(gè)更多的我們?cè)谝院蟮膶W(xué)習(xí)中再介紹;其中 cout 和 cin 必須包含< iostream >頭文件以及按命名空間使用方法使用 std ,其中 std 是C++標(biāo)準(zhǔn)庫(kù)的命名空間名,C++將標(biāo)準(zhǔn)庫(kù)的定義實(shí)現(xiàn)都放到這個(gè)命名空間中。所以我們可以展開(kāi) std 的命名空間:
#include <iostream>
using namespace std;
int main()
{
int input;
double d;
// 自動(dòng)識(shí)別類(lèi)型
cin >> input >> d;
cout << input << endl << d << endl;
return 0;
}
除此之外,cin 和 cout 還可以自動(dòng)識(shí)別變量的類(lèi)型,如上述代碼,它的輸出如下圖:
三、缺省參數(shù)
缺省參數(shù)是聲明或定義函數(shù)時(shí)為函數(shù)的參數(shù)指定一個(gè)缺省值。在調(diào)用該函數(shù)時(shí),如果沒(méi)有指定實(shí)參則采用該形參的缺省值,否則使用指定的實(shí)參。先看看缺省參數(shù)的使用:
在上面的使用中,Add 函數(shù)就是用了缺省參數(shù),在 Add 函數(shù)定義中,它指定了 a = 100,b = 200,意思就是,當(dāng)調(diào)用 Add 函數(shù)時(shí),如果沒(méi)有參數(shù)傳進(jìn)來(lái),就使用它自己定義的變量;傳參時(shí),就使用指定的實(shí)參,如下圖:
當(dāng)然也可以只傳一部分參數(shù),但是當(dāng)出現(xiàn)多個(gè)參數(shù)時(shí),參數(shù)必須從右往左依次來(lái)給出,不能間隔著給;例如:
#include <iostream>
using namespace std;
int Add(int a = 100, int b = 200, int c = 300)
{
return a + b + c;
}
int main()
{
int a = 10, b = 20, c = 30;
int ret = Add(a);
cout << ret << endl;
return 0;
}
以上這段的代碼輸出結(jié)果就是 510 ,那么例如 int ret = Add(a,,c);
這種傳參是不允許的。
那么我們可以給缺省參數(shù)分類(lèi),像上面代碼中,Add()
這種什么都不傳的就叫做全缺省參數(shù);像Add(a)
或者Add(a,b)
這種只傳一部分的就叫做半缺省參數(shù)。
最后,我們要注意缺省參數(shù)不能在函數(shù)聲明和定義中同時(shí)出現(xiàn),如果在函數(shù)聲明和函數(shù)中同時(shí)出現(xiàn),我們只需要在聲明中給缺省值即可。
四、函數(shù)重載
1. 函數(shù)重載的概念
函數(shù)重載:是函數(shù)的一種特殊情況,C++允許在同一作用域中聲明幾個(gè)功能類(lèi)似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個(gè)數(shù) 或 類(lèi)型 或 類(lèi)型順序)不同,常用來(lái)處理實(shí)現(xiàn)功能類(lèi)似數(shù)據(jù)類(lèi)型不同的問(wèn)題。我們先看使用:
#include <iostream>
using namespace std;
void Add(int a ,double b)
{
// 打印數(shù)據(jù)方便觀察
cout << "void Add(int a ,double b)" << endl;
}
void Add(double a, int b)
{
// 打印數(shù)據(jù)方便觀察
cout << "void Add(double a, int b)" << endl;
}
int main()
{
Add(3, 3.14);
Add(3.14, 3);
return 0;
}
運(yùn)行的結(jié)果如下:
以上代碼中,我們?cè)诤瘮?shù)中打印數(shù)據(jù),是為了說(shuō)明編譯器調(diào)用了這個(gè)函數(shù);我們定義了兩個(gè)同名的函數(shù),但是它們的參數(shù)類(lèi)型不一樣,而我們?cè)谑褂眠@兩個(gè)函數(shù)的時(shí)候,傳的參數(shù)也不一樣,所以它們會(huì)調(diào)用各自對(duì)應(yīng)的函數(shù);
2. C++支持函數(shù)重載的原理
C++支持函數(shù)重載的原理是因?yàn)镃++有自己的函數(shù)名修飾規(guī)則。
我們知道,.cpp文件或者.c文件在生成可執(zhí)行程序之前,要經(jīng)過(guò)預(yù)處理,編譯,匯編,鏈接的過(guò)程,具體回顧往期博客:預(yù)處理和程序環(huán)境;
其中,C語(yǔ)言在編譯過(guò)程中,符號(hào)匯總將所有.c文件的函數(shù)名匯總在一起,注意,是函數(shù)名,所以在C語(yǔ)言中,重名的函數(shù)名在編譯過(guò)程中會(huì)有沖突,編譯不通過(guò);
但是,在C++中的函數(shù)名修飾規(guī)則中,C++不是用函數(shù)名匯總在一起,而是有它自己的修飾規(guī)則,具體的修飾規(guī)則在不同的編譯器有不同的修飾規(guī)則,例如:
void func(int i, double d)
{}
void func(double d, int i)
{}
這兩個(gè)函數(shù),在 g++ 編譯器的函數(shù)修飾后變成【_Z+函數(shù)長(zhǎng)度+函數(shù)名+類(lèi)型首字母】,如圖:
所以它們?cè)诰幾g匯總的時(shí)候是可以區(qū)分開(kāi)來(lái)的。
五、引用
1. 引用的概念
引用不是新定義一個(gè)變量,而是給已存在變量取了一個(gè)別名,編譯器不會(huì)為引用變量開(kāi)辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間。先看一個(gè)簡(jiǎn)單的例子:
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
return 0;
}
以上代碼中int& b = a;
就是在定義引用類(lèi)型,b 就是 a 的別名,a 和 b 實(shí)際上都是指向同一個(gè)空間,a 的改變會(huì)影響 b ,b 的改變也會(huì)影響 a.
2. 引用特性
-
引用在定義時(shí)必須初始化
-
一個(gè)變量可以有多個(gè)引用
-
引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體
void Test() { int a = 10; // int& ra; // 該語(yǔ)句編譯時(shí)會(huì)出錯(cuò) int& ra = a; int& rra = a; }
int& ra;
會(huì)編譯出錯(cuò)是因?yàn)樵诙x時(shí)沒(méi)有初始化;上述代碼中,rra 是 ra 的別名,也是 a 的別名,這三個(gè)變量用的都是同一個(gè)空間,它們之間的互相改變都會(huì)影響彼此。
3. 常引用
我們?cè)谑褂靡脮r(shí)要遵守一條規(guī)則,就是在引用的過(guò)程中,權(quán)限可以平移,權(quán)限也可以縮小,但是權(quán)限不能放大。例如:
int main()
{
const int a = 0;
// 權(quán)限的放大,不允許
//int& b = a;
// 不算權(quán)限的放大,因?yàn)檫@里是賦值拷貝,b修改不影響a
//int b = a;
// 權(quán)限的平移,允許
const int& c = a;
// 權(quán)限的縮小,允許
int x = 0;
const int& y = x;
return 0;
}
上述代碼中,權(quán)限的放大是指,const int a = 0;
const修飾的 a 變量具有常性,不可修改,是只讀,但是int& b = a;
代表 b 的值可修改,并且 b 的值修改會(huì)影響 a ,b 是可讀可寫(xiě)的,但是 a 只有只讀,所以這里是權(quán)限的放大;但是int b = a;
不算權(quán)限的放大,因?yàn)檫@里是賦值拷貝,b 的修改不影響 a.
權(quán)限的平移是指,大家都具有一樣的權(quán)限,例如上述代碼中的const int& c = a;
此處的 c 和 a 都被 const 修飾了,大家都具有常性,所以是權(quán)限的平移,是可以的。
權(quán)限的縮小在上述代碼中,int x = 0; const int& y = x;
是指 x 是可讀可寫(xiě)的,但 y 被 const 修飾了,只有只讀,但是從可讀可寫(xiě)轉(zhuǎn)變成只讀是允許的,這種就叫做權(quán)限的縮小。
那么我們看一下以下的語(yǔ)句屬于什么呢?
void test()
{
int i = 0;
double& d = i;
}
首先我們應(yīng)該了解清楚,如果是int i = 0; double d = i;
也是可以的,因?yàn)樗鼈冎g會(huì)發(fā)生整型提升;那么我們要清楚,這個(gè)整型提升的過(guò)程中,會(huì)發(fā)生拷貝的過(guò)程,d 取的是 i 的臨時(shí)拷貝,如下圖,而這個(gè)臨時(shí)拷貝具有常性,不可被修改,所以這里是權(quán)限的放大,是不允許的。
所以正確的語(yǔ)句應(yīng)該如下:
void test()
{
int i = 0;
const double& d = i;
}
將 d 的屬性也變成不可修改,那么它們之間就是權(quán)限的平移關(guān)系了。
4. 引用的使用場(chǎng)景
(1)做參數(shù)(傳引用傳參)
我們常見(jiàn)的傳引用傳參就是交換函數(shù)了,寫(xiě)一個(gè)我們常用的交換函數(shù)如下:
#include <iostream>
using namespace std;
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
int main()
{
int a = 10, b = 20;
Swap(&a, &b);
return 0;
}
在這個(gè)交換函數(shù)中,我們需要傳 a 的地址和 b 的地址過(guò)去,才能改變 a 和 b 的值;在C++中,我們可以使用引用完成同樣的交換,代碼如下:
void Swap(int& p1, int& p2)
{
int tmp = p1;
p1 = p2;
p2 = tmp;
}
int main()
{
int a = 10, b = 20;
Swap(a, b);
return 0;
}
使用了引用后,代碼整體看起來(lái)就很舒服,不用像指針那樣傳地址和解引用;同時(shí)傳引用傳參還能提高傳參的效率,因?yàn)槊恳淮蝹髦坊蛘邆髦刀际且淮慰截悾總饕淮尉鸵嗫截愐淮?,效率很低;而引用則不需要拷貝,因?yàn)樾螀⑹菍?shí)參的別名,就不用進(jìn)行拷貝。
除此之外,傳引用傳參最舒服的地方還是在我們以前學(xué)過(guò)的單鏈表中,如往期博客 單鏈表 中,無(wú)論是頭插還是尾插等等操作,都需要傳二級(jí)指針才能改變鏈表的整體結(jié)構(gòu),而C++引入了引用之后,就不需要傳二級(jí)指針了,如下代碼:
void SLTPushBack(SLTNode*& phead, SLTDateType x)
{
// ...
if (phead == NULL)
{
phead = newnode;
}
else
{
//...
}
}
int main()
{
SLTNode* plist = NULL;
SLTPushBack(plist, 1);
SLTPushBack(plist, 2);
SLTPushBack(plist, 3);
return 0;
}
(2)做返回值(傳引用返回)
在使用傳引用返回時(shí)需要注意,不像傳引用傳參一樣,傳引用返回如果出了函數(shù)作用域?qū)ο筮€在的話才可以用,如果出了函數(shù)作用域?qū)ο蟛辉诰筒荒苡茫蝗缫韵麓a:
int& func()
{
int n = 0;
n = 10;
return n;
}
int main()
{
int ret = func();
return 0;
}
在這段代碼中,函數(shù) func 內(nèi)定義了一個(gè)變量 n,但是它的生命周期只在這個(gè)函數(shù)內(nèi),出了函數(shù)作用域它的空間就會(huì)被銷(xiāo)毀,畫(huà)圖更好地理解:
如上圖,func 銷(xiāo)毀后,n 隨之也會(huì)銷(xiāo)毀,將空間歸還給操作系統(tǒng),但是在 main 函數(shù)中,ret 實(shí)際上是相當(dāng)于訪問(wèn)已經(jīng)銷(xiāo)毀的 n ,這嚴(yán)格來(lái)說(shuō)相當(dāng)于野指針問(wèn)題了,也就是越界訪問(wèn)。
但是在不同的編譯器中,得出的結(jié)果卻不一樣,在 vs2019 中,是可以得到 n 的值,如下圖:
而在 gcc/g++ 的編譯器中,卻報(bào)錯(cuò)了,如下圖:
原因是因?yàn)椋@取決于棧幀銷(xiāo)毀之后,編譯器是否會(huì)對(duì)已經(jīng)銷(xiāo)毀的空間初始化,如果對(duì)已經(jīng)銷(xiāo)毀的空間進(jìn)行初始化,而繼續(xù)對(duì)它進(jìn)行訪問(wèn),就是越界,像 gcc/g++ 這樣的編譯器,很明顯在空間回收時(shí)會(huì)對(duì)空間進(jìn)行初始化,所以造成越界;而 vs2019 則沒(méi)有嚴(yán)格的檢查。
拓展:那如果將代碼改成如下,還能編譯通過(guò)嗎?
int& func()
{
int n = 0;
n = 10;
return n;
}
int main()
{
int& ret = func();
cout << ret << endl;
cout << ret << endl;
return 0;
}
這里將 ret 的接收改成了引用,也就是說(shuō),ret 是返回的 n 的別名,我們看執(zhí)行結(jié)果:
第二次執(zhí)行是隨機(jī)值,為什么呢?原因是因?yàn)?ret 是 n 的別名,它們公用同一個(gè)空間,在執(zhí)行 cout 語(yǔ)句時(shí),也會(huì)發(fā)生一系列函數(shù)棧幀的創(chuàng)建,所以新的空間會(huì)覆蓋之前的 func 所在的空間,也就是說(shuō),n 的空間被覆蓋了,也就是 ret 的空間被覆蓋了,所以 n 的值也就變成了隨機(jī)值;第一次是 10 的原因是原來(lái)的空間并沒(méi)有被覆蓋。
所以就引入了另一個(gè)話題,如果 n 的空間沒(méi)有被覆蓋,它是不是還是 10 呢?那么我們將代碼修改成以下代碼:
int& func()
{
int a[1000];
int n = 0;
n = 10;
return n;
}
int main()
{
int& ret = func();
cout << ret << endl;
cout << ret << endl;
return 0;
}
在 func 函數(shù)內(nèi),我們?cè)黾恿艘粋€(gè)長(zhǎng)度為 1000 的數(shù)組,我們先看運(yùn)行結(jié)果:
這個(gè)時(shí)候又變成了 10 ,這是因?yàn)楹瘮?shù)的棧幀中空間是向下創(chuàng)建的,所以在 func 函數(shù)內(nèi),先創(chuàng)建 1000 個(gè)空間,然后再為 n 創(chuàng)建空間,n 這個(gè)時(shí)候的位置是處于下方的;如果 func 銷(xiāo)毀后,如果有新的空間覆蓋,這要取決于這個(gè)空間是否比原來(lái) func 的空間要大,如果這個(gè)空間很大,覆蓋了 n ,那么 n 就會(huì)變成隨機(jī)值,否則,n 還是原來(lái)的值。
那么傳引用返回有什么應(yīng)用場(chǎng)景呢?我們常見(jiàn)的傳引用返回可以用作修改返回對(duì)象,例如在單鏈表中,查找函數(shù)和修改函數(shù)可以合并在一起寫(xiě),使用傳引用返回,這樣就既可以查找到想要查找的數(shù)據(jù),又能修改想要修改的值。例如以下代碼:
int& SLFindOrModify(struct SeqList& ps, int i)
{
assert(i < ps.size);
// ...
return (ps.a[i]);
}
int main()
{
// 定義對(duì)象
struct SeqList s;
// 查找 10 這個(gè)數(shù)據(jù),并將它修改成 20
SLFindOrModify(s, 10) = 20;
return 0;
}
(3)引用和指針的區(qū)別
現(xiàn)在我們都學(xué)過(guò)指針和引用了,我們可以發(fā)現(xiàn),其實(shí)引用和指針很相似,在很多用法上指針可以代替引用,引用也可以代替指針,那么它們之間又有什么區(qū)別呢?我們一一分析:
引用和指針的不同點(diǎn):
- 引用概念上定義一個(gè)變量的別名,指針存儲(chǔ)一個(gè)變量地址。
- 引用在定義時(shí)必須初始化,指針沒(méi)有要求
- 引用在初始化時(shí)引用一個(gè)實(shí)體后,就不能再引用其他實(shí)體,而指針可以在任何時(shí)候指向任何
一個(gè)同類(lèi)型實(shí)體 - 沒(méi)有NULL引用,但有NULL指針
- 在sizeof中含義不同:引用結(jié)果為引用類(lèi)型的大小,但指針始終是地址空間所占字節(jié)個(gè)數(shù)(32
位平臺(tái)下占4個(gè)字節(jié)) - 引用自加即引用的實(shí)體增加1,指針自加即指針向后偏移一個(gè)類(lèi)型的大小
- 有多級(jí)指針,但是沒(méi)有多級(jí)引用
- 訪問(wèn)實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來(lái)相對(duì)更安全
六、內(nèi)聯(lián)函數(shù)
1. #define定義宏
我們以前學(xué)過(guò) #define定義宏,如往期博客 #define定義宏 中,宏給我們帶來(lái)很多好處,如針對(duì)頻繁調(diào)用的小函數(shù),不需要建立棧幀,提高了效率;如以下代碼:
#define ADD(a,b) ((a)+(b))
int main()
{
int ret = ADD(10, 20);
cout << ret << endl;
return 0;
}
以上的宏定義了兩個(gè)數(shù)的相加,注意,這里宏定義的((a)+(b))
不能寫(xiě)成(a+b)
,因?yàn)榭紤]到運(yùn)算符優(yōu)先級(jí)問(wèn)題,如ADD(1 | 2 + 1 & 2)
這種表達(dá)式,加號(hào)優(yōu)先級(jí)更高,會(huì)先執(zhí)行加的操作,再執(zhí)行 | 和 & ,并不是我們想要的結(jié)果。
上面的宏定義在預(yù)處理階段是直接展開(kāi)替換,所以沒(méi)有建立棧幀,很好地提高了效率。
但是宏給我們帶來(lái)好處的同時(shí),必然會(huì)帶來(lái)不便,如使用宏定義會(huì)容易出錯(cuò),就如上面兩數(shù)相加的宏,少一個(gè)括號(hào)都不行,所以宏的語(yǔ)法坑很多。
最后總結(jié)一下宏的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
- 沒(méi)有類(lèi)型的嚴(yán)格限制。
- 沒(méi)有函數(shù)棧幀的建立,提高效率。
缺點(diǎn):
- 不方便調(diào)試宏。(因?yàn)轭A(yù)編譯階段進(jìn)行了替換)
- 導(dǎo)致代碼可讀性差。
- 沒(méi)有類(lèi)型安全的檢查 。
- 容易出錯(cuò),語(yǔ)法坑多。
2. 內(nèi)聯(lián)函數(shù)的概念
所以C++引入了內(nèi)聯(lián)函數(shù),以 inline 修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時(shí)C++編譯器會(huì)在調(diào)用內(nèi)聯(lián)函數(shù)的地方展開(kāi),沒(méi)有函數(shù)調(diào)用建立棧幀的開(kāi)銷(xiāo),內(nèi)聯(lián)函數(shù)提升程序運(yùn)行的效率。
例如以下的兩數(shù)相加的內(nèi)聯(lián)函數(shù):
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int ret = Add(10, 20);
cout << ret << endl;
return 0;
}
以上代碼中,兩數(shù)相加的內(nèi)聯(lián)函數(shù)既沒(méi)有建立函數(shù)棧幀,性能有很好的體現(xiàn),也沒(méi)有因?yàn)檫\(yùn)算符問(wèn)題需要添加很多括號(hào),所以內(nèi)聯(lián)函數(shù)是綜合了宏和函數(shù)的優(yōu)缺點(diǎn)來(lái)設(shè)計(jì)的。
2. 內(nèi)聯(lián)函數(shù)的特性
(1) inline是一種以空間換時(shí)間的做法,如果編譯器將函數(shù)當(dāng)成內(nèi)聯(lián)函數(shù)處理,在編譯階段,會(huì)用函數(shù)體替換函數(shù)調(diào)用,缺陷:可能會(huì)使目標(biāo)文件變大,優(yōu)勢(shì):少了調(diào)用開(kāi)銷(xiāo),提高程序運(yùn)行效率。
(2) inline對(duì)于編譯器而言只是一個(gè)建議,不同編譯器關(guān)于inline實(shí)現(xiàn)機(jī)制可能不同,一般建議:將函數(shù)規(guī)模較小(即函數(shù)不是很長(zhǎng),具體沒(méi)有準(zhǔn)確的說(shuō)法,取決于編譯器內(nèi)部實(shí)現(xiàn))、不是遞歸、且頻繁調(diào)用的函數(shù)采用inline修飾,否則編譯器會(huì)忽略inline特性。
也就是說(shuō),假設(shè)你使用了 inline,編譯器也不一定會(huì)視這個(gè)函數(shù)為內(nèi)聯(lián)函數(shù),因?yàn)槿绻@個(gè)函數(shù)的規(guī)模很大,代碼量大,會(huì)造成代碼膨脹,所以綜合性能方面考慮,我們?nèi)绻褂脙?nèi)聯(lián)函數(shù),盡量要簡(jiǎn)化代碼。
(3) inline 不建議聲明和定義分離,分離會(huì)導(dǎo)致鏈接錯(cuò)誤。因?yàn)?inline 被展開(kāi),就沒(méi)有函數(shù)地址了,鏈接就會(huì)找不到。
例如我定義了一個(gè) Test.h
的頭文件,里面包含 Add 函數(shù)的聲明:
inline int Add(int a, int b);
再定義一個(gè) Test.cpp
文件,里面包含 Add 函數(shù)的實(shí)現(xiàn):
#include "Test.h"
int Add(int a, int b)
{
return a + b;
}
然后在 main.cpp
函數(shù)中調(diào)用 Add 函數(shù):
#include "Test.h"
int main()
{
int ret = Add(10, 20);
cout << ret << endl;
return 0;
}
最后編譯出錯(cuò)了,如下圖:
這是因?yàn)槭裁茨??原因是因?yàn)轭^文件 #include "Test.h"
會(huì)在預(yù)處理階段在 main.cpp 文件中展開(kāi),展開(kāi)之后會(huì)有函數(shù) Add 的聲明,而 Add 函數(shù)前加了內(nèi)聯(lián) inline,編譯器會(huì)認(rèn)為它就是一個(gè)內(nèi)聯(lián)函數(shù),認(rèn)為它就會(huì)直接展開(kāi),所以在編譯階段沒(méi)有給它一個(gè)有效的地址,也就沒(méi)有進(jìn)入符號(hào)表;而在 main 函數(shù)中調(diào)用了 Add 函數(shù),它在符號(hào)表中并沒(méi)有找到自己對(duì)應(yīng)函數(shù)的地址,所以會(huì)出現(xiàn)鏈接錯(cuò)誤。
七、auto關(guān)鍵字
在 C++11 中,auto 的含義是,auto 聲明的變量必須由編譯器在編譯時(shí)期推導(dǎo)而得。也就是說(shuō),auto 是一個(gè)根據(jù)變量自動(dòng)推導(dǎo)類(lèi)型的關(guān)鍵字。
例如:
八、基于范圍的for循環(huán)(C++11)
當(dāng)我們需要遍歷一個(gè)數(shù)組時(shí),通常使用以下方式:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
return 0;
}
對(duì)于一個(gè)有范圍的集合而言,由程序員來(lái)說(shuō)明循環(huán)的范圍是多余的,有時(shí)候還會(huì)容易犯錯(cuò)誤。因此 C++11 中引入了基于范圍的 for 循環(huán)。for 循環(huán)后的括號(hào)由冒號(hào)“ :”分為兩部分:第一部分是范圍內(nèi)用于迭代的變量,第二部分則表示被迭代的范圍。使用范圍 for 我們可以結(jié)合上面所學(xué)的 auto 關(guān)鍵字結(jié)合使用,例如以下代碼:
如果我們需要改變數(shù)組中的值,是否像以下代碼那樣使用呢?
很明顯,答案是不可以的,因?yàn)?e 只是數(shù)組中的數(shù)據(jù)的臨時(shí)拷貝,改變臨時(shí)拷貝的值不影響數(shù)組中原來(lái)的值,所以我們要加上引用:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8 };
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << " ";
}
return 0;
}
加上引用后,e 就是數(shù)組中的數(shù)據(jù)的別名,改變 e 也就是改變數(shù)組中的內(nèi)容。
九、指針空值 nullptr
在早期設(shè)計(jì) NULL 空指針時(shí),NULL 實(shí)際上就是 0,所以導(dǎo)致有些地方使用 NULL 會(huì)造成不明確的函數(shù)調(diào)用,例如:
在以上代碼中,func 構(gòu)成函數(shù)重載,我們期望的 NULL 是調(diào)用 void func(int*)
函數(shù),但是它卻調(diào)用了另外一個(gè),所以這造成了不明確的函數(shù)調(diào)用。
所以在 C++11 中,引入了 nullptr,它的類(lèi)型是無(wú)類(lèi)型指針(void*),這很好地避免了以上的情況,例如下圖,nullptr 是調(diào)用了具有指針類(lèi)型的函數(shù):
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-588029.html
最后,C++ 入門(mén)的全部?jī)?nèi)容已經(jīng)全部分享完啦,感覺(jué)對(duì)自己有幫助的小伙伴趕緊點(diǎn)贊收藏吧~感謝支持!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-588029.html
到了這里,關(guān)于【C++】C++入門(mén)必備知識(shí)詳細(xì)講解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!