c++ 类的特殊成员函数:拷贝构造函数(四)

1. 简介

拷贝构造是一种特殊的构造函数,用于创建一个对象,该对象是从同一类中的另一个对象复制而来的。拷贝构造函数通常采用引用参数来接收要复制的对象,并使用该对象的副本来创建一个新对象。

2. 结构

class MyClass {
public:
    MyClass(const MyClass& other) {
        // 在此处实现拷贝构造函数
    }
};

具体代码:

#include 
#include 

using namespace std;

//拷贝构造函数
class stu{
public:
    string name;
    int age;
    stu(){
        cout << "无参构造" <<endl;
    }
    stu(string name , int age ) : name(name) , age (age){
        cout << "有参构造" <<endl;
    }

    //-=========================
    //stu s2 = s1;
    stu(stu & s){
        cout << "运行拷贝构造函数" <<endl;
        //把拷贝源对象的数据,拷贝到现在创建的对象身上。
        name = s.name;
        age = s.age ;
    }

    //==========================
    ~stu(){
        cout << "析构函数" <<endl;
    }
};

int main() {

    stu s1("叶凡" ,24);
    cout << "s1: " <<s1.name << " " << s1.age <<endl;

    stu s2 = s1; //会执行拷贝构造函数
    cout << "s2: " <<s2.name << " " << s2.age <<endl;

    return 0;
}

运行结果:

有参构造
s1: 叶凡 24
运行拷贝构造函数
s2: 叶凡 24
析构函数
析构函数

可知 s1对象是有参构造,s2 对象利用 s1 拷贝构造的,两个是不同的对象,所以执行两次析构函数。

3.细节

拷贝构造函数:

  1. 由于是拷贝,所以函数的参数一定是拷贝源(源对象),如果是其他参数例如,则是有参构造
 stu(int age)
  1. 参数一定是当前类的类型,不能是其他类型。
  2. 为什么拷贝构造函数的参数一定是引用的类型呢?
    (1)避免无限递归调用:如果拷贝构造函数的参数不是引用,那么在执行拷贝构造函数时会一直调用自己,造成无限递归。这是因为拷贝构造函数的定义是使用一个同类对象来初始化新对象,如果参数不是引用,那么在创建参数对象时需要调用拷贝构造函数,如此就会形成无限递归,最终导致程序堆栈溢出。
stu(const stu & s){// stu s = s1; --->?  stu s = s1;  ---> stu s = s1;

(2)保证对象的数据正确性:拷贝构造函数的目的是创建一个与原对象完全相同的新对象。如果参数是值传递,那么在创建参数时就需要调用拷贝构造函数,这就会使得新对象与原对象不同。而使用引用传递,可以确保新对象与原对象完全一致。

  1. 拷贝构造函数,一般会给参数加上 const , 为什么?
    为了确保在拷贝构造函数中不会对传递进来的对象进行修改,避免无意中修改原始对象。

4. 浅拷贝的问题

对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。如果数据中有属于动态成员( 在堆内存存放 ) ,那么浅拷贝只是做指向而已,不会开辟新的空间。默认情况下,编译器提供的拷贝操作即是浅拷贝。

#include 
#include 

using namespace std;

class stu{
public:
    string * name = nullptr;
    int age;
    
    stu(){
        cout << "无参构造" <<endl;
    }
    
    stu(string name , int age ) : name(new string(name)) , age (age){
        cout << "有参构造" <<endl;
    }
    
    stu(stu & s){
        cout << "拷贝构造" <<endl;
        name = s.name;
        age = s.age ;
    }
    
    ~stu(){
        cout << "析构函数" <<endl;
    }
};


int main() {

    stu s1("叶凡" ,24);
    cout << "s1: " <<*s1.name << " " << s1.age <<endl;

    stu s2 = s1;

    *s2.name = "叶黑";
    cout << "s1: " <<*s1.name << " " << s1.age <<endl;
    cout << "s2: " <<*s2.name << " " << s2.age <<endl;

    return 0;
}

运行结果:

有参构造
s1: 叶凡 24
拷贝构造
s1: 叶黑 24
s2: 叶黑 24
析构函数
析构函数

画图显示其中的关系:
c++ 类的特殊成员函数:拷贝构造函数(四)_第1张图片浅拷贝的问题:
如果一个类有一个指向动态分配内存的指针成员变量

  1. 在拷贝对象和原始对象中,成员变量所指向的内存区域是相同的,如果在拷贝对象中修改了这些成员变量,那么原始对象中对应的成员变量也会被修改,这可能会导致意想不到的行为。
  2. 如果拷贝构造函数使用浅拷贝,那么拷贝对象和原始对象将共享同一块内存,如果在拷贝对象中释放了这块内存,那么原始对象会成为一个悬空指针,可能导致程序崩溃或者出现未定义的行为。

5. 深拷贝

#include 
#include 

using namespace std;

class stu{
public:
    string * name = nullptr;
    int age;

    stu(){
        cout << "无参构造" <<endl;
    }

    stu(string name , int age ) : name(new string(name)) , age (age){
        cout << "有参构造" <<endl;
    }

    stu(stu & s){
        cout << "拷贝构造" <<endl;
        //name = s.name;
        name = new string (*s.name);
        age = s.age ;
    }

    ~stu(){
        cout << "析构函数" <<endl;
    }
};


int main() {

    stu s1("叶凡" ,24);
    cout << "s1: " <<s1.name  << " " << *s1.name << " " << s1.age <<endl;

    stu s2 = s1;
    cout << "s2: " <<s2.name << " " << *s1.name<< " " << s2.age <<endl;

    *s2.name = "叶黑";

    cout << "s1: " <<s1.name << " " << *s1.name<< " " << s1.age <<endl;
    cout << "s2: " <<s2.name << " " << *s2.name<< " " << s2.age <<endl;

    return 0;
}

运行结果:

有参构造
s1: 000001F37461E410 叶凡 24
拷贝构造
s2: 000001F3746235C0 叶凡 24
s1: 000001F37461E410 叶凡 24
s2: 000001F3746235C0 叶黑 24
析构函数
析构函数

图标说明如下:
c++ 类的特殊成员函数:拷贝构造函数(四)_第2张图片

深拷贝也是执行拷贝,只是在面对对象含有动态成员时,会执行新内存的开辟,仅仅是拷贝数据,而两个对象的指针成员有各自的空间。

6. 拷贝出现的场景

6.1 对象的创建依赖于其他对象

当使用一个对象初始化另一个对象时,拷贝构造函数将被调用来创建新对象的副本。

MyClass obj1;
MyClass obj2 = obj1; // 拷贝构造函数被调用来创建 obj2

6.2 函数参数(传递对象)

如果一个对象作为参数传递给一个函数,那么拷贝构造函数将被调用来创建一个新的对象,该对象是原始对象的副本。例如:

void func(MyClass obj) {
  // ...
}

MyClass obj1;
func(obj1); // 拷贝构造函数被调用来创建 obj2

6.3 函数返回值 (返回对象)

如果一个函数返回一个对象,那么拷贝构造函数将被调用来创建返回值的副本。例如:

MyClass func() {
  MyClass obj;
  // ...
  return obj; // 拷贝构造函数被调用来创建返回值的副本
}

只要存在了类型 对象 = 已有对象 ----> 就一定会执行拷贝构造函数。

你可能感兴趣的:(c/c++,c++,开发语言)