C++學習——指針詳解大全,帶你揭開層層迷霧

最近因工做要求要來學習C++了,在這裏作一下學習的記錄程序員

對於C語言而言,最重要的就是指針了,它是C語言的特點也是難點之一,固然,做爲繼承者C++也是如此編程

指針數組

int * p_updates;

這代表,* p_updates的類型爲int。因爲*運算符被用於指針,所以p_updates變量自己必須是指針。咱們說p_updates指向int類型,咱們還說p_updates的類型是指向int的指針,或int*。能夠這樣說,p_updates是指針(地址),而*p_updates是int,而不是指針.安全

 

 

 指針的危險函數

危險更易發生在那些使用指針不仔細的人身上。極其重要的一點是:在C++中建立指針時,計算機將分配用來存儲地址的內存,但不會分配用來存儲指針所指向的數據的內存。爲數據提供空間是一個獨立的步驟,忽略這一步無疑是自找麻煩.學習

long * fellow;
*fellow = 223323;

fellow確實是一個指針,但它指向哪裏呢?上述代碼沒有將地址賦給fellow。那麼223323將被放在哪裏呢?咱們不知道。因爲fellow沒有被初始化,它可能有任何值。無論值是什麼,程序都將它解釋爲存儲223323的地址。若是fellow的值碰巧爲1200,計算機將把數據放在地址1200上,即便這恰巧是程序代碼的地址。fellow指向的地方極可能並非所要存儲223323的地方。這種錯誤可能會致使一些最隱匿、最難以跟蹤的bug。this

指針和數字spa

指針不是整型,雖然計算機一般把地址看成整數來處理。從概念上看,指針與整數是大相徑庭的類型。整數是能夠執行加、減、除等運算的數字,而指針描述的是位置,將兩個地址相乘沒有任何意義。從能夠對整數和指針執行的操做上看,它們也是彼此不一樣的。所以,不能簡單地將整數賦給指針:指針

int *pt;
pt = 0xB8000000; //type mismatch

在這裏,左邊是指向int的指針,所以能夠把它賦給地址,但右邊是一個整數。您可能知道,0xB8000000是老式計算機系統中視頻內存的組合段偏移地址,但這條語句並無告訴程序,這個數字就是一個地址。在C99標準發佈以前,C語言容許這樣賦值。但C++在類型一致方面的要求更嚴格,編譯器將顯示一條錯誤消息,通告類型不匹配。要將數字值做爲地址來使用,應經過強制類型轉換將數字轉換爲適當的地址類型:code

int *pt;
pt = (int *)0xB8000000;

這樣,賦值語句的兩邊都是整數的地址,所以這樣賦值有效。注意,pt是int值的地址並不意味着pt自己的類型是int。例如,在有些平臺中,int類型是個2字節值,而地址是個4字節值。

使用new來分配內存

對指針的工做方式有必定了解後,來看看它如何實如今程序運行時分配內存。前面咱們都將指針初始化爲變量的地址;變量是在編譯時分配的有名稱的內存,而指針只是爲能夠經過名稱直接訪問的內存提供了一個別名。指針真正的用武之地在於,在運行階段分配未命名的內存以存儲值。在這種狀況下,只能經過指針來訪問內存。在C語言中,能夠用庫函數malloc( )來分配內存;在C++中仍然能夠這樣作,但C++還有更好的方法—new運算符。

下面來試試這種新技術,在運行階段爲一個int值分配未命名的內存,並使用指針來訪問這個值。這裏的關鍵所在是C++的new運算符。程序員要告訴new,須要爲哪一種數據類型分配內存;new將找到一個長度正確的內存塊,並返回該內存塊的地址。程序員的責任是將該地址賦給一個指針。下面是一個這樣的示例:

int * pn = new int;

new int告訴程序,須要適合存儲int的內存。new運算符根據類型來肯定須要多少字節的內存。而後,它找到這樣的內存,並返回其地址。接下來,將地址賦給pn,pn是被聲明爲指向int的指針。如今,pn是地址,而*pn是存儲在那裏的值。將這種方法與將變量的地址賦給指針進行比較:

