有三种不同的情况会由编译器自动调用‘拷贝’构造函数。分别是:
①使用括号或显式方法,直接调用拷贝构造函数。
使用一个已经创建完毕的对象来初始化一个新对象。
意为当有一个已经存在的对象时,再使用括号法或显式法,将类作为初始化参数传递给另一个对象中,会调用拷贝构造函数。
在实验之前,先做准备工作。
头文件Person.h
#pragma once
#include
#include
using namespace std;
class Person {
string person_name = "NULL";
int person_age = 0;
public:
//默认无参构造函数调用
Person();
//有参构造函数调用
Person(string name, int age);
Person( int age,string name);
Person(string name);
Person(int age);
//拷贝构造函数调用
Person(const Person& person);
//析构函数
~Person();
void set_name(string name);
string get_name();
void set_age(int age);
int get_age();
};
实现文件Person.cpp
#include
#include
using namespace std;
#include"Person.h"
//默认无参构造函数调用
Person::Person()
{
cout << "==========================" << endl;
cout << "默认无参构造函数,已调用。" << endl;
cout << "==========================" << endl;
}
//有参构造函数调用
Person::Person(string name, int age) {
person_name = name;
person_age = age;
cout << "=============================" << endl;
cout << "有参name+age构造函数,已调用。" << endl;
cout << "=============================" << endl;
}
Person::Person(int age,string name) {
person_name = name;
person_age = age;
cout << "=============================" << endl;
cout << "有参age+name构造函数,已调用。" << endl;
cout << "=============================" << endl;
}
Person::Person(string name) {
person_name = name;
cout << "==========================" << endl;
cout << "有参name构造函数,已调用。" << endl;
cout << "==========================" << endl;
}
Person::Person(int age) {
person_age = age;
cout << "==========================" << endl;
cout << "有参name构造函数,已调用。" << endl;
cout << "==========================" << endl;
}
//拷贝构造函数调用
Person::Person(const Person& p_copy) {
person_name = p_copy.person_name;
person_age = p_copy.person_age;
//为什么加了const之后类的私有权限下的成员属性可以取出来?
//参考链接:https://blog.csdn.net/weixin_40539125/article/details/84205194
//因为是在类内,甭管这个p_copy是什么对象,但是此时此刻就是在这个Person类内
cout << "======================" << endl;
cout << "拷贝构造函数,已调用。" << endl;
cout << "======================" << endl;
}
Person::~Person() {
cout << "=============" << endl;
cout << "析构函数调用." << endl;
cout << "=============" << endl;
}
void Person::set_name(string name) {
person_name = name;
}
string Person::get_name() {
return person_name;
}
void Person::set_age(int age) {
person_age = age;
}
int Person::get_age() {
return person_age;
}
主函数所在文件Section2.cpp
#include
#include
using namespace std;
#include"Person.h"
void test_v0() {
string name = "张三";
int age = 23;
//括号法调用有参构造函数
Person p_v0(name, age);
//括号法调用拷贝构造函数
Person p_v0_0(p_v0);
cout << "输出p_v0_0的姓名为:" << p_v0_0.get_name() << ",年龄为:" << p_v0_0.get_age() << "." << endl;
}
int main() {
cout << "hello world !" << endl;
test_v0();
system("pause");
return 0;
}
运行结果为:
可以看到,通过括号法进行参数的创建时,调用了拷贝构造函数。
②作为方法的参数传递时,调用拷贝构造函数。
值传递的方式给函数参数传值。
在作为函数(等同于“方法”)的值传递时,编译器会将实参传入,但此时因为是值传递,所以在函数内部会复制一份实参作为函数内部的局部变量,此时编译器自动调用了拷贝构造函数。
代码实现:
#include
#include
using namespace std;
#include"Person.h"
void test_v1_0(Person person) {
//括号法调用拷贝构造函数
Person p_v1_0(person);
cout << "p_v1_0的地址为:" << &p_v1_0 << "." << endl;
}
void test_v1() {
cout << "================================" << endl;
cout << "类作为参数时,调用拷贝构造函数。" << endl;
Person p_v1;
p_v1.set_name("张三");
p_v1.set_age(24);
cout << "p_v1的地址为:\t" << &p_v1 << "." << endl;
test_v1_0(p_v1);
cout << "================================" << endl;
}
int main() {
cout << "hello world !" << endl;
test_v1();
system("pause");
return 0;
}
运行结果如下:
可以看到用值传递的方式给函数test_v1_0
参数传值,在调用函数test_v1
时,会把实参拷贝一份放入函数中。所以在打印完p_v1
的地址后,进行了两次拷贝构造函数的调用。同时,在函数test_v1_0
调用结束后,会销毁两个对象,故调用了两次析构函数。在函数test_v1
结束时,销毁了最开始的p_v1
对象。
③类作为返回值时,调用拷贝构造函数。
以值方式返回局部对象。
返回局部对象,说的是不返回局部变量,而由编译器新拷贝了一个对象,赋值给新的对象。
代码实现:
#include
#include
using namespace std;
#include"Person.h"
Person test_v2_0() {
Person p_v2;
cout << "p_v2的地址为:\t\t" << &p_v2 << "." << endl;
return p_v2;
}
void test_v2() {
//类作为返回值时,调用拷贝构造函数
Person p_return = test_v2_0();
cout << "p_return 的地址为:\t" << &p_return << "." << endl;
}
int main() {
cout << "hello world !" << endl;
test_v2();
system("pause");
return 0;
}
注意:g++编译器默认开启了返回值优化RVO,返回时只有一个地址。如下所示:
PS:
若关闭返回值优化,则需要在项目资源管理器中右键对应包含的cpp接口实现文件-Person.cpp
单击属性,打开属性配置,
禁用即可,同时注意配置(C):
那里,要切换不同的配置,把优化都给关掉。
但是,仍然没有改变最后的结果。
所以,现在先理解,若以对象作为函数的返回值,会将在函数内部定义的局部变量销毁,并由编译器自动调用构造函数新建一个对象,作为返回值。
接着将g++编译器后面加上选项
-fno-elide-constructors
禁用优化,否则没有拷贝过程!!(bilibili弹幕)
参考:https://blog.csdn.net/weixin_42929607/article/details/106248798
问题a:如果开发者不自己写拷贝构造函数,所有的成员属性能被拷贝吗?
猜想a:开发者不重写的话,所有成员属性可以被拷贝。
实验验证a:取出使用括号法,显式法,隐式法调用的拷贝构造函数的对象的成员属性。(成员属性有初始值)需要把Person.h
和Person.cpp
拷贝构造函数的声明与实现注释掉,然后使用括号法进行拷贝构造函数的调用。
#include
#include
using namespace std;
#include"Person.h"
void test_v0() {
string name = "李四";
int age = 25;
//括号法调用有参构造函数
Person p_v0(name, age);
//括号法调用拷贝构造函数
Person p_v0_0(p_v0);
cout << "输出p_v0_0的姓名为:" << p_v0_0.get_name() << ",年龄为:" << p_v0_0.get_age() << "." << endl;
}
int main() {
cout << "hello world !" << endl;
test_v0();
system("pause");
return 0;
}
结果a:
所以,它是有默认的拷贝构造函数的,并且能够复制值。那么,其他的构造函数是否存在呢?比如有参。同理,将有参构造函数的声明与实现注释掉,查看结果。
发现会编译不通过,直接报错。
问题b:如果作为引用传递,编译器会调用哪种构造函数?同时,在函数中设置作为引用传入的实参对象中的成员属性会更改值吗?
猜想b:作为引入参数传入时,不会调用构造函数,因为是指针常量—类引用—传递到函数中,只有在新建对象时,才会调用拷贝构造函数。所以,析构函数语句显示应该有2条,默认无参构造函数显示一条,拷贝构造函数显示一条。在局部函数中,作为引用传入的实参对象中的成员属性会改变。
实验验证猜想:上代码:
#include
#include
using namespace std;
#include"Person.h"
void test_v1_0(Person &person) {
//括号法调用拷贝构造函数
Person p_v1_0(person);
cout << "p_v1_0的地址为:" << &p_v1_0 << "." << endl;
string v1_0_name = "王五";
int v1_0_age = 100;
person.set_name(v1_0_name);
person.set_age(v1_0_age);
cout << "此时p_v1_0的姓名为:\t" << p_v1_0.get_name() << ",年龄为:" << p_v1_0.get_age() << "." << endl;
}
void test_v1() {
cout << "================================" << endl;
cout << "类作为参数时,调用拷贝构造函数。" << endl;
Person p_v1;
p_v1.set_name("张三");
p_v1.set_age(24);
cout << "p_v1的地址为:\t" << &p_v1 << "." << endl;
cout << "此时p_v1的姓名为:\t" << p_v1.get_name() << ",年龄为:" << p_v1.get_age() << "." << endl;
test_v1_0(p_v1);
cout << "运行完test_v1_0传引入参数后..." << endl;
cout << "p_v1的地址为:\t" << &p_v1 << "." << endl;
cout << "此时p_v1的姓名为:\t" << p_v1.get_name() << ",年龄为:" << p_v1.get_age() << "." << endl;
cout << "================================" << endl;
}
int main() {
cout << "hello world !" << endl;
test_v1();
system("pause");
return 0;
}
结果:
可以看到,问题b的猜想已验证正确。
问题c:成员属性是private
权限,为什么拷贝构造函数开发者重写时可以直接使用“作为参数”的类对象成员属性?
回答:因为这样也算是在类中,只不过对象名不同。
参考链接:https://blog.csdn.net/weixin_40539125/article/details/84205194