类设计总结与回顾:
文章目录
1. 编译器生成的成员函数
默认构造函数
Star rigel;
Star pleiades[6];
原则:最好提供一个显式的默认构造函数,将所有的类初始化成员都初始化为一个合理地值。
- 默认构造要么没有参数,要么所有的参数都有默认值。
- 如果派生类构造函数的成员初始化列表没有显式的调用基类的构造函数,则编译器将使用基类的默认构造函数来构造派生类对象的基类部分。
- 自动生成的默认构造函数的另一项功能是,调用本身是对象的成员所属类的默认构造函数。
复制构造函数
Star(const Star &);
在下述情况下,程序将使用复制构造函数,分清什什时候调用很重要。
- 初始化一个同类的对象(左边未初始化)
- 编译器生成临时对象 (左边已经初始化),之后进行赋值
- 将对象作为参数按值传递给函数
- 函数按值返回局部对象
默认的复制构造函数将进行简单复制
注意:以下情况需要定义自己的复制构造函数
1. 使用new初始化的成员通常要求执行深度复制
2. 或者类可能包含需要修改的静态变量
赋值运算符
默认的赋值运算符处理同类对象之间的简单赋值。
- 不要将初始化与赋值混淆了!如果语句创建新对象,则是初始化;如果语句修改已有对象的值,则是赋值
Star sirius;
Star alpha = sirius; //初始化,复制构造
Star dogstar;
dogstar = sirius; //赋值
- 如果成员为类对象,则默认成员赋值将使用相应类的赋值运算符
- 如果需要显式的定义复制构造函数,则基于相同的原因,也都需要显示的定义赋值运算符
类的赋值运算符重载的一般形式:
Star & Star::operator=(const Star &)
//赋值运算符返回对象的引用,方便进行链式编程
编译器不会生成将一种类型赋值给另一种类型的赋值运算符
比如想将char*字符串直接赋值给Star对象
有如下两种解决办法:
- 重载赋值运算符:(运行速度快,代码多)
Star & Star::operator=(const char *) {...}
- 使用转换函数 (可能导致编译器混乱)
如使用单一参数的构造函数Star(const char *),将字符串转换成Star对象,然后进行赋值。
2. 其他类方法
构造函数
构造函数不同于其他类方法,因为它创建对象,而其他类方法只是被现有的方法调用。这是构造函数不被继承的原因之一。
析构函数
- 一定要定义显式析构函数来释放构造函数使用new分配的所有内存,并完成类对象所需任何特殊的清理工作。
- 对于基类,即使他不需要析构函数,也应提供一个虚析构函数
转换
- 使用一个参数就可以调用的构造函数(包括带默认值的多参数函数),来完成从参数类型 → 类类型 的转换。
Star(const char *); //converts char * to Star
Star(const Spectral &, int menbers = 1); //converts Spectral to Star
转换举例:
Star north;
north = "polaris";
//先调用Star(const char *)将polaris转换成Star对象,
//再调用Star & Star::operator=(const Star &)函数,进行对象赋值。
当然了,这个例子也可以直接提供Star & Star::operator=(const char *) 的赋值运算符重载进行直接转换。
在带一个参数的构造函数原型中使用explicit将禁止进行隐式转换,但仍允许显示转换
例如:
class Star
{
...
public:
explicit Star(const char *);
...
};
...
Star north;
north = "polaris"; // × explicit将禁止这种隐式转换
north = Star("polaris");// √ explicit允许进行显式转换
- 将对象转化为其他类型,应定义转换函数(特殊的C+运算符函数)—— operator typeName();
转换函数必须是类方法
Star::operator double() {}
Star::operator const char*() {}
应谨慎的使用转换函数,不必要不用,对于某些类,包含转换函数将增加代码的二义性
C++11支持对这种转换函数使用explicit
explicit operator int() const;
explicit operator double() const;
有了这些声明后,需要显式强制转换时才能调用说这些运算符
Stonewt poppins;
cout << int (poppins) <<endl; //显示调用转换函数 operator int() const
按值传递对象与传递引用
- 使用对象作为函数参数时,应选择按引用传递而不是按对象传递,因为这样效率更高,不用整个复制一份对象数据。
- 如果不修改对象,将参数声明为const引用
- 在继承使用虚函数时,被定义为基类引用的函数参数可以接受派生类,实现根据具体对象进行多态处理。
返回对象和返回引用
- 如果函数返回在函数中创建的临时对象,则不要返回引用,而是返回对象,这将返回一个临时对象的副本(临时对象会在函数结束时析构)
- 按引用可以返回调用函数的对象 或者是 作为参数传递给函数的对象
const Stock & Stock::topval(const Stock & s) const
{
if (s.total_val > this->total_val)
return s;
else
return *this;
}
使用const
- 使用const可以确保方法不修改参数
Star::Star(const char *s){...} //函数不会改变s指针指向的字符串
- 使用const可以确保不修改调用它的对象
void Star::show() const {...} //不会改变调用此方法的对象
- 返回类型 const Vector & 不修改返回对象的值
- 返回对象时,(2)语句2可能会引发错误
(1). net= force1 + force2
(2). force1 + force2 = net
使用 const Vector返回值
语句2将被禁用,而语句1可以正常使用,可以更好地避免可能引发的错误。
3. 公有继承的考虑因素
is - a关系
要遵循is-a关系,如果派生类不是一种特殊的基类,则不要使用公有派生。而是使用对象组合,将类对象作为另一个类的数据成员。。
什么不能被继承
构造函数不能被继承
析构函数也不能被继承
赋值运算符不能被继承
友元函数不属于成员函数,也不能被继承。
赋值运算符
使用new - 深度复制
成员是一个类对象,将自动调用它的赋值运算符
派生类使用new,显示调用基类的重载赋值运算符函数
赋值过程可能设计类型转换:
转换构造函数、转换函数、重载赋值运算符
私有成员与保护成员
基类私有数据成员
使用保护方法给派生类提供访问私有数据的权限,同时对类外关闭
虚方法
基类按引用或指针传递派生类对象参数,不能按对象传递参数(拷贝机制将阻止动态联编)。
析构函数
基类的析构函数应该是虚的
友元函数
并非成员函数,不能继承
可以通过强制类型转换,将派生类引用或指针转换为基类引用或派生类指针,然后使用转换后的指针或引用来调用基类的友元函数。
有关使用基类方法的说明
以公有方式派生的基类对象可以通过多种方式使用基类的方法:
- 派生类没有重定义的基类方法,直接调用
- 派生类自动调用基类的默认构造函数,或者成员初始化列表显示调用基类的指定构造函数。
- 派生类可以使用作用域解析运算符来调用公有的和受保护的基类方法。
- 派生类通过显式的函数式调用基类的赋值运算符重载函数。
- 派生类的友元函数可以通过强制类型转换,将派生类引用或指针转换为基类引用或指针,然后使用该引用或指针来调用基类的友元函数。