從 C 到 C++ 的一些不同

之前大致對 C 語言的內容進行了學習,這裏試着瞭解一下 C++。

如果我們用過 C++ 編過程序,會發現如果將只屬於 C++ 的部分刨掉之後,剩下的東西好像都差不多。也就是說,C++ 是能夠兼容 C 語言的,但是 C++ 相較於 C 語言也有些不同。

類型增強

類型檢查

// C_language
#include <stdio.h>

int main()
{
    const int a = 1;
    int b = a;
    const int c = b;

    const int *p = &a;
    const int *pp = &b;
    int *ppp = &a;
    int *pppp = &b;

    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);
    printf("*p = %d\n",*p);
    printf("*pp = %d\n",*pp);
    printf("*ppp = %d\n",*ppp);
    printf("*pppp = %d\n",*pppp);

    return 0;
}
// C++_language
#include <iostream>

using namespace std;

int main()
{
    const int a = 1;
    int b = a;
    const int c = b;

    const int *p = &a;
    const int *pp = &b;
    int *ppp = &a;
    int *pppp = &b;

    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;

    cout<<"*p = "<<*p<<endl;
    cout<<"*pp = "<<*pp<<endl;
    cout<<"*ppp = "<<*ppp<<endl;
    cout<<"*pppp = "<<*pppp<<endl;

    return 0;
}

上邊的程序是分別適合於 C 和 C++ 的相同程序,但是在下方的程序在編譯時候卻出現了問題,提示 “invalid conversion form ... to ...”,這意味着 C++ 中的類型檢查會更加嚴格。

同樣類似於下方的語句,在 C 和 C++ 中分別編譯得到的結果也是不同的。所以當使用 C++ 編寫程序時,最好是都給出顯式轉換,防止出現錯誤。

int *p = malloc(10);

布爾類型

C 語言中用 0 和非 0 來分別代替 true 和 false,但是在 C++ 中卻定義了布爾類型,並給出了兩個關鍵字 true 和 false 來代表真和假。

#include <iostream>

using namespace std;

int main()
{
    cout<<true<<" "<<false<<endl;

    return 0;
}

結果爲:

1 0

而這樣的程序在 C 語言中是不能通過編譯的。

字符串類型

在 C 語言中如果需要使用字符串,最常見的就是用字符數組和 char 指針來表示一個字符串。而 C++ 也給出了一個關鍵字 string 來表示字符串類型。

定義和初始化

#include <iostream>

using namespace std;

int main()
{
    string a = "abcde";
    cout<<a<<endl;

    return 0;
}

結果爲:

abcde

類型大小

#include <iostream>

using namespace std;

int main()
{
    string a = "hello";

    cout<<a<<endl;
    cout<<"sizeof(a) is "<<sizeof(a)<<endl;
    cout<<"sizeof(string) is "<<sizeof(string)<<endl;

    return 0;
}

結果爲:

hello
sizeof(a) is 24
sizeof(string) is 24

字符串運算

#include <iostream>

using namespace std;

int main()
{
    string a = "hello";
    string b = " world";
    string c = a+b;                 // plus and assign

    cout<<a<<endl;
    cout<<b<<endl;
    cout<<c<<endl;

    cout<<(a>b)<<endl;              // compare

    return 0;
}

結果爲:

hello
 world
hello world
1

成員函數

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string a = "hello";
    string b = "world";
    cout<<a<<endl;

    for(unsigned i=0;i<a.size();i++)             // size()   size of string
        cout<<a[i];
    cout<<endl;

    const char *str = const_cast<char *>(a.c_str());// c_str() return a C_language string
    cout<<str<<endl;

    cout<<a.find('l')<<endl;               // find()  find a char in string
    cout<<a.erase(0,1)<<endl;              // erase()  erase char in string
    a.swap(b);                             // swap()  swap string
    cout<<a<<endl;

    return 0;
}

結果爲:

hello
hello
hello
2
ello
world

字符串數組

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string str[5] = {"a","aa","aaa","aaaa","aaaaa"};

    for(unsigned i=0;i<5;i++)
        cout<<str[i]<<" "<<str[i].size()<<endl;

    return 0;
}

結果爲:

a 1
aa 2
aaa 3
aaaa 4
aaaaa 5

上邊的結果顯示字符串數組中的每個元素可以是不同的,這樣的實現比 C 語言中的二維數組要靈活的多。

枚舉類型

