C++关于拷贝构造函数的详解:
目录
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; }