int higgens;
int *pt = &higgens;

在這兩種狀況(pn和pt)下,都是將一個int變量的地址賦給了指針。在第二種狀況下,能夠經過名稱higgens來訪問該int,在第一種狀況下,則只能經過該指針進行訪問。這引出了一個問題:pn指向的內存沒有名稱,如何稱呼它呢?咱們說pn指向一個數據對象,這裏的「對象」不是「面向對象編程」中的對象,而是同樣「東西」。術語「數據對象」比「變量」更通用,它指的是爲數據項分配的內存塊。所以,變量也是數據對象,但pn指向的內存不是變量。乍一看,處理數據對象的指針方法可能不太好用,但它使程序在管理內存方面有更大的控制權。

爲一個數據對象(能夠是結構,也能夠是基本類型)得到並指定分配內存的通用格式以下:

typeName * pointer_name = new typeName;(如: double * p = new double)

使用delete釋放內存

當須要內存時,可使用new來請求,這只是C++內存管理數據包中有魅力的一個方面。另外一個方面是delete運算符,它使得在使用完內存後,可以將其歸還給內存池,這是通向最有效地使用內存的關鍵一步。歸還或釋放(free)的內存可供程序的其餘部分使用。使用delete時,後面要加上指向內存塊的指針(這些內存塊最初是用new分配的):

int *ps = new int;
delete ps;

這將釋放ps指向的內存,但不會刪除指針ps自己。例如,能夠將ps從新指向另外一個新分配的內存塊。必定要配對地使用new和delete;不然將發生內存泄漏(memory leak),也就是說,被分配的內存再也沒法使用了。若是內存泄漏嚴重,則程序將因爲不斷尋找更多內存而終止。

不要嘗試釋放已經釋放的內存塊,C++標準指出,這樣作的結果將是不肯定的,這意味着什麼狀況均可能發生。

另外,不能使用delete來釋放聲明變量所得到的內存,以下:

int jugs = 5;
int *pi = &jugs;
delete pi; //錯誤..內存不是經過new來進行分配的

意思就是 new delete是要同對出現的。

使用delete的關鍵在於,將它用於new分配的內存。這並不意味着要用於new的指針,而是用於new的地址

int *ps = new int;
int *pq = ps;
delete pq;

通常來講,不要建立兩個指向同一個內存塊的指針,由於這將增長錯誤地刪除同一個內存塊兩次的可能性。但稍後您會看到,對於返回指針的函數,使用另外一個指針確實有道理。

使用new來建立動態數組

若是程序只須要一個值,則可能會聲明一個簡單變量,由於對於管理一個小型數據對象來講,這樣作比使用new和指針更簡單,儘管給人留下的印象不那麼深入。一般,對於大型數據(如數組、字符串和結構),應使用new,這正是new的用武之地。例如,假設要編寫一個程序,它是否須要數組取決於運行時用戶提供的信息。若是經過聲明來建立數組,則在程序被編譯時將爲它分配內存空間。無論程序最終是否使用數組,數組都在那裏,它佔用了內存。在編譯時給數組分配內存被稱爲靜態聯編(static binding),意味着數組是在編譯時加入到程序中的。但使用new時,若是在運行階段須要數組,則建立它;若是不須要,則不建立。還能夠在程序運行時選擇數組的長度。這被稱爲動態聯編(dynamic binding),意味着數組是在程序運行時建立的。這種數組叫做動態數組(dynamic array)。使用靜態聯編時,必須在編寫程序時指定數組的長度;使用動態聯編時,程序將在運行時肯定數組的長度。

 

在C++中,建立動態數組很容易;只要將數組的元素類型和元素數目告訴new便可。必須在類型名後加上方括號,其中包含元素數目。例如,要建立一個包含10個int元素的數組,能夠這樣作:

int *psome = new int[10];

new運算符返回第一個元素的地址。在這個例子中,該地址被賦給指針psome。

