思维导图:
目录
函数是什么
维基百科中对函数的定义是:子程序
- 在计算机科学中,子程序是一个大型程序中的某部分代码,有一个或多个语句块组成它负责某项特定的任务,而且相较于其他代码,具有相对的独立性
- 一般会有输入参数并有返回值提供过程的分装和细节的隐藏,这些代码通常被集成软件库
C语言中的函数分类
库函数
C语言初期,只有一些简单的语句,并没有像 printf,scanf 等这样的函数,但是在发展的过程中发现一些函数被频繁、大量的使用,于是C语言提供了这样的函数,这大大提高了写代码的效率
在这个C语言库中有许多头文件。而在每个头文件下面有就有许多库函数,就可以阅读关于库函数的一些信息
库函数的分类:
- IO函数(input output 输入输出函数)
- 字符串操作函数(操作字符串的函数,如strlen)
- 字符操作函数
- 内存操作函数(memset)
- 时间/日期函数(time函数)
- 数学函数(sqrt开平方函数)
- 其他库函数
学习几个函数:
strcpy--string copy
参考 strcpy的用法
是将arr1中字符串(包括 \0 ) 复制,并将arr2中的前4个 # 替代,因为 \ 0 也被复制过去,所以后面的 # 不会被打印
memset--memory set
参考memset的用法:
要设置 value 的值 '*'( *传参的值是ascll码值是整型,所以不用定义),作为 int 传送给 ptr 指向内存块“hello world”的前 5 个字节作为定义,所以结果为 ***** world,这里要注意 num 的值不能大于字符串的长度,否则会溢出
学习库函数我们可以使用的一些工具:
MSDN(Microsoft Developer Network)
http://en.cppreference.com
www.cplusplus.com
自定义函数
是我们可以自己来设置的有函数名、返回值类型和函数参数的函数
函数的组成:
举一个例子:写一个函数比较两个函数的较大值
这里我们定义了一个函数 get_max
写一个函数交换两个整型变量的内容:
我们可以学习一下这个指针,再写这个函数:
对于 swap1,a,b和 x,y 的地址不同 ,都有自己的独立空间,所以当swap1里的 x,y的值互换 以后回到主函数后,a,b的值并没有改变,所以不能完成
- 所以我们要想办法让swap中的值与swap外的值建立联系
- 在这之前,我们学习这个指针的用法
在主函数中,我们直接将 a,b 的地址传过去,在sawp2中,我们用指针变量接收,然后借助 tmp 将 a,b 的值进行交换,就可以完成两个整型变量的交换
函数的参数
实际参数
真实传给函数的参数。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,他们都有确定的值以便把值传递给形参
形式参数
形参是指函数名括号中的变量,因此形参只有在函数被调用的过程中才能实例化(分配内存单元)。形参当函数调用完后自动销毁。
当实参传给形参的时候,形参其实是实参的一份临时拷贝,对形参的改变不会改变实参
函数的调用
传值调用
函数的形参和实参占有不同的内存块,对形参的改变不会影响实参
传址调用
- 是把函数外部创建变量的内存地址传递给函数的一种调用函数的方式
- 这种传参方式可以让函数和函数外边的变量建立真正的联系,也就是函数内部可以直接操作函数外部的变量
练习:
练习:写一个函数,每调用一次这个函数,num+1
void Add(int*p)
{
(*p)++;
}
int main()
{
int num=0;
Add(&num);
printf("num=%d\n",num);
Add(&num);
printf("num=%d\n",num);
Add(&num);
printf("num=%d\n",num);
return 0;
}
函数的嵌套调用和链式访问
函数和函数之间是可以有机组合
嵌套调用
将一个函数调进另一个函数
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i=0;
for(i=0;i<3;i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
链式访问
把一个函数的返回值作为另一个函数的参数
- 这个函数的打印逻辑是由内往外
- printf 的返回值是字符的个数
- 所以打印的结果是 “4 3 2 1”
函数的声明和定义
函数的声明
- 告诉编译器有一个函数叫什么,参数是什么,返回值类型是什么,但不具体存在
- 函数声明一般出现在函数使用之前,要先声明在使用
- 一般放在头文件里
这样写是对的,但是一般不这样写,可以直接把 int Add 直接放在 int main 前面,但是它既然存在就有它存在的意义
- 把函数声明放在头文件 add.h,函数定义放在add.c
- add.h 和 add.c 共同 形成一个加法
- 将来在 使用他的时候,只需要用 #include "add.h" 引用一下就行
这样的写法适用于完成一个复杂的应用,分模块来完成
函数的定义
函数的具体实现,交代函数的功能
函数的基本使用和递归
函数的递归
程序调用自身的编程技巧。一个过程或函数在其定义或说明中直接或间接调用自身的方法。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。只需要少量的程序就可以描述出解题过程中所需要的多次重复计算
写一个最简单的递归
死循环打印 hehe ,但是程序走着走着挂了
- 每次调用这个 main 函数都会从栈区开辟一部分空间,当空间被耗干就会出现栈溢出
下面写几个递归函数:
接受一个整型 1234,按顺序打印他的每一位1、2、3、4
思路:1234%10 得到 4,/10 得到 123,再%10 得到 3,再/10 得到 12,再%10得到 2,再 /10 得到1,再把4、3、2、1存起来,再按1、2、3、4打印出来
递归 :
print(num)
print(1234)
print(123) 4
print(12) 34
print(1) 234
可以分为两部分打印,每次从num中拆下来一位
写出代码:
- 当 num>9时,这个函数就进行上面思路
让num=123,进行这个思路,黑线是 递 ,红线是 归 。
与前面那个最简单的递归函数比较,这个递归函数没有栈溢出是因为它有 n>9 这个限制条件,所以递归有两个必要条件
递归的两个必要条件
- 存在限制条件,当满足这个条件时,递归便不在进行
- 每次递归后越来越接近这个限制条件
练习二:编写函数不允许创建临时变量,求字符串长度
思路:
my_strlen("bit")判断第一个元素不是\0
1+my_strlen("it")判断第一个元素不是\0
1+1+my_strlen("t")判断第一个元素不是\0
1+1+1+my_strlen("")判断第一个元素是\0
1+1+1+0=3
写出代码:
同样的可以根据这个思路进行递归
递归与迭代
递归:函数自己调自己
迭代:重复去做一件事情,与循环相似
练习:写 n 的阶乘
思路:
所以,我们既可以用循环来求,也可以用递归来解决
练习:斐波那契数
斐波那契数列:1、1、2、3、5、8、13、21、34、55······
思路:
写出代码:
当我们写出这个代码可以很好地计算斐波那契数,但是当 n 比较大时(n>50),运算比较慢
这是因为当我们求第50个时,需要求出第49、48个,但是第49、48又不是现成的,又要求48、47、46,所以就会产生许多重复
并且我们用 count 证实,确实重复很多次
所以我们可以用迭代(循环)的方式来解决
提示:
- 许多问题是以递归的形式来解释,这是因为它比非递归形式更清晰
- 但是这些问题的迭代往往效率更高,虽然代码可读性稍差
- 当一个问题复杂,难以用迭代实现时,此时递归简洁性就可以得到应用
可以自主研究两个经典递归问题:
1.汉诺塔问题
2.青蛙跳台问题
如果有什么问题请指出!!!