C++构造与析构(2) - 拷贝构造函数

目录

1.拷贝构造函数定义

2.什么时候被调用

3.什么时候需要自定义

4.与赋值运算符的区别

5.能否定义为private

6.参数必须使用引用传递

7.参数必须是const


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

可以参考本系列的后续篇章。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值