第七章 用函数实现模块化程序设计
函数、递推、递归
1、需要输出以下的结果,用函数调用实现:
***********************
How do you do!
***********************
#include<stdio.h>
int main()
{
void print_star(); // 声明函数
void print_message(); // 声明函数
print_star(); // 调用函数
print_message();
print_star(); // 调用函数
return 0;
}
void print_star() // 定义函数
{
printf("***********************\n");
}
void print_message()
{
printf("How do you do !\n");
}
2、输入两个整数,求输出二者中的大者。要求在主函数中输入两个整数,用一个函数max求出其中的大者,并在主函数中输出此值。
#include<stdio.h>
int main()
{
int max(int x,int y);
int a,b,c;
printf("Please enter two numbers:\n");
scanf("%d,%d",&a,&b);
c=max(a,b);
printf("The Max number is:%d\n",c);
}
int max(int x,int y)
{
int z;
if(x>y)
{
z=x;
}else
{
z=y;
}return(z);
}
max函数也可以写成这样
int max(int x,int y)
{
if(x>y) return(x);
else return (y);
}
3、输入4个正整数,找出其中最大的数。用一个函数来实现。
#include<stdio.h>
int main()
{
int max_4(int,int,int,int); // 对比较4个数的函数进行声明
int a,b,c,d,m;
printf("Please enter two numbers:\n");
scanf("%d,%d,%d,%d",&a,&b,&c,&d);
m=max_4(a,b,c,d); // 调用比较4个数的函数
printf("The Max number is:%d\n",m);
}
int max_4(int a,int b,int c,int d) // 定义比较4个的函数
{
int max(int ,int ); // 声明比较2个数的函数
int m;
m=max(a,b); // 调用比较2个数的函数
m=max(m,c); // 第3(c)个值和上一次比较出来的结果再进行比较
m=max(m,d); // 第4(d)个值和上一次比较出来的结果再进行比较
return m;
}
int max(int x,int y) // 定义比较2个的函数
{
if(x>y) return x;
else
return y;
}
4、有一个学生坐在一起,问第5个学生多少岁?他说比第4个学生大2岁。问第4个学生岁数,他说比第3个学生大2岁。问第3个学生岁数,他说比第2个学生大2岁。.最后问第1个学生,他说是10岁。请问第5个学生多大。(递归思想)
关系式 :age(5)=age(4)+2;
age(4)=age(3)+2;
age(3)=age(2)+2;
age(2)=age(1)+2;
递归出口 :age(1);
数学公式 :age(n) = 10 (n=1)
= age(n-1)+2 (n>1)
#include<stdio.h>
int age(int n)
{
if(n==1)
{
return 10;
}
else
{
return age(n-1)+2;
}
}
int main()
{
printf("%d\n",age(5));
}
5、分别用递推、递归方法实现n!
(1)递推方法
#include<stdio.h>
int main()
{
int n;
int f(int n);
printf("Please input a integer number: ");
scanf("%d",&n); // 用户输入n
long sum=0;
sum=f(n); // sum用来存储n!值
printf("%d!=%ld\n",n,sum);
return 0;
}
int f(int n)
{ int i;
long sum=1;
for(i=1;i<=n;i++) // 必须从1开始,for循环的长度为阶乘数 n
{
sum=sum*i; // 思想是 前几项的积再乘以后面的项 如:1*2=2*3
}
return sum;
}
(2)递归方法
递归公式 :n! = 1 (n=1)
= n.(n-1) ! (n>1)
#include<stdio.h>
int main()
{
int n;
int f(int n);
printf("Please input a integer number: ");
scanf("%d",&n); // 用户输入n
long sum=0; // 长类型接受超过6位的数值
sum=f(n); // sum用来存储n!值
printf("%d!=%ld\n",n,sum);
return 0;
}
int f(int n)
{
if(n==1)
return 1;
else
return f(n-1)*n; // 思路是 n是一个固定的值,分别往回乘,n-1又继续调用自己,每次减n每次1,直到n=1
}
递归注意点: (1)递归必须有递归关系式
(2)递归必有递归出口
递归两个过程:(1)回溯;
(2)递推。
什么样的问题可以用递归呢?
(1)问题是否能转化为同一种方法解决的子问题;
(2)子问题的规模比原问题的规模要小;
(3)必有递归结束条件。
数组元素、数组名作为函数参数
1、用数组元素作为函数实参
1)假设A、B队各有10个人,计算A队高于、低于、等于B队成绩的人数
#include<stdio.h>
int main()
{
int higher(int,int);
int a[10]={71,81,86,75,80,56,50,64,65,90};
int b[10]={70,80,85,75,85,56,50,60,60,80};
int i,j,g=0,d=0,e=0;
printf("A B\n");
for(i=0;i<10;i++)
{
if(higher(a[i],b[i])==1)
{
g++;
}
else if(higher(a[i],b[i])==-1)
{
d++;
}
else
{
e++;
}
printf("%d\t",a[i]);
printf("%d\t",b[i]);
printf("\n");
}
printf("\n");
printf("A组有 %d 学生的成绩高于B组\nA组有 %d 学生的成绩低于B组\nA组有 %d 学生的成绩等于B组\n",g,d,e);
if(g>=6)
{
printf("\nA Team win!\n\n");
}
return 0;
}
higher(int x,int y)
{
int flag;
if(x>y)
flag=1;
else if(x<y)
flag=-1;
else
flag=0;
return flag;
}
2、用数组名作为函数实参
1)有10个学生成绩,用一个函数求全体学生的平均成绩
#include<stdio.h> int main() { float average(float array[]); float score[10],aver; int i; printf("input 10 scores:\n"); for(i=0;i<10;i++) { scanf("%f",&score[i]); } aver=average(score); // 用数组名调用函数 printf("average score is %5.2f\n",aver); return 0; } float average(float array[]) { int i; float aver,sum=array[0]; for(i=1;i<10;i++) // 遍历数组元素 { sum=sum+array[i]; // 每次循环累加数组的元素 } aver=sum/10; // 最后将总累计数除10 return (aver); // 将aver带回主函数 }
注意几点: (1)数组名作为函数参数,在调用函数时并不开辟存放形参数组的空间;
(2)数组名代表的数组的首元素的地址;
(3)上例子中,形参数组首元素array[0]和实参数组首元素score[0]具有同一地址,共占同一存储空间;
(4)定义函数时数组大小的不做检查,即可以不定义长度。
2)有两个班,学生数不同,编写一函数,用来分别求各班的平均成绩。
#include<stdio.h> int main() { float average(float array[],int ); float score_1[5]={80,80,70,70,70}; // 有5个学生的成绩 float score_2[10]={80,80,70,70,70,90,80,80,80,70}; // 有10个学生的成绩 printf("The average of score_1 is %5.2f\n",average(score_1,5)); printf("The average of score_2 is %5.2f\n",average(score_2,10)); return 0; } float average(float array[],int n) // n来传递数组的长度 { int i; float aver,sum=array[0]; // 累加是从第一个元素开始的 for(i=1;i<n;i++) // 遍历数组元素 { sum=sum+array[i]; // 每次循环累加数组的元素 } aver=sum/n; // 最后将总累计数除n return (aver); // 将aver带回主函数 }
3、用一个函数实现用 选择法 对10个整数按升序排列
#include<stdio.h>
int main()
{
void sort(int array[],int n); // 对sort函数声明 因为只是排序不需要返回值
int a[10]={13,43,21,45,32,16,47,12,48,65};
int i,j;
printf("The sorted array:\n");
sort(a,10);
for(i=0;i<10;i++)
{
printf("%d\n",a[i]);
}
return 0;
}
void sort(int array[],int n)
{
int i,j,k,t; // t存放临时
for(i=0;i<n-1;i++) // n-1是因为有10元素,所以比较9次
{
k=i; // k用来存放当前“最小”的元素的序号
for(j=i+1;j<n;j++) // i+1是表示后一项,每次都会后移一位
{
if(array[j]<array[k]) // 如果前一项k比后一项j大,即后面项的比前面的项要小
{
k=j; // 那么把小项(即后面)的下标j存储在k中
}
}
t=array[k];array[k]=array[i];array[i]=t; // t存储下标k的值(即小的值),k的位置(后面的位置)用比较大的来代替,i的位置(即前面的位置)存储小值t
}
}
注意点: (1)数组名作为函数参数时传递的是首地址,即地址传递方式;
(2)按“值”传递方式需要开辟存储单元,而按“地址”传递方式需要开辟内存单元,即形参和实参共占同一段内存单元,地址是共享的;
(3)由于地址共享,可以通过改变形参数组的值来改变实参数组的值。
4、有4个学生,5门课的成绩,设计一个函数,用来求出其中最高的成绩
#include <stdio.h> int main() { float highest_score(float array[][5]); // 一维大小可以省略 float score[4][5]={{61,65,76,77,89},{81,70,80,78,60},{81,75,88,78,66},{82,71,85,79,66}}; printf("The highest score is %5.2f\n",highest_score(score)); return 0; } float highest_score(float array[4][5]) { int i,j; float max; max=array[0][0]; for(i=0;i<4;i++) { for(j=0;j<5;j++) { if(array[i][j]>max) { max=array[i][j]; } } } return max; } 变量的的作用域和生存期 1、局部变量和全局变量 1)局部变量 ① 在一个函数内部生效,如主函数中的 int i,j,k; ② 不同函数的变量名是可以相同的,互不干扰,互不影响; ③ 形参也是局部变量; ④ 复合语句中,可以定义变量,只在本复合语句中生效; 2)全局变量 ① 函数之外定义的变量,称为“全局变量”; ② 作用范围是本源文件开始到结束; ③ 如果同一个源文件中,全局变量和局部变量同名,局部变量优先有效。 例1:有4个学生,5门课的成绩,输出其最高成绩以及属于第几个学生、第几门课程; #include <stdio.h> int Row,Column; // 定义全局变量Row和Column int main() { float highest_score(float array[4][5]); // 一维大小可以省略 float score[4][5]={{61,65,76,77,89},{81,70,80,78,90},{81,75,88,78,66},{82,71,85,79,66}}; printf("The highest score is %5.2f\nRow:%d\nColumn:%d\n",highest_score(score),Row,Column); return 0; } float highest_score(float array[4][5]) { int i,j; float max; max=array[0][0]; for(i=0;i<4;i++) { for(j=0;j<5;j++) { if(array[i][j]>max) { max=array[i][j]; Row=i; Column=j; } } } return max; }
注意几点: 1)一个函数可以改变一个全局变量,该文件的其他函数也可以使用这个以改变的变量;
2)全局变量相当于是函数之间的联系通道;
3)全局变量一般用首个字母大写来定义;
4)全局变量整个过程都占用存储单元。
2、变量的存储方式和生存期
变量两个属性:作用域、生存期
变量存储方式:静态存储方式、动态存储方式。静态存储方式由静态存储区分配存储空间,动态存储方式由动态存储区分配存储空间。
区别: 静态存储方式,执行过程中占据固定存储单元,执行完释放,例如:全局变量;
函数中定义的变量是分配动态存储空间的,执行完释放,如:局部变量。
变量和函数两个属性:数据类型、数据存储类别:
① auto——自动变量
函数调用时、复合语句中都是此类别,执行完自动释放空间,int和绝大多数都是默认是“自动存储类别”。
② static——静态变量
函数调用后,不释放存储单元,继续保持原值,下次调用函数,改值还是上次结束后的值。
#include <stdio.h> int main() { int fac(int n); int i; for(i=1;i<=5;i++) // 先后调用5次fac函数 { printf("%d!=%d\n",i,fac(i)); // 输出每次i!的值 } return 0; } int fac(int n) { static int f=1; // 保留上次执行结束后的值 f=f*n; // 在上面基础上继续乘n return f; // 返回的是n!的值 }
静态局部变量注意点: 1)静态局部变量只赋一次初值,再次执行函数的时候,初值是上次执行函数结束的值。自动变量每次执行都重新赋初值。
2)静态局部变量不赋初值,编译时自动赋初值“0”(对数值型变量)或者空字符(对字符变量);
3)静态局部变量,不能给其他函数引用;
静态外部变量:定义外部变量前加一个 static 声明,只作用于本文件中,不能给其他文件引用。
③ register——寄存器变量
相对于不太频繁,但是又要经常用到的数据,可以使用寄存器变量,来提高执行效率。现在多数系统都自动将这些变量存放在寄存器中。
形式:register int f ;
④ extern——外部变量
外部变量的作用是当前文件中的变量的作用域扩展到其他文件中,例如:在A文件中定义了NUM变量,那B文件中要使用A文件中的NUM就要在B文件的头部 加上extern NUM,这样才使扩展了NUM可以在B中的使用。
小结:对数据的定义有个属性分别是 数据类型 和 存储类别

