Microsoft PowerPoint - 05-func.ppt

Similar documents
C++ 程式設計

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc

Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc

新版 明解C++入門編

FY.DOC

C 1

第1章

新・明解C言語入門編『索引』

CC213

c_cpp

C/C++语言 - C/C++数据

untitled

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.07.doc

Microsoft PowerPoint - C-Ch10.ppt

CC213

( CIP) /. :, ( ) ISBN TP CIP ( 2005) : : : : * : : 174 ( A ) : : ( 023) : ( 023)

CC213

2013 C 1 # include <stdio.h> 2 int main ( void ) 3 { 4 int cases, a, b, i; 5 scanf ("%d", & cases ); 6 for (i = 0;i < cases ;i ++) 7 { 8 scanf ("%d %d

C 1 # include <stdio.h> 2 int main ( void ) { 4 int cases, i; 5 long long a, b; 6 scanf ("%d", & cases ); 7 for (i = 0;i < cases ;i ++) 8 { 9

Microsoft PowerPoint - C-Ch08.ppt

運算子多載 Operator Overloading

C/C++ 语言 - 循环

untitled

untitled

Scott Effective C++ C++ C++ Roger Orr OR/2 ISO C++ Effective Modern C++ C++ C++ Scoot 42 Bart Vandewoestyne C++ C++ Scott Effective Modern C++ Damien

nooog

untitled

第3章.doc

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

Microsoft PowerPoint - 07-overloaded.ppt

Microsoft PowerPoint - ch11.ppt

Microsoft PowerPoint - string_kruse [兼容模式]

chap07.key

四川省普通高等学校

untitled

C/C++语言 - 分支结构

Python a p p l e b e a r c Fruit Animal a p p l e b e a r c 2-2

PowerPoint Presentation

untitled

ebook39-5

概述

Java

Template

Strings

Microsoft PowerPoint - 20-string-s.pptx

untitled

BOOL EnumWindows(WNDENUMPROC lparam); lpenumfunc, LPARAM (Native Interface) PowerBuilder PowerBuilder PBNI 2

untitled

Microsoft Word - About_C_PointerAdvanced.doc

C

epub 33-8

投影片 1

3.1 num = 3 ch = 'C' 2

提纲 1 2 OS Examples for 3

华恒家庭网关方案

untitled

C C C The Most Beautiful Language and Most Dangerous Language in the Programming World! C 2 C C C 4 C Project 30 C Project 3 60 Project 40

C语言的应用.PDF

運算子多載 Operator Overloading

科学计算的语言-FORTRAN95

Microsoft PowerPoint - Fig03_Stack.ppt [相容模式]

全国计算机技术与软件专业技术资格(水平)考试

Oracle Solaris Studio makefile C C++ Fortran IDE Solaris Linux C/C++/Fortran IDE "Project Properties" IDE makefile 1.

第5章修改稿

Microsoft PowerPoint - Class5.pptx

穨control.PDF

,,,,,,,,,, ( http: \ \ www. ncre. cn,, ) 30,,,,,,,, C : C : : 19 : : : /16 : : 96 : : : ISBN 7

Microsoft Word - p11.doc

2013 C 1 #include <stdio.h> 2 int main(void) 3 { 4 int cases, i; 5 long long a, b; 6 scanf("%d", &cases); 7 for (i = 0; i < cases; i++) 8 { 9 scanf("%

ebook8-30

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File

Microsoft Word - CPE考生使用手冊 docx

Microsoft Word - 01.DOC

Microsoft Word - PHP7Ch01.docx

錄...1 說...2 說 說...5 六 率 POST PAY PREPAY DEPOSIT 更

提问袁小兵:

ebook14-4

mvc

untitled

untitled

学习MSP430单片机推荐参考书

INTRODUCTION TO COM.DOC

Microsoft Word - 物件導向編程精要.doc

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++;

Microsoft PowerPoint - 11_Templates.ppt

Fuzzy Highlight.ppt

6 C51 ANSI C Turbo C C51 Turbo C C51 C51 C51 C51 C51 C51 C51 C51 C C C51 C51 ANSI C MCS-51 C51 ANSI C C C51 bit Byte bit sbit

Microsoft PowerPoint - ch09_AEL0080.ppt

