c++--拷贝构造函数&友元函数

时间:2024-11-07 13:39:42

目录

1.拷贝构造函数是什么

2.拷贝构造函数的基本格式

2.1 默认拷贝构造函数(浅拷贝)

2.2 深拷贝(Deep Copy)

2.3 浅拷贝(Shallow Copy)

2.3 浅拷贝和深拷贝总结

2.友元函数


1.拷贝构造函数是什么

拷贝构造函数是一个特殊的构造函数,用于在创建新对象时,用已有对象的数据来初始化新对象。拷贝构造函数的典型应用场景包括:

  1. 传值参数:当一个对象通过值传递给函数时,编译器会调用拷贝构造函数来创建函数参数的副本。
  • 为了将 obj1 传递给 passByValue,C++ 会调用 拷贝构造函数 创建一个新的 MyClass 对象。这个新的对象会有自己的 str,内容是 obj1.str
  • passByValue 函数内部,调用 obj.display() 显示副本对象的数据。
  • 注意:obj 是一个副本,函数执行完后,它会被销毁,所做的任何更改都不会影响 obj1
  1. 返回值:当函数返回一个对象时,编译器会用返回的对象来创建一个临时副本,然后将该副本返回给调用者。
  • returnByValue 函数中,创建了一个局部对象 temp,它的 str 初始化为 "临时对象"
  • 然后函数通过 return temp;temp 返回给调用者。
  • 返回时,C++ 会调用 拷贝构造函数 来创建一个新的对象,这个新对象的内容就是 temp 对象的副本。这里的拷贝构造函数会执行字符串的复制操作,确保新对象拥有自己的 str 数据。
  1. 对象初始化:创建新对象并将另一个对象赋值给它时,例如 MyClass obj2 = obj1;
#include <iostream>
#include <cstring>

class MyClass {
    char* str;

public:
    // 普通构造函数
    MyClass(const char* s) {
        str = new char[strlen(s) + 1];
        strcpy(str, s);
        std::cout << "调用普通构造函数" << std::endl;
    }

    // 拷贝构造函数
    MyClass(const MyClass &obj) {
        str = new char[strlen(obj.str) + 1];
        strcpy(str, obj.str);
        std::cout << "调用拷贝构造函数" << std::endl;
    }

    // 显示字符串
    void display() const {
        std::cout << "字符串内容: " << str << std::endl;
    }

    // 析构函数
    ~MyClass() {
        delete[] str;
    }
};

// 示例 1:传值参数
void passByValue(MyClass obj) {
    obj.display();
}

// 示例 2:返回值
MyClass returnByValue() {
    MyClass temp("临时对象");
    return temp;
}

int main() {
    // 示例 3:对象初始化
    MyClass obj1("Hello");  // 调用普通构造函数
    MyClass obj2 = obj1;    // 调用拷贝构造函数 (对象初始化)

    // 示例 1:传值参数
    passByValue(obj1);  // 调用拷贝构造函数 (传值参数)

    // 示例 2:返回值
    MyClass obj3 = returnByValue();  // 调用拷贝构造函数 (返回值)

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

    return 0;
}

2.拷贝构造函数的基本格式

拷贝构造函数的格式如下:

ClassName(const ClassName &other);

这里的参数 other 是对同类型对象的常量引用

使用引用的原因是避免在传参时调用拷贝构造函数,导致递归调用;而使用常量引用可以避免修改原对象。

如果other 是传值传递的,也就是说,编译器需要创建 other 的副本。而为了创建这个副本,编译器会再次调用拷贝构造函数——这样就会导致递归调用,拷贝构造函数会一遍一遍地自己调用自己,最终导致栈溢出错误

2.1 默认拷贝构造函数(浅拷贝)

如果没有定义拷贝构造函数,编译器会生成一个默认拷贝构造函数,对每个数据成员进行浅拷贝浅拷贝的方式是逐个复制数据成员,即将源对象的每个成员变量直接赋值给新对象。

