1、1,函数,第八章,2,函数的定义、说明与调用函数之间参数传递规则变量的存储类型与特性函数递归的概念与执行过程递归程序的编程方法,本 章 要 点,3,第一节 C程序结构 第二节 函数定义 第三节 函数的调用与返回 第四节 在函数之间传递数据 第五节 变量的存储属性 第六节 函数的递归调用 第七节 库函数简介,4,8.1 C程序结构,5,1 一个C程序由一个或多个源程序文件组成。将若干函数和其它内容分别放在若干个源文件中,以达到分别编写,分别编译,提高运行效率的目的。同时一个源文件可以为多个C程序公用。 2 一个源程序文件由一个或多个函数组成。每一个源文件可以独立编译,即一个源程序文件是一个编译单
2、位。,说明:,6,3 函数是最小的功能单位,一个函数可以被位于不同的源文件中的其它函数调用 4 一个C语言程序有且仅有一个主函数main( ),主函数可以放在任何一个源文件中,无论主函数在程序的什么位置,程序一定是从主函数开始执行的,并且在main函数中结束整个程序的运行,7,5 除main函数外,所有函数都是平行的,即所有函数都是互相独立的,一个函数并不从属于另一个函数,函数间可以互相调用,但不能调用main函数6 将属于同一程序的不同源文件组装成一个程序可以通过工程文件实现,8,模块化是结构化程序设计的基础。采用模块化程序设计有很多优越性: 控制程序设计的复杂性 提高软件的可靠性 提高软件
3、开发的效率 提高软件的可维护性 提高程序的重用性,9,函数分类:,从用户使用的角度看,函数分为: 库函数,库函数是由系统提供的,用户不必自己编写这些函数,可以直接使用它们。 用户函数,即用户自己定义的函数,用以解决用户的专门需要。 从函数的形式看,函数分为: 无参函数。在调用无参函数时,不必将数据传递给被调用函数。 有参函数。在调用有参函数时,在主调函数和调用函数之间有数据传递。,10,数据类型 函数名(形参表说明) 声明语句执行语句 ,8.2 函数定义,函数定义的一般形式:,无参函数的定义形式:,数据类型 函数名( ) 声明语句执行语句 ,函数头,函数体,11,1.函数定义中的类型是指函数返
4、回值的类型。函数返回值不能是数组,也不能是函数,除此之外如何合法的数据类型都可以是函数的类型。 2.函数的类型可以省略,当不指明函数类型时,系统默认的函数类型是整型。,说明:,12,3.函数名是标识符,是函数定义中唯一不可省略的,用于标识函数,并用该标识符调用函数。 4.形式参数表是用逗号分隔开的一组变量说明,包括形参的类型和形参标识符。其作用是当调用函数时,接受来自主调函数的数据。无参函数( )不可省略。,13,5. 有参函数的形参声明放在函数头当中,而形参的作用域也仅仅存在于本函数之中。 6. 括起来的部分是函数体。函数体是一段程序,确定该函数应执行的规定动作,集中体现了函数的功能。 不可
5、省略,函数定义实例1.语言中一个最简单的函数:dummy ( ) /* 函数名:dummy */ 没有数据类型说明、形参和形参说明,函数体为空。2.求阶乘函数facto的定义。long facto (int x ) long y;for (y=1; x0; - -x)y = y * x;return (y);,函数名,形式参数列表,函数类型,函数体,函数返回,15,main( ) int a,b,c;printf(”Enter a,bn”);scanf(”%d,%d”, ,形式参数列表:是用,分开的一组变量,用来接收调用时传入的数据,函数调用,3.求两个变量的最大值,16,有参函数调用的形式为
6、: 函数名(实参表列); 无参函数调用的形式为: 函数名(); 在函数调用过程中,实参与形参的个数应当相等,并且按顺序一一对应。实参可以是常量、变量或是表达式,甚至是另外一个函数调用的返回值。,8.3 函数的调用与返回,17,函数调用的过程:在一个函数中调用另一个函数时,程序将控制从调用函数处转移到被调用函数,并执行被调用函数。在执行完被调用函数的所有语句或者遇到return语句时,程序的控制要返回到调用函数中原来调用函数的地方继续执行。,18,1 函数语句 被调函数在主调函数中以语句的方式出现,通常只完成一种操作,不带回返回值。 func( ) printf(“This is a progr
7、am!”); main( ) func( ); ,函数的调用方式有三种形式:,19,2 函数表达式将函数的调用结果作为运算符的运算分量,这种调用方式下被调用函数必须有返回值。main( ) int a ,b ,i ,j ,c ;scanf(“%d %d %d %d”, ,20,3 函数参数函数的调用结果可以作为其它函数的实参,此时函数必须有返回值c = pow( a ,pow( b ,i);,21,主调函数调用被调函数之前,必须对被调函数做声明,声明的目的是告诉编译系统函数值是什么类型,有多少个参数,每一个参数是什么类型,为编译系统进行类型检查提供依据。函数说明的一般形式函数类型 函数名(形参
8、类型1 形参名,形参类型2 形参2,);,函数的声明,22,函数说明与函数头唯一区别:函数说明语句的( )之后必须有分号,而函数定义头部的( )之后没有分号。“说明”与“定义” 的区别: “说明”仅是向编译系统的一个说明,不包含具体的执行动作。 “定义”是给出函数的程序体。,23,1 当函数的返回值为整型或字符型时,如果在同一个文件中既定义函数,又调用该函数,则不论定义函数与调用函数在源程序中的位置关系如何,都可以省略函数声明。 2 当被调函数返回值是其它类型时,如果函数定义和函数调用在同一个文件中,且函数定义在源文件中的位置在调用该函数之前,则可以省略函数的声明。,在下列情况下可以省略函数声
9、明:,24,1 如果函数定义在源文件中的位置在调用该函数之后,则必须给出被调函数的声明。 2 如果函数的定义与调用在两个不同的文件中,则不论函数返回值的类型是什么,在调用该函数之前,都必须给出函数声明。,在下列情况必须对函数进行声明:,25,main( ) int m; long n;long facto(int x);scanf(“%d“, ,函数执行过程,main ( ) mm = facto( m );,facto ( x ) return (y ); ,调用,返回,例:用函数facto计算 m 阶乘,函数声明,函数调用,函数定义,函数返回值,26,main( ) int m, n; l
10、ong cmn;long facto( int x ); scanf (“%d%d“, ,例:计算:C(m,n) = m!/(n!*(m-n)!),27,对被调函数的声明可以简化为:函数类型 函数名(形参类型1 ,形参类型2 ,); 上式称为函数原型 long facto( int ); 通常将一个文件中需要调用的所有函数原型写在文件的开始。,函数原型,28,从函数返回的两种方法 用return语句从被调函数中退出,返回调用它的程序中(也称为主调函数); 被调函数如果没有return语句,被调函数执行结束遇到最外面的 ,返回主调函数。return的两重作用: 控制程序从当前函数(被调用函数)中
11、退出,返回到调用函数中继续执行; 从被调用函数向主调函数返回一个值(称为返回值)。,函数的返回,29,函数除了void类型之外,均有一个返回值,返回值的类型就是在定义函数时说明的函数类型。 当返回值类型为整型int时,在定义函数时可省去函数的数据类型定义说明。max ( int x , int y ) int z;z = x y ? x : y ;return ( z ); 对于返回值的类型为非整型的函数,在定义函数时,必须明确地给出函数数据类型说明;,返回值规定:,30,返回语句的格式与功能格式1: return;功能:将控制从被调函数返回到主调函数。格式2: return (表达式);或:
12、return 表达式 ;功能:在被调函数中计算表达式的值,将计算结果按照函数说明的函数类型返回到主调函数,并将控制返回主调函数。,31,main( ) int a, c ;scanf ( “%d” , ,例:,32,说明:(1)函数的返回值只能有一个。(2)如果函数值的类型和return语句中表达式的类型不一致,则以函数类型为准,按照数据类型转换规则进行数据转换,即由函数类型决定返回值的类型。,33,main( ) float a=4.5 , b=6.8 ; int c ;c = max( a , b ) ;printf ( “MAX is %d” , c ) ; max( float x ,
13、 float y ) float z ;z = xy ? x : y ; return z ; Output: MAX is 6,34,(3)一个函数体内可以有多个返回语句,不论执行哪一个,函数都将结束,并将控制流程返回到主调函数。if ( x = 0 )return ( 2*x*x-x ) ;else return ( 2*x*x ) ;,35,C语言中,函数的定义是平行的,不允许进行函数的嵌套定义,即不允许在一个函数体中再定义一个新的函数。而函数之间的调用可以是任意的,即允许在一个函数内再调用其它函数,这种调用称为函数的嵌套调用。,函数的嵌套调用,36,main函数 调用函数 A; ,函数
14、 A 调用函数 B; ,函数 B ,调用,调用,返回,返回,37,void func1( ) func2( ) ;printf( “ Function1! ” ) ; void func2( ) printf( “ Function2! ” ) ; main( ) func1( ) ;printf( “Main!” ) ; ,38,在不同的函数之间传递数据,可以使用: 参数:通过形式参数和实际参数 返回值:用return语句返回计算结果 全局变量:外部变量函数参数的传递规则:C语言中,函数参数遵守“值传递”规则,即在调用函数时,将实参变量的值取出来,复制给形参变量,使形参变量在数值上与实参相等
15、。在函数内部使用从实参中复制来的值进行处理。中的实参可以是一个表达式,调用时先计算表达式的值,再将结果(值)复制到形参变量中。,8.4 在函数之间传递数据,39,main ( ) int a=5, b=10;printf(“brfort swap a=%d,b=%dn“,a,b);swap(a, b); printf(“after swapa=%d,b=%dn“,a,b); swap (int x , int y) int temp;temp=x; x=y; y=temp;printf(“in swap x=%d,y=%dn“,x,y); ,例:用函数交换两个变量的值,5,main a = 5
16、;b = 10;swap(a, b); ,swap ( x, y ) temp = x; 语句 x = y; 语句 y = temp; 语句 ,5,10,实参变量 a,实参变量 b,形参变量 x,形参变量 y,变量 temp,复制,复制, temp = x, x = y, y = temp,调用swap函数,5,10,10,5,41,main( ) int i;for (i=0; i0; -n ) p *= x; return (p); ,例:计算 2 和 -3 的 0 到 5 次幂,42,值传递的优点:值传递的优点在于:被调用的函数不可能 改变调用函数中变量的值,而只能改变它的局 部的临时副
17、本。这样就可以避免被调用函数的 操作对调用函数中的变量可能产生的副作用。值传递的缺点:在值传递方式下,每个形式参数仅能传递 一个数据,当需要在函数之间传递大量数据时, 值传递方式显然不适用。,43,数组作为函数的参数,数组名作为函数的参数,必须遵循以下原则: 1 如果实参是数组名,则形参可以是同样维数的数组名或是指针。 2 实参数组和形参数组必须类型相同,形参数组可以不指明长度。 3 数组名作为函数参数时,实参与形参都对应的为数组的首地址,此时函数调用为传址调用方式。这样两个数组就共用同一段内存单元。因此形参数组中元素的值如果发生变化会使实参数组元素的值同时发生变化。,44,void sort
18、( 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 ( arrayj arrayk ) k = j ;t = arrayk ; arrayk = arrayi ; arrayi = t ; main() int a10 , i ;for ( i = 0 ; i 10 ; i+ ) scanf ( “ %d ” , ,对数组中的10个整数由小到大进 行排序,45,4-4 变量的存储类型与作用域,数据类型决定为变量分配的内存单元的长度,数
19、据的存放形式。(从程序设计角度,决定了可以表示的数的范围),问题:1. 何时为变量分配内存单?2. 变量位于内存的什么位?3. 变量的有效作用范围?,46,1 静态存储:变量存储在内存中的静态存储区,在编译时就分配了存储空间,在整个程序运行期间,该变量占有固定的存储单元,变量的值始终存在,程序结束和才释放。这类变量的生存期为整个程序。 2 动态存储:变量存储在内存中的动态存储区,在程序运行过程中,只有当变量所在函数被调用时编译系统才临时为该变量分配内存,函数调用结束变量空间释放。这类变量的生存期为函数调用期间。,变量的生存期是指变量值保留的期限:,47,变量的作用域是指变量的有效使用范围 一个
20、函数 一个文件 一个程序 1 局部变量:在一个函数或复合语句内定义的变量,局部变量仅在定义的函数或复合语句内有效。 2 全局变量:变量在所有函数之外定义称为全局变量,期作用域为从定义出开始到本文件结束。全局变量一经定义,编译系统为其分配固定的内存,在程序运行过程中始终占有固定单元。,变量的作用域,48,1 不同函数内的局部变量可以重名,互不影响。 2 全局变量与局部变量可以同名,在局部变量起作用的范围内,全局变量不起作用。 3 全局变量的初始化只能有一次,是在对全局变量说明时进行初始化。,说明:,49,int x = 100 ; main( ) int x =10 ;f( ) ;printf(
21、 “%dt” , x ) ; f( ) x+=100 ;printf( “%dt” , x ) ; Output: 200 10,50,自动变量(auto) 静态变量(static) 外部变量(extern) 寄存器变量(register)auto、static、extern和register为存贮类型说明符。变量说明的一般形式: 存贮类型说明符 类型说明符 变量名称;,变量存贮类型有四种:,51,自动变量是最常见的一类变量auto 类型说明符 变量名;如: auto int a;auto float pi;说明符“auto”可以省略。按照这种默认的规定,以前所使用的全部变量都是自动变量。,自
22、动变量,52,1.说明自动变量必须在一个函数体的内部。2.函数的形参也是自动变量。 作用域:自动变量的作用域是在所说明的函数内部。实质上是一个函数内部的局部变量。只有在函数被调用时才存在,从函数中返回时即消失,它们的值也仅限于说明它的函数,在其它的函数中不能存取。由于自动变量具有局部性,所以在两个不同的函数中可以分别使用同名的变量而互不影响。,说明:,53,main( ) int x = 1; void f1( ), f2( int );f1( ); f2(x); printf (“x=%dn“, x); void f1 ( ) int x = 3; printf (“x=%dt“, x);
23、void f2 (int x ) printf (“x=%dt“, +x); ,54,寄存器变量与其他类型变量的区别通常的变量,是使用内存中的存贮单元。寄存器变量是使用中央处理器(CPU)的通用寄存器。计算机从寄存器中存取数据的速度要远远快于从内存中存取数据,所以当变量使用非常频繁时,将变量定义为寄存器变量可以提高程序运行速度。寄存器是与机器硬件密切相关的,不同的计算机,寄存器的数目不一样,通常为2到3个,若在一个函数中说明多于2到3个寄存器变量,编译程序会自动地将它们变为自动变量。,寄存器变量,55,由于受硬件寄存器长度的限制,所以寄存器变量只能是char、int或指针型。寄存器说明符只能用
24、于说明函数中的变量和函数中的形式参数,外部变量或静态变量不能是register。寄存器变量的定义形式:register 类型标识符 变量名;,56,main( ) register int x=1,y=2,z; int temp, i;z = x+y;for ( i=0; i=10000; i+)for(temp=0;temp=30000;temp+);printf(“okn“);,57,main ( ) register int temp, i;int x=1, y=2, z;z = x+y;for ( i=0; i=30000; i+)for (temp=0;temp=10000;temp
25、+);printf (“okn“);,58,C语言的外部变量是定义在所有函数之外的全局变量。外部变量可被所有的函数访问,函数之间可通过外部变量传递数据。,外部变量,int x = 0; /* 说明外部变量x */ main ( ) void addone( ), subone( );x = 1; /* 为外部变量x赋值 */printf (“x begins is %dn“, x);addone( ); subone( );printf (“x winds up as %dn“, x); void addone ( ) x +; /* 使用外部变量x */printf (“add 1 to m
26、ake %dn“, x); void subone ( ) x -; /* 使用外部变量x */printf(“substract 1 to make %dn“,x);,60,1.外部变量在编译时由系统分配永久性的存储空间;自动变量则是在函数被调用时才分配临时性的存储空间。2.外部变量如果没有明确的初值,则初值 为0;自动变量没有明确赋初值,则值不定。,外部变量与自动变量的区别:,61,main ( ) int x; /* 自动变量 */printf (”x=%d”, x):int x ; /* 外部变量 */main ( ) printf (”x=%d”, x):自动变量x 的输出不定;外部
27、变量x 的输出为 0 。,62,对于大系统而言,可将一个程序分割为多个文件,通过工程文件,将整个系统连为一个整体。一个函数要在一个文件中。 如果外部变量的说明与使用在同一个文件中,则该文件中的函数在使用外部变量时,不需要再进行说明,可直接使用。 当外部变量的说明与使用在不同的文件,要使用在其它文件中说明的外部变量,就必须在使用该外部变量之前,使用“extern”存储类型说明符进行变量“外部”说明。,在不同的文件中使用外部变量和函数,63,extern仅仅是说明变量是“外部的”,以及它的类型,并不真正分配存储空间。在将若干个文件连接生成一个完整的可运行程序时,系统会将不同文件中使用的同一外部变量
28、连在一起,使用同一个系统分配的存储单元。 当被调用的函数在另一个文件中时,在调用该函数是,无论被调用的函数是什么类型,都必须用extern说明符说明被调用函数是“外部函数”。,/* 文件一 */ int x = 10; /* 说明外部变量x和y */ int y = 10; extern void sub(); void add ( ) int y = 5; /* 说明自动变量y */y = 10 + x; x *= 2; printf(“add:y=%d; “, y); main ( ) x += 5;add( ); sub( ); printf(“main:x=%d; main:y=%dn
29、“,x,y); /* 文件二 */ extern int x; /* 说明另一文件中的外部变量x */ void sub ( void ) int y = 5; /* 说明自动变量y */x -= y; printf(“sub:y=%d; “, y); ,65,静态变量存放在内存中的静态存储区。编译系统为其分配固定的存储空间,重复使用时变量的值会保留。 静态变量的说明是在变量说明前加 staticstatic 类型说明符 变量名; 静态变量有两种:外部静态变量,内部静态变量。 静态变量与外部变量的相同点:具有永久的存储空间;有编译器进行初始化。,静态变量,66,外部静态变量与外部变量的区别:外
30、部静态变量仅仅在定义它的一个文件中有效,而外部变量作用于整个程序。 内部静态变量与外部静态变量的区别:内部静态变量作用于定义它的当前函数。 内部静态变量与自动变量的区别:内部静态变量占用永久性的存储单元,在每次调用的过程中能够保持数据的连续性;自动变量不能。,/* 文件一 */static int x = 2; /* 说明外部静态变量x */int y = 3; /* 说明外部变量y */extern void add2( ); /* 说明外部函数add2 */void add1( );main ( ) add1( ); add2( ); printf(“x=%d; y=%dn“, x, y)
31、; void add1( ) /* 定义函数add1 */ x += 2; y += 3; printf (“in add1 x=%dn“, x); /* 文件二 */static int x=10; /* 说明外部静态变量x */void add2( ) /* 定义函数add2 */ extern int y; x += 10; y += 2;printf (“in add2 x=%dn“, x); ,main ( ) void inc1( ), inc2( );inc1( ); inc1( ); inc1( );inc2( ); inc2( ); inc2( ); void inc1( )
32、 int x = 0; /* 说明自动变量 x 并赋初值 */x+;printf (“in inc1 x=%dn“, x); void inc2( ) static int x=0; /* 说明内部静态变量 x 并初始化 */x+;printf (“in inc2 x=%dn“, x); ,69,输出: in inc1 x=1 in inc1 x=1 in inc1 x=1 in inc2 x=1 in inc2 x=2 in inc2 x=3,70,对于自动变量和寄存器变量:变量的初始化要在运行时,由赋值操作进行。在刚进入一个函数时,函数中说明的自动变量和寄存器变量的值是不定的。对于外部变量
33、和静态变量,变量的初始化工作是由编译系统控制。在编译程序时,系统为外部变量和静态变量分配永久性的存储单元并进行初始化。在运行程序时,外部变量和静态变量的初值是一定的。,变量初始化与赋初值的区别,71,在函数中说明自动变量或寄存器变量时,可使用这样的语句:main ( ) int x=3; register int y=4;.这不是给自动变量或寄存器变量初始化,是在运行时执行赋值操作为自动变量或寄存器变量赋初值。,在函数中说明外部变量或静态变量时,可使用这样的语句:int x1=2, x2;static int y=3;main ( ) static int z=10;这时编译程序会自动为 x1
34、、x2、y 和 z 进行初始化。不需要程序在运行时再做赋值操作了。对于象变量x2在说明时没指定初值的外部变量或静态变量,编译系统自动将初值置为0。这点与自动变量和寄存器变量是根本不同的。,73,性能 自动变量 外部变量 外部静态 内部静态 寄存器变量记忆能力 无 有 有 有 无 多个函数共享 否 可 可 否 否 在不同文件共享性 否 可 否 否 否 未显示赋值的取值 不定 不定 变量初始化 程序控制 编译器 编译器 编译器 程序控制 作用域 当前函数 整个程序 文件 函数 当前函数,变量存储类型的总结,74,递归是一种常用的程序设计技术,在一个程序中,若存在程序自己调用自己的现象就是构成了递归
35、。如果函数funA在执行过程又调用函数funA自己,则称函数funA为直接递归。如果函数funA在执行过程中先调用函数funB,函数funB在执行过程中又调用函数funA,则称函数funA为间接递归。,8.6 函数的递归调用,75,为了保证递归函数的正常执行,对要用递归函数解决的问题必须具备下列条件:要解决的问题能够被减阶要解决的问题有边界在程序中不应出现无终止的递归调用,应使用条件判断来完成递归调用。,76,main ( ) int n, p;printf (“N=?“); scanf (“%d“, ,已知:n阶乘的递归定义为:n! = 1 当 n = 1 时n! = n * (n-1)!
36、当 n 1 时,主函数 第一次调用 第二次 第三次 第四次n=4 p=facto(4)调用 n=4r=4*facto(3)调用 n=3r=3*facto(2)调用 n=2r=2*facto(1) n=1 return(1)返回r=2* 1 =2return(2)返回r=3* 2 =6return(6)返回r=4* 6 =24return(24)返回p=24 打印 24,facto ( n )int n; int r;if (n=1)r = 1;elser=n*facto(n-1);return (r); ,递归返回过程,递归调用过程,78,递归调用的执行过程,facto (int n ) in
37、t s;if ( n = 1 )s = 1;else s = n * facto(n-1); return (s); ,facto ( int n ) int s;if ( n = 1 )s = 1;else s = facto (n-1);s = n*s;return (s); ,等价于,当 n = 1 时 n! = 1 当 n 1 时 n! = n * (n-1)!,79,main( ) int x, n;printf (“ x=? n=? “);scanf(“%d%d“, ,例:采用递归方法计算 x 的 n 次方xn = 1 当 n = 0 时xn = x * xn-1 当 n 0 时,
38、80,C语言本身支持递归调用。变量存储类型(自动变量)的特点,保证了在每层递归调用的过程中,变量可以保持相对于各个层次的独立性,不会发生相互干扰。所有的递归问题一定可以用非递归算法实现。一些问题本身已经蕴涵了递归关系且结构复杂,用非递归算法可能会使程序结构非常复杂,采用递归算法实现,可使程序简洁,提高程序的可读性。递归调用会增加存储空间和执行时间上的开销。,1、下列的结论中只有一个是正确的,它是 。A) 所有的递归程序均可以采用非递归算法实现B) 只有部分递归程序可以用非递归算法实现C) 所有的递归程序均不可以采用非递归算法实现D) 以上三种说法都不对 答案:A 2、在下列结论中只有一个是正确
39、的,它是 。A) 递归函数中的形式参数是自动变量B) 递归函数中的形式参数是外部变量C) 递归函数中的形式参数是静态变量D) 递归函数中的形参可根据需要自己定义存储类 型 答案:A,82,3、在C语言的函数调用过程中,如果函数 funA 调用了函数 funB,函数 funB 又调用了函数 funA,则 。A) 称为函数的直接递归B) 称为函数的间接递归C) 称为函数的递归定义D) C语言中不允许这样的递归形式 答案:B,4、在下列结论中,只有一个是正确的,它是 。A) 在递归函数中使用自动变量要十分小心,因为在递归过程中,不同层次的同名变量在赋值的时候一定会产生相互影响B) 在递归函数中使用自
40、动变量要十分小心,因为在递归过程中,不同层次的同名变量在赋值的时候可能会产生相互影响C) 在递归函数中使用自动变量不必担心,因为在递归过程中,不同层次的同名变量在赋值的时候肯定不会产生相互影响D) 在C语言中无法得出上述三个结论之一 答案:C,84,递归问题的分类 数值性递归问题 非数值性递归问题对于不同类型的问题,可以采用不同的解决方法。 编写递归程序的关键 建立递归模型问题的递归定义是编写递归程序的基础 递归结束条件是保证递归可以正常结束的前提,85,数值型问题的递归求解一般方法:从数学公式入手:推导出问题的递归定义;确定问题的边界条件;再得到问题的递归算法和递归结束条件,86,建立问题的
41、递归定义:f(n) = 1 当 n=1 时f(n) = n + f(n-1) 当 n1 时程序: add (int n ) if (n=1) return (1); /* 递归结束 */else return ( n + add(n-1) ); ,例求自然数 1 到 n 之和,87,建立问题的递归定义:f(n)=1 当 n=1或n=2 时f(n)=f(n-1)+f(n-2) 当 n2 时程序: f (int n ) if (n=1|n=2) return (1); /* 结束条件 */else return ( f(n-1)+f(n-2) ); ,例:求菲波那奇序列,88,1.将非数值问题化简
42、:分析在最简单情况下问题的求解方法。此时,求解的方法一定是非递归的算法,而且十分简单。 2.将一个一般的问题分解为两个(或多个)小问题,且每个分解后的小问题与原来的问题仍然是相似的,具有相同的性质,只是在问题的规模上有所缩小。,非数值型问题的递归求解一般方法,89,3.假设分解后的小问题已经全部可以解决,将每个小问题看作一个整体,描述用小问题解决一般问题的算法。由1可以产生递归结束条件,由3可以推出递归算法。思路类似于“数学归纳法”。,90,问题分析:1. 若整数n为1位数字(0n9):则可以直接输出。2. 将任意一个整数 n (*+) (n=10) 分为两部分: 个位(+) 除个位以外的其余
43、部分(*)3. 将分解后的两部分分别看成整体,则解决原来问题的的算法为: 输出 n 的个位(+) 反序输出 n 的除个位以外的其余部分(*)由 1 推出递归终止条件。由 3 得到递归算法。,例:反序输出整数n(n=0)如:n=12345,输出54321,91,printn ( int n ) if ( 0=n ,例5:输入n值,输出高度为n的等边三角形。例如当 n=4 时的图形如下:* 程序填空main ( ) int i, n;scanf (“%d“, ,93,输出子函数: void prt ( char c, int n ) if ( n0 ) printf (”%c”, c ); /*
44、输出一个字符 */prt (c, n-1) ; /* 递归调用 */ ,94,例:输入n值,输出如下图形(N=5时):1 2 3 4 516 17 18 19 615 24 25 20 714 23 22 21 813 12 11 10 9 分析:根据本题图形的特点,我们可以构造一个递归算法。我们可以将边长为 N 的图形分为两部分:第一部分:最外层的方框,第二部分:中间的边长为 N-2 的图形。,95,建立模型:对于边长为N的正方型,若其中每个元素的行号为i(1iN),列号为j(1jN),第1行第1列元素表示为a1,1(a11=1),则有:对于最外层的框架可以用以下数学模型描述:上边: a1,
45、j=a1,1+j-1 (j1)右边: ai,N=a1,1+N+i-2 (i1)下边: aN,j=a1,1+3N-j-2 (j1)左边: ai,1=a1,1+4N-i-3 (i1),96,对于内层的边长为N-2的图形可以用以下数学模型描述:左上角元素:ai,i=ai-1,i-1+4(N-2i-1) (i1)当:i(N+1)/2且j(N+1)/2时,min=MIN(i,j),则有:a2,2 = fun(a1,1, min, min, n)ai,j=fun(a2,2,i-min+1,j-min+1,n-2*(min-1) )我们可以根据上述原理,分别推导出i和j为其它取值范围时的 min 取值。,9
46、7,main ( ) int a11=1, i, j, n;printf (“Enter n=“);scanf(“%d“, ,98,#define MIN(x,y) (xy) ? (y) : (x) fun ( int a11, int i, int j, int n) int min, a22;if ( i=j ,99,库函数不是C语言的一部分,它是由编译程序根据一般用户的需要编制并提供用户使用的一组程序。 基本概念函数库:函数库是由系统建立的具有一定功能的函数的集合。库中存放函数的名称和对应的目标代码,以及连接过程中所需的重定位信息。用户也可根据需要建立自己的用户函数库。,8.7 库函数简
47、介,100,库函数:存放在函数库中的函数。库函数具有明确的功能、入口调用参数和返回值。连接程序:将编译程序生成的目标码连接起生成可执行文件。头文件:也称为包含文件。C语言库函数与用户程序之间进行信息通信时要使用的数据和变量,在使用某一库函数时,都要在程序中嵌入(用#include)。,101,1.I/O函数 包括各种控制台I/O、缓冲型文件I/O和UNIX式非缓冲型文件I/O操作。需要的包含文件:stdio.h例如:getchar,putchar,printf,scanf,fopen,fclosefgetc,fgets,fprintf,fsacnf,fputc,fputs,fseek,fread,fwrite等。,Tubro C库函数分为九大类:,102,2.字符串、内存和字符函数 包括对字符串进行各种操作和对字符进行操作的函数。需要的包含文件:string.h、mem.h、ctype.h或string.h例如:用于检查字符的函数:isalnum,isalpha, isdigit,islower,isspace等。用于字符串操作函数:strcat,strchr,strcmp,strcpy,strlen,strstr等。,