第九章 函数《C语言程序设计现代方法(第2版)》读书笔记_c语言程序设计现代方法 笔记

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

函数简单来说就是一连串语句,这些语句被组合在一起,并被指定了一个名字。虽然“函数”这个术语来自数学,但是C语言的函数不完全等同于数学函数。在C语言中,函数不一定要有参数,也不一定要计算数值。(在某些编程语言中,“函数”需要返回一个值,而“过程”不返回值;C语言没有这样的区别。)                                                                                               
函数是
C
程序的构建块。每个函数本质上是一个自带声明和语句的小程序。

9.1 函数的定义和调用 🚀

%g:表示%f%e中较短的输出宽度输出单,双精度实数。(是printf的一个输出格式类型)。

9.1.1 函数定义 🚀

函数体内声明的变量专属于此函数,其他函数不能对这些变量进行检查或修改。在
C89
中,变量声明必须出现在语句之前。在C99
中,变量声明和语句可以混在一起,只要变量在第一

次使用之前进行声明就行。(
C99
之前的有些编译器也允许声明和语句混合。)

9.1.2 函数调用 🚀

我们所做的工作就是把
printf
函数的返回值强制类型转换(
7.4
节)成
void
类型。(在
C
语言中,“强制转换成void
”是对“抛弃”的一种客气说法。)使用
(void)
可以使别人清楚编写者是故

意抛弃返回值的,而不是忘记了。但是,
C
语言库中大量函数的返回值通常都会被丢掉;在调用它们时都使用(void)
会很麻烦,所以本书没有这样做。

9.2 函数声明 🚀

当遇到
main
函数中第一个
average
函数调用时,编译器没有任何关于
average
函数的信息:

编译器不知道
average
函数有多少形式参数,形式参数的类型是什么,也不知道
average
函数的返回值是什么类型。但是,编译器不会给出出错消息,而是假设average
函数返回
int
型的值(回顾9.1
节的内容可以知道函数返回值的类型默认为int
)。我们可以说编译器为该函数创建了一个隐式声明

implicit declaration
)。编译器无法检查传递给
average
的实参个数和实参类型,只能进行默认的实际参数提升( 9.3
节)并期待最好的情况发生。当编译器在后面遇到
average的定义时,它会发现函数的返回类型实际上是double
而不是
int
从而我们得到一条出错消息。

为了避免定义前调用的问题,一种方法是使每个函数的定义都出现在其调用之前。可惜的

是,有时候无法进行这样的安排;而且即使可以这样安排,程序也会因为函数定义的顺序不自然而难以阅读。

幸运的是,
C
语言提供了一种更好的解决办法:在调用前声明每个函数。
函数声明

function

declaration
)使得编译器可以先对函数进行概要浏览,而函数的完整定义以后再给出。函数声明类似于函数定义的第一行,不同之处是在其结尾处有分号:

无需多言,函数的声明必须与函数的定义一致。

为了与过去的那种圆括号内为空的函数声明风格相区别,我们把正在讨论的这类函数声明

称为
函数原型

function prototype
)。原型为如何调用函数提供了完整的描述:提供了多少

实际参数,这些参数应该是什么类型,以及返回的结果是什么类型。

顺便提一句,函数原型不需要说明函数形式参数的
名字
,只要显示它们的
类型
就可以了:

double average(double, double);

**通常最好是不要省略形式参数的名字,因为这些名字可以说明每个形式参数的目的,并且提醒程序员在函数调用时实际参数的出现次序。**当然,省略形式参数的名字也有一定的道理,有些程序员喜欢这样做。

C99
遵循这样的规则:在调用一个函数之前,必须先对其进行声明或定义。调用函数时,如果此前编译器未见到该函数的声明或定义,会导致出错。

9.3 实际参数 🚀


C
语言中,实际参数是
通过值传递
的;

&也是传值,只不过是值是他的地址,我们可以借用此值来访问实际数据。

9.3.1 实际参数的转换 🚀

C
语言允许在实际参数的类型与形式参数的类型不匹配的情况下进行函数调用。管理如何

转换实际参数的规则与编译器是否在调用前遇到函数的原型(或者函数的完整定义)有关。

编译器在调用前遇到原型。
就像使用赋值一样,每个实际参数的值被隐式地转换成相应

