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

 //test.h
#ifndef MYSTRING_H
#define MYSTRING_H
class MyString
{
char* m_str;
public:
MyString(char* str="");
~MyString();
void Display();
};
#endif //MYSTRING_H //test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "MyString.h"
#include<iostream>
#include<cstring>
using std::endl;
using std::cout; MyString::MyString(char* str)
{
m_str = new char[strlen(str) + ];
memset(m_str, , strlen(str) + );
strcpy(m_str,str);
} MyString::~MyString()
{
cout << "destructor" << endl;
delete[] m_str;
//m_str = 0; } void MyString::Display(){
cout << m_str << endl;
} //demo.cpp
#include"MyString.h"
#include<iostream>
using std::endl;
using std::cout; int main(){
MyString a("aaaaaa");
MyString b = a;//调用默认拷贝构造函数,此时b对象,和a对象都是指向同一块内存空间,当析构函数调用时,那么同一块内存空间会被释放两次,从而产生运行时错误。
a.Display(); return ;
}

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

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

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

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

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

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

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

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

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

禁止拷贝:

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

 #ifndef MYSTRING_H
#define MYSTRING_H
class MyString
{
char* m_str;
void AllocMemAndCpy(char* other);
MyString(const MyString& ms){}
MyString& operator=(const MyString& other){} public:
MyString(char* str="");
~MyString(); void Display();
};
#endif //MYSTRING_H

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

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

 class Empty {};
Empty(); // 默认构造函数
Empty( const Empty& ); // 默认拷贝构造函数
~Empty(); // 默认析构函数
Empty& operator=( const Empty& ); // 默认赋值运算符
Empty* operator&(); // 取址运算符
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. CENTOS系统安装及初始化配置相关

    一,配置网卡: 1,设置网卡ip地址:vi   /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0TYPE=EthernetONBOOT=yes ...

  2. leetcode521

    public class Solution { public int FindLUSlength(string a, string b) { : Math.Max(a.Length, b.Length ...

  3. remote Request

    import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; ...

  4. 使用 C++11 编写类似 QT 的信号槽——上篇

    了解 QT 的应该知道,QT 有一个信号槽 Singla-Slot 这样的东西.信号槽是 QT 的核心机制,用来替代函数指针,将不相关的对象绑定在一起,实现对象间的通信. 考虑为 Simple2D 添 ...

  5. redis之RDB持久化与AOF持久化

    Redis是一个键值对数据库服务器,服务器中通常包含着任意个非空数据库,而每个非空数据库中又可以包含任意个键值对,为了方便起见,我们将服务器中的非空数据库以及它们的键值对统称为数据库状态. 因为Red ...

  6. from __future__ import division

    导入python未来支持的语言特征division(精确除法),当我们没有在程序中导入该特征时,"/"操作符执行的是截断除法(Truncating Division),当我们导入精 ...

  7. Linux就业技术指导(五):Linux运维核心管理命令详解

    一,Linux核心进程管理命令 1.1 ps:查看进程 1.1.1 命令解释 功能说明 ps命令用于列出执行ps命令的那个时刻的进程快照,就像用手机给进程照了一张照片.如果想要动态地显示进程,就需要使 ...

  8. jquery 判断checkbox是否被选中问题

    1.jquery库2以上 $("#checkbox_check").click(function(){ alert($(this).prop("checked" ...

  9. eclipse新建maven项目出现红叉解决办法

    新建的maven项目,项目内代码及pom.xml没有任何问题,但项目上就是有红叉,这时点开Markers(Window–>show veiw–>Markers),查看错误的详细信息,信息上 ...

  10. 代理URI和服务器URI的不同

    [代理URI和服务器URI的不同] 1.向Web服务器直接发送请求时,路径为相对路径(不包含域名). 2.当向代理发送请求时,路径为绝对路径(包含域名). 参考<HTTP权威指南>6.5. ...