初始化(Initialize)的動作雖不起眼,但有它的重要性。
zero-initialize(以0做初始化)並不是在宣告時必做的事情,可以使用有意義的值去初始化物件,此篇文章
只是介紹zero-initialize的方法,請記得並沒有硬性規定要zero-initialize,當存在有意義的初始值時,則
就可以利用有意義的初始值去初始化,若沒有實際意義的初始化的值時,可以建議以0做初始化。
先來討論基本的(fundamental)型態(char,int,float,double,bool),怎麼以零初始化這些型態呢?
如下:
char ch = 0;
int iNum = 0;
float fNum = 0.0f;
double dNum = 0.0;
bool bFlag = 0;
0是一個通用的初值設定(initializer,或翻作初始式),0會轉型(cast)成所初始化的型態,
是一個很特別的值(special value),在C++中有另外一種寫法,但效果是一樣的:
char ch = char();
int iNum = int();
float fNum = float();
double dNum = double();
bool bFlag = bool();
這種方式並沒有什麼,有看過一次你就懂了。對於指標型態的變數是否適用呢?
char *pCh = 0;
int *pi = 0;
float *pf = 0;
double *pd = 0;
在C/C++的定義中,聲明指標型態中有一個特別的值"null pointer",用來與其它指標的值(物件的位址或函式的位址)
做區別,"null pointer"是指沒有指向任何的物件,但它與未初化的(uninitialized)指標不同,未初始化的指標可能指
向任何的位址。在C語言中,指標型態的變數若卻初始化成"null pointer",建議以NULL來初始化,而C++語言中,則
建議以0來做初始化。在C++中NULL的定義是0,可以在標題檔中找到它的定義:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
在C++中值接使用0能避免macro的呼叫,0與NULL在C++是一樣的,但C++之父建議使用0,詳情請參考0 and NULL
除了指標型態的變數,也包括指標變數哦!所以可以使用0來初始化。
對基本型態而言,如果以動態配置的方式(dynamic allocate)產生出的記憶體,要如何以0初始化呢?
Ex:
C語言:
char* dyMem = (char *)malloc(sizeof(char));
if( dyMem == NULL ) //動態配置失敗,會傳回NULL
{
printf( "Dynamic allocated failed! ");
...... //處理動態配置失敗
}
else
{
*dyMem = 0; //設為0
}
or
char* dyMem = NULL ; //初始化為"null pointer"
if( (dyMem = (char *)malloc(sizeof(char)))==NULL )//動態配置失敗,會傳回NULL
{
printf( "Dynamic allocated failed! ");
...... //處理動態配置失敗
}
else
{
*dyMem = 0; //設為0
}
or
char* dyMem = (char *)calloc(1,sizeof(char));
if( dyMem == NULL ) //動態配置失敗,會傳回NULL
{
printf( "Dynamic allocated failed! ");
...... //處理動態配置失敗
}
C++語言:
char* dyMem = new char(0); //動態配置並初始化為0
if( dyMem == 0 ) //動態配置失敗,會傳回0
{
cout << "Dynamic allocated failed! " ;
...... //處理動態配置失敗
}
or
char* dyMem = 0; //初始化為"null pointer"
if( (dyMem = new char(0)) == 0 ) //動態配置失敗,會傳回0
{
cout << "Dynamic allocated failed! " ;
...... //處理動態配置失敗
}
C語言當中是無法利用malloc()在動態配置的動作下初始化記憶體空間,而必須自己加上設定為0的動作,
若不加的話,記憶體的內容是前次程式所殘留下來的無效值。而可以利用calloc()來配置出每個bit都是0的記憶
體空間,所以就不必做zero-intialize。
C++中可以在動態配置的動作下初始化單一元素的記憶體空間,不須自己加上去。
C++與C動態配置的差別在於new會呼叫建構子而malloc/calloc不會呼叫建構子,請不要混用。
之前介紹是單一的變數,若是同一型態所構成的陣列,能不能以0初始化呢?
char chArray[10] = { 0 };
int iArray[10] = { 0 };
float fArray[10] = { 0.0f };
double dArray[10] = { 0.0 };
上面的陣列都有10個元素(element),每個元素的型態都是一樣的,只有single element initializer(單一元素初值設定)
,對陣列[0]以zero做初始化,雖只有單一元素初值設定,不過剩餘的9個元素依然會被隱含的初始化為zero,有點不清楚
沒有關係,以下是怎麼初始化呢?
int iArray[10] = { 1,2,3,4 };
iArray[0]會初始化為1
iArray[1]會初始化為2
iArray[2]會初始化為3
iArray[3]會初始化為4
其餘iArray[4] ~ iArray[9] 會被隱含的初始化為0。
註:隱含的是意思就是說無法從程式碼中看到元素初始化的動作,而是電腦加上去的。
若以動態配置的方式所產生的陣列,要如何初始化呢?不管是在C或C++動態配置的方式都無法同時初始化值,必
須自己加上去這個動作。當然可以使用迴圈的方式一個一個是設定為0,但有另外的選擇,可以使用memset函數來
處理,它的函式宣告是這樣的:
void * memset ( void * buffer, int c, size_t num );
以byte的方式,從buffer所指向的位置,填補num個字元c,如:
C語言:
#define N 10 //N表示動態配置出的記憶體大小
int* dyMem = (int *)malloc(sizeof(int)*N);
memset(dyMem, 0, sizeof(int)*N);
or
#define N 10
int *dyMem = (int *)calloc(N,sizeof(int));
C++:
const int N = 5; //N表示動態配置出的記憶體大小
int* dyMem = new int[5];
memset(dyMem, 0, sizeof(int)*N);
接下來要介紹,不同資料型態所構成的型態,稱為Aggregate type。有struct、class、union、array。
什麼是Aggregate type呢?節錄至C++ IOS/IEC section 8.5.1
Aggregate是一個陣列(array)或類別(class)/結構(struct),而類別(class)/結構(struct)的條件有四:
•沒有使用者所宣告的建構子(no user-declared constructors)
•沒有私有或保護等級的非靜態資料成員(no private or protected non-static data member)
•沒有基礎類別(no base classes)
•沒有虛擬函數(no virtual functions.)
Note:一個Aggregate陣列(array)或類別(class)/結構(struct)可能含有一個類別(class)/結構(struct)型態的成員
,成員具有使用者所宣告的建構子。所以具有使用者所宣告建構子的資料成員,是aggregate type。
請問一下何謂使用者宣告的建構子呢?其實就是非編譯器所產生的建構子,在C++ ISO/IEC section 12.1 第5 paragraph
中是這樣描述的:
如果類別中沒有任何使用者所宣告的建構子,就會有一個預設建構子被編譯器暗中的宣告出來,這一個被編譯器隱含
出來的預設建構子是一個沒有用處(trivial)的建構子。
作者認為,編譯器所產生的建構子被稱為non-user-declared constructors(非使用者宣告的建構子)。
另外 section 8.5.1 Aggregates 中第14 paragraph說明有關static aggregate物件初始化的方式,這與另一篇文章
compile-time最後所描述的內容有關,可以參考看看。
清楚何謂Aggregate type之後,Aggregate type可分為POD Aggregate type及non-POD Aggregate type
POD Aggregate type主要限制是不能擁有下列的描述:
•non-static data (including arrays) of any pointer-to-member type
•non-static data (including arrays) of any non-POD class type
•non-static data of any reference type
•user-defined copy assignment operator
•user-defined destructor
處處是定義,僅做參考。
註:使用編譯器是VC6.0,這是一個與C++ Standard不完全相容的的編譯器
所以很多的VC6.0並無法完全與標準相容,所以在編譯有關C++標準的程式,可能會出現問題。
但在Visual C++ 7.1 Version已經十分相容與C++ standard,但仍有出入,可以在MSDN中
查到出入與支授的部份:
Standard Compliance Issues in Visual C++
另外有一篇C++ Primer作者Stanley Lippman受訪問的文章,裡面有說到VC++的過去、現在與未來改良方向:
《對新任微軟Visual C++架構設計師的採訪》
開始介紹如何以0來初始化POD Aggregate struct,如下:
struct student
{
unsigned int iNum;
char* chName;
};
...
void main()
{
struct student stu1 = { 0 };
...
}
iNum會被{ 0 }中的0初始化為0,而chName則是暗中被初始化為0,這道理與陣列是一樣的。
在C++中宣告式中的struct可以不寫,但C語言中不能省略。Aggregate type初始化的方式須使用"{ initializer-list }"
,但若是以相同Aggregate型態的物件來初始化時,則可以使用"( )"的方式來初始化,為什麼呢?明明Aggregate type
中不沒有constructors(包括copy constructor 、no-argument constructor),那是因為compiler會有預設的copy
constructor及no-argument constructor,預設的copy constructor會以memberwise的方式將用來初始化物件內的值
逐一複製到被初始化的物件中,而no-argument constructor是一個trivial的建構子,來看看例子:
struct student
{
int iNum;
char sex;
};
int main()
{
struct student s1 = { 10 , 'M' };
struct student s2(s1);
struct student s3;
cout<<s1.iNum<<" "<<s1.sex<<endl
<<s2.iNum<<" "<<s2.sex<<endl
<<s3.iNum<<" "<<s3.sex<<endl;
return 0;
}
s2就是呼叫compiler所產生的預設的copy constructor
s3就是呼叫compiler所產生的預設的no-argument constructor,但這個constructor是trivial,沒有任何的作用產生
所以s1印出來應該是10 M而s2也是一樣,而s3印出來無效的值。清楚了吧!!
若動態配置的方式與陣列一樣,舉一個C++的例子:
struct student
{
int iNum;
char sex;
};
int main()
{
const int N = 5;
student *pStu = new student[N];
memset(pStu,0,sizeof(student)*N);
return 0;
}
注意請不要將sizeof(student)改為(sizeof(int)+sizeof(char)),兩個在一般情況下是不一樣的。
因為有struct member alignment的問題,這與記憶體存取的效率有關,暫不討論,請參考相關的文章。
介紹完由基本型態所型態的POD Aggregate type之後,來簡單介紹由有使用者所宣告建構子的資料成員所組成的
POD Aggregate type,如下:
#include <iostream>
#include <string>
using namespace std;
struct student
{
int iNum;
string Name;
};
void main()
{
student st1 = { 0 };
}
使用VC6.0編譯的結果:
error C2552: 'st1' : non-aggregates cannot be initialized with initializer list
error C2552 在MSDN中有說明原因:
Compiler Error C2552
其中有一句話
《In addition, Visual C++ does not allow data types in an aggregate that themselves contain constructors.》
(譯:除此之外,Visual C++6.0不允許aggregate中的資料型態含有建構子)
所以在VC6.0中,禁止使用含有建構子的型態作為aggregate type的資料成員,但在Visual C++ .Net 2003中,
已支援此項目,參考這裡:
Support Aggregate Initialization
如果是使用G++編譯器的話,並不會產生如何的問題,可以放心使用。
為什麼會照成這樣的結果呢?因為就在於每個編譯器符合標準C++的程度不同。
但Visual C++已經在往這方面改進了。可以參考之前所介紹的文章對新任微軟Visual C++架構設計師的採訪
POD Aggregate type大致介紹到此,而有關non-POD Aggregate type,這需要搭配到constructor,可以
參考其它的文章。
Written By James On 2004/02/08