[读书笔记] - <Essential C++> - 第二章: 面向过程的编程风格
将函数独立出来带来的好处:
1. 以一连串函数调用操作取代重复撰写相同的程序代码, 可使程序更容易读懂;
2. 我们可以在不同的程序中使用这些函数;
3. 我们可以更轻易地将工作分配给协力开发团队.
一个函数包括以下四个部分:
1. 返回类型;
2. 函数名称;
3. 参数列表;
4. 主体.
注: 在 C++ 中, 函数必须先声明, 然后才能被调用;
<cstdlib> 头文件中的 exit() 函数可结束一个程序, 且接受一个整数作为程序结束的状态值, 如: -1 表示出错;
通过 <limits> 头文件中的 numeric_limits 类可获得某个类型的最大值和最小值, 如: int max_int = numeric_limits<int>:: max();
如果函数主体的最后一个语句不是 return, 那么最后一个语句之后便是该函数的隐性退出点;
当我们调用一个函数时, 会在内存中建立起一块特殊区域, 称为 "程序栈 (program stack)". 这块特殊区域提供了每个函数参数的存储空间. 它也提供函数所定义的每个对象的内存空间-- -- 我们将这些对象称为 local object (局部对象). 一旦函数完成, 这块内存就会被释放掉, 或者说是从程序堆栈中被 pop 出来.
在 C++ 中, 除非用 '&' 符号声明, 否则所有函数的参数传递都为值传递. 如: void method(classA aa) 中的 aa 为值传递, 若想用引用传递, 应该这么写 void method(classA & aa);
面对引用的所有操作都像面对 "引用所代表的对象" 所进行的操作一样.
为什么要将参数声明为引用类型:
1. 希望能直接对所传入的对象进行修改;
2. 为了降低复制大型对象的负担.
引用与指针的区别:
1. 引用与指针的用法不同, 指针必须与 '*' 符号搭配才能针对其指向的对象, 而方法调用时需通过 '->' 符号;
2. 引用必须在定义时便赋值, 且一个引用变量始终只指向一个对象; 而指针可能不指向任何对象, 也可以通过赋值改变其所指向的对象, 故在使用指针时, 可以通过 if (pointer) 进行判断;
在函数执行时, 为对象配置的内存, 其存活期称为 储存期(storage duration) 或 范围(extent).
对象在程序内的存活区域称为该对象的 生存空间 (scope).
注: 内建类型的对象, 如果定义在 file scope 内, 必定被初始化为 0. 但如果它们被定义在 local scope 内, 那么除非程序员指定其初值, 否则不会被初始化.
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
动态变量储存于 堆内存 中, 这种内存必须由程序员自行管理, 其配置是通过 new 表达式来达成, 而其释放则经由 delete 表达式完成.
默认情况下, 储存于堆中的对象, 皆未经过初始化. 其通过 new 配置后, 将持续存活, 直到以 delete 表达式加以释放为止. 若要删除数组中的所有对象, 可以通过 delete [] arr 表达式来达成 (注: 无需检验 arr 是否非零).
一般程序在撰写时, 会以 "参数传递" 作为函数间的沟通方式, 来代替 "直接将对象定义在 file scope". 其中的主要原因是, 函数如果过度依赖 file scope 中的变量, 就比较难以在其他环境中被重复使用, 也比较难以维护 -- -- 我们不仅需要了解该函数的运行逻辑, 还要了解 file scope 中那些对象的运行逻辑.
C++允许我们为所有参数或部分参数设定默认值.
使用默认参数值的两个规则:
1. 默认值的解析操作由最右边开始进行, 故必须保证所有设定了默认值的参数在参数列表的最右边;
2. 默认值只能够指定一次. 可在函数声明处, 亦可在函数定义处, 但不能重复指定. -> 为了更高的可见度, 一般置于函数声明处.
局部静态对象和局部非静态对象不同的是, 其所处的内存空间, 即使在不同的函数调用过程中, 依然持续存在. 故可以将局部静态对象的地址作为结果返回.
内嵌函数
方法: 在函数定义前面加上关键词 inline;
效果: 表示要求编译器在每个函数调用点上, 将函数的内容展开;
注: 将函数指定为 inline, 只是对编译器提出的一种要求. 编译器是否执行这项要求, 需视编译器而定.
内嵌函数的定义常常被置于头文件中.
函数重载
方法: 拥有相同的函数名称, 而参数表不同 (可能是参数类型不同, 也可能是参数数量不同) 的两个或多个函数;
注: 编译器无法根据函数返回值类型来区分两个具有相同函数名称的函数; -> 返回值类型是函数执行后的结果, 编译器无法提前预知执行的结果来选择要执行的程序.
函数模板
方法: 以关键字 template 开场, 其后紧接着以成对尖括号 (< >) 包围起来的一个或多个识别名称. 这些名称用以表示我们希望延缓决定的数据类型.
效果: 将参数表中指定的所有 (或部分) 参数的类型信息抽离出来.
一般而言, 如果函数具备多种实现方式, 我们可将它重载(overload), 其每份实体提供的是相同的通用服务. 如果我们希望让程序代码的主体不变, 仅仅改变其中用到的数据类型, 可以通过函数模板达成目的.
函数指针
方法: 返回值类型 * 变量名 (参数列表); 如: const vector<int> * (* seq_ptr) (int);
注: 函数指针使用前最好先检验一下是否为空指针; if (seq_ptr)
赋值: 变量名 = 方法名;
定义头文件
将函数声明置于头文件中, 并在每个程序代码文件内包含这些函数声明, 将使得我们只需为函数维护一份声明即可. 如果其参数表或返回值类型需要改变, 也只需更改这份声明即可.
一般情况下, 我们不把函数的定义纳入头文件, 因为这样同一个程序的多个代码文件将可能都会含入这个头文件中. 但也有例外情况: inline 函数的定义. 为了能够扩展 inline 函数的内容, 在每个调用点上, 编译器都得取得其定义. 这就意味着我们必须将 inline 函数的定义置于头文件, 而不是把它放在各个不同的程序代码文件.
file scope 内定义的对象, 如果可能被多个文件存取, 就应该被声明于头文件中.
const object 的定义只要一出文件之外便不可见. 故我们可以在多个程序代码文件中加以定义, 不会导致任何错误.
引入头文件时的 ‘””’ 和 ‘< >’
如果该文件被认定为标准的, 或项目专属的头文件, 则应该以尖括号将文件名括住; 编译器搜寻此档时, 会先在某些默认的驱动器目录中找寻. 如果文件名由成对的双引号括住, 此文件便被认为是一个用户自行提供的头文件; 搜寻此文件时, 会由含入此文件的文件所在的驱动器目录开始找起.