當程序使用完new分配的內存塊時,應使用delete釋放它們。然而,對於使用new建立的數組,應使用另外一種格式的delete來釋放:

delete [] psome;

方括號告訴程序,應釋放整個數組,而不只僅是指針指向的元素。請注意delete和指針之間的方括號。若是使用new時,不帶方括號,則使用delete時,也不該帶方括號。若是使用new時帶方括號,則使用delete時也應帶方括號。C++的早期版本沒法識別方括號表示法。然而,對於ANSI/ISO標準來講,new與delete的格式不匹配致使的後果是不肯定的,這意味着程序員不能依賴於某種特定的行爲。下面是一個例子:

int * pt = new int;
short * ps = new short[500];
delete [] pt;   //effect is undefined, don't do this
delete ps;      //effect is undefined, don't do this

總之,使用new和delete時,應遵照如下規則。

  • 不要使用delete來釋放不是new分配的內存。
  • 不要使用delete釋放同一個內存塊兩次。
  • 若是使用new [ ]爲數組分配內存,則應使用delete [ ]來釋放。
  • 若是使用new [ ]爲一個實體分配內存,則應使用delete(沒有方括號)來釋放。
  • 對空指針應用delete是安全的。

下面的語句建立指針psome,它指向包含10個int值的內存塊中的第1個元素:

int * p = new int[10];

能夠將它看做是一根指向該元素的手指。假設int佔4個字節,則將手指沿正確的方向移動4個字節,手指將指向第2個元素。總共有10個元素,這就是手指的移動範圍。所以,new語句提供了識別內存塊中每一個元素所需的所有信息。

如今從實際角度考慮這個問題。如何訪問其中的元素呢?第一個元素不成問題。因爲psome指向數組的第1個元素,所以*psome是第1個元素的值。這樣,還有9個元素。若是沒有使用過C語言,下面這種最簡單的方法可能會令您大吃一驚:只要把指針看成數組名使用便可。也就是說,對於第1個元素,可使用psome[0],而不是*psome;對於第2個元素,可使用psome[1],依此類推。這樣,使用指針來訪問動態數組就很是簡單了,雖然還不知道爲什麼這種方法管用。能夠這樣作的緣由是,C和C++內部都使用指針來處理數組。數組和指針基本等價是C和C++的優勢之一(這在有時候也是個問題,但這是另外一碼事)。稍後將更詳細地介紹這種等同性

int main() {
    double* p3 = new double[3];
    p3[0] = 0.2;
    p3[1] = 0.5;
    p3[2] = 0.8;
    cout << "p3[1] is " << p3[1] << endl;
    p3 = p3 + 1;
    cout << "now p3[0] is " << p3[0] << endl;
    cout << "p3[1] is " << p3[1] << endl;
    p3 = p3 - 1;
    delete[] p3;
    return 0;
}

下面是該程序的輸出:

p3[1] is 0.5
now p3[0] is 0.5
p3[1] is 0.8

從中可知,指針p3看成數組名來使用,p3[0]爲第1個元素,依次類推。下面的代碼行指出了數組名和指針之間的根本差異:

 p3 = p3+1; 

不能修改數組名的值。但指針是變量,所以能夠修改它的值。請注意將p3加1的效果。表達式p3[0]如今指的是數組的第2個值。所以,將p3加1致使它指向第2個元素而不是第1個。將它減1後,指針將指向原來的值,這樣程序即可以給delete[ ]提供正確的地址。

相鄰的int地址一般相差2個字節或4個字節,而將p3加1後,它將指向下一個元素的地址,這代表指針算術有一些特別的地方。狀況確實如此。

指針、數組和指針算術

指針和數組基本等價的緣由在於指針算術(pointer arithmetic)和C++內部處理數組的方式。首先,咱們來看一看算術。將整數變量加1後,其值將增長1;但將指針變量加1後,增長的量等於它指向的類型的字節數。將指向double的指針加1後,若是系統對double使用8個字節存儲,則數值將增長8;將指向short的指針加1後,若是系統對short使用2個字節存儲,則指針值將增長2。如下程序演示了這種使人吃驚的現象,它還說明了另外一點:C++將數組名解釋爲地址

