你的类需要一个构造函数吗?
你的成员变量是私有的吗?
如果类设计者使用的是一个成员变量,那么设计者必须保证这个成员变量在任何时候都能够正确反映实际数量的数值,因为没有办法知道类的使用者什么时候会来访问这个信息。
你的类需要一个无参的构造函数吗?
如果你想声明该类的对象可以不必显式地初始化它们,则必须显式地写一个无参的构造函数。
是不是每个构造函数都初始化所有的数据成员?
一般来说,每个构造函数都要为所有的数据成员设置经过明确定义的值。如果没有做这一点,就很可能导致错误。当然,不一定是这样的,类会有一些数据成员只在它们的对象存在了一定时间之后才有意义。
你的类需要析构函数吗?
如果该类分配了资源,但这些资源不会由成员函数自动释放。就需要析构函数。
你的类需要一个虚析构函数吗?
声明一个派生类的对象的同时也自动声明了一个基类的对象。派生类的对象也可以认为是其基类的对象,但是反之不然。因此,C++允许一个基类对象的指针指向其派生类的对象(派生类包含基类的成员函数),但不允许一个派生类对象的指针指向其基类的对象(基类不一定包含派生类的成员函数)。
所以,当基类和派生类都有相同的成员函数时,基类指针无论是指向基类对象还是指向派生类对象,调用的都是基类的成员函数。(当然派生类指针指向派生类对象,调用的是派生类的成员函数)
下面有一例,说明这个问题:
#include <iostream.h>
class A{
public:
//虚函数第一次在基类中定义时,不能省略关键字virtual
virtual void show(){cout<<”A::show\n”};
};
class B: public A{
public:
void show(){cout<<”B::show\n”};
};
void main() {
A a,*pa;
B b;
pa = &a; pa->show();
pa = &b; pa->how();
}
程序运行结果为:
A::show
B::show
如果类A的成员函数show()不是虚函数,程序运行的结果应为:
A::show
A::show
另外这样的程序是非法的:
A a;
B b, *pb;
pb = (B *)&a;
pb->show();
这样的程序可能能运行(因为我没试过),但是这是一个很不好的习惯。如果基类A中没有成员函数show(),程序运行后会出现异常情况。这是因为程序把基类指针&a强制转化为派生类指针pb,然后通过pb引用对象 a中并不存在的派生类成员show()。
你的类需要复制构造函数吗?
关键在于复制该类的对象是否就相当于复制其数据成员和基类对象。如果不相当,就需要复制构造函数。如:
int x=0,y=1;
x = y;
就是类似的复制类对象的情况
如果不想用户能够复制类的对象,就声明复制构造函数(可能还有赋值操作符)为私有的:
class Thing {
public:
//…
private:
Thing (const Thing&);
Thing& operator= {const Thing&};
};
你的类需要一个赋值操作符吗?
如果需要复制构造函数,同理多半也会需要一个赋值操作符。如果不想用户能够设置类中的对象,就将赋值操作符私有化。类X的赋值由X::operator=来定义。通常,operator=应该返回一个X&,并且由
return * this;
结束以保证与内建的复制操作符一致。
你的赋仁操作符能正确地将对象赋给对象本身吗?
有可能在还没有实施复制之前就把原对象销毁了。例如:
class String{
public:
string&operator={const string& s};
private:
char*data;
}
//很明显但不正确的实现
String& string::operator={const String& s}
{
delete [] data;
data = new char[strlen{s.data}+1];
strcpy {data, s.data};
return *this;
}
//正确的实现方法1
String& string::operator={const String& s}
{
if (&s != this) {
delete [] data;
data = new char[strlen{s.data}+1];
strcpy {data, s.data};
}
return *this;
}
//正确的实现方法2
String& string::operator={const String& s}
{
char* newdata = new char[strlen(s.data +1)];
strcpy {newdata, s.data};
delete [] data;
data = newdata;
return *this;
}
你的类需要定义关系操作符吗?
如果你的类逻辑上支持相等操作,那么提供operator==和operator!=就可能会很有好处。类似地,如果你的类的值有某种排序关系,那就可能会想提供余下的关系操作符。即使不希望用户直接使用关系符,也可能需要这些关系操作符。只要它们想创建你的类型的有序集合,你就必须提供关系操作符。
删除数组时你记住了用delete[]吗?
记得在复制构造函数和赋值操作符的参数类型中加上const了吗?
复制构造函数应该是X::X(const X&)。实际上,由于绑定一个非const引用到一个临时的对象是非法的,使用X::X(X&)作为复制构造函数不会允许复制任何特殊表达式的结果。同样的道理也适用于赋值:使用X::operator=(const X&),而不是X::operator=(X&)。
如果函数有引用参数,它们应该是const引用吗?
只有当函数想改变参数时,它才应该有不用const声明的引用参数。
记得适当地声明成员函数为const的了吗?
如果确信一个成员函数不用修改它的对象,就可以声明它为const,这样就把它用于const对象了。例如:
template<class T> class Vactor {
public:
int length() const; //得到长度,int length();是错误
int length(int); //设置长度,返回前一个
// …
}
否则,我们会遇到类似下面的问题:
template<class T>
int padded_length(const Vector<T>& v, int n) {
int k = v.length(); //oops!
return k>n?k:n;
}
除非对length的声明中出现了前面所说的const,否则标注了oops!的行将不会编译,因为v是const引用。