首页 > 其他分享 >构造函数的深浅拷贝问题详解

构造函数的深浅拷贝问题详解

时间:2024-09-05 20:53:20浏览次数:14  
标签:std 初始化 对象 详解 拷贝 data 构造函数

 构造函数的分类

  1. 构造函数重载:构造函数可以通过不同的参数列表进行重载,这意味着可以有多个构造函数,每个构造函数有不同的参数。
  2. 多参构造函数:通过传递多个参数来创建对象。
  3. 无参(缺省)构造函数:不需要参数来创建对象。
  4. 类型转换构造函数:使用不同类型的参数来创建对象。
  5. 拷贝构造函数:使用同类型的另一个对象来创建新对象。

代码演示

#include <iostream>
#include <cstring>
using namespace std;

class Book {
public:
    // 无参构造函数
    Book() {
        title = "未命名";
        author = "未知";
        pages = 0;
        cout << "无参构造函数被调用" << endl;
    }

    // 带参构造函数,使用 const char* 类型参数
    Book(const char* t, const char* a, int p)
        : title(t), author(a), pages(p) {
        cout << "带参构造函数被调用" << endl;
    }

    // 重载带参构造函数,支持 string 参数
    Book(const string& t, const string& a, int p)
        : Book(t.c_str(), a.c_str(), p) /*委托构造函数*/{}

    // 拷贝构造函数
    Book(const Book& b) {
        title = b.title;
        author = b.author;
        pages = b.pages;
        cout << "拷贝构造函数被调用" << endl;
    }

    // 显示图书信息
    void displayInfo() {
        cout << "书名: " << title << ", 作者: " << author <<
         ", 页数: " << pages << endl;
    }

private:
    string title;
    string author;
    int pages;
};

// 主函数
int main() {
    // 使用无参构造函数创建对象
    Book book1;
    book1.displayInfo();

    // 使用带参构造函数创建对象,传递 C 风格字符串
    Book book2("C++ Primer", "Stanley Lippman", 976);
    book2.displayInfo();

    // 使用带参构造函数创建对象,传递 string 对象
    string title = "Effective C++";
    string author = "Scott Meyers";
    Book book3(title, author, 320);
    book3.displayInfo();

    // 使用拷贝构造函数创建对象
    Book book4 = book2;
    book4.displayInfo();

    return 0;
}

       (1).std::string 类型:

  • std::string 是 C++ 标准库提供的字符串类,封装了字符数组,并提供了很多方便的操作方法。
  • 但是 std::string 和 C 风格的字符串(const char*)是不同的类型,不能直接传递给接受 const char* 参数的函数或构造函数。

      (2).c_str() 方法:

  •  c_str() 是 std::string 类中的一个成员函数,它返回一个指向以 null 终止的字符数组的指针(const char*),这个数组表示与调用对象相同的字符序列。
  • 换句话说,t.c_str() 将 std::string 类型的 t 转换为 C 风格的字符串(const char*),同理,a.c_str() 也是如此。

      (3) Book(t.c_str(), a.c_str(), p) 的含义:

  •  这行代码调用了 Book 类的构造函数,将 t 和 a 这两个 std::string 类型的字符串转换为 const char* 类型,再加上一个整数 p,一起传递给 Book 的构造函数。
  • 最终,Book 类的构造函数使用 const char* 类型的 title 和 author 参数以及整数 pages 来初始化 Book 对象。

浅拷贝与深拷贝

概述

  •         浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两种不同的对象复制方式,主要区别在于它们如何处理对象内部的指针或动态分配的资源。
    •         浅拷贝:只是复制对象的各个成员的值,对于指针成员,浅拷贝只复制指针的值(即内存地址),而不复制指针所指向的内存内容。结果是多个对象共享同一块内存。
      •         深拷贝:不仅复制对象的各个成员的值,还会为指针成员分配新的内存,并复制指针指向的实际内容。结果是每个对象都有自己的一份独立的内存副本。

浅拷贝的实现与问题

        浅拷贝的实现通常由编译器默认提供的拷贝构造函数完成,它执行的是成员按位复制(bitwise copy)。

  • 问题:
    •         如果两个对象共享同一块内存,修改其中一个对象的数据会影响另一个对象。
      •         当其中一个对象被销毁时,另一对象的指针可能变成悬空指针,导致潜在的内存错误。

代码演示

#include <iostream>
using namespace std;

class ShallowCopyExample {
public:
    int* data;

    // 构造函数
    ShallowCopyExample(int value) {
        data = new int(value);
    }

    // 拷贝构造函数 - 浅拷贝
    ShallowCopyExample(const ShallowCopyExample &source)
        : data(source.data) {
        cout << "浅拷贝构造函数被调用" << endl;
    }

    // 显示数据
    void display() const {
        cout << "Data: " << *data << endl;
    }

    // 析构函数
    ~ShallowCopyExample() {
        delete data;
        cout << "析构函数被调用" << endl;
    }
};

int main() {
    ShallowCopyExample obj1(42);
    ShallowCopyExample obj2 = obj1;  // 浅拷贝

    obj1.display();
    obj2.display();

    // 修改 obj1 的数据
    *obj1.data = 84;
    obj1.display();
    obj2.display();  // obj2 也会受到影响

    return 0;
}

        在浅拷贝中,obj1 和 obj2 共享同一块内存,因此修改 obj1 的数据会影响到 obj2。当 obj1 和 obj2 都超出作用域时,会分别调用它们的析构函数,尝试释放同一块内存,导致双重释放错误(double free)

深拷贝的实现与解决方法 

解决办法

        在编写类时,如果类包含指针或动态分配的资源,通常需要提供自定义的拷贝构造函数和赋值运算符,以实现深拷贝,确保每个对象拥有自己的内存副本,而不是与其他对象共享内存。这种方式可以避免潜在的内存错误,保证程序的健壮性。

代码实现

#include <iostream>
using namespace std;

class DeepCopyExample {
public:
    int* data;

    // 构造函数
    DeepCopyExample(int value) {
        data = new int(value);
    }

    // 拷贝构造函数 - 深拷贝
    DeepCopyExample(const DeepCopyExample &source) {
        data = new int(*source.data);
        //strcpy(data,source.data)   //同样可以实现深拷贝
        cout << "深拷贝构造函数被调用" << endl;
    }

    // 显示数据
    void display() const {
        cout << "Data: " << *data << endl;
    }

    // 析构函数
    ~DeepCopyExample() {
        delete data;
        cout << "析构函数被调用" << endl;
    }
};

int main() {
    DeepCopyExample obj1(42);
    DeepCopyExample obj2 = obj1;  // 深拷贝

    obj1.display();
    obj2.display();

    // 修改 obj1 的数据
    *obj1.data = 84;
    obj1.display();
    obj2.display();  // obj2 不受影响

    return 0;
}

        在深拷贝中,obj1 和 obj2 各自拥有一份独立的内存。因此,修改 obj1 的数据不会影响 obj2。每个对象的析构函数都会释放自己独立的内存,从而避免双重释放(double free)错误。

总结

     浅拷贝:适合不涉及动态内存或资源管理的简单对象。对于指针成员,浅拷贝可能会导致多个对象共享同一内存,容易出现资源管理问题,如双重释放、悬空指针等。

     深拷贝:适用于包含动态内存或资源管理的复杂对象。深拷贝确保每个对象有自己独立的资源,避免了共享资源带来的问题。

初始化表

        初始化列表提供了一种直接初始化成员变量的方法,而不是在构造函数体内进行赋值。这样不仅代码更加简洁,还可以提高性能,特别是在需要初始化 const 或引用类型成员时。

改进代码1

#include <iostream>
#include <cstring>
using namespace std;