Microsoft PowerPoint - L17_Inheritance_v4.pptx

_汪_文前新ok[3.1].doc

热设计网

The golden pins of the PCI card can be oxidized after months or years

Microsoft Word - C-pgm-ws2010.doc

Microsoft PowerPoint - 04-array_pointer.ppt

C++ 程序设计 OJ9 - 参考答案 MASTER 2019 年 6 月 7 日 1

ebook15-C

上海市本科教学质量年度报告

Ps22Pdf

51 C 51 isp 10 C PCB C C C C KEIL

Transcription:

函式 (Functions) 用途 定義語法 使用前必須先宣告 呼叫 傳回值的資料型態 參數列 引數的傳遞方式 陣列參數 預設的引數值 不定數目的參數 return 敘述 遞迴函式 行間函式 處理指令行的選項 函式指標 與其它語言寫成的函式連結 Function - 1 函式的用途 程式碼的再利用 (code reuse) 便利模組化設計的 implementation 便利程式的維護 便利程式的移植 Function - 2

函式的定義語法 函式傳回值的資料型態 函式名稱 參數列 type function_name ( parameter list ) // function body // 包含變數宣告與指令敘述 Function - 3 範例 : // prog.cpp #include <iostream> using namespace std; 主函式 : 程式執行時的進入點 int max (int a, int b) return (a >= b)? a : b; int main () cout << max(3, 4) << endl; return 0; 函式的定義 呼叫函式 max Function - 4

函式使用前必須先宣告 函式在被呼叫之前必須先定義或宣告 譬如 : 在前一頁的範例中, 函式 max 的定義寫在呼叫之前 若函式 max 的定義寫在 main 函式之後的話, 則我們必須在呼叫之前加以宣告 宣告方式如下 : int max (int a, int b); int main () int max (int a, int b) 函式宣告 (declaration) 或稱函式原型 (prototype) Function - 5 外部函式 (external function) 是指定義在其它檔案之中的函式 外部函式必須先宣告才能呼叫 其宣告方式如下 : extern type function_name ( parameter list ); 比方說, 在之前的例子中, 如果函式 max 是存在 max.cpp, 則主程式檔 prog.cpp 必須改寫成 : // prog.cpp #include <iostream> using namespace std; extern int max (int a, int b); int main () // 此處同前, 故省略 而且以下列的方式來編譯 : CC prog.cpp max.cpp -o prog Function - 6

外部函式通常是宣告在.h 的標頭檔之中 比方說, 我們可以為 max.cpp 寫一個對應的 max.h 檔, 其中包含以下的宣告 : extern int max (int a, int b); 則主程式檔 prog.cpp 可以改寫成 : // prog.cpp #include <iostream> #include max.h using namespace std; int main () // 此處同前, 故省略 然後用前一頁的方式來編譯 Function - 7 從前一頁的說明, 你現在應該明白為什麼使用系統函式時, 必須在程式中加入適當的系統標頭檔了吧! 譬如, 如果想使用計算字串長度的系統函式 strlen, 你就必須用以下的方法加入系統標頭檔 string.h: #include <string.h> 理由是 : 系統函式屬於外部函式, 系統標頭檔包含了它們的宣告 如果你沒有加入適當的系統標頭檔, 就呼叫某系統函式, 編譯程式會認為該函式並未事先宣告, 因而發出編譯錯誤的訊息 Function - 8

函式的呼叫 函式呼叫採取以下的格式 : function_name ( argument list ) 對於函式呼叫, 編譯程式會檢查下列事項 : 函式名稱是否已經定義或宣告 引數的個數是否等於參數的個數 max (3) // error: 引數只有一個 引數的資料型態是否等於或可轉換至參數的資料型態 max (3.0, 4.0) // ok: double 引數自動轉換成 int 參數 max ( one, two ) // error: char * 引數無法轉換成 int 參數 Function - 9 int add ( int x, int y ) int s; s = x + y; return s; int add2 ( int x, int y ) int s; s = add ( x+1, y+1 ); C x*=2; y*=2; s += add ( x, y ); D return s; main () int x = 3, y = 4, s; s = add2 ( x, y ); B cout << s << endl; return 0; 左邊程式的函數呼叫次序如下 : 作業系統 (OS) main () add2 () add () add () 由上圖可知函數返回的次序剛好與函數呼叫的次序相反, 似乎與堆疊 LIFO 的特性相符 事實上, 堆疊也的確用於函數呼叫中 A B C D Function - 10

