C++ Primer Plus 12章(一)
早上阅读了一个小时,思考了良久,还是决定开始写下一些见解来记录自己所得。同时也给别人一些借鉴,也能得到反馈。
之前的11章,我会慢慢的补充上去。
在讲内容之前,我想先谈论一下这本书。从我真正静下心来每天花一个小时以上的时间去阅读后,才逐渐明白书里讲的内容。关于这本C++的书,有些章节写的极好,但有些章节我觉得还差了一点详细的东西,可能不太适合初学者,至少对C++和C有了一定了解之后,看这本书是很好的。
下面,进入正题:
(1)类中的成员使用指针和new进行动态内存分配
好处:假设我们要定义一个people类,每个people类里面有人名这个成员,我们使用字符数组类型来存储名字,但是我们究竟要使用多大的字符数组空间呢?用5个会不会小了,用20个又会不会多了?如果当我们创建的对象超过成千上万时,那么将会对内存空间造成极大的浪费。因此,此时使用new运算符来动态的为名称指定空间大小是最合适的,并且它会在对象结束的时候使用delete释放它。
坏处:这里我使用书上一个实际的例子进行示范,坏处在哪里。
定义一个stringBad 类:
class StringBad
{
private:
char *str;//字符指针
int len;
static int num_strings;//每创建一个对象时,此数值加1,因为使用了static修饰,无论创建了多少对象,该成员被所有对象共享,只创建一个副本
public:
StringBad(const char *s);//自定义构造函数
StringBad();//默认构造函数
~StringBad();析构函数
}
然后是它对应成员函数的实现方法:
StringBad::StringBad(const char *s)
{
len = std::strlen(s);//注意strlen函数获取字符串长度时,不包括最后的“\0”字符
str = new char[len+1];
std::strcpy(str,s);
num_strings++;
}
StringBad::StringBad()
{
len = 4
str = new char[4];
std::strcpy(str,"C++");
num_strings++;
}
StringBad::~StringBad()
{
--num_strings;
delete []str;
}
最后是在main中创建对象,并初始化它,这也是问题所在,请注意:
void callme1(StringBad &);
void callme2(StringBad);
int main()
{
using std::endl;
StringBad headline1("a");
StringBad headline2("b");
StringBad sports("c");
callme1(headline1);
callme2(headline2);//这里的调用是关键
StringBad sailor = sports;//这里和下面的两种对象初始化,都是存在问题的
StringBad knot;
knot = headline1;
return 0;
}
void callme1(StringBad & rsb)
{
cout<<rsb<<endl;
}
void callme2(StringBad sb)
{
cout<<sb<<endl;
}
分析:
在主函数中,创建如下两个对象并赋值的时候,其实可以知道,这两种初始化方法既没有调用默认的构造函数,也没有调用自定义的构造函数,而是使用了一种新的函数:复制构造函数(浅复制)。
StringBad sailor = sports;//这里和下面的两种对象初始化,都是存在问题的
StringBad knot;
knot = headline1;
这里导致的结果就是,本来sports对象创建时,会调用自定义的构造函数,生成一个指针,指向字符串str所在的内存地址;但是现在将sports赋值给sailor对象时,sailor对象也会生成一个指针指向字符串str所在的内存地址;即这两个对象指向了同一个内存地址。
那么在程序结束后调研析构函数进行内存释放时,先释放sailor是可以正常释放的,但是再去释放sports时,就会出错,因为sports指向的内存地址已经被释放了。
同理,创建knot时,释放knot时,也会出现类似的错误。
这就是类中指针成员的坏处。
解决方法:显示的定义一个复制构造函数(深复制)
StringBad::StringBad(const StringBad &st)
{
num_strings++;//每创建一个对象时,此数值加一
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
}
在这里面,我们重新使用new分配了内存空间,并有一个新的指针指向新的内存地址,但是内存地址里面存储的内容是一样的。
以此,可以解决指针成员初始化带来的问题。
(2)赋值运算符
上述问题,并不仅仅是因为复制构造函数和指针造成的,还有一些其他问题。比如赋值!
StringBad sailor = sports;//这里和下面的两种对象初始化,都是存在问题的
StringBad knot;
knot = headline1;
C++中,是允许可以直接将同类的对象进行直接赋值初始化的,但是它同样会带来和复制构造函数一样的问题,两个对象的指针指向同一个内存地址,释放一个之后,另一个就没了,不能正常释放。
解决方法:编写赋值运算符
相当于对 “=”运算符进行重载,只能由类成员函数来完成。
StringBad & StringBad::operator=(const StringBad &st)
{
if(this == &st)
return *this;
delete []str;
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
return *this;
}