void指針

1回頂部
  熱門文章:C++中extern 「C」含義深層探索      編程實現盜2005版QQ源碼

  1.概述
  許多初學者對C/C++語言中的void及void指針類型不甚理解,所以在使用上出現了一些錯誤。本文將對void關鍵字的深入含義進行解說,並詳述void及void指針類型的使用方法與技巧。

  2.void的含義
  void的字面意思是「無類型」,void *則爲「無類型指針」,void *能夠指向任何類型的數據。

  void幾乎只有「註釋」和限制程序的做用,由於歷來沒有人會定義一個void變量,讓咱們試着來定義:

void a;


  這行語句編譯時會出錯,提示「illegal use of type 'void'」。不過,即便void a的編譯不會出錯,它也沒有任何實際意義。

  void真正發揮的做用在於:
  (1) 對函數返回的限定;
  (2) 對函數參數的限定。

  咱們將在第三節對以上二點進行具體說明。

  衆所周知,若是指針p1和p2的類型相同,那麼咱們能夠直接在p1和p2間互相賦值;若是p1和p2指向不一樣的數據類型,則必須使用強制類型轉換運算符把賦值運算符右邊的指針類型轉換爲左邊指針的類型。 

  例如:
float *p1;
int *p2;
p1 = p2;


  其中p1 = p2語句會編譯出錯,提示「'=' : cannot convert from 'int *' to 'float *'」,必須改成:
p1 = (float *)p2;

2回頂部

  而void *則不一樣,任何類型的指針均可以直接賦值給它,無需進行強制類型轉換:
void *p1;
int *p2;
p1 = p2;


  但這並不意味着,void *也能夠無需強制類型轉換地賦給其它類型的指針。由於「無類型」能夠包容「有類型」,而「有類型」則不能包容「無類型」。道理很簡單,咱們能夠說「男人和女人都是人」,但不能說「人是男人」或者「人是女人」。下面的語句編譯出錯:
void *p1;
int *p2;
p2 = p1;


  提示「'=' : cannot convert from 'void *' to 'int *'」。

3.void的使用

  下面給出void關鍵字的使用規則:
  規則一 若是函數沒有返回值,那麼應聲明爲void類型

  在C語言中,凡不加返回值類型限定的函數,就會被編譯器做爲返回整型值處理。可是許多程序員卻誤覺得其爲void類型。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}


  程序運行的結果爲輸出:
  2 + 3 = 5
  這說明不加返回值說明的函數的確爲int函數。

  林銳博士《高質量C/C++編程》中提到:「C++語言有很嚴格的類型安全檢查,不容許上述狀況(指函數不加類型聲明)發生」。但是編譯器並不必定這麼認定,譬如在Visual C++6.0中上述add函數的編譯無錯也無警告且運行正確,因此不能寄但願於編譯器會作嚴格的類型檢查。

  所以,爲了不混亂,咱們在編寫C/C++程序時,對於任何函數都必須一個不漏地指定其類型。若是函數沒有返回值,必定要聲明爲void類型。這既是程序良好可讀性的須要,也是編程規範性的要求。另外,加上void類型聲明後,也能夠發揮代碼的「自注釋」做用。代碼的「自注釋」即代碼能本身註釋本身。
3回頂部

  規則二 若是函數無參數,那麼應聲明其參數爲void

  在C++語言中聲明一個這樣的函數:
int function(void)
{
return 1;
}


  則進行下面的調用是不合法的:
function(2);


  由於在C++中,函數參數爲void的意思是這個函數不接受任何參數。

  咱們在Turbo C 2.0中編譯:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}


  編譯正確且輸出1,這說明,在C語言中,能夠給無參數的函數傳送任意類型的參數,可是在C++編譯器中編譯一樣的代碼則會出錯。在C++中,不能向無參數的函數傳送任何參數,出錯提示「'fun' : function does not take 1 parameters」。

  因此,不管在C仍是C++中,若函數不接受任何參數,必定要指明參數爲void。

  規則三 當心使用void指針類型

  按照ANSI(American National Standards Institute)標準,不能對void指針進行算法操做,即下列操做都是不合法的:
