C++ Primer (3RD) 重读笔记——基于过程的程序设计

第三篇 基于过程的程序设计

第七章 函数

7.1 概述

函数可以被看作是一个由用户定义的操作。一般来说,函数由一个名字来表示。函数的操作数,称为参数(parameter),由一个位于括号中并且用逗号分隔的参数表(parameter list指定。函数的结果被称为返回值(return value)返问值的类型被称为函数返回类型(return type) 函数执行的动作在函数体(body)中。指定函数体包含在花括号中有时也称为函数块(function block)

7.2 函数原型

7.2.1 函数返回类型

7.2.2 函数参数表

7.2.3 参数类型检查

7.3 参数传递

所有的函数都使用在程序运行栈(run-time stack)中分配的存储区。该存储区一直保持与该函数相关联,直到函数结束为止。那时,存储区将自动释放以便重新使用。该函数的整个存储区被称为活动记录(activation record)

系统在函数的活动记录中为函数的每个参数都提供了存储区。

C++中参数传递的缺省初始化方法是把实参的值拷贝到参数的存储区中。这被称为按值传递(pass-by-value)

     按值传递并不是在所有的情况下都适合。不适合的情况包括

           1. 当大型的类对象必须作为参数传递。

           2当实参的值必须被修改时。

7.3.1 引用参数

把参数声明成引用,实际上改变了缺省的按值传递参数的传递机制。

7.3.2 引用和指针参数的关系

怎样决定把函数参数声明成引用还是指针呢?

引用必须被初始化为指向一个对象,一旦初始化了,它就不能再指向其他对象。指针可以指向一系列不同的对象也可以什么都不指向。

 

为了支持类(class)类型——尤其是支持有效直观地实现重载操作符机制,C++特别引入了引用机制。

7.3.3 数组参数

C++中数组永远不会按值传递。它是传递第一个元素(准确地说是第0个)的指针。

7.3.4 抽象容器类型参数

容器类型实际上是类类型。它比内置数组数据类型提供了更多的功能。

7.3.5 缺省实参

缺省实参是一种虽然并不普遍,但在多数情况下仍然适用的实参值。

函数可以用参数表中的初始化语法为一个或多个参数指定缺省实参。

7.3.6 省略号(ellipsis)

有时候我们无法列出传递给函数的所有实参的类型和数目。在这种情况下,我们可以用省略号(...)指定函数参数表。

 

7.4 返回一个值

return 语句被放在函数体内,这条语句结束当前正在执行的函数。

两个易犯的错误:

1. 返回一个指向局部对象的引用。

2. 函数返回一个左值。

7.4.1 参数和返回值与全局对象

一个程序中的各种函数可以通过两种机制进行通信。【这里的通信(communicate) 指的是值的交换。】一种方法是使用全局对象, 第一种方法是使用函数参数表和返回值。

7.5 递归

直接或间接调用自己的函数被称为递归函数(recursive function)。

递归函数必须定义一个停止条件(stopping condition)。

7.6 inline 函数

若一个函数被指定为inline 函数,则它将在程序中每个调用点上被内联地展开。

7.7 链接指示符extern C

7.8 main() 处理命令行选项

argv[0]总是被设置为当前正被调用的命令。从索引1 argc-1 表示被传递给命令的实际选项。

7.9 指向函数的指针

7.9.1 指向函数的指针的类型

7.9.2 初始化和赋值

7.9.3 调用

7.9.4 函数指针的数组

         int (*testCases[10])();

7.9.5 参数和返回类型

7.9.6 指向extern "C"函数的指针

extern "C" void (*pf)(int);

 

第八章 域和生命期

8.1

C++程序中的每个名字都必须指向惟一的一个实体(对象,函数,类型或模板)用来区分名字含义的一般上下文就是域(scope)。C++支持三种形式的域:局部域(local scope), 名字空间域(namespace scope), 类域(class scope)

8.1.1 局部域

局部域是包含在函数(定义或函数块中)的程序文本区。

8.2 全局对象和函数

全局域内的函数声明将引入全局函数(global function), 而在全局域内的变量声明将引入全局对象(global object)

8.2 全局对象和函数

8.2.1 声明和定义

8.2.2 不同文件之间声明的匹配

在多个文件中声明对象或函数的一个可能问题:在不同文件中的声明可能会随时间而不同或改变。

8.2.3 谈谈头文件

8.3 局部对象

在局部域中的变量声明引入了局部对象(local object)有三种局部对象:自动对象(automatic object), 寄存器对象(register object), 局部静态对象(local static object).

自动对象所在存储区从声明它的函数被调用时开始,一直到该函数结束为止。

寄存器对象是一种自动对象,它支持对其值的快速存取。

局部静态对象的存储区在该程序的整个执行期间一直存在。

8.4 动态分配的对象

全局对象和局部对象的生命期是严格定义的, 程序员不能以任何方式改变它们的生命期。

第三种对象允许程序员完全控制它的分配与释放。这样的对象被称为动态分配的对象(dynamically allocated object)动态分配的对象被分配在程序的空闲存储区(free store)的可用内存池中。程序员用new 表达式创建动态分配的对象, delete 表达式结束此类对象的生命期。

8.4.1 单个对象的动态分配与释放

常见程序错误都与动态内存分配有关:

1.应用delete 表达式失败,使内存无法返回空闲存储区

2.对同一内存区应用了两次delete 表达式。

3.在对象被释放后读写该对象。这常常会发生,因为delete 表达式应用的指针没有被设置为0

8.4.2 auto_ptr

8.4.3 数组的动态分配与释放

              // 分配单个int 型的对象

// 1024 初始化

int *pi = new int( 1024 );

// 分配一个含有1024 个元素的数组

// 未被初始化

int *pia = new int[ 1024 ];

// 分配一个含 4x1024 个元素的二维数组

int (*pia2)[ 1024 ] = new int[ 4 ][ 1024 ];

一般地,在空闲存储区上分配的数组不能给出初始化值集。

 

用来释放数组的delete 表达式形式如下

delete [] str1;

8.4.4 常量对象的动态分配与释放

const int *pci = new const int(1024);

在空闲存储区创建的const 对象有一些特殊的属性。首先,const 对象必须被初始化。第二,用 new 表达式返回的值作为初始值的指针必须是一个指向const 类型的指针。

           delete pci;

8.4.5 定位new 表达式

new 表达式的第三种形式可以允许程序员要求将对象创建在已经被分配好的内存中。这种形式的new 表达式被称为定位new 表达式(placement new expression)

new (place_address) type -specifier

 

place_address 必须是个指针。

不存在与定位new 表达式相匹配的delete 表达。因为定位new 表达式并不分配内。

8.5 名字空间定义

8.5.1 名字空间定义

用户声明的名字空间定义以关键字namespace 开头,后面是名字空间的名字。

8.5.2 域操作符(::

8.5.3 嵌套名字空间

8.5.4 名字空间成员定义

8.5.5 ODR 和名字空间成员

8.5.6 未命名的名字空间

8.6 使用名字空间成员

8.6.1 名字空间别名

8.6.2 using 声明

8.6.3 using 指示符

8.6.4 标准名字空间std

 

 

第九章 重载函数

如果两个函数名字相同,并且在相同的域中被声明,但是参数表不同,则它们就是重载函数(overloaded function)

9.1 重载函数声明

9.1.1 为什么要重载一个函数名

9.1.2 怎样重载一个函数名

9.1.3 何时不重载一个函数名

如果不同的函数名所提供的信息可使程序更易于理解的话,则再用重载函数就没有什么好处了。

9.1.4 重载与域

重载函数集合中的全部函数都应在同一个域中声明。

9.1.5 extern "c" 和重载函数

链接指示符只能指定重载函数集中的一个函数。

9.1.6 指向重载函数的指针

我们可以声明一个指向重载函数集合里的某一个函数的指针。

9.1.7 类型安全链接

每个函数名及其相关参数表都被作为一个惟一的内部名编码(encoded)

9.2 重载解析的三个步骤

函数重载解析(function overload resolution)是把函数调用与重载函数集合中的一个函数相关联的过程。

函数重载解析的步骤如下:

1. 确定函数调用考虑的重载函数的集合, 确定函数调用中实参表的属性。

2. 从重载函数集合中选择函数, 该函数可以在(给出实参个数和类型)的情况下用调用中指定的实参进行调用。

3. 选择与调用最匹配的函数。

9.3 参数类型转换

在函数重载解析的第二步中,编译器确定可以应用在函数调用的实参上的, 将其转换成每个可行函数中相应参数类型的转换, 并将其划分等级。这种等级有二种可能:

1.   精确匹配(exact match): 实参与函数参数的类型精确匹配。

2.   与一个类型转换(type conversion匹配。

3.   无匹配(no match)。实参不能与声明的函数的参数匹配,因为在实参与相应的函数参数之间无法进行类型转换。

精确匹配的实参并不一定与参数的类型完全一致,有一些最小转换可以被应用到实参上。在精确匹配的等级类别中可能存在的转换如下:

从左值到右值的转换

从数组到指针的转换

从函数到指针的转换

限定修饰转换

与一个类型转换匹配的等级类别是三个等级中最复杂的一个,几种类型转换都必须考虑到。可能的转换被分成三组:提升(promotion),标准转换(standard conversion) 用户定义的转换(user-defined conversions).

9.3.1 精确匹配的细节

精确匹配最简单的例子是实参与函数参数类型精确匹配。

这从左值到右值的转换。

从数组到指针的转换。

从函数到指针的转换

限定修饰转换,这种转换只影响指针

9.3.2 提升的细节

Charunsigned char short 型的实参被提升为int 型。如果机器上int 型的字长比short 整型的长,则unsigned short 型的实参被提升到int 型;否则,它被提升到unsigned int 型。

float 型的实参被提升到double 类型。

枚举类型的实参被提升到下列第一个能够表示其所有枚举常量的类型:int unsigned int long unsigned long

布尔型的实参被提升为int 型。

9.3.3 标准转换的细节

有五种转换属于标准转换:

1.整值类型转换:从任何整值类型或枚举类型向其他整值类型的转换(不包括前面提升部分中列出的转换)

2.浮点转换:从任何浮点类型到其他浮点类型的转换(不包括前面提升部分中列出的转换)

3.浮点整值转换:从任何浮点类型到任何整值类型或从任何整值类型到任何浮点类型的转换。

4.指针转换:整数值0 到指针类型的转换和任何类型的指针到类型void*的转换。

5.bool 转换:从任何整值类型,浮点类型,枚举类型或指针类型到bool 型的转换。

9.3.4 引用

实参与引用参数的匹配结果有下面的两种可能:

1 .实参是引用参数的合适的初始值。

2 .实参不能初始化引用参

9.4 函数重载解析细节

函数重载解析过程有三个步骤。

9.4.1 候选函数

候选函数与被调用的函数具有同样的名字。可以用下面两种方式找到候选函数:

1该函数的声明在调用点上可见。

2如果函数实参的类型是在一个名字空间中被声明的,则该名字空间中与被调用函数同名的成员函数也将被加入到候选函数集中。

9.4.2 可行函数

可行函数是候选函数集合中的函数。

9.4.3 最佳可行函数

最佳可行函数是具有与实参类型匹配最好的参数的可行函数。

最佳可行函数是满足下列条件的可行函数:

1用在实参上的转换不比调用其他可行函数所需的转换更差。

2在某些实参上的转换要比其他可行函数对该参数的转换更好。

9.4.4 缺省实参

缺省实参可以使多个函数进入到可行函数集合中。

 

 

第十章 函数模板

10.1 函数模板定义

10.2 函数模板实例化

10.3 模板实参推演

当函数模板被调用时,对函数实参类型的检查决定了模板实参的类型和值,这个过程被称为模板实参推演(template argument deduction).

10.4 显式模板实参

10.5 模板编译模式

函数模板的定义可用来作为一个无限个函数实例集合定义的规范描述(prescription)。模板本身不能定义任何函数。

10.5.1 包含编译模式

10.5.2 分离编译模式

10.5.3 显式实例化声明

10.6 模板显式特化

我们并不总是能够写出对所有可能被实例化的类型都是最合适的函数模版。在某些情况下,我们可能想利用类型的某些特性来编写一些比模板实例化的函数更高效的函数。在有些时候一般性的模板定义对于某种类型来说并不适用。

10.7 重载函数模板

函数模板可以被重载。

成功地声明一组重载函数模板并不能保证它们可以被成功地调用。在调用一个模板实例时,重载的函数模扳可能会导致二义性。

10.8 考虑模板函数实例的重载解析

10.9 模板定义中的名字解析

10.10 名字空间和函数模板

与其他全局域定义一样,函数模板定义也可以被放在名字空间中。

 

第十一章 异常处理

异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况(称为异常exception ),相互通信的机制。

11.1 抛出异常

11.2 try

当某条语句抛出异常时,跟在该语句后面的语句将被跳过。程序执行权被转交给处理异常的catch 子句。

11.3 捕获异常

如果异常声明的类型与被抛出的异常类型匹配,则选择这段处理代码来处理异常。

11.3.1 异常对象

catch 子句的异常声明可以是一个类型声明或一个对象声明。

11.3.2 栈展开

找到一个catch 子句, 以处理被抛出的异常的过程如下: 如果throw 表达式位于try 块中, 则检查与try 块相关联的catch 子句, 看是否有一个子句能够处理该异常. 如果找到一个catch子句, 则该异常被处理。 如果没有找到catch 子句, 则在主调函数中继续查找。

如果没有找到处理代码,程序就调用C++标准库中定义的函数terminate()terminate()的缺省行为是调用abort()指示从程序非正常退出。

11.3.3 重新抛出

在某些修正动作之后,catch 子句可能决定该异常必须由函数调用链中更上级的函数来处理,那么catch子句可以通过重新抛出(rethrow) 该异常, 把异常传递给函数调用链中更上级的另一个catch子句。

11.3.4 catch-all 处理代码

11.4 异常规范

异常规范(exception specification)提供了一种方案,它能够随着函数声明列出该函数可能抛出的异常。它保证该函数不会抛出任何其他类型的异常。

异常声明是函数接口的一部分,它必须在头文件中的函数声明上指定。异常规范是函数和程序余下部分之间的协议。它保证该函数不会抛出任何没有出现在其异常规范中的异常。

如果函数声明指定了一个异常规范,则同一函数的重复声明必须指定同一类型的异常规范。同一函数的不同声明上的异常规范是不能累积的。

11.4.1 异常规范与函数指针

我们也可以在函数指针的声明处给出一个异常规范。例如:

void (*pf) (int) throw(string);

11.5 异常与设计事项

因为抛出异常不像正常函数调用那样快,所以异常处理应该用在独立开发的不同程序部分之间,用于不正常情况的通信。

 

第十二章 范型算法

12.1 概述

12.2 使用泛型算法

12.3 函数对象

12.3.1 预定义函数对象

12.3.2 算术函数对象

12.3.3 关系函数对象

12.3.4 逻辑函数对象

12.3.5 函数对象的函数适配器

12.3.6 实现函数对象

12.4 回顾iterator

12.4.1 插入iterator

12.4.2 反向iterator

12.4.3 iostream iterator

12.4.4 istream_iterator

12.4.5 ostream_iterator

12.4.6 五种 iterator

12.5 泛型算法

12.5.1 查找算法

12.5.2 排序和通用整序算法

12.5.3 删除和替换算法

12.5.4 排列组合算法

12.5.5 算术算法

12.5.6 生成和异变算法

12.5.7 关系算法

12.5.8 集合算法

12.5.9 堆算法

12.6 何时不用泛型算法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值