内部函数和外部函数
1、内部函数
定义:只能被本文件中的其他函数所调用的函数,称为内部函数,也称为静态函数,形式如下:
satatic 类型标识符 函数名(形参表);
static int fun(int a,int b);
2、外部函数
定义:函数首部的最左端加关键字extern,此类函数可以被其他文件调用,如果定义时没写extern,则隐含为外部函数:
extern int fun(int a,int b);
例 1:有一个字符串,内有若干个字符,现输入一个字符,如果字符串中包含此字符,则把它删除。用外部函数实现。
// file1.c (文件1) #include <stdio.h> int main() { // 声明需要调用其他文件的函数 extern void enter_string(char str[]); extern void delete_string(char str[],char ch); extern void print_string(char str[]); char c; char str[80]; printf("Please enter a string\n"); enter_string(str); // 调用输入函数,用户输入一个字符串 printf("Please enter a char which you want to delete:\n"); scanf("%c",&c); // 用户输入一个需要删除的字符 delete_string(str,c); // 调用删除函数,给delet函数传入两个实参 print_string(str); // 调用打印函数,输入删除后的字符串 return 0; } // file2.c(文件2) #include <stdio.h> void enter_string(char str[80]) { gets(str); // 向字符数组输入字符串 } // file3.c(文件3) #include <stdio.h> void delete_string(char str[],char ch) { int i,j; for(i=j=0;str[i]!='\0';i++) { if(str[i]!=ch) // 如果遍历的字符不等于需要删除的字符 { str[j++]=str[i]; // 那么 } } str[j]='\0'; } // file4.c(文件4) #include <stdio.h> void print_string(char str[]) { printf("After delete string:\n"); printf("%s\n",str); }
分析:从str[0]开始逐个检查数组元素是否与指定要删除的字符相同。若不同,就留在数组中;若相同就不保留。可以看到,str[0]不等于要删除的字符,应保留,所以使str[0]赋给str[0],即保留原值,同理,把str[1]赋给str[1],str[2]赋给str[2],str[3] 赋给str[3],str[4]不保留,把str[5]赋给str[4]......即用原来的str[5]的值取代了str[4]的原值。用变量i作为循环变量,检查str数组中各个元素,用 j 来累计未被删除的字符。当然可以设两个数组,把不删除的字符一 一赋给新数组。但本程序只用一个数组,只把不被删除的字符保留在原数组中。由于 i 总是大于或等于 j,因此最后保留下来的字符不会覆盖未被检测处理的字符。最后将 结束符 \0 复制到被保留的字符后面。
提高部分
1、递归的典型例子——Hanoi 汉诺塔问题
1)课本代码实现:
#include <stdio.h>
int main()
{
void hanoi(int n,char x,char y,char z); // 对函数hanoi声明
int m;
printf("Please input the number of dishes:");
scanf("%d",&m); // 输入需要移动盘的数量
printf("The step to moving %d dishes:\n",m);
hanoi(m,'A','B','C'); // 执行移动盘子,ABC为三条柱子
return 0;
}
void hanoi(int n,char x,char y,char z)
{
void move(char a,char b); // 定义move函数
if(n==1)
{
move(x,z);
}else
{
hanoi(n-1,x,z,y); // 递归调用下一个和尚移动的n-1盘子
move(x,z); // 自己移动一个盘子
hanoi(n-1,y,x,z); // 递归调用下一个和尚移动的n-1盘子
}
}
void move(char a,char b)
{
printf("%c--->%c\n",a,b);
}
2)简化实现:
#include <stdio.h>
void hanoi(int n,char A,char B,char C)
{
if(n==1)
{
printf("%c——>%c\n",A,C);
}else
{
hanoi(n-1,A,C,B);
printf("%c——>%c\n",A,C); // A、C的值是不停更新的
hanoi(n-1,B,A,C);
}
}
int main()
{
hanoi(3,'A','B','C');
return 0;
}

浙公网安备 33010602011771号