C++之拷贝构造函数详解:定义、参数类型、调用的3种情况

C++关于拷贝构造函数的详解:

目录

C++关于拷贝构造函数的详解:

1.定义:

2.参数的类型 :

(1)值类型(不行,会产生递归);

剑指offer例题

1)会产生递归的原因:

2)代码执行过程:

3)总结:

(2)指针类型(会出现歧义);

(3)引用类型(本类对象的const引用)

1)代码执行过程:

3.拷贝条件:拷贝构造函数--本类旧对象去构造本类新对象

(1)代码执行过程

 (2)拷贝构造函数调用的3种情况

1)用已有对象去初始化本类的对象

2)函数传参,类类型的值传递

3)函数是类类型的值返回  局部对象->临时对象


1.定义:

“拷贝构造函数,又称复制构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其形参必须是引用,但并不限制为const,一般普遍的会加上const限制。此函数经常用在函数调用时用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。”

2.参数的类型 :

三种参数类型:值、指针、引用。

(1)值类型(不行,会产生递归);

剑指offer例题

//剑指offer例题
class A {
private:
	int value;
public:
	A(int n) { value = n; }
	A(A other) { value = other.value; }
	void Print() { cout << value << endl; }
};

int main()
{
	A a = 10; //constructor
	A b = a; //copy constructor
	b.Print();
	return 0;
}

选项为:

A.编译错误

B.编译成功,运行时程序崩溃

C.编译运行正常,输出10

答案: A、编译错误。复制构造函数A(Aother)传入的参数是A的一个实例。由于是传值参数,我们把形参复制到实参会调用复制构造函数。因此如果允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无休止的递归调用从而导致栈溢出
 

1)会产生递归的原因:

#include <iostream>
using namespace std;

class CTest{
private:
    int n;
public:
    CTest(int i):n(i){
        cout << "Constructor with argument" << endl;
    } //带参构造函数
    CTest(const CTest &ctest){
        n = ctest.n;
        cout << "Copy constructor" << endl;
    }
    //赋值运算符重载
    CTest& operator=(const CTest &t){
        cout << "Assignment operator" << endl;
        n = t.n;
        return *this;
    }
    void myTestFunc(CTest t){

    }
};

int main()
{
    CTest a(2);
    CTest b(3);
    b = a; //这里是赋值运算符
    CTest c = a;
    c.myTestFunc(a);
    return 0;
}

2)代码执行过程:

1.首先CTest a(2);执行,会调用带参数的构造函数,于是输出的第一行Constructor with  argument是这里引起的;
2.同理CTest b(3);也是一样;
3.然后b = a;这句因为b和a已经实例化了,所以不用构造函数进行实例化,直接调用赋值运算符,输出第三行的Assignment operator;

4.在CTest c = a;这里,需要调用复制构造函数来对c进行实例化,也就是执行CTest(CTest t)这个构造函数,要执行这个函数,我们传入的参数是a,a是实参,t是形参,这样的话我们需要将a复制一份给t,也就是执行CTest t = a,这个形式和一开始的CTest c = a是一样的,于是就这样无限不循环下去了,最后导致栈溢出。

        正常的情况下,也即复制构造函数的参数为引用的情况下,CTest c = a;这条语句是调用复制构造函数,也就是CTest(const CTest &ctest),由于参数是引用,所以不用进行形参和实参之间的复制即可直接调用。

3)总结:

        所以,拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝,而是避免拷贝构造函数无限制的递归下去。

(2)指针类型(会出现歧义);

class A
{
public:
	A(int i = 0, int j = 0) :m_i(i), m_j(j) //构造函数
    { 
        cout << "A" << endl;
    }
	void Print()//打印函数
	{
		cout << "m_i = " << m_i << "  m_j = " << m_j << endl;
	}
	~A()//析构函数
	{
		cout << "~A" << m_i << m_j << endl;
	}

	A(A* s):m_i(s->m_i),m_j(s->m_j)//拷贝构造函数//error
	{
		cout<<"A(A*)"<<endl;
	}//A b = &a;//A b(&a)
private:
	int m_i;
	int m_j;
};

void main()
{
	A a(3, 6);
	a.Print();
    
	A b = &a;//A b(&a),
	b.Print();
}
A b = &a;//A b(&a),

本质其实就是用a的数据成员去初始化b的数据成员//有歧义,感觉a的地址给b构造

(3)引用类型(本类对象的const引用)

