C++ 基础

C++ 主页 C++ 概述 C++ 环境设置 C++ 基本语法 C++ 注释 C++ Hello World C++ 省略命名空间 C++ 标记 C++ 常量/字面量 C++ 关键字 C++ 标识符 C++ 数据类型 C++ 数字数据类型 C++ 字符数据类型 C++ 布尔数据类型 C++ 变量类型 C++ 变量作用域 C++ 多变量 C++ 基本输入/输出 C++ 修饰符类型 C++ 存储类 C++ 数字 C++ 枚举 C++ 枚举类 C++ 引用 C++ 日期和时间

C++ 运算符

C++ 运算符 C++ 算术运算符 C++ 关系运算符 C++ 逻辑运算符 C++ 位运算符 C++ 赋值运算符 C++ sizeof 运算符 C++ 条件运算符 C++ 逗号运算符 C++ 成员运算符 C++ 强制类型转换运算符 C++ 指针运算符 C++ 运算符优先级 C++ 一元运算符

C++ 控制语句

C++ 决策语句 C++ if 语句 C++ if else 语句 C++ 嵌套 if 语句 C++ switch 语句 C++ 嵌套 switch语句 C++ 循环类型 C++ while 循环 C++ for 循环 C++ do while 循环 C++ Foreach 循环 C++ 嵌套循环 C++ break 语句 C++ continue 语句 C++ goto 语句

C++ 字符串

C++ 字符串 C++ 循环遍历字符串 C++ 字符串长度 C++ 字符串连接 C++ 字符串比较

C++ 函数

C++ 函数 C++ 多函数参数 C++ 递归函数 C++ 返回值 C++ 函数重载 C++ 函数重写 C++ 默认参数

C++ 数组

C++ 数组 C++ 多维数组 C++ 指向数组的指针 C++ 将数组传递给函数 C++ 从函数返回数组

C++ 结构 &联合

C++ 结构 C++ 联合

C++ 指针

C++ 指针 C++ 解引用 C++ 修改指针

C++ 类和对象

C++ 面向对象 C++ 类 &对象 C++ 类成员函数 C++ 类访问修饰符 C++ 静态类成员 C++ 静态数据成员 C++ 静态成员函数 C++ 内联函数 C++ this 指针 C++ 友元函数 C++ 指向类的指针

C++ 构造函数

C++ 构造函数 &析构函数 C++ 默认构造函数 C++ 参数化构造函数 C++ 复制构造函数 C++ 构造函数重载 C++ 带默认参数的构造函数 C++ 委托构造函数 C++ 构造函数初始化列表 C++ 使用构造函数动态初始化

C++ 继承

C++ 继承 C++ 多重继承 C++ 多级继承

C++ 面向对象

C++ 重载 C++ 多态性 C++ 抽象 C++ 封装 C++ 接口 C++ 虚函数 C++ 纯虚函数与抽象类

C++ 文件处理

C++ 文件和流 C++ 文件读取

C++ 进阶

C++ 异常处理 C++ 动态内存 C++ 命名空间 C++ 模板 C++ 预处理器 C++ 信号处理 C++ 多线程 C++ Web 编程 C++ 套接字编程 C++ 并发 C++ 高级概念 C++ Lambda 表达式 C++ unordered_multiset

C++ 实用资源

C++ 问答 C++ 快速指南 C++ 速查表 C++ STL 教程 C++ 标准库 C++ 实用资源 C++ 讨论


C++ 复制构造函数

复制构造函数

复制构造函数是一种构造函数,它通过使用先前创建的同一类的对象来初始化一个对象来创建一个对象。复制构造函数用于:-

  • 用同类型的另一个对象初始化一个对象。
  • 复制一个对象并将其作为参数传递给函数。
  • 复制一个对象并将其从函数中返回。

如果类中未定义复制构造函数,则编译器会自行定义一个。如果类包含指针变量并进行动态内存分配,则必须拥有复制构​​造函数。

语法

复制构造函数的最常见形式如下所示:-

classname (const classname &obj) {
    // 构造函数主体
}

此处,obj 是对用于初始化另一个对象的对象的引用对象。

复制构造函数示例

以下示例演示了复制构造函数的用法:

#include <iostream>

using namespace std;

class Line {

   public:
    int getLength( void );
    Line( int len ); // 简单构造函数
    Line( const Line &obj); // 复制构造函数
    ~Line(); // 析构函数

   private:
    int *ptr;
};

// 成员函数定义包括构造函数
Line::Line(int len) {
   cout << "Normal constructor allocating ptr" << endl;
   
   // 为指针分配内存;
   ptr = new int;
   *ptr = len;
}

Line::Line(const Line &obj) {
   cout << "Copy constructor allocating ptr." << endl;
   ptr = new int;
   *ptr = *obj.ptr; //复制值
}

Line::~Line(void) {
   cout << "Freeing memory!" << endl;
   delete ptr;
}

int Line::getLength( void ) {
   return *ptr;
}

void display(Line obj) {
   cout << "Length of line : " << obj.getLength() <<endl;
}

// 程序的主函数
int main() {
   Line line(10);

   display(line);

   return 0;
}

当编译并执行上述代码时,它会产生以下结果 -

Normal constructor allocating ptr
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!

使用复制构造函数创建新对象

您可以使用复制构造函数的概念,利用现有对象创建新对象。

在下面的示例中,复制构造函数用于创建一个新对象作为现有对象的副本。

示例

让我们看一个相同的示例,但略作修改,使用相同类型的现有对象创建另一个对象 -

#include <iostream>

using namespace std;

class Line {
   public:
    int getLength( void );
    Line( int len ); // 简单构造函数
    Line( const Line &obj); // 复制构造函数
    ~Line(); // 析构函数

   private:
    int *ptr;
};

// 成员函数定义包括构造函数
Line::Line(int len) {
   cout << "Normal constructor allocating ptr" << endl;
   
   // 为指针分配内存;
   ptr = new int;
   *ptr = len;
}

Line::Line(const Line &obj) {
   cout << "Copy constructor allocating ptr." << endl;
   ptr = new int;
   *ptr = *obj.ptr; // 复制值
}

Line::~Line(void) {
   cout << "Freeing memory!" << endl;
   delete ptr;
}

int Line::getLength( void ) {
   return *ptr;
}

void display(Line obj) {
   cout << "Length of line : " << obj.getLength() <<endl;
}

// 程序的主函数
int main() {

   Line line1(10);

   Line line2 = line1; // 这也调用复制构造函数

   display(line1);
   display(line2);

   return 0;
}

当编译并执行上述代码时,它会产生以下结果 -

Normal constructor allocating ptr
Copy constructor allocating ptr.
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!
Freeing memory!

隐式与显式复制构造函数

在 C++ 中,有两种类型的复制构造函数:隐式和显式。本文我们将讨论这两者之间的区别。

隐式复制构造函数

如果用户未定义自己的复制构造函数,则编译器会自动提供一个隐式复制构造函数。它执行对象的浅拷贝,这意味着它将对象每个成员的值复制到新对象中。

何时调用隐式复制构造函数?

  • 当用户将对象按值传递给函数时。
  • 当用户从函数按值返回对象时。
  • 当用户使用同类型的另一个对象初始化一个对象时(复制初始化)。

显式(用户定义)复制构造函数

它是用户定义的构造函数。这使您可以自定义复制行为,例如创建深层复制而不是默认的浅层复制。

示例

以下是 C++ 中显式和隐式复制构造函数的示例:

#include <iostream>
using namespace std;

class MyClass {
 private:
  int value;

 public:
  // 构造函数
  MyClass(int v) : value(v) {}

  // 显式复制构造函数
  MyClass(const MyClass& other) : value(other.value) {
    cout << "Explicit Copy Constructor called" << endl;
  }

  void display() const { cout << "Value: " << value << endl; }
};

void processValue(MyClass obj) {
    // 此处将调用隐式复制构造函数
    obj.display();
}

int main() {
    MyClass obj1(10); // 调用构造函数
    MyClass obj2 = obj1; // 调用显式复制构造函数
    obj1.display();
    obj2.display();
    
    processValue(obj1); // 调用隐式复制构造函数
    return 0;
}

当编译并执行上述代码时,它会产生以下结果 -

Explicit Copy Constructor called
Value: 10
Value: 10
Explicit Copy Constructor called
Value: 10

三/五规则

三规则和五规则建议,在定义复制构造函数 (ClassName(const ClassName& other)) 时,还应定义:

三规则和五规则建议,在定义复制构造函数 (ClassName(const ClassName& other)) 时,还应定义:

  • 三规则:
    • 析构函数(~ClassName())
    • 以及复制赋值运算符(ClassName& operant=(const ClassName& other)),以确保正确管理内存
  • 五规则:
    • 移动构造函数(ClassName(ClassName&& other)).
    • 移动赋值运算符 (ClassName& 运算符=(ClassName&& other))". 

这些特殊的成员函数对于正确管理类中的动态内存和其他资源(例如文件处理或网络连接)至关重要。

深拷贝 vs. 浅拷贝

在 C++ 中,深拷贝和浅拷贝是复制对象的不同方式,当类涉及动态内存管理时,它们非常重要。

1. 浅拷贝

当对象以某种方式复制时,原始对象和复制对象共享相同的资源,就会发生浅拷贝。这意味着复制构造函数或复制赋值运算符仅复制数据成员(如指针)的值,而不分配新内存或创建资源的独立副本。

示例

#include <iostream>
using namespace std;

class MyClass {
 private:
  int* data;  // 指向整数的指针

 public:
    // 构造函数
    MyClass(int value) {
        data = new int(value); // 分配内存
    }
    
    // 浅拷贝构造函数
    MyClass(const MyClass& other) {
        data = other.data; // 仅复制指针
    }
    
    // 析构函数
    ~MyClass() {
        delete data; // 释放内存
    }
    
    // 显示值
  	void showData() const { cout << "Data: " << *data << endl; }
};

int main() {
    MyClass obj1(42); // 创建对象
    MyClass obj2 = obj1; // 使用浅拷贝构造函数
    
    obj1.showData();
    obj2.showData();
    
    return 0;
}

当编译并执行上述代码时,它会产生以下结果 -

Data: 42
Data: 42
free(): double free detected in tcache 2

2. 深层复制

对象复制时,会为其自身分配新的内存,以确保原始对象和复制对象完全独立。避免双重释放错误或悬空指针。

示例

#include <iostream>
using namespace std;

class MyClass {
 private:
  int* data;  // 指向整数的指针

 public:
    // 构造函数:动态分配内存
    // 并使用值进行初始化
    MyClass(int value) { data = new int(value); }
    
    // 深拷贝构造函数
    // 分配新内存并复制值
    MyClass(const MyClass& other) { data = new int(*other.data); }
    
    // 析构函数用于清理内存
    ~MyClass() { delete data; }
    
    // 显示值
  	void showData() const { cout << "Data: " << *data << endl; }
};

int main() {
    MyClass obj1(42); // 创建对象
    MyClass obj2 = obj1; // 使用深拷贝构造函数
    
    obj1.showData(); // 显示 obj1 中的数据
    obj2.showData(); // 显示 obj2 中的数据
    
    return 0;
}

当编译并执行上述代码时,它会产生以下结果 -

Data: 42
Data: 42