每一個程式在執行之前, 系統會配置一個堆疊供其使用 此堆疊稱之為執行堆疊 ( run-time stack ) 其作用如下所述: 呼叫函數時 : 1. 將返回位址 (return address) push 至執行堆疊中 2. 將引數值依序 push 至執行堆疊中 3. 跳到被呼叫的函數 進入被呼叫函數時 : 1. 在執行堆疊中配置空間給局部變數 ( local variables) 2. 開始執行此函數程式碼 Function - 11 離開被呼叫函數時 : 1. 將返回位址之上所佔用的執行堆疊空間 pop 掉 2. 讀出執行堆疊最上面所存的返回位址, 然後將其 pop 掉 3. 將函數傳回值 push 置於執行堆疊中 4. 跳至返回位址繼續呼叫函數的執行 Function - 12

函式傳回值的資料型態 函式必須指定傳回值的資料型態 以下是允許的型態 : 基本型態, 如 int 或 double; 複合型態, 如 int& 或 double *; 自定型態, 如 enum 或 class; void, 代表函式無傳回值 範例 : class Date /* definition */ ; bool look_up (int *, int *); double calc (double); int count ( const string &, char ); Date& calendar ( const char * ); void sum (vector<int>&, int); Function - 13 函式傳回值的資料型態不可以是陣列或函式 範例 : int[10] foo_bar (); // error: 傳回值不可為陣列型態 int* foo_bar (); // ok 函式必須指定傳回值的資料型態 範例 : foo_bar (); int foo_bar (); // error: 未指定傳回值的型態 // ok Function - 14

函式的參數列 函式可以沒有參數, 如 : int foo_bar (); int foo_bar (void); 函式的參數必須個別地宣告, 如 : int foo_bar (int p1, int p2); // ok int foo_bar (int p1, p2); // error 函式的參數名稱必須不同, 如 : int foo_bar (int p1, int p2); // ok int foo_bar (int p1, int p1); // error Function - 15 函式宣告中的參數名稱可以省略, 如 : int foo_bar (int p1, int p2); int foo_bar (int, int); // ok // ok 函式宣告和定義中的參數名稱可以不相同, 如 : int foo_bar (int p1, int p2); int foo_bar (int x, int y) // declaration // definition 不過這樣做容易造成混淆, 應該僅量避免 函式宣告和定義中的參數資料型態必須一致, 否則會造成編譯上的錯誤 Function - 16

若參數不應該在函式中修改其值, 最好將其宣告為 const, 如 : int max (const int x, const int y) return (x >= y)? x : y; 宣告為 const 的參數, 不可以在函式中修改其值 ( 即出現在等號的左邊 ), 如 : void foo (int a); int bar (const int x) int y = x; x = x; x = y + 3; foo(x); foo(const_cast<int>(x)); // ok // error // error // error // ok Function - 17 引數的傳遞方式 如前所述, 呼叫函式時, 引數會透過執行堆疊傳遞至函式的對應參數 C++ 提供以下的兩種傳遞方式 : call by value( 數值傳遞 ) 傳遞引數的值至對應的參數 由於引數和參數是獨立的個體, 所以改變參數的值並不會影響到引數原有的值 call by reference ( 參照傳遞 ) 傳遞引數的 lvalue 至對應的參數 由於引數和參數是指涉同樣的記憶體區域, 所以改變參數的值等同於直接改變引數的值 Function - 18

Call by value( 數值傳遞 ) main () int a = 3, b = 4; cout << max(a,b) << endl; int max (int x, int y) return (x >= y)? x : y; main a 3 傳遞 b 4 傳遞 3 x max 4 y Function - 19 main () int a = 3, b = 4;... swap(a, b);... void swap (int x, int y) int temp; temp = x; x = y; y = temp; main a 3 傳遞 b 4 傳遞 3 x swap 4 y swap 只是交換 x 和 y 的值, 而沒有交換引數 a 和 b 的值 Function - 20