int main() {
    double wages[3] = { 10000.0,20000.0,30000.0 };
    short stacks[3] = { 3,2,1 };
    //獲取數組地址的兩種方式
    double* pw = wages; //數組名即爲地址
    short* ps = &stacks[0];
    cout << "pw= " << pw << ", *pw = " << *pw << endl;
    pw = pw + 1;
    cout << "pw指針加1:\n";
    cout << "pw= " << pw << ", *pw = " << *pw << endl;
    cout << endl;

    cout << "ps= " << ps << ", *ps = " << *ps << endl;
    ps = ps + 1;
    cout << "ps指針加1:\n";
    cout << "ps= " << ps << ", *ps = " << *ps << endl;
    cout << endl;

    cout << "使用數組表示法訪問兩個元素" << endl;
    cout << "stack[0] = " << stacks[0] << ", stack[1] = " << stacks[1] << endl;
    cout << "使用指針表示法訪問兩個元素" << endl;
    cout << "*stack = " << *stacks << ", *(stack+1) = " << *(stacks+1) << endl;

    cout << "wages數組的大小爲:" << sizeof(wages) << endl;
    cout << "pw指針的大小爲:" << sizeof(pw) << endl;
}

下面是該程序的輸出:

pw= 00EFFE80, *pw = 10000
pw指針加1:
pw= 00EFFE88, *pw = 20000

ps= 00EFFE70, *ps = 3
ps指針加1:
ps= 00EFFE72, *ps = 2

使用數組表示法訪問兩個元素
stack[0] = 3, stack[1] = 2
使用指針表示法訪問兩個元素
*stack = 3, *(stack+1) = 2
wages數組的大小爲:24
pw指針的大小爲:4

在多數狀況下,C++將數組名解釋爲數組第1個元素的地址。所以,下面的語句將pw聲明爲指向double類型的指針,而後將它初始化爲wages—wages數組中第1個元素的地址:

double * pw =wages;
//和全部數組同樣,wages也存在下面的等式
wages = &wages[0] = 數組第一個元素的地址

爲代表狀況確實如此,該程序在表達式&stacks[0]中顯式地使用地址運算符來將ps指針初始化爲stacks數組的第1個元素。

接下來,程序查看pw和*pw的值。前者是地址,後者是存儲在該地址中的值。因爲pw指向第1個元素,所以*pw顯示的值爲第1個元素的值,即10000。接着,程序將pw加1。正如前面指出的,這樣數字地址值將增長8,這使得pw的值爲第2個元素的地址。所以,*pw如今的值是20000—第2個元素的值(參見下圖,爲使改圖更爲清晰,對其中的地址值作了調整)。

 

 此後,程序對ps執行相同的操做。這一次因爲ps指向的是shor t類型,而short佔用2個字節,所以將指針加1時,其值將增長2。結果是,指針也指向數組中下一個元素。

如今來看一看數組表達式stacks[1]。C++編譯器將該表達式看做是*(stacks + 1),這意味着先計算數組第2個元素的地址,而後找到存儲在那裏的值。最後的結果即是stacks [1]的含義(運算符優先級要求使用括號,若是不使用括號,將給*stacks加1,而不是給stacks加1)。

 

不少狀況下,能夠相同的方式使用指針名和數組名。對於它們,可使用數組方括號表示法,也可使用解除引用運算符(*)。在多數表達式中,它們都表示地址。區別之一是,能夠修改指針的值,而數組名是常量。

數組的地址

對數組取地址時,數組名也不會被解釋爲其地址。等等,數組名難道不被解釋爲數組的地址嗎?不徹底如此:數組名被解釋爲其第一個元素的地址,而對數組名應用地址運算符時,獲得的是整個數組的地址:

    short tell[10];
    cout << tell << endl; //顯示 &tell[0];
    cout << &tell << endl; //顯示整個數組的地址

