close
一、函式
程式中超過兩次以上重複使用的程式碼,可以考慮將之定義為函式,以便重覆呼叫使用,降低相同程式片段的維護成本。



函式簡介
函式用來將程式組織為一個小的、獨立的運行單元,一個函式可以接受資料,並運行其中的算法,最後將結果傳回。
在C++中函式的組成主要包括四個部份:返回值、函式名稱、參數列與函式主體。
前三者被稱為函式宣告或函式原型 (Function prototype) ,在C++中規定函式被呼叫之前,必須先作宣告的動作,否則會出現編譯錯誤,函式原型描述的是函式的介面,通常宣告在一個獨立的表頭檔中,而被含入每一個想要呼叫函式的檔案中。
表頭檔案被儲存為.h檔案,接著您可以根據函式原型來實作函式主體。
在含入表頭檔案時,如果表頭檔案與含入表頭檔的文件在同一個目錄下,那麼就使用雙引號""來包括表頭檔案名稱,如果是標準或專案專屬的表頭檔,例如C++ 的標準表頭檔,那麼則使用角括號<>來括住,編譯器在尋找時就會從所設定的目錄中開始尋找。
在編譯時期,編譯器會對被呼叫函式作檢查,如果無法根據函式名稱、提供的引數型態與函式宣告上參數型態來決定被呼叫的函式,則會出現編譯錯誤,這就是必須先作函式宣告的原因,這對編譯器的檢查工作是必要的資料。



行內函式 (Inline function)
在呼叫函式時會需要分配記憶空間因而需要額外的資源負擔,像 pow2() 這樣的小函式,可以「建議」編譯器將之設定為「行內函式」(Inline function),如果建議被採納,則該函式會自動在呼叫點展現為程式碼,行內函式建議可以直接定義於表頭檔案中。
行內函式只能建議編譯器,也就是說建議並不一定會被採納,這視您的編譯器而定,像是使用到goto、static變數、迴圈、switch等等,這些編譯器可能不接受行內函式的建議,遞迴函式也無法在呼叫點展開,一個數千行的函式也不適合在呼叫點展開,如果編譯器拒絕將函式展開,它會將該函式視為一般函式進行編譯,inline的建議會被忽略。



預設引數
C++允許您使用預設引數,預設引數的使用在一開始的函式原型宣告中進行定義,而之後的函式定義則無需再宣告。
您可以指定兩個以上的預設引數,然而必須注意的是,預設引數一旦出現在參數列,則其右邊的參數也必須設定預設引數。
如果程式中有兩個以上的預設引數,則在程式呼叫時就必須注意,因為預設引數的使用是以引數的順序由左至右來進行的。



重載函式 (Overloaded function)
C++支援函式「重載」(Overload),重載機制為類似功能的函式提供了統一的名稱,但是根據參數列個數或型態的不同,而自動呼叫對應的函式。
函式過載時可以根據函式參數列的參數資料型態,也可以根據參數的個數,或是兩個的結合,但要注意的是,返回值型態不能用作判斷函式重載的依據。



變數可視範圍 (Scope)
在C++中,談到可視範圍(scope)可分為許多層次,也可以談到很複雜,在這邊先談談「全域變數」(Global variable)、「區域變數」(Local variable) 與「區塊變數」(Block variable)。
全域變數是指直接宣告在(主)函式之外的變數,這個變數在整個程式之中都「看」得它的存在,而可以呼叫使用。
全域變數的生命週期始於程式開始之時,終止於程式結束之時。
區域變數是指宣告在函式之內的變數,或是宣告在參數列之前的變數,它的可視範圍只在宣告它的函式區塊之中。
區域變數的生命週期開始於函式被呼叫之後,終止於函式執行完畢之時。
區塊變數是指宣告在某個陳述區塊之中的變數,例如while迴圈區塊中,或是for迴圈區塊。
區塊變數的生命週期在區塊結束之後,就會自動消失。
當一可視範圍大的變數與可視範圍小的變數發生同名狀況時,可視範圍小的變數會暫時覆蓋可視範圍大的變數,稱之為「變數覆蓋」
再來介紹static變數,當變數有宣告時加上static限定時,一但變數生成,它就會一直存在記憶體之中,即使函式執行完畢,變數也不會消失。
extern可以聲明變數會在其它的位置被定義,這個位置可能是在同一份文件之中,或是在其它文件之中。
要注意的是,extern 聲明變數在其它位置被定義,如果您在使用extern時同時指定其值,則視為在該位置定義變數,結果就引發重覆定義錯誤。



