漫谈C++:良好的编程习惯与编程要点【1】

举报
ShaderJoy 发表于 2021/12/30 00:01:05 2021/12/30
【摘要】 转载:melonstreet 带有指针数据成员的类:记得写三大件(拷贝构造函数、拷贝赋值函数、析构函数) C++的类可以分为带指针数据成员与不带指针数据成员两类,complex就属于不带指针成员的类。而这里要说的字符串类String,一般的实现会带有一个char *指针。带指针数据成员的类,需要自己实现class三大件:拷贝构造函数...

转载:melonstreet

带有指针数据成员的类:记得写三大件(拷贝构造函数、拷贝赋值函数、析构函数)

C++的类可以分为带指针数据成员与不带指针数据成员两类,complex就属于不带指针成员的类。而这里要说的字符串类String,一般的实现会带有一个char *指针。带指针数据成员的类,需要自己实现class三大件:拷贝构造函数、拷贝赋值函数、析构函数。


  
  1. class String
  2. {
  3. public:
  4. String (const char * cstr = 0);
  5. String (const String & str);
  6. String & operator = (const String & str);
  7. ~String();
  8. char * get_c_str() const {return m_data};
  9. private:
  10. char * m_data;
  11. }



如果没有写拷贝构造函数、拷贝赋值函数、析构函数,编译器默认会给我们写一套。然而带指针的类不能依赖编译器的默认实现——这涉及到资源的释放、深拷贝与浅拷贝的问题。在实现String类的过程中我们来阐述这些问题。

①析构函数释放动态分配的内存资源

如果class里有指针,多半是需要进行内存动态分配(例如String),析构函数必须负责在对象生命结束时释放掉动态申请来的内存,否则就造成了内存泄露。局部对象在离开函数作用域时,对象析构函数被自动调用,而使用new动态分配的对象,也需要显式的使用delete来删除对象。而delete实际上会调用对象的析构函数,我们必须在析构函数中完成释放指针m_data所申请的内存。下面是一个构造函数,体现了m_data的动态内存申请:


  
  1. /*String的构造函数*/
  2. inline
  3. String ::String (const char *cstr = 0)
  4. {
  5. if(cstr)
  6. {
  7. m_data = new char[strlen(cstr)+1]; // 这里,m_data申请了内存
  8. strcpy(m_data,cstr);
  9. }
  10. else
  11. {
  12. m_data= new char[1];
  13. *m_data = '\0';
  14. }
  15. }



这个构造函数以C风格字符串为参数,当执行

String *p = new String ("hello");
 



m_data向系统申请了一块内存存放字符串hello:

 

析构函数必须负责把这段动态申请来的内存释放掉:


  
  1. inline
  2. String ::~String()
  3. {
  4. delete[]m_data;
  5. }

 

②拷贝赋值函数与拷贝构造函数负责进行深拷贝


来看看如果使用编译器为String默认生成的拷贝构造函数与赋值操作符会发生什么事情。默认的拷贝构造函数或赋值操作符所做的事情是对类的内存进行按位的拷贝,也称为浅拷贝,它们只是把对象内存上的每一个bit复制到另一个对象上去,在String中就只是复制了指针,而不复制指针所指内容。现在有两个String对象:


  
  1. String a("Hello");
  2. String b("World");



a、b在内存上如图所示:

 

如果此时执行

b = a;
 



浅拷贝体现为:

 

存储World\0的内存块没有指针所指向,已经成了一块无法利用内存,从而发生了内存泄露。不止如此,如果此时对象a被删除,使用我们上面所写的析构函数,存储Hello\0的内存块就被释放调用,此时b.m_data成了一个野指针。来看看我们自己实现的构造函数是如何解决这个问题的,它复制的是指针所指的内存内容,这称为深拷贝

 


  
  1. /*拷贝赋值函数*/
  2. inline String &String ::operator= (const String & str)
  3. {
  4. if(this == &str) //①
  5. return *this;
  6. delete[] m_data; //②
  7. m_data = new char[strlen(str.m_data)+1]; //③
  8. strcpy(m_data,str.m_data); //④
  9. return *this
  10. }


这是拷贝赋值函数的经典实现,要点在于:
① 处理自我赋值,如果不存在自我赋值问题,继续下列步骤:
② 释放自身已经申请的内存
③ 申请一块大小与目标字符串一样大的内存
④ 进行字符串的拷贝

对于a = b,②③④过程如下:

 


同样的,复制构造函数也是一个深拷贝的过程:

 


  
  1. inline String ::String(const String & str )
  2. {
  3. m_data = new char[ strlen (str) +1];
  4. strcpy(m_data,str.m_data);
  5. }


另外,一定要在operator = 中检查是否self assignment 假设这时候确实执行了对象的自我赋值,左右pointers指向同一个内存块,前面的步骤②delete掉该内存块造成下面的结果。当企图对rhs的内存进行访问是,结果是未定义的。

 

 

 

 

文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/51794429

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。