void * pvoid;
pvoid++; //ANSI:錯誤
pvoid += 1; //ANSI:錯誤
//ANSI標準之因此這樣認定,是由於它堅持:進行算法操做的指針必須是肯定知道其指向數據類型大小的。
//例如:
int *pint;
pint++; //ANSI:正確


  pint++的結果是使其增大sizeof(int)。

  可是大名鼎鼎的GNU(GNU's Not Unix的縮寫)則不這麼認定,它指定void *的算法操做與char *一致。
4回頂部

  所以下列語句在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標準。

  規則四 若是函數的參數能夠是任意類型指針,那麼應聲明其參數爲void *

  典型的如內存操做函數memcpy和memset的函數原型分別爲:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );


  這樣,任何類型的指針均可以傳入memcpy和memset中,這也真實地體現了內存操做函數的意義,由於它操做的對象僅僅是一片內存,而不論這片內存是什麼類型。若是memcpy和memset的參數類型不是void *,而是char *,那才叫真的奇怪了!這樣的memcpy和memset明顯不是一個「純粹的,脫離低級趣味的」函數!
5回頂部

  下面的代碼執行正確:
//示例: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 *類型,標準庫函數的編寫者是多麼地富有學問啊!

  規則五 void不能表明一個真實的變量

  下面代碼都企圖讓void表明一個真實的變量,所以都是錯誤的代碼:
void a; //錯誤
function(void a); //錯誤


  void體現了一種抽象,這個世界上的變量都是「有類型」的,譬如一我的不是男人就是女人(還有人妖?)。

  void的出現只是爲了一種抽象的須要,若是你正確地理解了面向對象中「抽象基類」的概念,也很容易理解void數據類型。正如不能給抽象基類定義一個實例,咱們也不能定義一個void(讓咱們類比的稱void爲「抽象數據類型」)變量。

4.總結
  小小的void蘊藏着很豐富的設計哲學,做爲一名程序設計人員,對問題進行深一個層次的思考必然使咱們受益不淺。
void指針是什麼? 

void指針通常被稱爲通用指針或泛指針,它是C關於「純粹地址(raw address)」的一種約定。void指針指向某個對象,但該對象不屬於任何類型。請看下例:
    int    *ip;
    void    *p;
    在上例中,ip指向一個整型值,而p指向的對象不屬於任何類型。
    在C中,任什麼時候候你均可以用其它類型的指針來代替void指針(在C++中一樣能夠),或者用void指針來代替其它類型的指針(在C++中須要進行強制轉換),而且不須要進行強制轉換。例如,你能夠把char *類型的指針傳遞給須要void指針的函數。
何時使用void指針?

當進行純粹的內存操做時,或者傳遞一個指向未定類型的指針時,可使用void指針。void指針也經常用做函數指針。

有些C代碼只進行純粹的內存操做。在較早版本的C中,這一點是經過字符指針(char *)實現的,可是這容易產生混淆,由於人們不容易判斷一個字符指針到底是指向一個字符串,仍是指向一個字符數組,或者僅僅是指向內存中的某個地址。

