C語言中文網 目錄
首頁 > C語言專題 > 函數 閱讀:985

C語言函數完全攻略

C 語言強調模塊化編程,這里所說的模塊就是函數,即把每一個獨立的功能均抽象為一個函數來實現。從一定意義上講,C 語言就是由一系列函數串組成的。

在本章之前,我們的程序只有一個 main 函數,把所有代碼都寫在 main 函數中,這樣雖然程序的功能正常實現,但顯得雜亂無章,代碼可讀性、可維護性較差。學完本節之后,應把每個具體的獨立功能單位均抽象為一個函數,在 main 函數中調用各個函數。

C 語言函數大概包括兩種,一種是編譯系統提供的庫函數,如字符串處理復制函數 strcpy,這是 C 編譯系統提供的庫函數,該函數定義在 string.h 頭文件中,在使用時必須包含對應的頭文件,即需加上 #include<string.h> 預處理包含命令;另一種是程序設計者自定義的函數。

每一個 C 語言程序都含有一個 main 函數,操作系統調用 main 函數,main 函數調用各個庫函數或自定義函數。

函數的定義

函數是用戶與程序的接口,在定義一個函數前,首先要清楚以下三個問題。

1) 函數的功能實現及算法選擇。算法選擇會在后續文章詳細講解,本節重點關注函數的功能實現。一般選取能體現函數功能的函數名,且見名知意,如求和函數的函數名可取為 add,求最大值的函數名可取為 max,排序函數可取名為 sort 等。

2) 需要用戶傳給該函數哪些參數、什么類型,即函數參數。

3) 函數執行完后返回給調用者的參數及類型,即函數返回值類型。

函教定義格式

函數定義的一般格式為:

返回類型 函數名 (類型參數1,類型參數2,…)
{
    函數體
}


例如,定義一個求兩個整數之和的函數,返回該和值。其函數實現代碼為:
int add (int x,int y)
{
    return (x+y) ; //括號可省略
}
說明:
1) 一個函數定義包含函數頭和函數體兩部分。函數名、參數表和返回類型這三部分一般稱為函數頭。一對大括號 {} 括起來的為函數體。

2) 函數名:符合標識符的命名規則,最好見名知意。如使用 add 作為求和函數的函數名,sort 作為排序函數名。

3) 參數表:函數定義時的參數又稱為形式參數,簡稱形參??梢院幸粋€或多個參數,多個形參用逗號隔開。如下格式是錯誤的。
int add (int x;int y) //錯誤。函數各形參間用逗號隔開,而非分號
{
    return x+y;
}
各形式參數對應類型均不能省略,如下格式也是錯誤的。
int add (int x,y) //錯誤。形參y的類型不能省略
{
    return x+y;
}
也可以不含參數,不含參數時,參數表中可寫關鍵字 void 或省略,為規范起見,教程中對沒有參數的函數,參數表中統一寫 void。例如:

類型 函數名 ()
{
    函數體
}

等價于:
類型 函數名 (void) //建議的書寫方式
{
    函數體
}
4) 在函數定義中,參數表后不能加分號,如下函數定義格式是錯誤的。
float add (float x, float y); //錯誤。函數定義時,函數頭后不能有分號
{
    return x+y;
}
5) 函數體:即函數的功能實現代碼部分。用一對大括號 {} 括起來,函數體也可以為空,即函數體內不含任何代碼,便于以后擴充。例如:
void fun ()
{

}
6) 返回類型:也稱為函數類型,即給調用者返回值的類型。要求顯式指定返回類型??梢允腔緮祿愋腿?int、char、float 等,也可以是復合數據類型,如數組類型、指針類型,或者是自定義類型(結構體類型)。

如果返回類型省略,一般默認為 int 型,但不推薦這種不規范的寫法。

如果該函數沒有返回類型,則為 void 類型。例如:
void add (int x,int y)
{
    printf ("sum=%d\n", x+y);
}
除了 void 類型外,在函數體中,均需要顯式使用 return 語句返回對應的表達式的值。

函教返回值

函數的值是指調用函數結束時,執行函數體所得并返回給主調函數的值。 關于函數返回值說明如下。