浅拷贝对包含指针的类可能不适用。如果类中包含动态分配的资源(例如 new 分配的内存),浅拷贝会导致新旧对象指向相同的内存,可能导致一些问题:

  • 双重释放:如果两个对象指向同一块内存,当一个对象析构时会释放这块内存,而当另一个对象析构时会再次尝试释放这块内存,从而引发错误。
  • 数据混乱:如果一个对象修改了内存的内容,那么另一个对象也会受影响,因为它们共享同一块内存。

为了避免这些问题,通常需要自定义拷贝构造函数

2.2 深拷贝(Deep Copy)

深拷贝会创建一个新内存区域,并复制实际的数据到新内存,这样两个对象互不影响。深拷贝适用于包含指针成员的类。

#include <iostream>
#include <cstring>

class DeepCopyClass {
public:
    char* data;

    // 构造函数
    DeepCopyClass(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }

    // 深拷贝构造函数
    DeepCopyClass(const DeepCopyClass &obj) {
        data = new char[strlen(obj.data) + 1];
        strcpy(data, obj.data); // 复制实际内容
    }

    ~DeepCopyClass() {
        delete[] data;
    }
};

int main() {
    DeepCopyClass obj1("Hello");
    DeepCopyClass obj2 = obj1; // 调用深拷贝构造函数

    // 修改 obj2 的数据
    strcpy(obj2.data, "World");

    std::cout << "obj1 数据: " << obj1.data << std::endl; // 输出 "Hello",因为两者独立
    std::cout << "obj2 数据: " << obj2.data << std::endl;

    return 0;
}

2.3 浅拷贝(Shallow Copy)

浅拷贝仅复制对象的非动态内存内容或指针的地址。这意味着:

  • 浅拷贝会将指针地址复制到新对象中,但不会创建新内存。新对象的指针与原对象指向相同的内存区域。
  • 如果两个对象共享同一内存区域时,一个对象的修改会影响另一个对象。
  • 当程序试图释放内存时,会因为多次释放相同的内存而导致悬挂指针内存泄漏问题。
#include <iostream>
#include <cstring>

class ShallowCopyClass {
public:
    char* data;

    // 构造函数
    ShallowCopyClass(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }

    // 浅拷贝构造函数
    ShallowCopyClass(const ShallowCopyClass &obj) {
        data = obj.data; // 仅复制指针地址(浅拷贝)
    }

    ~ShallowCopyClass() {
        delete[] data;
    }
};

int main() {
    ShallowCopyClass obj1("Hello");
    ShallowCopyClass obj2 = obj1; // 调用浅拷贝构造函数

    // 修改 obj2 的数据
    strcpy(obj2.data, "World");

    std::cout << "obj1 数据: " << obj1.data << std::endl; // 输出 "World",因为两者共享相同内存
    std::cout << "obj2 数据: " << obj2.data << std::endl;

    return 0;
}

2.3 浅拷贝和深拷贝总结

特性 浅拷贝 深拷贝
内存分配 复制指针地址,指向同一内存 分配新内存,复制数据
内存管理 共用同一内存,容易出现问题 各自独立,互不影响
适用场景 不涉及动态内存分配的简单对象 含指针成员的复杂对象
示例 复制地址,仅共享数据 复制内容,独立内存

2.友元函数

友元函数(Friend Function)是一种特殊的函数,可以访问类的私有(private)和保护(protected)成员,即使它不是该类的成员函数。通常,在面向对象编程中,类的私有和保护成员只能被该类的成员函数或子类访问,而友元函数则打破了这个限制,允许非成员函数访问类的私有和保护成员。

友元函数的定义方式是在类内部使用关键字 friend 声明该函数

  #include <iostream>
using namespace std;

class Box {
private:
    int width;

public:
    Box() : width(0) {}

    // 将 friend 关键字用于函数声明,使其成为友元函数
    friend void setWidth(Box &box, int w);
};

// 友元函数定义
void setWidth(Box &box, int w) {
    // 可以直接访问 Box 类的私有成员 width
    box.width = w;
}

int main() {
    Box box;
    setWidth(box, 10);  // 调用友元函数
    return 0;
}

setWidthBox 类的友元函数,虽然它不是 Box 类的成员函数,但可以直接访问 Box 类的私有成员 width

通过友元函数,可以实现:访问私有和保护成员:在特定情况下,让非成员函数或其他类可以访问类的私有和保护成员。