深入理解void,void*和sizeof關鍵字

void的字面值是「無類型」,void*則是"無類型指針"。void*能夠指向任何類型的數據。void幾乎只有"註釋"和限制程序的做用,由於歷來沒有人會定義一個void變量。程序員

void a;    //編譯時提示"illegaluseoftype'void'"express

void真正發揮的做用在於:對函數返回的限定;對函數參數的限定編程

若是指針p1和p2的類型相同,那麼p1和p2之間可互相賦值;若是p1和p2指向不一樣的數據類型,則必須使用強制類型轉換運算符,把賦值運算符右邊的指針類型轉換爲左邊指針的類型,而後才能夠賦值。如:數組

float* p1;數據結構

int* p2;函數

p1 = p2;  //can't convert from 'int*' to 'float*',必須改成下面這種形式spa

p1=(float*)p2;指針

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

但這並不意味灃void*也可無須強制轉換地賦值給其餘類型的指針,這是由於"無類型"能夠包含"有類型",而"有類型"不能包容"無類型"繼承

void關鍵字使用規則

1.若是函數沒有返回值,那麼應聲明爲void類型。在C語言中,凡不加返回值類型限定的函數,就會編譯器做爲返回整型值處理,可是許多程序員卻誤認爲其爲void類型

例如

add(int a,int b)              int main()

{                     {

  return a+b;                printf("2+3=%d",add(2,3));

}                     }  //程序運行結果爲2+3=5,這說明不加返回值說明的函數,其返回值的確爲int類型

所以,爲避免混亂,對於任何函數都必須指定其返回類型。若是函數沒有返回值,必定要聲明爲void類型。發揮代碼的自注釋做用。這既是程序良好可讀性的須要,也是編程規範性的要求

2.若是函數無參數,那麼應聲明其參數爲void.若是在C++語言中聲明一個這樣的函數

int function(void)      //則進行function(2)是不合法的,由於在C++中,函數參數爲void的意思是這個函數不接受任何參數,但若是在Turbo C2.0中編譯則正確        

{              //說明在C語言中,能夠給無參數的函數傳送任意類型的參數,但在C++編譯器中編譯一樣的代碼則會出錯。因此,若函數不接受任何參數,必定要聲明爲void

  return 1;

}

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

典型的如內存操做函數:

void* memcpy(void* dest,const void* src,size_t len);

void* memset(void* buffer,int c,size_t num);

這樣,任何類型的指針均可以傳入memcpy和memset中,這也真實地體現了內存操做函數的意義,由於它操做的對象僅僅是一片內存,而不論這片內存是什麼類型

4.void不能表明一個真實的變量

void a      //錯誤

void體現了一種抽象,這個世界上的變量都是"有類型"的。void的出現只是爲了一種抽象的須要,若是你正確地理解了面向對象中"抽象基類"的概念,也很容易理解void數據類型。

二.sizeof

sizeof不是一個函數,由於sizeof unary-expression,sizeof(unary-expression)和sizeof(type-name)都合法,而第一種沒有括號

sizeof不是一元操做符,int a = 0;cout<<sizeof(a=3)<<endl;cout<<a<<endl;  運行結果爲4,0而不是4,3,因此不是一元操做符

這是因爲sizeof在編譯階段處理的特性形成的。sizeof不能被編譯成機器碼,sizeof做用範圍內,也就是()裏面的內容也不能被編譯(a=3語句不能編譯成機器碼),而是被替換成類型,因此,

sizeof不是一元操做符,由於sizeof不支持鏈式表達式,sizeof做用域範圍內的語句不會編譯成機器碼。更像一個特殊的宏,它在編譯階段求值,例如

cout<<sizeof(int)<<endl;      //32位機上輸出4

cout<<sizeof(1==2)<<endl;    //至關於size(bool),爲1

1.sizeof的用法

sizeof有兩種用法,第一種爲針對對象,用法爲sizeof(object)或sizeof object,第二種爲針對類型,用法爲sizeof(typename),例如

int i = 2;

cout<<sizeof(i)<<endl;         //sizeof(object)的用法,合理

cout<<sizeof i<<endl;        //sizeof object的用法,合理

cout<<sizeof 2<<endl;        //2被解析成int類型的object,sizeof object的用法,合理

cout<<sizeof(int)<<endl;      //sizeof(typename)的用法,合理

cout<<sizeof int<<endl;      //錯誤,對於操做符,必定要加(),不管是對對象仍是對類型取值,sizeof寫成sizeof這種函數形式永遠是正確的

2.基本數據類型的sizeof

函數類型和指針類型都屬於指針的範疇,指針主要用於存儲地址,在32位系統下,任何類型的指針其所佔用的內存大小均爲4字節;而函數類型比較特別,它以其返回類型做爲自身類型進行sizeof取值。不管指針是什麼類型,只要是指針,sizeof運算結果都是4

函數類型以其返回值的類型做爲自身類型。注意不能對返回void的函數進行sizeof取值,也不能對函數指針進行sizeof取值

int f1()          double f2()        void f2()

{             {              {

  return 0;          return 0;        return 0;

}             }             }

cout<<sizeof(f1())<<endl;        //f1()返回值爲int,所以被認爲是int