1) 帶返回值的函數,其值一般使用 return 語句返回給調用者。其格式為:

return 表達式;

或者

return (表達式);

例如:
int add (int a, int b)
{
    return (a + b); //return 后為表達式
}
2) 函數可以含一個或多個 return 語句,但每次調用時只能執行其中一個 return 語句。

例如,求整數絕對值的函數:
int f (int n) //含多個return語句,但每次調用只執行一個
{
    if (n >= 0)
        return n;
    else
        return -n; //或為 return (-1 * n);
}
3) 不帶返回值的函數,其返回類型一般顯式指定為 void 類型。如 void print_99 (void); 函數,其返回類型為 void。

4) 如果沒有顯式指定函數的返回類型,默認為 int 型,不推薦這種不規范的寫法。 例如:
add (int a, int b) //省略返回類型,默認為int型
{
    return (a + b);
}
5) return 后表達式的類型應與函數返回類型一致,如果不一致,則先將表達式的類型自動轉換為函數類型后再返回。例如:
int f (void) //函數返回類型為int
{
    int n = 1;
    return (n + 2.3); //表達式為 double 型
}
上述函數中,函數類型為 int 型,return 后表達式的類型為 double 型值 3.3,兩者類型不一致,故首先把表達式的類型 double 自動轉換為 int 型值 3,然后再把 3 作為函數返回值返回給調用者。這種情況一般會丟失精度,可能得不到預想的結果。

函教調用格式

無參函數的調用格式

函數名();

注意:無參函數調用時,參數表空著,而不能寫出 void,如下函數調用是錯誤的。

函數名 (void);//錯誤!無參函數調用時,參數表空著,不能加void

例如,設有定義好的無參函數 void print_99(void); 的調用如下。
print_99 ( ) ; //正確。調用無參函數
print_99 (void) ; //錯誤。實參表中不能加void

帶參函數的調用格式

函數名(實參1,實參2,…);

說明:
1) 其中各實參可以是各種類型的常量、變量或表達式。例如,對定義好的帶參函數 int add (int a,int b); 的調用如下。
add (2,5+1); //正確,實參可以為常量、變量或表達式 int n=7;
add(3,n); //正確,實參可以是變量
2) 調用函數時,不能寫函數類型。
int add(2,3);//錯誤,調用時不能加返回類型

函教調用過程

函數調用的過程是:首先是實參給形參賦初值,接著函數體對形參做相應處理,最后把處理結果作為函數值返回給調用者。

未被調用時的函數形參并不占用內存空間,在函數調用時為形參變量分配空間,把實參的值賦給對應形參變量的空間,函數調用結束時,收回分配給形參的內存空間。即形參僅在函數調用的過程中占有內存空間。

通過如下 add 函數來說明函數調用過程。
//函數定義
int add (int a, int b)
    int s;
    s=a+b;
    return s;
}
說明:
1) 以上是 add 函數的定義,a 和 b 為形參,s 為函數內定義變量,a、b、s 這三個變量均為局部變量,作用域為該函數,不能在 add 函數外使用。

2) 未調用 add 函數時,a、b 和 s 均不占用內存空間。函數調用時,即執行如下語句。
int x=2, y=3, sum;
sum=add(x,y);
該函數調用語句中,有兩個實參,第一個實參為 x,其值為 2,第二個實參為 y,其值為 3。在函數調用時,為形參 a 和 b 及函數內變量 s 這三個整型局部變量分配存儲空間,在 VC++ 6.0 里各占 4 個字節。函數調用時,實參與形參的關系如圖 1 所示。


圖 1

函數調用過程也就是實參給形參賦初值的過程,即:
a=x;
b=y;
函數體中,對形參 a 和 b 求和的結果賦給 s,最后把 s 的值作為函數的值返回賦給 sum 變量。調用過程結束,函數 add 中的所有局部變量的內存空間被收回。

由于形參僅在定義函數內有效,故在函數調用時,函數的實參可以和形參變量同名,互不影響。

函教原型聲明

