1.4 指针
1. 指針是一個變量, 存儲的是值的地址, 而不是值本身. 獲取常規變量的地址, 我們通常使用地址操作符&就可以獲得它的地址, 例如home是一個變量, 那么&home是它的地址.
2. 面向對象編程與傳統的過程性編程的區別在于, OOP強調的是在運行階段(而不是編譯階段)進行決策. 運行階段指程序正在運行時, 編譯階段指編譯器將程序組合起來時. 運行階段決策就好比度假時選擇那些景點取決于天氣和當時的心情; 而編譯階段決策更像不管在什么條件下, 都堅持設定的日程安排. 運行階段決策能夠更靈活, 例如確定數組的長度這件事情, C++使用關鍵字new請求正確數量的內存以及使用指針來跟蹤新分配的內存的位置, 這樣能避免內存分配不足和浪費內存空間的情況發生.
3. 指針存儲的是地址,*操作符稱為間接值或解除引言操作符,將其應用于指針,可以得到該地址處存儲的值。例如manly是一個指針,則manly表示的是一個地址,而*manly表示村粗在該地址的值。我們可以理解為*作用于地址,結果是該地址處存儲的值。例如一個變量home,&home是它的地址,而*(&home)是存儲在該地址的值,也就是home,記為home=*(&home)。
4. 聲明和初始化指針。
??? int *p1;
??? 表明*p1的類型是int,由于*操作符被用于指針,因此p1變量本身必須是指針,我們稱p1的類型是指向int的指針,或int*。注意下面de int* p1,p2;將創建一個指針p1和一個int型變量p2,若要聲明兩個指針變量,則為int* p1,*p2。
5. 指針的危險。C++在創建指針時,計算機將分配用來存儲地址的內存,但是不會分配用來存儲指針所指向數據的內存。下面一段代碼是危險的;int* fellow; *fellow=333; fellow是一個指針,但是程序并沒有說明它指向哪里,那么我們也不知道333被存儲到了哪里。這是指針最危險的地方。因此,定義指針后,一定要將其初始化為一個確定的、適當的地址或者為其分配一段明確的內存(如后面的例子一樣,數組指針),這是關于使用指針的金科玉律。比如int* p;p=(int*)malloc(4*sizeof(int));這里p是一個數組指針,指向了含有4個int類型大小的空間的首地址,p也就相當于數組的首地址,p是一個含有4個整型數據的數組。
6. 如果有已命名的內存,那么可以通過內存名來訪問內存,指針的用處對于這種情況也就不太大了。指針的真正用武之地在于,在運行階段分配未命名的內存以存儲值,在這種情況下,只能通過指針來訪問內存。
7. 使用new分配內存。在運行階段為一個int值分配未命名的內存,并且使用指針來訪問這個值,使用new操作符,程序員要告訴new操作符需要為哪種數據類型分配內存,new將找到一個長度正確的內存塊,并返回該內存塊的地址,程序員的責任是將該地址賦給一個指針。例如,int* pn=new int;new int告訴程序,需要適合存儲int的內存,new操作符根據數據類型來確定需要多少字節的內存,然后它找到這樣的內存,并返回其地址。接下來,降低至賦值給pn。pn指向的是一個數據對象。為一個數據對象(可以是基本類型和結構體)獲得并指定分配內存的通用格式如下,typeName pointer_name=new typeName;當然,如果已經聲明了一個指針,可以直接為它賦值,例如有int* p;p=new int。
8. 內存被耗盡。當計算機沒有足夠的內存滿足new的請求的時候,new將返回0。那么0將會賦給指針,C++中值為0的指針被稱為空指針,C++確保空指針不會指向任何有效的數據,因此空指針常被用來表示操作符或者函數失敗。
9. 使用delete來釋放內存, 不過delete只能釋放new分配的內存. delete后面加上指向由new分配的內存塊的指針. 例如
??? int *ps=new int;
??? ps[0]=0;ps[1]=1;ps[2]=2;
??? delete ps; //最后一句將釋放ps指向的內存塊, 但是不會刪除ps指針本身,?還可以重新指向另一個新分配的內存塊, 例如下面的句子
??? int *ps=new int[5];
??? ... ...
??? delete ps;
????操作符new和delete一定要配對的使用, 否則會發生內存泄露, 比如對指針ps使用了new, 再使用delete,?再使用了delete, 第一個delete已經將ps指向的內存塊釋放了, 但是沒有刪除指針本身, 刪除ps原型指向的內存塊后, ps的指向我們就無法得知了, 接著再一個delete就會刪除未知位置的數據, 這樣非常不安全. 所以操作符new和delete一定要配對使用. 如果只是用new而不使用delete, 那么分配內存卻不釋放內存, 系統可用內存會越來越少, 久了就會內存不足了. 有特殊的情況, 對空指針使用delete是安全的.
10. 使用new創建動態數組. 動態數組就是程序在運行時給數組分配內存, 有時候程序是否需要數組是由程序在運行過程中用戶輸入的信息決定的, 需要用數組時才給分配內存, 而靜態數組是在編譯時給數組分配內存, 不管程序最終是否使用數組, 數組都在那里, 占用了內存. 在上述情況下, 使用動態數組明顯使得程序更靈活, 更能合理使用內存. 創建方式如下:int *psome=new int[10]; new操作符返回第一個元素的地址, 該地址被賦予指針psome, 也就是說psome[0]的地址被賦予了指針psome. 使用delete釋放一個數組, 格式為: delete [] psome, 方括號[]告訴程序, 應該釋放整個數組, 而不僅僅是指針指向的元素. 數組名即是數組第一個元素的地址, 與指針等值. 而int array[10]是一個靜態數組.
11. 使用動態數組. 先分配動態數組int *psome=new int[10]; psome指向數組的第一個元素, 因此*psome是第一個元素的值, 要訪問其余九個元素, 只要把指針當做數組名就可以了, 第一個元素可以使用psome[0], 第二個元素是psome[1], ... ,第十個元素是psome[9].
12. 指針和數組名的根本差別. 在使用動態數組的時候, 我們可以把指針當做數組名使用, 但是指針和數組名還是有差別的, 根本差別在于我們不能修改數組名的值, 數組名的值永遠是數組第一個元素的地址, 但是指針是變量, 可以修改它的值. psome指向第一個元素的值, psome+1指向第二個元素的值.
13. 指針,數組和指針算術. 指針和數組基本等價的原因在于指針算術和C++內部處理數組的方式. C++將數組名解釋為地址, 即為數組第一個元素的地址. 對于算術, 整數變量加1后, 其值將增加1, 但是指針變量加1后, 增加的量等于它指向的類型的字節數, 即指向了下一個此類型的數據. 例如將指向double的指針加1后, 如果系統對double使用8個字節存儲, 那么指針的數值將增加8. 指針變量加1后, 其增加的值等于指向的類型占用的字節數.
14. 編譯器對數組表示法的解釋. 數組表示法arrayname[i],??C++編譯器將其解釋為*(arrayname+i), 如果指針pointername指向數組arrayname, 那么也可以用指針名來表示數組pointername[i]等價于arrayname[i], 將被C++編譯器解釋為*(pointername+i).
15. 指針和數組的另一個區別. 對數組使用sizeof操作符得到的是數組的長度, 而對指針使用sizeof操作符得到的是指針的長度. 例如動態數組int array=new int[4]={1, 2, 3, 4};指針pointer指向array數組. 那么sizeof(array)得到的是數組的長度, 共四個元素, 每個元素4字節, 共16字節, 而sizeof(pointer)得到的是指針的長度, 即指針存儲的地址的長度, 32位機是4個字節.
16. 指針和字符串. 根據前面的內容, 對于字符串char flower[10]="rose";將會分配是個char空間, 至少留一個給空字符, 將rose填入空間后, 剩下的空間都填空字符. 也可以這樣寫char flowe[10]={'r','o','s','e'}; 剩下的空間都填空字符. 數組名flower的值也就是數組的首地址.例如下面的語句
??? char flower[10]="rose";
??? cout<<flower<<"s are red/n"; 結果將輸出flowers are red
??? 數組名flower是字符串的首地址, 因此cout語句中flower是字符r的char元素的地址. cout對象認為char的地址是字符串的地址, 因此它會答應該地址處的字符, 然后繼續打印后面的字符, 知道遇到空字符/0為止. 總結來說, 如果給cout提供一個字符的地址, 它將從該字符開始打印, 直到遇到空字符為止. 我們之前提到給cout提供指針,那么cout將會輸出指針的值, 也就是一個地址, 那么我們給cout一個提供字符的地址, 如flower, 也相當于一個指針, 它將不會輸出字符指針flower的值, 而是輸出字符指針所指向的字符, 從它開始打印, 知道遇到空字符為止. 即如果指針類型為int*, 那么cout將輸出指針的值, 如果指針類型為char *, 那么將輸出字符. 那么我們怎么輸出字符的地址呢? 將字符的指針轉換成int*類型即可. 例如要輸出字符串flower的地址, 那么這樣就可以: cout<<(int*)flower;
?? 下面看這句中cout<<flower<<"s are red/n";中cout如何處理"s are red/n", 與cout對字符的指針處理相一致, 在C++中, 用括號引起的字符串像數組名一樣, 也是第一個元素的地址, 是字符's'的地址, 上述代碼不會講"s are red/n"整個字符串發送給cout, 而是指發送該字符串的地址, 即's'的地址. 我們可以這樣總結說, 在cout和多數C++表達式中, char數組名, 指向char的指針以及用引號括起來的字符串常量都被解釋為字符串第一個字符的地址.
17. 使用char指針, 指向一個字符串. char * animal="dog";(這句就等效于char animal[]="dog";) 這句話將"dog"的地址賦值給animal指針, 一般來說, 編譯器在內存留出一些空間, 以存儲程序源代碼中所有用括號括起來的字符串(如"dog"), 并將每個被存儲的字符串與其地址關聯起來. "dog"的值是一個地址, 被賦值給指針animal.
18.?要獲得字符串的副本,?需分配空間來存儲此副本, 要分配合適的空間才行, 例如要獲得animal的副本, 則分配一段內存存儲此副本, 使得指針pointer指向此副本, 分配空間可以這樣: char * pointer=new char[strlen(animal)+1];?注意strlen(animal)求得的是animal的長度, 不包含空字符, 結果是3. 接下來要將animal數組中的字符串復制到新分配的空間中, 不能僅僅將animal賦值給pointer, 這樣只是讓animal與pointer指向同一個字符串, 根本沒有創建副本. 我們用庫函數strcpy將animal的字符串賦值給pointer所指向的內存空間. 使用這個語句: strcpy(pointer, animal); 有兩個參數, 第一個參數pointer是目標地址, 第二個參數animal是要復制的字符串的地址, 整句話的意思是將animal指向的字符串復制一份給pointer指向的空間, 這樣就獲得了animal的副本了. 在理解了C++處理括號括起來的字符串的方式后, 我們也就理解了下面的語句了, strcpy(pointer, "dog"); 第二個參數是字符串, 編譯器在解釋的時候, 第二個參數實際上被解釋成字符串"dog"的地址, 也就是相當于一個指針了. 意義即是將字符串"dog"的地址指向的字符串(也就是字符串"dog"本身)復制一份給pointer指向的內存空間.
??? 如果要復制的字符串長度超過目標地址所指向空間的長度, 會怎么辦呢? 例如 char food[5];(或者 char* food=new char[5]), 而使用下面語句: strcpy(food, "CarrotsCabbage"); 字符串"CarrotsCabbage"長度是14, 而food長度僅為5, 在這種情況下, 函數除了將前五個字符復制到food所指內存中外, 還將字符串中剩余的部分復制到數組food后面的內存字節中, 這可能會覆蓋程序正在使用的其他內存, 這可能會發生錯誤. 要避免這種情況, 就是用strncpy()函數, 它將接受第三個參數, 即要復制的最大字符數. 要注意的是, 如果該字符在達到字符串結尾之前, 目標內存已經用完, 則它將不會添加空字符, 而一個字符串數組沒有空字符是非法的, 必須有空字符, 那么我們應該讓要復制的最大字符數比字符串數組的空間長度小1, 至少留一個空間給空字符. 如果在達到字符串尾部時, 目標內存還未用完, 那么剩下的內存都將存空字符. 還是上面的語句, 這樣使用: strncpy(food, "CarrotsCabbage", 4); food[4]='/0'; 復制完后, 在尾部添加空字符.
?
總結
- 上一篇: 1.3 数据处理
- 下一篇: 1.5 使用new创建动态结构和自动,