第5章 函數(修改版).ppt_第1頁
第5章 函數(修改版).ppt_第2頁
第5章 函數(修改版).ppt_第3頁
第5章 函數(修改版).ppt_第4頁
第5章 函數(修改版).ppt_第5頁
已閱讀5頁,還剩43頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、1,第五章 函 數,結構化(模塊化)程序設計基礎,2,5.1.1 函數的概念 、模塊化及優(yōu)點為模塊化而提出函數 結構化程序設計要求:將復雜的系統(tǒng),按照自頂向下、逐步細化的設計方法,劃分為相對獨立的、功能較為單一的子系統(tǒng)的組合。每個子系統(tǒng)稱為模塊,在C語言中就是子函數(函數)。,復雜問題,main(),a塊,b塊,c塊,a1塊,a2塊,b1塊,b2塊,c1塊,c2塊,各模塊之間的連接 (如帶參數調用)稱之為接口。,模塊化的6個優(yōu)點: 功能單一完整;可以獨立設計;單獨調試;易于維護;通用性強;便于分工協(xié)作。,5.1 函數概述,3,、按照不同的分類方法,函數可分為 (5 類): 、用戶型和系統(tǒng)型(又

2、稱標準庫函數,參見實驗指導后的附錄和教材5.3)函數之分。 、主函數和子函數(即調用和被調用)之分。 、有參函數和無參函數之分。 、有具體返回值和無具體返回值型函數之分。 、內部函數和外部函數之分: 內部函數:存儲類型以 static 定義的,即只能在本文件中使用。 外部函數:存儲類型以 extern(或省略)定義的,即既可被本文件(如a1.c)中的函數調用, 也可被其它文件(如a2.c)中的函數調用)。,4,、用戶子函數(函數) 結構和執(zhí)行過程引例 :,#include void main(void ) int a,b,c; scanf ( “%d,%d” , ,主函數,函數的類型,決定返回

3、值的類型。,函數名:用標識符命名,是引用函數的標識,并得到返回值。,子函數的形式參數(接口參數)說明表。,子函數定義一般要5確定: 函數的名稱;函數返回值的類型;函數的參數;函數的功能;是否返回值。,子函數定義,函數功能:此處是找大的。,引用(使用或調用)子函數!,5,5.1.2 用戶子函數(函數)定義的一般形式及2個說明,格式 : type 函數名(形式參數說明表) 內部變量定義語句; 功能語句; ,如果函數有返回值,應另含有return語句。,int max(int x ,int y) int z ; z=x=y? x : y ; return ( z) ; ,說明:,、函數不能嵌套定義(

4、但可以嵌套引用,且可引用自己)。,int max(int x,int y) int cx(int c,int d) /*Error !*/ ,、函數如果無返回值,type 應說明為空類型void。函數無 參數,也應定義成void。主函數和子函數都可以如此:,void Bell(void) printf(“2008.08.08”); ,6,5.1.3 函數的調用(使用或稱引用),、調用方式: 函數名(實參表),一般,函數調用有 4種出現形式: 獨立語句形式: max(a,b); 表達式形式: c= max(a, b) *2 ; 用戶函數參數: c=max (a , max (b , d) ) *

