c++学习笔记(12) 需要对对象做拷贝时(深拷贝,浅拷贝),如何重载赋值运算符
在c++学习笔记(8)中,介绍了拷贝构造函数的概念:涉及到深拷贝和浅拷贝的概念:
拷贝构造函数:每一个类都有一个都有一个拷贝构造函数,用于拷贝对象。拷贝构造函数可以用来创建一个对象,并用另一个对象的数据初始化新建对象。缺省的拷贝构造函数和赋值运算符(=)进行对象赋值采用的是一种所谓的“浅拷贝”,即如果数据域是一个指向其他对象的指针,那么就会简单复制指针保存的地址值,而不是复制指针指向的对象的内容。
如果没有显式的定义拷贝构造函数,C++会为每个类都定义一个缺省的拷贝构造函数。这个函数简单的将参数对象中的数据域复制给新建对象中相应的副本。
比如在C++学习笔记(8)中遇到course类:需要对拷贝构造函数进行定义,使其进行深拷贝:
为什么Course类中,要自定义拷贝构造函数,实现深拷贝?
原因::因为course类数据域有指向数组的指针students,如果使用上述的浅拷贝,在copy过程中,两个指针拷贝时,保存了相同地址,即指向了相同的地址。但是在程序执行完毕时,对象需要调用析构函数delete指针students,但是如果两个对象是copy的关系,则会调用两次析构函数删除相同的指针,这是程序就会报错。所以需要自定义拷贝构造函数,实现深拷贝。使两个对象中的数据域中指针students相互独立,就不会出现上述情况。
重载赋值运算符
赋值运算符=与缺省的拷贝构造函数一样,执行的是“浅拷贝”。但是,即使重新定义了拷贝构造函数,也不能改变赋值运算符(=)的缺省行为(即进行的操作是浅拷贝)。为了改变(=)缺省行为,需要重载=运算符
course.h文件
#ifndef COURSE_H #define COURSE_H #include <string> using namespace std; class Course { private: string courseName; string* students; int numberOfStudents; int capacity; public: Course(const string& courseName, int capacity); // 构造函数 ~Course(); // 析构函数 Course(const Course& course); // 拷贝构造函数 string getCourseName() const; void addStudent(const string& name); void dropStudent(const string& name); string* getStudents() const; int getNumberOfStudents() const; Course& operator=(const Course& course); // 重载=运算符 }; #endif
course.cpp文件
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\test_test\external_file\course.h" using namespace std; Course::Course(const string& courseName, int capacity) { numberOfStudents = 0; this->courseName = courseName; this->capacity = capacity; students = new string[capacity]; // 动态分配内存空间 } Course::~Course() { delete []students; } Course::Course(const Course& course) { numberOfStudents = course.numberOfStudents; courseName = course.courseName; capacity = course.capacity; students = new string[capacity]; for(int i=0;i<numberOfStudents; i++) // 实现deepcopy { students[i] = course.students[i]; } } string Course::getCourseName() const { return courseName; } void Course::addStudent(const string& name) { if(numberOfStudents>=capacity) { cout << "The class hsa been fuul" << endl; exit(0); // 课程添加人数已满,退出程序 } students[numberOfStudents++] = name; } void Course::dropStudent(const string& name) { int index = 0; bool found_flag = false; for(int i=0; i<numberOfStudents; i++) { if(name==students[i]) { index = i; found_flag = true; break; } //index++; } if(found_flag) { for(int j=index; j<numberOfStudents-1; j++) { students[j] = students[j+1]; } numberOfStudents--; cout << "Student " << name << " is successfully deleted from class!" << endl; } else { cout << "The student not in the class" << endl; exit(0); } } string* Course::getStudents() const { return students; } int Course::getNumberOfStudents() const { return numberOfStudents; } // 重载=运算符 Course& Course::operator=(const Course& course) { if(this!=&course) //如果是对象自己给自己复制,则不进行操作 { courseName = course.courseName; numberOfStudents = course.numberOfStudents; capacity = course.capacity; // 删除就的指针,理解:缺省的=运算符和拷贝构造函数,会首先创建一个对象 // 再将成员数据逐个赋值,这里的=在创建了新的对象后,后续的默认操作没有进行(逐个成员复制值) // 就被重载,所以在重载中delete []students时,此时的students还没有复制course对象中的指针值 // 所以直接delete掉。不会影响course中students指向的内容 // delete p的本质是将p指向的内存(由new分配的)释放掉,使p成为一个悬空的指针。 delete [] this->students; // delete the old array students = new string[capacity]; for(int i=0; i<numberOfStudents; i++) { students[i] = course.students[i]; } } return *this; }
main.cpp文件
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\test_test\external_file\course.h" using namespace std; void displayStudents(string*, int); void printStudentsList(const Course&); int main(int argc, char *argv[]) { Course course1("Java class", 20); Course course2("C++ class", 30); course1.addStudent("zhangsan"); course1.addStudent("lisi"); course2 = course1; // =运算符 course1.addStudent("zhaoliu"); course2.addStudent("wangwu"); string* student_list1 = course1.getStudents(); string* student_list2 = course2.getStudents(); int number1 = course1.getNumberOfStudents(); int number2 = course2.getNumberOfStudents(); displayStudents(student_list1, number1); displayStudents(student_list2, number2); course1.dropStudent("zhangsan"); course2.dropStudent("wangwu"); printStudentsList(course1); printStudentsList(course2); return 0; } void displayStudents(string* student, int studentNumber) { for(int i=0; i<studentNumber; i++) { cout << student[i] << endl; } cout << endl; } void printStudentsList(const Course& course) { string* student_list = course.getStudents(); int student_number = course.getNumberOfStudents(); for(int i=0; i<student_number; i++) { cout << student_list[i] << endl; } cout << endl; }
运行结果:
注:(书本)
拷贝构造函数,析构函数,=运算符,称为三规则或者大三元,如果他们没有显式的说明,将会被编译器自动生成,同时具有自己的“缺省行为”。如果类中有数据成员指向动态生成的数组或者对象,那么需要对大三元对应的内容进行修改。其中一个,其他两个也做对应的修改。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)