C语言中的函数分为库函数和自定义函数,库函数顾名思义就是已经封装好了的现成的函数,我们直接调用就可以了,而自定义函数就需要我们自己去写出来。
自定义函数
函数的组成
ret_type fun_name(para1, *)
{
statement;//语句项
}
ret_type是返回类型
fun_name是函数名
para1是函数参数
这就是一个函数的基本的形式,其中如果ret_type是void类型的,不需要返回值,而其他类型的则需要有返回值。
下面举个例子来看看
//交换两个变量的值
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
void Swap2(int* x, int* y)
{
int tmp = 0;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap1(a, b);
Swap2(&a, &b);
return 0;
}
从上面的代码中我们可以看到,交换变量的值有两种写法,那么是否都是正确的呢?
答案是否定的,我们在主函数中传递的变量是实参,而函数中建立的参数是形参(形参是实参的一份临时拷贝),实参在内存空间开辟了一块空间后,函数中的形参又另开辟了一块空间,将实参的值进行了拷贝,所以如果将实参的两个变量传到函数中,并不能起到改变实参的作用;
而如果将两变量的地址传到函数中的话,函数开辟的指针变量则可以远程操纵实参的值,从而达到交换的目的,所以显然Swap2可以达到交换的目的,而Swap1则不行。
函数的调用
上面的例子就引出了函数调用的两种形式:
传值调用
函数的形参和实参分别占有不同的内存块,修改形参的值对实参不产生影响。
传址调用
·传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用方式;
·这种传参方式可以将函数与外部创建的变量建立真正的联系,从函数的内部就可以直接操纵函数外部的变量。
函数的声明和定义
1、函数的声明一般在函数使用之前,满足先声明后使用
2、函数的声明一般放在头文件中
函数的递归
在函数中有一种特殊的函数,那就是递归函数,那么什么是递归?
递归
程序调用自身的编程技巧称为递归( recursion)。递归的主要思考方式在
于:把大事化小
递归的两个必要条件
1、存在限制条件,当满足这个限制条件后,递归将不再进行;
2、每次递归都会逼近这个限制条件。
下面举个例子
//求字符串长度
int MyStrlen(const char* str)
{
assert(*str != NULL);
if (*str == '\0')
return 0;
else
return 1 + MyStrlen(str + 1);
}
上面的例子中,我们可以看出,限制条件就是当“ *str == ‘\0’ ”的时候,每次 else 递归后,指针都会往后面挪动一位,逐渐逼近 ‘\0’ ,最终完成调用返回字符串长度。
递归与迭代
可以说掌握了递归这种编程技巧,那么无论是从代码量或是从逻辑实现的复杂度上,都会大大的降低难度,但是递归不是万能的,有的情况下递归很方便,但是有的情况下迭代缺更好,比如下面这个例子
//求第n个斐波那契数
//递归
int Fib1(int n)
{
return n <= 2 ? 1 : Fib1(n - 1) + Fib1(n - 2);
}
//迭代
int Fib2(int n)
{
int fib1 = 1;
int fib2 = 1;
int fib3 = 0;
int i = 0;
if (n <= 2)
{
return 1;
}
else
{
for (i = 3; i <= n; i++)
{
fib3 = fib1 + fib2;
fib1 = fib2;
fib2 = fib3;
}
return fib2;
}
上面的代码是求第N个斐波那契数的递归和迭代的两种写法,其中递归的时间复杂度是O(2^n),随着N的增加,时间复杂度呈指数增长,而迭代的时间复杂度却只有O(n),仅仅是线性增长,所以我们在使用递归的时候,需要多方面考虑。