7.1为什么要用函数
为了更好的实现模块化程序设计 函数(function)也有功能的意思
7.2怎样定义函数
为什么要定义函数?
事先编辑好的功能,编译系统按照定义的功能去执行
定义函数应包括以下内容:
指定函数的名字,以便以后按名调用
指定函数的类型,即函数返回值的类型
指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项
指定函数应该完成什么操作,也就是函数是做什么工作的,即函数的功能
定义函数的方法:
定义无参函数的方法:
函数名()
{
函数体
}
或者
函数名(void)
{
函数体
}
函数体内包含声明部分和语句部分
定义有参函数
函数程序设计
编写一个函数,利用参数传入一个3位数number,找出101~number之间所有满足下列两个条件的数:
它是完全平方数,又有两位数字相同,如144、676等,函数返回找出这样的数据的个数。请同时编写主函数。
例:(括号内为说明)
输入
3 (repeat=3)
150
500
999
输出
count=2
count=6
count=8
#include <stdio.h>
#include <string.h>
int search(int n); //对函数的声明
int main(void)
{
int number,ri,repeat;
scanf("%d",&repeat);
for(ri=1;ri<=repeat;ri++){
do{
scanf("%d",&number);
}while(number<101||number>999);
printf("count=%d\n",search(number));
}
return 0;
}
int search(int n) //对函数的定义
{
int i,j,a1,a2,a3, flag=0;
for (i = 101; i <= n; i++)
{
for (j = 1; j <= i; j++)
{
if (j * j == i)
{
a1 = i % 10;
a2 = i / 10 % 10;
a3 = i /100 % 10;
if ( (a1==a2)||(a1==a3) || (a2==a3) )
{
flag += 1;
break;
}
}
}
}
return flag;
}
定义有参函数的一般形式:
函数名(形式参数表列)
{
函数体
}
7.3调用函数
7.3.1函数调用的形式171
计算函数P(n,x) (函数递归)
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
输入一个整数n (n>=0)和一个双精度浮点数x,输出函数p(n,x)的值(保留2位小数)。
[1 (n=0)
p(n, x) = [x (n=1)
[((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n (n>1)
例:括号内是说明
输入
3 (repeat=3)
0 0.9 (n=0,x=0.9)
1 -9.8 (n=1,x=-9.8)
10 1.7 (n=10,x=1.7)
输出
p(0, 0.90)=1.00
p(1, -9.80)=-9.80
p(10, 1.70)=3.05
#include <stdio.h>
double p(int n, double x); //对函数的声明
int main(void)
{
int repeat, ri;
int n;
double x, result;
scanf("%d", &repeat);
for(ri = 1; ri <= repeat; ri++)
{
scanf("%d%lf", &n, &x);
result = p(n, x);
printf("p(%d, %.2lf)=%.2lf\n", n, x, result);
}
}
double p(int n, double x) //对函数的定义
{
double t;
if(n==0) t=1;
else if(n==1)
t=x;
else
t=((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n;
return t;
}
7.3.2函数调用时的数据传递
形式参数和实际参数
在定义函数时函数名后面的括号中的变量名称为“形式参数”或“虚拟参数”。
在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
实际参数可以是常量,变量或表达式。
实参和形参间的数据传递
在调用函数过程中,系统会把实参的值传递给被调用函数的形参。也可以这么理解,形参从实参得到一个值。该值在函数调用期间有效,可以参加该函数的运算。
7.3.3函数调用的过程
一个函数只能有一个返回值,(想返回多个可以参考后面的指针中的内容)
如果函数不需要返回值,则不需要return语句,这是函数的类型应定义为void类型。
7.4对被调用函数的声明和函数原型
在一个函数中调用另一个函数(即被调用函数)需要具备以下条件:
首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)
如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息“包含”到本文件中去
#include<stdio.h>在stdio.h文件中包含了输入输出库函数的声明
使用数学库中的函数,应使用#include<math.h>
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一文件中),应该在主调函数中被调用的函数作声明(declaration)。声明的作用是把函数名,函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。
思考总结:
可以发现,函数的声明和函数定义中的第1行(函数首部)基本上是相同的。只差一个分号(函数声明比函数定义中的首行多一个分号)
为什么要用函数的首部来作为函数声明呢?
这是为了对函数调用的合法性进行检查。因为在函数的首部包含了检查调用函数是否合法的基本信息(包含了函数名,函数值类型,参数个数,参数类型和参数顺序),在检查函数调用时要求函数名,函数类型,参数个数,参数类型和参数顺序必须和函数声明一致,实参类型必须与函数声明中的形参类型相同,这样就能保证函数的正确调用。
函数的“定义”和‘声明’并不是同一回事
函数的定义是指对函数功能的确立,包括指定函数名,函数值类型,形参及其类型以及函数体等,它是一个完整的,独立的函数单位。
而函数的声明的作用则是把函数名,函数值类型,参数个数,参数类型和参数顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如:函数名,函数值类型,参数个数,参数类型和参数顺序),他不包含函数体。
7.5函数的嵌套调用
C语言的函数定义是互相平行,独立的,也就是说,在定义函数时,一个函数内不能再定义另一个函数,即不能嵌套定义,但可以嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数。
7.6函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。
C语言函数递归调用_外太空程序员的博客-CSDN博客_c语言函数的递归调用
范例:计算5的阶乘,其实就是计算 1*2*3*4*5,
我们并不知道5的阶乘是多少,但是我们知道一点,4的阶乘 * 5他就等于5的阶乘。
我们并不知道4的阶乘是多少,但是我们知道一点,3的阶乘 * 4他就等于4的阶乘。
我们并不知道3的阶乘是多少,但是我们知道一点,2的阶乘 * 3他就等于3的阶乘。
所以递归调用的出口肯定是在1的阶乘,因为1的阶乘是1,可以作为出口,我们能够求出2的阶乘,也就是1*2;以此类推。
#include<stdio.h>
// 函数声明
int dg_jiecheng(int n);
int main() //主函数
{
int result = dg_jiecheng(5);
printf("result=%d\n",result); // 结果为120
}
// 函数定义
int dg_jiecheng(int n)
{
int result; // 局部变量,保存阶乘结果
if(n==1) // 1的阶乘就是1
{
return 1; // 这里就是该递归调用的出口
}
else
{
// 第一次是 result = dg_jiecheng(4)*5,然后进入到了 dg_jiecheng(4),这行代码就被暂存了;
第二次是 result = dg_jiecheng(3)*4,然后进入到了 dg_jiecheng(3),这行代码就被暂存了;
第三次是 result = dg_jiecheng(2)*3,然后进入到了 dg_jiecheng(2),这行代码就被暂存了;
第四次是 result = dg_jiecheng(1)*2,然后进入到了 dg_jiecheng(1),这行代码就被暂存了;
此时,dg_jiecheng(1)的出口条件成立了,终于,能够执行return 1,这可是 return 语句第一次捞着执行。
第一次:return 1,返回的是1,返回到dg_jiecheng(2)这里:
return =1*2 并且也执行return result;,返回1*2=2;
返回到dg_jiecheng(3)这里:
return =2 并且也执行return result;,返回2*3=6;
返回到dg_jiecheng(4)这里:
return =6 并且也执行return result;,返回6*4=24;
返回到dg_jiecheng(5)这里:
return =24 并且也执行return result;,返回24*5=120;
result = dg_jiecheng(n-1)* n;
}
return result;
}
递归的优缺点
优点:
代码少,代码看起来简洁,精妙。
缺点:
虽然代码简洁,精妙,但是不好理解。
如果调用的层次太深,调用栈(内存),可能会溢出,如果真出现这种情况,那么说明不能用递归解决该问题。
效率和性能都不高,这深层次的调用,要保存的东西很多,所以效率和性能肯定高不起来。有些问题用不用递归都行,有些问题可能必须用递归解决。汉诺塔
递归函数的直接和间接调用:
递归函数的直接调用:调用递归函数f的过程中,f函数有调用自己,这就是直接调用。
递归函数的间接调用:调用函数f1的过程中要调用f2函数,然后f2函数又调用f1函数,这就是间接调用。
7.7数组作为函数参数
编程题(指针数组)
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
编写程序,输入一个月份,输出对应的英文名称,要求用指针数组表示12个月的英文名称。
若输入月份错误,输出提示信息。
输入输出示例:括号内为说明
输入:
3 (repeat=3)
5
9
14
输出:
May
September
Wrong input!
#include<stdio.h>
void main()
{
int ri,repeat;
int month;
char *month_name[]={"","January","February","March","April","May","June","July","August","September","October","November","December"};
scanf("%d",&repeat);
for(ri=1;ri<=repeat;ri++){
scanf("%d",&month);
if((month>=1)&&(month<=12))
puts(month_name[month]);
else
printf("Wrong input!");
}
}
编程题 (指针数组,查找相同的字符串)
程序填空,不要改变与输入输出有关的语句。
输入一个正整数repeat (0<repeat<10),做repeat次下列运算:
定义一个指针数组将下表的星期信息组织起来,输入一个字符串,在表中查找,若存在,输出该字符串在表中的序号,否则输出-1。
(表格详见实验教材P99)
输入输出示例:括号内为说明
输入:
3 (repeat=3)
Tuesday
Wednesday
year
输出:
3
4
-1
#include<stdio.h>
#include<string.h>
void main()
{
int i,ri,repeat,dateNum;
char *date[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
char str[80];
scanf("%d",&repeat);
getchar();
for(ri=1;ri<=repeat;ri ++){
scanf("%s",str);
/*---------*/
dateNum=sizeof(date)/sizeof(char *);
for (i = 0; i<dateNum; i++){
if (strcmp(date[i],str) == 0) {
printf("%d\n",i+1); break;
}
}
if (i==dateNum) printf("%d\n",-1);
}
}
计算最长的字符串长度
编写一个函数int max_len(char *s[ ], int n),用于计算有n(n<10)个元素的指针数组s中最长的字符串的长度,并编写主程序验证。
例:(括号内为说明)
输入
4 (n=4)
blue
yellow
red
green
输出
length=6
#include <stdio.h>
#include <string.h>
int max_len(char *s[],int n);
void main()
{
int i,n;
char s[10][80],*p[10];
scanf("%d",&n);
for(i=0;i<n;i++){
scanf("%s",s[i]);
p[i]=s[i];
}
printf("length=%d\n",max_len(p,n));
}
int max_len(char *s[],int n)
{
int max,i;
max=strlen(s[0]);
for(i=1;i<n;i++)
if(max<strlen(s[i]))
max=strlen(s[i]);
return max;
}
静态局部变量的值(static局部变量):
有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次调用该函数时,该变量已有值(就是上一次函数调用结束时的值),这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。
自动变量(auto变量)
函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储去中,函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。
实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”
int b,c=3 ========== auto int b,c=3
第七章课后题
C语言程序设计第五版谭浩强 第七章答案_月已满西楼的博客-CSDN博客_c语言第七章课后题答案
第七章目录回顾:
第7章用函数实现模块化程序设计167
7.1为什么要用函数167
7.2怎样定义函数169
7.2.1为什么要定义函数169
7.2.2定义函数的方法170
7.3调用函数171
7.3.1函数调用的形式171
7.3.2函数调用时的数据传递172
7.3.3函数调用的过程174
7.3.4函数的返回值174
7.4对被调用函数的声明和函数原型176
7.5函数的嵌套调用179
7.6函数的递归调用181
7.7数组作为函数参数189
7.7.1数组元素作函数实参189
7.7.2一维数组名作函数参数191
7.7.3多维数组名作函数参数194
7.8局部变量和全局变量196
7.8.1局部变量196
7.8.2全局变量197
*7.9变量的存储方式和生存期201
7.9.1动态存储方式与静态存储方式201
7.9.2局部变量的存储类别202
7.9.3全局变量的存储类别205
7.9.4存储类别小结209
7.10关于变量的声明和定义211
*7.11内部函数和外部函数212
7.11.1内部函数212
7.11.2外部函数213