class Book {
public:
    // 无参构造函数,使用初始化列表
    Book() : title("未命名"), author("未知"), pages(0) {
        cout << "无参构造函数被调用" << endl;
    }

    // 带参构造函数,使用 const char* 类型参数,使用初始化列表
    Book(const char* t, const char* a, int p)
        : title(t), author(a), pages(p) {
        cout << "带参构造函数被调用" << endl;
    }

    // 重载带参构造函数,支持 string 参数,使用初始化列表
    Book(const string& t, const string& a, int p)
        : title(t), author(a), pages(p) {
        cout << "带参构造函数被调用" << endl;
    }

    // 拷贝构造函数,使用初始化列表
    Book(const Book& b)
        : title(b.title), author(b.author), pages(b.pages) {
        cout << "拷贝构造函数被调用" << endl;
    }

    // 显示图书信息
    void displayInfo() const {
        cout << "书名: " << title << ", 作者: " << author
             << ", 页数: " << pages << endl;
    }

private:
    string title;
    string author;
    int pages;
};

// 主函数
int main() {
    // 使用无参构造函数创建对象
    Book book1;
    book1.displayInfo();

    // 使用带参构造函数创建对象,传递 C 风格字符串
    Book book2("C++ Primer", "Stanley Lippman", 976);
    book2.displayInfo();

    // 使用带参构造函数创建对象,传递 string 对象
    string title = "Effective C++";
    string author = "Scott Meyers";
    Book book3(title, author, 320);
    book3.displayInfo();

    // 使用拷贝构造函数创建对象
    Book book4 = book2;
    book4.displayInfo();

    return 0;
}

代码说明 

    无参构造函数:

  • 改进前:在构造函数体内赋值。
  • 改进后:使用初始化列表直接初始化成员变量 title、author 和 pages。

    带参构造函数:

  • 改进前:在构造函数体内赋值。
  • 改进后:使用初始化列表直接初始化 title、author 和 pages,减少了赋值操作的开销。

    拷贝构造函数:

  • 改进前:在构造函数体内进行赋值。
  • 改进后:使用初始化列表直接将 b.title、b.author 和 b.pages 赋值给新对象的成员变量。

    重载带参构造函数:

  • 使用 string 作为参数时,直接在初始化列表中初始化 title 和 author,避免了不必要的 c_str() 转换。

 在拷贝构造函数引入初始化表

#include <iostream>
#include <cstring>  // 包含 strcpy 所需的头文件
using namespace std;

class String {
public:
     // 构造函数,动态分配内存并复制字符串内容
    String(const char* str) : data ( new char[strlen(str) + 1]) {
        strcpy(data, str);  // 深拷贝字符串内容
        cout << "构造函数被调用" << endl;
    }

     // 拷贝构造函数,进行深拷贝
    String(const String &source) : data(new char[strlen(source.data) + 1]) {
        strcpy(data, source.data);  // 深拷贝字符串内容
        cout << "深拷贝构造函数被调用" << endl;
    }

    // 显示字符串
    void display() const {
        if (data) {
            cout << "Data: " << data << endl;
        } else {
            cout << "Data is null" << endl;
        }
    }

    // 析构函数
    ~String() {
        // 注意:这里的 delete[] 仅当数据是由深拷贝生成时使用,否则可能导致未定义行为
        delete[] data;
        cout << "析构函数被调用" << endl;
    }

private:
    char* data;  // 指向传入字符串的指针
};

int main() {
    char str[] = "Hello, World!";
    String s1(str);  // 动态分配内存并复制字符串内容到 s1.data
    s1.display();

    String s2 = s1;  // 深拷贝,s2 的 data 指向新的内存,内容与 s1 相同
    s2.display();

    // 修改原字符数组
    str[0] = 'h';
    // 直接输出原字符数组 str 的内容
    cout << "str: " << str << endl;
    s1.display();  // s1 的内容不变,因为它是深拷贝,独立于原字符数组
    s2.display();  // s2 的内容不变,因为它是深拷贝,独立于 s1
    return 0;
}

