C語言程序設計-第7章 用函數實現模塊化程序設計



模塊化程序設計:事先編好一批常用的函數來實現不同的功能,需要使用時直接拿來用.而不是把所有程序代碼都寫在一個主函數里,用什么去寫什么.

從本質意義上來說,函數就是用來完成一定功能的.

每個函數用來實現一個特定的功能.函數的名字應反映其代表的功能.

在設計一個較大的程序時,往往把它分成若干個程序模塊,每一個模塊包括一個或多個函數,每個函數實現一個特定的功能.一個C程序可由一個主函數和若干個其他函數構成.由主函數調用其他函數,其他函數也可以互相調用.同一個函數可以被一個或多個函數調用任意多次.

在定義函數時若函數前加void,則意為函數無類型,即無函數值,也就是說,執行這兩個函數后不會把任何值帶回main函數.

若自定義的函數在main函數后面,則在main函數的開頭部分,應對調用的自定義函數進行聲明.

所有函數都是平行的,即在定義函數時是分別進行的,是互相獨立的.一個函數並不從屬於另一個函數,即函數不能嵌套定義.函數間可以互相調用,但不能調用main函數.main函數是被操作系統調用的.

從用戶角度看函數有兩種:庫函數和用戶自定義的函數.

從函數形式看,函數分兩類:無參函數和有參函數.

在調用無參函數時,主調函數不向被調用函數傳遞數據.無參函數可以帶回或不帶回函數值,但一般以不帶回函數值居多.

在調用函數時,主調函數在調用被調用函數時,通過參數向被調用函數傳遞數據,一般情況下,執行被調用函數時會得到一個函數值,供主調函數使用.此時有參函數應定義為與返回值相同的類型.

定義函數應包括收下幾個內容:

(1)指定函數的名字,以便以后按名調用.

(2)指定函數的類型,即函數返回值的類型.

(3)指定函數的參數的名字和類型,以便在調用函數時向它們傳遞數據.對無參函數不需要這項.

(4)指定函數應當完成什么操作,也就是函數是做什么的,即函數的功能.這是最重要的,是在函數體中解決的.

7.2.2定義函數的方法

1.定義無參函數

形式:

類型名 函數名()

{

函數體

}

類型名 函數名(void)

{

函數體

}

函數名后面括號內的void表示"空",即函數沒有參數.

函數體包括聲明部分和語句部分.

2.定義有參函數

函數名后面括號內有形式參數.

形式:

類型名 函數名(形式參數表列)

{

函數體

}

3.定義空函數

形式:

類型名 函數名()

{}

函數體是空的.調用此函數時,什么工作也不做,沒有任何實際作用.

7.3 調用函數

函數調用的形式:

函數名(實參表列)

實參表列各參數間用逗號隔開.如果是調用無參函數,則括號里為空.

1.函數調用語句.2.函數表達式.3.函數參數

函數調用時的數據傳遞:在定義函數時函數名后面括號中的變量名稱為"形式參數"(簡稱"形參")或"虛擬參數".在主調函數中調用一個函數時,函數名后面括號中的參數稱為"實際參數"(簡稱"實參").實際參數可以是常量,變量或表達式.

在調用函數過程中,系統會把實參的值傳遞給被調用函數的形參.或者說,形參從實參得到一個值.該值在函數調用期間有效,可以參加函數中的運算.

在定義函數中的形參,在未出現函數調用時,它們並不占內存中的存儲單元.在發生函數調用時,,函數的形參被臨時分配內存單元.調用結束,形參單元被釋放.實參單元仍保留並維持原值,沒有改變.

函數的返回值是通過函數中的return語句獲得的.

一個函數中可以有一個以上的return語句,執行到哪一個return語句,哪一個return語句就起作用.

函數值的類型.既然函數有返回值,這個值當然應屬於某一個確定的類型,應當在定義函數時指定函數值的類型.

在定義函數時指定的函數類型一般應該和return語句中的表達式類型一致.

如果函數值的類型和return語句中表達式的值不一致,則以函數類型為准.對數值型數據,可以自動進行類型轉換.即函數類型決定返回值的類型.

對於不帶回值的函數,應當用定義函數為"void類型"(或稱"空類型").這樣,系統就保證不使函數帶回任何值,即禁止在調用函數中使用被調用函數的返回值.此時在函數體中不得出現return語句.

7.4 對被調用函數的聲明和函數原型