從數字上說,這兩個地址相同;但從概念上說,&tell[0](即tell)是一個2字節內存塊的地址,而&tell是一個20字節內存塊的地址。所以,表達式tell + 1將地址值加2,而表達式&tell + 2將地址加20。換句話說,tell是一個short指針(* short),而&tell是一個這樣的指針,即指向包含20個元素的short數組(short (*) [20])。

您可能會問,前面有關&tell的類型描述是如何來的呢?首先,您能夠這樣聲明和初始化這種指針:

 short (*pas)[20] = &tell 

若是省略括號,優先級規則將使得pas先與[20]結合,致使pas是一個short指針數組,它包含20個元素,所以括號是必不可少的。其次,若是要描述變量的類型,可將聲明中的變量名刪除。所以,pas的類型爲short (*) [20]。另外,因爲pas被設置爲&tell,所以*pas與tell等價,因此(*pas) [0]爲tell數組的第一個元素。

 

總之,使用new來建立數組以及使用指針來訪問不一樣的元素很簡單。只要把指針看成數組名對待便可。然而,要理解爲什麼能夠這樣作,將是一種挑戰。要想真正瞭解數組和指針,應認真複習它們的相互關係。

總結

1.聲明指針

要聲明指向特定類型的指針,請使用下面的格式:

 typeName * pointerName //實例 double * pn; char *pc; 

其中,pn和pc都是指針,而double *和char *是指向double的指針和指向char的指針。

2.給指針賦值

應將內存地址賦給指針。能夠對變量名應用&運算符,來得到被命名的內存的地址,new運算符返回未命名的內存的地址。

double *pn;
double *pa;
char *pc;
double bubble = 3.2;
pn = &bubble;
pc = new char;
pa = new double[30];

3.對指針解除引用

對指針解除引用意味着得到指針指向的值。對指針應用解除引用或間接值運算符(*)來解除引用。所以,若是像上面的例子中那樣,pn是指向bubble的指針,則*pn是指向的值,即3.2。

 cout<< *pn; *pc='S' 

另外一種對指針解除引用的方法是使用數組表示法,例如,pn[0]與*pn是同樣的。決不要對未被初始化爲適當地址的指針解除引用。

4.區分指針和指針所指向的值

若是pt是指向int的指針,則*pt不是指向int的指針,而是徹底等同於一個int類型的變量。pt纔是指針。

 int *pt = new int; *pt =5; 

5.數組名

在多數狀況下,C++將數組名視爲數組的第一個元素的地址。

 int tacos[10]; 

一種例外狀況是,將sizeof運算符用於數組名用時,此時將返回整個數組的長度(單位爲字節)。

6.指針算術

C++容許將指針和整數相加。加1的結果等於原來的地址值加上指向的對象佔用的總字節數。還能夠將一個指針減去另外一個指針,得到兩個指針的差。後一種運算將獲得一個整數,僅當兩個指針指向同一個數組(也能夠指向超出結尾的一個位置)時,這種運算纔有意義;這將獲得兩個元素的間隔

int main() {
    int tacos[10] = {5,2,8,4,1,2,2,4,6,8}; 
    int* pt = tacos;            //假設tacos的起始地址爲3000
    pt = pt + 1;                //若是int佔4字節,此時就是pt = 3004
    int* pe = &tacos[9];        //pe爲3036
    pe = pe - 1;                //pe爲3032
    int diff = pe - pt;         //diff = 7,tacos[8]與tacos[1]的間隔
    cout << pt << endl;
    cout << pe << endl;

    cout << diff << endl;
}

7.數組的動態聯編和靜態聯編

使用數組聲明來建立數組時,將採用靜態聯編,即數組的長度在編譯時設置:

 int tacos[10]; 

使用new[ ]運算符建立數組時,將採用動態聯編(動態數組),即將在運行時爲數組分配空間,其長度也將在運行時設置。使用完這種數組後,應使用delete [ ]釋放其佔用的內存:

int * pz = new int[10];
delete [] pz;

8.數組表示法和指針表示法

使用方括號數組表示法等同於對指針解除引用:

  tacos[0]=*tacos=tacos地址的值 

  tacos[3]=*(tacos+3)=tacos+3地址的值 

數組名和指針變量都是如此,所以對於指針和數組名,既可使用指針表示法,也可使用數組表示法。

拓展部分

 指針和字符串

數組和指針的特殊關係能夠擴展到C-風格字符串。請看下面的代碼:

    char flower[10] = "rose";
    cout << flower << endl;

輸出結果:

rose

數組名是第一個元素的地址,所以cout語句中的flower是包含字符r的char元素的地址。cout對象認爲char的地址是字符串的地址,所以它打印該地址處的字符,而後繼續打印後面的字符,直到遇到空字符(\0)爲止。總之,若是給cout提供一個字符的地址,則它將從該字符開始打印,直到遇到空字符爲止

這裏的關鍵不在於flower是數組名,而在於flower是一個char的地址。這意味着能夠將指向char的指針變量做爲cout的參數,由於它也是char的地址。固然,該指針指向字符串的開頭,稍後將覈實這一點。

前面的cout語句中最後一部分的狀況如何呢?若是flower是字符串第一個字符的地址,則表達式「s are red\n」是什麼呢?爲了與cout對字符串輸出的處理保持一致,這個用引號括起的字符串也應當是一個地址。在C++中,用引號括起的字符串像數組名同樣,也是第一個元素的地址。上述代碼不會將整個字符串發送給cout,而只是發送該字符串的地址。這意味着對於數組中的字符串、用引號括起的字符串常量以及指針所描述的字符串,處理的方式是同樣的,都將傳遞它們的地址。與逐個傳遞字符串中的全部字符相比,這樣作的工做量確實要少。

 

如下例子演示瞭如何使用不一樣形式的字符串。它使用了兩個字符串庫中的函數。函數strlen( )咱們之前用過,它返回字符串的長度。函數strcpy( )將字符串從一個位置複製到另外一個位置。這兩個函數的原型都位於頭文件cstring(在不太新的實現中,爲string.h)中。該程序還經過註釋指出了應儘可能避免的錯誤使用指針的方式。

int main() {
    char animal[20] = "bear";
    const char* bird = "wren";
    char* ps;

    cout << animal << "and";
    cout << bird << endl;

    cout << "Enter a kind of animal:";
    cin >> animal;

    ps = animal;
    cout << ps << "!\n";
    cout << "Before using strcpy():" << endl;
    cout << animal << " at " << (int*)animal << endl;
    cout << ps << " at " << (int*)ps << endl;

    ps = new char[strlen(animal) + 1];
    strcpy(ps, animal);
    cout << "After using strcpy():" << endl;
    cout << animal << " at " << (int*)animal << endl;
    cout << ps << " at " << (int*)ps << endl;
    delete[] ps;
    return 0;
}

下面是程序運行狀況:

 

 

程序分析:

 程序建立了一個char數組(animal)和兩個指向char的指針變量(bird和ps)。該程序首先將animal數組初始化爲字符串「bear」,就像初始化數組同樣。而後,程序執行了一些新的操做,將char指針初始化爲指向一個字符串:

 const char* bird = "wren"; 

記住,「wren」實際表示的是字符串的地址,所以這條語句將「wren」的地址賦給了bird指針。(通常來講,編譯器在內存留出一些空間,以存儲程序源代碼中全部用引號括起的字符串,並將每一個被存儲的字符串與其地址關聯起來。)這意味着能夠像使用字符串「wren」那樣使用指針bird,以下面的示例所示:

 cout<<"A concerned"<<bird<<"speaks"; 

字符串字面值是常量,這就是爲何代碼在聲明中使用關鍵字const的緣由。以這種方式使用const意味着能夠用bird來訪問字符串,但不能修改它。最後,指針ps未被初始化,所以不指向任何字符串(正如您知道的,這一般是個壞主意,這裏也不例外)

