我们知道当调用默认拷贝构造函数时,一个对象对另一个对象初始化时,这时的赋值时逐成员赋值。这就是浅拷贝,当成员变量有指针时,浅拷贝就会在析构函数那里出现问题。例如下面的例子:

  1. //test.h
  2. #ifndef MYSTRING_H
  3. #define MYSTRING_H
  4. class MyString
  5. {
  6. char* m_str;
  7. public:
  8. MyString(char* str="");
  9. ~MyString();
  10. void Display();
  11. };
  12. #endif //MYSTRING_H
  13.  
  14. //test.cpp
  15. #define _CRT_SECURE_NO_WARNINGS
  16. #include "MyString.h"
  17. #include<iostream>
  18. #include<cstring>
  19. using std::endl;
  20. using std::cout;
  21.  
  22. MyString::MyString(char* str)
  23. {
  24. m_str = new char[strlen(str) + ];
  25. memset(m_str, , strlen(str) + );
  26. strcpy(m_str,str);
  27. }
  28.  
  29. MyString::~MyString()
  30. {
  31. cout << "destructor" << endl;
  32. delete[] m_str;
  33. //m_str = 0;
  34.  
  35. }
  36.  
  37. void MyString::Display(){
  38. cout << m_str << endl;
  39. }
  40.  
  41. //demo.cpp
  42. #include"MyString.h"
  43. #include<iostream>
  44. using std::endl;
  45. using std::cout;
  46.  
  47. int main(){
  48. MyString a("aaaaaa");
  49. MyString b = a;//调用默认拷贝构造函数,此时b对象,和a对象都是指向同一块内存空间,当析构函数调用时,那么同一块内存空间会被释放两次,从而产生运行时错误。
  50. a.Display();
  51.  
  52. return ;
  53. }

所以此时默认拷贝构造函数就出现问题,此时应该提供自己的拷贝构造函数,来实施深拷贝。

在类里面添加拷贝构造函数

  1. void MyString::AllocMemAndCpy(char* other){//类里面的工具函数,负责分配内存和拷贝内存中的值
  2. int len = strlen(other) + ;
  3. m_str = new char[len];
  4. memset(m_str,,len);
  5. strcpy(m_str,other);
  6. }
  7.  
  8. MyString::MyString(const MyString& other){//拷贝构造函数
  9. AllocMemAndCpy(other.m_str);
  10. }
  1. #include"MyString.h"
  2. #include<iostream>
  3. using std::endl;
  4. using std::cout;
  5.  
  6. int main(){
  7. MyString a("aaaaaa");
  8. MyString b = a;
  9. a.Display();
  10. b.Display();
  11. return ;
  12. }

此时运行结果就是正确的了,不会出现运行时错误了。

注意:当类中的成员变量有指针时,就要小心了。此时可能就需要拷贝构造函数了,还有就是类中有共享内存时,这是要在析构函数中计算还剩下多少对象,只有剩下最后一个对象被销毁时才能delete那块共享内存。

默认的赋值运算符也是浅拷贝,如果不添加自己的等号运算符,这个程序还是会出现运行时错误

  1. #include"MyString.h"
  2. #include<iostream>
  3. using std::endl;
  4. using std::cout;
  5.  
  6. int main(){
  7. MyString a("aaaaaa");
  8. MyString b = a;
  9. a.Display();
  10. b.Display();
  11.  
  12. MyString c;
  13. c.Display();
  14. c = a;//程序在此处调用了默认的赋值运算符(=operator),默认的赋值运算符也是浅拷贝
  15. //因此这时出现的运行时错误,也是在调用析构函数时,销毁两次同一块内存,为了使剩下成功运行,必须提供自己的等号运算符。
  16.  
  17. return ;
  18. }

上面的代码需要特别注意的是c=a;,这种赋值会造成被删除的两个指针指向同一块内存,在析构函数调用时,同一块内存会被析构函数释放两次。但是如果c=c;,这句代码就不会造成上述的错误,因为自己给自己赋值并没有使两个对象的指针同时指向一个块内存,就自然只会调用一次析构函数,从而释放一次对象。但是为了顾及中情况,应该添加如下的赋值运算符函数。

  1. MyString& MyString::operator=(const MyString& other){
  2. if (this ==&other)//赋值对象与被赋值对象是同一个对象。即语句a=a;
  3. return *this;
  4. delete m_str;//因为赋值对象与被赋值对象不是同一个对象,因此 被赋值对象之前指向的内容应该被删除,即a=c;那么a之前指向的内存应该被释放。
  5. AllocMemAndCpy(other.m_str);
  6. return (*this);
  7. }

禁止拷贝:

有时候有些对象时独一无二的,那么是独一无二的对象就禁止拷贝,禁止拷贝的办法就是把operator=() 与拷贝构造函数都声明成私有的,甚至是空函数都可以

  1. #ifndef MYSTRING_H
  2. #define MYSTRING_H
  3. class MyString
  4. {
  5. char* m_str;
  6. void AllocMemAndCpy(char* other);
  7. MyString(const MyString& ms){}
  8. MyString& operator=(const MyString& other){}
  9.  
  10. public:
  11. MyString(char* str="");
  12. ~MyString();
  13.  
  14. void Display();
  15. };
  16. #endif //MYSTRING_H

此时拷贝构造函数和operator=()都是声明在private中,并且都是空函数。此时Test a=c; b=c;这样的语句在编译时就会报错。

