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

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

阅读更多
个人分类: C++
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