Linux嵌入式学习--C语言函数
一、定义函数
1、一个c程序由多个程序模块组成,每一个程序模块作为一个源程序文件,对较大的程序,不希望把所有的内容放在一个文件中,而是把它们分别放在若干个源文件中,由若干个源程序文件组成一个c程序,这样便于编写和编译,提高调试效率,一个源程序文件可以为多个c程序共用。
2、在程序编译时,是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
3、函数都是平行相互独立,函数不能嵌套定义,但是可以相互调用,不能调用main函数。
4、库函数:由系统提供,用户不必自己定义,可以直接使用。
5、用户自己定义的函数,解决用户专门的函数。
6、无参函数:主函数不向被调函数传递数据,一般以不带会返回值的居多。
7、有参函数:通过参数向被调函数传递参数,被调函数会得到一个函数值提供给主调函数使用。
8、所有的函数必须先定义后使用。
1.1定义无参函数
一般形式:
类型名 函数名()
{
函数体
}
或者
类型名 函数名(void)//void表示空,表示函数没有参数
{
函数体
}
例如:
void printf_star()
{
printf("******\n");
}
1.2定义有参函数
一般形式:
类型名 函数名 (形式参数列表)
{
函数体
}
例如:
int表示函数值为整型的。
max是函数的名字
int x和int y是两个为整型的形式参数,调用函数的时候,主函数会把实际参数的值传给形参x和y.
return z是把z的值作为函数返回值带回给主函数。
int max(int x,int y)
{
int z;
z=x>y?x:y;
return z;
}
1.2定义空函数
一般形式
类型名 函数名()
{}
例如:
void sun()
{}
定义空函数的意义:
- 程序设计往往需要多个模块,在以后需要功能的时候可以补上
- 以后扩充新功能方便。
二、调用函数
一般调用形式:
函数名(实参列表)
例如:
printf_star();//调用无参函数
c=max(1,2);//调用有参函数
2.1调用的三种方式
- 函数调用语句
printf_star();//调用无参函数
- 函数表达式
c=2*max(1,2);//调用有参函数
- 函数参数
函数调用作为另外一个函数调用的实参
c=2*max(1,max(1,3));
printf("%d",max(1,2));
2.2调用时的两种数据传递方式
2.2.1形式参数和实际参数传递
1、定义函数的参数列表的变量是形式参数,是假的,虚拟的。
2、主函数中调用一个函数的时候,函数后面括号中的参数为实际参数。
实际参数可以是常量,变量和表达式。
3、系统会把实参的值传递给被调用函数的形参(形参从实参得到一个值)该值在函数调用期间有效,可以参加函数中的运算。
例如:输入两个整数,要求输出其中值较大者,用函数来找到最大值。
思路:
确定函数的返回值类型,因为最大值为整数,所以返回值可以是整型
输入的是两个整数,所以参数列表可以是两个int型的变量
#include<stdio.h>
int max(int x,int y)//max的形式参数
{
int z;
z=x>y? x:y;
return z;
}
int main()
{
int a,b,c;
printf("please input two numbers:");
scanf("%d,%d",&a,&b);
c=max(a,b);//a,b为实际参数传递给形参x和y
printf("the max is %d\n",c);
return 0;
}
注意:
- 实参可以是常量,变量或表达式,但是要有确定的值,在调用的时候将实参的值赋给形参
- 实参与形参的类型应该相同或者复制兼容。如果实参为int型而形参为float型,或者相反,则按不同类型数值的复制规则进行转换。
例如:
实参a为float型变量,其值为3.5,而形参x为int型,则在传递时先将实数3.5转化成整数3,然后送到形参x。字符型与int型可以相互通用。
2.3函数调用的过程
- 在没有调用函数的时候,形参并不占内存中的存储单元。被调用后,形参才被临时分配内存单元。
- 函数的返回值类型应该与函数类型一致。
- 如果函数不需要函数的返回值,则不需要return语句,定义为void类型。
- 实参向形参的数据传递是值传递,单向传递。
- 实参和形参在内存中占用不同的存储单元。
2.4函数的声明
1、调用的函数必须是已经定义的函数
2、使用库函数,应该在文件开头使用#include指令调用库函数所需要的信息。
3、自己定义的函数如果在主函数的后面,主函数调用的时候,应该子在主函数的前面对自己定义的函数进行声明。
为什么要在主函数之前进行函数的声明的呢?
声明的作用是把函数名,函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。
例如:输入两个实数,用一个函数求出它们之和。
#include<stdio.h>
int main()
{
float add(float x,float y);//对add函数的声明 ,也可以放在主函数的前面
float a,b,c;
printf("please input two numbers:");
scanf("%f%f",&a,&b);
c=add(a,b);//调用add函数
printf("sum is %f\n",c);
return 0;
}
float add(float x,float y)//定义add函数
{
float z;
z=x+y;
return z;//把变量z的值作为返回值
}
把函数的声明放在主函数的前面
#include<stdio.h>
float add(float x,float y);//函数声明放在主函数的外面
int main()
{
float a,b,c;
printf("please input two numbers:");
scanf("%f%f",&a,&b);
c=add(a,b);//调用add函数
printf("sum is %f\n",c);
return 0;
}
float add(float x,float y)//定义add函数
{
float z;
z=x+y;
return z;//把变量z的值作为返回值
}
编译是从上向下逐步进行的,如果没有对函数进行声明,编译系统就无法确定add是不是函数名,以及对实参的类型和个数进行检查。
为什么要用函数的首部来做函数的声明呢?
这是为了便于对函数调用的合法性进行检查。因为在函数的首部包含了检查调用函数是否合法的基本信息其中包括函数名,函数值类型,参数个数,参数个数和参数顺序。在检查函数调用时要求函数名,函数类型,参数个数,参数顺序必须和函数声明的一致,实参类型必须与函数声明中的形参类型相同,否则就按出错处理。这样就能保证函数的正确调用。
2.4.1常见函数声明的形式
函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2…)
函数类型 函数名(参数类型1,参数类型2,…)
例如:
float add(float ,float);
float add(float a,float b);
`
三、函数的嵌套使用
函数定义是互相平行独立的,一个函数中不能定义其他函数,也就是不能嵌套定义函数,但是可以嵌套使用函数。
【例1】输入4个整数,找出其中最大的数,用函数的嵌套调用来处理。
#include<stdio.h>
int main()
{
int max4(int a,int b,int c,int d); //对函数4的声明,也可以再int main函数的外面
int a,b,c,d,max;
printf("please enter 4 interger numbers:");
scanf("%d %d %d %d",&a,&b,&c,&d); //输入四个数
max=max4(a,b,c,d); //调用max4函数,得到4个数中的最大者
printf("max=%d\n",max); //输出4个数中的最大者
return 0;
}
int max4(int a,int b,int c,int d) //定义max4函数
{
int max2(int a,int b); //对max2函数的声明
int m;
m=max2(a,b); //调用max2函数,得到a和b两个数中的大者,放在m中
m=max2(m,c); //调用max2函数,得到a,b,c三个数中的大者,放在m中
m=max2(m,d); //调用max2函数,得到a,b,c,d四个数中的大者,放在m中
return m; //返回最大值
}
int max2(int a,int b)
{
if(a>=b)
return a;
else
return b;
}
把函数调用作为函数参数
int max4(int a,int b,int c,int d) //定义max4函数
{
int max2(int a,int b); //对max2函数的声明
int m;
m=max2(max2(max2(a,b),c),d); //把函数调用作为函数参数
return m; //返回最大值
}
也可以取消变量
int max4(int a,int b,int c,int d) //定义max4函数
{
int max2(int a,int b); //对max2函数的声明
return max2(max2(max2(a,b),c),d); //返回最大值
}
max2函数也可以用条件表达式写
}
int max2(int a,int b)
{
return (a>=b? a:b);
}
四、函数的递归
在函数的使用过程中直接或间接的调用函数该本身,称为函数的递归调用。
在使用递归函数的时候一定要有循环退出条件。
4.1用递归方法求n的阶乘
【例如】用递归方法求n的阶乘
我们得知道递归的公式。
n ! = { n ! = 1 n=0,1 n ∗ ( n − 1 ) n>1 n! = \begin{cases} n!=1 &\text{n=0,1} \\ n*(n-1) &\text{n>1 } \end{cases} n!={n!=1n∗(n−1)n=0,1n>1
#include<stdio.h>
int fac(int n); //函数的声明
int main()
{
int n;
int y;
printf("input a number:");
scanf("%d",&n);
y=fac(n);
printf("%d!=%d",n,y);
return 0;
}
int fac(int n)
{
if(n==0 || n==1)
return 1;
else
return n*fac(n-1);
}
4.2汉诺塔问题
【例2】古代有一个梵塔,塔内有三个座A,B,C。开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上,有一个老和尚想把这64个盘子从A座移到C座,但规定每次只能允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上,在移动的过程中可以利用B座,要求程序编程输出移动盘子的步骤。
【思路】 假设只有三个盘子移动,三个盘子从A移动到C,需要移动2的3次方次盘子。老和尚想要是有另外一个和尚将上面的两个盘子从一个座移动到另外一个座,那么问题就解决了,所以老和尚命令第2个和尚将2个盘子从A座移动到B座;老和尚自己把最底下最大的盘子从A移动到C座;再命令第2个和尚将2个盘子从B座移动到C座。那么第2个和尚就想要是能移动1个盘子从一个座移动到另外一个座就好了…这样层层递归就能解决问题。
【终止条件】最后一个和尚只需移动一个盘子,否则递归继续进行。
【移动情况如下】三个盘子为例
(1)将A座上2个盘子移动到B座上(借助C座)
1.1将A座上1个盘子从A座移动到C座
1.2将A座上1个盘子从A座移动到B座
1.3将C座上1个盘子从C座移动到B座
(2)将A座上1个盘子移动到C座
(3)将B座上2个盘子移动到C座上(借助A座)
3.1将B座上一个盘子从B座移动到A座上
3.2将B座上1个盘子从B座移动到C座上
3.3将A座上1个盘子从A座移动到C座上
【综上】一共需要7步
A->C,A->B,C->B,A->C,B->A,B->C,A->C
移动N个盘子则需要经历(2的N次方-1)步。
(1)将A座上N-1个盘子移动到B座上(借助C座)
(2)将A座上剩下的1个盘子移动到C座
(3)将B座上N-1个盘子移动到C座上(借助A座)
#include<stdio.h>
void hanoi(int n,int one,int two,char three);//函数的声明
int main()
{
int m;
printf("input the number of diskes:");
scanf("%d",&m);
printf("The step to move %d diskes:\n",m);
hanoi(m,'A','B','C');
return 0;
}
void hanoi(int n,int one,int two,char three)//one代表A,two代表B,three代表C
{
void move(char x,char y);//对move函数的声明
if(n==1)
move(one,three) ;
else
{
hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three);
}
}
void move(char x,char y)
{
printf("%c->%c\n",x,y);
}
五、数组作为函数参数
数组作为一个整体,在内存中占连续的一段存储单元。
数组元素作为函数实参的时候,是把值传给了形参,被称为“值传递。
如果是用数组名作为实参时,向形参传递的是数组元素的首地址。
注意:实参和形参的数据类型要保持一致
5.1数组元素作为实际参数
练习:输入10个数,要求输出其中值最大的元素和该数是第几个数
#include<stdio.h>
int max(int x,int y);//函数的声明
int main()
{
int a[10],m,n,i;
printf("enter 10 numbers :");
for(i=0;i<10;i++)//输入10个数据
{
scanf("%d",&a[i]);
}
printf("\n");
for(i=1,m=a[0],n=0;i<10;i++)
{
if(max(m,a[i])>m)//若max函数返回的值大于m
{
m=max(m,a[i]);//max函数返回的值取代m原值
n=i;//把此数组元素的序号记下来,放在n中
}
}
printf("The largest number is %d\nit is the %dth number.\n",m,n+1);
}
int max(int x,int y)//定义max函数
{
return (x>y? x:y);//返回x和y中的大者
}
5.2一维数组名作为函数参数
练习:
有一个一维数组score,放10个学生成绩,求平均成绩
思路:
用一个函数来求平均成绩,不用数组元素作为函数参数,而是用数组名作为函数实参,形参也用数组名,在函数中引用各数组元素,求平均成绩并且返回到main函数中
#include<stdio.h>
float average(float array[10]);//函数的声明
int main()
{
float score[10],aver;
int i;
printf("input 10 scores:\n");
for(i=0;i<10;i++)//输入10个数据
{
scanf("%f",&score[i]);
}
printf("\n");
aver = average(score);
printf("average score is %5.2f\n,aver);
return 0;
}
float average(float array[10])
{
int i;
float aver,sum = array[0];
for(i=1;i<10;i++)
sum = sum + array[i];
aver = sum/10;
return aver;
}
注意:
(1)实参和形参数组的数据类型应该保持一致,结果将出错
(2)实参数组的首元素的地址传递给形参数组名,形参数组获得了实参数组的首元素的地址,此时形参数组首元素和实参数组首元素的同样的地址,它们占用同一存储单元。
(3)形参也可以写成带括号的形式
float average(floatt array[])
等同于:
float average(float *array)
5.3多维数组名作为函数参数
常见的二维数组的定义:
(1)int array[3][10]//3行10列
(2)int array[][10]
注意:定义二维数组时,必须指定列数,即一行包含有多少个元素,由于形参和实参数组类型是相同的。
实参:
int a[3][10]
形参:
int a[3][10]或者int a[][10]
练习:有一个3*4的矩阵,求所有元素中最大值
#include<stdio.h>
int max(int arr[][4]);
int main()
{
int a[3][4]={1,3,5,7},{2,4,6,8},{15,17,34,12};
printf("max is %d\n",max(a));
return 0;
}
int max(int arr[][4])
{
int i,j,m;
m = arr[0][0];
for(i = 0;i<3;i++)
for(j = 0;j<4;j++)
if(arr[i][j]>m)
m = arr[i][j];
return m;
}