C語言的通用指針類型(void *)


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* 不能做取值和小表操作
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM