C++实现拷贝构造函数的方法详解

目录
  • 引入
  • 一.什么是拷贝构造函数
  • 二.什么情况下使用拷贝构造函数
  • 三.使用拷贝构造函数需要注意什么
  • 四.深拷贝浅拷贝
    • 4.1 浅拷贝
    • 4.2 深拷贝

引入

对于普通类型的对象来说,他们之间的复制很简单:

int a = 10;
int b = a;

但是对于类对象来说,其中会存在许多的成员变量。

#include <iostream>
using namespace std;

class CExample {
private:
     int a;
public:
      //构造函数
     CExample(int b)
     { a = b;}

      //一般函数
     void Show ()
     {
        cout<<a<<endl;
      }
};

int main()
{
     CExample A(100);
     CExample B = A; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
      B.Show ();
     return 0;
}

从以上代码可以看出系统为对象 B 分配了内存并完成了与对象 A 的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。

下面这个则是拷贝构造函数的工作过程

#include <iostream>
using namespace std;

class CExample {
private:
    int a;
public:
    //构造函数
    CExample(int b)
    { a = b;}

    //拷贝构造函数
    CExample(const CExample& C)
    {
        a = C.a;
    }

    //一般函数
    void Show ()
    {
        cout<<a<<endl;
    }
};

int main()
{
    CExample A(100);
    CExample B = A; // CExample B(A); 也是一样的
     B.Show ();
    return 0;
}

在这里CExample(const CExample& C) 就是我们自定义的拷贝构造函数。

一.什么是拷贝构造函数

同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员是共用的(只有一份拷贝)。在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。

拷贝构造函数本质上来说也是构造函数。

二.什么情况下使用拷贝构造函数

一般来说有以下三种情况:

  • 用旧对象去初始化新对象
  • 值传递—参数是类类型的值类型,从实参传递给形参的过程,是用实参去构造形参
  • 函数返回值是值类型–用局部对象去构造临时对象调用拷贝构造
class A
{
public:
    A(int i = 0):m_i(i)
    {
        cout<<"A(int) "<<m_i<<endl;
    }
    A(const A &a):m_i(a.m_i)
    {
        cout<<"A(A) "<<m_i<<endl;
    }
    ~A()
    {
        cout<<"~A "<<m_i<<endl;
    }
private:
    int m_i;
};
void fn(A t) //2.将c传递给t的过程,是值传递,此时临时对象形参t是新对象,用c去构造t,调用拷贝构造
{
    cout<<"fn end"<<endl;
    //在退出fn函数时,将临时对象t释放,调用析构函数
}
A test()
{
    A d(40); //调用普通构造A(int)构造对象d
    return d;
    /*
     3.在返回的时候,将局部对象的值给了临时对象(值传递,调用拷贝构造,用旧局部对象去构造新的临时对象)
     然后局部对象d就释放了,临时对象将值带回到主调函数中后,临时对象的值才释放
    */
}
A fnn()
{
    A s(50); //A(int) 50
    return s; //
    /*1.s->临时对象  拷贝构造
    2.析构函数局部对象s
    */
}
void main()
{
    A a(20);  //A(int) 20
    A b = a;  //1.用a初始化b A(A)
    cout<<"fn"<<endl;
    A c(30); //A(int) 30
    fn(c);
    cout<<"test"<<endl;
    c = test(); //临时对象将值赋给c(调用赋值函数)之后,调用析构,析构临时对象
    cout<<"fnn"<<endl;
    A t = fnn(); //临时对象初始化新对象t,是否会调用拷贝构造?--调用了,不过编译器做过优化
    cout<<"main end"<<endl;
}

三.使用拷贝构造函数需要注意什么

拷贝构造函数是构造函数的一个重载形式。

拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,称为:位拷贝。

四.深拷贝浅拷贝

4.1 浅拷贝

所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,指针,那么浅拷贝就会出现一些问题。

对于下面函数来说有指针作为数据成员,则用s1对象去构造s2对象的时候,调用默认拷贝构造,用s1中的数据成员指针m_str去初始化s2对象中的数据成员m_str,即是s2.m_str = s1.m_str,那么导致两个对象中的指针指向同一块内存单元,指向的都是构造s1对象时开辟的内存单元,所以在主函数退出时候要析构s2和s1时,将同一段空间释放两次出现内存错误。

class Str
{
public:
    Str(const char *str = "")
    {
        m_str = new char[strlen(str)+1];
        strcpy(m_str,str);
    }
    ~Str()
    {
        delete[]m_str;
    }
    void Print()
    {
        cout<<m_str<<endl;
    }
private:
    char *m_str;
};
void main()
{
    Str s1("pangpang");
    Str s2(s1);
    cout<<sizeof(Str)<<endl;
    s1.Print();
    s2.Print();
}

上述程序的内存布局:

对于指针作为数据成员的类,用s1对象去构造s2对象的时候,调用默认拷贝构造函数时,二者指向同一内存单元,即二者的初始地址相同这里均为0X0002000,当我们构造完以后将要进行析构时,这里将会出现错误:因为析构函数要释放空间,而这里我们的空间对应的是一块空间,当我们析构完s2后:这一块空间的内容已经被delete,而我们还需要析构s1,即:一个内存空间析构了两次,出现内存错误。

为了解决上述问题 我们就需要给s2中的m_str也开辟和s1中的m_str一样大小的空间,所以我们就需要 深拷贝 。

4.2 深拷贝

在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:

class Str
{
public:
    Str(const char *str = "")
    {
        m_str = new char[strlen(str)+1];
        strcpy(m_str,str);
    }
    ~Str()
    {
        cout<<"~Str"<<endl;
        delete[]m_str;
    }
    Str(const Str& s)
    {
        cout<<"Str(Str)"<<endl;
        m_str = new char[strlen(s.m_str)+1]; //开辟同样大小的空间
        strcpy(m_str,s.m_str); //将内容拷贝成一样的
    }
    void Print()
    {
        cout<<m_str<<endl;
    }
private:
    char *m_str;
};
void main()
{
    Str s1("pangpang");
    Str s2(s1); //拷贝构造
    s1.Print();
    s2.Print();
}

现在的程序内存布局为:

各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

