無型別指標void*的學習與使用
關於指標
C/C++中的指標通常來說有兩個屬性:
1. 指向變數
2. 指向物件的地址和長度
指標其實就是儲存被指向變數的地址,並不儲存其長度;
而且存的這個地址僅是變數的首地址,並不是該變數佔據記憶體的所有地址空間。如:
int a=3;
int *p=&a;
目前大多數的C/C++編譯環境中,整型int資料佔4個位元組的空間,如上圖所示。所以指標p儲存的地址(即指向的地址)為1號記憶體單元(首地址)。
當需要讀取一個例如int型資料時,編譯器根據指標的型別從指標指向的地址開始向後定址。指標型別不同則定址範圍也不同,比如:
int*從指定地址向後尋找4位元組作為變數的儲存單元;
double*從指定地址向後尋找8位元組作為變數的儲存單元。
說到這裡可能大家就會有一個問題:由於計算機內部的地址是整型數字,那麼為什麼不乾脆用一個整型變數儲存地址,還要發明指標變數呢?
如果我們從指標實現的角度講,指標就是一個整型變數,它儲存的是一個地址值,沒有任何附加資訊。目前為止貌似沒有什麼問題,其實不然。
就拿上述的程式碼:如果用一個整型變數b儲存a的地址,即int b=&a。當對b加1時,得到的新的地址相當於對a的首地址加1,即&a+1,由於int佔連續的四個儲存單元(預設),此時b儲存的是第二塊儲存單元的地址,所以根據變數b儲存的地址,將無法完整的讀出變數a的值,導致錯誤。而通過指標變數,可以解決這類問題:如果對上述程式碼中的指標p加1的話,實際上是p+sizeof(int),一次性增加了4個儲存單元。
PS.指標本身所佔據的記憶體區 :
指標本身佔了多大的記憶體?你只要用函式sizeof(指標的型別)測一下就知道了。
指標的作用是用來對記憶體空間進行定址,在32位機上,所有指標型別變數佔用記憶體位元組數都為4,因為32位機是按32位定址的。如果在64位機上,指標佔用記憶體大小就是:8個位元組。
無型別指標void*
void *vp;
void*是一種特別的指標,因為它沒有指向的型別,或者說不能根據這個型別判斷出指向物件的長度。void *指標具有以下特點:
- 任何指標(包括函式指標)都可以賦值給void指標;
type *p;
vp=p;
//不需轉換
//只獲得變數/物件地址而不獲得大小
2. void指標賦值給其他型別的指標時都要進行轉換;
type * p=(type *)vp;
//轉換型別也就是獲得指向變數/物件大小
3. void指標在強制轉換成具體型別前,不能解引用;
*vp
//錯誤
//因為void指標只知道,指向變數/物件的起始地址
//而不知道指向變數/物件的大小(佔幾個位元組)所以無法正確引用
4. void指標不能參與指標運算,除非進行轉換。
(type*)vp++;
//等價於:vp=vp+sizeof(type)
void*的作用
- 傳參:通用型別
可以作為函式模板,連結串列等引數的通用引數。在使用時,只需要強制型別轉換就可以。
例如記憶體操作函式memcpy和memset的函式原型分別為:
void* memcpy(void *dest, constvoid *src, size_t len);
void* memset(void *buffer, int c, size_t num);
這樣,任何型別的指標都可以傳入memcpy和memset中,這也真實地體現了記憶體操作函式的意義,因為它操作的物件僅僅是一片記憶體,而不論這片記憶體是什麼型別。
- 強制型別轉換
有時候由於過載等的干擾,導致需要轉換成void *,來進行取地址。
例如,(void *)obj.member,就可以取到member的地址;直接&(obj.member)取到的實際上是obj的開始地址。 - 指向0的地址
(void *)0,指向全是0的地址,相當於NULL。
下面舉一個使用void*指標的demo:
#include<iostream>
#include<string>
using namespace std;
typedef struct tag_st
{
string id;
float fa[2];
}ST;
int main()
{
ST* P = new ST;
P->id = "hello!";
P->fa[0] = 1.1;
P->fa[1] = 2.1;
ST* Q = new ST;
Q->id = "world!";
Q->fa[0] = 3.1;
Q->fa[1] = 4.1;
void * plink = P;
*(ST*)(plink) = *Q; //plink要先強制轉換一下,目的是為了讓它先知道要覆蓋的大小
//P的內容被Q的內容覆蓋
cout << P->id << " " << P->fa[0] << " " << P->fa[1] << endl;
return 0;
}
在寫這個例子的時候發生一點小插曲:把第24行:* (ST * )(plink) = * Q; 寫成了(ST * )(plink) = Q;結果編譯報錯,剛開始以為是void *指標不能被兩次賦值而引發的錯誤。經過研究,得知是因為強制型別轉化操作(目標型別是引用時除外)不能作為左值,所以編譯器才會報錯。另附上一篇介紹左值和右值的部落格helloworld的部落格。
注:關於void*指標的介紹,參考於wangicter的部落格WangIcter的專欄。