天天看點

C++程式設計:複合資料類型—枚舉與指針

作者:尚矽谷教育

實際應用中,經常會遇到某個資料對象隻能取有限個常量值的情況,比如一周有7天,一副撲克牌有4種花色等等。對于這種情況,C++提供了另一種批量建立符号常量的方式,可以替代const。這就是“枚舉”類型enum。

枚舉

1. 枚舉類型定義

枚舉類型的定義和結構體非常像,需要使用enum關鍵字。

// 定義枚舉類型

enum week

{

Mon, Tue, Wed, Thu, Fri, Sat, Sun

};

與結構體不同的是,枚舉類型内隻有有限個名字,它們都各自代表一個常量,被稱為“枚舉量”。

需要注意的是:

  • 預設情況下,會将整數值賦給枚舉量;
  • 枚舉量預設從0開始,每個枚舉量依次加1;是以上面week枚舉類型中,一周七天枚舉量分别對應着0~6的常量值;
  • 可以通過對枚舉量指派,顯式地設定每個枚舉量的值

2. 使用枚舉類型

使用枚舉類型也很簡單,建立枚舉類型的對象後,隻能将對應類型的枚舉量指派給它;如果列印它的值,将會得到對應的整數。

week w1 = Mon;

week w2 = Tue;

//week w3 = 2; // 錯誤,類型不比對

week w3 = week(3); // int類型強轉為week類型後指派

cout << "w1 = " << w1 << endl;

cout << "w2 = " << w2 << endl;

cout << "w3 = " << w3 << endl;

這裡需要注意:

  • 如果直接用一個整型值對枚舉類型指派,将會報錯,因為類型不比對;
  • 可以通過強制類型轉換,将一個整型值指派給枚舉對象;
  • 最初的枚舉類型隻有列出的值是有效的;而現在C++通過強制類型轉換,允許擴大枚舉類型合法值的範圍。不過一般使用枚舉類型要避免直接強轉指派。

指針

計算機中的資料都存放在記憶體中,通路記憶體的最小單元是“位元組”(byte)。所有的資料,就儲存在記憶體中具有連續編号的一串位元組裡。

C++程式設計:複合資料類型—枚舉與指針

指針顧名思義,是“指向”另外一種資料類型的複合類型。指針是C/C++中一種特殊的資料類型,它所儲存的資訊,其實是另外一個資料對象在記憶體中的“位址”。通過指針可以通路到指向的那個資料對象,是以這是一種間接通路對象的方法。

C++程式設計:複合資料類型—枚舉與指針

1. 指針的定義

指針的定義文法形式為:

類型 * 指針變量;

這裡的類型就是指針所指向的資料類型,後面加上星号“*”,然後跟指針變量的名稱。指針在定義的時候可以不做初始化。相比一般的變量聲明,看起來指針隻是多了一個星号“*”而已。例如:

int* p1; // p1是指向int類型資料的指針

long* p2; // p2是指向long類型資料的指針

cout << "p1在記憶體中長度為:" << sizeof(p1) << endl;

cout << "p2在記憶體中長度為:" << sizeof(p2) << endl;

p1、p2就是兩個指針,分别指向int類型和long類型的資料對象。

指針的本質,其實就是一個整數表示的記憶體位址,它本身在記憶體中所占大小跟系統環境有關,而跟指向的資料類型無關。64位編譯環境中,指針統一占8個位元組;若是32位系統則占4位元組。

2. 指針的用法

C++程式設計:複合資料類型—枚舉與指針
C++程式設計:複合資料類型—枚舉與指針

(1)擷取對象位址給指針指派

指針儲存的是資料對象的記憶體位址,是以可以用位址給指針指派;擷取對象位址的方式是使用“取位址操作符”(&)。

int a = 12;

int b = 100;

cout << "a = " << a << endl;

cout << "a的位址為:" << &a << endl;

cout << "b的位址為:" << &b << endl;

int* p = &b; // p是指向b的指針

p = &a; // p指向了a

cout << "p = " << p << endl;

把指針當做一個變量,可以先指向一個對象,再指向另一個不同的對象。

(2)通過指針通路對象

指針指向資料對象後,可以通過指針來通路對象。通路方式是使用“解引用操作符”(*):

