C++ Primer 读书笔记 – 第七章

来源:互联网 发布:linux系统整体迁移 编辑:程序博客网 时间:2024/06/05 03:18

 第7章 函数

㈠ 函数的声明和定义
⒈ 与变量类似:
⑴ 函数必须在调用前声明
⑵ 函数声明可与定义分离
⑶ 一个函数只能定义一次但可声明多次
⒉ 函数声明由函数返回类型、函数返回类型和形参列表组成
三者描述了函数的接口,称为函数原型(function prototype)
⑴ 函数的操作数,即形参(parameter),在一对圆括号中声明,并以逗号分隔
形参名是可选的,但形参需要在定义函数时命名才能使用
⑵ 函数执行的运算在一个称为函数体(function body)的块语句中定义
⒊ 函数一般在头文件中声明,在源文件中定义
此时应使后者包含前者,以便编译器检查定义和声明是否一致
⒋ 将一个较小的、常被调用的函数指定为
inline 可使函数在调用点展开,以避免调用函数的额外开销
但内联说明对编译器来说只是一个建议,编译器也可能选择忽略

㈡ 函数的调用和参数传递
⒈ 函数的调用
⑴ 使用调用操作符()实现函数调用
① 操作数是函数名和一组(可能为空的)用逗号分开的实参(argument)
② 结果类型为函数返回值的类型,结果为函数的返回值
⑵ 函数调用做两件事:
① 首先(隐式)定义形参,并用对应的实参进行初始化
· 形参的初始化与变量一样:
如果形参为非引用类型则复制实参的值,如果形参为引用类型则它只是实参的别名
② 主调函数(calling function)的执行被挂起,被调函数(called function)开始执行
⒉ 非引用形参
⑴ 指针形参
① 可以通过传入指针来间接访问指针所指对象
② 若需要保护指针指向的对象,可指定形参为指向 const 对象的指针
const 形参
① 用来初始化 const 形参的实参无论是不是 const 都可以
在函数中不能改变该实参的局部副本
② 非引用形参是否为 const 不影响编译器对其所属函数类型的判别
·注:对于指针,注意区分 const 是修饰指针本身的性质还是所指对象的性质
⑶ 不适合复制实参的情况
① 需要修改实参值时
② 需要将大型对象作为实参传递时
③ 无法复制对象时
以上情况可通过将形参定义为指针或引用类型解决
⒊ 引用形参
⑴ 可以使用引用形参返回额外信息
⑵ 可以利用 const 引用形参避免复制实参
⑶ 使用引用形参时,将不需要修改的定义为 const 会更灵活
const 引用形参不能用 const 左值或右值初始化,而定义为 const 则无此问题
⒋ 一般通过传递迭代器来传递 vector 等容器
⒌ 数组形参
⑴ 当把数组直接作为实参传给函数时:
① 若形参不是数组的引用,则自动转换为指向首元素的指针
例如以下三种定义完全等价:
void f(int*);//推荐,明确指出操作对象为指针
void f(int[]);
void f(int[10]);//维数被编译器忽略,写法容易引起误解
② 若形参是数组的引用则不发生转换,函数得到的是数组本身
此时编译器会检查形参和实参数组的维数是否匹配
⑵ 把数组传递给函数处理时,可以:
⒈ 使用标准库规范,即传入首元素指针和超出末端指针
⒉ 传递数组首元素地址,并显式传递表示数组大小的形参
⑶ 可以通过传递指向数组的指针来传递**数组
⒍ 含有可变形参的函数
为兼容C语言而保留的特性,只能传入简单数据类型,大多数类类型对象都不能正确复制
⒎ 默认实参
⑴ 通过给形参表中的形参提供明确的初值可指定默认实参,如
void f( int, int =1, int =2 );
① 默认实参可以是任何适当类型的表达式
② 如果一个形参有默认实参,则其后的所有形参都必须有默认实参
③ 在一个文件中,只能为一个形参指定默认实参一次
通常应在函数声明中指定默认实参,并将该声明放在合适的头文件中
⑵ 调用函数时可省略有默认值的实参:若省略则使用默认实参,否则使用用户提供的实参
· 默认实参只能用来替换函数调用缺少的靠后的实参
因此设计带有默认实参的函数时,应将最少使用默认实参的形参排在最前,最多使用默认实参的形参排在最后

