我此前另外一篇文章通过类String看拷贝构造函数,赋值函数的作用和区别
对于更深的拷贝构造函数讨论大家可以参见这篇帖子
C++类对象的复制-拷贝构造函数
通过编写类String的拷贝构造函数和赋值函数介绍了一些拷贝构造数.本文着重介绍拷贝构造函数的作用和重要性。
首先介绍下拷贝构造函数的使用范围即作用:
1) 一个对象以值传递的方式传入函数体;
2) 一个对象以值传递的方式从函数返回; 3) 一个对象需要通过另外一个对象进行初始化;如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的。
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象,这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出(Stack Overflow)。除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。
拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷贝。
1. 不自行定义拷贝构造函数产生的问题:
class CA
{
char* p;
public:
CA(const char* a)
{
if(NULL!=a)
{
p = new char[strlen(a)+1];
strcpy(p,a);
}
else
{
p = new char[1];
p='\0';
}
};
// CA(CA& mself)
// {
// p=new char[strlen(mself.p)+1];
// strcpy(p,mself.p);
// }
void Show()
{
printf("成员变量:%s\n",p);
}
~CA()
{
printf("析构函数\n");
delete p;
}
};
CA ca("hello");
CA cb= ca;
cb.Show();
执行结果:
当我们不使用自定义拷贝构造函数时,在系统自动调用析构函数时,同一片内存区域p会被释放两次,这时候就会出现上图中的释放错误.而我们将代码中注释掉的自定义拷贝构造函数放开,则不会出现这样的错误。
2. 拷贝构造函数和构造函数,以及无名对象。
先声明定义一个类
class Internet
{
public:
Internet(char *name)
{
std::cout<<"载入构造函数"<<std::endl;
strcpy(Internet::name,name);
}
Internet(Internet &temp)
{
std::cout<<"载入COPY构造函数"<<std::endl;
strcpy(Internet::name,temp.name);
std::cin.get();
}
~Internet()
{
std::cout<<"载入析构函数!";
std::cin.get();
}
void Show()
{
std::cout<<name<<std::endl;
}
protected:
char name[20];
};
2.1 无名对象
无名对象可以作为实参传递给函数,可以拿来拷贝构造一个新对象,也可以初始化一个引用的声明。
2.1.1
Internet b = Internet("test");// 初始化对象定义
图2.1
上面执行的是用无名对象拷贝构造一个对象b。按理说C++先调用构造函数Internet(char*);
创建一个无名对象,然后再调用拷贝构造函数Internet(Internet&);(或许是默认的)创建对象b;但是,由于是用无名对象去拷贝创建一个对象,拷贝完后,无名对象就失去了任何作用,对于这种情况,C++特别将其看作为Internet b = "test";效果一样,而且可以省略创建无名对象这一步。
如测试
Internet b = "test";
或者
Internet b("test");
输出的结果都是和上面一样的,因此可以把上面这3个初始化b的方式看成一样的。
2.1.2
Internet& refs = Internet("测试"); // 初始化引用
上面执行的是拿无名对象初始化一个引用。由于是在函数内部,所以无名对象作为局部对象产生在栈空间中,从作用域上看,该引用与无名对象是相同的,它完全等价于Internet refs = "测试";所以这种使用是多余的。
2.1.3
先定义一个使用自定义类型作参数的函数
void fn(Internet s)
{
}
测试函数fn(Internet("测试"));
输出结构仍然是图2.1
这次执行的是无名对象作为实参传递给形参s,C++先调用构造函数创建一个无名对象,然后该无名对象初始化了引用形参s对象,由于实参是在主函数中,所以无名对象是在主函数的栈区中创建,函数fn()的形参s引用的是主函数栈空间中的一个对象。它等价于:
Internet s("测试");
fn(s);
如果对象s仅仅是为了充当函数fn()实参的需要,完全可以用第
fn(Internet("测试"));
来代替。