C++入門基礎(萬字總結)(建議收藏!)
文章目錄
什麼是C++
C++的發展史
C++關鍵字
名稱空間
名稱空間的定義
1。名稱空間的普通定義
2。名稱空間可以巢狀
3。 同一個工程中允許存在多個相同名稱的名稱空間,編譯器最後會合成同一個名稱空間中。
名稱空間使用
1。加名稱空間名稱及作用域限定符
2。使用using namespace 名稱空間名稱引入
3。使用using將名稱空間中的成員引入
C++中的輸入和輸出
預設引數
全預設
半預設引數
函式過載
函式過載的原理
extern “C”
引用
引用的特徵
1。引用在定義時必須初始化
2。一個變數可以有多個引用
3。引用一旦引用了一個實體,就不能再引用其他實體
常引用
引用的使用場景
1。引用做引數
2。引用做返回值
引用和指標的區別
行內函數
特性
c++有哪些技術可以代替宏
auto關鍵字(C++11)
auto的使用細則
1。auto與指標和引用結合起來使用
2。在同一行定義多個變數
auto不能推導的場景
1。auto做為函式的引數
2。auto不能直接用來宣告陣列
基於範圍的for迴圈(C++11)
範圍for的語法
範圍for的使用條件
1。for迴圈迭代的範圍必須是確定的
2。 迭代的物件要實現++和==的操作。
指標空值nullptr
C++98中的指標空值
C++11中的指標空值
什麼是C++
C語言是結構化和模組化的語言,適合處理較小規模的程式。對於複雜的問題,規模較大的程式,需要高度
的抽象和建模時,C語言則不合適。為了解決軟體危機,20世紀80年代,計算機界提出了OOP(object
orientedprogramming:面向物件)思想,支援面向物件的程式設計語言應運而生。
1982年,BjarneStroustrup博士在C語言的基礎上引入並擴充了面向物件的概念,發明了一種新的程式語
言。為了表達該語言與C語言的淵源關係,命名為C++。因此:C++是基於C語言而產生的,它既可以進行C語
言的過程化程式設計,又可以進行以抽象資料型別為特點的基於物件的程式設計,還可以進行面向物件的程
序設計。
C++的發展史
1979年,貝爾實驗室的本賈尼等人試圖分析unix核心的時候,試圖將核心模組化於是在C語言的基礎上進行擴充套件,增加了類的機制,完成了一個可以執行的預處理程式,稱之為Cwith classes。
語言的發展也是隨著時代的進步,在逐步遞進的,讓我們來看看C++的歷史版本:
C++關鍵字
C++中總計有63個關鍵字:
其中畫圈的是C語言的關鍵字。這裡要注意了:false和true並不是C語言的關鍵字。
名稱空間
在C/C++中,變數、函式和類都是大量存在的,這些變數、函式和類的名稱都將作用於全域性作用域中,可能會導致很多命名衝突。
使用名稱空間的目的就是對識別符號和名稱進行本地化,以避免命名衝突或名字汙染,namespace關鍵字的出現就是針對這種問題的。
名稱空間的定義
定義名稱空間,需要使用到namespace關鍵字,後面跟名稱空間的名字,然後接一對{}即可,{}中即為命名
空間的成員。
注意:一個名稱空間就定義了一個新的作用域,名稱空間中的所有內容都侷限於該名稱空間中
1.名稱空間的普通定義
//1。普通的名稱空間,裡面可以定義變數,也可以定義函式
namespacexjt
{
int printf = 1;
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
}
2.名稱空間可以巢狀
//2。名稱空間可以巢狀
namespacexjt
{
int printf = 1;
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
namespace xjt2
{
int a = 0;
int Sub(int a, int b)
{
return a - b;
}
}
}
3. 同一個工程中允許存在多個相同名稱的名稱空間,編譯器最後會合成同一個名稱空間中。
//3。同一個工程中允許存在多個相同名稱的名稱空間,編譯器最後會合成同一個名稱空間中。
namespacexjt
{
int a = 3;
int b = 1;
}
它會與上面的xjt名稱空間合併
名稱空間使用
下面來看這麼一段程式碼
namespacexjt
{
int printf = 1;
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
}
#include
很顯然直接列印printf是不可能的,因為你這樣呼叫的是printf的地址,所以會出現的這樣的結果,正面的呼叫方法為以下三種。
1.加名稱空間名稱及作用域限定符
符號“::”在C++中叫做作用域限定符,我們透過“名稱空間名稱::名稱空間成員”便可以訪問到名稱空間中相應的成員
2.使用using namespace 名稱空間名稱引入
但是這種方式存在著一些弊端,如果我們在名稱空間中定義了一個名字為printf的變數,那麼之後再將namespacexjt這個名稱空間引入的話,就會造成命名的汙染了。
為了解決這個問題,出現了第三種引入方法。
3.使用using將名稱空間中的成員引入
這種方法可以防止命名的汙染,因為它只引入了一部分。
C++中的輸入和輸出
新生嬰兒會以自己獨特的方式向這個嶄新的世界打招呼,C++剛出來後,也算是一個新事物,那C++是否也應該向這個美好的世界來聲問候呢?我們來看下C++是如何來實現問候的。
#include
在C語言中有標準輸入輸出函式scanf和printf,而在C++中有
cin
標準輸入
和
cout
標準輸出
。在C語言中使用scanf和printf函式,需要包含標頭檔案stdio。h。在C++中使用cin和cout,需要包含
標頭檔案
iostream
以及
std
標準名稱空間
。
C++的輸入輸出方式與C語言更加方便,因為C++的輸入輸出不需要控制格式,例如:整型為%d,字元型為%c。
#include
注意:endl,這其中的l不是阿拉伯數字1,而是26個英文字母的l,它的作用相當於換行。
這裡我們還要注意下cin的特點,他和C語言中的gets有些像,gets是遇到換行符停止,而cin是以遇到空格,tab或者換行符作為分隔符的,因此這兒輸入helloworld會被空格符分隔開來。
這兒我輸入的是helloworld,但因為輸入時出現了空格,所以之後的內容並不會讀入,因此arr中存的就是hello。
預設引數
預設引數是宣告或定義函式時為函式的引數指定一個預設值。在呼叫該函式時,如果沒有指定實參則採用該
預設值,否則使用指定的實參。
//預設引數
#include
全預設
全預設引數,即函式的全部形參都設定為預設引數。
//全預設
#include
半預設引數
voidfunc(int a, int b, int c = 2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
注意:
1、半預設引數必須從右往左依次給出,不能間隔著給。
//錯誤示例
voidfunc(int a, int b = 2, int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
2、預設引數不能在函式宣告和定義中同時出現
//錯誤示例
//test。h
voidfunc(int a, int b, int c = 3);
//test。c
voidfunc(int a, int b, int c = 2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
因為:如果宣告與定義位置同時出現,恰巧兩個位置提供的值不同,那編譯器就無法確定到底該用那
個預設值。
3、預設值必須是常量或者全域性變數。
//正確示例
intx = 3;//全域性變數
voidfunc(int a, int b = 2, int c = x)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
函式過載
函式過載:是函式的一種特殊情況,C++允許在
同一作用域
中宣告幾個功能類似的
同名函式
,這些同名函式的
形參列表
(
引數個數或 型別 或 順序
)
必須不同
,常用來處理實現功能類似資料型別不同的問題
#include
注意:若僅僅只有返回值不同,其他都相同,則不構成函式過載。
shortAdd(short left, short right)
{
return left+right;
}
intAdd(short left, short right)
{
return left+right;
}
函式過載的原理
為什麼C++支援函式過載,而C語言不可以了?
這裡我們就要回顧一下以前的知識了,在執行到執行檔案前,要經過:
預編譯,編譯,彙編,連結
這些階段
其實問題就出在編譯完之後的彙編階段,因為在這裡C++和C語言有著些許的不同,下面我們來看看:
採用C語言編譯器編譯之後
採用C++編譯器編譯之後
總結:
1。其實歸根到底,還是因為C編譯器和C++編譯器對函式名的修飾不同。在gcc下的修飾規則是:【_Z+函式長度+函式名+類
型首字母】。
2。這其實也告訴我們為什麼函式的返回型別不同,不會構成函式過載,因為修飾規則並不會受返回值的影響。
extern “C”
有時候在C++工程中可能需要將某些函式按照C的風格來編譯,在函式前加extern“C”,意思是告訴編譯器,
將該函式按照C語言規則來編譯。比如:tcmalloc是google用C++實現的一個專案,他提供tcmallc()和tcfree
兩個介面來使用,但如果是C專案就沒辦法使用,那麼他就使用extern“C”來解決。
引用
引用不是新定義一個變數,
而是給已存在變數取了一個別名
,編譯器不會為引用變數開闢記憶體空間,它和它
引用的變數
共用同一塊記憶體空間
。
型別&引用變數名(物件名)= 引用實體;
#include
注意:
引用型別必須和引用實體是同種型別的
引用的特徵
1.引用在定義時必須初始化
//正確示例
inta = 10;
int&b = a;//引用在定義時必須初始化
//錯誤示例
inta = 10;
int&b;//定義時未初始化
b= a;
2.一個變數可以有多個引用
inta = 10;
int&b = a;
int&c = a;
int&d = a;
3.引用一旦引用了一個實體,就不能再引用其他實體
inta = 10;
int& b = a;
int c = 20;
b = c;//你的想法:讓b轉而引用c
但實際的效果,確實將c的值賦值給b,又因為b是a的引用,所以a的值見解變成了20。
常引用
上面提到,引用型別必須和引用實體是同種型別的。但是僅僅是同種型別,還不能保證能夠引用成功,這兒我們還要注意可否可以修改的問題。
voidTestConstRef()
{
const int a = 10;
//int& ra = a; // 該語句編譯時會出錯,a為常量
const int& ra = a;
// int& b = 10; // 該語句編譯時會出錯,b為常量
const int& b = 10;
double d = 12。34;
//int& rd = d; // 該語句編譯時會出錯,型別不同
const int& rd = d;
}
這裡的a,b,d都是常量,常量是不可以被修改的,但是如果你用int&ra等這樣來引用a的話,那麼引用的這個a是可以被修改的,因此會出問題。
下面我們來看這麼一段程式碼:
#include
這個引用對嗎?想要弄明白這個問題,首先要明白隱士型別提升的問題,在這裡int到double存在隱士型別的提升,而在提升的過程中系統會建立一個常量區來存放a型別提升後的結果。因此到這兒,這段程式碼一看就是錯了,因為你隱士型別提升時a是存放在常量區中的,常量區是不可以被修改的,而你用double&ra去引用他,ra這個引用是可以被修改的。
加個const就可以解決這個問題。
#include
注意:將不可修改的量用可讀可寫的量來引用是不可以的,但是反過來是可以的,將可讀可寫的量用只可讀的量來引用是可以的。
引用的使用場景
1.引用做引數
還記得C語言中的交換函式,學習C語言的時候經常用交換函式來說明傳值和傳址的區別。現在我們學習了引用,可以不用指標作為形參了。因為在這裡a和b是傳入實參的引用,我們將a和b的值交換,就相當於將傳入的兩個實參交換了。
//交換函式
voidSwap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
2.引用做返回值
當然引用也能做返回值,但是要特別注意,我們返回的資料不能是函式內部建立的普通區域性變數,因為在函式內部定義的普通的區域性變數會隨著函式呼叫的結束而被銷燬。我們返回的資料必須是被static修飾或者是動態開闢的或者是全域性變數等不會隨著函式呼叫的結束而被銷燬的資料。
不加static的後果
你是不是疑惑為什麼列印的不是2而是7了?
這人就更奇怪了,為什麼中間加了一句printf,就列印隨機值了?
下面我們來看看分析:
為什麼會出現隨機值,因為你在函數里定義的變數是臨時變數,出了函式函式是會銷燬的,這時它就隨機指向記憶體中的一塊空間了
。所以在引用做函式返回值時最好還是給在函式中定義的變數加上static。
這時你覺得你真的懂這段程式碼了嗎?
#include
可能你會好奇了?為什麼這兒是3了?下面來看看分析
其實你換種寫法,這兒的結果就會換成7,原因也很簡單,正是上面圖片中說的原因
注意:如果函式返回時,出了函式作用域,返回物件還未還給系統,則可以使用引用返回;如果已經還給系統了,則必須使用傳值返回。
這句話說的是下面這種例子:
int&Add(int a, int b)
{
int c=a+b; //出了函式作用域,c不在,回給了系統
return c;
}
int&Add(int a,int b)
{
static c=a+b; //出了函式作用域,c還在,可以用引用返回
return c;
}
大家是不是感覺這個傳引用返回用起來很怪了,下面我們來分析一下它是如何返回的。
總結:
傳值的過程中會產生一個複製,而傳引用的過程中不會,其實在做函式引數時也具有這個特點。
引用和指標的區別
在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間。
intmain()
{
int a = 10;
int& ra = a;
cout<<“&a = ”<<&a< cout<<“&ra = ”<<&ra< return 0; } 在底層實現上實際是有空間的,因為引用是按照指標方式來實現的。 intmain() { int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0; } 我們來看下引用和指標的彙編程式碼對比 引用和指標的區別 1、引用在定義時必須初始化,指標沒有要求。 2、引用在初始化時引用一個實體後,就不能再引用其他實體,而指標可以在任何時候指向任何一個同類型實體。 3、沒有NULL引用,但有NULL指標。 4、在sizeof中的含義不同:引用的結果為引用型別的大小,但指標始終是地址空間所佔位元組個數(32位平臺下佔4個位元組)。 5、引用進行自增操作就相當於實體增加1,而指標進行自增操作是指標向後偏移一個型別的大小。 6、有多級指標,但是沒有多級引用。 7、訪問實體的方式不同,指標需要顯示解引用,而引用是編譯器自己處理。 8、引用比指標使用起來相對更安全。 行內函數 概念:以inline修飾的函式叫做行內函數,編譯時C++編譯器會在 呼叫行內函數的地方展開 ,沒有函式壓棧的開銷, 行內函數提升程式執行的效率。(看到在加粗部分時,小夥伴肯定會想,這和c語言中的宏是不是很像了?) 如果在上述函式前增加inline關鍵字將其改成行內函數,在編譯期間編譯器會用函式體替換函式的呼叫 特性 inline是一種以空間換時間的做法,省去呼叫函式額開銷。所以程式碼很長/遞迴的函式不適宜 使用作為行內函數。 inline對於編譯器而言只是一個建議,編譯器會自動最佳化,如果定義為inline的函式體內程式碼比較長/遞迴等 等,編譯器最佳化時會忽略掉內聯。 inline不建議宣告和定義分離,分離會導致連結錯誤。因為inline被展開,就沒有函式地址了,連結就會 找不到。 //F。h #include c++有哪些技術可以代替宏 C++有哪些技術替代宏? 常量定義 換用const 函式定義 換用行內函數 auto關鍵字(C++11) 在早期的C/C++中auto的含義是:使用auto修飾的變數是具有自動儲存器的區域性變數,但遺憾的是一直沒有人去使用它。 在C++11中,標準委員會賦予了auto全新的含義:auto不再是一個儲存型別指示符,而是作為一個新的型別指示符來指示編譯器,auto宣告的變數必須由編譯器在編譯時期推導而得。可能光看這一句話,你不一定能懂,下面我們舉幾個例子。 #include 注意:使用auto定義變數時必須對其進行初始化,在編譯階段編譯器需要根據初始化表示式來推導auto的實際類 型。因此auto並非是一種“型別”的宣告,而是一個型別宣告時的“佔位符”,編譯器在編譯期會將auto替換為 變數實際的型別。 auto的使用細則 1.auto與指標和引用結合起來使用 用auto宣告指標型別時,用auto和auto*沒有任何區別,但用auto宣告引用型別時則必須加& #include 注意:用auto宣告引用時必須加&,否則建立的只是與實體型別相同的普通變數,只不過將其換了個姓名而已。 2.在同一行定義多個變數 當在同一行宣告多個變數時,這些變數必須是相同的型別,否則編譯器將會報錯,因為編譯器實際只對 第一個型別進行推導,然後用推匯出來的型別定義其他變數。 voidTestAuto() { auto a = 1, b = 2; auto c = 3, d = 4。0; // 該行程式碼會編譯失敗,因為c和d的初始化表示式型別不同 } auto不能推導的場景 1.auto做為函式的引數 //此處程式碼編譯失敗,auto不能作為形參型別,因為編譯器無法對a的實際型別進行推導 voidTestAuto(auto a) {} 2.auto不能直接用來宣告陣列 voidTestAuto() { int a[] = {1,2,3}; auto b[] = {4,5,6}; } 為了避免與C++98中的auto發生混淆,C++11只保留了auto作為型別指示符的用法 auto在實際中最常見的優勢用法就是跟以後會講到的C++11提供的新式for迴圈,還有lambda表示式等 進行配合使用。 基於範圍的for迴圈(C++11) 範圍for的語法 在C++98中如果要遍歷一個數組,可以按照以下方式進行: voidTestFor() { int array[] = { 1, 2, 3, 4, 5 }; //將陣列所有元素乘以2 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; } 對於一個 有範圍的集合 而言,由程式設計師來說明迴圈的範圍是多餘的,有時候還會容易犯錯誤。因此C++11中 引入了基於範圍的for迴圈。 for 迴圈後的括號由冒號“:”分為兩部分:第一部分是範圍內用於迭代的變數, 第二部分則表示被迭代的範圍。 注意不能寫成auto,不然改變不了原陣列 正確的寫法 voidTestFor() { int array[] = { 1, 2, 3, 4, 5 }; //將陣列中所有元素乘以2 for(auto& e : array) e *= 2; for(auto e : array) cout << e << “ ”; return 0; } 注意:與普通迴圈類似,可用continue來結束本次迴圈,也可以用break來跳出整個迴圈。 範圍for的使用條件 1.for迴圈迭代的範圍必須是確定的 對於陣列而言,就是陣列中第一個元素和最後一個元素的範圍;對於類而言,應該提供begin和end的 方法,begin和end就是for迴圈迭代的範圍。 注意:以下程式碼就有問題,因為for的範圍不確定 voidTestFor(int array[]) { for(auto& e : array) //這裡的array其實不是陣列,陣列在傳參時會退化成指標 cout<< e < 2. 迭代的物件要實現++和==的操作。 關於迭代器這個問題,以後會講,現在大家瞭解一下就可以了。 指標空值nullptr C++98中的指標空值 在良好的C/C++程式設計習慣中,在宣告一個變數的同時最好給該變數一個合適的初始值,否則可能會出現不可預料的錯誤。比如未初始化的指標,如果一個指標沒有合法的指向,我們基本都是按如下方式對其進行初始化: int*p1 = NULL; int*p2 = 0; NULL其實是一個宏,在傳統的C標頭檔案(stddef。h)中可以看到如下程式碼: #ifndefNULL #ifdef__cplusplus #defineNULL 0 #else #defineNULL ((void *)0) #endif #endif 可以看到,NULL可能被定義為字面常量0,或者被定義為無型別指標(void*)的常量。不論採取何種定義,在 使用空值的指標時,都不可避免的會遇到一些麻煩,比如: #include 程式本意本意是想透過Fun(NULL)呼叫指標版本的Fun(int*p)函式,但是由於NULL被定義為0,Fun(NULL)最終呼叫的是Fun(intp)函式。 注:在C++98中字面常量0,既可以是一個整型數字,也可以是無型別的指標(void*)常量,但編譯器預設情況下將其看成是一個整型常量,如果要將其按照指標方式來使用,必須對其進行強制轉換。 C++11中的指標空值 對於C++98中的問題,C++11引入了關鍵字nullptr。 在使用nullptr表示指標空值時,不需要包含標頭檔案,因為nullptr是C++11作為關鍵字引入的。 在C++11中,sizeof(nullptr)與sizeof((void*)0)所佔的位元組數相同,大小都為4。