main () int a = 3, b = 4;... swap(&a, &b);... void swap (int *x, int *y) int temp; temp = *x; *x = *y; *y = temp; main a b x y swap swap 利用指標參數 x 和 y 來間接地交換引數 a 和 b 的值 Function - 21 當引數是一個佔據大量記憶體的結構或 class 時, 使用數值傳遞直接拷貝這些記憶體的內容到函式並不是一個有效率的方式 譬如在以下的例子中, 如果 Matrix 物件佔據大量記憶體的話, 則函式 operator+ 的參數定義方式 會使得引數傳遞花費掉過多的時間 : class Matrix /* 矩陣類別的定義 */; Matrix operator+ (Matrix m1, Matrix m2) Matrix result; // 計算 m1 + m2 並把結果存入 result return result; Matrix a, b, c; c = a + b; Function - 22

前一頁所述的問題可以用指標參數的方法來解決, 即 Matrix operator+ (Matrix *m1, Matrix *m2) Matrix result; // 計算 *m1 + *m2 並把結果存入 result return result; 不過這樣一來, 我們就不能寫下列的算式 : Matrix a, b, c, d; d = &a + &b + &c; // error: should be d = &(&a + &b) + &c 因為上式可視為 : d = operator+(operator+(&a, &b), &c); 型態為 Matrix, 而非所需的 Matrix * Function - 23 參照資料型態 參照 (reference) 又稱為別名 (alias) 是用來提供物件的另外一個名稱 就如指標一般, 我們可以利用參照來間接地存取物件的值, 不同的是 : 參照不須要如指標般的繁瑣指涉方式 參照在 C++ 中, 主要是用於提高引數傳遞的效率 參照宣告的基本語法如下 : type &name = var_name; 其中, var_name 必須是之前已經宣告為 type 型態的變數名稱 Function - 24

範例 : int ival; int &refval = ival; // refval is an alias of ival ival = 1024; cout << refval << endl; // print 1024 refval += 100; cout << ival << endl; // print 1124 int *ip = &refval; // ip points to refval (*ip)++; ival 或 refval cout << ival << endl; // print 1125 Function - 25 參照必須設定初值, 如 : int ival; int &refval = ival; int &refval; // ok // error 參照一旦設定之後, 不可以再重新設定參照的對象, 因此以下的指令 : refval = kval; 並不是把 refval 重新設定去參照 kval, 而是把 kval 的值存入 refval 所指涉的變數 參照不是指標, 如 : int ival; int &refval = &ival; // error: refval 是 int, 非 int * int *ip; int *&refptrval = ip; // ok Function - 26

一般參照不可以用來指涉不同型態的物件或常數, 如 : double dval = 3.14; int &r3 = 1024; int &r4 = dval; // error // error 不同型態的物件或常數必須使用常數參照來指涉, 如 : double dval = 3.14; const int &r1 = dval; const int &r2 = 1024; const double &dr = dval + 1.0; // ok // ok // ok 常數參照事實上是指涉一個暫存變數, 譬如 : const int &r2 = 1024; 會被編譯程式轉化成 : int tmp = 1024 const int &r2 = tmp; 不能更改常數參照的值 Function - 27 範例 : double dval = 3.14; const int &r1 = dval; const double &dr = dval; cout << r1 << endl; // print 3 dval = 5.0; cout << r1 << endl; // print 3 r1 = 5; // error cout << r1 + 2 << endl; // print 5 dr = 5; // error: 不能更改常數參照 Function - 28

Call by Reference 參照在 C++ 中最主要是用於 call by reference 你可以用以下的格式來宣告參照參數 : type &para_name 假定函式 foo 的宣告如下 : int foo (int &x); 則函式呼叫 foo(a) 的參數傳遞方式相當於在 foo 中宣告 int &x = a; 使得參數 x 成為引數 a 的別名 在 foo 中對 x 的修改, 等同於對 a 的修改 Function - 29 main () int a = 3, b = 4;... swap(a, b);... void swap (int &x, int &y) int temp; temp = x; x = y; y = temp; main a x swap b y 在 swap 中交換 x 和 y 的值, 等同於交換引數 a 和 b 的值 Function - 30

