目录
1.拷贝构造函数定义
拷贝构造函数是类的一个成员函数,用于使用另一个对象来初始化本对象。拷贝构造函数遵循下面的函数基本原型:
ClassName (const ClassName &old_obj);
下面例子是一个简单的拷贝构造函数:
#include<iostream>
class Point
{
private:
int x, y;
public:
Point(int x1, int y1) { x = x1; y = y1; }
// 拷贝构造函数
Point(const Point &p2) {x = p2.x; y = p2.y; }
int getX() { return x; }
int getY() { return y; }
};
int main()
{
Point p1(11, 22); // 调用普通构造函数
Point p2 = p1; // 调用拷贝构造函数
std::cout << "p1.x = " << p1.getX() << ", p1.y = " << p1.getY() << std::endl;
std::cout << "p2.x = " << p2.getX() << ", p2.y = " << p2.getY() << std::endl;
return 0;
}
输出:
p1.x = 11, p1.y = 22
p2.x = 11, p2.y = 22
2.什么时候被调用
1. 按值返回类的对象
2. 做为参数按值传递
3. 相同类中的一个对象基于另一个对象来构造.
4. 编译器产生临时对象
但是,不保证拷贝构造函数在上面所有case中都能被调用到。因为C++标准允许编译器在某些情况下优化对象拷贝方式。
其中一个例子就是返回值的优化(return value optimization)(也称为RVO, 具体可参考http://en.wikipedia.org/wiki/Return_value_optimization)。
3.什么时候需要自定义
如果没有自定义的拷贝构造函数,则编译器会自动创建一个默认的拷贝构造函数,来实现成员的自动拷贝。
通常情况下这个默认的拷贝构造函数可以正常运行。但是,如果一个对象拥有指针,或者有任何运行时分配的资源,例如文件句柄,网络连接等,则需要自定义的拷贝构造函数。否则会出现一些意想不到的结果。
4.与赋值运算符的区别
下面两个表达式哪个调用拷贝构造,哪个调用赋值运算符?
MyClass t1, t2;
MyClass t3 = t1; // case1
t2 = t1; // case2
当使用一个已有对象,创建新的对象时,就调用拷贝构造,如case1所示。
当一个已有对象被赋予新值(来自另一个对象)时,则调用赋值运算符,如case2所示。
下面的类需要定义拷贝构造函数。
#include<iostream>
#include<cstring>
class String
{
private:
char *s;
int size;
public:
String(const char *str = NULL); // 构造函数
~String() { delete [] s; } // 析构函数
String(const String&); // 拷贝构造函数
void print() { std::cout << s << std::endl; }
void change(const char *);
};
String::String(const char *str)
{
size = strlen(str);
s = new char[size+1];
strcpy(s, str);
}
void String::change(const char *str)
{
delete [] s;
size = strlen(str);
s = new char[size+1];
strcpy(s, str);
}
String::String(const String& old_str)
{
size = old_str.size;
s = new char[size+1];
strcpy(s, old_str.s);
}
int main()
{
String str1("suv");
String str2 = str1;
str1.print();
str2.print();
str2.change("bus");
str1.print();
str2.print();
return 0;
}
输出:
suv
suv
suv
bus
如果上述代码中,删除了拷贝构造函数,会出现怎样的情况?
#include<iostream>
#include<cstring>
using namespace std;
class String
{
private:
char *s;
int size;
public:
String(const char *str = NULL); // constructor
~String() { delete [] s; } // destructor
void print() { cout << s << endl; }
void change(const char *);
};
String::String(const char *str)
{
size = strlen(str);
s = new char[size+1];
strcpy(s, str);
}
void String::change(const char *str)
{
delete [] s;
size = strlen(str);
s = new char[size+1];
strcpy(s, str);
}
int main()
{
String str1("suv");
String str2 = str1;
str1.print();
str2.print();
str2.change("bus");
str1.print();
str2.print();
return 0;
}
输出:
suv
suv
bus
bus
可以看到,结果不是所期望的。对str2的操作,影响到了str1。这就是上面说的,涉及到资源共享时,需要定义独立的拷贝构造函数。
5.能否定义为private
答案是可以!将类的拷贝构造函数定义为private, 则类的对象是不可拷贝的。这对于有指针成员或者会动态分配资源的类特别有用。
在这种情况下,我们可以自定义拷贝构造函数,像上面string例子那样,或者将拷贝构造定义为private,这样用户可以发现编译错误。
6.参数必须使用引用传递
拷贝构造函数中的参数,必须使用引用传递。
为什么呢?如果对象按值传递的话,会调用拷贝构造函数。这样拷贝构造函数中再次调用拷贝构造函数,会形成一个无限循环。所以,编译器禁止按值传递参数。
7.参数必须是const
可以参考本系列的后续篇章。