一开始,C++只是在C的基础上加上了一些面向对象的特性。C++最初的名称C with Class正反映了这样一个事实。但随着时间的推移以及语言的发展成熟,C++变得更加大胆和开放,各种新的特性被加入,补充和完善。如今的C++已经是一个多重泛型编程语言,一个同时支持过程形式(procedural),面向对象形式(object-oriented),函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的强大语言。这些新的特性使得C++拥有自己的完整特性,包括指针,包括模板,包括标准库。
定义(definition)的任务是提供编译器一些声明所遗漏的具体细节。对于不同对象(包括基本内置对象)而言,定义是编译器为此对象分配内存的地方。对函数或函数模板而言,定义提供了详细的代码细节。对类或类模板而言,定义列出了他们的具体成员。
初始化(initialization)是给与对象赋初值的过程。内建对象赋初值直接进行,而由用户定义的自定义类型的对象,初始化由构造函数执行。该构造函数可能由用户自定义,也可能由系统自动调用默认无参构造函数完成。
另外,关于自定义对象的赋值问题,需要特别关注拷贝构造函数和赋值操作符(=)重载的问题:
C++,包括C中的类似场景,有几点是每一位有志于从事C++开发的程序员都应该牢记的特性:声明,定义,初始化。
所谓的声明(declaration),是告诉编译器某个东西的名称和和类型,但略去细节。以下时一些常见的声明方式:extern int x;
std::size_t numDigits(int num);
class Widget;
template<typename T>
class GraphNode;
定义(definition)的任务是提供编译器一些声明所遗漏的具体细节。对于不同对象(包括基本内置对象)而言,定义是编译器为此对象分配内存的地方。对函数或函数模板而言,定义提供了详细的代码细节。对类或类模板而言,定义列出了他们的具体成员。
int x;
std::size_t numDigits(int number)
{
std::size_t digitSoFar = 1;
while ( (number /= 10) != 0)
++digitSoFar;
return digitSoFar;
}
class Widget
{
public:
Widget();
~Widget();
...
};
template<typename T>
class GraphNode
{
public:
GraphNode();
~GraphNode();
...
};
初始化(initialization)是给与对象赋初值的过程。内建对象赋初值直接进行,而由用户定义的自定义类型的对象,初始化由构造函数执行。该构造函数可能由用户自定义,也可能由系统自动调用默认无参构造函数完成。
class A
{
public:
explicait A(); // 默认无参构造函数
explicit A(int x = 0, bool b = true); // 默认有参构造函数
}
上述explicit 关键字表示,这些构造寒素不能执行隐式类型转换(implicit type conversions),但可以用来进行显式类型转换。通常这是受到鼓励的。
另外,关于自定义对象的赋值问题,需要特别关注拷贝构造函数和赋值操作符(=)重载的问题:
class Widget
{
public:
Widget();
Widget(const Widget& rhs);
Widget& operator=(Widget& rhs);
~Widget();
...
};
Widget w1; // 默认构造函数
Widget w2(w1); // 拷贝构造函数
w2 = w1; // 复制操作符重载
Widget w3 = w1; // 拷贝构造函数(why),w3赋值时,还没有创建出来,要先调用构造函数
...
bool hasAcceptableQuality(Widget w);
...
Widget wa;
if (hasAcceptableQuality(aw)); // 拷贝构造函数(why)同样,sw需要赋值给一个临时对象,这个临时对象也同样还没有创建出来,需要用构造函数创建
...
对于拷贝构造还是赋值操作符重载,虽然实际的结果总是类似的,但究竟是调用了哪个函数还是值得讨论一下。但其实其原则非常简单:凡是赋值的对象还没有创建,需要创建对象的,都是用拷贝构造函数;凡是对象已经存在,则调用赋值操作符重载函数。