深入了解函数

发布于:2022-11-07 ⋅ 阅读:(831) ⋅ 点赞:(0)

目录

1.函数是什么

2.C语言中函数的分类:

2.1库函数:

2.2自定义函数

3.函数的参数

3.1传值调用

3.2传址调用

4.函数的嵌套调用和链式访问

4.1嵌套调用

4.2链式访问

5 函数的声明和定义

5.1函数声明:

5.2函数定义:

6.函数递归

6.1什么是递归

6.2递归的两个必要条件

6.3递归与迭代


1.函数是什么

2.库函数

3.自定义函数

4.函数参数

5.函数调用

6.函数的嵌套调用和链式访问

7.函数的声明和定义

8.函数递归

1.函数是什么

维基百科中对函数的定义:子程序

● 在计算机科学中,子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。

● 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。

例:

比如strlen库函数我们都是直接调用使用这个库函数,并不知道它的实现方式和过程,这就对它提供了对过程的封装和细节的隐藏

返回类型就是调用完之后返回的值的类型。

2.C语言中函数的分类:

1.库函数

2.自定义函数

2.1库函数:

1.我们知道在我们学习C语言编程的时候,总是在一一个代码编写完成之 后迫不及待的想知道结果,想
把这个结果打印到我们的屏幕.上看看。这个时候我们会频繁的使用一个功能:将信息按照- -定的格
式打印到屏幕上(printf) 。
2.在编程的过程中我们会频繁的做- -些字符串的拷贝工作(strcpy) 。
3.在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,
为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数, 方便程序员
进行软件开发。

因此有了标准库 - 提供C语言的库函数。

C语言常用的库函数有:

● IO函数

● 字符串操作函数

● 字符操作函数

● 内存操作函数

● 时间/日期函数

● 数学函数

● 其他库函数

2.2自定义函数

因为库函数能干的事也是有限的,所以有了自定义函数。

自定义函数更加重要。

自定义函数和库函数一样,有函数名,返回值类型和函数参数。

但是不一样的是这些都是我们自己设计的。这给程序员带来了一个很大的发挥空间。

函数的组成:

函数参数一定要用小圆括号圈起来。

举一个栗子:

写一个加法函数

函数的常见样式有四种:

1.无参无返

2.无参有返

3.有参有返

4.有参无返

3.函数的参数

实际参数(实参):

真实传给函数的参数,叫实参、实叁可以是:常量、变量、表达式、函数等。

无论实参是何种类型的量,在进行函数调用时,它们必须有确定的值,以便把这些值传送给形参。

形式参数(形参):

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例(分配内存单元),所以叫形式参数。

形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

实参和形参,使用的不是同一空间

当实参传给形参的时候,形参其实是实参的一份临时拷贝,对形参的修改是不会改变实参的。

传址调用是把地址传过去的,可以通过地址找回来。

指针的用法

int main()

{

int a = 10;

//&取地址符号 - 把a的地址存在pa里

int* pa = &a; //pa指针变量

*pa = 20; //*解引用操作

//对pa解引用操作,通过p里面存的地址,找到它所指向的内容,所以这个*pa就是a

printf("%d",a);

return 0;

}

函数的调用:

3.1传值调用

函数的形参和实参分别占用不同内存块,对形参的修改不会影响实参。

3.2传址调用

•传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。

•这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

我们写函数的一个特点:先去写这个函数怎么用,再去写这个函数怎么去实现

4.函数的嵌套调用和链式访问

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

4.1嵌套调用

注:函数可以嵌套调用但是不能嵌套定义。

4.2链式访问

把一个函数的返回值作为另外一个函数的参数。

printf的返回值是打印在屏幕上字符总的个数。

2是第三个printf的返回值,1是第二个printf的返回值。

5 函数的声明和定义

5.1函数声明:

1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是具体是不是存在,函数声明决定不了。

2.函数的声明一般出现在函数的使用之前。要满足先声明后使用

3.函数的声明一般要放在头文件中的。

例子:

如果把函数定义在使用该函数的后面编译器就会出现一个警告。

虽然成功编译但是这里有一个警告!因为编译器扫描代码是从上到下扫描的,在还没扫描到add的定义时候就会报错,所以这时候就需要我们的函数声明。

