C++ 拷贝构造函数详解

下面的讲解将以C++标准库的string类作为讲解对象,string类:class with pointer member(s)

1、拷贝构造函数和拷贝赋值函数

1.1引入

下面是给出的测试函数,也是我们要能在自己设计的myString类中实现的功能:

int main()
{myString s1(); //无参数构造函数myString s2("Hello world!"); //传入字符串的构造函数myString s3(s1);    //拷贝构造cout<<s3<<endl;       //操作符重载,对<<重载s3 = s2;     //拷贝赋值cout<<s3<<endl;
}

当我们没有显式写出拷贝构造函数和拷贝赋值函数时,编译器会给我们默认提供拷贝构造和拷贝赋值函数,这两个函数做的都是逐字节地将一个对象的内容拷贝到另一个对象中。

对于成员没有指针的类,默认的拷贝构造函数一般不需要再重写。但是对于成员中含有指针的类,那么拷贝构造函数必须要进行重写。不能使用默认的拷贝构造函数。

#ifndef COPYCONSTRUCTOR_MYSTRING_H
#define COPYCONSTRUCTOR_MYSTRING_Hclass myString {private:char* m_data;//动态分配的方式
public:myString(const char* cstr = 0);myString(const myString& str);//拷贝构造函数myString& operator=(const myString& str);//拷贝赋值~myString();//析构函数,类死亡的时候调用char* get_c_char()const{return m_data;};//inline function
};#endif //COPYCONSTRUCTOR_MYSTRING_H

下面我们先对普通的构造函数和析构函数进行创建:

inline
myString::myString(const char* cstr) {if(cstr){m_data = new char[strlen(cstr)+1];//别忘了结束符要占用长度strcpy(m_data, cstr);}else{//未指定初值m_data = new char[1];*m_data = '\0';}
}inline
myString::~myString() {delete[] m_data;
}

上述的创建分别使用了array new和array delete,即array[]和delete[]的写法。两者一定要搭配使用,不然会造成内存泄漏:

可以进行这样的测试:

{myString s1();
myString s2("hello");myString* p = new myString("hello");
delete p;
}

使用new的关键字进行动态创建字符串,离开作用域时,必须写出delete p,删除指针对象。

未使用new关键字创建的字符串会自动调用析构函数。

1.2 拷贝构造函数

  • class with pointer members 必须有 copy cstr 和 copy op=

对于内含指针的构造函数,若使用默认的构造函数,则是**“浅拷贝”**memory leak,有可能造成内存泄漏。

**内存泄漏(Memory Leak)**是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

如下图,我们希望的是a和b各自有一个指针指向各自的字符串Hello\0,假使我们使用默认的拷贝构造,那么原先的World\0将不会有指针指向:造成了内存泄漏

alias:别名,两个指针指向同一个字符串也是非常危险的,a修改a的m_data,结果b的m_data也被修改了。这是我们所不希望的。

  • 拷贝构造函数的设置

    下面的写法就避免了内存泄漏,称为深拷贝

inline
myString::myString(const myString &str) {//创建出足够的空间放蓝本m_data = new char[strlen(str.m_data) + 1];//直接取另一个对象的private:兄弟之间互为友元strcpy(m_data, str.m_data);
}

可以看到在拷贝构造函数中,我们使用了另一个object对象的private成员,可以直接调用,这是因为同一个类的不同对象之间互为友元

可编写测试函数:

{myString s1("Hello");myString s2(s1);myString s3 = s1;//第三行和第四行是不同的操作,//一个是利用拷贝构造函数创建出一个新的对象//另一个是使用了拷贝赋值函数
}

1.3拷贝赋值函数

  • 赋值的过程

    • 销毁自己
    • 重分配空间
    • 返回*this
inline
myString & myString::operator=(const myString &str) {//检测自我赋值//self assignmentif(this == &str){//?return *this;}delete[] m_data; //①m_data = new char[strlen(str.m_data)+1];//加上结束符的长度②strcpy(m_data, str.m_data);//③return *this;}

上述的①②③即分别对应上述赋值过程。特别要注意的是自我赋值,自我赋值的检测不仅关系到效率,还关系到下面程序的正确性。

2.new 和 delete

2.1对象的生命——堆空间和栈空间

对象一定存储在内存中,但可以是存储在栈空间,也可是在堆空间。

s1,s2,s3的内存空间在栈中,称为stack object,又叫做local object,因为其声明在作用域结束的时候就结束了,又被称为auto object,因为他被自动清理——析构函数被自动调用。

  • static变量
{myString s1 = myString();  //无参数构造函数static myString s2 = myString("Hello world!");  //传入字符串的构造函数return 0;
}

假如s2对象设定为static,那么这个statck object就会变成static对象,其生存期为程序的生存期,声明在作用域结束之后仍存在。

要注意的是static变量只会初始化一次。即重复调用函数修改static变量的值也只会修改一次。

  • 全局变量
class Complex{...};
...
Complex c3(1,2);int main()
{……
}

像c3这样的变量称为全局变量,其作用域和static变量一样。

2.2 new的正确使用方法

new 必须搭配delete使用,不然可能造成内存泄漏。

而new运算符会被分解成三个操作:

  • 分配内存:使用operator new函数,内部调用malloc,为对象分配内存
  • 转型:把void转型成为Complex
  • 构造函数:通过转型得到的指针调用其构造函数

即整个new的动作是:先分配内存,再调用构造函数

2.3 delete的使用

delete ps;编译器会将其转换为:

myString::~myString(ps); //析构函数
operator delete(ps);    //释放内存

即delete被转化为两个动作:先调用析构函数然后释放内存。

而调用析构函数需要做什么?这需要我们自己定义:

myString::~myString() {delete[] m_data;
}

在我们定义的字符串的析构函数中,我们对动态申请的字符串的空间进行了删除,比如删除了字符串"hello word"。而字符串的m_data本身只是一个指针,此时还没有被删除。

operator delete(ps);则是内部调用free函数的一个函数,将指针删除。

而调用析构函数需要做什么?这需要我们自己定义:

myString::~myString() {delete[] m_data;
}

在我们定义的字符串的析构函数中,我们对动态申请的字符串的空间进行了删除,比如删除了字符串"hello word"。而字符串的m_data本身只是一个指针,此时还没有被删除。

operator delete(ps);则是内部调用free函数的一个函数,将指针删除。

C++ 拷贝构造函数详解相关推荐

  1. C++——拷贝构造函数详解

    C++--拷贝构造函数详解 1.拷贝构造函数的特点: 2.通过例子引入拷贝构造: 3构造对象的时候使用引用返回与不使用引用返回的问题: 3.1不使用引用返回: 3.2引用返回--从已经死亡的地址接收值 ...

  2. C++拷贝构造函数详解

    一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp] view plaincopy int a = 100; int b = a; 而类对象与普通 ...

  3. [016]转--C++拷贝构造函数详解

    一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp] view plaincopy int a = 100; int b = a; 而类对象与普通 ...

  4. 从零开始的移动构造函数,拷贝构造函数详解(C++)

    本文主要作为自己零散笔记进行记录,仍需要一定的C++知识,至少菜鸟相关的知识得看完.本文会尽量让刚入门的小白都能读懂,以便自己再来回顾的时候也能够读懂.如果有可以补充而外知识恳请评论或私信告诉,我会第 ...

  5. c++类的构造函数详解

    c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初 ...

  6. [转]c++类的构造函数详解

    c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初 ...

  7. c++构造函数详解(转)

    c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初 ...

  8. 构造函数详解_const的部分分析,部分转载于http://ticktick.blog.51cto.com/823160/194307

    c++构造函数的知识在各种c++教材上已有介绍,不过初学者往往不太注意观察和总结其中各种构造函数的特点和用法,故在此我根据自己的c++编程经验总结了一下c++中各种构造函数的特点,并附上例子,希望对初 ...

  9. C++构造函数详解及显示调用构造函数

    c++类的构造函数详解 一. 构造函数是干什么的 class Counter { public:          // 类Counter的构造函数          // 特点:以类名作为函数名,无 ...

最新文章

  1. Oracle Database 11g的使用
  2. 浅谈PHP面向对象编程(九)
  3. Apollo自动驾驶入门课程第②讲 — 高精地图
  4. Flowable通过api查询流程返回流程图节点
  5. 使用Kylin导入JDBC数据源遇到的问题
  6. 终于有人把SaaS讲明白了
  7. 网络协议栈深入分析(一)--与sk_buff有关的几个重要的数据结构
  8. PyTorch 1.0 中文文档:torch.utils.cpp_extension
  9. TensorFlow 教程 --进阶指南--3.7自定义数据读取
  10. 【Elasticsearch】es 5.3.0 bulk index 性能调优实践
  11. Java面向对象之抽象方法抽象类、接口的使用
  12. 计蒜客-----单独的数字(map)
  13. python获取本机ip地址_python3 获取本机ip地址
  14. 测试显示器使用时间的软件,解决方案:显示响应时间测试软件
  15. 苹果手机换电池对手机有影响吗_电池寿命真的影响手机性能~iPhone手机更换电池后性能对比...
  16. 腾讯反病毒实验室安全研究员杨经宇:开启IoT设备的上帝模式
  17. 电脑主板RS232串口硬件设计
  18. Android 动态分区详解(六) 动态分区的底层机制
  19. 新玺配资:市场情绪回暖 主流热点崛起
  20. 域格模块移动网络信号指标介绍

热门文章

  1. android 跳转oppo应用中心_android关于应用市场跳转的问题
  2. js跳转到新页面传参以及接收参数的方法
  3. Python下mysql数据库连接池
  4. usb鼠标制作调试记录
  5. H7-TOOL迎来新版固件V2.09,WiFi压缩图传,FDCAN/Modbus助手波形打印上线,完善串口助手/RTT/Lua小程序,脱机烧增加新型号
  6. Golang结构体和map
  7. 基于Anaconda3环境下的CNTK安装
  8. python程序设计教程张莉课后答案_新课标教育背景下Python项目式学习模式研究
  9. Paxos 诞生的曲折历史
  10. 动态加速度信号的时频域积分方法