在一個函數中調用另一個函數,這個被調用的函數一定是之前已經有的,可以是庫函數也可以是之前自己定義的函數,如果是庫函數那么要在開頭引入,如果是自己定義的函數且它在主調函數之后,那么要在開頭對這個被調函數先聲明一下,聲明它的類型,函數參數的個數和參數類型等信息.

例7.4 輸入兩個實數,用一個函數求出它們的和.

#include <stdio.h>
#include <stdlib.h>


int main()
{
    float add(float x,float y);
    float a,b,c;
    printf("please enter a and b:");
    scanf("%f,%f",&a,&b);
    c=add(a,b);
    printf("sum is:%f\n",c);
    return 0;
}
float add(float x,float y)
{
    float z;
    z=x+y;
    return(z);
}

7.5 函數的嵌套調用

C語言的函數定義是想到平行,獨立的,也就是說,在定義函數時,一個函數內不能再定義另一個函數,也就是不能嵌套定義,但可以嵌套調用函數,也就是說,在調用一個函數的過程中,又調用另一個函數.

例7.5 輸入4個整數,找出其中最大的數.用函數的嵌套調用來處理.

#include <stdio.h>
#include <stdlib.h>


int main()
{
    int max4(int a,int b,int c,int d);
    int a,b,c,d,max;
    printf("please enter 4 interger numbers:");
    scanf("%d,%d,%d,%d",&a,&b,&c,&d);
    max=max4(a,b,c,d);
    printf("max=%d\n",max);
    return 0;
}
int max4(int a,int b,int c,int d)
{
    int max2(int a,int b);
    int m;
    m=max2(a,b);
    m=max2(m,c);
    m=max2(m,d);
    return(m);
}
int max2(int a,int b)
{
    if(a>b)
    {
        return a;
    }else{
        return b;
    }
}

程序改進:

#include <stdio.h>
#include <stdlib.h>


int main()
{
    int max4(int a,int b,int c,int d);
    int a,b,c,d,max;
    printf("please enter 4 interger numbers:");
    scanf("%d,%d,%d,%d",&a,&b,&c,&d);
    max=max4(a,b,c,d);
    printf("max=%d\n",max);
    return 0;
}
int max4(int a,int b,int c,int d)
{
    int max2(int a,int b);
    return max2(max2(max2(a,b),c),d);
}
int max2(int a,int b)
{
    return(a>b?a:b);
}

7.6 函數的遞歸調用

在調用一個函數的過程中又出現直接或間接地調用該函數本身,稱為函數的遞歸調用.

例7.7 用遞歸方法求n!.

#include <stdio.h>
#include <stdlib.h>


int main()
{
    int fac(int n);
    int n;
    int y;
    printf("input an integer number:");
    scanf("%d",&n);
    y=fac(n);
    printf("%d=%d\n",n,y);
    return 0;
}
int fac(int n)
{
    int f;
    if(n<0)
        printf("n<0,data error!");
    else if(n==0||n==1)
        f=1;
    else f=fac(n-1)*n;
    return(f);
}

例7.8 Hanoi(漢諾)塔問題.這是一個古典的數學問題,是一個用遞歸方法解題的典型例子.問題是這樣的:古代有一個梵塔,塔內有3個座A,B,C,開始時A座上有64個盤子,盤子大小不等,大的在下,小的在上. 有一個老和尚想把這64個盤子從A座移到C座,但規定每次只允許移動一個盤,且在移動過程中在3個座上都始終保持大盤在下,小盤在上.在移動過程中可以利用B座.要求編程序輸出移動盤子的步驟.

解題思路:要把64個盤子從A座移到C座,需要移動大約264次盤子。把64個盤子從A座到C座,如果把上面63個盤子看成一個整體,那么就可以把這63個盤子移到B座,第64個盤子移到C座,然后再將63個盤子移到C座,這樣問題就變成怎么把這63個盤子從A座移到B座。把這63個盤子從A座移到B座可以把上面62個盤子看成整體從A座移到C座,將第63個盤子移到B座,然后再將62個盤子從C座移到B座,這樣問題就變成如何將62個盤子從A座移到C座…一次次遞歸下去,直到最后的遞歸結束條件是將一個盤子從一座移到另一座,否則遞歸一直進行下去。

由上面的分析可在,將n個盤子從A座移到C座可以分解為以下3個步驟:

(1)將A上n-1個盤借助C座先移到B座上;

(2)把A座上剩下一個盤移到C座上;

(3)將n-1個盤從B座借助A座移到C座上。

