讨论构造拷贝构造函数的N种调用情况
拷贝构造函数
- 该函数可以用一个对象去构造另一个对象,或者说,用另一个对象值初始化一个新构造的对象
- 对象作为函数参数传递时,也要涉及对象的拷贝。
void fn(Student fs)
{
//...
}
int main()
{
Student ms;
fn(ms);
}
- 函数fn()的参数传递方式是传值,参数类型是Student,调用时,实参ms传给了形参fs。
- ms在传递的过程中是不会改变的,形参fs是ms的一个拷贝。
临时对象
- 当函数返回一个对象时,要创建一个临时对象以存放返回的对象。
- 在下面的代码中,返回的ms对象将产生一个临时对象
Student fn()
{
//...
Student ms("Evey");
return ms;
}
int main()
{
Student s;
s = fn();
//...
}
系统调用拷贝构造函数将ms拷贝到新创建的临时对象中
一般规定,创建的临时对象,在整个创建它们的外部表达式范围内有效,否则无效。
- 也就是说,”s = fn();”这个外部表达式,当fn()返回时产生的临时对象拷贝给s后,临时对象就被析构了。
下面的例子中,引用refs则不再有效:
int main()
{
Student& refs = fn();
//...
}
- 因为外部表达式”Student& refs = fn()”到分号处结束,以后从fn()返回的临时对象便不再有效,这就意味着,引用refs的实体已不存在,所以接下去的任何对refs的引用都是错的。
无名对象(匿名对象)
- 可以直接调用构造函数产生无名对象
- 在下面的代码中,函数fn()里,创建了一个无名对象
class Student
{
public:
Student(char*);
//...
void fn()
{
Student("Evey");//此处为无名对象
//...
}
}
- 无名对象可以作为实参传递给函数,可以拿来拷贝构造一个新对象,也可以初始化一个引用的声明。
- 下面列举无名对象典型的三种用法:
void fn(Student& s);
int main()
{
Student& refs = Student("Evey");//初始化引用
Student s = Student("JS");//初始化对象定义
fn(Student("Remai"));//函数参数
}
- 第一个执行的是拿无名对象初始化一个引用。
- 在函数内部,无名对象作为局部对象产生在栈空间中。
- 从作用域上看,该引用与无名对象是相同的。
- 其完全等价于,”Student refs = “Evey”“。
- 所以该做法,略显多余。
- 第二个执行的是用无名对象拷贝构造一个对象s。
- 按理说,C++先调用构造函数”Student(char* );”创建一个无名对象,然后再调用一个拷贝构造函数”Student(Student&);”创建对象s。
- 但由于用无名对象去拷贝构造一个对象,所以拷贝完成之后,无名对象就失去了任何作用。
- 对于这种情况,C++特别地将其看作为”Student s= “JS”“。
- 效果相同,且可以省略创建无名对象这一步。
- 第三个执行的是无名对象作为实参传递给形参s。
- C++先调用构造函数创建一个无名对象,然后将该无名对象初始化给了引用形参s对象。
- 由于实参是在主函数中,所以无名对象实在主函数的栈区中创建。
- 函数fn()的形参s引用的是主函数栈空间中的一个对象。
小结
- 在从C++中,传参和传返回值时,如果是引用类型,则不用调用拷贝构造函数,直接返回别名。
- 当语句为一个表达式时,编译器会选择优化,将构造函数与拷贝构造合并。
- 在C++中,调用几次构造函数和拷贝构造函数,就会相应的调用几次析构函数。
只有当一个对象已经存在时,d=f()才调用赋值运算符重载,如果对象不存在,调用的则是构造函数。
练习
嘻嘻,我们接下来看一下一道简单的关于拷贝构造函数的题目吧
题目:
Test1中调用了___次AA的拷贝构造函数,___次AA的赋值运算符函数的重载。
Test2中调用了___次AA的拷贝构造函数,___次AA的赋值运算符函数的重载。
Test3中调用了___次AA的拷贝构造函数,___次AA的赋值运算符函数的重载。
class AA
{};
AA f (AA a)
{
return a ;
}
void Test1 ()
{
AA a1 ;
a1 = f(a1);
}
void Test2 ()
{
AA a1 ;
AA a2 = f(a1);
}
void Test3 ()
{
AA a1 ;
AA a2 = f(f(a1));
}
当当当当~~公布答案啦!你做对了么?
Test1中调用了2次AA的拷贝构造函数,1次AA的赋值运算符函数的重载。
Test2中调用了2次AA的拷贝构造函数,0次AA的赋值运算符函数的重载。
Test3中调用了3次AA的拷贝构造函数,0次AA的赋值运算符函数的重载。
接下来我们来分析一下答案是如何得出的吧~
* 代码测试
#include<iostream>
using namespace std;
class AA
{
public:
AA()
{
cout << "构造函数" << endl;
}
AA(const AA& a)
{
cout << "拷贝构造函数" << endl;
}
~AA()
{
cout << "析构函数" << endl;
}
AA& operator=(AA& a)
{
cout << "重载运算符" << endl;
return a;
}
};
AA f(AA a)
{
return a;
}
- Test 1 的代码测试
void Test1()
{
AA a1;//构造
a1 = f(a1);//拷贝构造*2 & 运算符重载
}
运行结果如下:
- Test 2的代码测试
void Test2()
{
AA a1;//构造
AA a2 = f(a1);//拷贝构造*2
}
运行结果如下:
- Test 3 的代码测试
void Test3()
{
AA a1;//构造
AA a2 = f(f(a1));//拷贝构造*3
}
运行结果如下:
题目到这里就结束啦~
天公作美,许我半途而吠
所以,天真人类很喜欢的话,送给大家2333
学编程的也可以有文艺情怀好嘛~