C 和 C++ 中都有枚舉類型,但是兩者是有所區別的。

// C_language
#include <stdio.h>

enum SEASON
{SPRING,SUMMER,FALL,WINTER};

int main()
{
    enum SEASON a =  SUMMER;
    enum SEASON b = 2;

    printf("a = %d\n",a);
    printf("b = %d\n",b);

    return 0;
}
// C++_language
#include <iostream>

using namespace std;

enum SEASON
{SPRING,SUMMER,FALL,WINTER};

int main()
{
    SEASON a = SPRING;
    SEASON b = 2;

    cout<<a<<endl;
    cout<<b<<endl;

    return 0;
}

從上邊的結果看來:

  • C 定義枚舉類型變量的時候類型名要全寫,如 enum SEASON
  • C++ 定義枚舉類型變量的時候類型名則不必,如 SEASON
  • C 定義枚舉類型變量的時候等號右邊可以是枚舉值或 int
  • C++ 定義枚舉類型變量的時候等號右邊必須是枚舉值

表達式賦值

在 C 和 C++ 中的某些賦值表達式的定義中,由於返回值不同,因此得到的結果和形式也有所差別。

// C_language
#include <stdio.h>

int main()
{
    int a=0,b=1,c=2;
    a=b=c;

    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);

    (a=b)=c;                  // error: lvalue required as left operand of assignment
   
    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);
    
    return 0;
}
// C++_language
#include <iostream>

using namespace std;

int main()
{
    int a=0,b=1,c=2;

    a=b=c;
    cout<<a<<" "<<b<<" "<<c<<endl;

    a=0,b=1,c=2;
    (a=b)=c;
    cout<<a<<" "<<b<<" "<<c<<endl;

    return 0;
}

在 C 的程序編譯過程中會出現編譯錯誤,也就是隻能進行右賦值。

而在 C++ 的程序中左賦值和右賦值都是可行的,但是兩者得到的結果分別爲:

2 2 2
2 1 2

輸入和輸出

從上邊的部分程序中也可以看出,C 和 C++ 中的輸入和輸出方式也是有所差別的。

cin/cout

C 中使用的標準輸入和標準輸出都依靠函數進行,如 scanf,printf

而 C++ 中的標準輸入和標準輸出則是依賴流對象實現的,如 cin,cout

和 Linux 中的標準輸入和標準輸出相似,C++ 中也存在標準輸入,標準輸出和標準錯誤輸出等:

流對象 含義 輸入/輸出設備
cin 標準輸入 鍵盤
cout 標準輸出 屏幕
cerr 標準錯誤輸出 屏幕
clog cerr 中的緩衝輸出 屏幕

函數重載

C 中是不能出現同名函數的,就算函數參數不同也不行。

但 C++ 卻能夠在滿足某些條件時,使用同名函數:

// C++_language
#include <iostream>

using namespace std;

int func(int a)
{
    return a;
}

int func(int a,int b)
{
    return b;
}


int main()
{
    cout<<func(1)<<endl;
    cout<<func(1,2)<<endl;

    return 0;
}

結果爲:

1
2

重載規則

  • 函數名相同
  • 參數個數不同,參數類型不同,參數順序不同,都能夠構成函數重載
  • 返回值類型相同

匹配原則

  • 嚴格匹配,找到則發生調用
  • 不能嚴格匹配,則通過隱式轉換找到匹配,然後發生調用
#include <iostream>

using namespace std;

int func(int a)
{
    cout<<"int func(int a)"<<endl;
    return a;
}

int func(int a,int b)
{
    cout<<"int func(int a,int b)"<<endl;
    return b;
}

int func(int a,double b)
{
    cout<<"int func(int a,double b)"<<endl;
    return a;
}

int func(double a,int b)
{
    cout<<"int func(double a,int b)"<<endl;
    return b;
}

int main()
{
    cout<<func(1)<<endl;
    cout<<func(1,2)<<endl;
    cout<<func(1,1.2)<<endl;
    cout<<func(1.2,2)<<endl;

    cout<<func(1,'2')<<endl;

    return 0;
}

結果爲:

int func(int a)
1
int func(int a,int b)
2
int func(int a,double b)
1
int func(double a,int b)
2
int func(int a,int b)
50

但是如果調用下邊的函數語句,則會出現錯誤:

cout<<func(1.1,1.2)<<endl;

