C++入門

終於進入了C++的學習。正所謂學好C++,走遍全天下。C++可以說爲我打開了新世界。不過數據結構的知識還是得不斷地複習啊

C++關鍵字

關鍵字在C語言中就已經見過了許多比如最常見的if-else,for,int,double等等。只不過在C++中還會遇到更多的,以後會慢慢學習

命名空間

C++引入命名空間這個概念是因爲在我們編程中可能寫到大量的函數,比如我在某個頭文件中寫了abc()這樣一個函數,但是在我的另一個庫文件中本身就存在abc()這樣一個函數。那麼我一旦調用就會發生衝突

所以使用命名空間的目的是對標識符的名稱進行本地化,以避免命名衝突或名字污染,namespace關鍵字的出現就是針對這個問題的

命名空間的定義
//普通的命名空間
namespace N1{//N1爲命名空間的名稱
    int a;
    //命名空間的內容,既可以定義變量,也可以定義函數
    int add(int b, int c){
        return b + c;
    }
}

//命名空間也可以嵌套
namespace N1{
	int a;
    int add(int b, int c){
        return b + c;
    }
    namespace N2{
        int add(int b, int c){
            return b + c;
        }
    }
}
//同一個工程中允許存在多個相同名稱的命名空間
//編譯器最後會合成同一個命名空間
namespace N1{
    int Mul(int b, int c){
        return b * c;
    }
}

注意:一定命名空間定義了一個新的作用域,命名空間中的所有內容都侷限於該命名空間中

命名空間的使用

1、加命名空間名稱及作用域限定符

int main(){
    printf("%d/n",N1::a);
    return 0;
}

2、使用using將命名空間中成員引入

using N1::a;
int main(){
    printf("%d\n",a);
}

3、使用using namespace命名空間引入

using namespace N1;
int main(){
    printf("%d\n",a);
    add(10,20);
}

C++輸入和輸出

學習一門語言最先開始要進行的打招呼儀式

#include <iostream>
using namespace std;
int main(){
    cout<< "hello,world" <<endl;
    return 0;
}

使用cout標準輸出cin標準輸入時,必須包含<iostream>頭文件以及std標準命名空間

關於.h:早期標準庫將所有功能在全局域中實現,聲明在.h的後綴的頭文件中,使用時只需要包含對應的頭文件即可,後來將其實現在std命名空間下,爲了和C頭文件區分,也爲了正確使用命名空間,規定C++頭文件不帶.h;舊編譯器(VC++6.0)還支持,後續不再支持。現在推薦使用<iostream> + std的方式

使用c++輸入輸出更方便,不需增加數據格式控制

#include <iostream>
using std::cout;
using std::endl;

int main(){
    int a;
    double b;
    char c;
    
    cin>>a;
    cin>>b>>c;
    
    cout<< a <<endl;
    cout<< b <<" "<<endl;
    
    return 0;
}

入門1

缺省參數

缺省參數可以理解爲備用值。

缺省參數概念

缺省參數是聲明或定義函數時爲函數的參數指定一個默認值。在調用該函數時,如果沒有指定實參則採用該默認值,否則使用指定的實參。

#include <iostream>    
    
using std::cout;    
using std::endl;    
    
void Func(int a = 9){    
  cout<<a<<endl;    
}    
    
int main(){    
    
  Func();    
  Func(100);    
  return 0;                                                                                                             
}

入門2

缺省參數的分類

全缺省參數

void testfunc(int a = 10,int b = 20,int c = 30){    
  cout<<"a = "<<a<<endl;    
  cout<<"b = "<<b<<endl;    
  cout<<"c = "<<c<<endl;    
}

半缺省參數

void testfunc2(int a,int b = 20,int c = 30){    
  cout<<"a = "<<a<<endl;    
  cout<<"b = "<<b<<endl;    
  cout<<"c = "<<c<<endl;    
} 

半缺省參數的函數在調用時,必須加上參數a的賦值,否則會報錯。

半缺省參數必須從右往左依次來給,不能間隔着給

缺省參數不能在函數聲明和定義中同時出現,因爲當聲明與定義位置同時出現,恰巧兩個位置提供的值不同,那編譯器就無法確認到底該用哪個缺省值

缺省值必須是常量或者全局變量

函數重載

函數重載的概念

函數重載是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數個數 或 類型 或 順序)必須不同,常用來處理實現功能類似數據類型不同的問題

#include <iostream>    
using std::cout;    
using std::endl;    
int add(int a,int b){    
  return a + b;    
}    
    
double add(double a, double b){    
  return a + b;    
}    
    
long add(long a,long b){    
  return a +b;    
}    
    
int main(){    
  cout<<add(10,20)<<endl;    
  cout<<add(10.0,20.0)<<endl;    
  cout<<add(10L,20L)<<endl;    
  return 0;                      
} 

