C++回炉之_C++PrimerPlus_第十二章 类和动态内存分配

复制构造函数

  • 如果没有定义复制构造函数 – C++会自动提供
  • 原型

    class_name(const class_name&);
    Point(const Point&);
  • 功能
    • 逐个复制非静态成员的值 – 浅复制
    • 如果含有成员的类型也是类, 则使用此成员的复制构造函数来复制此对象
    • 当类成员里含有指针的时候,那么这两个对象的此成员都会指向同一个内存 – 这很不好
      • 此时会使用new初始化指针成员,然后手动定义一个复制构造函数,进行深复制
        • 注意要保证所有的构造函数里new的使用都与析构函数保持一致 – 有时候需要调整默认构造以实现这一点
  • 何时会调用

    • 新建一个对象并将其初始化为同类现有对象时

      Point a(b);
      Point a = b;
      Point a = Point(b);
      Point a = new Point(b);
    • 每当程序生成对象副本的时候,都会调用复制构造函数

      • 函数按值传递对象 或 函数返回对象时 – 可按引用传递

复制赋值函数

  • 实质是赋值运算符的重载
  • 如果没有定义, C++会自动提供
  • 初始化类时不一定调用赋值运算符, 也可能只调用复制构造函数

    • 初始化总会调用复制构造函数,但只是有可能调用赋值运算符

      Point a = b; // 是否调用赋值运算符取决于实现
  • 原型

    class_name& class_name::operator=(const class_name&);
  • 使用

    • 同复制构造一样,当成员函数含有指针的时候,同样需要手动创建
    • 手动创建时需要注意一些事项
      • 需要先释放目标对象之前使用new分配的旧数据
      • 应尽量避免将对象赋值给本身 – 否则可能会在赋值前释放其旧数据
      • 应返回一个指向调用对象的引用 – 为了可以连续赋值
      • 由此可知赋值函数并不创建新的对象
    • 可以进一步重载赋值运算符,以支持其他类型到此类的转换
    • 如果目前还没有必要实现复制构造和赋值运算符,但也不希望有复制或赋值现象发生,可将这两个函数声明为private且忽略其实现(将永远不会被调用)

重载[]运算符

  • 对于[]操作符,两个操作数一个在[左边,一个在[]里面

    operator[](type_name);
  • a[4] 等价于a.operator[](4);

  • 有时候将返回类型声明为引用,即可使用[]对元素(即使是私有成员)进行赋值
  • 当对象为const类型时,便不能使用[]进行赋值

    • 此时需要声明一个const版本的[]重载版本

      const return_name& oeprator[](typename) const;

类中的new和delete*

  • 在类中使用new的注意事项

    • 构造函数中使用new, 析构函数中要使用delete
    • new对应delete – new[] 对应delete[]
    • 如果有多个构造函数,需要以相同的方式使用new – 只有一个析构函数
      • 也可以将指针初始化为空 – 0 NULL nullptr(C++11) – delete可以用于空指针
    • 应当定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象

      String::String(const String& s) {
          len = s.len;
          str = new char[len+1];          // 分配空间 -- 深复制
          strcpy(str, s.str);
      }
    • 应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象

      String& String::operator=(const String& s) {    // 返回引用 -- 防止创建额外副本
          if(this == &s) return *this;            // 自我赋值时 -- 直接返回this
          delete[] str;                           // 清理旧数据
          len = s.len;
          str = new char[len+1];
          strcpy(str, s.str);
          return *this;
      }

函数的返回对象类型

  • 返回指向const对象的引用

    • 返回对象将调用复制构造函数,而返回引用不会
    • 返回引用将提高效率
    • 返回的引用不能是所在函数的局部变量
    • const类型引用的返回值也应该是const
    • 返回const类型引用可使函数在调用的时候不被修改

      const int& Max(int& x, int& y);
      Max(a, b)++; // 这样便是不对的, 但如果不是const的话便可以
    • 返回const引用的函数可以赋值给此类型变量(赋值了一下),也可赋值给const类型的引用,但是不能赋值给非const的引用

      int c = Max(a, b); // 正确
      const int& c = Max(a, b); // 正确
      int& c = Max(a, b); // 错误
      const Point& Max(const Point& a, const Point& b) {
          if(a.x > b.x) return a;
          return b;
      }
  • 返回指向非const对象的引用

    • 有时候是为了提高效率 – 如重载赋值运算符
      • 也可以返回对象,也可以返回引用 – 返回引用可避免创建新的副本
    • 有时候是为了必须这样做 – 如重载 cout的<< 运算符
      • ostream对象没有公有的复制构造函数
  • 返回对象
    • 返回对象是函数中的局部变量时,只能使用返回对象而非引用 – 如重载+运算符
    • 存在复制构造函数创建返回的对象的开销,但无法避免
  • 返回const对象
    • 主要是为了防止函数(或重载了操作符的表达式)本身成为左值而导致的一些问题
      • a+b = x; 这种式子无意思 且如果用在if()时还有可能出现不易察觉的错误

指向对象的指针

Point* p = new Point(1, 2);
cout << p->get_x() << endl;
delete p;
  • 定位new运算符不能用delete释放 – 可显示调用析构函数

    char* buffer = new buffer[maxn];
    Point* p1 = new(buffer) Point;
    Point* p2 = new(buffer + sizeof(Point)) Point(1, 2);
    p2->~Point();
    p3->~Point();
    delete[] buffer;

你可能感兴趣的:(C/C++)