class A
{
public:
	A(int i = 0, int j = 0) :m_i(i), m_j(j) { cout << "A" << endl; }
	void Print()
	{
		cout << "m_i = " << m_i << "  m_j = " << m_j << endl;
	}
	A(const A& s) :m_i(s.m_i), m_j(s.m_j)  //拷贝构造函数
	{
		cout << "A(A&)" << endl;
	}
	~A()
	{
		cout << "~A" << m_i << m_j << endl;
	}

	
private:
	int m_i;
	int m_j;
};

void main()
{
	A a(3, 6);
	a.Print();

	A b = a;//1.用a对象去构造b,本质其实就是用a的数据成员去初始化b的数据成员
	b.Print();
	
}

1)代码执行过程:

1.调用构造函数,构造对象a,打印构造函数中写入的"A”

2.调用打印函数,输出对象a的内容

3.调用拷贝构造函数,用对象a拷贝构造对象b,打印拷贝构造函数写入的“A(A&)"

4.调用打印函数,输出对象b的内容

5.调用析构函数,释放对象b的空间,打印析构函数中写入的“~A"(注意:一定是后构造的先析构)

6.调用析构函数,释放对象a的空间,打印析构函数中写入的“~A"

3.拷贝条件:拷贝构造函数--本类旧对象去构造本类新对象

class A
{
public:
	A(int i = 0, int j = 0) :m_i(i), m_j(j) { cout << "A" << endl; }
	void Print()
	{
		cout << "m_i = " << m_i << "  m_j = " << m_j << endl;
	}
	A(const A& s) :m_i(s.m_i), m_j(s.m_j)  //拷贝构造函数
	{
		cout << "A(A&)" << endl;
	}
	~A()
	{
		cout << "~A" << m_i <<" " << m_j << endl;
	}

private:
	int m_i;
	int m_j;
};

void Test(A t) //A t = c 从实参c到形参t的过程,调用拷贝构造
{
	cout << "Test : " << endl;
	t.Print();
}

A fn()
{
    cout << "fn : " << endl;
	A d(10, 12);
	return d; //局部对象d构造临时对象
}
//
void main()
{
	A a(2, 6); //构造A(int,int)
	A b(a); //1-拷贝构造A(A&)
    //	A c;
	//	c = a; //不会调用拷贝构造,c在上面A c已经构造出来了,调用的是后面要学习的赋值运算符重载//不会调用拷贝构造,a是对c重新进行赋值,只有对新对象才会调拷贝构造,c已经构造出来,c调用的是运算符重载
	A c(9, 3);//构造A(int,int)
	Test(c);//拷贝构造
	//c = fn();
	A dd = fn();
	cout << "main end" << endl;
}

(1)代码执行过程

1.调用构造函数,构造对象a,打印构造函数中写入的"A”

2.调用拷贝构造函数,用对象a拷贝构造对象b,打印拷贝构造函数写入的“A(A&)"

3.调用构造函数,构造对象c,打印构造函数中写入的"A”

4.先传参,调用拷贝构造函数,用对象c拷贝构造临时对象,打印拷贝构造函数写入的“A(A&)"

5.调用Test函数,打印Test函数中的"Test : " 

6.调用打印函数,输出临时对象的内容

7.调用析构函数,释放临时对象的空间,打印析构函数中写入的“~A"

8.调用fn函数,打印fn函数中的"fn : " 

9.调用构造函数,构造对象d,打印构造函数中写入的"A”

10.调用拷贝构造函数,用对象d拷贝构造临时对象,打印拷贝构造函数写入的“A(A&)"

11.调用析构函数,释放临时对象的空间,打印析构函数中写入的“~A"

12.main函数执行完毕,打印main函数中的"main end"

13.调用析构函数,释放对象dd的空间,打印析构函数中写入的“~A"

14.调用析构函数,释放对象c的空间,打印析构函数中写入的“~A"

15.调用析构函数,释放对象b的空间,打印析构函数中写入的“~A"

16.调用析构函数,释放对象a的空间,打印析构函数中写入的“~A"

(2)拷贝构造函数调用的3种情况

1)用已有对象去初始化本类的对象

    A a(2, 6); //构造A(int,int)
	A b(a); //1-拷贝构造A(A&)

2)函数传参,类类型的值传递

void Test(A t) //A t = c 从实参c到形参t的过程,调用拷贝构造
{
	cout << "Test : " << endl;
	t.Print();
}

3)函数是类类型的值返回  局部对象->临时对象

A fn()
{
    cout << "fn : " << endl;
	A d(10, 12);//局部对象d构造临时对象
	return d; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值