C++ 类复制构造函数,浅/深拷贝,默认赋值运算符重载,友元类,内部类

目录

一、复制构造函数

1、定义

2、隐式调用场景

3、浅拷贝

4、深拷贝

二、赋值运算符重载函数

三、友元类

四、内部类和局部类

1、局部类定义

2、内部类定义

3、内部类和外部类的互相访问


一、复制构造函数

1、定义

    复制构造函数同默认构造函数一样是编译器在程序没有显示定义对应函数时自动添加的特殊成员函数,复制构造函数的入参是一个const或者非const的类引用,可以有其他入参,但是必须有默认值。

class ClassA{
private:
	int a=1;
	int b=2;
public:
	//构造函数
	ClassA(int i,int j);
	//析构函数
	~ClassA();
	//复制构造函数
//	ClassA(const ClassA & ca);
//	ClassA(ClassA & ca);
	ClassA(ClassA & ca,int i=2,int j=2);
};


ClassA::ClassA(int i,int j){
	a=i;
	b=j;
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

//ClassA::ClassA(const ClassA & ca){
//	std::cout<<"execute ClassA(const ClassA & ca),this->"<<this<<"\n";
//}

//ClassA::ClassA(ClassA & ca){
//	std::cout<<"execute ClassA(ClassA & ca),this->"<<this<<"\n";
//}

ClassA::ClassA(ClassA & ca,int i,int j){
	std::cout<<"execute ClassA(ClassA & ca),this->"<<this<<"\n";
}

ClassA::~ClassA(){
	std::cout<<"execute ~ClassA,this->"<< this<<"\n";
}

int main(){
	ClassA a(1,2);
    ClassA b=a;
    ClassA b2=a;
    ClassA b3=a;

	return 0;
}

执行结果如下,注意析构函数按对象创建的顺序倒序执行,即后创建先执行,主要是为了避免后面创建的对象对前面创建的对象有依赖的情形下删除前面创建的对象会产生不可预知的后果。

2、隐式调用场景

《C++ Primer Plus》中提到下列情形下编译器会隐式调用复制构造函数:

  1.  以某个对象实例初始化另一个同类型的对象
  2.  对象以实例而非引用或者指针的形式作为方法入参
  3.  以对象实例而非引用或者指针的形式作为方法的返回值
  4.  方法连续调用过程中产生临时对象

第四种实际上跟第三种是一样的,因为只有方法的返回值是对象实例而非引用或者指针的时候才会产生临时对象,大部分博客说的是前面三种情形。但是在当前支持C++11标准的GNU C比如版本 4.8.5 下,第三条在返回的对象实例是在方法中创建的情形下不成立,参考如下测试用例:

#include <iostream>

class ClassA{
private:
	int a=1;
	int b=2;
	static int c;
public:
	//构造函数
	ClassA(int i,int j);
	//析构函数
	~ClassA();
	//复制构造函数
	ClassA(const ClassA & ca);
	//运算符重载
	ClassA operator+(const ClassA & ca);
	//友元函数
	friend void print(ClassA cl);
};

int ClassA::c=0;

ClassA::ClassA(int i,int j){
	a=i;
	b=j;
	c++;
	std::cout<<"execute ClassA(int i,int j),this->"<< this<<"\n";
}

ClassA::~ClassA(){
	c--;
	std::cout<<"execute ~ClassA,this->"<< this<<"\n";
}

ClassA::ClassA(const ClassA & ca){
	a=ca.a;
	b=ca.b;
	c++;
	std::cout<<"execute ClassA(const ClassA & ca),this->"<<this<<"\n";
}

ClassA ClassA::operator +(const ClassA & ca){
	return ClassA(a+ca.a,b+ca.b);
}

void print(ClassA cl){
	std::cout <<"print a=" <<cl.a <<",b="<<cl.b <<",c="<< cl.c <<"\n";
}

ClassA getA(int a,int b){
	std::cout <<"getA a=" <<a <<",b="<<b <<"\n";
	return ClassA(a,b);
}

int main(){
	ClassA a(1,1.2);
	std::cout<<"a=" <<&a <<"\n";
	//情形1
    ClassA b=a;
    std::cout<<"b=" <<&b <<"\n";
    //情形2
    print(b);
    //情形3
    ClassA c=getA(2,3);
    std::cout<<"c=" <<&c<<"\n";
    //情形4
    ClassA d=a+b+c;
    std::cout<<"d=" <<&d <<"\n";

	return 0;
}

执行的结果如下:

      为什么第三种和第四种情形下调用了构造函数而没有调用复制构造函数?从反汇编找答案。之前在Java程序员自我修养——C与汇编语言探讨过结构体作为入参和返回值时编译器会通过寄存器传递或者直接内存拷贝的方式在调用函数与被调函数的栈帧之间复制结构体占用的内存,类在内存的表示跟结构体是一样的,那么处理方式是否一样了?

main方法中执行print(b)的汇编代码如下:

lea    -0x50(%rbp),%rdx
lea    -0x30(%rbp),%rax
mov    %rdx,%rsi
mov    %rax,%rdi
callq  0x4009dc <ClassA::ClassA(ClassA const&)>   调用复制构造函数,rdi是待创建的对象的地址,即this指针,rsi中是方法入                                                                                      参类引用。
lea    -0x30(%rbp),%rax
mov    %rax,%rdi
callq  0x400a9e <print(ClassA)>    调用print方法,因为print是非类成员没有this指针,rdi中保存的是方法入参ClassA类实例的地                                                         址,该实例是通过复制构造函数创建出来的
lea    -0x30(%rbp),%rax
mov    %rax,%rdi
callq  0x400994 <ClassA::~ClassA()>    调用析构函数销毁调用print()时临时创建的一个对象

执行复制构造函数ClassA::ClassA(ClassA const&)的主要汇编代码如下:

mov    %rdi,-0x8(%rbp)
mov    %rsi,-0x10(%rbp)  将寄存器参数拷贝至栈帧中
mov    -0x8(%rbp),%rax
movl   $0x1,(%rax)   初始化属性 int a=1
mov    -0x8(%rbp),%rax   
movl   $0x2,0x4(%rax)   初始化属性 int b=2
mov    -0x10(%rbp),%rax  
mov    (%rax),%edx  
mov    -0x8(%rbp),%rax
mov    %edx,(%rax)    完成属性赋值 a=ca.a
mov    -0x10(%rbp),%rax
mov    0x4(%rax),%edx
mov    -0x8(%rbp),%rax
mov    %edx,0x4(%rax) 完成属性赋值 b=ca.b
mov    0x201773(%rip),%eax       
add    $0x1,%eax
mov    %eax,0x20176a(%rip)   修改静态属性 c++

执行print(ClassA)的的部分汇编代码:

mov    0x2016df(%rip),%ebx   将静态变量c的值放入ebx中
mov    -0x28(%rbp),%rax
mov    0x4(%rax),%r12d  将 属性b放入r12d中
mov    -0x28(%rbp),%rax
mov    (%rax),%r13d  将 属性a放入r12d中

main方法中执行getA方法的部分代码如下:

lea    -0x60(%rbp),%rax
mov    $0x3,%edx
mov    $0x2,%esi
mov    %rax,%rdi     准备调用参数,getA不是类成员函数,但是依然将待创建的对象的内存地址即rdi中的值作为第一个参数传入                                   函数中
callq  0x400b25 <getA(int, int)>  

getA方法的部分代码如下

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值