遞迴 (Recursion)
遞迴(Recursion)是在函式中呼叫自身同名函式,而呼叫者本身會先被置入記憶體堆壘中,等到被呼叫者執行完畢之後,再從堆壘中取出之前被置入的函式繼續執行。
「堆疊」(Stack)是一種「先進後出」的資料結構,就好比您將書本置入箱中,最先放入的書會最後才取出。
那麼使用遞迴好還是使用迴圈求解好?
這並沒有一定的答案。不過通常由於遞迴本身有重複執行與記憶體堆疊的特性,所以若在求解時需要使用到堆疊特性的資料結構時,使用遞迴在設計時的邏輯會比較容易理解,程式碼設計出來也會比較簡潔,然而遞迴會有函式呼叫的負擔,因而有時會比使用迴圈求解時來得沒有效率,不過迴圈求解時若使用到堆疊時,通常在程式碼上會比較複雜。



參數的傳值、傳參考
C++在呼叫函式時的參數傳遞方式主要有兩種:傳值(Pass by value)、傳參考(Pass by reference)。
參數傳遞時的傳值就是傳送(變數)值給函式上對應的參數,值被複製一份給參數,傳遞者與接受者兩個變數彼此各佔有一個記憶體,互不相干。
在傳值應用上,您也可以將變數的記憶體位址值取出,傳遞給指定的指標參數,只要使用&運算子就可以了。
在函式上宣告指標參數的目的,通常目的是若作為引數的變數值同一位址上,在函式中若有變動該位址上的值時,呼叫者也可以保留這份變動的結果。
參考(Reference)型態代表了變數或物件的一個別名(Alias),參考型態可以直接取得變數或物件的位址,並間接透過參考型態別名來操作物件,作用類似於指標,但卻不必使用指標語法,也就是不必使用*運算子來提取值。
傳參考使用的時機使在於您希望傳遞的參數,在函式中若有變動時,呼叫者也可以保留這份變動的結果。
另一個使用的時機則是在大型物件的傳遞,如果使用傳值呼叫,大型物件在複製時會佔用一塊大的記憶體,此時您可以使用傳參考,如果您不想在函式中改變物件的話,而只想讓物件提供資訊,則可以加上const修飾。



return 的傳值、傳參考
在定義函式時,一定要定義函式的傳回值型態,如果函式不傳回值,則使用void表示不傳回任何數值;一旦指定函式的傳回值不為void,則在函式中一定要使用return傳回一個數值,否則編譯器將回報錯誤。
事實上您也可以傳回一個指標或是參考,傳回指標通常意味著您要對這個指標所指向的記憶體位置作取值或更動的動作。
注意如果您不是使用 new來配置,則在副函式中所宣告的變數記憶體,在函式執行結束後都會自動消失,則您傳回指標值也就沒有意義,也會造成存取錯誤,因為該塊記憶體在副函式執行完畢後已經自動回收了。
同樣必須注意的是,函式中的區域變數在函式開始時被配置,在函式結束後所佔有的記憶體位址也會被清除,絕對不要傳回一個區域變數的位址給呼叫者,或是以傳參考的方式傳回一個區域變數,因為您所存取的記憶體位址中資料是未知的,所以結果是不可預期的。



不定長度引數 (Variable-length argument)
在定義函式時,有時我們並無法事先得知要傳遞的參數個數,這邊介紹不定長度引數(Variable-length argument)的使用為了要使用不定長度引數,您必須含入cstdarg表頭檔案:
#include
不定長度引數使用幾個識別字來建立不定長度引數:
* va_list
一個特殊的型態(type),在va_start、 va_arg與va_end三個巨集(macro)時當作參數使用。
* va_start
啟始不定長度引數的巨集。
* va_arg
讀取不定長度引數的巨集。
* va_end
終止不定長度引數的巨集。
在宣告不定長度引數時,您在函式定義時使用 "..."表示將使用不定長度引數,而之前必須告知將傳遞幾個不定長度引數。
在使用va_arg巨集取出引數內容時,您必須指定將以何種資料型態取出。



函式指標
程式在執行時,函式本身在記憶體中也佔有一個空間,而函式名稱本身也就是指向該空間位址的參考名稱,當呼叫函式名稱時,程式就會去執行該函式名稱所指向的記憶體空間中之指令。
您可以宣告函式指標,並讓它與某個函式指向相同的空間,函式指標的宣告方式如下所示:
傳回值型態 (*指標名稱)(傳遞參數);
一個函式型態由傳回值型態與參數列決定,不包括函式名稱,一個函式指標可指向具有相同型態的函式,也就是具有相同傳回值型態和參數列的函式。
如果函式帶有參數,則函式指標本身的宣告也必須指定相同的參數型態與個數。
您也可以宣告函式指標陣列,例如:
bool (*compare[10])(int, int);
上面這個宣告產生具有10個元素的陣列,可以儲存10個sort函式型態的位址,不過這樣的宣告實在難以閱讀,可以使用typedef來改進:
typedef bool (*CMP)(int, int);
CMP compare[10];
arrow
arrow
    全站熱搜
    創作者介紹

    silverfoxkkk 發表在 痞客邦 留言(0) 人氣()