形式参数的类型。例如,如果把
int
类型的实际参数传递给期望得到
double
类型数据的

函数,那么实际参数会被自动转换成
double
类型。

编译器在调用前没有遇到原型。
编译器执行
默认的实际参数提升
:(
1
)把
float
类型的

实际参数转换成
double
类型,(2)执行整值提升,即把
char
类型和
short
类型的实际参

数转换成
int
类型。( C99实现了整数提升。)

9.3.2 数组型实际参数 🚀

一个关于数组型实际参数的重要论点:函数无法检测传入的数组长度的正确性。我们可以

**利用这一点来告诉函数,数组的长度比实际情况小。**假设,虽然数组
b

100
个元素,但是实际仅存储了50
个数。通过书写下列语句可以对数组的前
50
个元素进行求和:

sum_array
函数将忽略另外
50
个元素。(事实上,
sum_array
函数甚至不知道另外
50
个元素的存在!)

9.3.3 变长数组形式参数 🚀

一般来说,变长数组形式参数的长度可以是任意表达式。

9.3.4 在数组参数声明中使用 static 🚀

C99
允许在数组参数声明中使用关键字
static

C99
之前
static
关键字就已经存在,
18.2

节会讨论它的传统用法)。

在下面这个例子中,
static放在数字3之前表明数组a的长度至少可以保证是3

这样使用static
不会对程序的行为有任何影响。
static
的存在只不过是一个“提示”,
C
编译

器可以据此生成更快的指令来访问数组。(如果编译器知道数组总是具有某个最小值,那么它可以在函数调用时预先从内存中取出这些元素值,而不是在遇到函数内部实际需要用到这些元素的语句时才取出相应的值。)

最后,关于
static
还有一点值得注意:如果数组参数是多维的,
static
仅可用于第一维(例

如,指定二维数组的行数。)

9.3.5 复合字面量 🚀

让我们再来看看
sum_array
函数。当调用
sum_array
函数时,第一个参数通常是(用于求

和的)数组的名字。例如,可以这样调用
sum_array

这样写的唯一问题是需要把
b
作为一个变量声明,并在调用前进行初始化。如果
b
不作它用,

这样做其实有点浪费。

在C99中,可以使用复合字面量来避免该问题,复合字面量是通过指定其包含的元素而创

**建的没有名字的数组。**下面调用
sum_array
函数,第一个参数就是一个复合字面量:

在这个例子中,复合字面量创建了一个由
5
个整数
3, 0, 3, 4

1
组成的数组。这里没有对数组的长度进行特别的说明,是由复合字面量的元素个数决定的。当然,也可以做准确说明,如

(int[4]){1, 9, 2, 1}
,这种方式等同于
(int[]){1, 9, 2, 1}

一般来说,复合字面量的格式为:先在一对圆括号内给定类型名,随后在一对花括号内设

**定所包括元素的值。**复合字面量类似于应用于初始化式的强制转换。事实上,复合字面量和初始化式遵守同样的规则。复合字面量可以包含指示符,就像指定初始化式( 8.1

)一样;可以不提供完全的初始化(未初始化的元素默认被初始化为零)。例如,复合字面量(int[10]){8, 6}有10
个元素,前两个元素的值为
8

6
,剩下的元素值为
0

**函数内部创建的复合字面量可以包含任意的表达式,不限于常量。**例如:其中i、j、k都是变量。复合字面量的这一特性极大地增加了其实用性。

复合字面量为左值( 4.2节),所以其元素的值可以改变。如果要求其值为“只读”,可以

在类型前加上const,如(const int []){5, 4}。

9.4 return 语句 🚀

如果return语句中表达式的类型和函数的返回类型不匹配,那么系统将会把表达式的类型

隐式
**转换成返回类型。**例如,如果声明函数返回
int
类型值,但是
return
语句包含
double
类型

表达式,那么系统将会把表达式的值转换成
int
类型。

如果没有给出表达式,
return
语句可以出现在返回类型为
void
的函数中:

但是,return语句不是必需的,因为在执行完最后一条语句后函数将自动返回。

如果非void函数到达了函数体的末尾(也就是说没有执行return语句),那么如果程序试