這是因爲在 C++ 內部:

  • 允許 int 到 long 和 double 的隱式類型轉換
  • 允許 double 到 int 和 float 的隱式類型轉換

而 func(1.2,1.2) 會使編譯器不知道調用 func(int,int),func(int,double) 還是 func(double,int) 而發生錯誤。

重載實質

我們知道每個函數名在當前的命名空間中都是唯一的。C++ 重載後的函數名在當前的命名空間中是否也是唯一的呢?

其實 C++ 內部對於函數重載的命名問題使用了 name mangling 技術,對重載函數進行重命名並以此區分參數不同的同名函數。

name mangling 技術使用 v-c-i-f-l-d 表示 void,char,int,float,long,double 及其引用。

如果我們在 Linux 使用 g++ 命令查看編譯後的 .s 文件,就能夠在該文件中發現 funci,funcid,funcdi,funcii 等字眼,分別對應參數不同的重載函數。

extern “C”

在 C 語言中存在四個作用域修飾符,分別是 auto,static,register 和 extern,作用分別爲:

auto

  • auto 只能用來修飾局部變量,可以省略
  • 局部變量若無其它修飾符,則默認爲 auto,也就是說,平常使用的變量大部分都是 auto 修飾的
  • 特點:隨用隨開,用完即銷

register

  • 只能修飾局部變量
  • 一般情況下,是將內存中的變量放到 CPU 寄存器中存儲,能夠提高訪問速度
  • 但 CPU 寄存器數目有限,在程序優化階段通常會被優化爲普通的 auto 修飾的變量

extern

  • 只能用來修飾全局變量
  • 使用本文件之外定義的變量需要加 extern 修飾,否則就會造成重定義
  • extern 修飾的變量只能使用,不能重新賦值

static

static 修飾局部變量時:

  • 會更改局部變量的生命週期,使其與進程一致
  • static 修飾的局部變量如果未初始化,會被初始化爲 0

static 修飾全局變量時:

  • 會限制該變量的外延性,使其成爲只能在本文件內部使用的全局變量
  • 避免了命名污染

而這裏所說的 extern 的作用與 C 中的 extern 的作用稍有不同。

這裏的 extern ”C“ 主要是用來避免 name mangling。

我們知道 C 中不存在函數重載,C 中頭文件定義的函數也是沒有發生 name mangling 的。而 C++ 中的 name mangling 會發生在編譯階段和 .h 文件的聲明階段。因此爲了避免 C++ 對 C 編寫的庫函數或者代碼進行 name mangling,就需要在 C++ 的對應位置加上 extern ”C“ 來表明該段代碼不發生 name mangling。比如在 C 的一些庫函數中爲了能夠在 C++ 都會有 extern “C”。

操作符重載

操作符重載和函數重載都是重載的一種形式,這同樣也是 C++ 種特有的功能。該機制可以將同一操作符應用於不同的數據類型上。

// C++_language
#include <iostream>

using namespace std;

typedef struct complex
{
    int x;
    int y;
}COMP;

complex operator+(complex x, complex y)
{
    x.x = x.x + y.x;
    x.y = x.y + y.y;

    return x;
}

int main()
{
    COMP x={1,2},y={3,4};
    COMP p = x+y;

    cout<<p.x<<" "<<p.y<<endl;

    x.x = 5;x.y = 6;
    y.x = 7;y.y = 8;

    p = operator +(x,y);

    cout<<p.x<<" "<<p.y<<endl;

    return 0;
}

結果爲:

4 6
12 14

從上邊的結果可以看出,利用運算符重載,operator+ 和 + 實現的效果是一樣的,這就是運算符重載的作用。

默認參數

C 語言中,函數是不能設置默認參數的,因此在 C 中必須要顯式的傳遞所有的函數參數,否則便會報錯。

而在 C++ 中,函數形參是可以有默認值的,這可以省略掉參數賦值的過程,更加便捷。

#include <iostream>

using namespace std;

int func(int x,int y = 10,int z = 20)
{
    return x+y+z;
}

int main()
{

    cout<<func(10)<<endl;
    cout<<func(10,20)<<endl;
    cout<<func(10,20,30)<<endl;

    return 0;
}

結果爲:

40
50
60

默認規則