㈢ 局部对象的作用域和生命期
⒈ 函数体是一个作用域,在其中定义的变量称为局部变量(local variable),只可在该函数中访问
局部变量和形参均不能重名
⒉ 自动对象(automatic objects)是局部于函数的对象,会在每次函数调用时重新创建,并在函数结束时撤销
非静态局部变量和形参都是自动对象
static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时初始化,一旦创建在程序结束前都不会撤销
在所在函数被多次调用的过程中,static 局部对象会持续存在并保持它的值

㈣ 函数的返回值和 return 语句
⒈ 函数返回类型可为内置类型、类类型或复合类型,但不能是另一个函数或内置数组类型
void 类型表示该函数不返回任何值
return 语句
用于结束当前函数,将控制权返回给该函数的主调函数
⑴ 没有返回值的 return 语句
① 只能用于返回类型为 void 的函数
② 非必需,隐式 return 发生在函数最后一个语句完成时
⑵ 具有返回值的 return 语句
① 返回类型不是 void 的函数必须返回一个值(主函数 main 除外)
注:应在函数中的每条执行路径末尾都提供 return 语句;若不提供编译器可能也无法发现
② 用函数返回值初始化在调用函数处创建的临时对象,与用实参初始化形参的方法一样
即若返回非引用类型则得到副本,若返回引用类型则得到对象本身
③ 切勿返回局部对象的引用或指针

㈤ 函数的重载
⒈ 出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则为重载函数(overloaded function)
仅仅是返回类型不同、默认实参不同、非引用形参的 const 性质不同不能实现重载
⒉ 若局部地声明函数,则该函数屏蔽而非重载在外层作用域中声明的同名函数
⒊ 重载确定的三个步骤
⑴ 确定候选函数集合
候选函数(candidate function)是与被调函数同名的函数,且在调用点上声明可见
⑵ 从候选函数集合中选择可行函数(找不到则此调用错误)
可行函数(viable function)满足两个条件:
① 函数形参与该调用的实参个数匹配(考虑默认实参)
② 每个实参的类型须与对应形参匹配(类型相同或可隐式转换)
⑶ 寻找最佳匹配(有多个匹配程度相同的则调用有二义性)
原则是实参类型与形参类型越接近则匹配越佳。具体匹配程度从高到低为:
① 精确匹配:实参与形参类型相同
② 通过类型提升实现的匹配
③ 通过标准转换实现的匹配
④ 通过类类型转换实现的匹配

㈥ 函数指针
⒈ 定义形式
返回类型 (*标识符)(形参表)
⒉ 可使用 typedef 简化定义
typedef 返回类型 (*自定义类型名)(形参表)
⒊ 对函数指针进行初始化和赋值,可使用:
⑴ 同类型的函数
引用函数名但不调用该函数时,函数名被自动解释为指向函数的指针
因此可以直接使用函数名对函数指针初始化或赋值,不需要使用取地址操作符&
·注:指针类型须与函数完全匹配
⑵ 同类型的函数指针
0值常量表达式
⒋ 通过函数指针调用函数
可以不使用解引用操作符,直接使用函数调用操作符()调用所指函数
⒌ 函数指针作形参时,星号*可写可不写
⒍ 函数的指针作函数返回值
函数指针返回类型 (*函数名(函数形参表))(函数指针形参表)

㈦ 主函数 main
⒈ 返回值
⑴ 主函数返回类型为 int, 返回值在大多数系统中是一个状态指示器
返回0表示程序运行成功,其它大部分值表示失败
⑵ 不需要显式使用 return 语句,编译器将隐式插入返回0的语句
⑶ 为使返回值独立于机器,cstdlib 头文件定义了两个预处理变量
EXIT_FAILURE(运行失败)    EXIT_SUCCESS(运行成功)
⒉ 使用主函数形参处理命令行选项
int main(int argc,char* argv[])
· argv为一个C风格字符串数组,储存了从命令行调用程序时输入的字符串(包括程序名和参数)
· argc表示argv中字符串的个数
⒊ 主函数 main 不允许被显式调用、取地址或重载