2009年1月11日 星期日

變數初值

程式寫這麼久,以前經常會遇到,"啊? C++不可以這樣嗎?"或者是"這樣寫有差別嗎?"的驚訝,想說這可能不只是只有我有的感覺,因此把這些東西分享出來,給大家做參考,你也可以參考Effective C++這本書籍:

初值化(Initial)和給定數值(Assign)

(a) 什麼是initial?
變數宣告的時候,直接給了數值,像是:
int a=10;
(b)什麼是Assign?
變數宣告完後,再另外給定數值,像是:
int a;
a=10;

這兩種寫法有差異嗎? 對build-in類型,像是int, float, ...沒有差別,但是對物件而言,就決定了一點點效能上的差別了,我以一個類別A來解釋:
class A
{
}; // 什麼事都沒有的類別
使用assign你必須先宣告一個物件 A a;
此時,物件已經執行了一次建構式,把內部數值初始一次,你也許會問,類別中沒有建構式啊?何來呼叫建構式? 答案是,類別雖然沒有設計建構式,但是C++標準中有幾個函式你不需要寫,C++都會幫你生出來,而你寫了,就不會幫你產生,分別是:
預設建構式: A::A(){}
複製建構式: A::A(const A &a ){}
解構式: A::~A(){}
指定運算元: A::operator =( const A& a ){}

所以當你下A a;a=b;這樣的程式時,已經執行了一次建構式,之後再一行assign,物件又會執行一次"指定運算元",所以你跑了兩次物件的函式。
而Initial呢? 看起來A a=b; 這樣的程式碼,同樣是執行一次建構式,然後再執行一次指定運算元?答案是,編譯器直接執行複製建構式,所有數值一次搞定,比起Assign快了一倍,也許不多,但是,如果你的物件內部,變數數量超多,一個assign需要執行的指令碼很多,那也許就有點可觀了:)

說到建構式,也許有人會問,他看過以下兩種程式碼,有差別嗎?
A::A()
: a(120), c(0.3)
{
}

A::A()
{
a=120;
c=0.3;
}

答案依照結果論而言,沒有差別,但是同樣潛在一點點效能上的差別,首先第一種方法A()後面接著的":a(...." 那行稱為 "member initializer list",專供給物件內成員變數的初值化,與呼叫基底類別的建構式所用,在這個時候,物件的記憶體還在配置中,C++會一邊配置記憶體,一邊把你指定的數值塞進去,所以,變數給定數值的次數只有一次,而下面那種A::A(){ ... } 的方式呢? 此時記憶體已經配置完成,變數數值已經給定過了一次,而此時又再設定一次,所以跑了兩次數值給定的程序,所以會慢一滴滴。

member initializer list這麼好用,有什麼限制嗎?
有的,指定順序要跟變數宣告的順序一致,原因說過,C++會一邊配置記憶體,一邊給定數值,如果順序不對,以下的程式就可能出錯:
class A
{
vector buf;
int size;
A() : buf(size), size(20)
....
};

你希望配置大小為size的vector的陣列,但是在 呼叫buf(size)建構時,size記憶體還沒配置出來,根本不知道size的數值,而,size(20)初值後,buf(size)已經執行過了,所以會發生錯誤,所以正確的順序:
class A
{
int size;
vector buf;
A() : size(20), buf(size)
....
};

沒有留言:

張貼留言