标签:std,初始化,对象,详解,拷贝,data,构造函数
From: https://blog.csdn.net/qq_62850449/article/details/141886290

相关文章

  • 【自由能系列(中级),代码模拟】预测编码的核心:三个关键方程式的详解
    预测编码的核心:三个关键方程式的详解——探索预测编码背后的数学原理与应用核心结论:预测编码是一种基于贝叶斯定理的理论框架,它通过三个关键方程式描述了大脑如何处理和解释来自环境的信号。这些方程式分别建立了贝叶斯定理的简化形式、生成模型以及观察者模型,共同揭示了......
  • Maven超详细教程(二):Maven 生命周期管理详解
    Maven作为Java开发者广泛使用的项目管理工具,其核心特性之一就是其强大的生命周期管理。Maven生命周期定义了一系列有序的阶段(phases),每个阶段都可以绑定特定的插件(plugins)和目标(goals),以实现项目的构建、测试、打包、部署等任务。本文将详细梳理Maven的主要生命周期、各个......
  • Java运算符(详解)
    前言:    Java中运算符有哪些?    大致分为:    算术运算符、关系运算符、逻辑运算符、位运算符、移位运算符、条件运算符接下来,一一分析。算术运算符: 基本运算符:    加减乘除,是最基本的运算符。例子:publicstaticvoidmain(Str......
  • 快速排序(动图详解)(C语言数据结构)
    快速排序:        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:        任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左......
  • JSX 语法详解
    在现代前端开发中,React框架因其高效和灵活的特点而备受青睐。作为React的核心组成部分之一,JSX(JavaScriptXML)是一种语法扩展,它允许我们在JavaScript中书写类似HTML的标签。本文将从基础入手,逐步深入地介绍JSX的基本概念、常见问题及易错点,并通过具体的代码示例来帮助大家......
  • MySQL 数据类型详解
    MySQL是一种广泛使用的关系型数据库管理系统,它支持多种数据类型以满足各种应用场景的需求。本文将详细介绍MySQL支持的数据类型、它们的使用场景以及实现原理,并通过图示帮助读者更直观地理解。目录简介数值类型整型浮点型定点型日期和时间类型字符串类型字符串二进制字......
  • PLC结构化文本(ST)——构造函数(FB_init)
    PLCStructuredTextObjectOrientedProgrammingPLC结构化文本(ST)——构造函数(FB_init)构造函数的作用简单来说,构造函数是一种方法,用来初始化类的实例也就是对象。类在创建时会有一个默认的构造函数,如果自定义一个构造函数,那么默认构造函数失效。与高级语言的区别高级语言C#......
  • 零拷贝---零拷贝---零拷贝
    硬盘上的数据,在发往网络之前,需要经过多次缓冲区的拷贝,以及用户空间和内核空间的多次切换。如果能减少一些拷贝的过程,效率就能提升,所以零拷贝应运而生。零拷贝是一种非常重要的性能优化手段,比如常见的Kafka、Nginx等,就使用了这种技术。我们来看一下有无零拷贝之间的区别。(1)......
  • 若依框架登录鉴权详解
    若依框架(Ruoyi)后端的登录权限身份认证流程是一个复杂但高效的过程,它确保了系统的安全性和数据的保护。以下是一个典型的若依框架后端登录权限身份认证流程,基于多个来源的信息进行归纳和整理:1.发起请求获取认证凭证(token)现象:用户未登录或者token过期,刷新页面将重定向至登录页......
  • 互联网算法备案必要性+攻略全流程详解【附件+流程】
    一、算法备案的重要性算法备案是指相关企业或组织向有关部门提交其使用的算法的相关信息,以接受监管和审查。这一举措有助于确保算法的公正性、透明性和合法性,保护用户的权益,促进数字经济的健康发展。算法备案必要性强制性例如,在推荐系统中,如果算法存在偏见或歧视,可能会导致......