草庐IT

C++类的构造函数、析构函数、拷贝构造函数、赋值函数和移动构造函数详细总结

swansfight 2023-03-28 原文

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

运行结果:

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


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

void f_2(Person& p) {}

测试代码:

Person p_2;
f_2(p_2);

运行结果:

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


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

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

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

Person p_3 = f_3();

运行结果:

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

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


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

Person p_3;  
p_3 = f_3();

运行结果:

结果分析:
第一次构造函数:声明 [对象p_3] 时调用;
第二次构造函数:函数体中声明 [对象p] 时调用;
第三次拷贝构造:将函数体中声明的 [对象p] 的数据拷贝给一个 [临时对象] 时调用(如果把类中 移动构造函数的注释去掉 ,那么是不会调用拷贝构造函数的,而是调用移动构造函数);
第四次析构函数:函数体中声明的 [对象p] 被析构时调用;
第五次赋值函数:是将 [临时变量] 的数据复制给 [对象p_3] 时调用;
第六次析构函数:[临时变量] 完成拷贝构造之后,调用析构函数,释放所占内存。

为什么不直接调用拷贝构造就完事了呢?
 在 【4.3.1 测试代码-1】中可知,用函数返回的对象来初始化新对象时只需要调用一次拷贝构造或移动构造就行,是不存在复制函数被调用的情况,那为什么4.3.2 测试代码-2就需要调用?

原因剖析:
 原因一:执行Person p_3导致创建了 [对象p_3] ,而函数返回的也是一个被创建的对象(不是匿名函数),在两个已存在对象之间使用=是赋值操作,是不会调用拷贝构造或移动构造的。
 原因二:由于原因一,导致函数返回的 [对象p] 没用被使用,函数返回的 [对象p] 会被析构函数释放其所占内存。
 因为原因二会导致函数返回的 [对象p] 会被析构,注意此时都还没有赋值给 [对象p_3] 咧,所以 [对象p] 在析构之前需要把 [对象p] 的数据拷贝给一个 [临时对象] (调用拷贝构造),完成拷贝之后 [对象p] 就被析构。最后把 [临时对象] 的数据赋值给已存的 [对象p_3] 即可 (调用赋值函数),完成赋值之后 [临时对象] 就被析构。
 至此上面的所有步骤分析完成。


4.4 测试 f_4 函数(函数返回值测试 -- 匿名对象)

Person f_4() { return Person(); }

测试代码:

Person p_4 = f_4();

运行结果:

结果分析:
声明 [对象p_4] 的同时直接使用 [匿名对象] 去初始化,此时 [匿名对象] 会直接转化成 [有名对象p_4] 匿名对象使用情况2),所以就这个情况而已不会调用拷贝构造函数和移动构造函数。


如果是如下分开情况(先声明,再用匿名对象赋值):

Person p_4;
p_4 = f_4();

运行结果:

结果分析:
第一个默认构造:声明 [对象p_4] 时调用;
第二个默认构造:函数体中的 [匿名对象] 被创建时调用的;
第三个赋值函数:[匿名对象] 赋值给已存在的 [对象p_4] 时调用(匿名对象使用情况3,用匿名对象来赋值给已存在对象时,此时会发生赋值构造,是会调用赋值函数的,同样执行完该行之后匿名对象所占内存被释放);
第四个析构函数:完成赋值之后,[匿名对象] 所占内存被释放时调用。


5. 完整测试代码

点击展开完整测试代码
#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::string;

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

int main()
{
	//Person();
	//Person p = Person();

	cout << "------测试f_1函数------\n";
	Person p_1;
	f_1(p_1);
	cout << "-----------------------\n\n";

	cout << "------测试f_2函数------\n";
	Person p_2;
	f_2(p_2);
	cout << "----------------------\n\n";

	cout << "------测试f_3函数------\n";
	Person p_3;
	p_3 = f_3();

	//Person p_3 = f_3();
	cout << "----------------------\n\n";

	cout << "------测试f_4函数------\n";
	Person p_4 = f_4();
	cout << "----------------------\n\n";

	return 0;
}

有关C++类的构造函数、析构函数、拷贝构造函数、赋值函数和移动构造函数详细总结的更多相关文章

  1. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  2. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  3. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  4. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  5. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

    当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

  6. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  7. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  8. 没有类的 Ruby 方法? - 2

    大家好!我想知道Ruby中未使用语法ClassName.method_name调用的方法是如何工作的。我头脑中的一些是puts、print、gets、chomp。可以在不使用点运算符的情况下调用这些方法。为什么是这样?他们来自哪里?我怎样才能看到这些方法的完整列表? 最佳答案 Kernel中的所有方法都可用于Object类的所有对象或从Object派生的任何类。您可以使用Kernel.instance_methods列出它们。 关于没有类的Ruby方法?,我们在StackOverflow

  9. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

  10. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

随机推荐