构造函数constructor 与析构函数destructor(五)
我们知道当调用默认拷贝构造函数时,一个对象对另一个对象初始化时,这时的赋值时逐成员赋值。这就是浅拷贝,当成员变量有指针时,浅拷贝就会在析构函数那里出现问题。例如下面的例子:
//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(五)的更多相关文章
- 构造函数constructor 与析构函数destructor(一)
构造函数定义:构造函数c++中在创建对象时自动调用,用来初始化对象的特殊函数. (1)构造函数的名字必须与类的名字相同,不能有返回值,哪怕是void 也不行. (2)通常情况下构造函数应声明为公有函数 ...
- 构造函数constructor 与析构函数destructor(四)
拷贝构造函数:拷贝构造函数就是在用一个类对象来创建另外一个类对象时被调用的构造函数,如果我们没有显示的提供拷贝构造函数,编译器会隐式的提供一个默认拷贝构造函数. 拷贝构造函数的定义是X(const X ...
- 构造函数constructor 与析构函数destructor(二)
(1)转换构造函数 转换构造函数的定义:转换构造函数就是把普通的内置类型转换成类类型的构造函数,这种构造函数只有一个参数.只含有一个参数的构造函数,可以作为两种构造函数,一种是普通构造函数用于初始化对 ...
- 构造函数constructor 与析构函数destructor(三)
(1)构造函数初始化列表: 1 class Test{ 2 int i; 3 public: 4 Test(int vi):i(vi){}//这里的从冒号开始,到右大括号结束,这一段是构造函数初始化列 ...
- GCC的__attribute__ ((constructor))和__attribute__ ((destructor))
通过一个简单的例子介绍一下gcc的__attribute__ ((constructor))属性的作用.gcc允许为函数设置__attribute__ ((constructor))和__attrib ...
- javascript工厂函数(factory function)vs构造函数(constructor function)
如果你从其他语言转到javascript语言的开发,你会发现有很多让你晕掉的术语,其中工厂函数(factory function)和构造函数(constructor function)就是其中的一个. ...
- 【转】c++析构函数(Destructor)
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存.关闭打开的文件等,这个函数就是析构函数. 析构函数(Destructor)也 ...
- C++——构造函数 constructor
What is constructor C++中,如果你想要创建一个object,有一个函数会自动被调用(不需要programmer显式调用 ),这个函数就是constructor; construc ...
- 类(class)、构造函数(constructor)、原型(prototype)
类 Class 类的概念应该是面向对象语言的一个特色,但是JavaScript并不像Java,C++等高级语言那样拥有正式的类,而是多数通过构造器以及原型方式来仿造实现.在讨论构造器和原型方法前,我可 ...
随机推荐
- robot framework取出列表子元素
取出嵌套列表变量的子元素 ${list}型列表: ${list} = [["A1", "first"], ["A2", "seco ...
- 一个NPOI导出到excel文件的范例记录
'使用NPOI写入新创建的excel文件,导出文件: Private Sub Sub_WriteXls() Dim XlsBook As XSSFWorkbook Dim XlsSheet As XS ...
- 5.Struts2配置形式,覆盖
转自:https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 下面以对struts.i18n.encoding=UTF-8的配置为例进行说 ...
- jquery ajax 赋值问题, 后面程序判断逻辑用
添加async:false; 像 $.get() 等是没有async参数的, 所以需要用到如下 $.ajaxSetup({ async : false //这个意思是等这个ajax有了返回值后才会执行 ...
- python 3 属性查找与绑定方法
1.属性查找 类有两种属性:数据属性和函数属性 (1)类的数据属性是所有对象共享的 #类的数据属性是所有对象共享的,id都一样 class OldboyStudent: school='oldboy' ...
- C++Primer笔记-----继承
==========================================================================day11 面向对象程序设计============ ...
- 数组去重的三种方法 es6
[1,2,3,4,5,6,7,8,9,2,2,3,3,4,1].filter(function(el,index,arr){ return (index === arr.indexOf(el)); } ...
- github上关于iOS的各种开源项目集合(转)
UI 下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh - 下拉刷新控件. MJRefresh - 仅需一行代码就可以为UITable ...
- JAVA中request.getParameterMap()用法笔记
一. 根据Java规范:request.getParameterMap()返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系.这个返回值有个特别 ...
- How To Pronounce 3-Syllable Phrases
How To Pronounce 3-Syllable Phrases Share Tweet Share Tagged With: 3-Syllable Learn about the stress ...