函数是一种模块化的思想,我们把实现常用功能的一部分代码进行独立,类似于一个“黑箱”,你通过输入可以实现对应数据的输出。提高代码的效率和可读性。子函数虽然独立,但依然和main函数有关联。
一个函数包括以下三个部分:
1、函数的声明:在程序使用函数之前需要通知系统,在调用这个函数的时候便于进行对照检查函数各个部分是否满足要求,若不满足要求编译器回提示错误信息;
2、函数的一般形式:<数据类型> <函数名><形参>;
3、函数的返回值类型:<数据类型>;
而函数的调用方式如下:函数名 <实参>;(实参功能就是将值传递给对应子函数中的形式参数)
函数的定义形式如下:
<函数的返回值类型> <函数名><形参> == <数据类型> <函数名><形参>
{
c语句块;
return 返回值;//结束函数并返回函数的返回值
}
下面看个例子:
//用函数方式实现输入两个数并输出它们的和
#include
#include "fun.h"
int fun(int m, int n);//函数声明
int main(int argc, const char *argv[])
{
int a = 0, b = 0;
int ret = 0;
printf("请输入2个整数:\n");
scanf("%d%d", &a, &b);
ret = fun(a,b);//fun函数调用
printf("%d\n",ret);
return 0;
}
int fun(int m, int n)//fun函数定义
{
return m + n;
}
注:(1)形参也是在函数执行时在内存栈区创建的局部变量,作用域在本函数空间,一旦函数执行完毕,栈区空间将立即释放,局部变量将消失。
(2)函数传参的本质,其实就是复制的过程,实参往形参复制(表现为赋值),实参本身并未消失,形参为实参在栈区创建的副本,传递完成后,形式参数和实际参数没有本质关联,这种值传递方式成为复制传参。比如下面的程序:
//交换a、b两个数字的值
#include
int fun(int m,int n);
int main(int argc, const char *argv[])
{
int a = 10,b = 5;//a、b的值存放在栈区,&a,&b分别指向10,5
fun(a,b);//这时候把a、b的值复制给形参m、n
printf("a:%d b:%d\n",a,b);//a、b的值并没有发生变化a = 10,b = 5
return 0;
}
int fun(int m,int n)//函数实现m、n内容的交换
{
int temp;
temp = m;
m = n;
n = temp;
printf("m:%d n:%d\n",m,n);//m = 5, n = 10;
return 0;//函数结束,将栈区创建的m、n清空
}
若要实现a、b的交换,不妨对a、b的地址进行操作,程序修改为下面的样子:
#include
int fun(int *m,int *n);//声明形参为int型指针变量
int main(int argc, const char *argv[])
{
int a = 10,b = 5;
fun(&a,&b);//通过函数操作a、b的地址
printf("a:%d b:%d\n",a,b);
return 0;
}
int fun(int *m,int *n)
{
int temp;
temp = *m;//*m = 10
*m = *n;//*n = 5
*n = temp;
printf("m:%d n:%d\n",m,n);
return 0;//m、n此时依然会被清除,但是通过地址操作a、b里的值,a、b已经完成了交换。
}
但如果是全局变量,将会有不一样的结果出现,比如下面的程序探讨的那样:
//探究函数对全局变量的赋值操作
#include
int num = 123;//num为全局变量,全局变量储存在整个函数执行范围内均有效
int fun()//fun函数的赋值操作
{
num = 456;
return 0;
}
int main(int argc, const char *argv[])
{
printf("num:%d\n",num);//num = 123
fun();//fun函数结束,num值并不会被清除,num的值需要到main函数末尾才清空
printf("num:%d\n",num);//num = 456
return 0;
}
(3)另一种传参方式叫做址传递:将地址值进行传递,形参选用指针变量进行接收,这个时候,子函数就拥有了main函数中的地址,也就可以对main函数中的值进行对应操作。
*特别地,对于数组传递而言无论是值传递,还是地值传递,都是传递数组名,又因为数组名通常情况下表示数组的首地址,所以本质都是地址传递。比如下面的例子:
//探究数组传递方式:以值传递方式获取数组元素,以地址传递方式打印
#include
int fun(int m[],int n);//值传递方式:形参为一维数组
int fun1(int *p,int n);//地址传递方式:形参为一个指针变量
int main(int argc, const char *argv[])
{
int a[6];//定义一个元素个数为6个的一维数组
fun(a,6);//调用获取函数,a这里是一维数组的首个元素
fun1(a,6);//调用打印函数,a在这里是指向一维数组首地址的指针
//由实参的书写形式即可获得信息,数组的值传递方式和地址传递方式本质上是相同的。
return 0;
}
#if 1
int fun(int m[],int n)//值传递
{
int i;
for(i = 0;i < n;i++)
{
scanf("%d",&m[i]);//scanf前面格式固定添加地址符
}
return 0;
}
int fun1(int *p,int n)//地址传递
{
int i;
for(i = 0;i < n;i++)
{
printf("%d ",*(p+i));//*表示依次打印p指针指向的地址存储的内容
}
printf("\n");
return 0;
}
#endif
#if 0
int fun(int *p,int n)//地址传递
{
int i;
for(i = 0;i < n;i++)
{
scanf("%d",p+i);//*表示依次输入p指针指向的地址
}
return 0;
}
int fun(int m[],int n)//值传递
{
int i;
for(i = 0;i < n;i++)
{
printf("%d ",m[i]);
}
return 0;
}
#endif
通常情况下数组传递时的形参是传递数组的数组名,以及数组的元素个数。将以上的内容扩展至二维数组传参,有着类似的结论,看下面的例子:
//上面的问题在二维数组情况下的表现
#include
int fun(int m[][4],int n);//值传递,二维数组传参过程一般要把列数进行一个约束,这样可以看成一个元素为行数组的一维数组。
int fun(int (*p)[4],int n);//地址传递,首个形参为数组指针。
int main(int argc, const char *argv[])
{
int a[3][4];
fun(a,3);
fun1(a,3);
return 0;
}
int fun(int m[][4],int n)//值传递
{
int i,j;
for(i = 0;i < n;i++)
{
for(j = 0;j < 4;j++)
{
scanf("%d",&m[i][j]);
}
}
return 0;
}
int fun1(int (*p)[4],int n)//地址传递
{
int i,j;
for(i = 0;i < n;i++)
{
for(j = 0;j < 4;j++)
{
printf("%2d ",p[i][j]);//或者写成*(*(p+i)+j),二者等价
}
printf("\n");
}
return 0;
}
最后看一种没有形参,也没有返回值的函数:void函数。其函数定义为:
void 函数名(void);(简单讲就是什么也不做,但在实际情况中如果需要预留一部分函数功能的话还是有点用处的。)
以上。