C語言中文網 目錄
首頁 > 編程筆記 > C語言筆記 > 指針 閱讀:3,320

void指針及其應用,C語言void指針及使用注意事項詳解

void 指針是一種特殊的指針,表示為“無類型指針”,在 ANSI C 中使用它來代替“char*”作為通用指針的類型。由于 void 指針沒有特定的類型,因此它可以指向任何類型的數據。也就是說,任何類型的指針都可以直接賦值給 void 指針,而無需進行其他相關的強制類型轉換,如下面的示例代碼所示:
void *p1;
int *p2;
…
p1 = p2;
雖然如此,但這并不意味著可以無需任何強制類型轉換就將 void 指針直接賦給其他類型的指針,因為“空類型”可以包容“有類型”,而“有類型”則不能包容“空類型”。正如我們可以說“男人和女人都是人”,但不能說“人是男人”或者“人是女人”一樣。因此,下面的示例代碼將編譯出錯,如果在 VC++2010 中,將提示“a value of type"void*"cannot be assigned to an entity of type"int*"”的錯誤信息。
void *p1;
int *p2;
…
p2 = p1;
由此可見,要將 void 指針賦值給其他類型的指針,必須進行強制類型轉換。如下面的示例代碼所示:
void *p1;
int *p2;
…
p2 = (int*)p1;

避免對void指針進行算術操作

ANSI C 標準規定,進行算法操作的指針必須確定知道其指向數據類型大小,也就是說必須知道內存目的地址的確切值。如下面的示例代碼所示:
char a[20]="qwertyuiopasdfghjkl";
int *p=(int *)a;
p++;
printf("%s", p);
在上面的示例代碼中,指針變量 p 的類型是“int*”,指向的類型是 int,被初始化為指向整型變量 a。

在執行語句“p++”時,編譯器是這樣處理的:把指針 p 的值加上了“sizeof(int)”(由于在 32 位系統中,int 占 4 字節,所以這里是被加上了 4),即 p 所指向的地址由原來的變量 a 的地址向高地址方向增加了 4 字節。但又由于 char 類型的長度是一個字節,所以語句“printf("%s",p)”將輸出“tyuiopasdfghjkl”。

而對于 void 指針,編譯器并不知道所指對象的大小,所以對 void 指針進行算術操作都是不合法的,如下面的示例代碼所示:
void * p;
p++;      // ANSI:錯誤
p+= 1;      // ANSI:錯誤
上面的代碼在 VC++2010 中將提示“expression must be a pointer to a complete object type”的錯誤信息。

但值得注意的是,GNU 則不這么認為,它指定“void*”的算法操作與“char*”一致。因此下列語句在 GNU 編譯器中都是正確的:
void * p;
p++;      // GUN:正確
p+=1;      // GUN:正確
下面的示例代碼演示了在 GCC 中執行對 void 指針的自增操作:
#include <stdio.h>
int main(void)
{
    void * p="ILoveC";
    p++;
    printf("%s\n", p);
}
運行結果為:
LoveC

由此可見,GNU 和 ANSI 還存在著一些區別,相比之下,GNU 較 ANSI 更“開放”,提供了對更多語法的支持。但是在真實的設計環境中,還是應該盡可能符合 ANSI 標準,盡量避免對 void 指針進行算術操作。

如果函數的參數可以是任意類型指針,應該將其參數聲明為 void*

前面提到,void 指針可以指向任意類型的數據,同時任何類型的指針都可以直接賦值給 void 指針,而無需進行其他相關的強制類型轉換。因此,在編程中,如果函數的參數可以是任意類型指針,那么應該使用 void 指針作為函數的形參,這樣函數就可以接受任意數據類型的指針作為參數。

比較典型的函數有內存操作函數 memcpy 和 memset,如下面的代碼所示:
void *memset(void *buffer, int b, size_t size)
{
    assert(buffer!=NULL);
    char* retAddr = (char*)buffer;
    while (size--> 0)
    {
        *(retAddr++) = (char)b;
    }
    return retAddr;
}
void *memcpy (void *dst,  const void *src,  size_t size)
{
    assert((dst!=NULL) && (src!=NULL));
    char *temp_dest = (char *)dst;
    char *temp_src = (char *)src;
    char* retAddr = temp_dest;
    size_t i = 0;
    /* 解決數據區重疊問題*/
    if ((retAddr>temp_src) && (retAddr<(temp_src+size)))
    {
        for (i=size-1; i>=0; i--)
        {
            *(temp_dest++) = *(temp_src++);
        }
    }
    else
    {
        for (i=0; i<size; i++)
        {
            *(temp_dest++) = *(temp_src++);
        }
    }
    *(retAddr+size)='\0';
    return retAddr;
}
這樣,任何類型的指針都可以傳入 memcpy 函數和 memset 函數中,這也真實地體現了內存操作函數的意義,因為它操作的對象僅僅是一片內存,而不論這片內存是什么類型。memcpy 函數的調用示例如下面的代碼所示:
char buf[]="abcdefg";
// buf+2(從c開始,長度3個,即cde)
memcpy(buf, buf+2 ,3);
printf("%s\n", buf);
或者進行如下形式的調用:
int dst[100];
int src[100];
memcpy(dst, src, 100*sizeof(int));
因為參數類型是 void*,所以上面的調用都是正確的。現在假設 memcpy 函數的參數類型不是 void*,而是 char*,如下面的代碼所示:
char *memcpy(char* dst, const char* src, size_t size)
{
    assert((dst !=NULL) && (src != NULL));
    char *retAddr = dst;
    size_t i = 0;
    if ((retAddr>src) && (retAddr<(src+size)))
    {
        for (i=size-1; i>=0; i--)
        {
            *(dst++)= *(src++);
        }
    }
    else
    {
        for (i=0; i<size; i++)
        {
            *(dst++) = *(src++);
        }
    }
    *(retAddr+size)='\0';
    return retAddr;
}
現在繼續執行如下形式的調用:
int dst[100];
int src[100];
memcpy(dst, src, 100*sizeof(int));
由于類型不匹配,編譯器就會報錯,如圖 1 所示。


圖 1

由此可見,這樣的函數同時也失去了通用性。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo