异常
当一个异常被抛出后,C++的运行时支持系统“在调用栈中向上”搜索与抛出对象类型匹配的catch子句,即查找抛出异常的函数中的try语句,然后在抛出异常的函数的调用者中查找,再在更上层的调用者中查找,依次类推,直到找到匹配的catch语句。
如果找不到匹配的catch子句,程序就会终止。在搜索过程中遇到的每个函数中,以及每个作用域中,都会调用析构函数进行清理工作,这个过程被称为堆栈解退(stack unwinding)。
一旦对象的构造函数执行完毕,我们就认为对象已经构造出来了,而在堆栈解退过程中,或者退出对象作用域时,对象被销毁。
这意味着,作用域中还没有完全构造完成的对象(某些成员或基类已经构造完成,而另外一些没有构造完成)、数值和变量能够被正确处理。子对象当且仅当构造完毕后才能被销毁。
- 不要在析构函数中抛出异常!这条原则意味着析构函数不能失败。
//永远不要这样做!
X :: ~X() {if(in_a_real_mess()) throw Mess();}
定义这样的原则的主要原因是,如果析构函数在堆栈解退过程中抛出一个异常(而它自身又没有捕获这个异常的话),我们并不知道应该处理哪了个异常。
名字空间
名字空间(namespace)将相关的声明组织在一起,用来避免名字冲突:
int a;
namespace Foo{
int a;
void f(int i)
{
a +=i; //这是Foo的a(Foo::a)
}
}
boid f(int);
int main()
{
a = 7; //这是全局a(::a)
f(2); //这是全局f(::f)
Foo::f(3); //这是Foo的f
::f //这是全局f(::f)
我们可以显式地用名字空间名来限定名字(如Foo::f(3)),或者用::来限定名字(::f(2)),后者表示全局作用域。
我们可以使用一条单一的名字空间指令,使名字空间中的所有名字都可以被使用。eg:
using namespace std;
在使用using指令时一定要小心,虽然获得了名字使用上的便利性,但可能就会导致潜在的名字冲突。
特别的,不要在头文件中使用using指令。
我们可以使用名字空间声明,使名字空间中的某个名字可以访问:
using Foo::g;
g(2); //这里是Foo的g(Foo:g)
预处理指令
每个C++实现都包含预处理器(preprocessor)。理论上,预处理器在编译器之前运行,恰当地将我们编写的源码转换为编译器所需要的形式。
在实际中,预处理过程通常集成在编译器中,而且除非它引起了错误,否则对我们来说没有意义。
以#开头的代码行都是预处理指令。
- #include
#include "file.h"
这条指令告诉预处理器,在源码中指令出现的这个位置包含file.h的内容。
对于标准头文件,我们使用<…>而不是”…”,例如:
#include<vector>
这是包含标准头文件的建议语法。
- #define
预处理器实现一种字符处理机制,这种机制被称为宏代换(marco substitution)。例如,我们可以为字符串定义名字:
#define FOO bar;
因此,凡是出现FOO的地方,都会被替换成bar:
int FOO = 7;
int FOOL = 9;
进过预处理器处理,编译器看到的将是:
int bar = 7;
int FOOL = 9;
定义带有参数的宏:
#define MAX(x,y)(((x)>(y))?(x) : (y))
我们可以这样使用这个宏:
int xx = MAX(FOO+1, 7);
int yy = MAX(++xx, 9);
这段代码会被扩展为:
int xx = (((bar+1)>(7))?(bar+1) : (7));
int yy = (((++xx)>(9))?(++x) : (9)); //xx“悄悄”做了两次递增运算
如果一定要使用宏,一般全部使用大写字母来定义。