目錄
1. 命名空間
1.1 命名空間的創建和使用
2. 輸入輸出
2.1 輸出
2.2 輸入
3. 缺省參數
3.1 全缺省
3.2 半缺省
4.函數重載
4.1 為什么C++支持重載而C語言不支持?
4.1.2 編譯的四個過程
4.2 extern是什么
5.引用
5.1 引用的特性
5.1.1 引用的“隱式類型轉換”
5.2 引用的使用場景
本欄目針對于C語言已經學習完畢的讀者,只會講C++特有的特性。
1. 命名空間
? ? ? ? 命名空間是C++為了防止全局變量、函數、類,因為作用域的不同導致的沖突,從而提出的解決方案,在一個命名空間內,變量、函數、類是相對獨立的,不會對其他命名空間造成影響。
1.1 命名空間的創建和使用
命名空間內部可以創建變量和函數以及類;
命名空間可以嵌套使用;
相同的命名空間后面會被編譯器合并;
#include<iostream>// 命名空間可以定義變量和函數
namespace N1
{int a = 0;void swap(){;}}// 命名空間的嵌套
namespace N2
{namespace N3 {// ....}
}// 相同命名空間,編譯器會進行合并
namespace N2
{int b = 1;
}
如果要使用命名空間內的變量或者函數或者類,那么一共有三種方式進行調用。
①直接使用命名空間::變量來調用;
②將某一個變量直接進行展開,后續使用變量的時候就不需要再加上命名空間了;
③將一個命名空間完全展開,命名空間內的所有內容,直接可以使用。
// 命名空間可以定義變量和函數
namespace N1
{int a = 0;void swap(){;}}// 命名空間的嵌套
namespace N2
{namespace N3{// ....int c = 1;}
}// 相同命名空間,編譯器會進行合并
namespace N2
{int b = 1;
}int main()
{// 方式1printf("%d ", N1::a);// 方式2using N2::b; // 如果經常使用a,那么就直接展開aprintf("%d ", b);// 方式3// 全部展開N2內的所有內容using namespace N1;printf("%d ",a);swap();return 0;
}
? ? ? ? 想要使用C++,就必須使用std庫,我們可以直接使用:將標準庫全部展開,到那時還是有一個缺陷,就是后續項目中如果定義一個和std庫內容相同的函數或者變量,那么就會出現重復命名的問題。
using namespace std;
2. 輸入輸出
2.1 輸出
? ? ? ? 利用上面的命名空間的知識,我們可以順便介紹一下c++的輸入輸出。首先需要包含c++的輸入輸出庫,后續才能使用相關的函數。
#include<iostream>
? ? ? ? 輸出流:cout,這是在標準庫中的一個函數,也可以叫做標準輸出流,利用上面所學的知識,我們可以有三種方式來完成hello world的輸出。
方式一:
? ? ? ? 在平時練習中,我們可以直接將標準庫展開,然后直接使用cout,將想要輸出的內容直接輸出到輸出流中,這里的endl,是換行的意思,我們可以使用c語言的\n來替代這個功能。
#include<iostream>
// 直接將std全部展開
using namespace std;
int main()
{cout << " hello world " << endl;cout << " hello world \n ";
}
????????這樣做就會產生一個缺陷,如果你定義的變量中含有和std庫中相同的變量名稱,這樣就會命名混淆從而導致報錯。
方式二:
? ? ? ? 在平時的項目中,為了避免命名沖突,我們直接使用std::cout的方式進行輸出,雖然一定程度避免了命名沖突,但是書寫起來比較麻煩。
#include<iostream>
int main()
{std::cout << " hello world " << std::endl;std::cout << " hello world \n ";
}
方式三:
? ? ? ? 居中的辦法就是,只展開后續會用到的函數:
using std::cout;
using std::endl;
#include<iostream>
int main()
{cout << " hello world " << endl;cout << " hello world \n ";
}
注意:細心的讀者可能發現了,c++的輸出和c語言的輸出不太一樣,c++是直接根據輸出內容的類型直接判斷輸出的類型,而不是像c語言一樣,需要提前指定類型,再根據指定的類型輸出,這就是后面面向對象需要學習的函數的重載。
2.2 輸入
? ? ? ? 這里用到了std庫的cin函數,這里和cout一樣,都是可以流式輸入,注意>>箭頭的方向
using std::cout;
using std::endl;
using std::cin;
#include<iostream>
int main()
{int a = 0;float b = 0.0;cin >> a >> b;cout << a << " " << b << endl;
}
3. 缺省參數
? ? ? ? 正常情況下,在函數有形參的時候需要這樣調用:
#include<iostream>
using namespace std;void Func(int a)
{// cout << a << endl;
}int main()
{Func(10);
}
????????在c++中需要注意的是,在函數形參中提供默認參數,當沒有傳入實參的時候,就會使用默認參數。
#include<iostream>
using namespace std;void Func(int a = 0)
{// cout << a << endl;
}int main()
{Func(10);Func();
}
3.1 全缺省
? ? ? ? 顧名思義,全部形參都可以不寫,叫全缺省,也可以按照順序缺省后2個或者3個形參。
// 全缺省
void Func1(int a = 0, int b = 1, int c = 2)
{// cout << a << endl;cout << b << endl;cout << c << endl;
}int main()
{Func1();// 全缺省Func1(10); // 缺省b,c形參Func1(10,20);// 缺省c形參Func1(10,20,30);// 不缺省
}
3.2 半缺省
形參中部分參數沒有指定缺省值,例如下面代碼,a沒有指定缺省值,那么傳入實參的時候必須至少有一個參數是傳給a的,如果后面有其他參數,則繼續傳給b,c;
// 半缺省
void Func1(int a, int b = 1, int c = 2)
{// cout << a << endl;cout << b << endl;cout << c << endl;
}int main()
{Func1(10); // 缺省b,c形參,必須得傳入一個Func1(10,20);// 缺省c形參Func1(10,20,30);// 不缺省
}
這種半缺省必須是從右往左進行缺省:
間隔缺省是錯誤的。
從左往右缺省是錯誤的。
傳參是從左往右依次傳參。
4.函數重載
? ? ? c++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數個數或類型或順序)必須不同,常用來處理功能類似,數據類型不同的問題。
int func()
{
}// 參數個數不同
int func(int a)
{
}// 參數類型不同
int func(long a)
{
}// 參數順序不同
int func(long a ,int b)
{
}
int func(int b, long a)
{
}
對返回值完全沒有要求。
如何調用重載函數:
4.1 為什么C++支持重載而C語言不支持?
? ? ? ? 下面使用linux的環境來進行解釋
list.h
list.c
test.c
使用gcc命令編譯:
gcc -o listc list.c test.c
發現報錯
究其原因就是C語言的編譯器是不支持重載的:
我們使用g++命令進行編譯,發現通過編譯了;
g++ -o listcpp list.c test.c
這就涉及到編譯器編譯的四個過程:
4.1.2 編譯的四個過程
①預處理:宏替換,去掉注釋,展開頭文件,條件編譯;
②編譯:檢查語法問題,將源代碼轉換成匯編代碼;
③匯編:將匯編代碼轉換成二進制機器碼;
④鏈接:將兩個目標文件鏈接在一起,生成可執行文件。
例如在test中執行一個Func1函數,轉換成匯編指令就是:
call Func1 (0EE11DBH),括號內的是這個函數的地址,也可以說這個函數第一個變量的地址。
????????回到上面的函數,同理我們只觀察匯編階段的匯編碼;在test.o文件中我們需要調用list_push_back這個函數,那么其實就是call list_push_back(函數地址),但是問題來了,此時在test函數中僅僅只包含了頭文件,也就是說僅僅對這個函數進行了聲明,那么這個函數實際的地址,這里是不知道的;每一個object文件中都在維護一個符號表,記錄每一個函數以及對應的地址,test.o這個文件的符號表僅僅只有main函數的地址。
? ? ? ? 反之在list.o文件中,我們可以看到這里是完成了兩個函數的實現,所以符號表里就存放著兩條數據,分別是這兩個函數的地址。
? ? ? ? 上面括號內的問號表示,在編譯的時候,這個函數只有聲明沒有定義。在鏈接的時候會其他目標文件的符號表中找到該函數的地址。
? ? ? ? 言歸正傳,在C語言中,函數名是必須是獨一無二的,函數的名字沒有修飾,也就是說,當去list.o的符號表去找地址的時候,突然發現ADD的地址有兩個,那么此時就不知道到底是哪一個的地址。
? ? ? ? 在C++中函數名是有修飾的,函數名由下面幾部分組成:
①前綴:_Z
②函數字符個數:例如add就是3
③函數名稱
④形參類型的首字母:add形參首字母就是ii、func形參首字母是idpi,p代表指針,pi代表int類型的指針。
????????回到上面的代碼,我們直接將生成的匯編代碼進行展示:下圖是cpp的匯編代碼,雖然函數名都是add,但是仍然可以根據命名修飾進行區分,從而找到相對應的函數地址。
? ? ? ? 那么c語言就不同了,我們可以發現下圖是c語言的匯編代碼,我們可以發現add完全沒有任何函數名修飾,這就意味著,如果有兩個相同的函數名,當在鏈接的時候,編譯器無法區分這兩個函數的地址,因為函數名都相同,那么在符號表中就無法進行區分。
4.2 extern是什么
? ? ? ? extern C就是按照C語言進行編譯,假如有一個C++編寫的程序編譯成了庫文件供別人調用,其他C++程序是可以完全調用的,但是如果C語言程序就無法調用,這是因為我們編譯的規則是不同的,如果這個程序是C++寫的,那么要求C語言能夠正確編譯,需要在函數聲明加上extern "C",如下圖綠框所示:
? ? ? ? 這里的具體意義是,按照C的形式去符號表內找這個函數的地址。
5.引用
? ? ? ? 引用就是給變量取了一個別名,不會額外開辟內存空間。
#include<iostream>
using namespace std;
int main()
{int a = 0;int& ra = a; // ra是a的引用,給a起了一個別名
}
5.1 引用的特性
①一個變量可以有多個引用,但是只會占用一個空間;
②引用聲明的時候必須初始化;
③第一次給引用初始化的時候,就永遠是這個變量的引用,后續無法改變。
#include<iostream>
int main()
{int a = 1;int& ra = a;int b = 2;ra = b; // 此時ra仍然是a的引用,a的值被改成了2}
此時ra的類型是int,而不是int&,因為ra只是a的別名,類型和a一樣。
如果代碼改成下面這樣的,就會出現問題:
#include<iostream>
int main()
{const int a = 1;int& ra = a; // 此時報錯}
const int a只是可讀的,int& ra是可讀可寫的,如果將前者直接轉換成后者,就會報錯。
那么反過來就對了,a可讀可寫,ra只可讀,類似于一個大海能填滿溪流,但是溪流不能填滿大海。
#include<iostream>
int main()
{int a = 1;const int& ra = a; // 此時ok}
5.1.1 引用的“隱式類型轉換”
? ? ? ? 首先看下面的代碼,將a賦值給double類型的引用,是不可以的,在前面加了一個const修飾就可以了,這是為什么呢?
#include<iostream>
int main()
{int a = 1;double& b= a; // 此時不行const double& c= a; // 此時ok}
? ? ? ? 當變量賦值的時候會產生一個臨時變量,這個變量具有常性(只能讀不能改),那么事實上a賦值給這個臨時變量,臨時變量再賦值給double類型的引用,此時只讀賦值給可讀可寫,一定會有問題。
? ? ? ? 反之在double& 前加一個const修飾,這里的引用就會變成可讀的,那么可讀賦值給可讀就是合理的。
5.2 引用的使用場景
我們寫一個比較常見的交換函數,分別使用c語言和c++的特性,注意形參和實參的變化。
int& a其實就是取實參的引用,此時引用和實參指向一個地址,因此可以直接交換。
#include<iostream>
using namespace std;void c_swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}
void cpp_swap(int& r1, int& r2)
{int tmp = a;a = b;b = tmp;
}int main()
{int a = 10;int b = 20;c_swap(&a, &b);cpp_swap(a, b);
}
????????再舉一個例子,count1返回的是一個臨時變量,是只讀的,所以給r1的時候會報錯,此時r1需要加上const類型才能對應,(只讀-只讀),count2直接返回引用,所以類型能夠匹配,所以這是沒問題的。