函数的声明就是告诉编译器有一个叫add的函数存在,这样就不会报错了。

或者我们可以函数的定义写在使用之前。

上面那些写法只不过是在我们学习的时候这么用的,其实在一个项目里我们是把函数的声明和函数的实现分开写在两个不同的源文件里,如果我们想使用它,只需要包含该声明的源文件就行。

列如:#include "add.h",包含这个头文件

因为我们写的是一个加法函数所以叫加法模块,用来测试代码的叫测试模块。

后缀为.h的叫头文件用来存放函数的声明

两个.c文件一个是用来实现函数的,存放主函数main的.c文件是用来测试(调试)代码的。

为什么要有头文件:

模块化开发(分工)

单纯的使用源文件,组织项目结构的时候,项目越大越复杂的时候,维护成本会变得越来越高!

.h:头文件,组织项目结构的时候,减少大型项目的维护成本

.h基本都是要被多个源文件包含,可能有一个问题,头文件被重复包含的问题。

解决方案:

方法一:#pragma once //防止头文件被重复包含

注:定义变量和定义函数是差不多的。

5.2函数定义:

函数的定义是指函数的具体实现,交待函数的功能实现。

6.函数递归

6.1什么是递归

程序调用吱声的编程技巧称为递归(recursion)。

递归做为一种算法在程序设计语言中广发应用。一个过程或函数在其定义或说明中(实现过程)有直接或间接调用自身的。

一种方法,通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略

只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式在于:把大事化小

6.2递归的两个必要条件

存在限制条件,当满足这个限制条件的时候,递归便不再继续。

每次递归调用之后越来越接近这个限制条件。

6.2.1练习1:(画图讲解)

接收一个整形值(无符号),按照顺序打印它的每一位。

列如:

输入:1234,输入 1 2 3 4

首先num等于1234的时候,进去if判断num>9,因为num>9就证明它是两位数以上,让函数再调用它本身num/10这时候printf并没有立即执行,一直调用它本身直到num<9,这时候num才只有一位数才开始递归回去执行printf这一条语句从而开始打印1在打印2然后3最后4就可以全部输出在屏幕上。

递归:递 - 递推,归 - 回归。

6.2.2练习2:

编写函数不允许创建临时变量,求字符串的长度。

但是这种方法创建了临时变量

第二种方法使用递归

*str每次解引用拿出一个字符,如果不等于\0则+1当遇到\0的时候返回0,然后开始返回统计每次拿出字符+1的次数。这种方法满足我们没有创建临时变量

6.3递归与迭代

6.3.1练习1:

求n的阶乘。(不考虑溢出)

一个正整数的阶乘factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。1808年,基斯顿·卡曼引进这个表示法。

举个例子:

用!来表示阶乘

5的阶乘等于5! = 5 * 4 * 3 * 2 * 1 = 120

4的阶乘等于4! = 4 * 3 * 2 * 1 = 24

根据上面的例子我们可以推出n!的等于n! = n*(n -1)!

递归虽然好但是也有无法解决问题的时候,当一个数非常大的时候就会出现栈溢出,因为函数每次调用的时候都会在栈区开辟一块新的空间,当栈区满了就会出现栈溢出的请况。(至于什么是栈溢出等我把下一篇博客写了再说。)

迭代(循环)

其实迭代也是循环,一直重复做某件事

6.3.2练习2:

求第n个斐波那契数。(不考虑溢出)

斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

规律前两位数的和等于第三个数

为什么输入50没有显示?其实它还在计算,因为这个数非常的大需要花费时间,所以这时候就可以使用循环来解决这个问题

 

定义三个变量全部初始化为1,如果n小于2则执行return sum;返回1,当n大于2则进入循环,先让a+b的结果付给sum在把b付给a,最后把sum付给b直到n小于等于2循环结束。不管多大的数都可以立马得出结果

总结如何解决上述的问题:

1.将递归改写成非递归。

2.使用static对象替代局部对象(定义的局部变量)。在递归函数设计中,可以使用static对象代替局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放局部对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。(简单来说就是延长局部变量的生命周期,并且使它每次在调用函数开辟新的空间时候不被重新初始化)

提示:

1.许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。

2.但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。

3.当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到