雖然 C++ 中開放了函數默認參數的設置,但是並不是說可以隨便的設置默認參數,而是要符合一定的規則:

  • 實參的順序從左到右給出,形參中默認參數的順序從右到左給出,不能間隔跳轉
  • 函數聲明和定義一體時,默認參數在直接在形參列表中
  • 函數聲明和定義分開時,聲明在前,定義在後,默認參數在聲明處
  • 一個函數不能同時充當重載和具有默認參數的函數

如下邊的函數重載是行不通的:

int func(int x,int y = 10,int z = 20)
{
    return x+y+z;
}

int func(int x)
{
    return (int)x;
}

引用

引用含義

引用的形式爲 “&var”。

引用(reference)本身是一段內存的引用,也就是變量別名。有點類似於 Linux 中的變量別名。

規則

  • 引用本身沒有定義,只是一種關係型聲明,用來聲明它和某個變量的關係
  • 引用的類型與原類型保持一致,且與引用的變量有相同的地址
  • 從上邊的形式可以看出,引用的作用大致與指針相似,但是引用本身不分配內存
  • 聲明的時候必須初始化,聲明之後,不可變更
  • 可對引用,再次進行引用,此時只說明一個變量具有多個別名
  • 這裏的 & 也是經過了運算符重載,只有在數據類型時,纔是引用,剩下的均爲地址
#include <iostream>
#include <typeinfo>

using namespace std;

int main()
{

    int a =3,b =4;
    int &c = a;

    cout<<a<<endl;
    cout<<b<<endl;
    cout<<c<<endl;
    cout<<sizeof(a)<<" "<<sizeof(b)<<" "<<sizeof(c)<<" "<<endl;
    cout<<typeid(a).name()<<" "<<typeid(b).name()<<" "<<typeid(c).name()<<" "<<endl;
    cout<<&a<<" "<<&b<<" "<<&c<<" "<<endl;

    c = b;

    cout<<a<<endl;
    cout<<b<<endl;
    cout<<c<<endl;
    cout<<sizeof(a)<<" "<<sizeof(b)<<" "<<sizeof(c)<<" "<<endl;
    cout<<typeid(a).name()<<" "<<typeid(b).name()<<" "<<typeid(c).name()<<" "<<endl;
    cout<<&a<<" "<<&b<<" "<<&c<<" "<<endl;

    return 0;
}

結果爲:

3
4
3
4 4 4
i i i
0x61fe88 0x61fe84 0x61fe88
4
4
4
4 4 4
i i i
0x61fe88 0x61fe84 0x61fe88

跟指針不同的是,引用不能夠再次進行賦值。在定義引用 c 的時候,我們爲 c 初始化了變量 a,也就是說此時 c 爲變量 a 的別名。但在之後我們又想利用下邊的賦值將引用 c 指向變量 b:

c=b;

但結果卻不是我們想要的那樣。從結果來看,c 此時是變量 a 的一個別名,而上式只是一個賦值,將變量 b 的內容同時賦給了 a 和 c。

引用使用

引用一般使用在函數調用中,將變量引入到函數中,進行函數調用,操作實參,而不需要傳入指針。

#include <iostream>

using namespace std;

void swap(int &x,int &y)
{
    x = x^y;
    y = x^y;
    x = x^y;
}

int main()
{
    int x = 1,y = 2;
    cout<<x<<" "<<y<<" "<<endl;
    swap(x,y);
    cout<<x<<" "<<y<<" "<<endl;

    return 0;
}

結果爲:

1 2
2 1

可以看出,如果要交換兩個變量的值,可以直接傳入引用,而不必傳入指針。

引用實質

我們上邊提到引用的作用跟指針類似,但其實引用的本質就是指針,而只是對指針進行了包裝。

而指針的形式則可以通過初始化方式和大小進行探究:

#include <iostream>

using namespace std;

struct typec
{
    char &r;
};

struct typei
{
    int &r;
};

struct typed
{
    double &r;
};

int main()
{
    cout<<sizeof(char &)<<" "<<sizeof(int &)<<" "<<sizeof(double &)<<endl;
    cout<<sizeof(typec)<<" "<<sizeof(typei)<<" "<<sizeof(typed)<<endl;

    return 0;
}

結果爲:

1 4 8
4 4 4

從上邊的結果可以看出:

  • 引用這種數據類型在使用 sizeof 時,會隨着 datatype 的變化而變化,這一點有點像常量
  • 而對引用類型進行 sizeof 時,又不會隨着 datatype 的變化而變化,這一點有點像指針

再加上引用必須要在定義時初始化,不能改變指向,但卻可以改變引用的值。綜合起來看就是:

