1、数据抽象与封装
数据抽象是一种接口和实现相分离的编程技术,设计者关心的是如何实现这些接口,而使用者仅仅知道这些接口,抽象地考虑这些接口做什么的就可以了,不必去考虑如何实现这一层次。
封装是将低层次的元素结合起来组成高层次的实体,比如函数是封装的一种形式,函数本身可以看做一个大的实体,封装了函数里面所执行的实现细节,于是被封装的元素隐藏了他们的实现细节,可以通过调用该函数 来执行函数封装的功能,但不能直接访问函数所执行的语句。同理,类也是一个封装的实体,类是许多成员的大集合,隐藏了实现该类类型的成员。
标准库类型vector在使用的时候只考虑它能执行的操作,即值考虑接口,所以具有抽象特性,同时外部又无 法了解和访问该类型是如何实现其细节的,所以又具有封装特性。但是数组就不一样了,用户可以通过访问数 组的内存来直接对数组进行操作,所以虽然数组的定义类似于vector,但数组既不具有抽象特性,也不具有封装特性。
2、类的复制构造函数与默认构造函数
在类中是否需要显示定义类的复制构造函数,就看在进行类的对象复制的时候是否就相当于复制对象的所有数据成员,如果不是,就需要显示定义复制构造函数。因为隐式的复制构造函数是按照值进行复制的,因此,对于data成员的隐式复制完成之后,两个对象的data成员将指向相同的内存地址,在销毁的时候,会被释放两次。
class string
{
public:
string();
string(const string *str);
string & operator=(const string *str);
~string();
private:
char *data;
}
所以常常是类中有指针成员时,要求定义复制构造函数。
例如:string str1; //调用的是默认构造函数
string str2(str1); //调用的是copy构造函数
str1=str2; //调用的是赋值操作符
string str3=str1; //调用了copy构造函数、赋值操作符
copy构造函数与赋值操作符很容易混淆,但同时也很容易区分,有新对象产生的时候调用的一定是构造函数,如上述的string str3,如果没有定义新对象,如上述的str1=str2,就不会有构造函数被调用,调用的是赋值操作。
3、要考虑用重载以避免隐含的类型转换
比如:在字符串类String,class String{...},要进行比较,
bool operator==(const String &,const String&)
{
//代码中某处有下面的语句
if(caseOfString=="mingzi")
{...}
}
很明显,"程序中就相当于if(caseOfString==String("mingzi")),而我们只是为了读取"mingzi",而没有必要去复制。所以要用重载以避免转换,如
bool operator==(const String &str,const char *str1),bool operator==(const char *str1,const String &str)。