一,对象的复制
对象复制时可以使用的运算符:=,(),{},={}等。
默认情况下的对象复制是将对象的每个成员变量逐个进行复制,可以通过定义拷贝构造函数或重载赋值运算符"operator="来改变默认操作。
代码样例:
对象的定义:
cpp
class Time
{
public:
int Hour;
int Minute;
int Second;
};
对象的复制操作:
cpp
Time myTime;
Time myTime1 = myTime;
Time myTime2(myTime);
Time myTime3{myTime};
Time myTime4 ={myTime};
二,拷贝构造函数
1.概念介绍
如果复制一个基本数据类型的变量,比如int,是可以直接进行拷贝的,如果复制一个类类型的变量,则只能使用拷贝构造函数类进行拷贝。
第一个参数是类类型的引用。
对象发生复制时会调用拷贝构造函数。
如果定义一个类的时候没有定义自己的拷贝构造函数,编译器会根据需要生成一个默认的拷贝构造函数。
只要发生值传递的方式产生一个新的对象,编译器就会调用拷贝构造函数进行初始化。
由于拷贝构造函数被用来初始化非引用类型的对象,因此拷贝构造函数本身的参数必须是引用类型。
除了显式调用拷贝构造函数初始化对象,编译器遇到以下情况也会调用拷贝构造函数:
1.将一个对象作为实参传递给一个非引用类型的形参。
2.函数返回非引用类型的对象。
2.拷贝构造函数的代码样式
cpp
ClassName(const ClassName& param)
{
//process code
}
3.代码样例
Demo1:
cpp
#include <iostream>
using namespace std;
class Time
{
public:
int Hour;
int Minute;
int Second;
Time() {
std::cout << "调用了构造函数." << std::endl;
}
Time(const Time& tmpTime)
{
std::cout << "调用了拷贝构造函数." << std::endl;
}
};
int main() {
Time myTime;
Time myTime1 = myTime;
Time myTime2(myTime);
Time myTime3{ myTime };
Time myTime4 = { myTime };
return 0;
}
运行结果:
bash
调用了构造函数.
调用了拷贝构造函数.
调用了拷贝构造函数.
调用了拷贝构造函数.
调用了拷贝构造函数.
Demo2:
cpp
#include <iostream>
using namespace std;
class Wall {
private:
double length;
double height;
public:
Wall(double len, double hgt) {
length = len;
height = hgt;
}
//copy constructor with a Wall object as parameter
Wall(Wall& obj) {
length = obj.length;
height = obj.height;
}
double calculateArea() {
return length * height;
}
};
int main() {
// create an object of Wall class
Wall wall1(10.5, 8.6);
// copy contents of wall1 to wall2
Wall wall2 = wall1;
cout << "Area of Wall 1: " << wall1.calculateArea() << endl;
cout << "Area of Wall 2: " << wall2.calculateArea() << endl;
return 0;
}
运行结果:
bash
Area of Wall 1: 90.3
Area of Wall 2: 90.3
三,拷贝赋值运算符
1.概念介绍
拷贝赋值运算符是二元运算符"operator="的重载,它只能被定义为类的成员函数,不能被定义为普通函数。
拷贝赋值运算符把右操作数的成员数据拷贝给左操作数的成员。
为了避免对象在拷贝过程中的不必要的复制,拷贝赋值运算符返回类型为引用类型。
拷贝赋值运算符运行结束一般会返回指向该对象的this指针,方便被连续调用。
拷贝赋值运算符的使用场景和拷贝构造函数不一样,如果对一个已经构造过的对象进行拷贝赋值,则此时并不会调用拷贝构造函数,而是调用拷贝赋值运算符。
在重载赋值运算符的时候,也可以让拷贝赋值运算符复制不同类型的对象,只需要在重载的函数内部增加相应的类型转换逻辑即可实现。
以下代码区分了拷贝构造和拷贝赋值:
cpp
Time myTime;
Time myTime1 = myTime; //调用了拷贝构造函数
Time myTime2;
myTime2 = myTime; //没有调用拷贝构造函数,调用了拷贝赋值运算符
2.拷贝赋值运算符的代码样式
cpp
ClassName& operator=(const ClassName& param)
{
//process code
}
3.代码样例
cpp
#include <iostream>
using namespace std;
class Time
{
public:
int Hour;
int Minute;
int Second;
Time() {
std::cout << "调用了构造函数." << std::endl;
}
Time(const Time& tmpTime)
{
std::cout << "调用了拷贝构造函数." << std::endl;
}
Time& operator=(const Time& tmpTime)
{
std::cout << "调用了拷贝赋值运算符." << std::endl;
return *this;
}
};
int main() {
Time myTime;
Time myTime1 = myTime;
Time myTime2;
myTime2 = myTime;
return 0;
}
运行结果:
bash
调用了构造函数.
调用了拷贝构造函数.
调用了构造函数.
调用了拷贝赋值运算符.
如果想禁止对象之间的拷贝赋值,可以将赋值运算符重载用private修饰,代码样例:
cpp
private:
Time& operator=(const Time& tmpTime);
四,对象的移动
将对象A的所有权转移给对象B。
对于同一块内存,原先用来存放对象A,发生对象移动以后,这块内存用来存放对象B。
发生对象移动以后,原有的对象A将不能再被使用。
五,移动构造函数
1.概念介绍
和拷贝赋值运算符一样,移动构造函数也是二元运算符"operator="的重载,它只能被定义为类的成员函数,不能被定义为普通函数。
移动构造函数在构造对象的时候避免了拷贝一个新的对象。
移动构造函数可以重复利用原有的内存空间,提供了代码效率。
移动构造函数的形参是(&&)右值引用,而不是(&)左值引用。
当一个对象发生移动以后,不会自主销毁,我们可以在移动构造函数的代码逻辑中显式地让该对象被析构。
2.移动构造函数的代码样式
cpp
ClassName(const ClassName&& param)
{
//process code
}
六,移动赋值运算符算
1.概念介绍
和拷贝赋值运算符的逻辑类似,如果对一个已经构造过的对象进行移动,则此时并不会调用移动构造函数,而是调用移动赋值运算符。
以下场景的成员变量可以移动:
基本数据类型(int, float)的成员变量可以移动。
类类型的成员变量,且这个类有对应的移动操作相关的函数。
2.移动赋值运算符的代码样式
cpp
ClassName& operator=(const ClassName&& param)
{
//process code
}
3.代码样例
Demo1:
cpp
#include <iostream>
using namespace std;
class Time
{
public:
int Hour;
int Minute;
int Second;
Time() {
std::cout << "调用了构造函数." << std::endl;
}
Time(const Time& tmpTime)
{
std::cout << "调用了拷贝构造函数." << std::endl;
}
Time(const Time&& tmpTime)
{
std::cout << "调用了移动构造函数." << std::endl;
}
Time& operator=(const Time& tmpTime)
{
std::cout << "调用了拷贝赋值运算符." << std::endl;
return *this;
}
Time& operator=(const Time&& tmpTime)
{
std::cout << "调用了移动赋值运算符." << std::endl;
return *this;
}
};
int main() {
Time myTime;
Time myTime1 = myTime;
Time myTime2;
myTime2 = myTime;
Time myTime3 = std::move(myTime1);
Time myTime4;
myTime4 = std::move(myTime2);
return 0;
}
运行结果:
bash
调用了构造函数.
调用了拷贝构造函数.
调用了构造函数.
调用了拷贝赋值运算符.
调用了移动构造函数.
调用了构造函数.
调用了移动赋值运算符.
Demo2:
cpp
#include <iostream>
#include <vector>
using namespace std;
// Move Class
class Move {
private:
int* data;
public:
Move(int d)
{
data = new int;
*data = d;
cout << "Constructor is called for "
<< d << endl;
};
// Move Constructor
Move(Move&& source)
: data{ source.data }
{
cout << "Move Constructor for "
<< *source.data << endl;
source.data = nullptr;
}
// Destructor
~Move()
{
if (data != nullptr)
cout << "Destructor is called for "
<< *data << endl;
else
cout << "Destructor is called"
<< " for nullptr "
<< endl;
delete data;
}
};
int main()
{
// Vector of Move Class
vector<Move> vec;
// Inserting Object of Move Class
vec.push_back(Move{ 10 });
vec.push_back(Move{ 20 });
return 0;
}
运行结果:
bash
Constructor is called for 10
Move Constructor for 10
Destructor is called for nullptr
Constructor is called for 20
Move Constructor for 20
Move Constructor for 10
Destructor is called for nullptr
Destructor is called for nullptr
Destructor is called for 10
Destructor is called for 20
七,委托构造函数
1.概念介绍
类的构造函数可以在初始化列表的位置调用该类的另一个构造函数,这个构造函数就叫委托构造函数,因为它把构造对象的工作委托给了另一个构造函数。
委托构造函数有助于精简函数代码。
委托构造函数对其他构造函数的调用的相关代码,不能放在委托构造函数的函数体内,必须放在构造函数的初始化列表中。
不能在委托构造函数的初始化列表中初始化成员变量,会导致代码编译失败。
可以在委托构造函数的函数体中设置成员变量的值。
2.委托构造函数的代码样式
cpp
ClassName(param_list1, ...): ClassName(param_list2, ...)
{
//process code
}
3.代码样例
Demo1:
cpp
#include <iostream>
using namespace std;
class Time
{
public:
int Hour;
int Minute;
int Second;
Time(int tmpHour) {
Hour = tmpHour;
std::cout << "调用了构造函数." << std::endl;
}
Time(int tmpHour2, int tmpMinute) : Time(tmpHour2)
{
Minute = tmpMinute;
std::cout << "调用了委托构造函数." << std::endl;
}
};
int main() {
Time myTime(11, 30);
return 0;
}
运行结果:
bash
调用了构造函数.
调用了委托构造函数.
Demo2:
cpp
#include <iostream>
#include <string>
#include <string_view>
class Employee
{
private:
std::string m_name{};
int m_id{ 0 };
void printCreated() const
{
std::cout << "Employee " << m_name << " created\n";
}
public:
Employee(std::string name)
: m_name{ name }
{
std::cout << "In Constructor." << std::endl;
printCreated();
}
Employee(std::string name, int id)
: Employee(name)
{
std::cout << "In Delegating Constructor." << std::endl;
m_id = id;
printCreated();
}
};
int main()
{
Employee e1{ "James" };
Employee e2{ "Dave", 42 };
}
运行结果:
bash
In Constructor.
Employee James created
In Constructor.
Employee Dave created
In Delegating Constructor.
Employee Dave created
补充:
构造函数名=default:让编译器生成默认的某构造函数。
构造函数名=delete:让编译器禁止调用某构造函数。
八,参考阅读
《C++新经典》
《C++ Primer》
《C++ Primer Plus》
C++ Constructors: Types and Copy Constructors (programiz.com)
Move Constructors in C++ with Examples - GeeksforGeeks