一、函数是什么
- 函数的定义:子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。
二、为什么会有库函数:
- 我们最开始时候就接触库函数,按照格式打印文本到屏幕上(printf)
- 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)
- 计算n的k次方这样的运算(pow)
---- 类似上述,它们不是业务性的代码,我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。
库函数搜索网址:http://www.cplusplus.com/reference/clibrary/?kw=clibrary
三、函数的调用:传值调用和传址调用
传值调用
- 函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量。
举一个经典例子:写一个函数可以交换两个整形变量的内容。
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
}
void Swap2(int *px, int *py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int num1 = 1;
int num2 = 2;
Swap1(num1, num2);
printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
Swap2(&num1, &num2);
printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
return 0;
}
运行后会发现 Swap1 没有交换成功,而 Swap2 交换成功了。简单地说 Swap1 这种方式是将数据拷贝了一份到另外一个变量空间,Swap2 这种方式就是通过地址的方式对原变量空间进行操作。
四、嵌套调用和链式访问的概念
- 函数的嵌套调用:函数和函数之间可以有机的组合。意思就是一个函数体中可以调用另外一个函数。
- 链式访问:把一个函数的返回值作为另外一个函数的参数。
printf("%d", printf("%d", printf("%d", 43)));
五、函数的声明和定义
函数声明:
-
告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关紧要
-
函数的声明一般出现在函数的使用之前。要满足先声明后使用
-
函数的声明一般要放在头文件中的
函数定义:
- 函数的定义是指函数的具体实现,交待函数的功能实现。
六 、函数递归
什么是递归:
- 指的是在函数定义中使用函数自身的方法。(A调用A)函数自调用,递归的主要思考方式在于:把大事化小。
递归的两个必要条件:
-
存在终止条件,一旦满足递归便不再继续
-
递归通式
注意:尽管满足如上也不一定能有结果如果递归的程度过深,将造成栈溢出
练习1(试手):
接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4.
参考代码:
#include <stdio.h>
void print(int n)
{
if (n>9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 1234;
print(num);
return 0;
}
练习2:
编写函数不允许创建临时变量,求字符串的长度。参考代码:
#include <stdio.h>
int Strlen(char* str)
{
if (*str != '\0')
return 1 + Strlen(str + 1);
else
return 0;
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
七、递归与迭代
上述介绍了递归,迭代指重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B)。
附,循环(迭代)和递归的区别:https://blog.youkuaiyun.com/tz2101/article/details/49736009
练习3:
求n的阶乘。(不考虑溢出) 参考代码:
#include <stdio.h>
int Facl(int n){
if (n == 0)
return 1;
else
return n*Facl(n - 1);
}
int main(){
printf("%d\n",Facl(3));
return 0;
}
练习4:
求第n个斐波那契数。(不考虑溢出) 参考代码:
// 1 1 2 3 5 8 13 21……
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
但是我们发现有问题!
在使用fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间,数分钟乃至数十分钟之久,更别说计算更大的数。
为什么呢?
我们发现 fib 函数在调用的过程中很多计算其实在一直重复。 如果我们把代码修改一下:
int count = 0;//全局变量
int fib(int n)
{
if(n == 3)
count++;
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
最后我们输出看看count,是一个很大很大的值。那我们如何改进呢?
同时在调试 factorial 函数求阶乘的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出) 这样的信息。 系统分配给程序的栈空间是有限的
那如何解决上述的问题:
当递归不行的时候可以试想下使用循环(迭代)
//求n的阶乘
int factorial(int n)
{
int result = 1;
while (n > 1)
{
result *= n ;
n -= 1;
}
return result;
}
//求第n个斐波那契数
int fib(int n)
{
int first_num;
int second_num;
int result;
result = first_num = second_num = 1;
while (n > 2)
{
n -= 1;
result = first_num + second_num;
first_num = second_num;
second_num = result;
}
return result;
}
提示:
- 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
- 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
函数递归的经典题目:
- 汉诺塔问题
- 青蛙跳台阶问题
提示:本文为我个人学习笔记,皆纯手爪书写,如有错误请指教,转载请注明出处。