国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

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

2019-11-17 05:45:23
字體:
供稿:網(wǎng)友
       
要害字     C++ 入門 指針 數(shù)組 動態(tài)內(nèi)存 
  
.形形色色的指針

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

1)指向指針的指針

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

int *pInt;

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

int **pIntPtr;

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

pIntPtr = &pInt;

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

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

2)指針與常量

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

const int ClassNumber = 18;

      則我們可以通過&ClassNumber表達式得到常量ClassNumber的地址(不是常數(shù)18的地址!)。其實在存儲特點上常量與變量基本是一樣的(有對應(yīng)的地址,并且在對應(yīng)地址上存有相應(yīng)的值),我們可以把常量看作一種“受限”的變量:只可讀不可寫。既然它們?nèi)绱讼嗨疲兞坑袑?yīng)的指針,那么常量也應(yīng)該有其對應(yīng)的指針。比如,一個指向int型常量的指針pConstInt定義如下:

const int *pConstInt;

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

pConstInt = &ClassNumber;

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

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

int * const p = &a;

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

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

const int * const p = &ClassNumber;

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

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

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

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

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

const (int*) p;

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

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

const unsigned int ClassNumber = 18;

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

typedef const unsigned int ConstUInt;

這樣我們只須寫

ConstUInt ClassNumber = 18;

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

typedef int * IntPtr;

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

int* p1, p2;

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

int (*p1), p2;

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

IntPtr p1, p2;

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

const IntPtr p;

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

(const)  (int *) p;

而不是前面的

(const int)  (*p);

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

BTW:當(dāng)初第一次看到的時候,我也是毫不猶豫,可惜答錯了^_^

3.指針、動態(tài)內(nèi)存、數(shù)組

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

變量定義可以看作兩個功能的實現(xiàn):1.分配內(nèi)存;2.將內(nèi)存與變量名聯(lián)系起來。

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

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

int *p;

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

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

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

int *p;

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

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

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

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

要打印的時候就寫:

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

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

int *myPtr;

myPtr = p + 1;       // OK,現(xiàn)在myPtr就指向第二內(nèi)存單元的地址

也可以進行自加操作:

myPtr++;       // 按上面的初值,自加后myPtr已經(jīng)指向第三內(nèi)存單元的地址

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

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

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

      前面我們介紹了一種分配內(nèi)存的新方法:利用new運算符。new運算符分配的內(nèi)存除了沒有變量分配時附帶有的變量名外,它與變量分配還有一個重要的區(qū)別:new運算符是在堆(heap)中分配空間,而通常的變量定義是在棧(stack)上分配內(nèi)存。

      堆和棧是程序內(nèi)存的兩大部分,初學(xué)可以不必細究其異同,有一點需要明白的是,在棧上分配的內(nèi)存系統(tǒng)會自動地為其釋放,例如在函數(shù)結(jié)束時,局部變量將不復(fù)存在,就是系統(tǒng)自動清除棧內(nèi)存的結(jié)果。但堆中分配的內(nèi)存則不然:一切由你負責(zé),即使你退出了new表達式的所處的函數(shù)或者作用域,那塊內(nèi)存還處于被使用狀態(tài)而不能再利用。好處就是假如你想在不同模塊中共享內(nèi)存,那么這一點正合你意,壞處是假如你不打算再利用這塊內(nèi)存又忘了把它釋放掉,那么它就會霸占你寶貴的內(nèi)存資源直到你的程序退出為止。

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

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

…  // 使用*p

delete p;     // 釋放p所指的內(nèi)存,即用new分配的內(nèi)存

假如是多個單元的,則應(yīng)該是這樣:

int *p = new int[18];

… // 使用

delete[] p;    // 注重,由于p指向的是一塊內(nèi)存,所以delete后要加“[]”

// 以確保整塊內(nèi)存都被釋放,沒有“[]”只會釋放p指的第一塊內(nèi)存

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

int a[18];

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

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

int number;

cin >> number;

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

但我可以寫

int number;

cin >> number;

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

當(dāng)然最后別忘了釋放就成了:

delete[] a;

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



發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 历史| 商南县| 双桥区| 右玉县| 武平县| 天津市| 诸城市| 沙雅县| 繁峙县| 东山县| 南平市| 英超| 玛曲县| 遵义市| 屏山县| 库伦旗| 和田市| 阿拉尔市| 阿坝县| 大庆市| 布拖县| 呼和浩特市| 富民县| 新平| 铅山县| 肇庆市| 布拖县| 天等县| 松滋市| 远安县| 海盐县| 靖西县| 济源市| 密云县| 鹤山市| 化德县| 麦盖提县| 安泽县| 土默特左旗| 纳雍县| 迁西县|