03_深入了解拷贝构造函数、拷贝赋值运算符

一、拷贝构造函数

定义: 如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都是默认值,则此构造函数是拷贝构造函数。

class Foo {
 public:
  Foo();  // 默认构造函数
  Foo(const Foo&);  // 拷贝构造函数
  // ...
}

1.1 拷贝构造函数的参数类型为什么必须是一个引用类型?

如果其参数不是引用类型,则需要拷贝实参,又需要调用拷贝构造函数,如此无限循环。

1.2 拷贝构造函数的参数类型可不可以是指针类型?

不可以,根据定义,参数是指针类型的构造函数可以是转换构造函数,但不可以是拷贝构造函数。

1.3 拷贝构造函数可以声明显式调用explicit吗?

不能,拷贝构造函数在几种情况下会被隐式调用,因此不能是explict。

拷贝初始化应用场景:

  • 用 = 定义变量时
  • 将一个对象作为实参传递给一个非引用类型的形参
  • 从一个返回类型为非引用类型的函数返回一个对象
  • 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
  • 标准容器insert/push等也会使用
string dots(10, '.');  // 直接初始化
string s(dots);        // 直接初始化
string s2 = dots;      // 拷贝初始化

二、拷贝赋值运算符

拷贝赋值运算一般组合了拷贝构造和析构操作,操作前要先判断是否是自赋值。

class Foo {
 public:
  Foo();  // 默认构造函数
  Foo(const Foo&);  // 拷贝构造函数
  // 拷贝赋值运算符通常返回一个指向左侧运算对象的引用。
  Foo& operator=(const Foo&);  // copy-assignment Operator,(翻译成赋值构造函数有点牵强,一般翻译为拷贝赋值运算符)
  // ...
}

2.1 拷贝赋值运算符返回值有什么用?返回值是否为引用类型有区别吗?

拷贝赋值运算符的返回值为引用类型比较好,在给多个对象赋值时,可以节省内存开支。在进行链式操作时,不但可以节省内存开支,还可以对其进行修改性操作

2.2 拷贝赋值运算符参数也必须为引用吗?

class Person {
  public:
    Person() {}
    Person(const Person& p) {
      cout << "Copy Constructor" << endl;
    }

    Person& operator=(const Person &p) {
      // Person& operator=(const Person p) {  // change
      cout << "Assign" << endl;
      return *this;
    }
  private:
    int age;
    string name;
};

int main() {
    Person p;
    Person p2;
    p2 = p;

    return 0;
}

// 引用传递运行结果
在这里插入图片描述

// 值传递运行结果
在这里插入图片描述

可见,参数不必为引用,但是使用引用可以减少多余操作。

2.3 拷贝构造函数和拷贝赋值运算符区别?

// 使用上文Person类
Person p;
Person p1 = p; // 1

Person p2;
p2 = p;       // 2
  1. 这是虽然使用了"=",但是实际上使用对象p来创建一个新的对象p1。也就是产生了新的对象,所以调用的是拷贝构造函数。
  2. 首先声明一个对象p2,然后使用赋值运算符"=",将p的值复制给p2,显然是调用赋值运算符,为一个已经存在的对象赋值。
  • 拷贝构造函数和赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使 用已有的对象创建一个新的对象 ,赋值运算符是将 一个对象的值复制给另一个已存在的对象 。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。

三、关于类中指针成员的问题

拷贝赋值和移动赋值指针成员数据时,需要先释放旧数据,再将rhs赋值到旧数据。

希望阻止拷贝的类应该使用=delete 来定义它们自己的拷贝构造函数和拷贝赋值运算符,而不应该将它们声明为private。

一个带指针变量拷贝控制示例:

#ifndef __MYSTRING__
#define __MYSTRING__

class String {
  public:
    String(const char* cstr=0);
    String(const String& str);  // 拷贝构造
    String& operator= (const String& str);  // 拷贝赋值
    ~String();

    char* get_c_str const(){
      return m_data;
    }

  private:
    char* m_data;  // 带指针成员的类:一定要注意拷贝赋值,拷贝构造,析构
    // String的底部通过char实现,在外部表现为string

};

inline String::String ( const char* cstr=0 ) {
    if(cstr) {
      m_data = new char[strlen(cstr)+1];
      strcpy(m_data,cstr);  //  char * strcpy ( char * destination, const char * source );
    } else { // 考虑cstr=0;
      m_data=new char[1];
      m_data[0]='\0';
    }
}

inline String::String(const String& str) {
    m_data=new char[strlen(str.get_c_str())+1];
    strcpy(m_data,str.m_data);
}

inline String::~String() {
    delete[] m_data;
}

inline String& String::operator=(const String& str){
    if(this==&str){ // self assignment :1.**自我检验**  (如果没有进行这样的处理,在自我赋值会产生严重的错误)
      return *this;
    }

    // 2.**构造函数是第一次构造,因此不需要删除原内存对象,但是赋值需要先进行delete**
    delete[] m_data;  // 先删除,重新分配一样大小!
    m_data=new char[strlen(str.get_c_str())+1];
    strcpy(m_data,str.m_data);
    return *this;   // 其实不用返回*this也可以,因为已经实现了修改,但是这样有一个好处  可以实现 a=b=c;  (因为返回的类型继续支持=)
}

// 调用形式: cout <<  String() ;   第一个参数为 << 左边 ,第二个参数为 << 右侧;返回ostream 可以实现 cout <<  a << b ;
ostream& operator<<(ostream& os,const String& str){
    os<<str.get_c_str();
    return os;
}

#endif

参考

https://www.bbsmax.com/A/xl56OQN9dr/

https://www.cnblogs.com/wangguchangqing/p/6141743.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值