5、2 ; /*嵌套*/ 標準函數參數: printf(“%dn”, max(a,b) ) ;,7,、帶參數函數(max)調用過程,void main (void) int a ,b ,c ; scanf (“%d,%d” , /*括號也可不要*/ ,、從右到左先計算實參的值,再從右到左向形參傳遞值。,2,3,a b,X y,2,3,、轉移到子函數中運行,執(zhí)行到“return 表達式”語句時, 返回表達式的值,并將值由函數名帶回到調用函數的調用處。,z,3,8,(3)、關于返回值的3點補充說明:,子函數只能通過return語句返回一個值; 也可以不返回值,此時應在定義函數時用void類型加以說明。

6、 子函數若沒有return語句,則由它的最后一 個“”返回一個不確定的值!,函數中可以出現多個return語句,但只有一個有效。,int max ( int x , int y ) if (x=y ) return x ; return y ; ,返回值的類型應與函數的類型一致,如不一致,以函數類型為準,先轉換為函數類型后,再返回。,int max(void) float z; return z; /*z先轉為整型*/ ,(4)、 函數的調用聲明(說明) 為了確保參數類型、返回值類型正確和調用合法,有時必須在函數調用前對其加以說明,該說明稱之為聲明,如:,9,例如:通過子函數求 xn;主函數輸

7、出x19。,#include /*系統(tǒng)函數調用聲明*/ double pow(float x, int n); void main(void) int i ; for (i=1; i =9 ; i+) printf ( “%d , %f n” , i, pow(2.5 ,i); double pow(float x ,int n ) int i ; double p=1 ; for (i=1 ; i=n ; i+) p=p*x ; return ( p) ; ,用戶函數調用聲明:因子函數是非整形,并且出現在函數調用之后,故此處聲明不能缺。,定義子函數,調用函數。,特別注意: “定義” 函數與“

8、聲明”函數的不同意義 !,10,5.2 再論函數間的數據傳遞,兩個過程:函數在調用的過程中,調用函數和被調函數存在 數據的相互傳遞(接口)。數據的傳遞包括兩個過程:,、將待處理數據(數據本身或數據的地址)傳給被調函數;,、將結果數據(數據本身或數據的地址)返回給調用函數。,調用函數(主),被調函數(子),向被調函數傳數據或數據的地址(帶參數)。,將結果數據或數據地址返回調用函數。,先講指針 !,11,主、子函數間有 5種數據傳遞方式:,、通過函數參數傳待處理數據的實際值,數據傳遞單向性(前面已多次出現) ; 、通過return返回結果數據的實際值 (前面已多次出現) ; 、通過函數參數傳待處理

9、數據的地址值(首地址) ;地址參數本身仍然為單向傳遞,但其數據可理解為雙向傳遞;實際 上,此種情況數據本身沒有傳 ! 、通過return返回結果數據的地址值; 、通過全局變量或數組傳待處理數據的實際值或結果數據的實際值。 下面分別舉例介紹: (以在主函數中I/O數據,在子函數中實現兩數相加求和為例;求兩數和并非一定如此):,12,5.2.1 通過函數參數和return傳數據的實際值(、 ),通過實參與形參的結合,將數據本身傳給形參;形參的數值改變不影響實參,因為它們有各自存儲單元,單向傳。,#include int sum(int , int ) ; /*或int sum(int x,int

10、y );或缺*/ void main(void ) int a , b , c ; scanf (“ %d , %d” , ,調用函數,被調函數,x、y為形參。,a、b為實參變量的值。,傳遞參數,a:2,b:3,y:,x:,3,2,z:,5,返回值,13,5.2.2 通過函數參數傳數據的地址值( ),形參定義為指針變量(以此接收另一變量的地址),實參為 變量的地址;在被調函數中,由指針操作所指變量;高效 !,2,3,a,b,2000H,2002H,x,y,2002H,2000H,前例修改如下: #include void main(void ) int a,b,c; scanf (%d,%d,

11、 ,指針變量,普通變量,3000H,3002H,14,前例可否修改為如下(即不要return,但在主函數中仍可得到被調函數的處理結果) ? #include void sum(int *x,int *y,int *z) *z=*x+*y; void main(void ) int a,b,c=0; scanf (%d,%d, ,屬于,即通過return返回結 果數據的地址值:指針型函數。,輔以圖示說明,15,5.2.3 用外部變量或數組傳數據實際值或結果實際值(),定義外部變量,調用函數和被調函數共享;任何函數對其值的修改,都會影響到其他函數中所用到的外部變量的值。,#include /*前例

12、又可修改為:*/ int c=2 ; void sum (int x , int y ); void main ( void) /*還是求兩個數和*/ int a ,b ; scanf ( “ %d ,%d ” , ,定義外部變量,2,c,5,注意:應盡量少用外部變量,這樣可使函數內部的內聚性強,函數之間的偶合性弱,從而模塊的獨立性就較強。,16,5.3 標準 函數庫 (略),17,5.4 數組(數值、字符、一、二維)做函數參數,問題的提出: 在數組一章曾經說過:引用數組時只能引用數組元素。如果用元素做實元參數傳遞數組,則函數需要參數(實參和形參)的量太多且不高效。為了解決此問題,也可以傳遞數

13、組的首地址傳數據的地址高效。 當然,元素是可以做實元參數的,它的用法同一般變量,所以這里不再專門介紹,見08版5.4.1和09版p129下面。,數組名或指針名作實參,被調函數的形參形式有3種:,形參為指針: int ave( int *p,int num) 形參為無下標數組: int ave( int a ,int num) 形參為有下標(大小不限)數組: int ave( int a10 , int num) 以上也適合二維 數組(數值、字符) ! 下面舉一個例說明以上3種情況:,18,例1:子函數求數組平均值av ,主函數中I/O(av、a0)值。,#include float mean(

14、 int *, int); /3種情況不變 void main (void) int a5=1,2,3,4,5 ; float av ; av=mean(a,5) ; printf( “av=%f,%dn” ,av ,a0 ) ; float mean( int *p , int num ) int i ; float avg=0 ; for (i=0 ; inum ; i+) avg+= *p+或pi或*(p+i) ; avg/=num ; *(p-num) =-1; return (avg ) ; ,形參為指針; *p+或*(p+) 均表示先取*p做運算,再做p+(指向下一數據單元,非字節(jié)

15、單元) ;,形參為指針,形參為無下標數組;,無下標數組。,int a ,a0 =-1;,a i ;或*(a+i) ;,int a10,形參為有下標(大小不限,虛)數組。,形參為有下標數組。,兩點說明:,三種方法的實質都是傳遞地址。,形參數據的任何改變都影響(共享 空間)實參數據(如*(p-num)=-1;) 。,使子函數通用,num等價“4*num” !但不可這樣用,用指針p,如何做?,補:,*p=a,19,例2、在子函數中求3行3列矩陣(二維數組)主對角線元素的和并將其返回。試用各種可行的參數形式和元素的表示方法。,void main() /hscs1.c int s, a33=1,2,3,

16、1,2,3,1,2,3; s=fun(a) ; / printf(Sum=%dn,s); ,還有可行的實參、形參和元素的表示方法如下: fun(a) 與int fun( int a3 ) 實參用一級指針名p(p=*a)和二級指針名pp(pp=a),形參用數組,可行但都有警告)。 實、形參都用指向元素的指針,訪問元素方法用指針加位置號: int *p=*a;s=fun(p);與int fun(int *p) 。訪問元素:*(p+3*i+i) 實參用行指針名,形參不變;或fun(a) 與int fun( int (*p)3)。 實參用指針數組名與形參用二級指針。 實參用二級指針名與形參用指針數組。

