1. 程式人生 > >無型別指標void*的學習與使用

無型別指標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 *指標具有以下特點:

  1. 任何指標(包括函式指標)都可以賦值給void指標;
 type *p;
  vp=p;

//不需轉換
//只獲得變數/物件地址而不獲得大小
2. void指標賦值給其他型別的指標時都要進行轉換;

type * p=(type *)vp;

//轉換型別也就是獲得指向變數/物件大小
3. void指標在強制轉換成具體型別前,不能解引用;

 *vp

//錯誤
//因為void指標只知道,指向變數/物件的起始地址
//而不知道指向變數/物件的大小(佔幾個位元組)所以無法正確引用
4. void指標不能參與指標運算,除非進行轉換。

(type*)vp++;

//等價於:vp=vp+sizeof(type)
void*的作用

  1. 傳參:通用型別
    可以作為函式模板,連結串列等引數的通用引數。在使用時,只需要強制型別轉換就可以。
    例如記憶體操作函式memcpy和memset的函式原型分別為:
void* memcpy(void *dest, constvoid *src, size_t len);   
void* memset(void *buffer, int c, size_t num);

這樣,任何型別的指標都可以傳入memcpy和memset中,這也真實地體現了記憶體操作函式的意義,因為它操作的物件僅僅是一片記憶體,而不論這片記憶體是什麼型別。

  1. 強制型別轉換
    有時候由於過載等的干擾,導致需要轉換成void *,來進行取地址。
    例如,(void *)obj.member,就可以取到member的地址;直接&(obj.member)取到的實際上是obj的開始地址。
  2. 指向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的專欄