p = &a; // p是指向a的指針

cout << "p指向的記憶體中,存放的值為:" << *p << endl;

*p = 25; // 将p所指向的對象(a),修改為25

cout << "a = " << a << endl;

在這裡由于p指向了a,是以*p可以等同于a。

3. 無效指針、空指針和void*指針

(1)無效指針

定義一個指針之後,如果不進行初始化,那麼它的内容是不确定的(比如0xcccc)。如果這時把它的内容當成一個位址去通路,就可能通路的是不存在的對象;更可怕的是,如果通路到的是系統核心記憶體區域,修改其中内容會導緻系統崩潰。這樣的指針就是“無效指針”,也被叫做“野指針”。

int* p1;

//*p1 = 100; // 危險!指針沒有初始化,是無效指針

指針非常靈活非常強大,但野指針非常危險。是以建議使用指針的時候,一定要先初始化,讓它指向真實的對象。

(2)空指針

如果先定義了一個指針,但确實還不知道它要指向哪個對象,這時可以把它初始化為“空指針”。空指針不指向任何對象。

int* np = nullptr; // 空指針字面值

np = NULL; // 預處理變量

np = 0; // 0值

int zero = 0;

//np = zero; // 錯誤,int變量不能指派給指針

cout << "np = " << np << endl; // 輸出0位址

//cout << "*np = " << *np << endl; // 錯誤,不能通路0位址的内容

空指針有幾種定義方式:

  • 使用字面值nullptr,這是C++ 11 引入的方式,推薦使用;
  • 使用預處理變量NULL,這是老版本的方式;
  • 直接使用0值;
  • 另外注意,不能直接用整型變量給指針指派,即使值為0也不行

是以可以看出,空指針所儲存的其實就是0值,一般把它叫做“0位址”;這個位址也是記憶體中真實存在的,是以也不允許通路。

空指針一般在程式中用來做判斷,看一個指針是否指向了資料對象。

(3)void * 指針

一般來說,指針的類型必須和指向的對象類型比對,否則就會報錯。不過有一種指針比較特殊,可以用來存放任意對象的位址,這種指針的類型是void*。

int i = 10;

string s = "hello";

void* vp = &i;

vp = &s;

cout << "vp = " << vp << endl;

cout << "vp的長度為: " << sizeof(vp) << endl;

//cout << "*vp = " << *vp << endl; // 錯誤,不能通過void *指針通路對象

void* 指針表示隻知道“儲存了一個位址”,至于這個位址對應的資料對象是什麼類型并不清楚。是以不能通過 void* 指針通路對象;一般 void* 指針隻用來比較位址、或者作為函數的輸入輸出。

4. 指向指針的指針

指針本身也是一個資料對象,也有自己的記憶體位址。是以可以讓一個指針儲存另一個指針的位址,這就是“指向指針的指針”,有時也叫“二級指針”;形式上可以用連續兩個的星号**來表示。類似地,如果是三級指針就是***,表示“指向二級指針的指針”。

C++程式設計:複合資料類型—枚舉與指針

int i = 1024;

int* pi = &i; // pi是一個指針,指向int類型的資料

int** ppi = π // ppi是一個二級指針,指向一個int* 類型的指針

cout << "pi = " << pi << endl;

cout << "* pi = " << * pi << endl;

cout << "ppi = " << ppi << endl;

cout << "* ppi = " << * ppi << endl;

cout << "** ppi = " << ** ppi << endl;

如果需要通路二級指針所指向的最原始的那個資料,應該做兩次解引用操作。

5. 指針和const

指針可以和const修飾符結合,這可以有兩種形式:一種是指針指向的是一個常量;另一種是指針本身是一個常量。

(1)指向常量的指針

指針指向的是一個常量,是以隻能通路資料,不能通過指針對資料進行修改。不過指針本身是變量,可以指向另外的資料對象。這時應該把const加在類型前。

const int c = 10, c2 = 56;

//int* pc = &c; // 錯誤,類型不比對

const int* pc = &c; // 正确,pc是指向常量的指針,類型為const int *

pc = &c2; // pc可以指向另一個常量

int i = 1024;

pc = &i; // pc也可以指向變量