函數原型包括返回類型、函數名、參數列表等函數定義的基本信息。一般用于告知調用者該函數的基本信息,便于調用。

函數原型聲明通常有以下兩種形式。

無參函數原型聲明格式為:

返回類型 函數名 (void);

或者

返回類型 函數名 ();

帶參函數原型聲明通常有如下兩種形式。

1) 返回類型 函數名 (類型參數 1,類型參數 2,…);

這種寫法是把函數定義時的函數頭直接復制過來加分號即可,在編程時,操作方便,較節省時間,例如:
int add (int a, int b); //正確,函數頭后面直接加分號

2) 返回類型 函數名 (類型,類型,…);

這種寫法在第一種寫法的基礎上,去掉了各個形參名,只保留各個形參類型。這種寫法比較專業,但可能多花費些時間。例如:
int add (int, int);//正確,只指明有兩個整型形參即可
如果把函數定義的代碼寫在了調用語句之前,在這種情況下,雖然不加函數原型聲明,也可以正常調用函數。但為了規范起見,要求所有定義函數,在函數調用前必須加函數原型聲明語句。

比較常見規范的函數使用方式是:先函數原型聲明,再調用,一般函數定義在程序的后面。

說明:函數原型聲明,原則上只要在函數調用前聲明都可以,但為了不讓 main 函數顯得臃腫,一般不放在main函數里面,比較規范的做法是把其放在 main 函數前面。本書采用這種方式。

函教調用舉例

【例 1】帶參函數調用舉例。設計一個求兩個整型數之和的函數。

問題分析:
1) 欲求兩個整型數之和,調用者必須傳遞給該函數兩個整型數,故函數需要兩個整型類型的“容器”即形參,用于接收調用者傳來的兩個整型數。因實現功能為求和運算,函數名可取為 add,把求和的結果(整型)返回給調用者,即返回值類型也為整型。

2) 函數調用之前必須聲明函數原型,一般放在 main 函數前面。

3) 函數調用時,把欲求和的被加數和加數作為實參傳遞給函數形參。

4) 函數的返回值即求和的結果,可以直接輸出,或保存到某變量中參與其他運算或輸出。

實現代碼:
#include<stdio.h>
int add (int a, int b); //函數聲明
int main (void)
{
    int a=2,b=3, s;
    s=add(a,b); //函數調用,返回值賦給s
    printf("%d+%d=%d\n",a,b,s);
    return 0;
}
int add (int a, int b) //函數定義
{
    int s;
    s=a+b;
    return s;
}
運行結果為:
2+3=5

【例 2】無參函數調用舉例,編寫一個打印九九乘法表的函數。

分析:該函數根據實現的功能可取名為 Print_99,該函數不需要調用者(main 函數)向其傳遞任何參數,該函數就可以正常打印九九乘法表,故該函數可以定義為無參類型。

實現代碼:
#include<stdio.h>
void print_99 (void);//函數聲明
int main (void)
{
    print_99();//無參函數調用
    return 0;
}
void print_99 (void) //無參函數定義
{
    int i, j;
    for(i=1;i<=9;i++)
    {
        for (j=1; j<=i; j++)
            printf("%d*%d=%d\t",i,j,i*j);
        printf ("\n");
    }
}
運行結果:
1*1=1
2*1=2   2*2=4
3*1=3   3*2=6   3*3=9
4*1=4   4*2=8   4*3=12  4*4=16
5*1=5   5*2=10  5*3=15  5*4=20  5*5=25
6*1=6   6*2=12  6*3=18  6*4=24  6*5=30  6*6=36
7*1=7   7*2=14  7*3=21  7*4=28  7*5=35  7*6=42  7*7=49
8*1=8   8*2=16  8*3=24  8*4=32  8*5=40  8*6=48  8*7=56  8*8=64
9*1=9   9*2=18  9*3=27  9*4=36  9*5=45  9*6=54  9*7=63  9*8=72  9*9=81

函數的嵌套調用

在 C 語言中,函數不能嵌套定義,即不能在一個函數中定義其他函數。例如,在 main 函數中嵌套定義函數 fun,為錯誤語法。
int main (void)
{
    int fun(void)//錯誤,不能嵌套定義
    {
         //fun函數體
    }
    //...
    return 0;
}
此代碼就屬于函數的嵌套定義,是錯誤的語法。