例如,strcpy()函數將一個字符串拷貝到另外一個字符串中,strncpy()函數將一個字符串中的部份內容拷貝到另外一個字符串中:
    char  *strepy(char'strl,const char *str2);
    char  *strncpy(char *strl,const char *str2,size_t n);   
    memcpy()函數將內存中的數據從一個位置拷貝到另外一個位置:
    void  *memcpy(void *addrl,void *addr2,size_t n);
   
    memcpy()函數使用了void指針,以說明該函數只進行純粹的內存拷貝,包括NULL字符(零字節)在內的任何內容都將被拷貝。請看下例:
    #include "thingie.h"    /* defines struct thingie */
    struct thingie *p_src,*p_dest;
    /*  ...  */
    memcpy(p_dest,p_src,sizeof(struct thingie) * numThingies);

在上例中,memcpy()函數要拷貝的是存放在structthingie結構體中的某種對象op_dest和p_src都是指向structthingie結構體的指針,memcpy()函數將把從p_src指向的位置開始的sizeof(stuctthingie) *numThingies個字節的內容拷貝到從p_dest指向的位置開始的一塊內存區域中。對memcpy()函數來講,p_dest和p_src都僅僅是指向內存中的某個地址的指針。
void和void指針解析(原)
(一)基本概念
    void 類型:空類型,用於特殊目的的沒有操做,也沒有值的類型。不能被顯式或隱式的轉換爲任意非空類型,能夠經過強制類型轉換爲void類型。
    void指針:指向任何對象的指針均可以轉換爲void*類型指針,且不會丟失信息。在ANSI C使用類型void*代替char*做爲通用指針的類型。
(二)使用方法
1. void的使用吐舌鬼臉
第一種是:對函數返回的限定
    在不加返回值類型限定的狀況之下,編譯器會將其處理爲整型的類型。例如如下的狀況:

#include <stdio.h>
// 參考了別人寫void的例子,可是這個例子十分形象
// 的代表了不加返回類型值限定時,編譯器的處理規則。
add (int a, int b) 
{ 
    return a + b; 
}
int main(int argc, char* argv[]) 
{ 
    printf("2 + 3 = %d", add (2, 3)); 
    return 0;
}
    對於每一個函數,惡魔咱們都要明確的指定其返回值的類型。該void的時候,就void。不要省略不寫,這回帶來大麻煩惡魔。

第二種:對函數參數的限定
    當函數不容許接受參數時,必須使用void限定。例如如下兩種狀況:
在C語言下:
#include <stdio.h>
// 如下兩個函數均可以進行正常的編譯,不會報錯。
// C的容忍度仍是很大的。。。
int func()
{
    return 1;
}
 
int func1(void)
{
    return 1;
}
 
int main(int argc, char* argv[]) 
{ 
    printf("%d\n", func(2)); 
    printf("%d\n", func1(2)); 
    return 0;
} 
運行結果太棒了:
20RQ76LKBLX5_Q_thumb
在C++語言下:
#include <iostream>
int func()
{
    return 1;
}
int func1(void)
{
    return 1;
}
int main(int argc, char* argv[]) 
{ 
    std::cout << func(1) << std::endl;
    std::cout << func1(1) << std::endl;
    return 0;
} 
運行結果哭泣的臉:
7A8_IPQDX6VMHP7O5V2_thumb
    惡魔在C語言中,能夠給無參數的函數傳送任意類型的參數,可是在C++編譯器中編譯一樣的代碼則會出錯。惡魔在C++中,不能向無參數的函數傳送任何參數。

2. void指針的使用
    在《C++ primer》中,對void指針的做用作了闡述:與另外一指針比較;向函數傳遞void指針或從函數返回void指針;給void指針賦值。
    因爲void指針能夠指向任意類型的數據,亦便可用任意數據類型的指針對void指針賦值,所以還能夠用void指針來做爲函數形參,這樣函數就能夠接受任意數據類型的指針做爲參數。

如下摘至sgi stl中的代碼片斷:吐舌鬼臉

/* __n must be > 0      */
static void* allocate(size_t __n)
{
  void* __ret = 0;
 
  if (__n > (size_t) _MAX_BYTES) {
    __ret = malloc_alloc::allocate(__n);
  }
  else {
    _Obj* __STL_VOLATILE* __my_free_list
        = _S_free_list + _S_freelist_index(__n);
    // Acquire the lock here with a constructor call.
    // This ensures that it is released in exit or during stack
    // unwinding.
    ifndef _NOTHREADS
    /*REFERENCED*/
    _Lock __lock_instance;
    endif
    _Obj* __RESTRICT __result = *__my_free_list;
    if (__result == 0)
      __ret = _S_refill(_S_round_up(__n));
    else {
      *__my_free_list = __result -> _M_free_list_link;
      __ret = __result;
    }
  }
 
  return __ret;
}
/* __p may not be 0 */
static void deallocate(void* __p, size_t __n)
{
  if (__n > (size_t) _MAX_BYTES)
    malloc_alloc::deallocate(__p, __n);
  else {
    _Obj* __STL_VOLATILE*  __my_free_list
        = _S_free_list + _S_freelist_index(__n);
    _Obj* __q = (_Obj*)__p;
 
    // acquire lock
      ifndef _NOTHREADS
    /*REFERENCED*/
    _Lock __lock_instance;
      endif /* _NOTHREADS */
    __q -> _M_free_list_link = *__my_free_list;
    *__my_free_list = __q;
    // lock is released here
  }
}
    對於指針的自增行爲,要迎合ANSI C 的規範。在ANSI C中void指針不能自增的,這種行爲是一種非法行爲,由於未知void的大小。哭泣的臉

#include <iostream>
int main(int argc, char* argv[]) 
{ 
    int i = 0;
    void *p;
    int *pint;
    int *pint1;
    pint = &i;
    p = pint;
    std::cout << pint << std::endl;
    pint++;
    std::cout << pint << std::endl;
    std::cout << p << std::endl;
    pint1 = (int *)p;
    std::cout << pint1 << std::endl;
    return 0;
} 
運行的結果:

9OV3PPIYTPM41KMFY_thumb


文獻參考:

1. 《C++ primer》和《C語言程序設計》

2.http://wenku.baidu.com/view/22c4b8d86f1aff00bed51edc.html這篇文章很出名啊太陽,不少void的文章都是轉載這一篇文章的。
void指針總結
今天在看memcpy函數原型的時候遇到void指針,我有些地方不明白,就從網上搜集了一些資料,而後總結一下。
先來看下memcpy函數的原型:
void * memcpy ( void * destination, const void * source, size_t num )
我開始覺得void指針能夠進行應用和計算,出現以下愚蠢的錯誤:
void * dest,src;
*dest ++= *src++;
知錯就改,補習一下指針的知識。
基本概念:「指針」是指地址, 是常量,「指針變量」是指取值爲地址的變量。 定義指針的目的是爲了經過指針去訪問內存單元。
指針有兩個屬性:指向變量或者對象的地址和長度。可是指針只存儲地址,長度則取決於指針的類型 ,編譯器根據指針的類型從指針指向的地址向後尋址 指針類型不一樣則尋址範圍也不一樣。好比: int*從指定地址向後尋找4字節做爲變量的存儲單元 double*從指定地址向後尋找8字節做爲變量的存儲單元。
(1)void指針是一種特別的指針。void指針沒有特定的類型,所以只知道地址而不能由類型判斷出指針所指變量或者對象的長度。
  void * vp;
(2)任何類型的指針均可以賦給void指針。不須要類型轉換,vp只是獲取地址,並無得到變量或者對象的長度。
  type * p;
  void* vp = p;
(3)void指針賦值給其餘類型的指針時都要進行轉換
  void * vp  =pointer; // vp指向一個變量
  type *p = (type*) vp; //類型轉換4)void指針不能引用
    void * vp  =pointer; // vp指向一個變量
   *vp // 錯誤的5)void指針不能參與指針運算,除非進行轉換
void * dest, *src;
*dest ++= *src++;  //錯誤的
void * vp  =pointer;
(type*)vp++  //進行類型轉換後才能夠進行指針運算
下面經過memcpy的源碼來學習一下void指針的用法,我寫memcpy的時候沒有考慮內存的重疊,只是簡單的逐位拷貝。
void * memcpy ( void * dest, const void * src, size_t n )
{
    if(src == NULL)
    {
        free(dest);
        return NULL;
    }
    if(dest == NULL)
    {
        return NULL;
    }
    if(dest == src)
      return dest;
    size_t count = n;
   char *d = (char*)dest;   //此時進行類型轉換,對void指針進行操做
    char *s = (char*)src;
    while(count--)
    {
        *d++ = *s++;    //經過將void指針轉換爲char*後進行指針運算
    }
    return dest;
}
先介紹下void指針:
其中的第三個參數類型爲void指針.咱們知道一個指針有兩個屬性:指向變量或對象的"地址""長度".可是指針只存儲"地址".
長度則取決於指針的類型.編輯器根據指針的類型從指針的"地址"向後尋址,指針不一樣,則尋址範圍也不一樣.好比:
int * 從指定地址向後尋址4個字節做爲變量的存儲單元; double * 則從指定的地址向後尋址8個字節做爲變量的存儲單元.
1.void指針是特別的指針,由於它沒有類型,也就是咱們不知道其長度.
    void *vp;
2.任何指針均可以賦值給void指針.
     type *tp;
     vp = tp;//不須要轉換.
                 //只得到tp的地址.
3.void指針轉賦值給其餘類型的指針時須要轉換.
    type *tp = (type *)vp;//這樣便得到了地址和長度.
4.void指針不能復引用,由於void指針並不知道指針的長度.
    *vp;//錯誤的
5.void指針不能參加指針運算,除非先對其進行轉換.
參:http://www.cppblog.com/dragon/archive/2008/09/02/60760.aspx
---------------------------------------------------------------------------------------------------------------------
下面咱們在來看看cocos2d中的:
CCCallFuncND *callFun = [CCCallFuncND actionWithTarget:(id) selector:(SEL) data:(void *)];
 
咱們能夠看到第三個參數的類型是void指針.
對於obj-c的對象指針來講,能夠直接將其做爲參數傳給data.咱們前面的2中說了,任何指針均可以賦值給void指針,可是要注意,丟了類型.
例子1:
NSString *str = @"hello world";
CCCallFuncND *ac =
[CCCallFuncND actionWithTarget:self selector:@selector(move:data:) data:tempString ];
 
- (void)move(id)sender data:(void *)data {
      NSString *str = (NSString *)data;// 咱們能夠將void指針轉換爲NSString.
}

例子2:
int t = 100;
CCCallFuncND *ac =
[CCCallFuncND actionWithTarget:self selector:@selector(move:data:) data:(void *)t ];//這裏不加一個轉換xcode會給出警告.
- (void)move(id)sender data:(void *)data {
      int t = (int)data;//若是不轉換xcode會給出警告.
}
 
相關文章
相關標籤/搜索