上面第(1)步到第(3)步,都是把n-1個盤從一個座移到另一個座上,采取的辦法是一樣的,只是座的名字不同而已。

上面3個步驟可以分成兩類操作:

(1)將n-1個盤從一個座移到另一個座上(n>1)。這就是大和尚讓小和尚做的工作,它是一個遞歸的過程,即和尚將任務層層下放,直到第64個和尚為止。

(2)將1個盤子從一個座上移到另一個座上。這是大和尚自己做的工作。

編寫程序:分別用兩個函數實現以上的兩類操作,用hanoi函數實現上面第1類操作(即模擬小和尚的任務),用move函數實現上面第2類操作(模擬大和尚自己移盤),函數調用hanoi(n,one,two,three)表示盤子從“one”座移到“three”座的過程(借助“two”座)。函數調用move(x,y)表示將1個盤子從座移到y座的過程。x和y是代表A,B,C座之一,根據每次不同情況分別取A,B,C代入。


#include <stdio.h>
#include <stdlib.h>

int main()
{
   void hanoi(int n,char one,char two,char three);
   int m;
   printf("input the number of diskes:");
   scanf("%d",&m);
   printf("The step to move %d diskes:\n",m);
   hanoi(m,'A','B','C');
}

void hanoi(int n,char one,char two,char three)
{
    void move(char x,char y);
    if(n==1)
        move(one,three);
    else
    {
        hanoi(n-1,one,three,two);
        move(one,three);
        hanoi(n-1,two,one,three);
    }
}

void move(char x,char y)
{
    printf("%c-->%c\n",x,y);
}


7.7 數組作為函數參數

例 7.9 輸出10個數,要求輸出其中值最大的元素和該數是第幾個數。

#include <stdio.h>
#include <stdlib.h>

int main()
{
  int max(int x,int y);
  int a[10],m,n,i;
  printf("enter 10 integer numbers:");
  for(i=0;i<10;i++)
  {
      scanf("%d",&a[i]);
  }
  printf("\n");
  for(i=0,m=a[0],n=0;i<10;i++)
  {
      if(max(m,a[i])>m)
      {
          m=max(m,a[i]);
          n=i;
      }
  }
  printf("The largest number is %d\nit is the %dth number.\n",m,n+1);
}

int max(int x,int y)
{
    return(x>y?x:y);
}


用數組元素作實參時,向形參變量傳遞的是數組元素的值,而用數組名作函數實參時,向形參(數組名或指針變量)傳遞的是數組首元素的地址。

例:7.10 有一個一維數組score,內放10個學生成績,求平均成績。

#include <stdio.h>
#include <stdlib.h>

int main()
{
  float average(float array[10]);
  float score[10],aver;
  int i;
  printf("input 10 scores:\n");
  for(i=0;i<10;i++)
    scanf("%f",&score[i]);
  aver=average(score);
  printf("average score is %5.2f\n",aver);
  return 0;
}

float average(float array[10])
{
    int i;
    float aver,sum=array[0];
    for(i=1;i<10;i++)
        sum=sum+array[i];
    aver=sum/10;
    return(aver);
}


例7.11 有兩個班級,分別有35名和30名學生,調用一個average函數,分別求這兩個班的學生的平均成績。

#include <stdio.h>
#include <stdlib.h>

int main()
{
  float average(float array[],int n);
  float score1[5]={98,93,92,96,95};
  float score2[10]={77,99,33,98,34,35,87,68,32,15};
  printf("The average of class A is %6.2f\n",average(score1,5));
  printf("The average of class B is %6.2f\n",average(score2,10));
  return 0;
}

float average(float array[],int n)
{
    int i;
    float aver,sum=array[0];
    for(i=1;i<10;i++)
        sum=sum+array[i];
    aver=sum/n;
    return(aver);
}


例 7.12 用選擇法對數組中10個整數按由小到大排序。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    void sort(int array[], int n);
    int a[10], i;
    printf("enter array:\n");
    for (i = 0; i < 10; i ++)
        scanf("%d",&a[i]);
    sort(a, 10);
    printf("The sorted array:\n");
    for(i = 0; i < 10; i++)
        printf("%d",a[i]);
    printf("\n");
    return 0;
}

void sort(int array[], int n)
{
    int i, j, k, t;
    for(i = 0; i < n - 1; i ++)
    {
        k = i;
        for (j = i + 1; j < n; j ++){
            if (array[j] < array[k])
                k = j;
        }
        t = array[k];
        array[k] = array[i];
        array[i] = t;
    }
}


