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

 //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. JSON.Stringify()和JSON.parse()的比较使用

    1.  JSON.Stringify() 将一个对象解析成字符串 <script> function myonclick() { var value = $('select option: ...

  2. python中 cmp

    python3.*版本中取消了数值之间的比较:cmp(x, y).取而代之的是 (x > y) - (x < y) 因为python中 false 为 0, true 为 1.

  3. File 操作

    文件乱码 服务器地址 try-with-resource 属性文件获取 文件排序 文件过滤 文件下载 流文件传递 文件乱码: WINDOWS系统桌面默认使用GBK,Linux系统默认使用UTF-8. ...

  4. 10 python 封装----@property的用法

    1.基本概念 在python中用双下划线开头的方式将属性隐藏起来(设置成私有的) #其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: class A ...

  5. UI5-文档-4.32-Routing with Parameters

    现在我们可以在overview和detail页面之间导航,但是我们在overview中选择的实际项目还没有显示在detail页面上.我们的应用程序的一个典型用例是在详细信息页面上显示所选项目的附加信息 ...

  6. python: no module named bz2

    https://stackoverflow.com/questions/8115280/importerror-no-module-named-bz2-for-python-2-7-2 centos6 ...

  7. python之commands模块

    要获得shell命令的输出只需要`cmd`命令就可以了, 需要得到命令执行的状态则需要判断$?的值, 在Python中有一个模块commands也很容易做到以上的效果.看一下三个函数:1). comm ...

  8. 关于HTTP协议及SOCKET通信

    一.HTTP 1.报文结构 HTTP:超文本传输协议,报文分为请求报文和响应报文. 2.端口(tomcat端口) http在熟知的80端口使用TCP的服务:tomcat的默认端口是8080 3.状态码 ...

  9. (1)shiro简介和第一个demo

    之前一直在用shiro开发,不过只是会使用,并没有深入了解,最近有时间学习了一下,把最近学习所得分享一下. shiro简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授 ...

  10. jremoting的功能扩展点

    1  InvokeFilter,实现此接口 可以在consumer端 与provider端的调用过程中拦截住请求调用. 已经实现的InvokeFilter包括 RetryInvokeFilter:实现 ...