reference: https://blog.csdn.net/cumirror/article/details/4631701
https://blog.csdn.net/Lee_Shuai/article/details/53193436
指針有兩個屬性:指向變量/對象的地址和長度,但是指針只存儲地址,長度則取決於指針的類型;編譯器根據指針的類型從指針指向的地址向后尋址,指針類型不同則尋址范圍也不同,比如:
int* 從指定地址向后尋找4字節作為變量的存儲單元 double* 從指定地址向后尋找8字節作為變量的存儲單元
void即“無類型”,void *則為“無類型指針”,可以指向任何數據類型。
1 .void的作用
1) 對函數返回的限定
a) 當函數不需要返回值時,必須使用void限定。例如: void func(int, int)
2) 對函數參數的限定
a) 當函數不允許接受參數時,必須使用void限定。例如: int func(void)
2 .void指針使用規范
2.1 void指針可以指向任意類型的數據,即可用任意數據類型的指針對void指針賦值。例如
int *pint ; void *pvoid ; //它沒有類型,或者說這個類型不能判斷出指向對象的長度 pvoid = pint ; //只獲得變量/對象地址而不獲得大小,但是不能 pint =pvoid;
2.2 如果要將pvoid賦給其他類型指針,則需要強制類型轉換如:
pint = (int *)pvoid; //轉換類型也就是獲得指向變量/對象大小
轉:http://icoding.spaces.live.com/blog/cns!209684E38D520BA6!130.entry
2.3 void指針不能復引用(即取內容的意思)
*pvoid //錯誤
要想復引用一個指針,或者使用“->”運算符復引用一部分,都要有對於指針指向的內存的解釋規則。
例如,int *p;
那么,當你后面復印用p的時候,編譯器就會把從p指向的地址開始的四個字節看作一個整數的補碼。
因為void指針只知道指向變量/對象的起始地址,而不知道指向變量/對象的大小(占幾個字節)所以無法正確引用
2.4 void指針類型運算
按照ANSI(AmericanNationalStandardsInstitute)標准,不能對void指針進行算法操作,即下列操作都是不合法的:
void* pvoid ; pvoid++ ; //ANSI:錯誤 pvoid+=1 ; //ANSI:錯誤
ANSI標准之所以這樣認定,是因為它堅持:進行算法操作的指針必須是確定知道其指向數據類型大小的。
//例如:
int* pint; pint++ ; //ANSI:正確
pint++的結果是使其增大sizeof(int)。
但是大名鼎鼎的GNU(GNU's Not Unix的縮寫)則不這么認定,它指定void * 的算法操作與char * 一致。
因此下列語句在GNU編譯器中皆正確:
pvoid++ ; //GNU:正確 pvoid+=1 ; //GNU:正確
pvoid++的執行結果是其增大了1。
在實際的程序設計中,為迎合ANSI標准,並提高程序的可移植性,我們可以這樣編寫實現同樣功能的代碼:
void * pvoid ; (char*) pvoid++ ; //ANSI:正確;GNU:正確 (char*) pvoid+=1 ; //ANSI:錯誤;GNU:正確
GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支持。但是我們在真實設計時,還是應該盡可能地迎合ANSI標准。
2.5 如果函數的參數可以是任意類型指針,那么應聲明其參數為void *。
典型的如內存操作函數memcpy和memset的函數原型分別為:
void * memcpy(void *dest,constvoid * src,size_tlen); void * memset(void * buffer,intc,size_tnum);
這樣,任何類型的指針都可以傳入memcpy和memset中,這也真實地體現了內存操作函數的意義,因為它操作的對象僅僅是一片內存,而不論這片內存是什么類型。如果 memcpy和memset的參數類型不是void *,而是char *,那才叫真的奇怪了!這樣的memcpy和memset明顯不是一個“純粹的,脫離低級趣味的”函數!
下面的代碼執行正確:
//示例:memset接受任意類型指針
int intarray[100] ; memset(intarray,0,100*sizeof(int)); //將intarray清0
//示例:memcpy接受任意類型指針
int intarray1[100], intarray2[100]; memcpy(intarray1,intarray2,100*sizeof(int)); //將intarray2拷貝給intarray1
有趣的是,memcpy和memset函數返回的也是void*類型,標准庫函數的編寫者是多么地富有學問啊!
2.6 void不能代表一個真實的變量
下面代碼都企圖讓void代表一個真實的變量,因此都是錯誤的代碼:
void a ; //錯誤 function(void a) ; //錯誤
void體現了一種抽象,這個世界上的變量都是“有類型”的,譬如一個人不是男人就是女人(還有人妖?)。
void的出現只是為了一種抽象的需要,如果你正確地理解了面向對象中“抽象基類”的概念,也很容易理解void數據類型。正如不能給抽象基類定義一個實例,我們也不能定義一個void(讓我們類比的稱void為“抽象數據類型”)變量。
typedef struct tag_st { char id[10]; float fa[2]; }ST;
int main() { ST * P=(ST *)malloc(sizeof(ST)); strcpy(P->id,"hello!"); P->fa[0]=1.1; P->fa[1]=2.1; ST * Q=(ST *)malloc(sizeof(ST)); strcpy(Q->id,"world!"); Q->fa[0]=3.1; Q->fa[1]=4.1; void ** plink=(void **)P; *((ST *)(plink)) = * Q; //plink要先強制轉換一下,目的是為了讓它先知道要覆蓋的大小. //P的內容竟然給Q的內容覆蓋掉了. cout<id<<""<fa[0]<<" "<fa[1]<return 0; }
void *指針只能保存對象(也就是數據)指針。將函數指針轉換為void *指針是不可移植的。(在某些機器上,函數指針可能很大—–比任何數據指針都大。)但是,可以確保的是,所有的函數指針類型都可以相互轉換,只要在調用之前轉回了正確的類型即可。因此,可以使用任何函數類型(通常是int (*)()或void (*)(),即未指明參數、返回int或void的函數)作為通用函數指針。如果你需要一個既能容納對象指針又能容納函數指針的地方,可移植的解決方案是使用包含void *指針和通用函數指針(任何類型都可以)的聯合。
void* 表指向的對象類型不確定。void * 可以和任何指針直接做變換,除了函數指針外。
如:
int *pi; void *pv; pi=pv; //注意在C++編譯器中必須轉換 pi=(int*)pv; pv=pi;
由於void * 可以和任何指針直接做變換,除了函數指針外,在C編譯器下,我們知道malloc函數的返回類型是void*,所以下2句是等價的
int *p =(int*)malloc(100*sizeof(int)); int *p =malloc(100*sizeof(int))
但是void* 不能做取值和小表操作