C 語言雖然不支持函數的嵌套定義,但支持函數的嵌套調用,即在一個函數中可以調用其他函數,在前面已經涉及函數嵌套調用,就是在main函數中調用其他自定義函數。自定義函數之間也可以相互嵌套調用。

【例 3】編程實現求 12+22+32+42+52+...+102 的值

實現代碼為:
#include<stdio.h>
int pow(int , int);
int sum(int);
int main(void)
{
    int r;
    r=sum(10);
    printf("result=%d\n",r);
    return 0;
}
int sum(int n)
{
    int i,s=0;
    for(i=1;i<=n;i++)
    {
        s+=pow(i,2);
    }
    return s;
}
int pow(int m,int n)
{
    int i,p=1;
    for(i=1;i<=n;i++)
    {
        p*=m;
    }
    return p;
}
運行結果為:
result=385

程序說明:該程序的執行過程是,操作系統調用 main 函數,main 函數調用 sum 函數,sum 函數調用 pow 函數,pow 函數執行完后,返回到其調用者 sum 函數,接著往下執行,sum 函數執行完,返回調用處 main 函數,接著往下執行,執行完 main 函數,return 0; 后返回給操作系統,整個程序執行結束。

另外,sum 函數及 pow 函數中均含有相同名字的變量 n 和 i。因為它們都是局部變量,作用域僅局限于各自的函數體中,故它們互不相干,互不影響。

傳值調用和傳址調用

C 語言中函數調用方式可分為傳值調用傳址調用兩大類。

傳值調用

函數調用時,把實參的值傳遞給對應形參變量。這種調用形式,相當于形參復制了實參的一個副本,函數體內對形參(實參的副本)操作,形參變量的變化并不會影響到實參的值。即函數調用過程中,數據的傳遞是單向的。

傳值調用時,傳入的實參是普通變量(包括數組的某個元素)和常量及常量表達式。

例如,分析如下程序。
#include<stdio.h>
void swap (int, int);
int main (void)
{
    int a=3, b=5;
    swap(a,b);
    printf ("a=%d,b=%d\n",a,b);
    return 0;
}

void swap (int x, int y)
{
    int t;
    t=x;
    x=y;
    y=t;
}
【運行結果】
a=3,b=5

程序分析:

形參為普通變量(整型),實參為普通變量(整型變量 a 和 b),故該函數調用為傳值調用。形參相當于復制了實參的一個副本,函數內對形參的操作,均是對實參副本的操作,不會對實參變量產生任何影響。

另外,swap 函數中借助于變量 t,把形參 x 和 y 的值進行交換,由于 x、y 和 t 均屬于 swap 函數內的局部變量,函數調用結束后,三個變量的空間全收回,對實參變量 a 和 b 沒有任何影響。故調用該函數后,a 和 b 的值并未發生交換。

傳址調用

實參是某個空間的地址,把該地址賦給形參變量,函數內對該地址操作,可間接對該地址所指的空間進行操作。即傳址調用過程,函數可以通過傳入的地址值,改變該地址空間的值。數組作為函數參數和指針作為函數參數均可實現址調用。

傳址調用時,實參為地址(一維數組名被看成數組首元素的地址)。形參一般為數組類型或指針類型。如果形參為數組類型,則實參為同類型數組的數組名或首元素的地址。

例如,用數組類型作函數形參,編程實現求斐波那契數列的前 n 項的程序。實現代碼為:
#include<stdio.h>
#define N 10
void Fib (int x[], int n);
int main (void)
{
    int i,a[N] = {0,1};
    Fib(a,N);
    for(i=0;i<N;i++)
        printf ("%-4d",a[i]);
    printf ("\n");
    return 0;
}

void Fib (int x[], int n)
{
    int i;
    for(i=2;i<n;i++)
        x[i]=x[i-1]+x[i-2];
}
運行結果:
0   1   1   2   3   5   8   13  21  34

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo
极速pk10开户