目录
写在前面:本篇博客所提到的拷贝构造函数等同于赋值构造函数及复制构造函数。
拷贝(copy)构造函数的调用时机有四种应用场景,拷贝(copy)构造函数主要完成的是两个对象之间的赋值,用一个对象去初始化另外一个对象。
先延续前篇所编写的类程序:
class Test4
{
public:
Test4() //无参数构造函数
{
m_a = 0;
m_b = 0;
cout<<"无参数构造函数"<<endl;
}
Test4(int a)
{
m_a = a;
m_b = 0;
}
Test4(int a, int b) //有参数构造函数 //3种方法
{
m_a = a;
m_b = b;
cout<<"有参数构造函数"<<endl;
}
//拷贝构造函数 (copy构造函数) //
Test4(const Test4& obj )
{
cout<<"我也是构造函数 " <<endl;
}
public:
void printT()
{
cout<<"普通成员函数"<<endl;
cout<<"m_a"<<m_a<<" m_a"<<m_a<<endl;
}
private:
int m_a;
int m_b;
};
第1个调用时机
//1 复制构造函数 用1个对象去初始化另外一个对象
void main41()
{
Test4 t1(1, 2);
Test4 t0(1, 2);
//赋值=操作 会不会调用构造函数
//operator=()//抛砖
t0 = t1; //用t1给t0赋值 注意:赋值操作和初始化是两个不同的概念
//赋值(copy)构造函数的第1种调用方法
Test4 t2 = t1; //用t1来初始化 t2
t2.printT();
cout<<"hello..."<<endl;
system("pause");
return ;
}
在上面的代码中,并没有给t2进行赋值,所以,t2打印出来的应该是垃圾值。
所以,为了不是垃圾值,应该在 拷贝(copy)构造函数中,进行赋值:
//赋值构造函数 (copy构造函数) //
Test4(const Test4& obj )
{
cout<<"我也是构造函数 " <<endl;
m_b = obj.m_b + 100;
m_a = obj.m_a + 100;
}
添加两行语句,对m_a,m_b进行赋值,可以自己做一些个性化的操作。完成了两个模块的解耦和再输出的时候就都变成了100。
第2个调用时机
//第二种调用时机
void main44()
{
Test4 t1(1, 2);
Test4 t0(1, 2);
Test4 t2(t1); //用t1对象 初始化 t2对象
t2.printT();
cout<<"hello..."<<endl;
system("pause");
return ;
}
通过括号法,直接进行赋值构造。只是形式不同而已,其他和第一种一模一样。
注:赋值操作和初始化操作是不一样的!虽然都是通过一个"="号来实现的,但是,初始化操作,会调用复制构造函数,而赋值并不会调用赋值构造函数!
第3个调用时机
对象作函数参数,先举例子:
class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
Location( const Location & p ) //复制构造函数
{
X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;
其中,拷贝构造函数用一个对象去初始化另一个对象。直接跳到应用场景去查看:
void playobj()
{
Location a(1, 2);
Location b = a;
cout<<"b对象已经初始化完毕"<<endl;
f(b); //b实参取初始化形参p,会调用copy构造函数
}
当执行到第二行代码的时候,会调用拷贝构造函数。
再看:
//业务函数 形参是一个元素
void f(Location p)
{
cout<<p.GetX()<<endl;
}
void playobj()
{
Location a(1, 2);
Location b = a;
cout<<"b对象已经初始化完毕"<<endl;
f(b); //b实参取初始化形参p,会调用copy构造函数
}
void main51()
{
playobj();
cout<<"hello..."<<endl;
system("pause");
return ;
}
整个程序的运行过程如下:
主函数开始运行->进入到"playobj()"函数中->运行"Location a(1, 2);",调用有参数构造函数,初始化对象,->"Location b = a;",调用拷贝构造函数,初始化对象,这里的对象和上一次初始的对象不是一个。->运行到"f(b);"处,进入该函数->因"void f(Location p)"的形参,再次调用拷贝构造函数,初始化对象p,同样,新初始化的对象和之前的都不一样。->"f(b)"函数运行完毕,对象p进行析构 ->"playobj()"运行完毕时,之前拷贝构造的函数进行析构->再是之前有参数构造函数初始化的对象析构->返回主函数运行,直至运行完毕。
其中,当运行到f(b)中,再执行拷贝构造函数的时机,就是本节所讲的拷贝构造函数的第3个调用时机。
第4个调用时机
函数的返回值是一个对象(元素)
假设如下场景里面,返回了一个元素:
Location g()
{
Location A(1, 2);
return A;
}
但是,所返回的这个元素并没有名字,所以返回出的是一个匿名对象。 要知道,先创建一个对象A,在函数结束的时候,用return将其进行返回,等同于在最后,又创建了一个跟A一样的对象,这个对象没名字,称之为匿名对象。但同样也是通过拷贝构造函数来实现的。
那关键就看设计程序的时候怎么去接这个返回出的匿名对象。那么:
//对象初始化操作 和 =等号操作 是两个不同的概念
//匿名对象的去和留,关键看,返回时如何接
void mainobjplay()
{
//若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
Location B;
B = g(); //用匿名对象 赋值 给B对象,然后匿名对象析构
}
如果是上面这种情况,是非常慢的,需要匿名对象先赋值,然后,本身再进行析构。
void mainobjplay()
{
//若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象
Location B = g();
cout<<"传智扫地僧测试"<<endl;
}
如果是这种情况的话,就快的多了,直接将创建的匿名对象转成有名对象,不会被析构掉,直接实现了新的初始化。这样做从内存角度看,提前牺牲了内存,加快了编译器的处理速度。
再看一个程序:
Location g()
{
Location A(1, 2);
return A;
}
//
void objplay2()
{
g();
}
void main()
{
objplay2();
cout<<"hello..."<<endl;
system("pause");
return ;
}
这个程序看起来很简单,但中间的运行过程非常经典,下面来捋一遍:
正常的进入主函->进入"objplay2()"函数->进入"g()"函数->运行"Location A(1, 2);"运行有参数构造函数,构建对象A->"return A;",启动拷贝构造函数,根据对象A创建匿名对象,同时,将对象A进行析构->运行到"objplay2()"函数中,由于“g()”没有参数进行接收,匿名对象也进行析构,->最后返回主函,程序运行结束。
void objplay3()
{
//用匿名对象初始化m 此时c++编译器 直接把匿名对转成m;(扶正) 从匿名转成有名字了m
Location m = g();
printf("匿名对象,被扶正,不会析构掉\n");
cout<<m.GetX()<<endl;;
}
void objplay4()
{
//用匿名对象 赋值给 m2后, 匿名对象被析构
Location m2(1, 2);
m2 = g();
printf("因为用匿名对象=给m2, 匿名对象,被析构\n");
cout<<m2.GetX()<<endl;;
}
void main()
{
//objplay2();
objplay3();
objplay4();
cout<<"hello..."<<endl;
system("pause");
return ;
}
"objplay3()"是直接把匿名对象给转换成对象m了;
"objplay4()"是中间先创建一个m2,然后,再将"g()"去给m2赋值,这个时候,匿名对象会被析构。是非常慢的!
一定要搞明白,匿名对象是怎么来的,又是怎么没的!
结论:
1: 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数)。
2: 有关匿名对象的去和留
如果用匿名对象初始化 另外一个同类型的对象,匿名对象转成有名对象
如果用匿名对象 赋值给 另外一个同类型的对象,匿名对象被析构
总体代码
dm04_copy构造函数的调用12.cpp
#include <iostream>
using namespace std;
class Test4
{
public:
Test4() //无参数构造函数
{
m_a = 0;
m_b = 0;
cout<<"无参数构造函数"<<endl;
}
Test4(int a)
{
m_a = a;
m_b = 0;
}
Test4(int a, int b) //有参数构造函数 //3种方法
{
m_a = a;
m_b = b;
cout<<"有参数构造函数"<<endl;
}
//赋值构造函数 (copy构造函数) //
Test4(const Test4& obj )
{
cout<<"我也是构造函数 " <<endl;
m_b = obj.m_b + 100;
m_a = obj.m_a + 100;
}
public:
void printT()
{
cout<<"普通成员函数"<<endl;
cout<<"m_a"<<m_a<<" m_a"<<m_b<<endl;
}
private:
int m_a;
int m_b;
};
//1 赋值构造函数 用1个对象去初始化另外一个对象
void main41()
{
Test4 t1(1, 2);
Test4 t0(1, 2);
//赋值=操作 会不会调用构造函数
//operator=()//抛砖
t0 = t1; //用t1 给 t0赋值 到操作 和 初始化是两个不同的概念
//第1种调用方法
Test4 t2 = t1; //用t1来初始化 t2
t2.printT();
cout<<"hello..."<<endl;
system("pause");
return ;
}
//第二种调用时机
void main44()
{
Test4 t1(1, 2);
Test4 t0(1, 2);
Test4 t2(t1); //用t1对象 初始化 t2对象
t2.printT();
cout<<"hello..."<<endl;
system("pause");
return ;
}
dm05_copy构造函数的调用场景3.cpp
#include <iostream>
using namespace std;
class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
//copy构造函数 完成对象的初始化
Location(const Location & obj) //copy构造函数
{
X = obj.X; Y = obj.Y;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;
//业务函数 形参是一个元素
void f(Location p)
{
cout<<p.GetX()<<endl;
}
void playobj()
{
Location a(1, 2);
Location b = a;
cout<<"b对象已经初始化完毕"<<endl;
f(b); //b实参取初始化形参p,会调用copy构造函数
}
void main51()
{
playobj();
cout<<"hello..."<<endl;
system("pause");
return ;
}
dm06_copy构造函数的第4种应用场景.cpp
#include <iostream>
using namespace std;
class Location
{
public:
Location( int xx = 0 , int yy = 0 )
{
X = xx ; Y = yy ; cout << "Constructor Object.\n" ;
}
//copy构造函数 完成对象的初始化
Location(const Location & obj) //copy构造函数
{
X = obj.X; Y = obj.Y;
}
~Location()
{
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX () { return X ; } int GetY () { return Y ; }
private : int X , Y ;
} ;
//g函数 返回一个元素
//结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数)
//
//结论2: 有关 匿名对象的去和留
//如果用匿名对象 初始化 另外一个同类型的对象, 匿名对象 转成有名对象
//如果用匿名对象 赋值给 另外一个同类型的对象, 匿名对象 被析构
//
//你这么写代码,设计编译器的大牛们:
//我就给你返回一个新对象(没有名字 匿名对象)
Location g()
{
Location A(1, 2);
return A;
}
//
void objplay2()
{
g();
}
//
void objplay3()
{
//用匿名对象初始化m 此时c++编译器 直接把匿名对转成m;(扶正) 从匿名转成有名字了m
Location m = g();
printf("匿名对象,被扶正,不会析构掉\n");
cout<<m.GetX()<<endl;;
}
void objplay4()
{
//用匿名对象 赋值给 m2后, 匿名对象被析构
Location m2(1, 2);
m2 = g();
printf("因为用匿名对象=给m2, 匿名对象,被析构\n");
cout<<m2.GetX()<<endl;;
}
void main()
{
//objplay2();
//objplay3();
objplay4();
cout<<"hello..."<<endl;
system("pause");
return ;
}