使用函数的返回值
,其行为是
未定义
的。有些编译器会在发现非
void
函数可能到达函数体末尾时产生诸如

control reaches end of non-void function
**”**这样的警告消息。

9.5 程序终止 🚀

省略函数的返回类型在C99
中是不合法的,所以最好不要这样做。省略
main函数参数列表

中的void
是合法的(可变参数列表),但是(从编程风格的角度看)最好显式地表明
main
函数没有参数。(后面将看到,main
函数有时是有两个参数的,通常名为
argc和argv

13.7

。)

main
函数返回的值是状态码,在某些操作系统中程序终止时可以检测到状态码。如果程序正常终止,main函数应该返回
0
;为了表示异常终止,
main
函数应该返回非
0
的值。(实际上,这一返回值也可以用于其他目的。)即使不打算使用状态码,确保每个C
程序都返回状态码也是一个很好的实践,因为以后运行程序的人可能需要测试状态码

exit 函数

在main函数中执行return语句是终止程序的一种方法,另一种方法是调用exit函数,此函

数属于<stdlib.h>头( 26.2节)。传递给exit函数的实际参数和main函数的返回值具有相同的
含义:两者都说明程序终止时的状态。为了表示正常终止,传递0

因为0有点模糊,所以C语言允许用EXIT_SUCCESS来代替(效果是相同的)

传递EXIT_FAILURE表示异常终止:

EXIT_SUCCESS

EXIT_FAILURE
都是定义在
<stdlib.h>
中的宏。
EXIT_SUCCESS

EXIT_FAILURE的值都是由实现定义的,通常分别是
0

1

作为终止程序的方法,return语句和exit函数关系紧密。事实上,main函数中的语句

return
语句和
exit
函数之间的差异是:不管哪个函数调用
exit
函数都会导致程序终止,
return

语句仅当由main
**函数调用时才会导致程序终止。**一些程序员只使用
exit函数,以便更容易定位程序中的全部退出点。

9.7 泛性选择(了解)🚀

C11提供像C++的函数重载一样,为了用同一个函数名但是返回类型不同的函数的功能

语法:_Generic(表达式,泛型关联列表)

问与答 🚀

问:在程序的形式参数列表的后边,我们遇见过把形式参数的类型用单独的声明进行说明的例子:

*(下面的话·简单说就是合法但不推荐,最终可能消失这种写法)

答:这种定义函数的方法来自于经典
C,所以可能会在较早的书籍和程序中遇到这种方法。
C89

C99
支持这种格式以便于可以继续编译旧的程序。然而,由于下面两个原因本书避免在新程序中采用此种方法。

首先用经典
C
**的方法定义的函数不会经受和新格式函数一样程度的错误检查。**当函数采用经典方法定义(并且没有给出原型)时,编译器将不会检测调用函数的实际参数的数量是否正确,也不会检测实际参数是否具有正确的类型。相反,编译器会执行默认的实际参数提升( 9.3
节)。

其次
C
标准提到经典格式是“逐渐消亡的”,这意味着不鼓励此种用法,并且这种格式最终可能会从C
语言中消失。

问:一些编程语言允许过程和函数互相嵌套。C语言是否允许函数定义嵌套呢?

答:不允许。
C
语言不允许一个函数的定义出现在另一个函数体中。这个限制可以使编译器简单化。

*问:为什么编译器允许函数名不跟着圆括号?(p.133)

答:在后面某一章中将会看到,编译器把不跟圆括号的函数名看成是指向函数的
指针

17.7
节)。指向函数的指针有合法的应用,所以编译器不能自动假定函数名不带圆括号是错误的。语句

是合法的,因为编译器会把

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

允许一个函数的定义出现在另一个函数体中。这个限制可以使编译器简单化。

*问:为什么编译器允许函数名不跟着圆括号?(p.133)

答:在后面某一章中将会看到,编译器把不跟圆括号的函数名看成是指向函数的
指针

17.7
节)。指向函数的指针有合法的应用,所以编译器不能自动假定函数名不带圆括号是错误的。语句

是合法的,因为编译器会把

[外链图片转存中…(img-V0hzupKy-1715886059581)]
[外链图片转存中…(img-ZuV7ixzJ-1715886059581)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值