C++ Classes and Objects-3 (Copy Constructor and Operator Overloading) (Super Detailed)

1. Copy constructor

1.1 Concept

In real life, there may be a self identical to you, which we call a twin.

Insert image description here
When creating an object, can you create a new object that is the same as an existing object?

Copy constructor: There is only a single formal parameter . This formal parameter is a reference to an object of this class type (generally commonly used with const modification). It is automatically called by the compiler when creating a new object with an existing class type object.

1.2 Features

The copy constructor is also a special member function with the following characteristics:

1. The copy constructor is an overloaded form of the constructor.


class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		cout << "Date(int,int,int):" << this << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
    
    
		cout << "Date(const Date& d):" << this << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
		
	}
	~Date()
	{
    
    
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1(2022, 1, 13);
	Date d2(d1);
	return 0;
}

Insert image description here


2. There is only one parameter to the copy constructor and it must be a reference to a class type object. If you use the pass-by-value method, the compiler will directly report an error because it will cause infinite recursive calls.


class Date
{
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
    
    
        _year = year;
        _month = month;
        _day = day;
    }
    
    //Date(const Date d)   // 错误写法:编译报错,会引发无穷递归
    Date(const Date& d)   // 正确写法
    {
    
    
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    
    
    Date d1;
    Date d2(d1);
    return 0;
}

Insert image description here


3. If not explicitly defined, the compiler will generate a default copy constructor. The default copy constructor object copies the object in byte order according to memory storage. This type of copy is called shallow copy, or value copy.

class Time
{
    
    
public:
	Time()
	{
    
    
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
    
    
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 如果Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

In the above code:
If d2 is constructed by copying the existing d1, the copy constructor of the Date class will be called here;
if the Date class does not explicitly define a copy constructor, the compiler will generate a default copy for the Date class. Constructor.

Note: In the default copy constructor generated by the compiler, the built-in type is copied directly in byte form, while the custom type is copied by calling its copy constructor.


4. The default copy constructor generated by the compiler can already complete byte order value copying. Do I still need to implement it explicitly by myself? Of course classes like date classes are not necessary. What about the following classes? Try verifying it?

Here you will find that the following program will crash:

typedef int DataType;
class Stack
{
    
    
public:
	Stack(size_t capacity = 10)
	{
    
    
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
    
    
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

Insert image description here

Therefore: if there is no resource application involved in the class, the copy constructor can be written or not; once resource application is involved, the copy constructor must be written, otherwise it will be a shallow copy.


5. Typical calling scenarios of copy constructor:

  • Create a new object using an existing object
  • The function parameter type is a class type object
  • The function return value type is a class type object

Let’s look at a sample code:

class Date
{
    
    
public:
	Date(int year, int minute, int day)
	{
    
    
		cout << "Date(int,int,int):" << this << endl;
	}
	Date(const Date& d)
	{
    
    
		cout << "Date(const Date& d):" << this << endl;
	}
	~Date()
	{
    
    
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Test(Date d)
{
    
    
	Date temp(d);
	return temp;
}
int main()
{
    
    
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

Running results:
Insert image description here
Graphical illustration:
Insert image description here

In order to improve program efficiency, generally try to use reference types when passing object parameters. When returning, use references as much as possible according to the actual scenario.


2. Assignment operator overloading

2.1 Operator overloading

C++ introduces operator overloading in order to enhance the readability of the code. Operator overloading is a function with a special function name . It also has its return value type, function name and parameter list. Its return value type and parameter list are similar to ordinary functions.
The function name is: the keyword operator is followed by the operator symbol that needs to be overloaded.
Function prototype: return value type operator operator (parameter list)

  • New operators cannot be created by concatenating other symbols: such as operator@
  • Overloaded operators must have a class type parameter
  • Operators used for built-in types cannot change their meaning. For example: the built-in integer type + cannot change its meaning.
  • When overloaded as a class member function, its formal parameters appear to be 1 less than the number of operands, because the first parameter of the member function is hidden this
  • [.* ] [ :: ] [ sizeof ] [ ?: ] [.] Note that the above five operators cannot be overloaded. This often appears in multiple-choice questions in written exams.

Let’s look at a global operator==

class Date
{
    
     
public:
 Date(int year = 1900, int month = 1, int day = 1)
   {
    
    
        _year = year;
        _month = month;
        _day = day;
   }    
//private:
 int _year;
 int _month;
 int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
    
    
    return d1._year == d2._year
   && d1._month == d2._month
        && d1._day == d2._day;
}
void Test ()
{
    
    
    Date d1(2018, 9, 26);
    Date d2(2018, 9, 27);
    cout<<(d1 == d2)<<endl;
}

You will find that overloading the operator into a global one requires that the member variables be public. Then the question arises, how to ensure encapsulation?
This can actually be solved using the friends we will learn later, or simply overloaded into member functions.

as follows:

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
    {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    // bool operator==(Date* this, const Date& d2)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象
    bool operator==(const Date & d2)
    {
    
    
        return _year == d2._year
        && _month == d2._month
            && _day == d2._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

2.2 Assignment operator overloading

1. Assignment operator overloading format

  • Parameter type: const T&, passing reference can improve the efficiency of parameter passing.
  • Return value type: T&. Returning a reference can improve the efficiency of return. The purpose of returning a value is to support continuous assignment.
  • Check whether you assign a value to yourself
  • Return *this: To compound the meaning of continuous assignment
class Date
{
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date& d)
    {
    
    
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    Date& operator=(const Date& d)
    {
    
    
        if (this != &d)
        {
    
    
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }

        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};

2. The assignment operator can only be overloaded as a member function of the class and cannot be overloaded as a global function.

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
    
    
	if (&left != &right)
	{
    
    
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

Insert image description here
reason:

If the assignment operator is not implemented explicitly, the compiler will generate a default one. At this time, if the user implements a global assignment operator overload outside the class, it will conflict with the default assignment operator overload generated by the compiler in the class, so the assignment operator overload can only be a member function of the class.
[In C++, the assignment operator is designed as an operation between objects of a specific class. It needs to access private members of the class and also handle situations such as self-assignment. In order to be able to access members of a class, the assignment operator must be a member function of that class.


3. When the user does not explicitly implement it, the compiler will generate a default assignment operator overload and copy it byte by value in the form of value. Note: Built-in type member variables are directly assigned, while custom type member variables need to call the assignment operator overload of the corresponding class to complete the assignment.

class Time
{
    
    
public:
	Time()
	{
    
    
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
    
    
		if (this != &t)
		{
    
    
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

Now that the default assignment operator overloaded function generated by the compiler can complete byte order value copying, do you still need to implement it yourself? Of course classes like date classes are not necessary. What about the following classes? Try verifying it?

Here you will find that the following program will crash:

typedef int DataType;
class Stack
{
    
    
public:
	Stack(size_t capacity = 10)
	{
    
    
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
    
    
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);  
	Stack s2;
	s2 = s1;
	return 0;
}

Insert image description here

Note: If resource management is not involved in the class, the assignment operator can be implemented or not; once resource management is involved, it must be implemented.

Take note

Copy construction: An existing object is used to initialize another object to be created.
Assignment: Two existing objects are copied to generate

a constructor and destructor by default:

1. Built-in types are not processed.
2. When a custom type calls the default constructor,

copy construction and assignment overloaded functions are generated by default:

1. Built-in type value copy (shallow copy)
2. Custom type calls copy construction and assignment overloaded functions

Typical calling scenarios of copy constructor:

1. Create a new object using an existing object
2. The function parameter type is a class type object
3. The function return value type is a class type object

2.3 Pre++ and post++ overloading

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	Date& operator++()
	{
    
    
		_day += 1;
		return *this;
	}
	// 后置++:
		Date operator++(int)
	{
    
    
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d;
	Date d1(2022, 1, 13);
	d = d1++;    // d: 2022,1,13   d1:2022,1,14
	d = ++d1;    // d: 2022,1,15   d1:2022,1,15
	return 0;
}

Prefix++:

Return the result after +1.
Note: The object pointed to by this will not be destroyed after the function ends, so it is returned by reference to improve efficiency.

Date& operator++()
	{
    
    
		_day += 1;
		return *this;
	}

Post++:

Both prefix ++ and postfix ++ are unary operators. In order to allow the formation of prefix ++ and post++ to be correctly overloaded,
C++ stipulates that when post++ is overloaded, an additional int type parameter is added, but This parameter does not need to be passed when calling the function, the compiler automatically passes it.
Note: Postfix ++ is used first and then +1, so the old value before +1 needs to be returned. Therefore, you need to save a copy of this first during implementation, and then give this + 1 and temp is a temporary object, so it can only be returned as a value, not a reference.

Date operator++(int)
	{
    
    
		Date temp(*this);
		_day += 1;
		return temp;
	}

3. const member function

The const-modified "member function" is called a const member function. The const- modified class member function actually modifies the this pointer implicit in the member function , indicating that no member of the class can be modified in the member function.
Insert image description here

Let’s take a look at the following code:

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
    
    
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
    
    
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}

Please consider the following questions:

  1. Can a const object call non-const member functions?
  2. Can non-const objects call const member functions?
  3. Can other non-const member functions be called within a const member function?
  4. Can other const member functions be called within a non-const member function?

Answer:
1 and 3 are not allowed, 2 and 4 are OK.

Take note

[Permissions can be panned and reduced, but cannot be enlarged]

4. Address retrieval and const address retrieval operator overloading

These two default member functions generally do not need to be redefined, and the compiler will generate them by default.

class Date
{
    
    
public:
	Date* operator&()
	{
    
    
		return this;
	}

const Date* operator&()const
{
    
    
	return this;
}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

These two operators generally do not need to be overloaded. Just use the default address-taking overload generated by the compiler. Only in special circumstances, overloading is needed, such as if you want others to get the specified content!

(End of chapter)

Guess you like

Origin blog.csdn.net/originalHSL/article/details/131989181