此文章仅用作自身复习使用
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中的之前已有的对象来初始化新的对象。
以下几种情况会调用拷贝构造函数:
- 1.用一个类的已创建的对象去初始化该类的另一个对象时,系统自动调用拷贝构造函数实现赋值。
- 2.若函数参数形参为类对象,则调用函数时,实参赋给形参(此时也可以看作一个已经创建的对象去初始化另一个对象),系统自动调用拷贝构造函数。
- 3.当函数返回值是类对象时,系统自动调用拷贝构造函数。
如果类中没有定义拷贝构造函数,系统会默认生成一个。如果带有指针变量,并有动态内存分配,则它必须要有一个拷贝构造函数。
拷贝构造函数常见形式如下:
classname(const classname & obj)
{
函数内容;
}
实例:
#include <iostream>
using namespace std;
class Line
{
private:
int *ptr;
public:
int getLength();
Line(int len); // 普通构造函数
Line(const Line &obj);// 拷贝构造函数
~Line();
};
// 构造函数定义
Line::Line(int len)
{
cout << "调用构造函数." << endl;
ptr = new int; // 为指针分配一块内存
*ptr = len; // 给指针指向的内存块赋值
}
// 拷贝构造函数定义
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数." << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝已有对象的值
}
// 析构函数定义
Line::~Line()
{
cout << "释放内存." << endl;
delete ptr; // 释放ptr指向的那块内存
}
// 普通成员函数定义
int Line::getLength()
{
return *ptr;
}
void display(Line obj)
{
cout << "line值为:" << obj.getLength() << endl;
}
int main()
{
Line line(10);
display(line);
return 0;
}
修改main函数,使用已有对象来创建新对象,调用拷贝构造函数。
int main()
{
Line line(10);
display(line);
Line line2 = line;
display(line2);
return 0;
}
以下几种情况会调用拷贝构造函数:
- 1.对象以值传递的方式传入函数参数
class A
{
private:
int a;
public:
A(int b) // 默认构造函数
{
a = b;
}
A(const A &sameclass) // 拷贝构造函数
{
a = sameclass.a;
}
~A(); // 析构函数
void show()
{
cout << "a = " << a << endl;
}
};
void a_function(A a)
{
cout << "test A." << endl;
}
A a_obj(99);
a_function(a_obj);
上述代码中属于类A的对象a_obj以值传递的方式传入a_function函数中,会先产生一个临时变量temp,然后调用拷贝构造函数将a_obj的值赋给temp,就等于A temp(a_obj),直到a_function函数执行完毕后析构掉temp对象。
- 2.对象以值的方式从函数返回
A a_function()
{
A temp(3);
return temp;
}
int main()
{
a_function();
return 0;
}
上述代码中先产生一个临时变量xxx,然后调用拷贝构造函数把temp的值赋给xxx,整个过程类似于A xxx(temp); 函数执行到最后先析构temp局部变量,等a_function执行完毕后再析构掉xxx对象。
注 因为声明函数时为A a_function(),所以返回类型为A,而不是A的引用,所以是以值的类型返回。
默认拷贝构造函数
很多时候我们并没有定义拷贝构造函数,但传递对象参数或者从函数返回对象都能很好的进行,这是因为编译器会自动给我们生成一个默认拷贝构造函数。仅仅用于老对象的数据成员的值来对新对象的数据成员赋值。形式如下:
Rect::Rect(const RECT &r)
{
width = r.width;
length = r.length;
}
但有些情况如果自己不定义拷贝构造函数而使用系统默认拷贝构造函数可能会出错。比如:
class Rect
{
private:
int width;
int length;
static int count;
public:
Rect()
{
count ++;
}
~Rect()
{
couunt --;
}
static int getCount()
{
return count;
}
};
Rect rect1;
cout << " The count of Rect is :" << Rect::getCount() << endl;
Rect rect2(rect1); // 新建一个rect2
cout << " The count of Rect is :" << Rect::getCount() << endl;
image.png
我们明明又创建了一个rect2,但结果还是显示此时只有1个Rect类的对象。
上述代码对Rect类加入类一个静态变量用来计数(注:同一个类所有的对象公用一个静态变量,所以可以用来计数)。按照代码,此时应该有两个对象存在,但是实际程序运行时输出的都是1,反应出只有1个对象。此外,在销毁对象时,由于会调用销毁两个对象,类的析构函数也会调用两次,此时计数器将变为负数。默认的拷贝构造函数没有处理静态数据成员。,出现这个问题的根本原因是在于赋值对象时候,计数器没有递增,我们重新编写拷贝构造函数。
{
width = r.width;
length = r.length;
count ++ ; // 加上对静态成员的操作
}
浅拷贝
浅拷贝指的就是在对象复制过程中,只是简单的将对象的值赋给另一个对象的数据成员。比如说默认拷贝构造函数。但如果对象中存在类动态成员,那么浅拷贝就会出现问题。
class A
{
private:
int width;
int length;
int *p;
public:
A() // 默认构造函数
{
p = new int(100);
}
~A() // 析构函数
{
if (p!=NULL)
{
delete p;
}
}
};
A a1;
A a2 = a1;
上述代码运行结束之前,会出现一个错误,原因是在于复制对象时,对于动态分配的内容没有进行正确的操作。
- 在运定义a1对象后,由于构造函数中有一个动态分配内存的语句,因此执行后的内存情况如下:
image.png
在用a1创建a2时,由于只是做简单的赋值处理,这时候a1.p = a2.p,也就是两个对象中的指针都指向同一块内存。 image.png
当销毁对象时,同一个内存块会被连续释放两次,所以会出现错误。我们需要的不是两个具有相同值的p,而是两个p指向的空间具有相同的值。(注:new int(100) 为创建一个int型大小的内存块并赋值100)
image.png
防止默认拷贝构造函数的发生
我们可以声明拷贝构造函数时将它声明为似有成员变量,这样由于拷贝函数为私有成员,所以如果用户按值传递或者函数返回该类对象,将得到一个编译错误,从而避免按值传递或者返回类对象。
private:
A(const &b)
{
width = b.width;
}
一些其他关于拷贝构造函数的知识
- 如果对一个类class,如果一个构造函数的参数是下列之一且没有其他参数或者其他参数都有默认值,那么这个函数是拷贝构造函数。
class &x
const class &x
volatile class &x
const volatile class &x
- 一个类中可以存在多于一个的拷贝构造函数
class X
{
public:
X(const X &x ); // const的拷贝构造函数
X(X &x); // 非const的拷贝构造函数
};
网友评论