本文已参与「新人创作礼」活动,一起开启掘金创作之路。
游戏开发之拷贝构造函数,即复制构造函数(explicit关键字及拷贝构造函数的调用时机)(C++基础)
拷贝构造函数是一个特殊的构造函数,是用一个类对象赋值给另外一个类对象时调用。 拷贝构造函数如果没有重写,编译器会自动生成。而重写了拷贝构造函数,编译器不会在生成默认拷贝构造函数,默认构造函数编译器也不会自动生成,需要程序员自己手写。 拷贝构造函数一般用于深拷贝(如果类的内部使用指针则需要重写)。 语法:类名(const 类名& 变量名){函数体}
C++类的构造函数按类型分类:分为普通构造函数和拷贝构造函数(复制构造函数)
1.类的拷贝构造函数(复制构造函数)
class A
{
public:
//拷贝构造函数 是一个特殊的构造函数
/*拷贝构造函数如果没有重写,编译器会自动生成。
而重写了拷贝构造函数,编译器不会在生成默认拷贝构造函数,
默认构造函数编译器也不会自动生成,需要程序员自己手写。*/
//用一个类赋值给另外一个类时调用
//一般用于深拷贝
//如果类的内部使用指针则需要重写
//语法:类名(const 类名& 变量名){函数体}
int a;
A()
{
std::cout << "调用默认构造函数" << std::endl;
_age = 0;
}
//有参构造函数
A(int age) {
std::cout << "1个参数" << std::endl;
_age = age;
}
A(const A &a_)
{
std::cout << "调用拷贝构造函数" << std::endl;
a = a_.a;
_age = a_._age;
}
void Print() {
std::cout << "Age:" << _age << std::endl;
}
private:
int _age;
};
int main()
{
A a1;
a1.a = 200;
A a2(a1);
//很像赋值,语义不明确,不推荐使用
A a3 = a1;
return 0;
}
关于使用匿名对象调用拷贝构造函数:
class A
{
public:
int a;
A()
{
std::cout << "调用默认构造函数" << std::endl;
_age = 0;
}
//有参构造函数
A(int age) {
std::cout << "1个参数" << std::endl;
_age = age;
}
A(const A &a_)
{
std::cout << "调用拷贝构造函数" << std::endl;
a = a_.a;
_age = a_._age;
}
void Print() {
std::cout << "Age:" << _age << std::endl;
}
private:
int _age;
};
int main()
{
//匿名对象:没有名字的对象。
//匿名对象:显示调用构造函数
A();
//匿名对象调用a3实体的拷贝构造函数
A a3 = A(300);
a3.Print();
//匿名对象调用a4实体的拷贝构造函数
//注意:使用匿名对象初始化判断哪一个构造函数需要看匿名对象的参数类型
A a4(A(400));
a4.Print();
//此时A(a6) 等价于 A a6
A(a6);
a6.Print();
return 0;
}
b 为 A 的实例化对象, A a = A(b) 和 A(b)的区别? 当 A(b) 有实例化对象来接收的时候,那么编译器认为他是一个匿名对象;当没有变量来接的时候,编译器认为你A(b)就等价于A b。
TIPS:不能调用拷贝构造函数初始化匿名对象。 示例如下:
class B {
public:
B() {
std::cout << "默认构造函数!" << std::endl;
}
B(const B& teacher) {
std::cout << "拷贝构造函数!" << std::endl;
}
public:
int mAge;
};
int main()
{
B t1;
B(t1);//错误,不能用拷贝构造函数初始化一个匿名函数!!
return 0;
}
2.explicit关键字
explicit用于修饰构造函数,防止隐式转化。 主要是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。 限制在构造时使用:类对象实体 = 初始化,这种格式进行初始化。
class A
{
public:
explicit A(int a) { std::cout << "构造函数A" << std::endl; }
//explict 明确语义,避免二义性
A(const A &a_)
{
std::cout << "调用拷贝构造函数" << std::endl;
a = a_.a;
}
};
class B
{
public:
B(int a) { std::cout << "构造函数A" << std::endl; }
};
int main(int argc, char** argv)
{
A a1 = 1; //错误,不允许隐式转换
A a2(1); //正确
B b1 = 1; //正确
B b2(2); //正确
A a3;
a3.a = 200;
A a4(a3);
//很像赋值,语义不明确,不推荐使用
A a5 = a1;//错误。附加条件explicit,限制条件后,便不允许该种用法
return 0;
}
3.拷贝构造函数的调用时机
数据结构如下:
class A
{
public:
A()
{
std::cout << "默认构造函数!" << std::endl;
_nAge = 10;
}
A(int age)
{
std::cout << "有参构造函数!" << std::endl;
_nAge = age;
}
A(const A& person)
{
std::cout << "拷贝构造函数!" << std::endl;
_nAge = person._nAge;
}
~A()
{
std::cout << "析构函数!" << std::endl;
}
private:
int _nAge;
};
void test(A p) {}
A test2()
{
A p(10);
std::cout << "局部变量p:" << *(int*)&p << std::endl;
return p;
}
void test3()
{
A p = test2();
std::cout << "局部变量p:" << *(int *)&p << std::endl;
}
1.旧对象初始化新对象会调用拷贝构造函数
int main()
{
//1.旧对象初始化新对象
A p(10);
A p1(p);
A P2 = A(p);//相当于A p2(A(p));
A p3 = p;//相当于A p3(p);
return 0;
}
2.函数的形参是普通类对象,实参也是普通的类对象,调用函数会调用拷贝构造函数
int main()
{
//2.函数的形参是普通类对象,实参也是普通的类对象,调用函数会调用拷贝构造函数
A p4(10);
test(p4);
return 0;
}
3.函数返回局部类对象会调用拷贝构造函数
int main()
{
//3.函数返回局部类对象会调用拷贝构造函数
test2();
return 0;
}
4.函数调用返回局部类对象的函数并初始化另一个类对象
在vs2017的debug模式下,调用一次拷贝构造函数 在vs2017的release模式下,不调用拷贝构造函数
int main()
{
//4.函数调用返回局部类对象的函数并初始化另一个类对象
//在vs2017的debug模式下,调用一次拷贝构造函数
//在vs2017的release模式下,不调用拷贝构造函数
test3();
return 0;
}
TIPS:test3()说明编译器在编译期间会对拷贝构造函数的调用进行优化。 版本声明:本文为CSDN博主[ufgnix0802]的原创文章。
原文链接:(blog.csdn.net/qq135595696…)