例 7.13 有一個 3*4的矩陣,求所有元素中的最大值。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int max_value(int array[][4]);
    int a[3][4] = {{1, 3, 5 ,7}, {2, 4, 6, 8}, {15, 17, 34, 12}};
    printf("Max value is %d\n", max_value(a));
    return 0;
}

int max_value(int array[][4])
{
    int i, j, max;
    max = array[0][0];
    for (i = 0; i < 3; i ++)
        for (j = 0; j < 4; j ++)
            if (array[i][j] > max)
                max = array[i][j];
    return(max);
}


7.8 局部變量 和 全局變量

變量的作用域問題,每一個變量都有一個作用域問題,即它們在什么范圍內有效。

7.8.1 局部變量

定義變量可能有3種情況:在函數的開頭定義;在函數內的復合語句內定義;在函數的外部定義。

在一個函數內部定義的變量只在本函數范圍內有效,也就是說只有在本函數內才能引用它們,在此函數以外是不能使用這些變量的。在復合語句內定義的變量只在本復合語句范圍內有效,只有在本復合語句內才能引用它們。在該復合語句以外是不能使用這些變量的,以上這些稱為“局部變量”。

主函數中定義的變量也只在主函數中有效,主函數也不能使用其他函數中定義的變量。

不同函數中可以使用同名的變量,它們代表不同的對象;互不干擾。

形式參數也是局部變量。

在一個函數內部,可以在復合語句中定義變量,這些變量只在本復合語句中有效,離開該復合語句該變量就無效,系統會把它占用的內存單元釋放,這種復合語句也稱為“分程序”或“程序塊”。

7.8.2 全局變量

程序的編譯單位是源程序文件,一個源文件可以包含一個或若干個函數。在函數內定義的變量是局部變量,而在函數之外定義的變量稱為外部變量,外部變量是全局變量(也稱為全程變量)。全局變量可以為本文件中其他函數所共用。它的有效范圍為從定義變量的位置開始到本源文件結束。

在函數內定義的變量是局部變量,在函數外定義的變量是全局變量。

為了便於區別全局變量和局部變量,在C程序設計人員中有一個習慣(但非規定),將全局變量名的第1個字母用大寫表示。

例 7.14 有一個一維數組,內放10個學生成績,寫一個函數,當主函數調用此函數后,能求出平均分,最高分和最低分。

解題思路:調用一個函數可以得到一個函數返回值,現在希望通過函數調用能得到3個結果。可以利用全局變量來達到此目的。

#include <stdio.h>
#include <stdlib.h>

float Max = 0, Min = 0;
int main()
{
    float average(float array[], int n);
    float ave, score[10];
    int i;
    printf("Please enter 10 scores: ");
    for (i = 0; i < 10; i++)
        scanf("%f", &score[i]);
    ave = average(score, 10);
    printf("max = %6.2f\nmin = %6.2f\naverage=%6.2f\n",Max,Min,ave);
    return 0;
}

float average(float array[], int n)
{
    int i;
    float aver, sum = array[0];
    Max = Min = array[0];
    for (i = 1; i < n; i++)
    {
        if (array[i] > Max) Max = array[i];
        else if (array[i] < Min) Min = array[i];
        sum = sum + array[i];
    }
    aver = sum / n;
    return(aver);
}


建議不在必要時不要使用全局變量,因為:

1.全局變量在程序的全部執行過程中都占用存儲單元,而還是僅在需要時才開辟單元。

2.這使函數的通用性降低了,因為如果在函數中引用了全局變量,那么執行情況會受到有關的外部變量的影響,如果將一個函數移到另一個文件中,還要考慮把有關的外部變量及其值一起移過去。但是若該外部變量與其他文件的變量同名時,就會出現問題。這就降低了程序的可靠性和通用性。

3.使用全局變量過多,會降低程序的清晰性,人們往往難以清楚地判斷出每個瞬間各個外部變量的值。由於在各個函數執行時都可能改變變量的值,程序容易出錯。因此,要限制使用全局變量。

例 7.15 若外部變量與局部變量同名,分析結果。

#include <stdio.h>
#include <stdlib.h>

int a = 3, b = 5;
int main()
{
    int max(int a, int b);
    int a = 8;
    printf("max=%d\n",max(a, b));
    return 0;
}

int max(int a, int b)
{
    int c;
    c = a > b ? a : b;
    return(c);
}


7.9 變量的存儲方式和生存期

7.9.1 動態存儲方式和靜態存儲方式