如果之前的 Matrix 加法改寫如下 : Matrix operator+ (const Matrix &m1, const Matrix &m2) Matrix result; // 計算 m1 + m2 並把結果存入 result return result; 我們就可以寫下列的算式 : Matrix a, b, c, d; d = a + b + c; 因為上式可視為 : d = operator+(operator+(a, b), c); Function - 31 對於不應該在函式中修改的參照參數, 最好用 const 將其宣告成常數參照參數 void foo (const int &x) x = 3; // error 參照參數的對應引數不可以是常數, 常數參照參數則可 void foo (int &x); void bar (const int &x); int a; foo(a); // ok foo(3); // error bar(a); // ok bar(3); // ok Function - 32

陣列參數 在 C/C++ 中, 陣列不是把內容採用數值傳遞的方式整個傳入函式, 而是用指標的方式把陣列第一個元素的位址傳入函式 比方說 : int a[10]; foo(a); 傳入函式 foo 的是 a[0] 的位址, 而不是 a 的 10 個元素 以下三種函式宣告的方式具有相同的意義 : void foo (int *x); void foo (int x[]); void foo (int x[10]); Function - 33 由於陣列的傳遞方式, 在函式中更改陣列參數的元素, 等同於更改陣列引數的元素 譬如 : int main () int a[] = 0, 1; foo(a); cout << a[0] = << a[0] << endl; cout << a[1] = << a[1] << endl; void foo (int x[]) x[0] = 1; x[1] = 0; 所得的輸出結果為 : a[0] = 1 a[0] = 0 Function - 34

由於只傳入陣列第一個元素的位址, 函式因此無法得知陣列之中有多少個元素 然而你可以用以下的幾種方法來解決這個問題 加上一個額外的參數來傳遞陣列的元素個數 int total (int x[], int size) int sum = 0; for (int k = 0; k < size; k++) sum += x[k]; return sum; int a[10] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9; cout << total(a, 10) << endl; // print 45 Function - 35 在陣列的最後一元素中, 存放一個表示結束的特殊值 比方說,C 字串是一個以 null 字元 (0) 結尾的字元陣列 int string_length (char *str) if (!str) return 0; int k = 0, length = 0; while (str[k++]) length++; return length; char s[] = Hello, world! ; cout << string_length(s) << endl; // print 13 Function - 36

把參數宣告成陣列參照 int total (int (&x)[10]) // x 是一個 10 元素陣列的參照 int sum = 0; for (int k = 0; k < 10; k++) sum += x[k]; return sum; int a[10] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9; cout << total(a) << endl; // print 45 int b[5] = 1, 2, 3, 4, 5; int s = total(b); // error Function - 37 預設的引數值 我們可以在函式宣告中指定預設的引數值 (default argument), 來簡化函式的呼叫方式 如 : char *screeninit (int height = 24, int width = 80, char bgchar = ); 其中的三個參數都指定了預設的值 以下都是合法的方式來呼叫 screeninit: screeninit(); // 等同於 screeninit(24, 80, ) screeninit(66); // 等同於 screeninit(66, 80, ) screeninit(66, 256); // 等同於 screeninit(66, 256, ) screeninit(66, 256, # ); Function - 38

呼叫時所缺的引數值是由後補齊 screeninit(? ); // 等同於 screeninit(?, 80, ) screeninit(,,? ); // error 函式的參數可以全部或部份給予預設的引數值 然而, 未指定預設引數值之參數的前面, 不可再宣告具有預設引數值的參數 比方說, 以下是錯誤的宣告方式 : char *screeninit (int height = 24, int width, char bgchar = ); 因為 width 之前的 height 宣告成具有預設的引數值 在設計具有預設引數值的函式時, 應該把變動性大的參數擺在前面, 變動性小的參數擺在後面 以 screeninit 為例, 設計者認為 height 的變動性最大 width 次之 bgchar 最小 Function - 39 預設的引數值不可以重複寫在函式的宣告與定義中 // ff.h int ff (int k = 0); // ff.cpp #include ff.h int ff (int k = 0) // error 一般的習慣是寫在宣告, 並擺在.h 標頭檔之中 如果擺在定義的話, 就只能在同檔案中使用預設引數值的呼叫方式 Function - 40

