1. 函数是什么?
在数学中我们就学过函数,都是你了解C语言中的函数码?维基百科对函数的定义:子程序。
简单来说,一个函数就是一个大型工程中的某部分代码,由一个或多个语句块组成,它负责完成某项特定任务,而且相对于其他代码,具有相对的独立性。
2. 函数的分类
函数可以分为库函数和自定义函数。
2.1 库函数
在使用C语言的过程中,有一些我们会频繁用到的功能,比如求字符串的长度,比如数学中的求开平方等等,为了简化代码,更方便软件开发,于是就有了库函数。我们可以在www.cplusplus,com网站中的C library中去了解到选择C语言有哪些库,库里面的库函数以及如何使用这些库函数。
C语言中常用的库函数都有:
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/时间函数
数学函数
其他函数
举例:memset函数
memset的语法结构是:void* memset (void* ptr , int value , size_t num)
这个函数的作用是把从ptr开始的num个字节的内容全部设置成特定的值value,value不一定是int类型的值,也可以是字符等其他类型的值。这个函数常用来替换字符串中的某一个字符。
在上面的代码中,我们使用memset将str的第三个字符开始的三个字符替换成了字符v。
2.2 自定义函数
库函数虽然提供了我们常用的功能,但是在写程序的时候更重要的还是自定义函数,这是由我们程序员自己创建的函数,自定义函数也有函数名、返回类型、函数参数等,最重要的时函数体,因为函数体的代码才是我们函数功能的实现。
3.函数的参数
3.1 实参
实参时真实传给函数的参数,可以是常量、变量、表达式、函数等,无论实参是何种类型的值,在函数调用的时候都必须有确定的值,以便把这些值传递给形参。
3.2 形参
形式参数是指函数定义中函数名后面的括号里的变量。形式参数只有在函数被调用的时候才实例化(开辟内存),而且形式参数在函数调用完毕后会自动销毁,因此形参只在函数范围内有效。
3.3 形参和实参的关系
当实参传递给形参的时候。形参是实参的一份临时拷贝,对形参的修改不影响实参。
形参和实参的名字可以相同也可以不同。
当需要修改参数时,需要传它的的地址,如果不用修改实参就传值。
数组传参是传数组名,不用加后面的方括号,数组传参本质上传过去的是数组首元素的的地址,而不是将整个数组都传过去,这样会函数会开辟很大的栈帧,浪费空间,所以形参中实际存的是一个指针变量,这就意味着在函数内部去使用sizeof(arr)/sizeof(arr[0])计算数组长度是不靠谱的。对于数组传参,我们只需要传数组名和数组长度就能访问到数组里所有的元素。
4. 函数调用
4.1 传值调用
把实参的值作为参数传给函数。这时候形参只是实参的一份临时拷贝,对形参的修改不会影响实参。
4.2传址调用
传址调用就是把函数外部创建的变量的内存地址传递给函数,这种传参方式可以让函数和函数外部的变量建立起真正的联系,也就是在函数内部可以直接操作函数外部的变量。
注意:函数中执行完return就调用完函数了,会直接返回在主函数,不再执行后面的代码。
函数功能要尽量单一,不要将多个功能写在一个函数内,要追求高内聚低耦合。
5.函数的嵌套调用和链式访问
5.1 嵌套调用
函数之间是可以相互调用的,这就是嵌套调用,但是不能嵌套定义,就是不能在一个函数内区定义另外一个函数
5.2 链式访问
就是把一个函数的返回值作为另一个函数的参数,链式访问的前提条件是有返回值。
6.函数的声明和定义
6.1 函数声明
返回类型 函数名 (函数参数);
函数的声明就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是具体存不存在函数声明决定不了,有时候会出现假声明的情况,即声明了,但是没有函数的具体实现(定义)。
函数的声明一般出现在函数使用之前,要满足先声明后使用。如果函数定义在main函数后面,程序运行的时候会按顺序扫描,当扫描到该函数时,因为前面没有出现这个函数的声明或定义,这时候编译器没有见过这个函数,所以会报一个警告。但是当我们把函数定义在main之前则不需要声明也能正常运行。
函数的声明一般放在头文件中,因为我们一般会把不同的模块放在不同的文件中,当包含放有函数声明的头文件时,会把头文件的内容拷贝过来,也就对函数进行了声明。
初学编程是可能会觉得把所有的代码写到一个文件中最方便,那是因为现阶段我们写的代码量很少,但是当我们写的工程代码量很大时,或者在公司里协作工作的时候,模块化设计不仅会让效率变高,而且把不同的功能按模块划分出来更有利于代码的阅读和维护。
6.2 函数的定义
函数的定义主要是指函数的实现,在这里交待了函数是如何实现我们想要实现的功能的。
7.函数递归
7.1 函数递归的含义
简单来说,函数递归就是函数自己调用自己,核心思想是把大事化小。递归可以大大减少函数的代码量。就拿这个问题举例:按顺序打印一个整数的每一位,比如1234.
这时候我们可能会无从下手,但是我们很了解另一个功能:打印一个整数的个位,这个时候我们只需要打印1234%10就可以实现。按照这个思路,我们假设我们写的函数是print(1234),这个函数的功能是打印整数的各位,这时候我们是不是可以想到可以先执行print(123)再来打印我们的
4...
通过上面的代码,我们就递归实现了打印整数的每一位。
7.2 函数递归的必要条件
函数递归的必要条件:
递归使用一定要有限制条件,当满足这个条件时递归便不再继续。没有限制条件的递归就是死递归。每一次函数调用都会在栈区申请空间,死递归会导致栈溢出最终程序崩溃。
每一次递归之后都要越来越接近限制条件。
要记住,递归既要递推,也要回归,而回归的条件就是限制条件,没有回归的递归是死递归。
7.3 递归与迭代
迭代的意思就是重复用,比如循环这种,循环是一种迭代,迭代却不只有循环。
对于有些问题,既可以用递归实现,也可以用迭代实现,一些问题适合用递归完成,比如上面的打印整数的每一位,而有些问题则适合迭代实现,比如求第n个斐波那契数,这个问题如果用递归的话会存在大量重复的计算,花费时间很多,效率太低,时间复杂度为2^n,而用迭代的话循环n次就可以得出结果。
递归不能四递归,且当递归层次太深的时候也会出现栈溢出的情况
递归实现求第n个斐波那契数
迭代实现第n个斐波那契数
关于递归还有几个经典问题,比如汉诺塔和青蛙跳台阶问题,这两个问题后期也会发文来说说解题思路。