ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C语言——函数

C语言——函数

一、函数的定义:

C语言中的函数就是实现一个完成某项特定的任务的一小段代码。这一段代码有着特殊的写法与调用方法。

在C语言中我们一般会见到两种函数

a、库函数

b、自定义函数

(1)库函数:
C语言标准中规定了C语言的各种风格语法标准,C语言不提供库函数;C语言的国际标准ANSI规定了一些常用的函数的标准,那不同的编译器厂商提供ANSI提供的C语言标准就给出一系列函数的实现,这些函数就称之为库函数。

(2)自定义函数:
理解了库函数之后,我们更多的关注就聚焦在自定义函数之上,自定义函数更加的重要,可以为程序员写代码创造更多的价值。

自定义函数的形式如下:

接下来我们举出一个例子:我们在这里写一个加法函数:

a、实际参数:在上面的代码中,上面的哪一行就是Add函数的定义,有了函数之后,在下面调用Add函数传递给函数的参数a与b,称之为实际参数,简称为形参,实际参数就是真实传递给函数的参数。

b、形式参数:在上面的代码中,上面代码定义函数的时候,在函数名Add后的括号中写的是x与y,称之为形式参数,为啥叫做形式参数?实际上,如果我们仅仅只是定义Add函数而不去调用的话,Add函数的参数x与y只是形式上存在的,不会在内存中申请空间,不会真实存在的,所以叫做形式参数。形式参数只有在函数被调用的过程中为了存放实际参数传递过来的值,才会向内存申请空间,这个过程就叫做形参的实例化。

其实变量在创建的时候是要在内存中创建空间的,形式参数与实际参数是不同的内存空间,当实际参数传递给形式参数的时候,形式参数会创建自己的空间来保存实参,对形式参数的修改不会影响实际参数。

二、return语句(函数中有可能有return语句,也有可能没有)

在函数的设计中,函数经常会出现return语句,下面就是return语句的注意事项:

(1)return后面可能是一个数值或者是表达式,如果是表达式,则会先执行表达式再返回。

下面就是对应的例子:

(2)return后面可以啥都没有,直接写return即可(在一个函数执行过程中需要提前返回,但是不需要带回任何值,就需要提前返回),这种情况适用于返回类型是void的情况。

下面就是对应的例子:

在上述的例子中,当我们执行到retrurn的时候,函数就会立即结束,说明这个函数不会返回值,所以return的意思就是提前结束函数。

(3)return语句执行之后,后面的代码就不再执行了。

这段函数在调用结束之后运行的结果为hello,但是后面的world就不会打印了,所以这里你可以将return理解为这个函数到这里就结束了。

(4)return返回的值与函数返回的类型不一致,系统就会自动将返回的值隐式转换为函数的返回类型,所以函数的返回类型与返回的值的类型要一致。

这里函数要求返回的是int,但是3.14是double,所以编译器就是进行类型转换,所以这里3.14转换为int类型就会变为3,所以最终的返回类型就是3,所以这里我们最好遵循:返回的是啥类型的数据,函数前面就尽量写对应的返回类型。

(5)如果函数中有if等分支语句,就要保证每一种情况下都有return返回,否则就会有编译错误。

例如上述的这个写法就是有问题的,但是如果x>0,确实就会返回1;但是如果x<=0,程序此时就走不到任何return,所以正确的写法是:

这样无论是大于0还是小于0,此时函数都一定会有返回值。

(6)函数的返回类型如果不写的话,编译器会默认函数的返回类型是int

对于上述的代码,老式C中可能会默认当做int,但是在现代C中应该明确写上int,否则现代编译器通常会警告甚至报错,所以这一条可以理解为这是历史上的就规矩,但是写代码的时候一定要明确的写上返回类型。

(7)函数写了返回类型,但是函数中没有使用return返回值,那么函数的返回值就是未知的。

三、数组做函数参数:

在使用函数解决问题的时候,难免会将数组作为参数传递给函数,在函数的内部对数组进行操作。

变量、数组、函数等名字,都叫做标识符。

标识符的命名规则:
a、可以是数字+字母+下划线组成

b、但是不可以以数字开头

下面的代码就是对于数组传参的简单运用:

数组在作为参数进行传递的时候,上面的两个函数是如何设计的?
这里我们需要知道数组传参的几个重要的知识点:

首先我们需要记住一个核心点:数组传参的时候,数组名通常会转换为指向首元素的指针。因此,函数形参虽然写成数组形式,但它实际上接受的是指针,而不是创建一个新的数组。

(1)函数的形式参数要与函数的实参个数相互匹配。

这里定义了两个形参,a与b,调用的时候传入两个实参,因此,调用时实参的数量必须与函数定义的形参数量是相互匹配的。这里讨论的是普通的、具有原型是声明的函数调用。

(2)函数的实参是数组,形参也是可以写成数组形式的。

这里a是一个实参,它是一个数组,int arr[]是形参,它采用数组形式书写,调用时test(a)会将数组表达式a转换为指向首元素的指针,传递给函数,下面需要注意:

也就是说,虽然写的是arr[],但是函数形参实际上是指针,不是一个新的数组。

(3)形参如果是一维数组,数组的大小是可以省略不写的。

这三种写法在普通函数形参中,调整后的类型是int*。

(4)形参如果是二维数组的话,此时行是可以省略的,但是列是不可以省略的。

下面的这三种写法就是正确的方式:

这三种写法在普通函数形参中,调整之后的类型都是int(*)[5],也就是指向含有5个int元素的数组的指针。

(5)形参操作的数组与实参操作的数组是同一个数组。

四、嵌套调用与链式访问:

(1)嵌套调用:函数中调用其他的函数

下面我们举一个经典的例子,计算某年某月有多少天?

上面的代码就是一个经典的函数嵌套,主要的任务就是返回这一年的这一个月有多少天。

(2)链式访问:所谓的链式访问其实就是将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来进行访问

上面的这一段代码就是对于链式访问的实际运用:

这里还有一个比较有趣的代码:

在对于这段代码得物返回值进行猜测之前,我们首先需要知道printf函数返回值是啥?这里我们需要补充的是printf返回的类型是本次成功输出的字符个数,不包括字符串结束符\0

所以首先最里面打印43,这是两个字符,所以第二层返回的就是字符的个数2,最后最外层的就是返回1,所以最终的打印结果就是4321。

五、函数的声明与定义:

(1)单个文件:

注意:函数的使用一般需要满足先声明再使用。

(2)多个文件:

一般在企业中我们写代码的时候,代码可能是比较多的,不会将所有的代码都写在一个文件中,我们往往会根据程序的功能,将代码拆分在多个文件中。

头文件一般会防止函数的声明或者类型的声明,函数的定义会放置在.c文件中。

六、static与extern:

这二者都是C语言中的关键字:

在了解这两个关键字的时候,我们首先需要了解一下作用域与生命周期:

a、作用域:是程序的设计概念,通常来说,一段程序代码中所用的名字并不是有效的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

b、生命周期:指的是变量的创建(申请空间)到变量的销毁(收回内存)之间的一个时间段

1、局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束。

2、全局变量的生命周期是:整个程序的生命周期。

程序的生命周期==main函数的生命周期。

(1)static是静态的意思,可以用来:

a、修饰局部变量:

这里运行结果就是三个1,因为每一次调用test函数的时候吗,普通局部变量a都会初始化为0,然后执行a++,所以每次输出1。

下面将int改为static:

此时我们就会发现运行的结果为1 2 3,为什么这里结果会变,因为static修饰的局部变量具有静态存储期:整个程序运行期间,它一直存在,而且只初始化一次。

结论:static此时局部变量的时候改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储的内存中的栈区的,但是被static修饰之后存储在静态区,存储在静态区的变量与全局变量是一致的,生命周期与程序的生命周期是一样的,只有程序结束之后,内存才会回收,但是作用域是不变的。

我们就以上面这张图作为理解即可。

b、修饰全局变量

一个全局变量被static修饰,使得这个全局变量只可以在本源文件中使用,本质原因在于全局变量默认是具有外部链接属性的,在外部的文件中想要使用只要适当的声明即可,但是当全局变量被static修饰之后,外部链接属性就变为了内部链接属性,只能在自己的源文件内部进行使用,其他源文件即使声明了也是无法正常使用的。

c、修饰函数

使用建议:未来一个变量出了函数之后,我们还想继续保留值,等下一次进入函数继续使用,就可以使用static进行修饰。

(2)extern是用来声明外部符号的:

我们可以将extern理解为一句话:extern的作用是“声明一个变量或者函数在别的地方已经有定义了,我这里只是拿来用”,它最常用的方式即使在.c文件之间共享全局变量或者函数。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进