【C语言】函数

目录

一、什么是函数

二、库函数

2.1、标准库、库函数、头文件的概念       

2.2、库函数查阅工具

三、自定义函数

3.1、函数的语法形式

3.2、形参和实参

3.3、函数调用操作符

3.4、return语句

3.5、数组做函数的参数

3.6、函数的嵌套调用和链式访问

3.7、函数的声明和定义

四、多个文件

五、static和extern

5.1、作用域和生命周期

(1)作用域

(2)生命周期

5.2、extern

5.3、static

(1)static修饰局部变量

(2)static修饰全局变量和函数


一、什么是函数

        函数是执行某个特定任务的一小段代码,又叫做子程序。一个大的计算机任务可以分成很多个函数。一个拥有特定功能的函数能够重复利用,减少代码量和代码冗杂,提高软件开发的效率。

        函数分为库函数和自定义函数。

二、库函数

2.1、标准库、库函数、头文件的概念       

        C语言的国际标准ANSI C规定了一些常用函数的标准(包括功能、参数、返回值等),这些函数的标准就叫做标准库。编译器厂商根据这些标准实现函数,这些函数就叫做库函数

        程序员能直接使用库函数,而不必浪费时间精力再写有这些常用功能的函数;编译器厂商对于函数的实现不断优化更新,专业的人做专业的事,也比程序员自己重写的函数执行效率高、质量高。

        这些库函数根据功能划分,声明在不同的头文件中。库函数相关的头文件介绍:C 标准库头文件 - cppreference.com。不用一次性把库函数全部看懂,在今后需要用某个库函数时,再查看文档,经常用就熟悉了。

        注意,<Window.h>不是C标准库的头文件,而是Windows操作系统的头文件。

2.2、库函数查阅工具

        C\C++官方:C 标准库头文件 - cppreference.com

        cplusplus.com:C library - C++ Reference (cplusplus.com)

        官方的网站是中文的,容易阅读,但是搜索功能是谷歌的,搜索不了。cplusplus.com是英文的,但能搜索,转换成旧版本就行。

        以sqrt函数为例:

三、自定义函数

        程序员应该聚焦在自定义函数上,发挥创造性编写有特殊功能、库函数中没有的函数。

3.1、函数的语法形式

函数类型 函数名(参数){
   ......
}
  • 函数类型:表示函数的计算结果的类型。可以是void,表示什么也不返回。
  • 函数名:为了一看到名字就知道是什么功能,尽量起有意义的名字。
  • 参数:()里的是函数的参数。若没有参数,可以写void,也可以什么也不写;若有参数,需要写明参数的类型和名字。参数的数量可以是:0~很多个。
  • 函数体:{}里的就是函数体,是完成计算的过程。

        函数根据传入的参数,执行函数体,最后给出计算结果。如果函数定义时没有给出函数的返回值类型,则隐含类型是int

3.2、形参和实参

        实参是函数调用时,传给函数的实际参数。形参是函数定义时写在()里的形式参数。形参和实参是一一对应的。

        形参在函数调用时,为了存储实参传递过来的值,才申请内存空间的过程,叫做形参的实例化。形参实际上是实参的一份临时的拷贝

        为什么这么说呢?举个例子:

        在函数调用前,形参是不存在的。

        进入函数后,形参才被分配了内存空间,可以看到x与a、y与b的地址是不一样的,但是存储的内容相同,所以说形参是实参的一份拷贝。

        函数返回计算结果后,可以看到形参的内存被回收了,所以说这份拷贝是临时的。

        最后,形参和实参的命名是可以相同的。

3.3、函数调用操作符

        在上面的例子中,我们看到了函数调用的方式,()就是函数调用操作符,Add、a、b都是它的操作数。同样对于printf("哈哈!")库函数,printf 和 "哈哈!" 是()的操作数。

3.4、return语句

  • return语句执行后,函数会立马返回。
  •  return语句后面可以是一个值,也可以是一个表达式。如果是表达式,则先执行表达式,再执行return。
  •  对于返回值是void类型的函数,当需要提前返回时,则可以写return;。注意,break只能在循环和switch语句中使用。
  •  如果返回值和函数的返回类型不匹配时,会将返回值强制转换成函数的返回值类型。
  •  如果函数中有分支语句,则需要保证每种情况的都有return返回,否则编译器会报警告。

        被调用函数的返回值会返回给调用的函数,mian函数的返回值是返回给系统指定的一个函数。

