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

 构造函数的分类

  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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值