17、 無參和使用外部靜態(tài)數組。等。,#include stdio.h int fun(int b33 ) / int sum=0,i; for(i=0;i3;i+) sum=sum+ bii ; return sum; ,20,21,22,23,5.5 函數的嵌套和遞歸調用,5.5.1 函數的嵌套調用 1、引言 C 語言的函數不 允許嵌套定義 ,但可 以 嵌套調用。見右圖 (假定一個C程序有3個 函數單位),main,a 函數,b 函數,調用a 函數,調用b 函數,1,2,3,4,5,6,7,8,9,結束,返回,返回,2、嵌套調用實例,逐級返回,逐級調用,例如: 計算 s=(1*1*1)!+(2*

18、2*2)!+(3*3*3)!+(4*4*4)!+(5*5*5)!,使用嵌套調用結構設計程序,程序清單如下:,main,b函數,a函數,24,#include double fact(int m) /求階乘的子函數 double a=1; int i ; for(i=1;i=m;i+) a=a*i; return a; / ,double squa(int n) /求“n*n*n”的子函數 double b; int k ; b= fact(n*n*n) ; return b; / ,void main(void) double s=0; int i; for(i=1;i5;i+) s=s+ s

19、qua(i) ; printf(“n s=%en”,s); ,25,5.5.2 函數的遞歸調用,概念:函數直接或間接地自我調用稱為遞歸函數。,int fun1(int x) z=fun1(y); ,int fun1(int x) int fun2(int t) z=fun2(y); c=fun1(a); ,直接引用自身。,控制條件和遞歸表達式:遞歸在沒有控制條件的情況下是無 窮的遞歸(相當于死循環(huán)),不能用。,無控制條件: void main(void) printf(“*n”); main(); ,加控制條件,void main(void) printf(“*n”); ch=getchar(

20、); if(ch!=9)main(); ,26,舉例1:用遞歸的方法求n!(曾使用過循環(huán)遞推法)。,n!=,1 n=0或n=1,n(n-1)! n1,f(n)=,1 n=0或n=1,n*f(n-1) n1 (人工求結果就是 根據遞歸表達式),#include long lfac(long); void main(void) long n=4,x; x=lfac(n); printf(“n%ld”,x); long lfac(long n) if (n=0|n=1) return 1; /不要理解為1返回到主函數 else return(n*lfac(n-1); /等價:return(4*3*2

