C++入门知识-拷贝构造函数-浅拷贝、深拷贝

简介: C++入门知识-拷贝构造函数-浅拷贝、深拷贝

一、概述

拷贝构造函数特征:第一个参数是自身类类型的引用,且任何额外参数都有默认值

class Foo
{
public:
  Foo(); // 默认构造函数
  Foo(const Foo &); // 拷贝构造函数
  //...
};

注意:

如果没有为一个类定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数,默认的拷贝构造函会依次将非static成员拷贝到正在创建的对象中(对于基本类型的成员变量,按字节复制;对于类类型成员变量,调用其相应类型的拷贝构造函数);

但是,当你的类含有指针类型的私有数据成员时,默认拷贝构造函数是危险的,因为它使两个对象的指针都指向了同一块内存区域,这时便是浅拷贝了。


二、什么时候会触发拷贝构造函数

1.使用同类型的对象 去创建另外一个对象;

string nines = string(100,'9');

2.把对象传入函数的非引用参数时;

void Function(string s) //调用这个函数时,实参传到这个形参s时,会触发拷贝构造
{
  // ...
}

3.把对象作为函数非引用返回值时。

string s = "hello"
string Function()
{
  // ...
  return s; // 触发拷贝构造
}

三、浅拷贝

浅拷贝小故事

张三有 100 元现金和一个房产证,房产证上写着房子的地址“魔都1巷007号”;

某天,张三就想把自己的东西复制一份给李四;

于是就复制了 100 元和一个房产证给李四,房产证上的地址还是写着“魔都1巷007号”。

后来,张三把房子卖了,李四某天也去卖房子的时候就奔溃了。

浅拷贝

就是在拷贝对象时,只是按字节地拷贝了类成员的值,对于指针或引用类型,没有拷贝其指向地具体数据过去;

就像上面故事中,只拷贝了100元和房产证,而两份房产证都是指向同一份数据(“魔都1巷007号”)。

用代码的形式表示

#include <cstdio>
#include <cstring>
#define HOUSE_LEN 64
class Person
{
  int money;    // 钱
  char *house;  // 房子
public:
  Person(int _money, char *_house)
  {
    money = _money;
    house = _house;
  }
  void Show()
  {
    printf("I have %d Yuan,and house %s\n",money, house);
  }
  void SellHouse()// 卖房子
  {
    delete[] house;
  }
};
int main()
{
  char *pHouse = new char[HOUSE_LEN];
  strncpy(pHouse, "魔都1巷007号", HOUSE_LEN);
  Person ZhangSan(100, pHouse);
  printf("I am ZhangSan\n");
  ZhangSan.Show();
  Person LiSi = ZhangSan;
  printf("I am LiSi\n");
  LiSi.Show();
  ZhangSan.SellHouse();
  printf("ZhangSan SellHouse\n");
  //LiSi.SellHouse();  // 执行这一句会使程序报错,double free
  //printf("LiSi SellHouse\n");
  return 0;
}

浅拷贝的隐患

多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏。


四、深拷贝

深拷贝小故事

张三有 100 元现金和一个房产证,房产证上写着房子的地址“魔都1巷007号”;

某天,张三就想把自己的东西复制一份给李四;

于是就复制了 100 元和一个房产证给李四,并且新建了一座房子“魔都1巷007号-2”,把李四的房产证上地址改为“魔都1巷007号-2”。

深拷贝

就是在拷贝对象时,对于指针或引用类型的成员,需要申请新的内存并把源对象成员指向的数据一起拷贝到新的内存。

就像上面故事中,拷贝了100元和房产证,并且新建了“魔都1巷007号-2”,把李四的房产证地址也改为“魔都1巷007号-2”。

用代码的形式表示

#include <cstdio>
#include <cstring>
#define HOUSE_LEN 64
class Person
{
  int money;    // 钱
  char *house;  // 房子
public:
  Person(int _money, char *_house)
  {
    money = _money;
    house = _house;
  }
  Person(const Person &person)
  {
    money = person.money;
    house = new char[HOUSE_LEN];
    snprintf(house,HOUSE_LEN, "%s-2",person.house);
  }
  void Show()
  {
    printf("I have %d Yuan,and house %s\n",money, house);
  }
  void SellHouse()// 卖房子
  {
    delete[] house;
  }
};
int main()
{
  char *pHouse = new char[HOUSE_LEN];
  strncpy(pHouse, "魔都1巷007号", HOUSE_LEN);
  Person ZhangSan(100, pHouse);
  printf("I am ZhangSan\n");
  ZhangSan.Show();
  Person LiSi = ZhangSan;
  printf("I am LiSi\n");
  LiSi.Show();
  ZhangSan.SellHouse();
  printf("ZhangSan SellHouse\n");
  LiSi.SellHouse();  // 因为有了拷贝构造函数做了深拷贝,这里不会报错
  printf("LiSi SellHouse\n");
  return 0;
}

五、总结

在设计一个类的过程中,当这个类有指针或引用类型的成员时,且可能会触发调用拷贝构造函数,就需要自己定义一个拷贝构造函数,避免编译器的默认的拷贝构造函数引起浅拷贝而导致程序面临危险。

参考资料

《C++Primer》

C++细节 深拷贝和浅拷贝(位拷贝)详解

如果对你有帮助的话,记得点赞收藏,如果有什么遗漏的或者什么体会,请在评论告诉我,好东西记得分享 ^ _ ^

目录
相关文章
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
40 2
C++入门12——详解多态1
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
81 1
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
28 0
|
2月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
2月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
32 0
|
2月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
33 0
|
2月前
|
存储 算法 C++
C++入门10——stack与queue的使用
C++入门10——stack与queue的使用
42 0
|
2月前
|
存储 C++ 容器
C++入门9——list的使用
C++入门9——list的使用
19 0