`

放假了,n长时间没有用过c++的class了,回顾一下.

阅读更多
1、你的类需要构造函数么?
对于一个复杂的类,我们要隐藏掉复杂的细节,而让用户看到一个简洁的界面,
构造函数对于隐藏内部的工作方式非常重要,因为他设定了整个程序的初始状
态,我们只把需要由用户设定的数据,作为参数,让他们来初始化为恰当的值
而其他我们都有一个默认的设置,这就隐藏掉了内部的细节。我们甚至需要思
考这个类是能产生单个实例还是多个,这就需要我们思考是否应该将构造函数
私有。


2、你的那些数据应该私有?
通常使用公有的数据成员不是个好事,因为将失去对这个数据对象的控制,使用
者可以随意修改这个值,而我们又无法在这个更改的过程中提供一个有效的合法
检查机制,同时我们也无法提供一个检测到这种更新的方法从而同时保证与之相
关联的数据得到及时的更新。下面我们思考一下容器向量类设计:
template<class T>
class Vector
{
 public:
   int length;
 private:
  T *v;
};

如果我们把length的长度声明为公有的,我们将无法保证当前的length是否真正的反
映了当前Vector的实际大小。我们同样也无法保证这个值是否在合法的范围之内。因
为我们无法知道使用者何时访问length变量、是否修改这个变量以及如何修改这个变
量,我们即使检测到length的变化,而这个变化也是滞后的,甚至是不可预料的,我
们就无法及时的更新的Vector的容量。所以我们这么设计:
template<class T>
class Vector
{
public:
 int get_length()const;
 void set_length(int len);
private:
 int length;
 T *v;
};

我们可以在set_length()函数中提供合法的范围检查以及及时地更新与之相关的数据。


3、你的类需要一个无参的构造函数么?
如果一个类有了其他的构造函数,而你又想申明一个不需要显式初始化的对象,则必须
需要一个无参的构造函数,这样的对象有时你会忽视。以为你可能是有意这么做的,但
你必须考虑到,当这个类试图生成一个对象数组时,所要面临的问题。
例如:
class Point
{
 public:
  Point(int x_Pos,int y_Pos):x(x_Pos),y(y_Pos)
  {
  }
 private:
  int x,y;
};

当我们试图生成该类的对象数组时,却是非法的。
Point p[200];

虽然对一个大的对象,定义一个对象数组,导致大量对象的初始化,可能要付出性能的代价,
但必须要权衡是否因此而禁止对象数组。


4、是否要初始化所有的数据成员:
构造函数的功能,就是初始化生成对象的状态。对象的状态是由他的数据成员反映的。因此,
构造函数为每个数据设置一个合理的初值是必要的,如果没有做到这一点,就可能导致错误。
当然,有时,一些数据的值只有在对象存在一段状态之后才有意义,这时候需要你思考设置
一个什么样的初值。


5、类需要析构函数么?
对于一个简单的类,只需要默认的析构函数,如果要深入考虑一个类的要做些什么,那么是否
需要析构函数是一个很明显的问题了。只需要考虑你是否new了一些资源,而这些资源不会自动
释放就足够了。


6、类需要虚析构函数么?
当然如果你的类不是用作基类的,当然不需要考虑虚析构函数的问题。下面我们看看,虚析构函
数的使用:
class A
{
 //...
};
class B: A
{
 //...
};
int main()
{
 A *ap = new B;
 delete ap;
 return 0;
}

只这里如果没有虚析构函数就会出现问题,可见作为基类一个虚析构函数是非常重要的,因为我们
经常用基类的指针去指向他的子类的对象。


7、你的类需要深拷贝构造函数么?
  如果一个类复制仅仅是想简单的拷贝其数据成员,那就不需要。但有时是需要的,例如类
String
Class String
{
public:
 String();
 String(const char* s);
 ~String();
private:
 char *data;
};

如果没有深拷贝构造函数的话,复制String对象就是简单复制他们他的data成员,复制完后,两个data
成员就会指向同样的内存,当两个对象被销毁的时候,这个内存就会被释放两次。
  有时候你可能会禁止使用者复制一个类对象,这往往是出于性能的考虑,为了禁止用户不当的使用。
例如:
class SomeThing
{
public:
  //...
private:
  //许多的数据成员;
};

如果你允许复制,使用者总会无意的在不恰当的时机使用这项功能,例如:
void func(SomeThing st){}
这往往是给了使用者不恰当使用的机会。如果你禁用复制这个功能,他就会想,哦,这样做不合适,就会写出: void func(SomeThing &st){}
这里我们只需要将拷贝构造函数私有(可能还有赋值操作符号):
class SomeThing
{
 public:
  //...
 private:
  SomeThing(const Thing&);
  SomeThing &operator=(const Thing&);
};

当然,除了一个很大的对象的复制行为给予禁止,大多数类我们不会这么做。


8、你的类需要一个赋值操作符么?
如果你需要一个拷贝构造函数的时候,大多会需要赋值操作符类,许多人会把赋值操作的原型搞错
类String的原型为:
String::String &operator=(const String&)
{
 if(&s != this)
  {
     delete []data;
     date = new char[ strlen(s.data)+1 ];
     strcpy(data,s.data);
   }
   return *this;
};

这里的参数是const的,这么做的理由是:1)复制毕竟不改变原来的对象;2)使用const更具有通用性,
如果没有const你就无法去复制一个const对象,而如果加上const则不仅可以复制const对象,一般的对象
也同样胜任。这点在拷贝构造函数中也是一样的。许多人写的时候就会漏写if的判断,似乎自己赋值给对象本身不是什么严重错误。但是由于:
1)自我赋值本身就是一个无意义的操作;2)由于上述的实现方式,在赋值拷贝之前已经把自身delete掉
了,所以在没有实施复制之前就把原对象销毁了。


9、你需要定义关系运算符么?
如果你在逻辑上支持operator==和operator!=就可能会很有好处,类似的你的类中有某些排序关系,那么你还需要提供类似operator>,operator<之类的操作符,即使你不想让用户直接使用关系符,只要你想创建你的类型的有序集合,你就必须提供关系操作符.


10、适当的声明成员函数为const的。
例如前面的例子:
template<class T>
class Vector
{
 public:
  int get_length()const;
  //...
};

如果我们不把get_length声明为const,就会遇到下面问题:
int f(const Vector<T>& v,int n)
{
 int k = v.length();
 return k > n ? k : n; 
}

如果get_length()不是const的话,v.length()就会出现问题。
总结:设计一个c++的类需要考虑许多东西,而不想java。换言之,c++更适合喜欢思考的程序员。



分享到:
评论
1 楼 healer_kx 2008-07-14  
其实我想评价你其他的C++文章。

相关推荐

Global site tag (gtag.js) - Google Analytics