我們可以為現有的函式加上預設的引數值 比方說 :stdlib.h 中宣告了以下的函式 : int chmod (char *filename, int protmode); 它是用來設定檔案的讀寫模式 如果在某個應用中, 大部份檔案的讀寫模式是唯讀的話, 則我們可以重新宣告如下 : #include <stdlib.h> int chmod (char *filename, int protmode = 0444); 然後用簡化的呼叫方式 : chmod (filename); 把檔案的讀寫模式改成唯讀 Function - 41 已有預設引數值的參數, 不可再宣告預設引數值, 即使值相同也不允許 比方說, 函式 ff 的宣告如下 : 則 // ff.h void ff (int a, int b, int c = 0); #include ff.h void ff (int a, int b, int c = 0); void ff (int a, int b, int c = 1); 但可加入新的預設引數值, 譬如 : // error // error #include ff.h void ff (int a, int b = 0, int c); void ff (int a = 0, int b = 0, int c); void ff (int a = 0, int b, int c); // ok // ok // error Function - 42

引數的預設值除了常數以外, 也可以是一般算式或呼叫其它的函式 int adefault (); int bdefault (int); int cdefault (double = 0); int glob; ff ( int a = adefault (), int b = bdefault (glob), int c = cdefault (), int d = 2 * glob ); 預設值算式是在函式呼叫時才計算, 然後再把結果傳入函式之中 Function - 43 不定數目的參數 C/C++ 允許函式具有不定數目的參數 這類函式的宣告格式如下 : type function_name (para_list, ); 連續三個句點 para_list 代表固定數目的參數, 之後的 代表不定數目的參數 比方說, 由於標準函式 printf 除了第一個參數以外, 之後還可以再接不定數目的參數, 因此它的宣告如下 : int printf(const char *format, ); Function - 44

如何決定參數的個數? 在這種具有不定參數個數的函式中, 我們常用以下兩種技巧來決定有多少參數傳進來 : 把參數個數的資訊存在某個引數傳進來 比方說,printf 的第一個參數存著格式設定字串, 其中包含的格式設定數目即是之後所跟參數的數目 printf( The sum of %s is %d\n, title, total); 在最後一個引數中存放特殊值, 用來指示參數的結束 如 : average(1, 2, 3, 4, 5, -1); // -1 作為最後一個引數 Function - 45 如何取得參數? va_list typedef for pointer to list of arguments defined in STDIO.H defined in stdarg.h void va_start( va_list arg_ptr, prev_param ); va_start sets arg_ptr to the first optional argument in the list of arguments passed to the function. The argument arg_ptr must have va_list type. The argument prev_param is the name of the required parameter immediately preceding the first optional argument in the argument list. If prev_param is declared with the register storage class, the macro s behavior is undefined. va_start must be used before va_arg is used for the first time. type va_arg( va_list arg_ptr, type ); va_arg retrieves a value of type from the location given by arg_ptr and increments arg_ptr to point to the next argument in the list, using the size of type to determine where the next argument starts. va_arg can be used any number of times within the function to retrieve arguments from the list. void va_end( va_list arg_ptr ); After all arguments have been retrieved, va_end resets the pointer to NULL. Function - 46

#include <stdarg.h> int average( int first,... ) average(1, 2, 3, 4, 5, -1); // Returns the average of a variable list of integers. int count = 0, sum = 0, i = first; va_list ap; va_start(ap, first); // Initialize variable arguments. while( i!= -1 ) sum += i; count++; i = va_arg(ap, int); va_end(ap); // Reset variable arguments. return( count? (sum / count) : 0 ); Function - 47 #include <stdarg.h> /* minprintf: minimal printf with variable argument list */ void minprintf(char *fmt,...) var_list ap; // points to each unnamed arg in turn char *p, *sval; int ival; float fval; va_start(ap, fmt); for (p = fmt; *p; p++) if (*p!= '%') putchar(*p); continue; // make ap point to 1st unnamed arg. The sum of %s is %d Function - 48