雖然函數名都是一樣的,但是參數傳參的類型不同。當在調用的時候,編譯器檢查到參數的不同,對應到相應的函數中,這樣就可以實現不同類型的計算了。

//下面兩個函數,雖然參數不同,但是函數類型已經確定了返回值,這樣的話函數不管參數給什麼都沒法實現不同類型的計算,就不存在重載的意義了
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
名字修飾

在C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理,編譯,彙編,鏈接

而名字修飾是一種在編譯過程中,將函數、變量的名稱重新改編的機制,簡單來說就是編譯器爲了區分各個函數,將函數通過某種算法,重新修飾爲一個全局唯一的名稱

C語言的名字修飾比較簡單,就直接在函數名前面加了一個下劃線。比如add修飾爲_add

但C++不同,由於C++要支持重載,命名空間等,使得其修飾規則比較複雜,不同編譯器在底層的實現方式可能都有差異

名字的修飾過程不必深入瞭解,只需知道被重新修飾後的名字中包含了:函數的名字以及參數類型,這就是爲什麼函數重載中幾個同名函數要求其參數列表不同。

Linux下我們通過指令反彙編來查看objdump -d a.out >log.txt

入門3

我們可以看到三個很相似的名字,_Z3addii_Z3adddd,_Z3addll

但是仔細觀察它們的最後兩個字母都不相同,這代表了參數類型不同。分別是int,double,long

但是它們的規制是一樣的"返回類型+函數名+參數列表"

這也可以解釋了爲什麼C語言中不支持函數重載,因爲在函數編譯過程中,C語言並不能將同名不同參數的函數名字修飾,所以它沒法支持函數重載。

extern“C”

有時候在C++工程中可能需要將某些函數按照C的風格來編譯,在函數前加extern “C”,意思是告訴編譯器,將該函數按照C語言規則來編譯。

extern "C" int Add(int left, int right);
int main()
{
 Add(1,2);
 return 0;
}

//可以編譯通過,但是鏈接時出現錯誤

引用

引用的概念

引用不是新定義一個變量,而是給已存在的變量取了一個別名,編譯器不會爲引用變量開闢內存空間,它和引用的變量共用同一塊內存空間

類型& 引用變量名(對象名) = 引用實體

void TestRef()
{
int a = 10;
int& ra = a;//<====定義引用類型
printf("%p\n", &a);
printf("%p\n", &ra);
}

引用類型必須和引用實體是同種類型的

引用特性

1、引用在定義時必須初始化

2、一個變量可以有多個引用

3、引用一旦引用一個實體,再不能引用其他實體

常引用
void TestConstRef()
{
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;//這樣引用類型可以,但是返回值是int類型
}

int const&表示此引用所代表的int類型變量無法使用此引用修改.

const int&表示此引用所代表的是const int類型變量,同樣也無法用此引用修改.

使用場景
int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

當執行這段代碼時,輸出應該是3,但是輸出的結果是7

入門4

這是因爲再函數返回時,離開函數作用域後,其棧上空間已經還給了系統,因此不能用棧上的空間作爲引用類型返回。如果以引用類型返回,返回值的生命週期必須不受函數的限制(即比函數生命週期長)

再來看這段代碼,ret第一次引用之後爲3,但是函數離開了作用域後,棧上的空間被歸還了。但此時又調用了一次這個函數,棧上的空間可能還爲上一次使用的區域,此時ret就爲7了。

引用和指針的區別

在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間

但是在底層實現上實際是有空間的,因爲引用按照指針方式實現的

引用和指針的不同點

1. 引用在定義時必須初始化,指針沒有要求
2. 引用在初始化時引用一個實體後,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實
體
3. 沒有NULL引用,但有NULL指針
4. 在sizeof中含義不同:引用結果爲引用類型的大小,但指針始終是地址空間所佔字節個數(32位平臺下佔4
個字節)
5. 引用自加即引用的實體增加1,指針自加即指針向後偏移一個類型的大小
6. 有多級指針,但是沒有多級引用
7. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
8. 引用比指針使用起來相對更安全

內聯函數

inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數壓棧的開銷,內聯函數提升程序運行的效率。

內聯是一種空間換時間的做法,省去調用函數額開銷。所以代碼很長或者有循環/遞歸的函數不適宜使用作爲內聯函數。

入門5

如果在上述函數前增加inline關鍵字將其改成內聯函數,在編譯期間編譯器會用函數體替換函數的調用。

編譯器在不同的模式下查看有區別:

在release模式下,查看編譯器生成的彙編代碼中是否存在call Add

在debug模式下,需要對編譯器進行設置,否則不會展開

auto關鍵字

C++11中,標準委員會賦予了auto全新的含義:auto不再是一個存儲類型指示符,而是作爲一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導而得

