讨论构造拷贝构造函数的N种调用情况

来源:互联网 发布:数学题在线解答软件 编辑:程序博客网 时间:2024/06/05 05:14

讨论构造拷贝构造函数的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
学编程的也可以有文艺情怀好嘛~

原创粉丝点击