switch (*++p) case 'd': // %d ival = va_arg(ap, int); printf("%d", ival); break; case 'f': // %f fval = va_arg(ap, float); printf("%f", fval) break; case 's': // %s for (sval = va_arg(ap, char *); *sval; sval++) putchar (*sval); break; default: putchar(*p); break; va_end(ap); // clean up when done Function - 49 return 敘述 return 敘述是用來終止函式的執行, 然後返回到呼叫函式繼續往下執行 它有下列兩種形式 : return; return expression; 第一種形式用於傳回值為 void 的函式, 第二種形式則用於傳回值為其它型態的函式, 此處 expression 計算結果的型態必須能夠轉換成函式傳回值的型態 譬如 : char *foo () return 3; // error: 無法把 int 轉換成 char* Function - 50

範例 : void d_copy (double *src, double *dest, int sz) // copy src array into dest // it is assumed that arrays are same size if (!src!dest) return; if (src == dest) return; if (sz == 0) return; for (int ix = 0; ix < sz; ix++) dest[ix] = src[ix]; // no explicit return necessary Function - 51 函式是以傳值的方式把函式值傳回呼叫函式 譬如 : Matrix grow (Matrix &p) Matrix m; // 把計算結果存入 m return m; 執行完成之後,grow() 函式把存放在變數 m 的計算結果拷貝到呼叫函式 如果傳回值是一個龐大的結構, 就會使函式變得沒有效率 Function - 52

函式可以用傳回參照的方式把函式值傳回呼叫函式 譬如 : Matrix &grow (Matrix &p) Matrix *m; // 動態配置 m 以及把計算結果存入 *m return *m; 執行完成之後,grow() 函式把計算結果存放在指標 m 所指的記憶體, 然後以參照的方式把 m 傳回到呼叫函式 當傳回值是一個龐大的結構時, 傳回參照是最有效率的一種方法 Function - 53 不要傳回函式內部物件的參照 譬如 : Matrix &grow (Matrix &p) Matrix m; // 把計算結果存入 m return m; Matrix x = grow(y); // run-time error grow() 執行完成後, 以參照的方式把 m 傳回到呼叫函式 然而, 離開了 grow() 之後,m 即不存在, 因而造成執行上的錯誤 Function - 54

若要避免意外地更動函式的傳回值, 我們可以把參照傳回值宣告成 const 譬如: int &get_val(int a[], int ix) return a[ix]; int v[5] = 1, 2, 3, 4, 5; get_val(v, 0)++; // v[0] 變成 2 如果把 get_val 的宣告改成 : const int &get_val(int a[], int ix) 就能避免上述的問題 Function - 55 遞迴函式 以直接或間接的方式呼叫自己的函式稱之為遞迴函式 (recursive function) 譬如以下是一個用遞迴方式來計算 n 階乘 : int factorial (int n) if (n < = 0) // termination condition return 1; return n * factorial(n-1); Function - 56

行間函式 只要在函式的宣告之前加上關鍵字 inline 就可以得到行間函式的宣告, 亦即 : inline type function_name (parameter list) 當函式宣告成行間函式時, 編譯程式會儘量用巨集展開的方式來處理函式的呼叫, 而不是用正常的函式呼叫方式 這樣做的目的是為了避免正常函式呼叫所需的額外時間 inline 宣告只是對編譯程式的一種建議, 編譯程式會審視函式的形式, 然後採用適當的處理方式 Function - 57 舉例來說, 假定行間函式 max 的定義如下 : inline int max (int x, int y) return (x > y)? x : y; 編譯程式會把下面的指令 : int m = max (i, j); 用巨集展開的方式轉換成 : int m = (i > j)? i : j; 因此避免了函式呼叫所需的額外時間 Function - 58

雖然使用前一頁的 max 行間函式與使用下面的巨集定義 : #define MAX(x, y) (((x) > (y))? (x) : (y)) 會得到相同的執行結果, 但是 C++ 的行間函式通常比 C 前置處理器的巨集來得有效率, 譬如 : int m = max (i+j, i-j); int m = MAX (i+j, i-j); t1 = i + j; t2 = i - j; m = (t1 > t2)? t1 : t2; m = (((i+j) > (i-j))? (i+j) : (i-j)); 計算兩次 Function - 59 呼叫某一個行間函式時, 該行間函式必須已經定義在同檔案之中 ( 不僅僅是宣告而已 ), 編譯程式才能夠引用其內容來進行巨集展開 基於這個原因, 當多個程式檔須要共用某一個行間函式時, 該行間函式的定義應該寫在.h 標頭檔之中, 然後在這些程式檔中加入此標頭檔 譬如 : // file: foo.h inline int max (int x, int y) return (x > y)? x : y; // file: bar.cpp #include foo.h int m = max(i, j); Function - 60