reference = datatype * const var;

但是引用使用也有所限制:

  • 可以定義指針的引用,而不能定義引用的引用
  • 可以定義指針的指針,但是不能定義引用的指針
  • 可以定義指針數組,但是不能定義引用數組,可以定義數組引用
#include <iostream>

using namespace std;

int main()
{
    int x = 1;

    int *p = &x;
    int * &r = p;      // reference to pointer

    cout<<x<<endl;
    cout<<&x<<" "<<&p<<" "<<&r<<endl;
    cout<<*p<<" "<<*r<<endl;

    int y[3] = {1,2,3};
    int (&rr)[3] = y;     // reference to array

    for(int i=0;i<3;i++)
    {
        cout<<y[i]<<" ";
    }
    cout<<endl;

    for(int i=0;i<3;i++)
    {
        cout<<rr[i]<<" ";
    }

    return 0;
}

結果爲:

1
0x61fe8c 0x61fe88 0x61fe88
1 1
1 2 3
1 2 3

常引用

就像可以用 const 修飾指針一樣,同樣可以使用 const 修飾引用,也能夠得到一些特殊的效果。

  • const 對象的引用一定要是 const 的

不能將 const 變量給非 const 引用是限制了不能通過引用修改原始變量的值,畢竟不能只通過別名修改原始 const 的值。

而可以將非 const 變量給 const 引用則是隻給引用開放了讀的權限,而原始值卻是可以修改的。

  • const 引用可使用臨時對象初始化

臨時對象可以理解爲不可取地址的對象,主要有:

  • 常量
  • 表達式
  • 函數返回值
  • 類型不同的變量

下邊的形式編譯都是能夠通過的:

#include <iostream>

using namespace std;

int func()
{
    int x = 1;
    return x;
}

int main()
{
    const int &a = 2;
    cout<<a<<endl;

    int b = 2,c = 3;
    const int &d = b+c;
    cout<<d<<endl;

    const int &e = func();
    cout<<e<<endl;

    double f = 3.25;
    const int &g = f;
    cout<<g<<endl;

    return 0;
}

上邊的常引用形式都可以理解爲它們在內存中產生了臨時變量,然後將這一個臨時變量給了常引用,進行了初始化。

  • use const whatever possible
  • 使用 const 可以避免對數據進行誤操作
  • 使用 const 可以處理 const 和非 const 實參,不然就只能接受非 const 實參
  • 使用 const 引用,可以使函數能夠正確生成並使用臨時變量

new/delete

雖然 C++ 能夠兼容使用 C 中的 alloc 和 free 函數,但是 C++ 還是另外定義了兩個關鍵字 new 和 delete 來完成對堆內存的申請和釋放。

new/new[]

new 用來申請單個變量空間,而忽略數據類型

new[] 用來申請連續的變量空間,而忽略數據類型

delete/delete[]

delete 用來釋放申請的單個變量空間,與 new 對應

delete[] 用來釋放申請的連續變量空間,與 new[] 對應

#include <iostream>
#include <string.h>

using namespace std;

int main()
{
    int *p = new int(1);
    cout<<*p<<endl;
    delete p;

    int *pp = new int[5];
    for(int i=0;i<5;i++)
        pp[i]=i+1;

    for(int i=0;i<5;i++)
        cout<<pp[i]<<" ";

    cout<<endl;
    delete []pp;

    char **ppp = new char *[5];
    ppp[0] = (char *)"apple";ppp[1] = (char *)"orange";ppp[2] = (char *)"banana";
    ppp[3] = (char *)"watermelon";ppp[4] = (char *)"strawberry";
    for(int i=0;i<5;i++)
        cout<<ppp[i]<<" ";

    cout<<endl;
    delete []ppp;

    int (*pppp)[3] = new int [2][3];
    for(int i=0;i<2;i++)
        for(int j=0;j<3;j++)
            pppp[i][j] = i*3+j;

    for(int i=0;i<2;i++)
        for(int j=0;j<3;j++)
            cout<<pppp[i][j]<<" ";

    cout<<endl;
    delete []pppp;

    char (*z)[11] = new char [5][11];
    strcpy(z[0],"apple");
    strcpy(z[1],"orange");
    strcpy(z[2],"banana");
    strcpy(z[3],"watermelon");
    strcpy(z[4],"strawberry");

    for(int i=0;i<5;i++)
        cout<<z[i]<<" ";

    delete []z;

    return 0;
}

