【C-Primer-Plus读书笔记】第9章:函数
复习函数
- 什么是函数:函数是完成特定任务的独立程序代码单元。
为什么要使用函数:
- 使用函数可以省去编写重复代码的苦差。
- 即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。
- 使用函数
要使用一个函数需要使用3次该函数标识符:函数原型告诉编译器函数的类型;函数调用表明执行函数;函数定义明确地指定了函数要做什么。 函数参数
- 定义带形式参数的函数
函数定义中的形式参数(形参)是局部变量,属该函数私有。 - 声明带形式参数函数的原型
函数声明中形参写法与函数定义基本一致,但声明可以省略形参变量名只写变量类型。 - 调用带实际参数的函数
在函数调用中,实际参数(实参)提供了形参的值。
- 定义带形式参数的函数
简而言之,形参是被调函数中的变量,实参是主调函数赋给被调函数的具体值。
- 黑盒视角
黑盒视角即在函数里发生了什么对主调函数是不可见的。 使用return从函数中返回值
- 参数的作用是把信息从主调函数传递给被调函数,反过来,函数的返回值可以把信息从被调函数传回主调函数。
- 关键字
return
后面的表达式的值就是函数的返回值。return
还有一个作用是终止函数并把控制返回主调函数的下一条语句。
函数类型
- 声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为
void
类型。 - 类型声明是函数的一部分。要记住,函数类型指的是返回值的类型,不是函数参数的类型。
- 声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为
ANSI C函数原型
- 问题:
ANSI C标准之前的声明函数方案有缺陷,因为只需要声明函数的类型,不用声明任何参数。会导致参数不匹配,而编译器根本不会察觉出来,最终得出错误的程序结果。 原因:
- 主调函数把它的参数存储在被称为栈的临时存储区,被调函数从栈中读取这些参数。
- 主调函数根据函数调用中的实参决定传递的类型,而被调函数根据它的形参读取值。
- ANSI C的解决方案:
针对参数不匹配问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型来声明函数的返回类型、参数的数量和每个参数的类型。有了这些信息,编译器可以检查函数调用是否与函数原型匹配。 - 无参数和未指定参数
一个支持ANSI C的编译器如果在函数中参数列表留空,编译器会假定用户没有用函数原型来声明函数,他将不会检查参数。为了表明函数确实没有参数,应该在参数列表中使用void
关键字。 - 函数原型的优点
它让编译器捕获在使用函数时可能出现的许多错误或疏漏。如果编译器没有发现这些问题,就很难察觉出来。
但也不一定必须使用函数原型,首先要明白,之所以使用函数原型,是为了让编译器在第一次执行到该函数之前就知道如何使用它。因此,把整个函数定义放在第一次调用函数前也有相同的效果。
递归
C允许函数调用它自己,这种调用过程称为递归。
递归的基本原理
- 每级函数调用都有自己的变量。也就是说各级变量互不相干,都属于各自的局部变量。
- 每次函数调用都会返回一次。当函数执行完毕后,控制权将被传回上一级递归。程序必须按顺序逐级返回递归。
- 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行。
- 递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行。
- 虽然每级递归都有自己的变量,但是并没有拷贝函数的代码。程序按顺序执行函数中的代码,而递归调用就相当于又从头开始执行函数的代码。
- 递归函数必须包含能让递归调用停止的语句。
- 尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在return语句前。这种形式的递归被称为尾递归,因为递归调用在函数的末尾。尾递归是最简单的递归形式,因为它相当于循环。 - 递归和倒序计算
递归在处理倒序时非常方便,在解决这类问题中,递归比循环简单。 - 递归的优缺点
优点:递归为某些编程问题提供了简单的解决方案。
缺点:一些递归算法会快速消耗计算机的内存资源。
编译多源代码文件的程序
使用头文件
- 如果把
main()
放在第一个文件中,把函数定义放在第二个文件中,那么第一个文件仍然要使用函数原型。把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。C标准库就是这样做的。 - 另外,程序中常用C预处理器定义的符号常量,通常也放入头文件中。
- 如果把
查找地址:&运算符
- 指针是C语言最重要的(有时也是最复杂的)概念之一,用于存储变量的地址。
- 一元
&
运算符给出变量的存储地址。用%p
转换说明打印指针。 - 常见的函数传参是传的值,这样做可以保护主调函数中的变量不会被意外修改(或防止原始变量被被调函数中的副作用意外修改)。
更改主调函数中的变量
但也有需要被调函数改变主调函数中的值的场合,鉴于return语句只能把被调函数中的一个值传回主调函数,所以这种情况只能使用指针实现。
指针简介
指针是一个值为内存地址的变量。
要创建指针变量,先要声明指针变量的类型。
间接运算符
*
使用间接运算符*
找出存储在变量中的值,该运算符有时也称为解引用运算符(即与&
相反)。//指针的一般使用流程 ptr = &bah; val = *ptr;
声明指针
- 声明指针的一般格式如下:
int * pi; char * pc; float * pf, * pg;
即
[data_type] * [var_name]
格式,一般把* [var_name]
看作一个整体,就跟声明普通变量一样,唯一区别就是指针(变量)名前加上间接运算符*
。*
和指针名之间的空格可有可无,通常在声明时使用空格,在解引用变量时省略空格。- 在大部分系统中,指针是一个无符号整数,但是不要把指针认为是整数类型,指针实际上是一个新类型,不是整数类型。
使用指针在函数间通信
- 一般而言,在函数中可以把变量的两类信息传递给函数,即值和地址。
- 给函数传递指针变量的一般流程如下:
//交换两变量的值 void swap(int * a, int * b); int a = 10, b = 20; int main(void) { swap(&a, &b); //引用传值,传递地址 return 0; } void swap(int * a, int * b) { int temp; temp = *a; //解引用取出该地址的值 *a = *b; //解引用改变该地址的值 *b = temp; }
普通变量把值作为基本量,把地址作为通过
&
运算符获得的派生量;而指针变量把地址作为基本量,把值作为通过*
运算符获得的派生量。