1、什麼是函數
在C語言中,完成一個特定任務的程序段、子程序稱作函數,在一個完整的C程序中,通常是由很多個函數組成的,每一個函數完成其指定的任務。
2、什麼是函數原型
一個函數有函數名、函數的返回值及返回值的類型、函數的參數及參數的類型。對函數的所有這些特征予以描述稱作函數的原型。函數原型一般放在整個程序文件的開始部分(內部函數)。對於可在當前源文件以外使用的函數,應該在一個頭文件中說明,要使用這些函數的源文件必須包含這個頭文件。
3、什麼是函數的返回值
當一個函數執行完之後傳遞給調用函數一個值,稱為返回值。是否需要返回一個值給調用函數,具體情況視程序中的需要而定,有時根本就不需要返回一個值給調用函數。
4、為什麼要說明函數的原型
函數原型能告訴編譯程序一個函數接受什麼樣的參數,將返回什麼樣的返回值,這樣編譯程序就能檢查對函數的調用是否正確,是否存在錯誤的類型轉換。例:
int some_func(int,char *,long);
編譯程序應會檢查所有對該函數的調用(包括該函數的定義)是否使用了三個參數並且返回一個int類型的值。如果編譯程序發現函數的調用或定義與函數原型不匹配,編譯程序應會報告出錯或警告信息。例如對上述函數原型來說,當編譯程序檢查以下語句時,就會報告出錯或警告消息:
x=some_func(1); /*參數個數少*/
x=some_func("HELLO!",1,"DUDE!"); /*參數類型錯誤*/
x=some_func(1,str,2879,"T"); /*參數太多*/
下面的函數調用同樣是不正確的,因為函數some_func()的返回值不是一個long *類型的值。
lValue=some_func(1,str,2879); /*函數返回值應該是int而不是long型。*/
同樣編譯程序還能檢查函數的定義(或函數體)是否與函數原型匹配。例如當編譯程序檢查以下函數定義時,應會報告出錯或警告信息:
int some_func(char * string,,long lValue,int iValue) /*參數的位置不對*/
總之,在源文件中說明函數原型提供了一種檢查函數是否被正確引用的機制。目前許多流行的編譯程序都會檢查被引用的函數的原型是否已在源文件中說明過,如果沒有,就會發出警告信息。
5、一個函數可以有多少個參數
一個函數的參數的數目沒有明確的限制,但是參數過多(例如超過8個)顯然是一種不可取的編程風格。參數的數目直接影響到調用函數的速度,參數越多,函數調用越慢。另一方面,函數的參數少,程序就顯得精練、簡捷,這有助於檢查和發現程序中的錯誤,因此通常應該盡量減少參數的數目,如果一個函數的參數超過4 個,就應該考慮一下函數是否編寫得當。如果一個函數不得不使用很多參數,可以定義一個結構來容納這些參數,這是一種非常好的解決辦法。
在下例中,函數print_report()需要使用10個參數,然而在它的參數說明中並沒有列出這些參數,而是通過一個RPT_PARMS的結構得到這些參數。
#include<stdio.h>
typedef struct
{
int orientation;
char rpt_name[25];
char rpt_path[40];
int destination;
char output_file[25];
int starting_page;
char ending_page;
char db_name[25];
char db_path[40];
int draft_quality;
}RPT_PARMS;
int print_report(RPT_PARMS *);
void main(void)
{
RPT_PARMS rpt_parm;
...
...
rpt_parm.orientation=ORIENT_LANDSCAPE;
rpt_parm.rpt_name="QSALES.RPT";
rpt_parm.rpt_path="C:\REPORTS";
rpt_parm.destingation=DEST_FILE;
rpt_parm.output_file="QSALES.TXT";
rpt_parm.starting_page=1;
rpt_parm.ending_page=RPT_END;
rpt_parm.db_name="SALES.DB";
rpt_parm.db_path="C:\DATA";
rpt_parm.draft_quality=TRUE;
ret_code=print_report(&rpt_parm);
....
....
}
int print_report(RPT_PARMS *p)
{
int rc;
....
....
orient_printer(p->orientation);
set_printer_quality(p->draft_quality==TRUE)?DRAFT:NORMAL);
....
....
return rc;
}
上例唯一不足的是編譯程序無法檢查引用print_report()函數時RPT_PARMS結構的10個成員是否符合要求。
6、什麼是內部函數
內部函數用static來說明,是作用域只限於說明它的源文件的函數。作用域指的是函數或變量的可見性。如果一個函數或變量在說明它的源文件以外也是可見的,那麼就稱它有全局或外部作用域果一個函數或變量在說明它的源文件中是可見的,那麼就稱它具有局部或內部作用域。
內部函數只能在說明它的源文件中使用。如果你呈希望一個函數不會在說明它的源文件以外被使用,就應該將它說明為內部函數,這樣做是一個好的編程習慣,因為這樣可以避免與其它源文件中的同名函數發生沖突。
例:
#include<stdio.h>
int open_customer_table(void);
static int open_customer_indexes(void);
int open_customer_table(void)
{
int ret_code;
....
....
if(ret_code==OK)
{
ret_code=open_customer_indexes();
}
return ret_code;
}
static int open_customer_indexes(void)
{
int ret_code;
....
....
return ret_code;
}
在上例中,函數open_customer_table()是一個外部函數,它可以被任何模塊調用,而函數open_customer_indexes()是一個內部函數,它永遠不會被其它模塊調用。之所以這樣說明這二個函數,是因為函數open_customer_indexes()只需被函數open_customer_table()調用,即只需在上例的源文件中使用。
7、如果一個函數沒有返回值,是否需要加入return語句
在C語言中,用void關鍵字說明的函數是沒有返回值的,並且也沒有必要加入return語句。在有些情況下,一個函數可能會引起嚴重的錯誤,並且要立即退出該函數,這時就應該加入return語句,以跳過函數體內還未執行到的代碼。但是在void函數中隨意加入return語句是一個不好的編程習慣,困此,在void函數中退出函數的操作應該盡量集中和簡潔。
8、怎樣把數組作為參數傳給函數
在把數組作為參數傳給函數時,有值傳遞(傳值 by value)和地址傳遞(傳址 by reference)二種方式。在值傳遞方式中,在說明和定義函數時,要在數組參數尾部加上一對方括號,調用函數時只需要將數組的地址(即數組名)傳遞給數組。
例:在下例中數組x[]是通過值傳遞方式傳遞給byval_func()函數的。
#include<stdio,h>
void byval_func(int[]); /*這個函數的參數是一個int型數組*/
void main(void)
{
int x[10];
int y;
for(y=0;y<10;y++) /*初始化整型數組*/
x[y]=y;
byval_func(x); /*把數組名放在參數裡,注意是值傳遞*/
}
void byval_func(int i[])
{
int y;
for(y=1;y<11;y++)
{
i[y]=y;
printf("%d"\n",i[y]);
}
}
在上例中,定義了一個名為x的數組,並對它的10個元素賦了初值,函數byval_func()的說明如下所示:
int byval_func(int[]);
參數int[]告訴編譯程序byval_func()只有一個參數,即一個int類型值組成的數組,在調用byval_func()函數時,只需將數組的地址傳遞給該函數,即:
byval_func(x);
在值傳遞方式中,數組x將被復制一份,復制所得的數組將被存放在棧中,然後由byval_func()函數接收並打印出來。由於傳遞給byval_func()函數的是初始數組的一份考貝,因此在byval_func()函數內部修改傳遞過來的數組對初始數組沒有任何影響。
值傳遞方式的開銷是非常大的,其原因有這樣幾點:第一,需要完整地復制初始數組並將這份考貝存放到棧中,這將耗費相當長的時間,因而值傳遞方式的效率比較低二,初始數組的考貝需要占用額外的內存空間(棧中的內存)三,編譯程序需要專門產生一部分用來復制初始數組的代碼,這將使程序變大。
地址傳遞方式克服了值傳遞方式的缺點,是一種更好的方式。在地址傳遞方式中,傳遞給函數的是指向初始數組的指針,不用復制初始數組,因此程序變得精練和高效,並且也節省了內存和棧空間。在地址傳遞方式 中,只需在函數原型中將函數的參數說明為指向數組元素數據類型 的一個指針。
例:
#include<stdio.h>
void const_func(const int *);
void main(void)
{
itn x[10];
int y;
for(y=0;y<10;y++)
x[y]=y;
const_func(x);
}
void const_func(const int *i)
{
int y;
for(y=1;y<11;y++)
{
i[y]=y;
printf("%d\n",*(i+y));
}
}
在上例中,同樣定義了一個名為x的數組,並對它的10個元素賦了初值。函數const_func()的說明如下所示:
int const_func(const int *);
參數const int *告訴編譯程序const_func()函數只有一個參數,即指向一個int類型常量的指針。在調用const_func()函數時,同樣只需將數組的地址傳遞給該函數,即:
const_func(x);
在地址傳遞方式中,沒有復制初始數組並將其考貝存放在棧中,const_func()函數只接收到指向一個int 類型常量的指針,因此在編寫程序時要保證傳遞給const_func()函數的是一個由int類型值組成的數組的指針。const修飾符的作用是防止const_func()函數意外地修改初始數組中的某一個元素。
地址傳遞方式唯一不足的是必須由程序本身來保證將一個數組傳遞給函數作為參數,例如在函數const_func()的原型和定義中,都沒有明確指示該函數的參數是指向一個由int類型值組成的數組的指針。但是,地址傳遞方式速度快,效率高,因此在對運行速度要求比效高時,應訪采用這種方式。