21、*1) ,lfac(4),4* lfac(3),3* lfac(2),2* flac(1),2*1,3*2*1,4*3*2*1 /最后一次返回乘積結果到主程序,遞過程,歸過程(隱含逐次返回調用處),1,提醒:理解為逐次返回到調用處,最后返回主函數 !,27,遞歸舉例2: 寫出下列程序的執(zhí)行結果,main() int y; y=f(6); printf(“%d”,y); f(int n) int f1; if(n=1|n=2) f1=1; else f1=f(n-1)+f(n-2); return(f1); ,8,分析:當執(zhí)行f(6)時 f1=f(5)+f(4) =f(4)+f(3)+ f(3)

22、+f(2) = =f(3)+f(2)+f(2)+f(1) + f(2)+f(1)+f(2) = = f(2)+f(1)+ f(2)+f(2)+f(1) + f(2)+f(1)+f(2)=等價if(n=1|n=2) return 1(即(f1=11) =1+1+1+1+1+1+1+1= 8,理解為遞歸終止條件;返回到調用處 !,返回到調用處,最后返回主函數 !,28, 5.6 變量的作用域和存儲類型,變量不但有數據類型,還有存儲類型。 而存儲類型不但確定了變量在空間上的作用域,而且還確定了變量在時間上的生存期。,變量的存儲類型有以下四種:,自動類型 auto /定義時可缺省,寄存器類型 regi

23、ster,靜態(tài)類型 static,外部類型 extern /聲明外部變量;定義時可缺省,從而變量的定義格式是:,存儲類型 數據類型 變量名表;,確定變量在內存中的表示方法。,確定變量的生存期和作用域。 在函數內部,該項省略表示auto存儲類型。在函數外部,該項省略表示extern。,29,auto存儲類型,3個特點:塊內定義;塊內有效;隨塊生存。,#include void main(void) auto int a,b; scanf(“%d,%d”, ,a b 的 作 用 域,iTemp的作用域,此處編譯出語法錯: Undeclared identifier,若增加定義 iTemp=0 還有

24、語法錯嗎?輸出結果 ?,特別提醒:形式參數也屬于auto型 !,4,0,30, register存儲類型,、作用域和生存期與auto相同。 、差別在于:如果CPU內部的寄存器空閑, 則使用寄存器作為變量的存儲單元,以提高速度。 主要用于循環(huán)變量。 實際上,編譯系統(tǒng)在CPU不忙的情況下,能 自動將使用頻繁的變量(如循環(huán)變量)處理成register。,31, static(靜態(tài))存儲類型,作用域: 同auto存儲類型,即塊內定義,塊內有效。,生存期: 不同auto存儲類型,從定義直到整個程序執(zhí)行結 束,退出 塊時,原值并不消失,但是塊外不能引用其值。,void row (void) ; void

25、main ( void ) /下列程序能輸出“九九”乘法表? int b ; /auto型,局部,每次進入程序塊都初始化 ! for (b=1 ; b=9 ; b+ ) row() ; void row (void ) int a=1 ; int b ; /auto型,局部 for (b=1 ; b=9 ; b+) printf ( “%5d” , a*b ) ; printf ( “ n ”) ; a+ ; ,static,改為靜態(tài)變量。,a的作用域,如何修改就能輸出“九九”乘法表?,還有哪些修改方法 ?,聯想前面講的函數間數據傳遞的5種方法 !,32, 外部變量(extern存儲類型),外

26、部變量是定義在任何模塊之外的變量。也稱為全局變量。,作用域:從定義點到文件結束(不同前3種 )。,生存期:在程序的整個執(zhí)行過程中(同static),任何函 數對外部變量值的修改,都會影響其他函數中該外部變量的值。,#include void add(void); int a,b,c; void main(void) scanf(“%d,%d”, ,定義外部變量, extern缺省。其他文件(其他編譯單位)中聲明后可用。,作用域,注意:08版教材p137關于外部變量的介紹有錯(新版無錯) : 定義(說明)與聲明不同; 定義時不加extern; 應是b1.c中使用了a1.c , 。,33,關于外部

27、變量的4點補充說明:, 通過聲明,可以改變外部變量的作用域或后定義先使用。,void main( ) int i; /*定義 i為外部變量*/ void max(int a,int b) ,i 作用域,extern int i; /聲明外部變量,數據類型可省,i 新的作用域, 通過聲明,可使外部變量被另一個文件共享。,file1.c file2.c int a ; extern int a ;,文件1定義的外部變量。,在文件2中先聲明要使用文件1中已定義的外部變量的值。,如果外部變量值只在本文件中使用,可以加static定義。,static,34,關于外部變量的4點補充說明(續(xù)):,定義外部變

28、量占用內存且增加模塊的耦合性,故應少使用 外部變量。 另外,外部變量還可能引起其他問題,見下例:,#include int i; void prt(void); void main( void ) for(i=0;i5;i+) prt(); void prt(void) for(i=0;i5;i+) printf(“%c”, *); printf(“n”); ,函數的作用: 打印五個* ,再回車。,該程序想輸出25個*,能做到嗎 ? 如何改,就能輸出25個*?,不,因函數只調用1次 !,35, 當模塊中出現和全局變量同名的局部變量時,局部變量在 模塊中優(yōu)先,見下例執(zhí)行結果:,int a=5;

29、void fun(void); void main(void) int b;b=a; /a取5 printf(%dn,b); fun(); void fun(void) int b,a=8; b=a; /這里a取8 printf(%dn,b); ,#include int a=5; void fun(void); void main(void) int b; b=a; printf(%dn,b); fun(); void fun(void) int b,a=8; extern a; /聲明 b=a; printf(%dn,b); ,5 5,右邊的程序執(zhí)行結果又是什么 ?,5 8,36,5. 用

30、戶子函數有內部函數和外部函數之分 子函數本質上是全局的,因為一個子函數就是要被 所有 函數調用;但是,可以指定子函數不能被其他文件中的函數調用。根據函數能否被其他源文件調用,將函數區(qū)分為內部函數和外部函數。 (1)、內部函數 如果一個函數只能被本文件中其他函數所調用,它稱為內部函數。在定義內部函數時,函數首部的前面必須加static,即: static 類型標識符號 函數名(形參表) 如: static int fun(int a , int b) 內部函數又稱靜態(tài)函數。使用內部函數,可以使函數只限于所在文件使用,如果在不同的文件中有同名的內部函數,互不干擾。這樣不同的人在編寫子函數時,不必擔

31、心函數是否會與其他文件中的函數同名(如同前面所講的static型變量)。,37,(2)、外部函數 、在定義函數時,如果在函數首部的最左端冠以存儲器類型關鍵字extern或缺?。– 語言規(guī)定,如果在定義函數時省略extern ,則隱含為外部函數), 則表示此函數是外部函數。外部函數本文件和其他文件都可調用。例如: extern int fun(int a , int b) 、如果某文件要調用另一個文件中的此函數,應在調用此函數的文件中先聲明所調用的函數是外部函數(聲明時extern 也可缺?。?。 、舉例:略,聲明外部變量不能缺省,38,5.7 編譯預處理,C 語言除了說明語句、執(zhí)行語句及控制語

32、句外,還有編譯 預處理語句。該類語句的作用是在編譯前對程序作一定的 處理。編譯預處理是C語言的重要特征。,編譯預處理語句的語法形式:,#關鍵詞 參數表,編譯預處理語句不用分號作為語句結束標志!共有3類:,宏定義預處理, 不帶形式參數的宏定義和宏替代,作用:定義常量名,提高程序的可讀性,便于修改。,格式: #define 宏名 字符串,#define PI 3.1415926 void main(void) flaot r; scanf(“%f ”, ,編譯前用字符串替代程序中的宏名。,3.1415926,39,6點說明:,宏名一般用大寫; 編譯前用字符串無條件替代程序中的宏名;,#define

33、 PI 3.1415926; /注意串中若有分號的情況 ! PI*r*r,3.1415926;*r*r,宏名的有效范圍,從定義處到整個程序尾。也可以通過 #undef 修改定義范圍。,#define G 9.8 void main(void ) #undef G ,現G的范圍,可以在宏定義的字符串中使用已定義過的宏名。, “ ” 中的宏名不替換。,#define R 3.0 #define PI 3.14159 #define S PI*R*R,printf(“S=%f”,S);,不替換。,宏定義可應用于定義輸出格式等,以簡化源程序的書寫。,40,例:宏定義可應用于定義輸出格式,以簡化源程序的

34、書寫。,41, 帶形式參數的宏定義和宏調用,格式: #define 宏名(參數表) 含參數的字符串,#define S( a , b ) a*b area=S( 3 , 4 );,替換過程(編譯前完成):,用實際參數替換宏定義的形參。,替換字符串中的參數。,替換整個宏(即用第步的結果替代宏調用)。,3*4,帶參的宏與函數的區(qū)別:,帶參宏中的形參不分配內存;也不返回值;只是在編譯前(預處理)按規(guī)則替換;實參若是表達式不能先計算。,關于帶參宏的2點說明:,要嚴格按格式書寫,宏名后不能加空格,否則會造成錯誤。,對于 #define S (a,b)a*b , S(3,4)被替換成:(a,b)a*b(3

35、,4),參數有可能用到表達式時,字符串中參數字符要加()。,它與無括號的作用不同 ! #define S(r) PI*r*r S(a+b)替換為:PI*a+b*a+b,#define S(r) PI*(r)*(r) S(a+b)替換為:PI*(a+b)*(a+b),42, 文件包含預處理,格式: #include #include “被包含的文件名”,其中:表示編譯系統(tǒng)定義路徑,即先找?guī)旌瘮担?“ ”表示用戶指明路徑,即先找用戶目錄。,作用:將指定文件的內容(原形)和當前文件合并一起編譯。 聲明系統(tǒng)函數調用。 共享某指定的文件(但以這種方式共享會造成代碼冗 余)。,4點說明:,一個#include只包一個文件。被包含的文件也可是用戶文件。,被包含文件可以嵌套包含文件,見下面例子中my.h中又出現了#include 。,對用戶來說,一般將宏定義及函數的原型聲明放在被包含文件中,也可做到簡化程序書寫。,被包含的文件一般擴展名為.h,稱為頭文件。,43,文件包含舉例: 求半徑為r的園面積、園周長。

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論