正确实现String可以考察C++的一些基本功,稍不注意就会出错,下面来列举一下关键点:

  1. 构造函数,拷贝构造,赋值操作符是必须的;
  2. 成员初始化列表执行顺序;
  3. c_str()不要返回空指针;
  4. 实现swap函数;
  5. 分配内存的时候要用外面传进来的size;
  6. 赋值操作符的实现方法:参数以传值的形式;
  7. 析构函数delete [];

先看下代码:

class String {
public:
    String() 
        : len_(0)
        , buffer_(nullptr) 
    {
    }

    String(const char *str, size_t size)
        : len_(size)
        , buffer_(size ? new char[size + 1] : nullptr)
    {
        if (buffer_) {
            std::copy(str, str + len_, buffer_);
            buffer_[size] = '\0';
        }
    }

    String(const char *str)
        : String(str, strlen(str))
    {
    }

    String(const String &str)
        : String(str.buffer_, str.len_)
    {
    }

    ~String()
    {
        delete[] buffer_;
    }

    void swap(String& right) 
    {
        std::swap(len_, right.len_);
        std::swap(buffer_, right.buffer_);
    }

    // 传统写法
    //String& operator= (const String &str)
    //{
    //    String tmp(str);
    //    this->swap(tmp);
    //    return *this;
    //}

    String& operator=(String str)
    {
        this->swap(str);
        return *this;
    }

    size_t size()
    {
        return len_;
    }

    const char* c_str()
    {
        return buffer_ ? buffer_ : "";
    }

private:
    size_t len_;
    char *buffer_;
};

简化代码

第二个构造函数很重要,有了它后面的构造函数写起来就方便多了。否则你要写多个类似下面的代码:

    String(const char *str, size_t size)
        : len_(size)
        , buffer_(size ? new char[size + 1] : nullptr)
    {
        if (buffer_) {
            std::copy(str, str + len_, buffer_);
            buffer_[size] = '\0';
        }
    }

成员初始化列表

注意:成员初始化列表是以成员定义的顺序来进行初始化的。
如果成员变量定义的是如下顺序:

private:
  char *buffer_;
    size_t len_;

而构造函数是下面写法是会出错的:

  // 错误的写法
    String(const char *str, size_t size)
        : len_(size)
        , buffer_(len_ ? new char[len_ + 1] : nullptr)
    {
        if (buffer_) {
            std::copy(str, str + len_, buffer_);
            buffer_[size] = '\0';
        }
    }

根据len的长度来创建buffer,看起来先将size赋值给了len然后再使用len,但其实在初始化buffer的时候len还没有被初始化,所以导致buffer_一直为nullptr。为了避免这个错误你需要注意成员变量的初始化顺序,但是这样做并不好,当成员变量多了的时候很难注意到这个问题。所以最好的做法就是:在成员初始化列表中使用传进来的参数,不要使用赋值后的参数。

字符串以'\0'结尾

由于实现的是字符串所以要多分配一个字符已'\0'结尾,如果是其他buffer,如:int[]就不需要了。

参数通过传值的方式实现赋值操作符

    String& operator=(String str)
    {
        this->swap(str);
        return *this;
    }

原理是:str传进来的是一个临时拷贝,当swap之后,把当前this的buffer交换给了临时str,临时str的buffer赋值给了this,当临时str析构的时候它帮我们把this先前的buffer给释放了,而它的buffer给了this。

聊天