從變量值的存在時間(生存期)觀察變量,有的在程序運行的整個過程都是存在的,有的則在調用其所在的函數時才臨時分配存儲單元,而在函數調用結束后該存儲單元就馬上釋放了,變量不存在了。

靜態存儲方式是指在程序運行期間由系統分配固定的存儲空間的方式,而動態存儲方式則是在程序運行期間根據需要進行動態的分配存儲空間的方式。

全局變量全部存放在表態存儲區中,在程序執行過程中它們占據固定的存儲單元,而還是動態地進行分配和釋放。

在動態存儲區中存放以下數據:

1 函數形式參數。在調用函數時給形參分配存儲空間。

2 函數中定義的沒有用關鍵字static聲明的變量,即自動變量。

3 函數調用時的現場保護和返回地址等。

在C語言中,每一個變量和函數都有兩個屬性:數據類型和數據的存儲類別。

C的存儲類型包括4種:自動的(auto),靜態的(static),寄存器的(register),外部的(extern)。

7.9.2 局部變量的存儲類別

關鍵字“auto”可以省略,不寫auto則隱含指定為”自動存儲類別“,它屬於動態存儲方式。

有時希望函數中的局部變量的值在函數調用結束后不消失而繼續保留原值,即其戰勝的存儲單元不釋放,在下一次南再調用該函數時,該變量已有值(就是上一次函數調用結束時的值)。這里就應該指定該局部變量為”靜態局部變量“,用關鍵字static進行聲明。

如果有一些變量使用頻繁,為提高執行效率,允許將局部變量的值放在CPU中的寄存器中,需要用時直接從寄存器取出參加運算,不必再到內存中去存取。由於對寄存器的存取速度遠高於對內存的存取速度,因此這樣做可以提高執行效率。這種這是叫做寄存器變量,用關鍵字rgister作聲明.

3種局部變量的存儲位置是不同的:自動變量存儲在動態存儲區;靜態局部變量存儲在靜態存儲區;寄存器存儲在CPU中的寄存器中。

如果外部變量不在文件的開頭定義,其有效的作用范圍只限於定義處到文件結束。在定義點之前的函數不能引用該外部變量。如果出於某種考慮,在定義占之前的函數需要引用該外部變量,則應該在引用之前用關鍵字extern對該變量作”外部變量聲明“,表示把該外部變量的作用域擴展到此位置。有了此聲明,就可以從”聲明“處起,合法地使用該外部變量。

如果想在一個文件中引用另一個文件中已定義的外部變量Num,可以在作一個文件中定義外部變量Num,而在另一文件中用extern對該變量作”外部變量聲明“,即”extern Num;“。在編譯和連接時,系統會由此知道這個變量Num有”外部鏈接“,可以從別處找到已定義的外部變量Num,並將另一個文件中定義的外部變量Num的作用域擴展到本文件,在本文件中可以合法地引用外部變量Num.

在編譯時遇到extern時,先在本文件中找外部變量的定義,如果找到,就在本文優缺點 擴展作用域;如果找不到,就在連接時從其他文件中找外部變量的定義。如果從其他文件中找到了,就將作用域擴展到本文件;如果再找不到,就按出錯處理。

有時在程序設計中希望某些外部變量只限於被本文件引用,而不能被其他文件引用。這時可以在定義外部變量時加一個static聲明。

這種加上static聲明,只能用於本文件的外部變量稱為靜態外部變量。

用static聲明一個變量的作用是:

1 對局部變量作static聲明,把它分配在表態存儲區,該變量在整個程序執行期間不釋放,其所分配的空間始終存在。

2 對全局變量用static聲明,則該變量的作用域只限於本文件模塊(即被聲明的文件中)。

 

7.10 關於變量的聲明和定義

有一個簡單的結論,在函數中出現的對變量的聲明(除了用extern聲明的以外)都是定義。在函數中對其他函數的聲明不是函數的定義。

7.11 內部函數和外部函數

根據函數能否被其他源文件調用,將函數區分為內部函數和外部函數。

內部函數又稱為表態函數,因為它是用static聲明的。使用內部函數,可以使函數的作用域只局限於所在文件。

通常把只能由本文件使用的函數和外部變量放在文件的開頭,前面都冠以static使之局部化,其他文件不能引用。這就提高了程序的可靠性。

如果在定義函數時,在函數的首部的最左端加關鍵字extern,則此函數是外部函數,可供其他文件調用。

利用函數原型擴展函數作用域最常見的例子是#include指定的應用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM