1. 程式人生 > >c++學習筆記(12) 需要對物件做拷貝時(深拷貝,淺拷貝),如何過載賦值運算子

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

執行結果:

注:(書本)

拷貝建構函式,解構函式,=運算子,稱為三規則或者大三元,如果他們沒有顯式的說明,將會被編譯器自動生成,同時具有自己的“預設行為”。如果類中有資料成員指向動態生成的陣列或者物件,那麼需要對大三元對應的內容進行修改。其中一個,其他兩個也做對應的修改。