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.实现打印年历功能。