cout<<sizeof(f2())<<endl;        //f2()返回值爲double,所以被認爲是double

cout<<sizeof(f3())<<endl;        //錯誤,沒法對void類型使用sizeof

cout<<sizeof(f1)<<endl;         //錯誤,沒法對函數指針使用sizeof

下面是數組的sizeof

char a[] = "abvdef";

char b[] = {'a','b','c','d','e','f'};

int c[20] = {3,4}

char d[2][3]={"aa","bb"};

cout<<sizeof(a)<<endl;  //輸出7,表示字符串

cout<<sizeof(b)<<endl;  //輸出6,僅表示字符數組

cout<<sizeof(c)<<endl;  //輸出80

cout<<sizeof(d)<<endl;  //輸出6

cout<<sizeof(*a)<<endl;  //輸出1

cout<<sizeof(*b)<<endl;  //輸出1

cout<<sizeof(*c)<<endl;  //輸出4

cout<<sizeof(*d)<<endl;  //輸出3

若是字符數組表示字符串的話,數組末尾自動插入的'\0',在sizeof時不能遺漏,因此數組a的大小在定義時未指定,編譯時給它分配的空間是按照初始化的值肯定的,也就是7。c是多維數組,佔用的空間大小是各維數的乘積,也就是6,能夠看出,數組的大小就是它在編譯時被分配的空間,也就是各維數的乘積,也就是6。能夠看出數組的大小就是它在編譯時被分配的空間,也就是各維數的乘積*數組元素的大小。對應二維數組d,*d等價於d[0](d[0]是一維數組長度爲3),因此sizeof(*d)等於3

C語言中多維數組的實現是經過一維數組實現的,也就是說對於多維數組,你所操做的數組元素有可能自己就是一個數組。這是應該引發注意的。數組的大小是各維數的乘積*數組元素的大小

向函數開通傳遞數組,數組將會退化爲指針,失去了原有數組的特性

int GetStrLength(char str[])            int main()

{                         {

  return sizeof(str);                 char szStr[] = "abcdef";

}                           cout<<"sizeof(szStr[])="<<GetStrLength()<<endl;

                          }        //輸出sizeof(szStr[])=4

struct的sizeof爲CPU對齊問題

對一個空struct結構體取sizeof運算,運算結果爲1而不是0。這是編譯器爲了保證此空struct存在而專門分配的這一字節,若是存在結構體嵌套,不管外層仍是內層均需採用內存對齊

class數據結構的sizeof問題

1.不含繼承和static成員變量的類

此種狀況下,類類型和struct類型對象大小的處理方式相同,只需考慮對齊方式便可,例如

class CA

{

};

class CB

{

  private:

    int m_nValue;

    double m_dValue;

  public:

    int GetInt() {return m_nValue;}

    double GetDouble(){return m_dValue;}

};

cout<<"sizeof(CA)="<<sizeof(CA)<<endl;

cout<<"sizeof(CB)="<<sizeof(CB)<<endl;

運行結果以下:

sizeof(CA) = 1    sizeof(CB) = 16

總結:

不含繼承和static成員變量的類,類對象大小計算遵循下面的準則

和struct複合類型同樣,空的class一樣也佔用1字節

class類型和struct同樣採用對齊來計算對象的大小

計算類對象的大小時,類的成員函數不佔用對象空間,只需考慮類中數據成員的大小便可。由於它們爲全部的類對象共享,存儲於代碼段中

2.包含繼承和static成員變量的類

在單繼承狀況下,只要class中存在virtual函數,在編譯時編譯器就會自動插入一個指向虛函數表的指針vptr(大小爲4字節)。不一樣的編譯器,vptr插入的位置可能不一樣,VC編譯器插入vptr的位置通常從數據成員開始。而static成員是分配在全局區爲類的全部對象共享(VC編譯器可能爲了方便將其放入文字常量表),sizeof時不該計入static成員。

class CBase

{

  public:

    virtual void foo(){};

  private:

    int m_nValue1;

}

class CStaticClass

{

  private:

    static double m_dValue;

 

};

class CDrivedA:public CBase

{

  private:

    virtual void foo(){}

  private: 

    int m_nValue2;

};

int main()

{

  cout<<"sizeof(CStaticClass)="<<sizeof(CStaticClass)<<endl;

  cout<<"sizeof(CBase)="<<sizeof(CStaticClass)<<endl;

  cout<<"sizeof(CDrived)="<<sizeof(CStaticClass)<<endl;

  return 0;

}

執行結果爲:

sizeof(CStaticClass)=1

sizeof(CBase)=8

sizeof(CDrived)=12

對於類CStaticClass,雖然包含了一個static double類型的成員變量,可是CStaticClass大小倒是1,因此能夠看出static成員變量不佔用對象大小。對於CBase類只包含一個成員變量,理論大小應該是4,而實際是8,因此class中存在virtual函數,編譯器就會自動插入一個指向虛函數表的指針vptr(大小爲4字節)

影響類大小的因素總結以下:

1.類的非static類型數據成員

2.類是否存在virtual成員函數

3.當前編譯所採用的對齊方式

相關文章
相關標籤/搜索