哪些行間函式不會巨集展開? 大部份的編譯程式對下列三種行間函式通常不會採取巨集展開的處理方式 : 行間函式中包含迴圈, 而且迴圈的執行次數不是常數值 在行間函式中呼叫自己的遞迴函式 行間函式包含過多的指令行 存放於函式指標的行間函式 Function - 61 處理指令行的選項 在文字終端機模式下執行應用程式指令時 ( 如在 UNIX 或 DOS 之下 ), 我們通常可以在指令名稱之後加上一些指令選項 (option), 如 : prog -d -o ofile data 指令選項 作業系統會把這些指令選項傳入程式 問題是 : 程式要如何接收和處理這些指令選項呢? Function - 62

若要接收指令選項, 我們必須把原本沒有參數的主函式宣告 : 改成 : int main () int main (int argc, char *argv[]) 當程式開始執行時,argc 會存放指令行上的字 (word) 數, argv 是一個字串陣列, 依序地存放指令行上的字 此處的 字 是指空白( 空白字元 TAB 字元 與一些特殊字元 ) 所隔開的字串, 譬如 : prog -d -o ofile data quoted option 0 1 2 3 4 5 總共有 6 個字 Function - 63 以下面的指令行為例 : prog -d -o ofile data quoted option argc 的值是 6,argv 的內容則如下 : argv[0] argv[1] argv[2] argv[3] argv[4] argv[5] p r o g \0 - d \0 - o \0 o f i l e \0 d a t a \0 q u o t e d o p t i o n \0 值得注意的是 :argv[0] 中的字串是指令的名稱 Function - 64

#define MIN_ARG 3 /* minimal number of options */ int main (int argc, char *argv[]) if (argc < MIN_ARG) /* print usage message and exit*/ for (int j = 1; j < argc; j++) if (argv[j][0]!= - ) // handle options without prefix - else switch (argv[j][1]) case h : // handle -h option break; case a : // handle -a option break; default: // handle unknown options break; // begin processing... Function - 65 函式指標 函式指標是一種存放函式位址的指標 它的宣告格式如下 : type (*func_ptr) (parameter list); 比方說 : void f (string s) /* */ void (*fp) (string); fp = &f; *fp( error ); // pointer to function // fp points to function f // call f through fp 上兩行也可簡化成 : fp = f; fp( error ); Function - 66

函式指標只能存放形態相同的函式, 換句話說, 兩者的宣告必須滿足下面的條件 : 相同的傳回值資料型態 相同的參數個數與資料型態 比方說 : void (*fp) (char *s); void f1 (char *s); void f2 (int n); int f3 (char *s); void f4 (char *s, int n); fp = f1; fp = f2; fp = f3; fp = f4; // ok // error // error // error Function - 67 範例 : typedef int (*CFT) (const char *, const char *); int smallest (char *sa[], int sz, CFT cmp) // find the place of the smallest string if (sz == 0) return -1; char *min = sa[0]; int ix = 0; for (int j = 1; j < sz; j++) if (cmp(min, sa[j]) > 0) min = sa[j]; ix = j; return ix; Function - 68

extern int strcmp(const char *s, const char *t) int lencmp (const char *s, const char *t) int slen = strlen(s), tlen = strlen(t); if (slen > tlen) return 1; else if (slen == tlen) return 0; else return -1; smallest(strarray, n, strcmp ); smallest(strarray, n, lencmp ); // 找出最 小 的字串 // 找出最 短 的字串 Function - 69 函式指標陣列 函式指標陣列的宣告格式如下 : type (*fptrarray [size]) (parameter list); 或利用函式指標型態來宣告 : typedef type (*FP) (parameter list); FP fptrarray [size]; 比方說, int (*testcases [10]) (); 或可寫為 : typedef int (*FPV) (); FPV testcases [10]; Function - 70