1程序模塊化
在程序設計過程中大多數程序要比我們之前設計的程序復雜的多,傳統的設計方法是“自定向下,逐步求精”的過程。該過程就是將一個大的問題按照層次分解成多個方便解決的小問題,直至各個功能模塊,每個單獨的功能模塊可以單獨設計,最後將所有的功能模塊有機的結合成完整的程序。
例子:計算出該日是該年的第幾天。
問題可以分解為:獲取輸入;判斷平年閏年;判斷每個月的總天數;得到總天數;
例子代碼:
#include "stdio.h"
/*(1)判斷閏年*/
int leap(int year)
{int flag;
if (year%4==0&&year%100!=0||year%400==0)
flag=1;
else flag=0;
return flag;
}
/*(2)求某月的天數*/
int month_days(int year,int month){
int d;
switch(month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:d=31;break;
case 2: d=leap(year)?29:28;break; /*通過調用函數計算,閏年29天,平年28天*/
default:d=30;break;
}
return d;
}
/*(3)求天數和。*/
int days(int year,int month,int day){
int i,ds=0;
for(i=1;i<month;i++)
ds=ds+month_days(year,i); /*函數days調用函數month_days,求各月份對應的天數*/
ds=ds+day;
return ds;
}
/*主程序中調用各個模塊來計算天數的和*/
void main()
{ int year,month,day,t_day;
printf("input year-month-day:\n");
scanf("%d-%d-%d",&year,&month,&day);
/*函數scanf作為輸入模塊是系統定義的,
主函數main可以直接調用它*/
t_day=days(year,month,day); /*求天數和*/
printf("%d-%d-%d is %dth day of the year!n",year,month,day,t_day);
/*函數printf作為輸出模塊也是系統定義,
主函數main可以直接調用*/
}
2函數的定義
2.1函數簡介:函數是C語言程序的基本模塊,函數一般可以從3中角度進行分類:
從函數定義角度:
1庫函數:庫函數由C系統提供,用戶無需定義,可以直接調用。
2用戶自定義函數:用戶自己編寫的函數,在調用的函數中還必須對被調用函數進行類型說明才能使用。
從返回值角度看:
1有返回值的函數:有返回值的函數在被調用後將向調用者返回一個執行結果,這個結果就是返回值,用戶在定義這種函數時應該說明返回值類型。
2無返回值函數:無返回值函數用於完成某項特定的功能,執行完成後不用返回函數值,用戶定義時聲明這種函數的返回值為空類型,即void。
從數據傳輸來看:
1無參函數:無參函數是指函數在定義和調用中均不帶參數,調用和被調用函數之間不進行參數傳遞。
2有參函數:參數包括形式參數和實際參數,在函數定義和說明時的參數稱之為形參,調用時給出的函數稱之為實參。
2.2函數的定義
定義的一般形式:
類型說明符 函數名(類型名 形式參數1,類型名 形式參數2,.....)
{
函數體
}
函數名:是一個標示符,取名要求有意義、簡短,同一源程序文件中不能重名。
類型說明符:值函數的返回值類型,使用void來指定函數無返回值,返回值類型可以是基本類型也可以是指針、結構體和用戶自定義類型。
形式參數:定義函數時的參數稱之為形式參數,參數列表說明了參數的類型和個數,一下兩種方式都是正確的:
int max(int a,intb){
Return 0;
}
int max(a,b)
int a,b;{
Return 0;
}
函數體:即函數具體功能的實現。
調用函數判斷素數例子代碼:
#include "math.h"
main() {
int n;
int flag;
printf("input n:\n");
scanf("%d",&n);
flag=prime(n); /*函數調用*/
if (flag) /*判斷返回值*/
printf("%d is prime.\n");
else printf("%d is not prime.\n");
}
int prime (int n){
int m;
for (m=2;m<=sqrt(n);m++)
if (n%m==0) return 0;
return 1;
}
3函數調用
3.1函數調用的一般形式:
函數名(參數列表);
1有返回值的函數調用:
int max(int x,int y){
return x>y?x:y;
}
調用該函數:
a=max(a,b);
2無返回值的調用
void printstart(int n){//輸出n個星號
int i;
for(i=1;i<n;i++){
printf("*");
}
}
調用該函數:
pirntstart(5); //輸出5個星號
3.2被調用函數的聲明和函數原型
除去scanf和printf兩個函數外,任何系統標准函數的調用都必須在本文件的開頭用編譯預處理命令#include將函數所在的頭文件信息包含到本文件中。例如:#include "stdio.h"
如果被調用的函數是用戶自己定義的函數,除了對函數功能的定義以外,通常還應在主調用函數或主調函數所在的源文件中對被調用函數進行聲明,其目的是指出被調用函數的返回值類型和參數的個數和類型,以便在調用該函數時系統按此進行檢查。
聲明函數的一般格式是:
類型標示符 函數名(參數類型1,參數類型2,...);
調用函數和主調用函數的位置關系主要分為三種情況:
1調用函數和主調用函數在同一文件中,且主調函數在調用函數的前面。
2調用函數和主調用函數在同一文件中,且主調函數在調用函數的後面。
3調用函數和主調用函數不在同一文件中。
三種位置關系的例子代碼
1被調用函數位於主函數之後:
#include "stdio.h"
main(){ /*(1) main( )在sum()前面*/
int n;
long p;
long sum(int); /*函數聲明*/
scanf("%d",&n);
p=sumt(n); /*函數調用*/
printf("\n %ld",p);
}
long sum( int m){ /*函數定義*/
int i;
long s=0;
for(i=1;i<=m;i++)
s+=i;
return(s); /*函數返回*/
}
2被調用函數在主函數之前:
#include "stdio.h"
long sum( int m){ /* 函數定義*/
int i;
long s=1;
for(i=1;i<=m;i++)
s+=i;
return(s);
}
main(){
int n;
long p; /*不需函數聲明*/
scanf("%d",&n);
p=sum(n); /*函數調用*/
printf("\n %ld",p);
}
3被調用函數在另一個文件中,首先我們設sum()函數存放在fun.c中,則編寫主調用函數時需用include命令對被調用函數聲明:
#include "fun.c"
main(){
int n=7;
n=sum(n);
}
4參數傳遞
4.1形式參數和實際參數
參數傳遞中的數據變化:
#include "stdio.h"
void fun(int num) {
num=20;
printf("%d\n",num);
}
main(){
int x;
x=10;
fun(x); /(調用函數無返回值的函數fun*/
printf("%d\n",x);
}
運行結果:
20
10
函數fun有一個形參變量num,當main函數調用時,實參x的值傳送到num,在fun函數中雖然num被修改為20,但是並不影響x的值。
程序從main開始運行,這是x擁有儲存空間,初值為10,當fun函數被調用時,為形參分配空間,接受實參的值,流程轉到調用函數fun中執行,為變量num重新賦值為20,次數賦值對實參沒有影響,同時實參在fun函數中也不可用,函數調用結束後,形參空間撤銷,流程返回。這就是值傳遞。
當函數有多個參數時,在調用實參列表的求值順序是並不確定的,不同的系統可能不同,如果想知道自己系統的求值順序可以使用自增、自減來測試:
#include "stdio.h"
int f(int a,int b,int c)
{
int z;
z=a+b*c;
return z;
}
main()
{
int x=3,y;
y=f(x,x++,x++); /*由右至左對實參求值*/
printf("%d\n",y);
}
運行結果:17.
4.2函數的返回值:一個函數的實現都是含有特定的功能,有時函數執行結束後需要將執行結果返回給調用函數,這個結果就可以通過返回值表現出來,返回值使用return語句來實現,具體格式是:
return 表達式; 或return (表達式);
該語句的功能就是返回一個值給主調用函數,釋放函數在執行過程中分配的所有空間,結束被調用函數的運行,將流程控制交給主調函數。
如果沒有返回值可以使用renturn ;來代替。
float fun(int n){
return n;
}
main(){
float x;
x=f(20);
printf("%f",x);
}
5數組作為函數的參數
當數組作為參數傳遞時,系統就會將作為實參的數組元素首地址傳遞給形參所表示的數組名,即參數傳遞時地址傳遞。
5.1一維數組作為參數:一維數組名表示數組中下標為0的元素在內存中的起始地址。假設數組a在內存中從2000地址開始存放,則a的值為2000。2000是地址值,是指針型數據。
數組作為參數傳遞的例子代碼:
#include "stdio.h"
void input(int a[10],int n) { /*輸入函數*/
int i;
for(i=0;i<n;i++)
scanf("%d",&a[i]);
printf("\n");
}
int maxa(int a[10],int n) { /*求最大值函數*/
int i;
int m;
m=a[0];
a[1]=100;
for(i=1;i<n;i++)
if (m<a[i]) m=a[i];
return m;
}
void print(int a[10],int n) { /*輸出函數*/
int i;
for(i=0;i<n;i++)
printf("%4d",a[i]);
printf("\n");
}
void main() {
int b[10];
int max;
input(b,10);
max=maxa(b,10);
printf("array max is %d\n",max);
printf("the array is :\n");
print(b,10);
}
地址傳遞就是形參和實參同時指向同一個地址,修改在改存儲單元上的數據。
6函數的嵌套調用
C語言中不允許嵌套的函數定義,但允許函數嵌套的調用,即在被調用函數中又調用其他函數。
嵌套調用的程序結構:
哥德巴赫猜想例子代碼:
#include "stdio.h"
#include "math.h"
int prime (int); /*函數聲明*/
void even(int);
main(){
int n;
printf("Enter n even number(>=6):");
scanf("%d", &n);
if(n%2==0&&n>=6)
even(n); /*函數調用語句*/
else
printf("The %d isn't even number\n",n);
}
void even(int x) { /*找素數函數*/
int i;
for(i=2;i<=x/2;i++)
if(prime(i)) /*調用判斷素數函數*/
if(prime(x-i)){
printf("%d=%d+%d\n",x,i,x-i); }
}
int prime(int n) /*判斷素數函數*/
{ int i, k= sqrt(n);
for(i=2; i<=k; i++)
if (n%i==0) return 0;
return 1;
}
7函數的遞歸調用
7.1遞歸的含義:函數直接或間接的對自己進行調用就是遞歸。
例如:
int f(int x){
int y;
z=f(y);
return z;
}
這個函數就是遞歸的調用自己,但該函數會無止境的運行下去,這顯然是不正確的,編寫代碼時應該給程序一個跳出遞歸的出口。
8練習
1遞歸求階乘
2.使用函數實現比較4個數的大小。
3.實現打印年歷功能。
摘自 letthinking的專欄