結果爲:

1
1 2 3 4 5
apple orange banana watermelon strawberry
0 1 2 3 4 5
apple orange banana watermelon strawberry

注意事項

  • new/delete 是關鍵字,效率要高
  • new/delete 配對使用,new []/delete [] 配對使用
  • 因爲實現的機理不同,因此 new 申請的空間不要用 free 釋放,alloc 申請的空間不要用 delete 釋放
  • new 申請空間失敗不會拋出異常,因此不能使用 q == NULL 判斷申請空間是否成功

內聯函數

在 C 中,有些簡短的代碼可以使用宏實現,避免了不斷的壓棧和出棧,但是當時提到說這樣會增加預處理的工作量,增加代碼的體積,並且宏只是簡單的代碼替換,不能夠檢測語法。而 C++ 中的內聯函數相比於宏來說:

  • 同樣避免了重複開發
  • 增加了類型檢查
  • 但是會帶來壓棧和出棧的開銷
#include <iostream>

using namespace std;

inline int func(int x)
{
    return x;
}

int main()
{
    cout<<func(10)<<endl;

    return 0;
}

上邊的代碼段只是說明了內聯函數的語法,並不是說明什麼。因爲內聯函數的優點只要在發生頻繁的調用時,纔會有所體現。同時由於內聯函數的特點,只有當函數體很小,且被多次調用時才考慮使用內聯函數。

類型轉換

C 中的類型轉換有隱式轉換和強制轉換,但是 C 中能夠實現任意類型之間的轉換。

隱式轉化

不需要顯式地進行類型轉化就能夠完成的類型轉換就被稱爲隱式轉換

算術轉化

  • 整形提升:char,short,int 等類型在一起運算時,會被提升到 int 類型。
  • 混合提升:運算時,以表達式中最長類型爲主,將其他類型均轉換成該類型。
    • 若運算中最大範圍爲 double,則轉化爲 double。
    • 若運算中最大範圍爲 float 則轉化爲 float。
    • 若運算中最大範圍爲 long long 則轉化爲 long long。
    • 若運算中最大範圍爲 int 則轉化爲 int。
    • 若運算中有 char,short 則一併轉化爲 int。

賦值轉化

整型和實型之間能夠進行直接賦值的。操作時,一個加 0 ,一個去小數位。

強制轉化

有時候隱式轉換不能夠滿足需求時,就要進行強制類型轉化。

在 C++ 中對類型轉換進一步做出了限制。

static_cast

語法

static_cast<datatype> (var)

規則

  • 在一個方向上可以做隱式轉化,在另一個方向上就可以進行靜態轉換。
#include <iostream>

using namespace std;

int main()
{
    char a = 65;
    int b = 100;
    double c = 200.25;

    double d = a;

    cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;

    d = b;
    cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;

    a = c;

    cout<<static_cast<char>(d)<<" "<<static_cast<int>(d)<<endl;

    return 0;
}

結果爲:

A 100 200.25 65
A 100 200.25 100
d 100

對於指針也是如此,可以認爲靜態類型轉換是將 C 風格的類型轉換寫成了 C++ 風格的類型轉換形式。

reinterpret_cast

語法

reinterpret_cast<datatype>(var)

規則

  • 通常爲操作數的位模式提供較低層的重新解釋,也就是說是將數據以二進制存在形式的重新解釋
  • 在雙方向都不能進行隱式類型轉換的,需要重新解釋類型轉換。
#include <iostream>

using namespace std;

int main()
{

    int a[5] = {1,2,3,4,5};
    cout<<hex<<*((int *)((int)a+1))<<endl;
    cout<<hex<<*(reinterpret_cast<int *>(reinterpret_cast<int>(a)+1));

    return 0;
}

結果爲:

2000000
2000000

上邊的過程爲:

  • 將數組 a 的起始地址作爲 int 解釋,對應的是元素 1 的最低字節地址
  • 然後將該值 +1,對應的是元素 2 的次低字節地址
  • 再將該地址解釋爲 int *,指向的就是以當前地址起始的連續四個字節
  • 再用 * 解引用,以二進制輸出就是 2000000

const_cast

語法

const_cast<datatype>(var)            // datatype只能是指針或者引用

規則

  • 用來移除對象的常量屬性
  • 使用 const_cast 去除 const 限定的目的不是爲了修改內容
  • 因爲 const 變量不能通過非 const 引用,因此 const_cast 通常是爲了函數能夠接受該實際參數
