复制构造函数&&赋值函数(何时调用,什么时候调用)

   平时看C++,对复制构造函数,赋值函数的概念是知道的,但一直不太清楚在调用函数(函数形参是类)、从函数返回一个类对象以及赋值时,这些函数的调用关系具体是怎么样的。今天就来个了断。主要针对复制构造函数及赋值函数,至于普通构造函数及析构函数是简单的。

测试1:

测试代码如下:

#include <iostream>
using namespace std;


class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)   //B(int) works as a converter ( int -> instance of B)
{
cout<<"constructed by parameter " << data <<endl;
}
B & operator=(const B &other)
{
if(this == &other)
return *this;
data = other.data;


return *this;
}


B(const B& other)
{
data = other.data;
}
private:
int data;
};
B Play( B b)
{
return b ;
}


int main(int argc, char* argv[])
{
//B t1 = Play(5); 
//B t2 = Play(t1); 
//B b (5); //construct 
//B a =b; //copy-construct
//B c; //normal-construct
//c=b; //operator =


B t1 = Play(5); 
B t2 = Play(t1);
//先复制构造将t1复制给Play中的b,然后Play函数返回时,再复制构造给t2.中间没有出现所谓的临时变量,至少从调试中观察变量data等的地址判断是这样
//B t3;
//t3 = Play(t1);
return 0;
}


测试方法:通过在类B的普通构造函数、赋值函数、复制构造函数及析构函数中添加断点,然后调试程序,用调试器中的观察窗口产看复制构造函数中的&other及&data,Play(B b)中的&b,还有opertator =()中的&other等,看这些变量之间的关系。

 下面是我的观察结果:

(1):main中的断点设置在B t2=Play(t1)处,然后单步调试进入观察。发现:&t1=0x0012ff54,&t2=0x0012ff40.

第一次调用B(const B& other)时,其中的&other=0x0012ff54,说明该other就是t1本身,&data=0x0012ff34。暂时看不出该data是哪个对象的,没关系,继续调试下去,进入Play(B b)中时发现&b = 0x0012ff34,说明第一次调用复制构造函数时,那个data是b对象中的数据成员,这说明什么?说明第一次调用复制构造函数其实就是将参数t1复制到形参b中,中间并没有临时对象来过渡。

(2):继续调试,当Play(B b)函数快返回时,再一次调用复制构造函数B(const B &other),其中的&other=0x0012ff34(和对象b的地址一样),&data=0x0012ff40.由于&t2=0x0012ff40,所以这里显然是将对象b复制给对象t2.接着调试,调用~B(),对象b析构,中间没有产生什么临时变量来过渡。后面的就简单了.... ... ...这两步是容易疑惑的地方。


测试2:

#include <iostream>
using namespace std;


class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)   //B(int) works as a converter ( int -> instance of B)
{
cout<<"constructed by parameter " << data <<endl;
}
B & operator=(const B &other)
{
if(this == &other)
return *this;
data = other.data;


return *this;
}


B(const B& other)
{
data = other.data;
}
private:
int data;
};
B Play( B b)
{
return b ;
}


int main(int argc, char* argv[])
{
//B t1 = Play(5); 
//B t2 = Play(t1); 
//B b (5); //copy-construct 
//B a =b; //copy-construct
//B c; //normal-construct
//c=b; //operator =


B t1 = Play(5); 
//B t2 = Play(t1);//先复制构造将t1复制给Play中的b,然后Play函数返回时,再复制构造给t2.中间没有出现所谓的临时变量,至少从调试中观察变量data等的地址判断是这样
B t3;

        t3 = Play(t1);

return 0;
}

测试方法:将断点设置在t3=Play(t1)处,其他同测试1:。

首先记录&t1=0x0012ff54,&t3=0x0012ff48。单步调试,第一次调用复制构造函数B(const B& other),其中&other=0x0012ff54(和t1的相同,说明是同一个内存实体),&data=0x0012ff34,当从该函数返回进入Play(B b)时发现&b=0x0012ff34,这说明第一次调用复制构造函数其实就是将t1复制给b。接着往下调试,当Play()快返回时第二次调用复制构造函数B(const B& other)时,其中的&other=0x0012ff34(和b的相同),&data=0x0012ff70(和t1、t2、和b的地址都不同),显然这个&data所在的对象就是编译器产生的临时对象,然后马上调用了~B(),析构b,b对象消失;接着往下走,后来就调用了operator=()函数,该函数中的&other=0x0012ff70(这个other就是之前产生的临时对象),operator=()函数中的&data=0x0012ff48(和t3的地址相同)。这就是将该临时对象赋值给对象t3,调用完operator =()之后马上调用了~B(),析构临时对象。这就是整个流程。如果对最后一句,产生怀疑,可以在析构函数中观察打印data的地址,观察是不是等于0x0012ff70,如果是就一定是析构该临时对象了,呵呵。只需要将B的析构函数改为:~B()
{
cout<<"destructed"<<endl;

cout<<data<<endl;

}

经调试观察,确实该析构函数中的data的地址为0x0012ff70。

总结:

1)复制初始化并不会产生临时对象,只有赋值时才会产生临时 对象。

2)调用参数是类对象类型的函数时,会首先调用复制构造函数将实参复制给形参,当该函数返回时,还会调用复制构造函数一次,此时将该形参(或者其他类对象)要么复制给函数调用处的其他类对象(如测试1中的t2),要么复制给临时对象(如测试2中的0x0012ff70),这取决于你用的是复制初始化还是赋值,如果是复制初始化(测试1),则直接复制给函数调用处的类对象,如果是先定义对象(测试2中的t3),然后再赋值,那么就是先复制给临时对象,然后再通过调用operator=()将该临时对象赋值给函数调用处的类对象(t3),然后临时对象被析构。

3)以类对象类型为参数的函数返回前会调用析构函数,析构该形参参数;临时对象在operator=()调用后马上就会被析构。


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值