- C++面向對象程序設計
- 杜茂康等編著
- 2309字
- 2019-01-01 05:54:53
2.3 指針
2.3.1 指針概念的回顧
指針用于存放一個對象在內存中的地址,通過指針能夠間接地操作這個對象。指針的典型用法是建立鏈接的數據結構,如樹(tree)和鏈表(list),并管理在程序運行過程中動態分配的對象,或者用于函數參數,以便傳遞數組或大型的類對象。
對于類型T,T*是到T的指針,即一個類型為T*的變量能夠保存一個類型為T的對象的地址。例如:
int i=10; //L1 int *p; //L2 p=&i; //L3
語句L2定義了一個整型指針變量,它表示p能夠存放一個整型變量的內存地址。L3將變量i的地址存放到指針變量p所代表的內存單元中,如下所示:
有人說,指針代表了兩個變量:一個是指針變量本身,另一個是它所指向的變量。對于語句L2,就可以認為它定義了兩個變量p和*p,p是一個地址變量,只能存放整型變量在內存中的地址;*p是一個整型變量,只能存放一個整數。
指針是一個復雜的概念,它能夠指向(保存)不同類型變量的內存地址。例如:
int *pi; // pi是指向int的指針 int **pc; // pc是指向int指針的指針 int *pA[10]; // pA是指向int的指針數組 int (*f)(int,char); // f是指向具有兩個參數的函數的指針 int *f(int) // f是一個函數,返回一個指向int的指針
2.3.2 指針與0和void*
沒有任何變量會被分配到地址0,所以0就可以作為一個指針常量,與NULL的含義相同,表明指針當時沒有指向任何變量。例如,對于前面的指針定義,以下賦值都是正確的:
pc=0; pA=0; f=0;
指針具有兩個屬性:地址和長度。地址大小固定,與類型無關;長度則與指針類型相關,這個類型指示編譯器怎樣解釋它所指定內存區域的內容,以及該內存區域應該跨越多少個內存單元。例如,在32位微機上,int、float或double類型的指針都是4字節的地址,但int型的指針會指示編譯器存取連續的4字節的內存單元作為一個整數,double型的指針則會指示編譯器連續存取8字節的內存單元作為一個雙精度浮點數……
因此,指針變量本身存儲的僅是一個內存地址,其大小是固定的,而指針類型用于確定如何存取其所指內存區域的數據。鑒于此,C++提供了一種無類型指針void *。兩個void*指針可以相互賦值、比較相等與否。
任何類型的指針都是大小相同的地址,而void*指針僅表示與之相關的值是個地址,因此能夠接收任何數據類型(除函數指針外)的指針。但是,由于void*無法確定其所指內存數據的類型,因此只有顯式地將一個void*轉換成某種數據類型的指針之后,才能訪問其所指內存區域的數據,其他操作都是不允許的。
【例2-3】 void*指針的應用。
//Eg2-3.cpp #include<iostream.h> void main(){ int i=4,*pi=&i; void* pv; double d=9,*pd=&d; pv=&i; //L1:正確 pv=pi; //L2:正確 // cout<<*pv<<endl; //L3:錯誤 pv=pd; //L4:正確 cout<<*(double*)pv; //L5:正確,輸出9 }
因為pv是void*指針,無法確定*pv所指內存區域的大小和類型,無法訪問,必須像L5語句那樣經過強制類型轉換之后才能訪問。
void*最重要的用途是作為函數的參數,以便向函數傳遞一個類型可變的對象。另一種用途就是從函數返回一個無類型的對象,在使用時再將它顯式轉換成適當的類型。
2.3.3 new和delete
指針常與堆(heap)空間的分配有關。所謂堆,就是一塊內存區域,它允許程序在運行時以指針的方式從其中申請一定數量的存儲單元(其他存儲空間的分配是在編譯時完成的),用于程序數據的處理。堆內存也稱動態內存。
堆內存的管理由程序員完成。在C語言中,如果需要使用堆內存,程序員可以用函數malloc()從堆中分配指定大小的存儲區域,用完之后必須用函數free()將之歸還系統。如果用完之后沒有用函數free()將它釋放,就會造成內存泄漏(也就是自己不用了,其他程序也無法使用)。因此,函數malloc()和free()在C程序中總是成對出現的。例如:
#include<stdlib.h> //malloc()和free()定義于此頭文件中 main(){ int *p; //從堆中分配1個int對象需要的內存,并將之轉換為int類型 p=(int*)malloc(sizeof(int)); *p=23; free(p); //釋放堆內存 }
malloc( )的使用比較麻煩,除了要計算需求的內存大小之外,還必須對獲得的內存區域進行類型轉換才能使用。為此,C++提供了new和delete兩個運算符進行堆內存的分配與釋放,它們分別與malloc( )和free( )相對應,但使用起來更簡單。
(1)new的用法
new的功能類似于malloc,用于從堆內存中分配指定大小的內存區域,并返回獲得的內存區域的首地址。
用法1:p=new type; 用法2:p=new type(x); 用法3:p=new type[n];
其中,p是指針變量,type是數據類型。用法1只分配堆內存,用法2將分配到的堆內存初始化為x,用法3分配具有n個元素的數組。new能夠根據type自動計算分配的內存大小,不需要用sizeof()計算。如果分配成功,會將得到的堆內存的首地址存放在指針變量p中;如果分配不成功,則返回空指針(0),在程序中可以此作為判斷內存分配成功與否的依據。
(2)delete的用法
delete的功能類似于free,用于釋放new分配的堆內存,以便它被其他程序使用。
用法1:delete p; 用法2:delete []p;
其中,p是用new分配的堆空間指針變量。用法1用于釋放動態分配的單個指針變量,用法2用于釋放動態分配的數組存儲區域。
【例2-4】 用new和delete分配與釋放堆內存。
//Eg2-4.cpp #include <iostream.h> int main(){ int *p1,*p2,*p3; p1=new int; //分配一個能夠存放int類型數據的內存區域 p2=new int(10); //分配一個int類型大小的內存區域,并將10存入其中 p3=new int[10]; //分配能夠存放10個整數的數組區域 if(!p3){ //程序中常會見到這樣的判定 cout<<"allocation failure"<<endl; //分配不成功,就顯示錯誤信息 return 1; //終止程序,并返回錯誤代碼 } *p1=5; *p3=1; p3[1]=2; //訪問指向數組的數組元素 p3[2]=3; cout<<"p1 address: "<<p1<<" value: "<<*p1<<endl; cout<<"p2 address: "<<p2<<" value: "<<*p2<<endl; cout<<"p3[0] address: "<<p3<<" value: "<<*p3<<endl; cout<<"p3[1] address: "<<&p3[1]<<" value: "<<p3[1]<<endl; delete p1; //釋放p1指向的內存 delete p2; delete p3; //錯誤,只釋放了p3指向數組的第1個元素 delete []p3; //釋放p3指向的數組 return 0; }
delete p3和delete []p3是有區別的,前者只釋放了第一個數組元素,即p[0],而沒有釋放其他數組元素p[1]~p[9],會造成內存泄漏;后者則將p3指向的數組區域全部歸還系統。
(3)new、delete和malloc、free的區別
在C++程序中,仍然可以使用malloc、free進行動態存儲空間的管理,但它們沒有用new、delete方便。主要體現在以下幾方面。
① new能夠自動計算要分配的內存大小,不必用sizeof()計算所要分配的內存字節數,減少了出錯的可能性。
② new不需要進行類型轉換,能夠自動返回正確的指針類型。
③ new可以對分配的內存進行初始化。
④ new和delete可以被重載,程序員可以借此擴展new和delete的功能,建立自定義的存儲分配系統。