我们知道当我们不提供构造函数时,编译器会提供默认构造函数,当我们不提供拷贝构造函数时,编译器会提供默认拷贝构造函数,那么再创建一个空的类时,编译器到底提供什么呢?

  1. class Empty {};
  2. Empty(); // 默认构造函数
  3. Empty( const Empty& ); // 默认拷贝构造函数
  4. ~Empty(); // 默认析构函数
  5. Empty& operator=( const Empty& ); // 默认赋值运算符
  6. Empty* operator&(); // 取址运算符
  7. const Empty* operator&() const; // 取址运算符 const

上面的就是一个空类,我们在使用时要注意,有时候这些编译器默认提供的不适合,我们要自己写。

构造函数constructor 与析构函数destructor(五)的更多相关文章

  1. 构造函数constructor 与析构函数destructor(一)

    构造函数定义:构造函数c++中在创建对象时自动调用,用来初始化对象的特殊函数. (1)构造函数的名字必须与类的名字相同,不能有返回值,哪怕是void 也不行. (2)通常情况下构造函数应声明为公有函数 ...

  2. 构造函数constructor 与析构函数destructor(四)

    拷贝构造函数:拷贝构造函数就是在用一个类对象来创建另外一个类对象时被调用的构造函数,如果我们没有显示的提供拷贝构造函数,编译器会隐式的提供一个默认拷贝构造函数. 拷贝构造函数的定义是X(const X ...

  3. 构造函数constructor 与析构函数destructor(二)

    (1)转换构造函数 转换构造函数的定义:转换构造函数就是把普通的内置类型转换成类类型的构造函数,这种构造函数只有一个参数.只含有一个参数的构造函数,可以作为两种构造函数,一种是普通构造函数用于初始化对 ...

  4. 构造函数constructor 与析构函数destructor(三)

    (1)构造函数初始化列表: 1 class Test{ 2 int i; 3 public: 4 Test(int vi):i(vi){}//这里的从冒号开始,到右大括号结束,这一段是构造函数初始化列 ...

  5. GCC的__attribute__ ((constructor))和__attribute__ ((destructor))

    通过一个简单的例子介绍一下gcc的__attribute__ ((constructor))属性的作用.gcc允许为函数设置__attribute__ ((constructor))和__attrib ...

  6. javascript工厂函数(factory function)vs构造函数(constructor function)

    如果你从其他语言转到javascript语言的开发,你会发现有很多让你晕掉的术语,其中工厂函数(factory function)和构造函数(constructor function)就是其中的一个. ...

  7. 【转】c++析构函数(Destructor)

    创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存.关闭打开的文件等,这个函数就是析构函数. 析构函数(Destructor)也 ...

  8. C++——构造函数 constructor

    What is constructor C++中,如果你想要创建一个object,有一个函数会自动被调用(不需要programmer显式调用 ),这个函数就是constructor; construc ...

  9. 类(class)、构造函数(constructor)、原型(prototype)

    类 Class 类的概念应该是面向对象语言的一个特色,但是JavaScript并不像Java,C++等高级语言那样拥有正式的类,而是多数通过构造器以及原型方式来仿造实现.在讨论构造器和原型方法前,我可 ...

随机推荐

  1. 0_Simple__simplePitchLinearTexture

    对比设备线性二维数组和 CUDA 二维数组在纹理引用中的效率 ▶ 源代码.分别绑定相同大小的设备线性二维数组和 CUDA 二维数组为纹理引用,做简单的平移操作,重复若干次计算带宽和访问速度. #inc ...

  2. 很详细的curl命令使用大全

    可以看作命令行浏览器 1.开启gzip请求 curl -I http://www.sina.com.cn/ -H Accept-Encoding:gzip,defalte 2.监控网页的响应时间 cu ...

  3. 39. 在linux下装好Tomcat要给 tomcat/bin/下面所有.sh的文件执行权限

    chmod a+x *.sh(赋予可执行的权限)

  4. Redis hash数据结构

    1, 新增一个 hash 或者 新增数据 => hset key field value 2, 获取某个字段值 => hset key field 3, 获取所有字段值 => hge ...

  5. linux 安装svn服务器

    一.下载 http://subversion.tigris.org/downloads/subversion-1.6.1.tar.gz http://subversion.tigris.org/dow ...

  6. Declaration terminated incorrectly 讨厌 这样就不可以了

    #include "vcl.fctreeview.hpp"#include "RM_Class.hpp"#include "RM_Common.hpp ...

  7. 队列queue实例(生产者和消费者模型)

    import queue, threading, time q = queue.Queue(maxsize=10)def producter(n): count = 1 while True: q.p ...

  8. windows7安装node

    一.在官网下载node 二.按照提示进行安装 三.安装好的目录结构 四.测试是否安装好了node 首先按快捷键win+r,在运行窗口输入cmd,调出命令提示窗口,在命令提示窗口中输入path查看nod ...

  9. visibility和display

    visibility: hidden----将元素隐藏,但是在网页中该占的位置还是占着.display: none----将元素的显示设为无,即在网页中不占任何的位置.

  10. 拓扑排序获取所有可能序列JAVA实现

    在看算法基础这本书,看到有向无环图,其中介绍到了拓扑排序,讲到了获取拓扑序列的方法,结合自己的理解,用JAVA代码实现了获取所有可能序列,水平有限,效率什么的就没有考虑,下面贴上代码: package ...