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

 //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. as3 三行三列 布满9个为一个界面

    var n:int=int(iconIndex/3); e.x =(int(n/3)*3+iconIndex%3)*557; e.y = int(iconIndex / 3) % 3 * 260; i ...

  2. 15 python re 模块

    1.基础知识 正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎 正则表达式的大致匹配流程:依次拿出表达式和文本中的字符比较, 如果每一个字符都能匹配,则匹配成功:一旦有匹 ...

  3. UI5-文档-4.28-Unit Test with QUnit

    现在我们在应用程序中有了一个测试文件夹,我们可以开始增加我们的测试覆盖率. 实际上,到目前为止我们添加到应用程序中的每个特性都需要单独的测试用例.到目前为止,我们完全忽略了这一点,所以让我们为步骤23 ...

  4. const对象,指向const对象的指针 和 const 指针

    const对象: const对象声明时必须赋初值,该值在编译阶段确定,不可在程序中修改. const修饰符既可放在类型名前也可放在类型名后,通常放在类型名前.不过放在类型名后易于理解. const i ...

  5. Javascript系列:总体理解

    js是一个脚本客户端(浏览器)语言.和sql html类似.多练习. 没有排错的经验,弱类型语言,浏览器解释执行,出错也不会报错 预备 <!DOCTYPE html PUBLIC "- ...

  6. 本博客已经迁移去http://blog.brightwang.com/

    本博客已经迁移去http://blog.brightwang.com/ ,感谢各位支持.

  7. ora-01652无法通过128(在temp表空间中)扩展temp段

    有两种错误:1.数据表空间不足 2.临时表空间不足 有两种原因:一是临时表空间空间太小,二是不能自动扩展. 分析过程:    既然是temp表空间有问题,那当然就要从temp表空间说起啦.首先要说明的 ...

  8. zabbix出现中文不能选的情况

    像这里一样,有些选项是选不了的,这个时候我们要做的第一步就是,找到这个配置文件. 如果不知道在哪里的话可以用find命令查找. sudo find / -name locales.inc.php 找到 ...

  9. mysql开通tcp远程连接

    1.登陆mysql: mysql -u root mysql 2.运行下面命令 UPDATE `mysql`.`user` SET `Host` = '%' WHERE `user`.`Host` = ...

  10. RecycleView实现侧滑删除item

    对于列表空间的侧滑操作,网上有很多开源的空间可以使用,Google在它的新控件RecycleView中增加了侧滑的API,完全遵循Material Design设计规范,下面看看效果演示: 下面看看介 ...