函数
- 问题的引入
有时候,我们经常需要再一个程序中,对一个数组进行输入。
如:
int a[10];
for(i = 0;i < 10;i++)
{
scanf("%d",&a[i]);
}
int b[5];
for(i = 0;i < 5;i++)
{
scanf("%d",&b[i]);
}
...
函数:重复利用代码块。
-
函数
什么是函数?
function 功能函数是完成某一个功能的指令序列的封装。
C语言除了初始化语句,其他所有的语句必须在函数内部。
函数可以实现代码复用,以及实现模块化的设计。
结构化程序设计者主张把一个大的任务分成多个功能函数来实现。
函数:相同功能的代码块,重复利用的,模块化设计思想。
-
如何去设计一个函数?
函数是用来实现某种功能。-
明确函数的功能是什么?要完成什么样的目标
函数功能
比如:
想去求一个数组的最大值就可以给函数进行一个命名
给函数命令需要注意两点:
1) 函数名的命名要符合C语言的标识符命名规则
2) 请在给函数命名是尽量做到“见其名而知其意”。find_array_max zhao_max
-
考虑完成该任务/功能需要哪些已知条件?
完成该任务/功能/函数需要输入哪些参数
比如:
需要给我的参数:
要给我数组名
数组中的元素的个数 -
完成该任务/功能/函数的输出结果
-
算法的实现以及代码的调试等。
-
-
C语言中函数的语法形式
函数返回值类型 函数名(输入参数列表)
{
C语句;(用来具体实现代码的部分—>决定你函数具体的功能)
return 表达式;
}“函数返回值类型”:
return语句后面的那个表达式值的类型。
一般可以是“单值”类型,即基本类型和指针类型。
函数也可以没有返回值。
不指定函数返回值类型,如果不指定的话,则默认的类型为int。“函数名”:
1) 函数名的命名要符合C语言的标识符命名规则
2) 请在给函数命名是尽量做到“见其名而知其意”。“输入参数列表”:
格式:数据类型1 参数名1,数据类型2 参数2…
void 表示该函数不带参数“return”:
返回的意思,return只能在函数的内部。
表示函数结束的意思。
return ;//表示函数结束,不带返回值
return 表达式;//表示函数结束,同时也带一个返回值,这个返回值就是
“表达式”的值,函数的返回值类型,就是return语句后面的表达式的类型。注意:
函数不能嵌套定义,即函数不能定义在函数的内部,只能定义在外部。例子:
设计一个函数,函数功能:求两个整数之和。
1. 明确函数的功能,根据函数功能起一个“见其名而知其意”的名字。
求两个整数之和—>sum
2. 完成该任务/功能/函数需要输入哪些参数
输入的参数是:两个整数
sum(int a,int b)
3. 完成该任务/功能/函数的输出结果
int sum(int a,int b)//函数头
4. 算法的实现以及代码的调试等int sum(int a,int b)//函数头 { //函数体 int s; s = a + b; return s; }
-
函数的调用
函数调用:调用一个已经写好的函数去执行。(1) 函数的调用
a. 指定函数名
b. 准备好函数所需要的参数
主调函数:调用其他函数的函数,比如main()
被调函数:被其他函数调用的函数。
形式参数:被调函数在定义时的参数
实际参数:在函数调用过程中,主调函数传递给被调函数的输入参数值
在调用函数的时候,需要指定实参,并且实参需要和形参一一对应。
一一对应:形参与实参的个数要一致,并且数据类型必须一致。
在C语言中,函数的参数的传递只有一种情况,就是“值传递”:就是把实参的值
传递给相应的形参。sum(3,4); sum(m,n); sum(3+5,4-2); sum(sum(3,5),4); int m = 1,n = 2; sum(int m,int n);//error 函数调用时,只需要指定实参的值,不需要实参的类型。 sum(4);//error 因为实参与形参的个数不一致
(2) 函数调用过程
sum(3,5);1. 把实参的值赋值给相应的形参 2. 跳转到指定的函数中去执行,直到遇到return或者函数语句执行完毕后再返回 到函数调用处。return后面表达式的值将作为整个函数调用表达式的值。
-
数组作为函数的参数
当一个函数的参数是一个数组时,数组作为形式参数时,该如何描述呢?数组元素的类型 数组名[数组元素的个数];
例子:
(1) int a[10] //参数b接收一个int类型的数组的数组名 //参数n接收数组中元素的个数 xxx(int b[],int n) { } 调用: xxx(a,10); (2) int a[3][4]; //参数b接收一个int[4]类型的数组的数组名 //参数n接收数组中元素的个数 yyy(int b[][4],int n) { }
-
函数的声明
“声明”:C语言中的声明是一个已经存在的标识符(对象的名字)。为什么需要声明?
C语言中编译源文件时,是从第一行到最后一行,一行一行进行编译的。
如果项目中存在多个.c文件时,是一个文件到一个文件的编译,有时候我们在一个文件中
1.c中需要用到2.c中定义的对象(变量,函数。。。。)。在编译1.c的时候,碰到了
这个对象的名字,编译器根本就不认识这个标识符是什么东西。约定: 我们一般将声明语句放在调用的前面。
声明:
变量的声明:
extern 变量的类型 变量名;函数的声明:
外部函数的声明:
extern 外部函数的头部;int sum(int a,int b) { return a + b; } 声明:int sum(int ,int )
-
变量的作用域与生存期
(1) 作用域
一个对象(函数、数组、变量等)起作用的范围。全局变量: 在函数外面定义的变量就叫全局变量。 全局变量没有初始化的话,默认初始化为0. 全局变量的作用域:自定义起往下到文件结束(别的文件中也可以调用,但是需要 用extern声明)。 static如果修饰一个全局变量,那么这个全局变量的作用域就限制在本文件中。 static修饰的变量在不初始化的情况下默认初始化为0. 同理,如果有一个函数被static修饰,那么这个函数也只能在本文件中使用。 局部变量: 在函数体内或者复合语句内定义的变量,叫局部变量。 局部变量的作用域:自定义起到函数或复合语句结束(即第一个右花括号结束)。 不同作用域的两个变量,必然是两个独立的内存空间。 即使重名,就近往上找。 形不改实: 当主调函数调用被调函数时,且实际参数是变量时,则被调函数里面的形参变量的 值的变化,不会影响到实参的值。 原因:形参只是被调函数中的局部变量,与实参变量是两个独立的内存空间 只是形参变量的值被赋值与实参的值而已。两者互不影响。
(2) 生存期
是指一个对象从生到死的期间(生存期)。一个变量过了它的生存期,则其内存空间将会被系统释放掉。 全局变量: 随进程的持续性。你的程序一运行,全局变量就一直存在,直到你的进程退出。 一个运行起来的程序就称之为进程。 局部变量: 1. 普通局部变量 int a; 普通局部变量的生存期,是从定义处到第一个右花括号结束。 void f() { int a = 7; a++; printf("a = %d\n",a); } int main() { f();//8 f();//8 } 2. static(静态)局部变量 static int a; static局部变量的生存期:随进程的持续性。 只初始化一次。 void f() { static int a = 7; a++; printf("a = %d\n",a); } int main() { f();//8 f();//9 } static在C语言中只有两个作用: 1. static用于修饰全局变量和函数时,作用是: 使被修饰的全局变量和函数的作用域,变成仅在本文件中有效。 2. static用于修饰局部变量,作用是: 使被修饰的局部变量的生存期,随进程的持续性。
-
递归函数
递归函数:直接或者间接的调用函数本身。
自己调用自己什么情况下用递归呢?
解决一个问题的时候,解决思路化成与问题本身类似的问题时。C语句递归函数的设计:
1. 问题模型本身要符合递归模型(递推关系)。
2. 先明确函数要实现的功能与参数之间的关系,暂不管功能的具体实现。
3. 问题的解,当递归到一定的层次时,答案是必须要显而易见的,且能够结束
函数(不能无限递归)。
4. 要呈现第n层与第n-1层之间的递推关系。例子:
(1) 写一个函数来求第n个人的年龄。
第1个人的年龄10岁,第二个人的年龄是12岁,以此例推。
//普通版int age(int n) { int a[n]; a[0] = 10; int i; for(i = 1;i < n;i++) { a[i] = a[i - 1] + 2; } return a[n-1]; }
//递归版本
int age(int n) { age(n) = age(n - 1) + 2; ... age(5) = age(4) + 2; ... age(2) = age(1) + 2; age(1) = 10; } int age(int n) { if(n > 1) { return age(n-1) + 2; } else { return 10; } } age(4) --->return age(n-1) + 2; age(3) ---> return age(n-1) + 2; age(2) --->return age(n-1) + 2; 10
(2) 写一个递归函数,实现n!
int jiechen(int n)
{
if(n == 0 || n == 1)
{
return 1;
}
else
{
return jiechen(n-1)*n;
}
}
(3) Hanio塔
把n个盘子从A移动到C,中间可以利用B。
把移动的步骤以及移动的步数打印出来。
2
A->B
A->C
B->C
3steps
int step;
//把n个盘子从A移动到C,中间可以利用B。
void hanio(int n,char A,char B,char C)
{
//当盘子只有两个时,答案是显而易见的。
if(n == 2)
{
printf("%c-->%c\n",A,B);
step++;
printf("%c-->%c\n",A,C);
step++;
printf("%c-->%c\n",B,C);
step++;
return ;
}
else
{
//找出第n层与第n-1层之间的关系
//step1:把位置A的第n-1层以及以上的盘子放置到B,可以利用C
hanio(n-1,A,C,B);
//step2:把位置A上的最后一个大盘子直接移动到C
printf("%c-->%c\n",A,C);
step++;
//step3:把位置B的第n-1层以及以上的盘子放置到C,可以利用A
hanio(n-1,B,A,C);
}
}
hanio(3,'A','B','C')
{
hanio(2,'A','C','B');
{
if(n == 2)
{
printf("%c-->%c\n",A,B);//A-->C
step++;
printf("%c-->%c\n",A,C);//A-->B
step++;
printf("%c-->%c\n",B,C);//C-->B
step++
return ;
}
}
printf("%c-->%c\n",A,C);//A-->C
hanio(2,'B','A','C');
{
if(n == 2)
{
printf("%c-->%c\n",A,B);//B-->A
step++;
printf("%c-->%c\n",A,C);//B-->C
step++;
printf("%c-->%c\n",B,C);//A-->C
step++
return ;
}
}
}