编译器生成的成员函数
编译器会自动生成一些公有的成员函数——特殊成员函数。
1、 默认构造函数
提供构造函数的动机之一是确保对象总能被正确地初始化。如果类包含指针成员,则必须初始化这些成员。最好提供一个显式默认构造函数,将所有的类数据成员都初始化为合理的值。
如果定义了某种构造函数,编译器将不会定义默认构造函数。如果没有定义任何构造函数,编译器将定义默认构造函数。
默认构造函数要么没有参数,要么所有的参数都有默认值。
2、 复制构造函数
复制构造函数接受其所属类的对象作为参数。例如,Star类的赋值构造函数的原型如下:
Star(const Star &);
在下述情况下,将使用复制构造函数:
将新对象初始化为一个同类对象;
按值将对象传递给函数;
函数按值返回对象;
编译器生成临时对象;
3、 赋值运算符
初始化和赋值不是一回事;
Star sirius;
Star alpha = sirius; //initialization 初始化
Star dogstar;
dogstar = sirius; //assignment 赋值
默认的赋值运算符用于处理同类对象之间的赋值;
如果需要显式定义复制构造函数,基于相同的原因,也需要显式定义赋值运算符。
其他的类方法
1、构造函数
构造函数不同于其他类方法,因为它创建新的对象。而其他类方法知识被现有的对象调用。这是构造函数不被继承的原因之一。
2、析构函数
一定要定义显式析构函数来释放类构造函数使用new分配的所有内存,并完成类对象所需的任何特殊的清理工作。对于基类,即使它不需要析构函数,也应提供一个虚析构函数。
3、转换
4、按值传递对象与传递引用
编写使用对象作为参数的函数时,应将参数声明为const引用。这样可以提高程序运行的效率。
按引用传递对象的另外一个原因是,在继承使用虚函数时,被定义为接受基类引用参数的函数可以接受派生类。
5、返回对象和返回引用
返回对象需要生成临时副本,这就要调用复制构造函数来生成副本和调用析构函数来删除副本。
返回引用可以节约内存空间和时间,但是并不总是要返回引用。
6、使用const
使用const来确保方法不修改调用它的对象。
公有继承的考虑因素
在程序中使用继承,有很多问题需要注意。
1、is-a关系
必须是is-a关系,无需进行显式类型转换,基类指针就可以指向派生类对象,基类引用可以引用派生类对象。
2、什么不能被继承
构造函数、析构函数、赋值运算符;都不可以;
3、赋值运算符
如果编译器发现程序将一个对象赋给同一个类的另一个对象,它将自动为这个类提供一个赋值运算符。
4、私有成员与保护成员
对于派生类而言,保护成员类似于公有成员。
但对于外部而言,保护成员与私有成员类似。
派生类可以直接访问基类的保护成员,但只能通过基类的成员函数来访问私有成员。
5、虚方法
设计基类时,必须确定是否将类方法声明为虚的。如果希望派生类能够重新定义方法,则应在基类中将方法定义为虚的。这样可以启动动态联编。
6、析构函数
基类的析构函数应当是虚的。这样,当通过通过对象的基类指针或引用来删除派生对象时,程序将首先调用派生类的析构函数,然后调用基类的析构函数,而不仅仅是调用基类的析构函数。
7、友元函数
友元函数并非类成员,因此不能被继承。
8、有关使用基类方法的说明
以公有方式派生的类的对象可以通过多种方式来使用基类的方法。
派生类对象自动使用继承而来的基类方法,如果派生类没有重新定义该方法;
派生类的构造函数自动调用基类的构造函数;
派生类的构造函数自动调用基类的默认构造函数,如果没有在成员初始化列表中指定其他构造函数;
派生类构造函数显式地调用成员初始化列表中指定的基类构造函数;
派生类方法可以使用作用域解析运算符来调用公有的和受保护的基类方法;
派生类的友元函数可以通过强制类型转换,将派生类引用或指针转换为基类引用或指针;