到此这篇关于C++实现拷贝构造函数的方法详解的文章就介绍到这了,更多相关C++拷贝构造函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中拷贝构造函数的应用详解

    一.C++中拷贝构造函数的定义: 有一个参数的类型是其类类型的构造函数是为拷贝构造函数. 如下所示: X::X( const X& x); Y::Y( const Y& y, int =0 ); //可以是多参数形式,但其第二个即后继参数都有一个默认值 二.拷贝构造函数的应用: 当一个类对象以另一个同类实体作为初值时,大部分情况下会调用拷贝构造函数. 一般是这三种具体情况: 1.显式地以一个类对象作为另一个类对象的初值,形如X xx=x; 2.当类对象被作为参数交给函数时. 3.当函数返回

  • C++中拷贝构造函数的总结详解

    1.什么是拷贝构造函数: 拷贝构造函数嘛,当然就是拷贝和构造了.(其实很多名字,只要静下心来想一想,就真的是顾名思义呀)拷贝又称复制,因此拷贝构造函数又称复制构造函数.百度百科上是这样说的:拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化.其唯一的参数(对象的引用)是不可变的(const类型).此函数经常用在函数调用时用户定义类型的值传递及返回. 2.拷贝构造函数的形式 复制代码 代码如下: Class X{public: X(); X(const

  • C++超详细讲解拷贝构造函数

    目录 构造函数 特征 编译器生成的拷贝构造 拷贝构造的初始化列表 显式定义拷贝构造的误区 结论 构造函数 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用 拷贝构造函数是构造函数的一个重载,因此显式的定义了拷贝构造,那么编译器也不再默认生成构造函数. 特征 拷贝构造也是一个特殊的成员函数 特征如下: 拷贝构造是构造函数的一个重载: 拷贝构造的参数只有一个并且类型必须是该类的引用,而不是使用传值调用,否则会无限递归: 若没有显

  • 详解C++ 拷贝构造函数

    拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象.拷贝构造函数通常用于: 通过使用另一个同类型的对象来初始化新创建的对象. 复制对象把它作为参数传递给函数. 复制对象,并从函数返回这个对象. 如果在类中没有定义拷贝构造函数,编译器会自行定义一个.如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数.拷贝构造函数的最常见形式如下: classname (const classname &obj) { // 构造函数的主体 } 在这里,o

  • C++实现拷贝构造函数的方法详解

    目录 引入 一.什么是拷贝构造函数 二.什么情况下使用拷贝构造函数 三.使用拷贝构造函数需要注意什么 四.深拷贝浅拷贝 4.1 浅拷贝 4.2 深拷贝 引入 对于普通类型的对象来说,他们之间的复制很简单: int a = 10;int b = a; 但是对于类对象来说,其中会存在许多的成员变量. #include <iostream> using namespace std; class CExample { private: int a; public: //构造函数 CExample(in

  • C++编程析构函数拷贝构造函数使用示例详解

    目录 构造函数 析构函数 拷贝构造之深拷贝和浅拷贝 深浅拷贝区别 首先定义一个类进行操作. class MM { public: protected: int year; string name; } 构造函数在类中默认有一个无参的构造函数 默认的构造函数为 类名(){}:这个构造函数 如果直接写了构造函数那么这个构造函数将会没有 构造函数 class MM { public: //MM() {};//无参构造函数 MM(int year, string name) :year(year), n

  • 深入C++拷贝构造函数的总结详解

    拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三个问题:1. 以下函数哪个是拷贝构造函数,为什么?X::X(const X&);   X::X(X);   X::X(X&, int a=1);   X::X(X&, int a=1, b=2);  2. 一个类中可以存在多于一个的拷贝构造函数吗?3. 写出以下程序段的输出结果, 并说明为什么? 如果你都能回答无误的话,那么你已经对拷贝构造函数有了相当的了解. #include <iost

  • c++中的单例类模板的实现方法详解

     1.什么是单例模式 在架构设计时,某些类在整个系统生命周期中最多只能有一个对象存在 ( Single Instance ).如超市收银系统,其外观主要由显示器(1个).扫描枪(1个).收款箱(1个)组成,在系统正常运行期间这三部分都是唯一存在的:也就是说,显示器.扫描枪.收款箱这三部分都应该有各自的类,并且每个类只能唯一地实例化一个对象,基于这种模式的程序设计,称为单例模式. !!!单例模式只能创建一个对象,且该对象的生命周期伴随系统的整个运行期间. 2.怎么实现单例模式 思考:如何定义一个类

  • JavaScript使用Fetch的方法详解

    目录 进行 fetch 请求 支持的请求参数 发送带凭据的请求 上传 JSON 数据 上传文件 上传多个文件 检测请求是否成功 自定义请求对象 Headers Guard Response 对象 Body 特性检测 Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应.它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源. 这种功能以前是使用 XMLHttpRequest实现的.Fetch提供了一个更好

  • C++简易版Tensor实现方法详解

    目录 基础知识铺垫 内存管理 allocate 实现Tensor需要准备shape和storage Tensor的设计方法(基础) Tensor的设计方法(更进一步) 基础知识铺垫 缺省参数 异常处理 如果有模板元编程经验更好 std::memset.std::fill.std::fill_n.std::memcpy std::memset 的内存填充单位固定为字节(char),所以不能应用与double,非char类型只适合置0. std::fill 和 std::fill_n 则可以对指定类

  • C++实现String类的方法详解

    目录 前言 string模拟实现 string简单实现 string完整实现 完整代码 前言 在C语言中,没有专门用来表示字符串的类型.C语言的字符串是一系列以’\0’为结尾的字符的集合.虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与字符串这个类型是分开的,这不太符合C++中面试对象的思想,所以在C++中封装了一个string类,来帮助我们操作字符串.string该如何使用,我这里就不做赘述了,大家可以去看看官方文档呀 string - C++ Re

  • C++模拟实现string的方法详解

    目录 1.string 成员变量 2.构造函数 3.拷贝构造.赋值重载和析构函数 1.拷贝构造 2.赋值重载 3.析构函数 4.访问成员变量 5.遍历 1.下标+[] 2.迭代器(iterator) 3.范围for 6.空间的申请 1.reserve 2.resize 7.增删查改 8.重载cin 和 cout 1.cout 2.cin 1.string 成员变量 首先需要一个动态开辟的指针指向这个字符串,然后还需要容量和存储的个数,并且我们不能和标准库的string进行冲突所以我们需要写在我们

随机推荐