#include <iostream>

using namespace std;

int func(int &b)
{
    return b;
}

int main()
{

    const int a = 1;

    const int &b = a;
    cout<<func(const_cast<int &>(b));

    return 0;
}

但還存在一種未定義行爲:

#include <iostream>

using namespace std;

struct A
{
    int x;
};

int main()
{
    const int a = 10;
    int &ra = const_cast<int &>(a);

    ra = 20;
    cout<<a<<" "<<ra<<endl;

    const A c = {30};
    A &rc = const_cast<A &>(c);
    rc.x = 40;
    cout<<c.x<<" "<<rc.x<<endl;

    return 0;
}

結果爲:

10 20
40 40

因此,對於 const_cast 來說:

  • const_cast is only safe if you are adding const to an originally non-const variable. Trying to remove the const status from an originally-const object, and then perform the write operation on it will result in undefined behavior.

dynamic_cast

跟多態有關。

命名空間

C 中的命名空間是根據編寫完成後的代碼自動實現的,而 C++ 中命名空間卻可以手動創建,從而對全局空間進行再次劃分,以避免不同模塊下的命名衝突。

全局命名空間

全局命名空間包含了所有的命名空間,主要有:

  • 全局變量
  • 數據類型
  • 函數
  • 其它命名空間

而自定義的命名空間也可以按照上邊的層次進行創建,只是該命名空間中的內容都屬於該命名空間。

聲明

#include <iostream>
#include <string>

using namespace std;

namespace first
{
    int a = 10;
    typedef struct A{int x;}typeA;
    int func(int x){return x;}

    namespace second
    {
        int a = 20;
        typedef struct A{ int x;}typeA;
        int func(int x){return x;}
    }
}

int main()
{
    return 0;
}

使用

  • 直接指定 namespace,namespace::var = value;
  • 使用 using namespace::var,using namespace::var;
  • 使用 using namespace spacename,如 using namespace std;
#include <iostream>
#include <string>

using namespace std;

namespace first
{
    int a = 10;
    typedef struct A{int x;}typeA;
    int func(int x){return x;}

    namespace second
    {
        int a = 20;
        typedef struct A{ int x;}typeA;
        int func(int x){return x;}
    }
}

int main()
{
    cout<<first::a<<endl;

    using first::a;
    a = 20;
    cout<<a<<" "<<first::a<<endl;

    using namespace first;
    second::a = 30;
    cout<<a<<" "<<second::a<<endl;

    return 0;
}

結果爲:

10
20 20
20 30

使用 using namespace spacename 的形式,相當於直接將命名空間 spacename 中的變量全部解壓到當前。

範圍限定

上邊的形式可能會覆蓋之前定義過的同名變量,因此如果我們想要使用不同命名空間中的同名變量時,需要加 {} 來限定變量的作用域。

#include <iostream>
#include <string>

using namespace std;

namespace first
{
int a = 10;
}

namespace second
{
int a = 20;
}

int main()
{
    int a = 100;
    cout<<a<<endl;
    {
        using first::a;
        cout<<a<<endl;
    }
    cout<<a<<endl;
    {
        using second::a;
        cout<<a<<endl;
    }
    cout<<a<<endl;

    return 0;
}

 結果爲:

100
10
100
20
100

但是不能這樣:

#include <iostream>
#include <string>

using namespace std;

namespace first
{
int a = 10;
}

namespace second
{
int a = 20;
}

int main()
{
    int a = 100;
    cout<<a<<endl;
    {
        using namespace first;
        cout<<a<<endl;
    }
    cout<<a<<endl;
    {
        using namespace second;
        cout<<a<<endl;
    }
    cout<<a<<endl;
    using namespace first;
    cout<<a<<endl;

    return 0;
}

結果爲:

100
100
100
100
100
100

原因在於 using namespace spacename 和 using namespace::var 的含義不同。

支持嵌套

在之前的例子中可以看出一個命名空間中可以嵌套另一個命名空間。

協同開發

有時候大型項目的開發需要多個協作共同完成一個命名空間中所有屬性的創建,但是屬性卻分佈在不同的文件中。C++ 對這種情況做出了規定:

  • 同名命名空間自動合併
  • 對於一個命名空間中的類,要同時包含聲明和實現

更多關於 namespace 的說明和注意事項在這篇文章

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