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


模塊化程序設計:事先編好一批經常使用的函數來實現不一樣的功能,須要使用時直接拿來用.而不是把全部程序代碼都寫在一個主函數裏,用什麼去寫什麼.

從本質意義上來講,函數就是用來完成必定功能的.編程

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

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

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

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

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

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

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

在調用無參函數時,主調函數不向被調用函數傳遞數據.無參函數能夠帶回或不帶回函數值,但通常以不帶回函數值居多.code

在調用函數時,主調函數在調用被調用函數時,經過參數向被調用函數傳遞數據,通常狀況下,執行被調用函數時會獲得一個函數值,供主調函數使用.此時有參函數應定義爲與返回值相同的類型.對象

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

(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指定的應用。

相關文章
相關標籤/搜索