设为首页 加入收藏

TOP

C++类的构造函数、析构函数、拷贝构造函数、赋值函数和移动构造函数详细总结(一)
2023-07-23 13:36:11 】 浏览:67
Tags:贝构造

1. 五种函数介绍

构造函数:负责对象的初始化工作,构造函数可以重载,但不可以在构造函数前加virtual

析构函数:负责在撤销对象前,完成清理工作(释放内存),析构函数不可以重载,一个类中有且只有一个析构函数

拷贝构造函数:一种特殊的构造函数,用同类的对象去构造和初始化另一个对象。函数名和类名一致,只有一个参数,这个参数是一个被const修饰的本类型引用变量

赋值构造函数:当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数,就是重载了=操作符,去完成对应的对象赋值操作(这里涉及深浅拷贝问题)

移动构造函数:使用一个右值来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。


2. 左值&右值怎么区分?怎么看?

判断方法: 可以取地址的变量就是左值,不可以的就是右值

类型 含义
泛左值 (glvalue) 一个表达式,其值可确定某个对象或函数的标识
纯右值 (prvalue) 符合下列之一的表达式:
① 计算某个运算符的操作数的值(这种纯右值没有结果对象)
② 初始化某个对象(称这种纯右值有一个结果对象)
亡值 (xvalue) 代表它的资源能够被重新使用的对象或位域的泛左值 (通过移动构造函数)
左值 (lvalue) 非亡值的泛左值
右值 (rvalue) 纯右值或亡值

注意:亡值就是将亡值,同一个概念。


3. 匿名对象的3种使用情况

情况1没有被对象接收,执行完所在行之后立刻调用析构函数,释放其所占内存。

情况2用来初始化对象,用匿名对象来初始化新对象时,可以这么理解:匿名对象被创建,但被用来初始化新对象,相当于匿名对象变成有名对象。因此只存在拷贝构造。

情况3用来赋值给对象,用匿名对象来赋值给已存在对象时,此时会发生赋值构造,是会调用赋值函数的,同样执行完该行之后匿名对象所占内存被释放。

针对三种情况举例测试:

点击展开[匿名对象]测试代码
class Person{
public:
	// 析构函数
	~Person() { std::cout << "destroy...\n";}
	// 构造函数
	Person() { std::cout << "default constructor...\n"; }	
	// 赋值函数
	Person& operator=(const Person& p) {
		std::cout << "assign function...\n";
		return *this;
	}
};
Person f() { return Person(); }
int main()
{
	// 情况一:匿名对象没有被对象接收
	Person();
	
	// 情况二:用建匿名对象来初始化对象
	Person p_2 = Person(); // 等价于 Person p_2 = f();

	// 情况三:用匿名对象来赋值给已存在对象
	Person p_3;
	p_3 = f();
	return 0;
}

4. 代码详细验证每个函数调用情况

代码详细简单,每一步都做过注释,很好理解。

点击展开[函数调用]测试代码
class Person
{
public:
	// 析构函数
	~Person() { std::cout << "destroy...\n";}

	// 默认构造函数
	Person() { std::cout << "default constructor...\n"; }

	// 拷贝构造函数
	Person(const Person& p) { std::cout << "copy constructor...\n"; }

	// 移动构造函数
	//Person(Person&& p) { std::cout << "move constructor...\n"; }

	// 赋值函数
	Person& operator=(const Person& p) {
	std::cout << "assign function...\n";
	return *this;
	}
};

// 1.调用拷贝构造函数
void f_1(Person p) {}

// 2.不会调用拷贝构造函数
void f_2(Person& p) {}

// 3.调用默认构造函数
Person f_3() {
	Person p;
	return p;//注意p它是一个将亡值,如果此时存在移动构造是会调用移动构造的
}

// 4.调用默认构造函数
Person f_4() { return Person(); }

4.1 测试 f_1 函数(函数形参测试 -- 值传递)

void f_1(Person p) {}

测试代码:

Person p_1;
f_1(p_1);

运行结果:
image

结果分析:
第一个默认构造:声明 [对象p_1] 时调用;
第二个拷贝构造:[对象p_1] 传值给 [形参对象p] 时调用;
第三个析构函数:函数结束,形参对象p 占用内存被释放。


4.2 测试 f_2 函数(函数形参测试 -- 引用传递)

void f_2(Person& p) {}

测试代码:

Person p_2;
f_2(p_2);

运行结果:
image

结果分析:
第一个默认构造:声明 [对象p_2] 时调用;
注意:因为函数形参是该类型的对象引用,所以不存在拷贝构造,函数执行完后也就不存在内存被释放。


4.3 测试 f_3 函数(函数返回值测试 -- 具名对象)

Person f_3() {
	Person p;
	return p;//注意p它是一个将亡值,如果此时存在移动构造是会调用移动构造的
}

4.3.1 测试代码-1(初始化新对象)

Person p_3 = f_3();

运行结果:
image

结果分析:
第一个默认构造:函数体中声明 [对象p] 时调用;
第二个拷贝构造:函数体中创建的 [对象p] 被返回,然后赋值给 [对象p_3] 时调用(注意:由于函数体中的对象不是 [匿名对象],无法直接转化为有名 [对象p_3],需要调用拷贝构造去将[对象p]的数据拷贝给[对象p_3]
第三个析构函数:是执行完拷贝构造之后,将在函数体中创建的 [对象p] 析构时调用。

重点: 如果此时类中存在 移动构造函数(把注释去掉),那么是不会调用拷贝构造函数的,而是调用 移动构造函数(避免大量数据的拷贝),结果如下:
image

4.3.2 测试代码-2(赋值给已存在对象)

Person p_3;  
p_3 = f_3();

运行结果:
image

结果分

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇C++——const和constexpr区别 下一篇驱动开发:配置Visual Studio驱动..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目