//simple demo
#include<iostream>

using std::cout;
using std::endl;
int main(){
    
    int a = 10;
    auto b = a;
    cout<< "b is "<< b <<endl;
    cout<< a << endl;
}

注意!!

注意!!

注意!!

使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據初始化表達式來推導auto的實際類型。因此auto並非是一種“類型”聲明,而是一個類型聲明時的“佔位符”,編譯器在編譯期間會將auto替換爲變量實際的類型

auto使用規則

1、auto與指針和引用結合起來使用

用auto聲明指針類型時,用auto和auto*沒有任何區別,但用auto聲明引用類型時,則必須加&

int main(){
	int a = 10;
	auto b = a;
	auto* c = &a;
	auto& d = a;

	cout << "b is " << b << endl;
	cout << a << endl;
	cout << *c << endl;
	cout << d << endl;
	return 0;
}

2、在同一行定義多個變量

當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因爲編譯器實際只對第一個類型進行推導,然後用推導出來的類型其他變量。

int main(){
    auto a = 1,b = 2;
    auto c = 3,d = 4.0;//編譯會失敗,因爲c和d的初始化表達式類型不同
}

auto不能推導的場景

1、auto不能作爲函數的參數

void TestAuto(auto a){
    pass;
}

上面代碼是不能編譯通過的,auto不能作爲形參類型,因爲編譯器無法對a的實際類型進行推導

2、auto不能直接用來聲明數組

void TestAuto(){
    int a[] = {1,2,3};
    auto b[] = {4,5,6};
}

3、auto不能定義類的非靜態成員變量

4、實例化模板時不能使用auto作爲模板參數

基於範圍的for循環(C++11)

範圍for的語法

在C++98如果要遍歷一個數組,可以按照以下方式進行:

void TestFor(){
    int array[] = {1,2,3,4,5};
    for(int i = 0;i < sizeof(array)/sizeof(array[0]);++p){
        array[i] *= 2;
    }
    for(int* p = array;p < array + sizeof(array)/sizeof(array[0]);++p)
        cout<< *p <<endl;
}

C++是一個追求極致性能的語言,在有範圍的數組中,由程序員來說明循環的範圍是多餘的,有時還會容易犯錯誤。for循環後的括號由冒號":"分爲兩部分,第一部分是範圍內用於迭代的變量,第二部分則表示被迭代的範圍

void TestFor(){
    int array[] = {1,2,3,4,5};
    for(auto& e : array)
        e *= 2;
    for(auto e : array)
        cout<< e << " ";
}
範圍for的使用條件

1、for循環迭代的範圍必須是確定的

對於數組而言,就是數組中第一個元素和最後一個元素的範圍;對於類而言,應該提供begin和end的方法,begin和end就是for循環迭代的範圍

比如如下代碼,數組作爲形參傳入時,這時候的循環條件是不確定的。

void TestFor(int array[]){
    for(auto& e : array)
        cout<< e <<endl;
}

2、迭代的對象要實現++和==的操作

指針空值nullptr

在C語言中,我們一般定義一個空指針是int *p = NULL或者int *p = 0

兩者的意思基本上是相似的。不過NULL實際上是一個宏

#define NULL 0
#define NULL ((void*) 0)

在頭文件的定義中,一般是這樣的。可以看到,NULL可能被定義爲字面常量0,或者被定義爲無類型指針(void*)0)

比如在函數中想調用int*的函數,但是NULL被定義成0,因此與程序的初衷相悖

void f(int) 
{ 
 cout<<"f(int)"<<endl; 
} 
void f(int*) 
{ 
 cout<<"f(int*)"<<endl; 
} 
int main() 
{ 
 f(0); 
 f(NULL); 
 f((int*)NULL); 
 return 0; 
}
nullptr和nullptr_t

爲了考慮兼容性,C++11並沒有消除常量0的二義性,C++11給出了全新的nullptr表示空值指針。C++11爲什麼

不在NULL的基礎上進行擴展,這是因爲NULL以前就是一個宏,而且不同的編譯器廠商對於NULL的實現可能

不太相同,而且直接擴展NULL,可能會影響以前舊的程序。因此:爲了避免混淆,C++11提供了nullptr,

即:nullptr代表一個指針空值常量。nullptr是有類型的,其類型爲nullptr_t,僅僅可以被隱式轉化爲指針類

型,nullptr_t被定義在頭文件中:

typedef decltype(nullptr) nullptr_t;

注意!!!

注意!!!

注意!!!

1.在使用nullptr表示指針空值時,不需要包含頭文件,因爲nullptr是C++11作爲新關鍵字引入的。

2.在C++11中,sizeof(nullptr) 與 sizeof((void)0)所佔的字節數相同。*

3.爲了提高代碼的健壯性,在後續表示指針空值時建議最好使用nullptr

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章