1. gzyueqian
      18529173453
      首頁 > 新聞中心 > > 正文

      C++入門解惑——初探指針

      更新時間: 2007-05-25 09:57:23來源: 粵嵌教育瀏覽量:793

        
        摘要:本文主要介紹c語言中指針的基本概念以及應用.
        關鍵字 C++ 入門 指針 數組 動態內存

        形形色色的指針

        前一章我們引入了指針及其定義,這一節我們繼續研究各種不同的指針及其定義方式(注:由于函數指針較為特殊,本章暫不作討論,但凡出現“指針”一詞,如非特別說明均指數據指針)。

        1 指向指針的指針

        我們已經知道,指針變量是用于儲存特定數據類型地址的變量,假如我們定義

      int *pInt;

        那么,pInt為一個指向整型變量的指針變量。好,我們把前面這句話的主干提取出來,就是:pInt為變量。既然pInt是變量,在內存中就會有與之對應的存放數據的地址值,那么理論上也就應該有對應的指針來存儲,嗯,實際上也如此,我們可以向這樣來定義可以指向變量pInt的指針:

      int **pIntPtr;

        按前一章的方法很好理解這樣的定義:**pIntPtr是一個int類型,則去掉一個*,*pIntPtr就是指向int的指針,再去一個*,我們終得到的pIntPtr就是一個“指向int型指針變量的指針變量”,呵呵,是點拗口,不管怎么說我們現在可以寫:

      pIntPtr = &pInt;

        令其指向pInt變量,而*pIntPtr則可以得回pInt變量。假如pInt指向某個整型變量如a,*pInt可以代表a,因此*(*pIntPtr)此時也可以更間接地得到a,當然我們如果省去括號,寫成**pIntPtr也是可以的。

        以此類推,我們還可以得到int ***p這樣的“指向指向指向int型變量的指針的指針的指針”,或者再復雜:int ****p,“指向指向指向指向……”喔,說起來已經很暈了,不過原理擺在這里,自己類比一下即可。

        2 指針與常量

        C++的常量可以分兩種,一種是“文本”常量,比如我們程序中出現的18,3.14,’a’等等;另一種則是用關鍵字const定義的常量。大多數時候可以把這兩種常量視為等同,但還是有一些細微差別,例如,“文本”常量不可直接用&尋找其在內存中對應的地址,但const定義的常量則可以。也就是說,我們不能寫&18這樣的表達式,但假如我們定義了

      const int ClassNumber = 18;

        則我們可以通過&ClassNumber表達式得到常量ClassNumber的地址(不是常數18的地址!)。其實在存儲特點上常量與變量基本是一樣的(有對應的地址,并且在對應地址上存有相應的值),我們可以把常量看作一種“受限”的變量:只可讀不可寫。既然它們如此相似,而變量有對應的指針,那么常量也應該有其對應的指針。比如,一個指向int型常量的指針pConstInt定義如下:

      const int *pConstInt;

        它意味著*pConstInt是一個整型常量,因此pConstInt就是一個指向整型常量的指針。我們就可以寫

      pConstInt = &ClassNumber;

        來令pConstInt指向常量ClassNumber. 給你三秒鐘,請判斷pConstInt是常量還是變量。1,2,3!OK,假如你的回答是變量,那么說明你對常量變量的概念認識得還不錯,否則應該翻本C++的書看看const部分的內容。

        唔,既然int、float、double甚至我們自己定義的class都可以有對應的常量類型,那么指針應該也有常量才對,現在的問題是,我們應該如何定義一個指針常量呢?我們通常定義常量的作法是在類型名稱前面加上const,像const int a等等,但如果在指針定義前面加const,由于*是右結合的,語義上計算機會把const int *p 視為 (const int) (*p)(括號是為了突出其結合形式所用,但不是合法的C++語法),即*p是一個const int型常量,p就為一個指向const int常量的指針。也就是說,我們所加的const并非修飾p,而是修飾*p,換成int const *p又如何呢?噢,這和const int *p沒有區別。為了讓我們的const能夠修飾到p,我們必須越過*號的阻撓將const送到p跟前,假如我們先在前面定義了一個int變量a,則語句

      int * const p = &a;

        就終如我們所愿地定義了一個指針常量p,它總是表示a的地址,也就是說,它恒指向變量a.

        嗯,小結一下:前面我們講了兩種指針,一種是“指向常量的指針變量”,而之后是“指向變量的指針常量”,它們定義的區別就在于const所修飾的是*p還是p. 同樣,還會有“指向常量的指針常量”,顯然,必須要有兩個const,一個修飾*p,另一個修飾p:

      const int * const p = &ClassNumber;

        以*為界,我們同樣很好理解:*表示我們聲明的是指針,它前面的const int表示它指向某個整型常量,后面的const表示它是的個常量指針。為方便區別,許多文章都介紹了“從右到左”讀法,其中把“*”讀作“指針”:

      const int *p1 = &ClassNumber; // p1是一個指針,它指向int型常量

      int * const p2 = &a; // p2是一個指針常量,它指向int型變量

      const int * const p3 = &ClassNumber; // p3是一個指針常量,它指向int型常量

        好了,我們前面定義指針常量時,受到了*號右結合的困擾,使得前置的const修飾不到p,假如*號能與int結合起來(就像前一章所說的“前置派”的理解),成為一種“指向整型指針的類型”,如:

      const (int*) p;

        const就可以修飾到p了。但C++的括號只能用于改變表達式的優先級而不能改變聲明語句的結合次序,能不能想出另一種方法來實現括號的功能呢?答案是肯定的:使用關鍵字typedef.

        typedef的一個主要作用是將多個變量/常量修飾符捆梆起來作為一種混合性的新修飾符,例如要定義一個無符號的整型常量,我們要寫

      const unsigned int ClassNumber = 18;

        但我們也可以先用typedef將“無符號整型常量”定義成一個特定類型:

      typedef const unsigned int ConstUInt;

        這樣我們只須寫

      ConstUInt ClassNumber = 18;

        就可以達到與前面等價的效果。咋看似乎與我們關注的內容沒有關系,其實typedef的“捆梆”就相當于加了括號,假如,我們定義:

      typedef int * IntPtr;

        這意味著什么?這意味著IntPtr是一個“整型指針變量”類型,這可是前面所沒有出現過的新復合類型,實際上這才是上章“前置派”所理解的“int*”類型:我們當初即使寫

      int* p1, p2;

        雖然有了空格作為我們視覺上的區分,但不幸的是編譯器不吃這一套,仍會把*與p1結合,變成

      int (*p1), p2;

        所以可憐的p2無依無靠只得成為一個整型變量。但現在我們寫

      IntPtr p1, p2;

        結論就不一樣了:有了typedef的捆梆,IntPtr已經成為了名符其實的整型指針類型,所以p1,p2統統成為了貨真介實的指針。那么我們寫

      const IntPtr p;

        噢,不好意思,編譯出錯了:沒有初始化常量p……咦,看見了沒有?在const IntPtr的修飾下p已經成為指針常量了(而不是const int *p這樣的指向常量的指針),哦,明白了,由于typedef的捆梆,const與IntPtr都同心協力地修飾p,即理解為:

      (const) (int *) p;

      而不是前面的

      (const int) (*p);

        所以,不要小瞧了typedef,不要隨意將它看作是一個簡單的宏替換。事實上《C++ Primer》就曾經出了這樣的類似考題,大約也是考你:const IntPtr p中的p是指向const int的指針呢還是指向int的指針常量。我知道現在你可以毫不猶豫地正確地回答這個問題了。

      BTW:當初次看到的時候,我也是毫不猶豫,可惜答錯了^_^

        3.指針、動態內存、數組

        我們上一章談到變量時已經知道,變量實際上就是編譯系統為我們程序分配的一塊內存,編譯器會將變量名稱與這塊內存正確地聯系起來以供我們方面地讀寫。設想一下,假如一塊這樣的存儲單元沒有“變量名”,我們應該如何訪問它呢?噢,如果有這個單元的地址,我們通過*運算符也可以得回該對應的變量。

        變量定義可以看作兩個功能的實現:1.分配內存;2.將內存與變量名聯系起來。

        按前面所說,如果知道地址,也可以不需要變量名,所以上兩個功能如果變成:1.分配內存;2.將分配所得的內存的地址保存起來;

        理論上也可以實現上面的功能。在C++中,我們使用new運算符就可以實現第二種方法。new表達式會為我們分配一適當的內存,并且返會該內存的首地址(確切說應該是一個指針)。在表達式中,關鍵字new后面通常緊跟著數據類型,以指示分配內存的大小及返回的指針類型,例如new int表達式會為我們分配一塊整型變量所需的內存(32位機上通常為4字節),然后這個表達式的值就是一個指向該內存的整型指針值。因此我們可以寫:

      int *p;

      p = new int; // 分配一塊用于存儲一個整型變量的內存,并將地址賦給指針p

        這樣我們就可以通過*p來對這塊“沒有變量名”的內存進行相同的操作。

        前面我們僅僅在內存中分配了一個整型存儲單元,我們還可以分配一塊能存儲多個整型值的內存,方法是在int后面加上用“[ ]”括起來的數字,這個數字就是你想分配的單元數目。如:

      int *p;

      p = new int[18]; // 分配一塊用于存儲18個整型變量的內存,并將首地址賦給指針p

        但這時候我們用*p只能對18個整型單元的個進行存取,如何訪問其它17個單元呢?由于這些單元都是連續存放的,所以我們只要知道首地址的值以及每個整型變量所占用的空間,就可以計算出其它17個單元的起始地址值。在C++中,我們甚至不必為“每個整形變量所占空間”這樣的問題所累,因為C++可以“自動地”為我們實現這一點,我們只需要告訴它我們打算訪問的是相對當前指針值的第幾個單元就可以了。

        這一點通過指針運算可以實現,例如,按前面的聲明,現在p已經指向18塊存儲單元的塊,如果我想訪問第二塊,也就是p當前所指的下一塊內存呢?很簡單,只要寫p+1,這個表達式的結果就會神奇地得出第二塊內存單元的地址,如果你的機器是32位,那么你感興趣的話可以打印一下p的地址值與p+1的地址值,你會發現它們之間相差的是4個字節,而不是1個,編譯器已經自動為我們做好了轉換的工作:它會自動將1乘上指針所指的一個變量(整型變量)所占的內存(4字節)。于是我們如果想要給第二內存單元賦值為3 ,則只須寫:

      *(p + 1) = 3; // 注意:*號優先級比+號要高,所以要加上括號

        要打印的時候就寫:

      cout << *(p+1); // 輸出3

        總之這些和一般的變量一樣使用沒有什么兩樣了。我們當然也可以將它的地址值賦給另外的指針變量:

      int *myPtr;

      myPtr = p + 1; // OK,現在myPtr就指向第二內存單元的地址

        也可以進行自加操作:

      myPtr++; // 按上面的初值,自加后myPtr已經指向第三內存單元的地址

      *myPtr = 18; // 現在將第三個內存單元賦予整型值18,也就相當于*(p + 2) = 18

        到目前為止一切都很好,但*(p +1)這樣的寫法太麻煩,C++為此引入了簡記的方法,就是“[ ]”運算符(當初定義的時候也用過它哦):要訪問第二單元內存,我們只需要寫p[1]就可以,它實際上相當于*(p + 1):

      p[1] = 3; // *(p + 1) = 3;

      cout << p[15]; // cout << *(p + 15);

      p[0] = 6; // *(p + 0) = 6; 也就是 *p = 6;

        為了說明“[ ]”與*(… + …)的等效性,下面再看一組奇怪的例子:

      1[p] = 3; // *(1 + p) = 3;

      cout << 15[p]; // cout << *(15 + p);

      0[p] = 6; // *(0 + p) = 6; 也就是 *p = 6;

        看起來是不是很怪異?其實這一組只不過交換了一下加數位置而已,功能與上一組是完全一樣的。

        前面我們介紹了一種分配內存的新方法:利用new運算符。new運算符分配的內存除了沒有變量分配時附帶有的變量名外,它與變量分配還有一個重要的區別:new運算符是在堆(heap)中分配空間,而通常的變量定義是在棧(stack)上分配內存。
       
        堆和棧是程序內存的兩大部分,初學可以不必細究其異同,有一點需要明白的是,在棧上分配的內存系統會自動地為其釋放,例如在函數結束時,局部變量將不復存在,就是系統自動清除棧內存的結果。但堆中分配的內存則不然:一切由你負責,即使你退出了new表達式的所處的函數或者作用域,那塊內存還處于被使用狀態而不能再利用。好處就是如果你想在不同模塊中共享內存,那么這一點正合你意,壞處是如果你不打算再利用這塊內存又忘了把它釋放掉,那么它就會霸占你寶貴的內存資源直到你的程序退出為止。

        如何釋放掉new分配的堆內存?答案是使用delete算符。delete的大概是C++中簡單的部分之一(但也很容易粗心犯錯!),你只要分清楚你要釋放的是單個單元的內存,還是多個單元的內存,假如:

      int *p = new int; // 這里把分配語句與初始化放在一起,效果和前面是一樣的

      … // 使用*p

      delete p; // 釋放p所指的內存,即用new分配的內存

        如果是多個單元的,則應該是這樣:

      int *p = new int[18];

      … // 使用

      delete[] p; // 注意,由于p指向的是一塊內存,所以delete后要加“[]”

      // 以確保整塊內存都被釋放,沒有“[]”只會釋放p指的塊內存

        剛才我們是在堆中分配連續內存,同樣,在棧上也可以分配邊續內存,例如我們同樣要分配18個單元的整型內存空間,并將首地址賦予指針a,則定義如下:

      int a[18];

        類似于前面用new的版本,系統會在棧上分配18個整型內存單元,并將首地址賦予指針a,我們同樣可以通過“[ ]”操作符或者古老的“*(… + …)”來實現對它的訪問。需要注意的是a是一個指向整型的指針常量類型,不可以再對a賦值使其指向其它變量。同樣,由于是在棧中分配內存,釋放工作也不必由我們操心。由于a“看起來”包含了許多個相同類型的變量,因此C++將其稱為數組。

        由上面看來,棧分配的數組似乎比堆分配要簡單好用,但棧分配有一個缺點,就是必須在編譯時刻確定內存的大小,也就是說,假如我要寫一個排序程序,每次參加排序的元素個數都不一樣,但我不能寫

      int number;

      cin >> number;

      int a[number]; // 錯誤,number是變量,而作為棧上分數空間的數組a的大小必須在編譯時就決定

        但我可以寫

      int number;

      cin >> number;

      int *a = new int[number]; // 沒有問題,堆空間分配可以在程序運行時才確定

        當然別忘了釋放就成了:

      delete[] a;

        由于堆內存的分配比棧內存具有更大的靈活性,可以在程序執行期動態決定分配空間的大小,所以又稱為動態內存。

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 亚洲国产原创AV在线播放 | 精品国产欧美日韩在线不卡 | 亚洲国产在人线播放午夜免费 | 最新国产精品精品在线看 | 日韩欧美国产另类 | 亚洲乱码一区AV春药高潮 |