拷贝构造函数和赋值构造

为什么空类可以创建对象呢?

复制构造函数的参数可以是 const 引用,也可以是非 const 引用。 一般使用前者,这样既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。一个类中写两个复制构造函数,一个的参数是 const 引用,另一个的参数是非 const 引用,也是可以的。

造函数的最常见形式如下:

classname (const classname &obj) { // 构造函数的主体 }

示例代码如下:

#include <iostream>
using namespace std;

class Empty
{
};

void main()
{
    Empty obj1;
    cout << sizeof(Empty) << endl;  // 1
}

让我们先看看这个例子。既然都没有构造函数,怎么实现对象obj1的构建呢?

哦,经过大脑的回旋式搜索,忆得有一本书上说过,当用户定义一个空类(如上)时,编译器就会为这个类默认生成六个方法。

既然是编译器默认完成的工作,那我们只要知道具体是那些方法,其余就学习编译器的原理了。

那么,编译器生成了那六个方法:示例代码如下:

class Empty
{
public:
    Empty();                      // 默认构造方法
    Empty(const Empty &);         // 拷贝构造函数
    ~Empty();                     // 析构函数
    Empty &operator=(const Empty &);   // 赋值构造函数
    Empty *operator &();               // 取地址
    const Empty * operator&() const;   // 常对象取地址
};

OK,这就是默认生成的那六个方法。其中前四个是经常谈及到的,也是平常定义一个类时,尤其要慎重考虑的。

到这里,也许有人还看出了点问题。那就是为什么空类的大小是1呢??呵呵~~编译器搞的鬼?其实也不是随随便便搞的鬼,我们说:存在即是合理的!这样做肯定有它的道理。

大家试想一个极端问题,仍然是上面这个空类,我们定义一个语句:Empty ar[10]; // 一个包含10个Empty对象的数组。

好啦,如果sizeof(Empty) == 0,那么我们如何区分数组中的十个元素呢??你想想这是不是狭隘的表现吗?

所以说,为了更完善,更健壮,更伟大。编译器插入这个字节,是为了在使用这个类定义多个对象的时候能保证每个对象有自己的地址。

另外,大家看看这个例子:

Empty *pa1, *pa2;
    pa1 = new Empty();
    pa2 = new Empty();
    // ..
    if (pa1 == pa2)    // 如果不分配内存,这个比较就会失去意义
    {
    }

如果不分配内存,如上的代码会不成立!

通常大家会对拷贝构造函数和赋值函数混淆,这儿仔细比较两者的区别:

1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。

class  A;  
A a;  
A b=a;   //调用拷贝构造函数(b不存在)  
A c(a) ;   //调用拷贝构造函数  
  
/****/  
  
class  A;  
A a;  
A b;     
b = a ;   //调用赋值函数(b存在)

何时调用

拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符

调用拷贝构造函数主要有以下场景:

  • 对象作为函数的参数,以值传递的方式传给函数。 
  • 对象作为函数的返回值,以值的方式从函数返回
  • 使用一个对象给另一个对象初始化

代码如下:

class Person
{
public:
	Person(){}
	Person(const Person& p)
	{
		cout << "Copy Constructor" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Assign" << endl;
		return *this;
	}

private:
	int age;
	string name;
};

void f(Person p)
{
	return;
}

Person f1()
{
	Person p;
	return p;
}

int main()
{
	Person p;
	Person p1 = p;    // 1
	Person p2;
	p2 = p;           // 2
	f(p2);            // 3

	p2 = f1();        // 4

	Person p3 = f1(); // 5

	getchar();
	return 0;
}

上面代码中定义了一个类Person,显式的定义了拷贝构造函数和赋值运算符。然后定义了两个函数:f,以值的方式参传入Person对象;f1,以值的方式返回Person对象。在main中模拟了5中场景,测试调用的是拷贝构造函数还是赋值运算符。执行结果如下:

分析如下:

  1. 这是虽然使用了"=",但是实际上使用对象p来创建一个新的对象p1。也就是产生了新的对象,所以调用的是拷贝构造函数。
  2. 首先声明一个对象p2,然后使用赋值运算符"=",将p的值复制给p2,显然是调用赋值运算符,为一个已经存在的对象赋值 。
  3. 以值传递的方式将对象p2传入函数f内,调用拷贝构造函数构建一个函数f可用的实参。
  4. 这条语句拷贝构造函数和赋值运算符都调用了。函数f1以值的方式返回一个Person对象,在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值;返回后调用赋值运算符将临时对象tmp赋值给p2.
  5. 按照4的解释,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象p3,也就是会调用两次拷贝构造函数。不过,编译器也没有那么傻,应该是直接调用拷贝构造函数使用返回值创建了对象p3。

举个例子:

#include <iostream>
using namespace std;

class demo {
   public:
    demo() : num(new int(0)) { cout << "construct!" << endl; }
    //拷贝构造函数
    demo(const demo &d) : num(new int(*d.num)) {
        cout << "copy construct!" << endl;
    }
    ~demo() { cout << "class destruct!:0x%x" << this<< endl; }

   private:
    int *num;
};


demo get_demo() { return demo();}//demo b;return b; }

int main() {
demo a = get_demo();
return 0;
}

construct!
copy construct!   //return b;调用 
class destruct!:0x%x0x7fff87509860  //b的析构
copy construct!   //a=X构造
class destruct!:0x%x0x7fff875098a0  //临时变量析构
class destruct!:0x%x0x7fff87509898  //a的析构

详细参考此文:https://www.cnblogs.com/alantu2018/p/8459250.html

https://www.cnblogs.com/liushui-sky/p/7728902.html

https://www.cnblogs.com/wangguchangqing/p/6141743.html

https://blog.csdn.net/CSDN_LSD/article/details/78024629

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值