有时候,我们可能需要使用一些 API 或者自定义一些功能实现,这些都是通过函数实现的。其实函数在日常的程序编写中使用频率还是比较高的,使用函数主要有几个好处:
- 程序的可读性强
- 利于程序维护
- 提高了代码的移植性
- 提高了程序开发的效率
C 标准库及普通库函数
- 对于一些使用频率比较高的功能来说,库帮我们实现了这些功能,并给出了函数接口,其它用户可以简单的通过程序调用就能够实现这部分功能,从而节省了使用者的时间和精力
- 库函数无须用户定义,也不必在程序中作类型说明,只需在程序前包含有该函数定义的头文件
- 对于库函数来说,我们主要关注的是函数名、函数参数、函数返回值
- 有几个经常使用的库的头文件(stdio.h,math.h,ctype.h,stdlib.h)
自定义函数
语法
一般来说,不管是库函数还是自定义函数,函数的结构形式都是一样的:
returntype funcname(type arg1,type arg2)
{
(……);
}
其中 returntype 说明返回值的类型,funcname 说明函数名,type 说明参数的类型,arg 说明参数名,…… 说明函数体。
声明和定义
- 函数定义在调用之前,函数调用会正常
- 函数定义在调用之后,函数调用会报错,此时需要进行前向声明
- 声明就是在函数调用之前对函数进行说明,一般是复制函数头到调用之前,声明后加分号
- 定义是构建整个函数,从函数头到函数体,以大括号结尾
- 函数调用中的参数为实参,可以是常量、变量或者是表达式
- 函数定义中的参数为形参,在未进行函数调用时,形参不占用内存;只有在进行函数调用时,形参才被分配内存;调用结束后,形参内存被释放
#include <stdio.h>
double find_max(double a,double b) //定义
{
return a>b?a:b;
}
int main()
{
double a = 1.0;
double b = 2.0;
double max_value = find_max(a,b);
printf("The max value is %f\n",max_value);
return 0;
}
#include <stdio.h>
double find_max(double a, double b); //声明
int main()
{
double a = 1.0;
double b = 2.0;
double max_value = find_max(a,b);
printf("The max value is %f\n",max_value);
return 0;
}
double find_max(double a,double b) //定义
{
return a>b?a:b;
}
#include <stdio.h>
int main()
{
double a = 1.0;
double b = 2.0;
double max_value = find_max(a,b);
printf("The max value is %f\n",max_value);
return 0;
}
double find_max(double a,double b) //定义
{
return a>b?a:b;
}
上边三种函数声明和定义情况,只有前两种是正确的,最后一种编译会报错。
指针传递
在函数体内部,如果要使用并修改函数调用外的变量,一般需要用指针的方式进行参数传递。可以看下边的程序:
#include <stdio.h>
void swap_value(double a,double b)
{
double tmp;
tmp = a;
a = b;
b = tmp;
printf("a = %f\tb = %f\n",a,b);
}
void swap_pointer(double *a,double *b)
{
double tmp;
tmp = *a;
*a = *b;
*b = tmp;
printf("a = %f\tb = %f\n",*a,*b);
}
int main()
{
double a = 1.0;
double b = 2.0;
swap_value(a,b);
printf("a = %f\tb = %f\n",a,b);
swap_pointer(&a,&b);
printf("a = %f\tb = %f\n",a,b);
return 0;
}
结果为:
a = 2.000000 b = 1.000000
a = 1.000000 b = 2.000000
a = 2.000000 b = 1.000000
a = 2.000000 b = 1.000000
从上边的结果我们可以看出,对函数传递数值,虽然在函数内部实现了数值交换,但是在函数调用外的参数值并没有发生改变。
只有传递指针,利用指针访问内存的特点,改写地址指向的变量值才能够实现真正的变量修改。
数组传递
之前我们提到过数组有三个要素,起始位置,单个元素的字节长,范围。在将数组当作实参进行传递的时候,也要将数组的全部要素进行传递。我们可以看下边的程序:
#include <stdio.h>
void show1(int array[])
{
printf("sizeof(array) = %d\n",sizeof(array));
for (int i = 0; i < 10; i++)
printf("array[%d] = %d\n",i,array[i]);
}
void show2(int array[10])
{
printf("sizeof(array) = %d\n",sizeof(array));
for (int i = 0; i < 10; i++)
printf("array[%d] = %d\n",i,array[i]);
}
void show3(int array[],int n)
{
printf("sizeof(array) = %d\n",sizeof(array));
for (int i = 0; i < n; i++)
printf("array[%d] = %d\n",i,array[i]);
}
void show4(int *array,int n)
{
printf("sizeof(array) = %d\n",sizeof(array));
for (int i = 0; i < n; i++)
printf("array[%d] = %d\n",i,array[i]);
}
int main()
{
int array[10] = {1,2,3,4,5,6,7,8,9,10};
printf("sizeof(array) = %d\n",sizeof(array));
for (int i = 0; i < 10; i++)
printf("array[%d] = %d\n",i,array[i]);
printf("\\***************************\\\n");
show1(array);
printf("\\***************************\\\n");
show2(array);
printf("\\***************************\\\n");
show3(array,10);
printf("\\***************************\\\n");
show4(array,10);
printf("\\***************************\\\n");
return 0;
}
结果为:
sizeof(array) = 40
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 6
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 10
\***************************\
sizeof(array) = 4
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 6
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 10
\***************************\
sizeof(array) = 4
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 6
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 10
\***************************\
sizeof(array) = 4
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 6
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 10
\***************************\
sizeof(array) = 4
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 6
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 10
\***************************\
从上面的结果可以看出:
- 只有在 main 函数中的 sizeof(array) = 40,而几个 show 函数中的 sizeof(array) = 4,这表明数组名作为实参进行传递的时候只是作为了一个指针,并不具有数组的性质
- 在各个函数调用时,不管形参的形式是 array[],array[10] 还是 *array,都没有出错,这又证明了用数组名传递时只是传递了一个指针
- 也就是说,array[10] 中的 10 并没有意义,因为 sizeof(array) = 4
- 使用时并不建议 show1 和 show2 的形式,因为不知道数组的长度,这样的函数不具有移植性,可以采用 show3 和 show4 的形式
- 在各个函数调用中,array[i] 都能够访问到数组元素是因为 array[i] == *(array + i)
函数调用
普通调用
所有函数都是平行的,在函数调用时分别调用各自对应的函数,函数相互独立,各函数之间也可以进行互相调用。比较常见的有平行调用、嵌套调用等。
递归调用
函数递归即函数对自身进行调用,就好像是锁链一样,一环套一环。递归的结构一般为:
returntype func(递归变量)
{
if (递归终止条件)
终止处理
else
func(递归趋于终止的条件)
}
比如可以利用函数递归实现阶乘:
#include <stdio.h>
int factorial(int n)
{
if (n == 0)
return 1;
else
return n * factorial(n-1);
}
int main()
{
int n = 10;
printf("fac = %d\n",factorial(n));
return 0;
}
由递归的过程我们能够看出,递归的过程好像和循环有点相似,上面的程序同样也可以利用循环实现。
- 可以认为循环和递归的逻辑是相通的
- 递归的写法比较简洁,但简洁性的代价意味着你可能要花费很多时间才能够理解包含递归代码段的代码
- 递归对于内存消耗比较大,因为每一次的递归变量都需要在栈内保存,可能会导致栈溢出
- 一般情况下,能用循环解决的问题,尽量不要采用递归的方法