很多程序猿在进行抽象之后, 不知道该如何设计类,哪些是有用的, 哪些是可忽略的, 现提供一些设计建议, 请斟酌取之。
1、是否需要构造函数
class test
{
public:
int length()
{
return len;
}
private:
int len;
};
当我们定义了一个int类型的变量, 我们如何给int变量赋初值呢, 首先:
int len = 0; // 这种方式肯定是不行的,语法规定数据成员在定义的时候不能直接初始化,那么在类里面有提供什么特殊的机制来初始化成员变量吗?
这时候,你就需要构造函数来帮你完成必要的初始化过程。
class test
{
public:
test()
{
len = 0;
}
int length()
{
return len;
}
int len;
};
构造函数主要是提供了初始化过程和完成内存分配。
但是有一些初始化过程比较特殊, 比如const的类型的数据如何进行初始化, C++提供了冒号语法:
class test
{
public:
test():len(0)
{
}
int length() const
{
return len;
}
private:
const int len;
};
所以,构造函数取决设计者的问题复杂度,这个可以灵活把握。注意:不显示的声明一个构造函数, 编译器会给你生成一个默认的构造函数。
2、如何设计数据成员
class test
{
public:
int len;
};
这是一个错误的示范,如何你的类如此设计, 那么你会将隐私暴露给所有人, 就好像你不穿衣服走到大街上,简直一览无余。
C++一个重要的思想就是信息隐藏,将一些不想让别人知道的信息封装起来, 所以,好的设计应当是这样:
class test
{
public:
test():len(0){}
void set_length(int v)
{
return len = v;
}
int get_length()const
{
return len;
}
private:
int len;
};
当然了,不一定所有的数据成员设计成protected活private就是好的设计, 比如一些状态,信号等可以考虑使用public,主要是与实际的使用场景有关。
3、析构函数
不是所有的类都需要析构函数,如果类并未分配资源,那么你的类就不需要析构函数。那么什么时候我需要析构呢?
如果你的类进行了资源分配,而且这些资源不会有成员函数负责回收,那么你就需要析构函数。特别是有new、malloc的场景。
class base
{
public:
int length;
};
class deviced:public base
{
public:
int len;
};
base *ptemp = new deviced;
delete ptemp; //编译出错
那么什么时候需要虚析构函数呢?
首先要有继承关系,其次类分配了资源, 需要回收。
4、拷贝构造函数
谈到拷贝构造, 就不得不来说说深浅拷贝的问题。
class test
{
public:
test (const char* pbuf=NULL)
{
if (pbuf)
{
m_pbuff = new char[strlen(pbuf)+1];
strcpy(m_pbuff, pbuf);
}
else
{
m_pbuff = NULL;
}
cout << "调用构造" << endl;
}
inline char * get_str(void)
{
return m_pbuff;
}
~test ()
{
if(m_pbuff)
{
delete[] m_pbuff;
m_pbuff = NULL;
}
cout << "调用析构" << endl;
}
private:
char * m_pbuff;
};
int _tmain(int argc, _TCHAR* argv[])
{ <p> test temp("hello world");
test temp1(temp);</p><p> cout << "temp " << temp.get_str()<< endl;
cout << "temp1 " << temp1.get_str()<< endl; </p> return 0;
}
先看一下运行结果:
先在分析一下:
编译通过了,运行后出现一堆的错误,为什么?!这就是浅拷贝带来的问题。
事实上在对象拷贝过程中,如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,对于类类型成员变量,调用其相应类型的拷贝构造函数。原型如下:
test(const test& str) {}
缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标也就是浅拷贝。
其中temp和temp1都指向了hello word的内存空间,调用get_str()方法时,当然可以使用,但是一旦有一个对象析构之后, 就会影响其他对象使用,主要有两种情况:
1、temp析构,temp1在使用的get_str()就会出现问题, 当前m_pbuff为NULL可能不是预期的结果
2、temp析构,temp1析构时就会出现上面的错误, 因为试图去释放一个已释放的空间。
由此C++引入了拷贝构造的方法,来解决浅拷贝带来的问题。
test::test (const test &v1)
{
m_pbuff = new char[strlen(v1.m_pbuff) +1];
strcpy(m_pbuff, v1.m_pbuff);
}
运行结果如下:
拷贝构造函数将同一个类的不同对象之间的联系切断了, 也应当是这样, 就好比你去银行存钱,你的账户可以被其他用户使用,这样的话你还会去存钱吗?呵呵比喻不是很恰当,但是C++的哲学就是这样, 需要多思考,才能进步。
5、赋值操作符
先看一下赋值符如何定义:
test& operator=(const test& v1){}
为什么需要赋值操作符呢?编译器默认所有的赋值操作都是按字节复制的,如果类中存在指针,那不就是又陷入了深浅拷贝的圈套了, 上面已经叙述原因,这里主要说一下赋值操作符的写法:
//写法1
test& operator=(const test& v1)
{
if (&v1 != this)
{
delete[] m_pbuff;
m_pbuff = new char[strlen(v1.m_pbuff) +1];
strcpy(m_pbuff, v1.m_pbuff);
}
return *this;
}
//写法2
test& operator=(const test& v1)
{
if (&v1 != this)
{
char* temp = new char[strlen(v1.m_pbuff) +1]; //如果失败,由析构负责回收资源
strcpy(temp, v1.m_pbuff);
delete[] m_pbuff;
m_pbuff = temp;
}
return *this;
}
//写法3 看网上大牛的代码, 感受颇多啊。
test& operator=(const test& v1)
{
if (&v1 != this)
{
test temp(v1);
char *temp1 = m_pbuff;
m_pbuff = temp.m_pbuff;
temp.m_pbuff = m_pbuff;
}
return *this;
}
事实上,这是借助了以上自定义的拷贝构造函数。定义了局部对象temp,在拷贝构造中已经为temp的成员指针分配了一块内存,所以只需要交换指针即可,简化了程序的设计,因为temp是局部对象,离开作用域会自动析构,避免了内存泄露,大神的思路确实不错, 值得学习和深思。
6、关系操作符
关系操作符的定义如下:
test::operator 关系符(const test& v){}
是否需要关系操作符的原则是:创建类的有序集合,就必须提供关系操作符。也就是说,类提供了比较相关的操作,比如类的两个实例进行大小比较操作等等。
7、const的使用
如果成员函数有引用参数,只有当函数想改变参数时才不需要const的来修饰,否则都需要。这不仅是一个良好的习惯, 而且会减少出错的几率。
class test
{
public:
test (const char* pbuf=NULL)
{
if (pbuf)
{
m_pbuff = new char[strlen(pbuf)+1];
strcpy(m_pbuff, pbuf);
}
else
{
m_pbuff = NULL;
}
cout << "调用构造" << endl;
}
inline char * get_str(void)
{
return m_pbuff;
}
~test ()
{
if(m_pbuff)
{
delete m_pbuff;
m_pbuff = NULL;
}
cout << "调用析构" << endl;
}
private:
char * m_pbuff;
};
int fun(const test& v)
{
cout << v.get_str() << endl; //error const对象不能调用非cosnt成员函数。
}
这里需要inline char * get_str(void)改为inline char * get_str(void)const 就可以了, 也应当是这样的,关于const请看http://blog.csdn.net/Eric_Jo/article/details/4138548
这里不在啰嗦了。
8、总结
C++更适合于哪些喜欢思考的程序猿,希望能与君共勉。