接下來,程序說明了這樣一點,即對於cout來講,使用數組名animal和指針bird是同樣的。畢竟,它們都是字符串的地址,cout將顯示存儲在這兩個地址上的兩個字符串(「bear」和「wren」)。若是激活錯誤地顯示ps的代碼,則將可能顯示一個空行、一堆亂碼,或者程序將崩潰。建立未初始化的指針有點像簽發空頭支票:沒法控制它將被如何使用。

對於輸入,狀況有點不一樣。只要輸入比較短,可以被存儲在數組中,則使用數組animal進行輸入將是安全的。然而,使用bird來進行輸入並不合適:

  • 有些編譯器將字符串字面值視爲只讀常量,若是試圖修改它們,將致使運行階段錯誤。在C++中,字符串字面值都將被視爲常量,但並非全部的編譯器都對之前的行爲作了這樣的修改。
  • 有些編譯器只使用字符串字面值的一個副原本表示程序中全部的該字面值。

下面討論一下第二點。C++不能保證字符串字面值被惟一地存儲。也就是說,若是在程序中屢次使用了字符串字面值「wren」,則編譯器將可能存儲該字符串的多個副本,也可能只存儲一個副本。若是是後面一種狀況,則將bird設置爲指向一個「wren」,將使它只是指向該字符串的惟一一個副本。將值讀入一個字符串可能會影響被認爲是獨立的、位於其餘地方的字符串。不管如何,因爲bird指針被聲明爲const,所以編譯器將禁止改變bird指向的位置中的內容。

試圖將信息讀入ps指向的位置將更糟。因爲ps沒有被初始化,所以並不知道信息將被存儲在哪裏,這甚至可能改寫內存中的信息。幸運的是,要避免這種問題很容易—只要使用足夠大的char數組來接收輸入便可。請不要使用字符串常量或未被初始化的指針來接收輸入。爲避免這些問題,也可使用std::string對象,而不是數組。

接下來,請注意下述代碼完成的工做:

    ps = animal;
    //...
    cout << animal << " at " << (int *)animal << endl;
    cout << ps << " at " << (int*)ps << endl;        

它將生成下面的輸出:

fox at 0049FAA4
fox at 0049FAA4

通常來講,若是給cout提供一個指針,它將打印地址。但若是指針的類型爲char *,則cout將顯示指向的字符串。若是要顯示的是字符串的地址,則必須將這種指針強制轉換爲另外一種指針類型,如int *(上面的代碼就是這樣作的)。所以,ps顯示爲字符串「fox」,而(int *)ps顯示爲該字符串的地址。注意,將animal賦給ps並不會複製字符串,而只是複製地址。這樣,這兩個指針將指向相同的內存單元和字符串

要得到字符串的副本,還須要作其餘工做。首先,須要分配內存來存儲該字符串,這能夠經過聲明另外一個數組或使用new來完成。後一種方法使得可以根據字符串的長度來指定所需的空間:

 ps = new char[strlen(animal) + 1]; 

字符串「fox」不能填滿整個animal數組,所以這樣作浪費了空間。上述代碼使用strlen( )來肯定字符串的長度,並將它加1來得到包含空字符時該字符串的長度。隨後,程序使用new來分配恰好足夠存儲該字符串的空間。

接下來,須要將animal數組中的字符串複製到新分配的空間中。將animal賦給ps是不可行的,由於這樣只能修改存儲在ps中的地址,從而失去程序訪問新分配內存的惟一途徑。須要使用庫函數strcpy( ):

 strcpy(ps, animal); 

strcpy( )函數接受2個參數。第一個是目標地址,第二個是要複製的字符串的地址。您應肯定,分配了目標空間,並有足夠的空間來存儲副本。在這裏,咱們用strlen( )來肯定所需的空間,並使用new得到可用的內存。

經過使用strcpy( )和new,將得到「fox」的兩個獨立副本:

fox at 0049FAA4
fox at 004301c8
相關文章
相關標籤/搜索