*pc = 1000; // 錯誤,不能通過pc更改資料對象

這裡發現,pc是一個指向常量的指針,但其實把一個變量i的位址賦給它也是可以的;編譯器隻是不允許通過指針pc去間接更改資料對象。

(2)指針常量(const指針)

指針本身是一個資料對象,是以也可以區分變量和常量。如果指針本身是一個常量,就意味它儲存的位址不能更改,也就是它永遠指向同一個對象;而資料對象的内容是可以通過指針改變的。這種指針一般叫做“指針常量”。

指針常量在定義的時候,需要在星号*後、辨別符前加上const。

int* const cp = &i;

*cp = 2048; // 通過指針修改對象的值

cout << "i = " << i << endl;

//cp = &c; // 錯誤,不可以更改cp的指向

const int* const ccp = &c; // ccp是一個指向常量的常量指針

這裡也可以使用兩個const,定義的是“指向常量的常量指針”。也就是說,ccp指向的是常量,值不能改變;而且它本身也是一個常量,指向的對象也不能改變。

6. 指針和數組

C++程式設計:複合資料類型—枚舉與指針

(1)數組名

用到數組名時,編譯器一般都會把它轉換成指針,這個指針就指向數組的第一個元素。是以我們也可以用數組名來給指針指派。

int arr[] = {1,2,3,4,5};

cout << "arr = " << arr << endl;

cout << "&arr[0] = " << &arr[0] << endl;

int* pia = arr; // 可以直接用數組名給指針指派

cout << "* pia = " << *pia << endl; // 指針指向的資料,就是arr[0]

也正是因為數組名被認為是指針,是以不能直接使用數組名對另一個數組指派,數組也不允許這樣的直接拷貝:

int arr[] = {1,2,3,4,5};

//int arr2[5] = arr; // 錯誤,數組不能直接拷貝

(2)指針運算

如果對指針pia做加1操作,我們會發現它儲存的位址直接加了4,這其實是指向了下一個int類型資料對象:

pia + 1; // pia + 1 指向的是arr[1]

*(pia + 1); // 通路 arr[1]

所謂的“指針運算”,就是直接對一個指針加/減一個整數值,得到的結果仍然是指針。新指針指向的資料元素,跟原指針指向的相比移動了對應個資料機關。

(3)指針和數組下标

我們知道,數組名arr其實就是指針。這就帶來了非常有趣的通路方式:

* arr; // arr[0]

*(arr + 1); // arr[1]

這是通過指針來通路數組元素,效果跟使用下标運算符arr[0]、arr[1]是一樣的。進而我們也可以發現,周遊元素所謂的“範圍for循環”,其實就是讓指針不停地向後移動依次通路元素。

(4)指針數組和數組指針

指針和數組這兩種類型可以結合在一起,這就是“指針數組”和“數組指針”。

  • 指針數組:一個數組,它的所有元素都是相同類型的指針;
  • 數組指針:一個指針,指向一個數組的指針;

int arr[] = {1,2,3,4,5};

int* pa[5]; // 指針數組,裡面有5個元素,每個元素都是一個int指針

int(* ap)[5]; // 數組指針,指向一個int數組,數組包含5個元素

cout << "指針數組pr的大小為:" << sizeof(pa) << endl; // 40

cout << "數組指針ap的大小為:" << sizeof(ap) << endl; // 8

pa[0] = arr; // pa中第一個元素,指向arr的第一個元素

pa[1] = arr + 1; // pa中第二個元素,指向arr的第二個元素

ap = &arr; // ap指向了arr整個數組

cout << "arr =" << arr << endl;

cout << "* arr =" << *arr << endl; // arr解引用,得到arr[0]

cout << "arr + 1 =" << arr + 1 << endl;

cout << "ap =" << ap << endl;

cout << "* ap =" << *ap << endl; // ap解引用,得到的是arr數組

cout << "ap + 1 =" << ap + 1 << endl;

這裡可以看到,指向數組arr的指針ap,其實儲存的也是arr第一個元素的位址。arr類型是int *,指向的就是arr[0];而ap類型是int (*) [5],指向的是整個arr數組。是以arr + 1,得到的是arr[1]的位址;而ap + 1,就會跨過整個arr數組。

繼續閱讀