data/method/一些思考/工作材料/06—函数.txt

866 lines
35 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

函数与模块化编程
一、函数的概念:
1、我们为什么要使用函数
在之前的学习过程中我们已经可以编写一些中小型的程序了不过对于一些大型程序而言还是力不从心。因为程序的功能比较多规模比较大如果把所有的代码都写在main()函数内不仅会使main()函数变得十分复杂,而且代码的调试、维护、重构等工作会变得十分困难。此外,若需要反复实现某项功能,则需要多次重复编写一段代码,这样会使得程序冗长,不够精炼。
因此我们引入“函数”的概念。使用函数后,我们可以:
1避免使主函数main()变得庞杂、逻辑混乱。
2避免程序冗长、不精练
3如果需要多次实现某功能不使用函数的话则需要重复编写某段代码工作量大。
……
2、函数的概念
函数function的概念是从数学上引入的在计算机学里指一段在一起的用来执行某功能的代码。针对不同的功能我们编写不同的函数这就是模块化程序设计的思路。在设计较大程序的时候往往把它分割成若干个小模块每个模块包含一个或多个函数每个函数实现一个特定功能。
在JAVA中函数被称之为“方法”本质类似。
除了自己实现的函数C语言还提供各种已经实现好的库函数来方便程序开发者使用如之前用过的printf()、scanf()、strcpy()、strcat()、strlen()等
注意:使用系统库函数需要包括相应的头文件
在程序设计过程中要善于利用函数,不仅能够减少重复书写代码的工作量,也更利于实现模块化的程序设计。
示例:使用函数,打印以下结果:
********************
How do you do!
********************
#include<stdio.h>
int main()
{
void print_star(void);//函数声明
void print_message(void);//函数声明
void print(void);//函数声明
print();//函数调用
return 0;
}
void print()//函数名
{//函数体
print_star();//函数调用
print_message();//函数调用
print_star();//函数调用
}
void print_star()//函数名
{
printf("********************\n");//函数体
}
void print_message()//函数名
{
printf("how do you do!\n");//函数体
}
调用关系:
main()----->print()----->print_star()和print_message()
3、模块化编程
引入了函数的概念之后人们自然就提出了“模块化编程”的编程思想。简单来说就是通过编写各种函数事先实现各种不同的功能例如sin()表示求正弦函数abs()表示求一个数的绝对值……我们把事先编写好的各种函数放置在函数库中,需要的时候调出相应功能的函数,这样就可以实现功能。
这种编程的思想就是“模块化编程”。我们在程序开发过程中若程序规模较大则我们可以把程序分隔成若干模块每个模块包含一个或多个功能函数每个函数实现一个特定的功能。一个C语言程序由一个主函数main()和若干个子函数构成。主函数调用子函数,函数和函数间也可互相调用,甚至函数自己可以调用自己。
使用模块化编程不仅让程序脉络清晰、功能划分详细,而且还大大减少了重复代码的书写量,而且也更加方便程序的设计与编写。
二、函数的使用:
与变量相同,函数必须要先定义再使用
1、函数定义
定义一个函数需要包括以下几个内容:
1指定函数的名称以便后续调用
2指定函数类型即函数返回值的类型
3指定函数的参数的名字和类型以便在调用函数时进行数据传递
4指定函数应当完成什么操作即函数的功能实现。
5可选对定义的函数添加适当的注释进行说明
函数的一般形式:
类型名 函数名(参数列表)
{
函数体
……
}
例如上文的print_star()函数就是一个函数定义,只不过没有参数
例如以下定义的max函数是用来求出2个数中更大的数
int max(int x,int y)
{
int z;
z=x>y?x:y;
return z;//返回值
}
这个函数就是有参数的函数。
2、函数定义的几个关键要素详解
1函数名函数的标识。函数名是其他函数用来调用这个函数的标识函数名应符合C语言的标识符命名规范且不得重名不得与系统函数如printf()等库函数或main()函数重名。命名应做到“见文知意”而避免起简单的函数名如aa()等不好的命名)
函数名命名通常有3种命名规则
⒈大驼峰法函数名的所有单词的首字母大写。如SetStudentName()。这种是Windows的命名规则。
⒉下划线法函数名的所有单词的首字母都不大写单词与单词之间使用下划线_来分隔开。如set_student_name()。这种是Linux的命名规则。
⒊小驼峰法函数名的所有单词除首个单词首字母不大写外其他单词首字母都大写。如setStudentName()。这种是苹果IOS的命名规则。
初学者在命名函数的时候应遵循其中一个命名规则,而不要自主随意命名。
2类型名函数的类型即函数的返回值。函数的返回值可理解为函数的计算结果。函数可以没有返回值但如果有返回值返回值类型需要与函数类型匹配。例如上文的max()函数函数类型是int类型表示这个函数的计算结果是int类型。因此函数的返回值return z;这句也是int类型。若该函数不需要返回值如上文print_star()函数则应将函数类型定义为void空类型
3参数列表指函数需要的用于计算的数据。参数可以有0个或多个。参数有形参和实参两种有关形参和实参的讲解见下面章节。
4函数返回值返回值必须与函数函数类型匹配。对于void型函数可以没有返回值或直接写return;
5函数体函数的执行代码逻辑。
注意:若某函数定义在调用它的函数后,需要在调用函数中添加这个函数的声明。
示例先写一个max()函数求出2个数中的最大值再在main()函数中调用它
#include<stdio.h>
int max(int x,int y)
{
int z;
z=x>y?x:y;
return z;
}
int main()
{
int a,b,c;
printf("请输入2个待比较的数");
scanf("%d%d",&a,&b);
c=max(a,b);//调用max函数传递两个参数a,b进行运算返回值给c
printf("最大数是%d\n",c);
return 0;//main()函数也需要返回值因为main()函数的类型是int所以返回一个整数。返回0表示程序正常结束
}
练习1编写一个函数求一个数x的n次方。其中x和n作为函数的参数传递进去。之后写main()函数测试
答案:
#include<stdio.h>
int n_ci_fang(int x,int n)
{
int i,sum=1;
for(i=0;i<n;i++)
{
sum *= x;
}
return sum;
}
int main()
{
int zhishu,dishu,ji;
printf("请输入底数:");
scanf("%d",dishu);
printf("请输入指数:");
scanf("%d",zhishu);
ji=n_ci_fang(dishu,zhishu);
printf("积是%d\n",ji);
return 0;
}
练习2编写一个函数求3个数中的最大值。之后写main()函数测试
答案:
#include<stdio.h>
int max(int a,int b,int c)
//始终让a保存3个数中的最大值然后返回a
{
if(a<b)
{
a=b;
}
if(a<c)
{
a=c;
}
return a;
}
int main()
{
int x1,x2,x3;
printf("请输入3个数\n");
scanf("%d%d%d",&x1,&x2,&x3);
printf("3个数中的最大值是%d\n",max(x1,x2,x3));
return 0;
}
3、函数的调用
定义函数并不是最终目的,调用函数并使其生效才是我们需要的。因此,熟练掌握调用函数是十分必要的。
调用函数的一般形式是:
函数名(实参列表)
例如上文中使用的ji=n_ci_fang(dishu,zhishu)、max(x1,x2,x3)等都属于函数调用。
注意:函数调用时可能没有实参列表(具体实参情况需要依据形参决定),但是绝对不可以省略括号。
在函数调用过程中我们把调用函数的函数称为“主调函数”相应地被主调函数调用访问的函数称为“被调函数”。例如练习1主调函数是main()被调函数是n_ci_fang()练习2主调函数是main()函数中的printf()被调函数是max()。
在函数调用过程中有些被调函数需要携带返回值给主调函数这种情况下就需要主调函数使用一个变量来接收函数返回值。例如在练习1中存在函数调用ji=n_ci_fang(dishu,zhishu)此时主调函数使用变量ji去接收被调函数得到的返回值。当然不是所有的函数调用都需要主调函数去接收返回值某些本身无返回值的函数就不需要主调函数去接收返回值。例如在示例程序中的print_star()与print_message()函数都是void类型就无需接收返回值。甚至在某些情况下有返回值的函数也可以不接收返回值。例如大家都十分熟悉的scanf()/printf()函数该函数实际是有返回值返回值类型为int但基本不需要接收该函数的返回值。
思考scanf()/printf()函数的返回值是什么意义?
4、函数调用时的数据传递
1形式参数与实际参数
在调用函数的时候,主调函数和被调函数之间存在数据传递关系。被调函数在参数列表中定义的参数称为“形式参数”(简称形参),在主调函数调用被调函数的时候真正传递的数据称为“实际参数”(简称实参)。实参可以是常量、变量或表达式等,但要求必须有确定的值。
在函数调用的过程中,系统会把实参的值传递给被调函数的形参,或者说,形参从实参那里获得了一个值。之后被调函数才进行运算。
注意:
⒈实参可以是常量、变量或表达式等,但必须有确定的值。在调用时将实参的值赋值给形参。
⒉实参与形参的数量必须相同,类型必须相同或兼容。
在定义函数时出现的形参,并不占用存储单元。在调用过程中,形参会被临时开辟内存单元,实参将值传递给形参,形参拿到值开始函数的运算。在函数调用结束后,形参单元会被释放。
2数组名作为函数的参数
若被调函数的形参是一个数组,则需注意的是,主调函数在传递数组的时候,实参是该数组的首地址。(在学习指针之后我们就可以知道,实际上编译器是将该形参当做指针变量处理)
示例有一个一维数组score数组内存储的是学生的成绩。编写一个函数求出平均成绩
#include<stdio.h>
float avgrage(float array[])
{
int i;
float aver,sum=0;
for(i=0;i<10;i++)
{
sum+=array[i];
}
aver=sum/10;
return aver;
}
int main()
{
float score[10],avg;
int i;
for(i=0;i<10;i++)
{
scanf("%f",&score[i]);
}
avg=avgrage(score);
printf("average score is %.2f\n",avg);
return 0;
}
注意:
⒈实参与形参的类型要一致。
⒉在被调函数内试图通过任何方式求出或声明形参内数组的大小都是不起任何作用的因为C语言编译器对数组类型的形参当做指针去处理不会检查形参的数组是否有越界。在调用时主调函数的实参将首地址传递给被调函数的形参数组名。例如如果我们将示例程序写成
#include<stdio.h>
float avgrage(float array[])
{
int i;
int n = sizeof(array)/sizeof(array[0]);//试图在被调函数内求出数组元素个数
float aver,sum=0;
for(i=0;i<n;i++)
{
sum+=array[i];
}
aver=sum/10;
return aver;
}
可以发现并没有得到正确的结果。
进一步深究发现是被调函数内的循环无法运行。这是因为数组的形参array本质上是指针。在计算n=sizeof(array)/sizeof(array[0])的时候分子分母都是4则n的结果是1循环根本无法运行。
因此,在将数组作为形参时,常见的情况是主调函数将数组的长度通过传参的形式传递给被调函数,或者可以使用全局变量数组。例如,将程序写成如下方式就可以得到正确结果:
#include<stdio.h>
float avgrage(float array[],int n)
{
int i;
float aver,sum=0;
for(i=0;i<n;i++)
{
sum+=array[i];
}
aver=sum/10;
return aver;
}
int main()
{
float score[10],avg;
int i;
for(i=0;i<10;i++)
{
scanf("%f",&score[i]);
}
avg=avgrage(score,sizeof(score)/sizeof(score[0]));
printf("average score is %.2f\n",avg);
return 0;
}
⒊如果形参是多维数组以二维数组为例在定义函数时可以省略第一个下标绝对不可以省略第二个下标。例如若我们想编写一个函数求出一个3*4的二维数组内最大值则可以写成
int max_value(int array[3][4])
int max_value(int array[][4])
但是绝对不可以写成:
int max_value(int array[][])//省略了第二个下标,错误
这点要格外注意。
练习:编写函数,实现对一个数组的冒泡排序
答案:
#include<stdio.h>
void bubble_sort(int array[],int n)
{
int i,j;
for(i=0;i<n;i++)
{
for(j=0;j<n-i-1;j++)
{
if(array[j]>array[j+1])
{
array[j]^=array[j+1];
array[j+1]^=array[j];
array[j]^=array[j+1];
}
}
}
}
int main()
{
int a[10],i;
for(i=0;i<10;i++)
{
scanf("%d",&a[i]);
}
bubble_sort(a,sizeof(a)/sizeof(a[0]));
for(i=0;i<10;i++)
{
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
3局部变量与全局变量
在C语言程序中定义一个变量有以下3种情况
⒈在函数的开头定义
⒉在函数的复合语句中定义如for()循环中临时定义的int i等
⒊在函数的外部定义
在函数内部定义的变量只在本函数范围内有效;在函数的复合语句中定义则只在这个复合语句中有效。以上的两种情况变量都只在一定范围内有效,因此这样的变量叫“局部变量”。
例如在函数fun1()中定义两个变量a,b在函数fun2()中定义两个变量a,c。则fun1()之中的a和fun2()之中的a不是一个变量即使同名因为这两个a变量只在自己的函数作用范围内有效。
注意:
⒈在main()函数之中定义的变量也只在main()函数之中有效,而并非对整个程序有效。
⒉不同函数间的局部变量可以同名,但即使同名也不是同一个变量,这点要特别注意。
⒊参数列表中的形参也是局部变量。
如果有一个变量可以被本文件中所有的函数共用,则这个变量叫“全局变量”(或外部变量)。全局变量的作用域是从定义变量的位置开始直到本文件结束。
定义全局变量的方法:在所有函数的外部定义变量即可。
示例:
int p=1,q=5;
float f1(int a)
{
int b,c;
}
char c1,c2;
char f2(int x,int y)
{
int i,j;
}
int main()
{
int m,n;
return 0;
}
分析:
函数f1()可用的变量p,q,a,b,c
函数f2()可用的变量p,q,c1,c2,x,y,i,j
函数main()可用的变量p,q,c1,c2,m,n
使用全局变量的作用是增加了函数间数据联系的渠道。若有好几个函数间需要数据传递,则可建立一个全局变量,这样几个函数都可以访问这个全局变量。
练习在一维数组中存储10个学生成绩编写2个函数一个计算总分和平均值并输出一个输出所有学生的成绩并求最大最小值
答案:
#include<stdio.h>
int score[10]={89,95,87,100,67,97,58,84,73,90};
int sum=0,max=-1,min=101;
float avg=0;
void average(int array[],int n)//注意数组作为形参的写法
{
int i;
for(i=0;i<n;i++)
{
sum += array[i];
}
avg = (float)sum/n;
printf("总分是%d\n平均分是%f\n",sum,avg);
}
void print_max_min(int array[],int n)
{
int i;
for(i=0;i<10;i++)
{
printf("第%d个学生的成绩是%d\n",i+1,array[i]);
if(array[i]>max)
max=array[i];
if(array[i]<min)
min=array[i];
}
printf("最大值是%d\n最小值是%d\n",max,min);
}
int main()
{
print_max_min(score,10);//注意数组作为实参的写法
average(score,10);
return 0;
}
思考:如果全局变量和局部变量同名会出现什么情况?
示例:
#include<stdio.h>
int a=3,b=5;
int max(int a,int b)
{
int c;
c=a>b?a:b;
return c;
}
int main()
{
int result1 = max(a,b);
printf("结果1是%d\n",result1);
int a=8,result2;
result2=max(a,b);
printf("结果2是%d\n",result2);
return 0;
}
答案输出“结果1是5结果2是8”
在这里我们故意设置了局部变量与全局变量同名。我们会发现,当局部变量与全局变量同名时,在局部变量的作用范围内,全局变量会被“屏蔽”,即只有局部变量在起作用。
4值传递与地址传递
一般情况下被调函数靠返回值将函数的计算结果返回给主调函数。但是return语句只能执行1次。若有其他语句写在return语句之后则在执行return语句之后其他的语句都不会被执行了。
若我们的函数可能得到不止一个结果则在这种情况下无法通过设置多个return的方式同时获得好几个返回值。这时候我们必须采用别的方式将被调函数的数据传回主调函数。
请看以下示例。在示例中我们编写一个swap函数希望将2个数交换。
示例1
void swap(int x,int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
int main()
{
int a=5,b=3;
swap(a,b);
printf("a是%d\nb是%d\n",a,b);
return 0;
}
输出结果a是5b是3
我们发现调用swap函数并没有使变量a和b的值发生改变。是我们的swap函数逻辑错误吗我们修改示例1在swap函数的最后添加一行测试输出
示例2
void swap(int x,int y)
{
int temp;
temp = x;
x = y;
y = temp;
printf("x是%d\ny是%d\n",x,y);//添加测试输出
}
int main()
{
int a=5,b=3;
swap(a,b);
printf("a是%d\nb是%d\n",a,b);
return 0;
}
输出结果:
x是3y是5
a是5b是3
可以看到我们的swap函数的逻辑并没有问题在swap函数内变量的值确实发生了交换。
思考:为什么会出现这样的情况?
答案因为在swap函数中变量x和y是swap函数的局部变量。在进行计算的时候main函数的变量a和b分别将自己的值传递给变量x和yx和y在代替a和b进行计算。计算完毕后即函数执行完毕后函数被系统回收相应地变量x和y也被系统回收而没有把真正的把计算结果返回给main函数中的a和b。
这种将需运算的变量的值传递给函数形参的方式称之为“值传递”。
如果我们需要正确地完成swap函数的功能我们就不能使用值传递而是需要使用另一种参数传递方法地址传递。
地址传递:将需运算的变量的地址传递给函数形参的方式称之为“地址传递”。
使用地址传递完成swap函数如下
示例3
//使用地址传递来完成swap函数的交换功能
void swap(int *x,int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a=5,b=3;
swap(&a,&b);//注意这里需要取地址运算符&
printf("a是%d\nb是%d\n",a,b);
return 0;
}
/************
运算符*为指针运算符,表示访问该指针指向的内容
运算符&为取地址运算符,表示计算该变量所在的内存地址
*************/
输出结果a是3b是5
可以发现使用地址传递成功地实现了swap函数的功能。
关于地址传递需要注意以下几点:
⒈地址也是一种特殊的值,因此地址传递可看成特殊情况下的值传递。只不过地址传递的方式传递的是地址(而不是简单的变量的值)因此将值传递与地址传递两种方式人为区分。
⒉通常情况下,我们可以这样区分值传递与地址传递:若主调函数传参是普通变量的值,被调函数的参数列表也是普通变量值,则可认为使用的是值传递;若主调函数传参是变量的地址,而被调函数的参数列表是地址,则可认为使用的是地址传递。
⒊使用地址传递时,被调函数内对形参的操作采取的是间接寻址的方法,即通过这个地址找到变量的实际值,进而进行操作。(通过堆栈区存放的地址访问主调函数的实参变量)
主调函数传递给被调函数数据,如果想要保护数据(即不希望被调函数修改主调函数的值),则我们应使用值传递;如果主调函数希望被调函数修改自己的变量值,则应使用地址传递。
地址传递的作用是若一个子函数需要求出多个返回值的时候这时候无法使用多个return语句这时我们可以使用地址传递的方式将多个返回值传递回主调函数。
练习数组中存放10个学生的成绩。编写一个函数求出10个学生的成绩的总和和平均值返回成绩低于平均值的学生数量。
提示:总和和平均值使用地址传递,低于学生平均值的学生数量使用函数返回值。
答案:
#include<stdio.h>
int fun(int a[],int n,int *sum,float *avg)
{
int i,count=0;
*sum = 0;
*avg = 0;
for(i=0;i<n;i++)
{
*sum += a[i];
}
*avg = (float)*sum/n;
for(i=0;i<n;i++)
{
if(a[i]<*avg)
{
count++;
}
}
return count;
}
int main()
{
int score[10]={80,60,72,90,98,51,88,84,67,75};
int sum;
float avg;
int count;
count = fun(score,10,&sum,&avg);
printf("总成绩是%d\n平均成绩是%f\n低于平均分的学生有%d人\n",sum,avg,count);
return 0;
}
5、函数的递归调用
C语言的特点之一是允许函数递归调用。
递归调用:在调用函数的过程中又出现直接或间接地调用自身,称为函数的递归调用。
示例1
int f(int x)
{
int y,z;
z = f(y);//在f()函数中又调用了f()函数
return 2*z;
}
在示例1中f()函数中又调用了一个f()函数,这种情况属于直接调用自身函数。
示例2
int f1(int x)
{
int y,z;
z = f2(y);//f1()函数中调用f2()函数
return 2*z;
}
int f2(int x)
{
int y,z;
z = f1(y);//f2()函数中调用f1()函数
return 2*z;
}
在示例2中f1()函数调用了f2()函数而f2()函数又调用了f1()函数。这种情况属于间接调用自身函数。
可以看到以上两个示例都会出现无止境的递归调用。显然一个程序不应该出现无止境的递归调用即死递归而应使用有限次的、可终止的递归调用。可以使用if()语句等条件控制语句来实现只有满足某些条件才继续递归调用,否则不再继续调用。
示例有5个学生问第5个学生多大他说比第4个学生大2岁。问第4个学生多大他说比第3个学生大2岁。问第3个学生多大他说比第2个学生大2岁。问第2个学生多大他说比第1个学生大2岁。问第1个学生多大他说他10岁。求第5个学生的年龄。
提示:显然学生的年龄符合以下规律:
age(n)=age(n-1)+2;//n为2~5
age(n)=10;//n为1
因此可以使用递归调用来计算第5个学生的年龄。
#include<stdio.h>
int age(int n)
{
int c;
if(n==1)//递归出口
c = 10;
else
c = age(n-1)+2;//递归逻辑
return c;
}
int main()
{
printf("第5个学生的年龄是%d\n",age(5));
return 0;
}
注意我们在使用递归的时候一定要设置递归的终止条件称之为“递归出口”如果没有递归出口则递归会无限次循环下去不仅可以使程序崩溃严重的更有可能造成系统崩溃。因此初学者在使用递归的时候一定要谨慎。如果程序数据计算量不大而运行时间又过长则我们可认为出现了死递归请使用ctrl+c来终止程序。
练习1用递归法求n!n由键盘输入。
答案:
int jiecheng(int n)
{
int f;
if(n<0)
{
printf("输入数据错误!\n");
return 0;
}
else if(n==0||n==1)
f = 1;
else
f = n*jiecheng(n-1);
return f;
}
int main()
{
int n;
printf("请输入n的值");
scanf("%d",&n);
if(jiecheng(n)>0)
{
printf("%d的阶乘是%d\n",n,jiecheng(n));
}
else
{
printf("输入错误!\n");
}
return 0;
}
练习2用递归方法求n阶勒让德多项式的值。递归公式为
Pn(x)=1;(n=0)
Pn(x)=x;(n=1)
Pn(x)=((2n-1) * x - Pn-1(x) - (n-1) * Pn-2(x))/n;(n>1)
答案:
#include<stdio.h>
float Legendre(float x,int n)
{
if(n==0)
return 1;
else if(n==1)
return x;
else
return ((2*n-1)*x - Legendre(x,n-1) - (n-1)*Legendre(x,n-2))/n;
}
int main()
{
float x;
int n;
printf("请输入x的值");
scanf("%f",&x);
printf("请输入n的值");
scanf("%d",&n);
printf("勒让德多项式的值是%f\n",Legendre(x,n));
return 0;
}
练习3Hanoi汉诺塔问题
古代经典数学问题。有一个梵塔塔内有3个塔座A、B、C。开始时A塔座上有64个盘子大小不一大盘在下小盘在上。有一个老和尚想将A塔座的64个盘子全部移动到C塔座上规定
⒈一次只能移动一个圆盘
⒉必须始终保持大盘在下,小盘在上
⒊圆盘只能在3个塔座间移动不允许放在地面上等其他地方
编程输出n个圆盘的Hanoi塔问题的移动步骤。
注意n个圆盘的Hanoi塔需要(2^n)-1步移动步骤因此我们输入的圆盘数n不应过大。
提示先动手模拟数量较少的圆盘如3个圆盘的移动步骤再思考其中的算法
分析Hanoi塔问题使用的递归解法
⒈将A塔座上的n-1个圆盘借助C塔座先移动到B塔座上
⒉将A塔座上的第n个圆盘移动到C塔座上
⒊将B塔座上的n-1个圆盘借助A塔座移动到C塔座上
而第一步跟第三步都是将n-1个圆盘从"one"塔座借助"two"塔座移动到"three"塔座上,不同的是第一步"one"对应A"two"对应C"three"对应B而第三步"one"对应B"two"对应A"three"对应C。因此可以将以上三步分成2种操作
⒈将n-1个盘子从一个塔座移动到另一个塔座n>1这是一个递归的过程
⒉将第n个盘子从一个塔座移动到另一个塔座。
因此我们应编写2个函数一个函数hanoi(int n,char one,char two,char three)表示将n个盘子从one借助two移动到three上。另一个函数move(char x,char y)表示将x移动到yx和y分别代表A、B、C其中之一根据不同情况每次选取不同的塔座带入move()函数中。
答案:
#include<stdio.h>
void move(char x,char y)
{
printf("%c--->%c\n",x,y);
}
void hanoi(int n,char one,char two,char three)
{
if(n==1)//递归出口
{
move(one,three);
}
else//递归过程
{
hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three);
}
}
int main()
{
int m;
printf("请输入圆盘的个数:");
scanf("%d",&m);
hanoi(m,'A','B','C');
return 0;
}
练习4使用递归解决“棋子移动”问题
有2nn>=4个棋子排成一行其中黑棋B有n个白棋W有n个并留有两个空格。例如当n=4时排列如下所示W为白棋B为黑棋0为空格
W W W W B B B B 0 0
当n=5时排列如下所示W为白棋B为黑棋0为空格
W W W W W B B B B B 0 0
现在需要移动棋子,移动规则如下:
⒈每次必须同时移动相邻的两个棋子
⒉每次移动必须跳过若干棋子
⒊不能随意调换任意两个棋子的位置
目标:将所有的棋子移动为黑白棋相间的形式,中间不能有空格。
例如当n=4时移动步骤如下
起始: W W W W B B B B 0 0
第一步W W W 0 0 B B B W B
第二步W W W B W B B 0 0 B
第三步W 0 0 B W B B W W B
第四步W B W B W B 0 0 W B
第五步0 0 W B W B W B W B完成
编程实现从键盘输入nn>=4求每一步的棋子移动
答案:
//递归出口当n=4的时候
//递归逻辑当n>4时move(n,n+1)→(2n+1,2n+2); move(2n-1,2n)→(n,n+1); 递归n-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define SPACE 0
#define WHITE -1
#define BLACK 1
void print_array(int a[],int n)//打印棋子
{
int i;
for(i=0;i<n;i++)
{
if(a[i]==WHITE)
printf("W ");
else if(a[i]==BLACK)
printf("B ");
else
printf("0 ");
}
printf("\n");
}
void move_chess(int a[],int x1,int x2)//移动棋子。x1为起始位置x2为终点位置
{
x1--;
x2--;
if(a[x2]!=SPACE)
{
printf("There's not space!\n");
return;
}
else if(a[x1]==SPACE)
{
printf("Can't move blank-chess\n");
return;
}
else if(x1+1==x2 || x1+2==x2)
{
printf("Can't move chess like that!\n");
return;
}
else
{
a[x1]^=a[x2];
a[x2]^=a[x1];
a[x1]^=a[x2];
a[x1+1]^=a[x2+1];
a[x2+1]^=a[x1+1];
a[x1+1]^=a[x2+1];
}
}
void blackwhite_chess(int a[],int n1,int n)//黑白棋移动函数
{
if(n1==4)//递归出口
{
move_chess(a,4,9);
print_array(a,n*2+2);
move_chess(a,8,4);
print_array(a,n*2+2);
move_chess(a,2,8);
print_array(a,n*2+2);
move_chess(a,7,2);
print_array(a,n*2+2);
move_chess(a,1,7);
print_array(a,n*2+2);
return;
}
else//递归逻辑
{
move_chess(a,n1,2*n1+1);
print_array(a,n*2+2);
move_chess(a,2*n1-1,n1);
print_array(a,n*2+2);
blackwhite_chess(a,n1-1,n);
}
}
int main()
{
int n;
int i;
printf("please input the number:");
scanf("%d",&n);
int *a=(int*)malloc(sizeof(int)*(2*n+2));
for(i=0;i<2*n+2;i++)//初始化1
{
if(i<n)
a[i]=WHITE;
else if(i<2*n)
a[i]=BLACK;
else
a[i]=SPACE;
}
print_array(a,2*n+2);
blackwhite_chess(a,n,n);
return 0;
}
6、static关键字
1静态局部变量
有时我们希望函数中的局部变量在函数调用后不消失而是继续保留原值即它占用的存储空间不被系统释放。在下一次调用该函数时该变量还保存着上次函数使用结束后的值。这时我们可以使用static关键字来定义一个静态局部变量。
用法static 变量类型 变量名;
static char c;
static int a;
说明:
⒈静态局部变量的存储空间在静态存储区,与函数的堆栈区不在同一个区域,因此函数运行完毕后静态局部变量不会被释放。
⒉静态局部变量只赋一次初值。下次使用静态局部变量的时候直接使用上次保存下来的值而不会重新赋值。
⒊如果静态局部变量未赋初值则系统自动赋初值0int型等或'\0'char型。而其他变量则不会这样。其他的变量若未赋初值则变量内会保存一个随机的数。
⒋虽然静态局部变量在函数被系统释放后仍然存在,但它仍不可被其他函数调用。因为本质上讲它还是一个局部变量,只能在本函数内使用而不能在其他函数内使用。
示例:
#include<stdio.h>
void f()
{
int a=1;
static int b=1;
a++;
b++;
printf("a是%d\nb是%d\n",a,b);
}
int main()
{
f();
f();
f();
f();
f();//调用5次f()函数
return 0;
}
在示例程序中尽管a和b都是局部变量但变量b是静态局部变量它会保存上一次函数运行后的值。因此每次运行f()函数变量a的值都会初始化一次而变量b则会使用上一次f()结束后的值。
练习使用static变量编程分别输出从1到6的阶乘值。
答案:
#include<stdio.h>
int jiecheng(int n)
{
static int f=1;
f=f*n;
return f;
}
int main()
{
int i;
for(i=1;i<=6;i++)
{
printf("%d!=%d\n",i,jiecheng(i));
}
return 0;
}
2将外部变量的作用域限制在本文件中静态外部变量
//请对比extern关键字的第二条
有时程序设计需要某些外部变量只能在本文件中使用而不允许在其他文件中使用。这时可以在定义外部变量时加static声明。
示例:
//文件file1.c
static int A;
……
//文件file2.c
void fun()
{
A++;//出错
}
这种加上了static声明只能在本文件中使用的外部变量称为“静态外部变量”。静态外部变量的作用是可以将某些变量“屏蔽”起来从其他文件角度看这个变量“不可见、不可使用”这样就保护了这个外部变量防止程序运行出错。
3定义一个内部函数
//请对比extern关键字的第三条
如果一个函数只能被本文件的其他函数调用而不允许被其他文件的函数调用这样的函数称为“内部函数”。定义内部函数时在函数名和类型名前加static关键字。
static int fun(int a,int b)
则该函数fun(int a,int b)只能被这个文件中的其他函数所调用,不能被其他文件的函数所调用。
7、extern关键字
1在一个文件内扩展外部变量的作用域
如果一个外部变量不在文件开头定义则其有效范围是从这个变量定义处到文件结束。在定义开始前的函数不能使用这个外部变量。如果需要使用则可在引用这个变量前加extern关键字进行“外部变量声明”。例如
示例:
int f()
{
extern int a;//外部变量声明
……
}
int a=10;//外部变量定义
这样就相当于扩展了外部变量a的使用范围在函数f()中也可以使用变量a了。
关键字extern对外部变量进行“外部变量声明”表示把该外部变量的作用域扩展至此位置。
注意:
⒈extern仅仅起到变量的声明的作用而不是变量的定义。在示例中如果没有int a=10;即没有定义一个外部变量则不能够使用extern int a;变量。extern声明外部变量可以有很多但外部变量定义只允许有一个否则算重复定义变量编译报错
⒉我们提倡将外部变量写在所有需要引用它的函数之前(或直接写在文件开头,预处理指令(文件包含(#include和宏定义#define和条件编译#ifdef/#ifndef等下面
⒊变量的声明不需要建立存储空间,而变量定义则需要建立存储空间。
2将外部变量的作用域扩展到其他文件中
//请对比static关键字的第二条
一个C程序可以由多个文件组成。如果程序由多个文件组成在其中一个文件中需要引用另一个文件中已知的外部变量可以使用extern关键字将这个外部变量的使用范围扩展到需要使用的文件中。
示例:
//文件file1.c
int A;
……
//文件file2.c
extern A;
……
这样就可以在文件file2.c中使用文件file1.c的外部变量A
3定义一个外部函数
//请对比static关键字的第三条
如果一个函数不仅可以被本文件的其他函数调用而且可以被其他文件的函数调用这样的函数称为“外部函数”。定义外部函数时在函数名和类型名前加extern关键字。
extern int fun(int a,int b)
则该函数fun(int a,int b)可以被其他文件调用
综合练习1买彩票
编写一个“买彩票”的程序。彩票程序在后台随机生成1~35内的7个各不相同的数字。用户会输入一组7个数字中奖规则
猜中
7个500万
6个100万
5个1万
4个5000
3个500
012个没中奖
输出是否中奖及奖金。
综合练习2五子棋
编写一个“双人五子棋”的程序。棋盘为9*9的矩阵输入横纵坐标落子黑白双方轮流落子直至一人获胜为止。