3.5、数组做函数的参数

  • 函数的实参是数组,形参也可以写成数组的形式(但实际上是数组的地址)。如果是一维数组,可以省略数组的大小;如果是二维数组,可以省略行的大小。
  • 数组传参,形参不会创建新的数组,而是跟实参操作同一个数组。

        看下面的例子,单独写一个函数打印数组:

        可以看到形参和实参的地址是相同的。因为形参没有创建新的数组,所以形参不需要标明数组的大小(申请内存空间才需要知道要多大)。

        又因为实参传给形参的是数组的地址,sizeof函数是无法根据数组的地址(a数据类型是int*)来计算数组的大小(arr的数据类型是int[10])的,所以数组的大小必须通过参数传入,而不能在定义的函数里计算。

        验证一下,用sizeof计算形参a的大小,将会是指针类型的大小:

        数组的大小:一个整型4字节,10个整型40字节。

        数组指针的大小:本质是指针类型,在32位系统下为4字节,在64位系统下为8字节。

3.6、函数的嵌套调用和链式访问

        函数的嵌套调用就是函数之间互相调用。链式访问就是一个函数的返回值作为另一个函数的实参。

        从最里层开始,打印43,返回字符数2;打印2,返回字符数1;打印1。

3.7、函数的声明和定义

        函数应该先声明再使用。如果函数的定义在函数的调用之后,则必须在函数调用之前声明函数:

        如果没有函数声明,虽然也能运行正确,但是编译器会给出警告,需要改正:

        当然,如果把函数的定义写在函数调用之前,函数的定义本身是一种特殊的函数声明。注意,函数的声明只要是在调用的前面就行,如下的位置也可以:

四、多个文件

        当代码过多时,会根据程序的功能拆分到多个文件中。函数声明、类型声明放到头文件中(.h),函数的定义放到源文件中(.c)。如下例子:

        这样写代码的好处:

  • 方便多人协作。(若一个项目有很多个工程师分工合作,如果不拆分成很多个文件,那么等第一个人写完了,第二个人再去写,这样也就太慢了,不现实。)
  • 模块化,让代码可以复用。
  • 在一定程度上对源码进行隐藏。

        接下来讲讲,为什么能对源代码进行隐藏。如果A公司开发了一套游戏引擎,卖给B公司10W一年,那么A肯定不愿意把源码卖给他,因为卖了源码B公司就不会再来买了。因此需要把源码隐藏起来,只给头文件,而不给源代码,像这样做:

        上图是要卖的项目,右击Add项目 >> 属性 >> 常规 >> 配置类型 >> 改为静态库(.lib):

        确定后再次生成解决方案,生成静态库文件:

        将静态库文件打开,会是乱码,因为文件是二进制的(实现了隐藏):

        现在A公司只需把Add.h和Add.lib两个文件卖给B公司,下面是B公司的项目:

        有些编译环境默认不支持多文件编译的,比如VS Code,想要支持还得改配置文件。但是VS是支持多文件编译的,这是它的优势。

五、static和extern

5.1、作用域和生命周期

(1)作用域

        代码中的名字并不是在每个地方都是可用的,这个名字可用的代码范围就叫做它的作用域。        

  • 局部变量的作用域是变量所在的局部范围。
  • 全局变量的作用域是整个项目。

(2)生命周期

        变量的创建(申请内存空间)到销毁(回收内存空间)之间的这段时间。

  • 局部变量的生命周期是进入作用域创建,生命周期开始,出作用域生命周期结束。
  • 全局变量的生命周期是整个程序的生命周期。

5.2、extern

        extern是用来声明外部符号的。A文件定义的全局变量/函数,可以被B文件使用,但需要用extern声明。如下图代码验证:

5.3、static

(1)static修饰局部变量

        static修饰局部变量,会将它的生命周期延长至跟程序的生命周期一样。如下图代码验证:

        在5次循环中,每进入func函数,a都重新初始化为2并加1得3,返回函数后收回a的内存空间;b的内存空间在编译阶段就已经申请了,所以在执行代码的时候不会执行声明静态局部变量这一行(反汇编中没有翻译静态局部变量的汇编语句、调试逐行执行语句可以验证),它的内存空间到程序执行完毕才回收,因此每进入一次func函数就会累积加1。

        在之前的文章(变量的分类部分:http://t.csdnimg.cn/cnzG1)中讲过,内存大概分为三个部分:栈区(存放局部变量、形参)、堆区(动态内存管理malloc、calloc、realloc、free)、静态区(全局变量、静态变量)。实际上static修饰局部变量的本质,是改变了局部变量的存储类型,从存储在栈区,改为了存储在静态区。

        想局部变量出了作用域还存在,就用static修饰。

(2)static修饰全局变量和函数

        static修饰全局变量/函数,会将其外部链接属性改为内部链接属性。尽管B文件中使用extern声明了外部A文件的全局变量/函数,也不能使用A文件中被static修饰的全局变量/函数了,因为它们只能在文件内部使用。如下图代码验证:

        想全局变量和函数只在本源文件使用,可以用static修饰。

        最后提醒一下,如果自定义头文件"add.h"的内容如下:

int add(int a, int b);

        在调用函数前,声明函数 int add(int a, int b); 和使用 #include "add.h"的作用是一样的,最终编译器会把#include "add.h"替换成具体的声明内容。

  • 46
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值