【C++】9:string类的模拟实现

在看string类的模拟实现之前,需要理解stirng类的理解和使用,大家可以看一下我的上一篇讲解string的文章:string类的理解和使用string的相关文档

目录

一、String类的构成

二、String类的结构设计

三、构造函数

四、析构函数

五、遍历

5.1 重载[]

5.2 范围for

5.3 自己写一个迭代器

六、pushback的实现

七、append的实现

八、+=运算符的重载

8.1 string& operator+=(char ch)

8.2 string& operator+=(const char* str)

九、insert的实现

9.1 void insert(size_t pos, char ch)

9.2 void insert(size_t pos, const char* str)

十、erase的实现

​十一、find的实现

十二、substr的实现

十三、拷贝构造函数

十四、赋值运算符重载

十五、字符串比较

十六、流插入运算符<<重载

十七、流提取运算符>>重载

17.1 优化

十八、拷贝构造函数的现代写法

十九、赋值运算符重载的现代写法

二十、代码

20.1 string.h

20.2 string.cpp

20.3 test.cpp


一、String类的构成

在上一篇文章string类的理解和使用中,我们讲过vs下string类的构成,如下图所示:

当字符串长度小于16时,会把数据存在_buff的数组中,我们在模拟实现的时候,没有实现这个功能,会将字符串内容都存在_str所指向的堆区。

二、String类的结构设计

我们会创建三个文件,如下所示:

1 test.cpp        // 测试逻辑

2 string.cpp     // string的方法

3 string.h     // string类的声明

此外,为了很好的区分vs自带的string,我们会将我们模拟实现的string写在我们namaspace中,同时也能随时根据类域随时切换到vs自带的string,如下所示:

在string.cpp和test.cpp的文件中也会新建一个同名的类域,多个文件相同的namespace会合在一起。如下所示:

三、构造函数

在string类里面提供了很多的构造函数,我们这里只写两个构造函数,一个是无参的构造函数,另外一个是有参的构造函数

 string的无参构造函数和 string的有参构造函数代码如下:

	string() 
		:_str(nullptr)
		,_size(0)
		,_capacity(0)
	    {}
	string(const char* str) 
	{
		_size = strlen(str);
		_capacity = _size;//_capacity不包含‘\0’,因此会在开空间的时候多开一个
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}

因为此时我们没有重载<<运算符,所有不能通过cout<<s1<<endl;来输出,所以,我们先写一个c_str函数来获取存储字符串空间的地址,然后在通过cout来打印,代码如下:

c_str()函数:

const char* c_str() {
    return _str;
}

测试代码1:测试代码是写到test.cpp的zx类域里面

namespace zx
{
    void string_test1() {
        string s1;
        string s2("hello world");
        cout << s1.c_str() << endl;
        cout << s2.c_str() << endl;
    }
}

这里的string前面没有加类域,会默认是zx类域里面我们模拟实现的string,这是因为在同一个类域里面,如果我们想要切换到库里面自带的string,就可以在前面加一个std类域就可以了。

我们在test.cpp里面来调用这个测试函数:zx::string_test1();

我们发现输出不了,这是为什么呢?

我们来切换库里面的string类,代码如下所示:

namespace zx
{
    void string_test1() {
        std::string s1;
        std::string s2("hello world");
        cout << s1.c_str() << endl;
        cout << s2.c_str() << endl;
    }
}

然后再在test.cpp里面来调用这个测试函数:zx::string_test1();

输出如下所示:

因此我们模拟实现写的string是有问题的。

我们在创建s1对象时,没有传入参数,s1的_str是指向nullptr的,会出现错误,因此我们在写string的无参构造函数时,应该给他一个额外的字符空间,用来存放‘\0'.代码如下所示:

        string() 
            :_str(new char[1] {'\0'})    //或者_str(new char('\0'))
            ,_size(0)
            ,_capacity(0)
        {}

我们再把string的类域切换为zx或者把std::去掉,去掉std::之后他就会优先在namespace里面取寻找string类,然后执行测试,发现能够像库里面的这样输出了。

最后,我们可以将无参构造函数和有参构造函数合并,代码如下:

string(const char* str="")
{
    _size = strlen(str);
    _capacity = _size;//_capacity不包含‘\0’,因此会在开空间的时候多开一个
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}

当我们没有传入参数时,默认参数有一个空字符串,空字符串里面时默认包含’\0‘的并且strcpy函数也能拷贝这个’\0‘,因此能够使用这种方法来实例化一个空串。

四、析构函数

我们在堆区里面开辟了数据,因此需要写析构函数来释放堆区的数据,函数如下:

~string() {
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
}

五、遍历

string类的理解和使用中我们写了三种方法来遍历string,这里我们也写同样的三种方法。如下所示:

5.1 重载[]

在重载[]之前,我们先写一个返回_size()的函数,如下所示:

size_t size() const{
    return _size;
}

string类对象有普通对象和常对象,所有我们应该写两个版本的重载[]的函数,此外,在库里面的string,我们可以使用[]来修改字符串,因此我们应该返回对应位置的引用,如下所示:

char& operator[](int pos) {
    assert(pos < _size && pos >= 0);
    return _str[pos];
}
const char& operator[](int pos) const{
    assert(pos < _size && pos >= 0);
    return _str[pos];
}

测试代码如下:测试代码是写到test.cpp的zx类 域里面

void string_test2() {
    zx::string s1("hello world");
    for (int i = 0; i < s1.size(); i++) {
        s1[i] += 2;
    }
    cout <<s1.c_str()<<endl;
}

我们在test.cpp来调用这个函数:zx::string_test2();

运行结果如下:

模拟实现的string运行起来和库里面自带的string一样

5.2 范围for

那我们模拟实现string能支持范围for遍历嘛?我们可以写个代码来测试一下:测试代码是写到test.cpp的zx类域里面

void string_test3() {
    zx::string s1("hello world");
    for (auto ch : s1) {
        cout << ch;
    }
    cout << endl;
}

我们来运行这个函数:zx::string_test3();运行之后发现报错了

这是为什么呢?在string的类的理解和使用里面,我们说过范围for的底层就是替换成迭代器。但是我们模拟实现的string类里面是没有迭代器的。于是我们可以使用一个char* 类型的指针来模拟一下迭代器。如下所示:

5.3 自己写一个迭代器

自己模拟实现的迭代器如下所示:

typedef char* iterator;
iterator begin() {
    return _str;
}
iterator end() {
    return _str+_size;
}

然后写一个测试代码来使用迭代器来遍历string,代码如下:

void string_test4() {
    string s1("hello world");
    string::iterator it = s1.begin();
    while (it != s1.end()) {
        cout << *it;
        it++;
    }
    cout << endl;
}

我们来运行这个函数:zx::string_test4();运行结果如下:

我们在实现迭代器之后,再去执行上面的范围for遍历,发现上面的代码也不会报错了。

我们在学到后面其他的STL容器,如list,queue等等,会发现这些容器里面都有iterator,这些iterator实际上都是typedef出来的,但不是char*类型,在list容器里面的iterator是这样定义的,如下所示:

为什么我们能用原生指针char*来模拟实现string类的iterator呢?这个主要的原因就是string类里的字符串数据是连续存放的。

迭代器很好了体现了面向对象的封装特性:屏蔽了底层细节,提供了统一的类似访问容器的方式,不需要关心容器底层结构和实现细节。

除了这个迭代器之外,还有const迭代器,定义如下:

typedef char* iterator;
typedef const char* const_iterator;
iterator begin() 
{
    return _str;
}
iterator end() {
    return _str+_size;
}
const_iterator begin() const 
{
    return _str;
}
const_iterator end() const
{
    return _str + _size;
}

六、pushback的实现

在上面的函数都是比较简单的,我们都是直接写在类里面的,现在我们来实现声明和实现相分离,在类域的string里面声明函数,然后在string.cpp的类域里面实现这函数。

在pushback之前,我们会首先检查容量满了没有,我们可以写一个reserve函数来增加空间。代码如下:

void string::reserve(size_t n) {
    if (n > _capacity) {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

pushback代码如下:

void string::pushback(char ch) {
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    _str[_size] = ch;
    _size++;
    _str[_size] = '\0';
}

测试代码如下:

void string_test5() {
    string s1("hello world");
    s1.pushback(' ');
    s1.pushback('$');
    cout << s1.c_str() << endl;
}

运行结果如下:

七、append的实现

append代码实现如下:

void string::append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity) {
        //大于2倍,需要多少开多少,小于2倍按2倍扩空间。
        reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
    }
    strcpy(_str + _size, str);
    _size += len;
}

测试代码如下:

void string_test6() {
    string s1("hello");
    s1.append(" world$$$$$$");
    cout << s1.c_str() << endl;
}

运行结果如下:

八、+=运算符的重载

+=运算符有两个版本,可以加一个字符,也可以加一个字符串。

8.1 string& operator+=(char ch)

在上面我们写了pushback来追加一个字符,我们加一个字符可以来调用这个函数,如下所示:

string& string::operator+=(char ch)
{
    pushback(ch);
    return *this;
}

测试代码如下:

void string_test7() {
    string s1("hello");
    s1 += ' ';
    s1 += '#';
    cout << s1.c_str() << endl;
}

运行结果如下:

8.2 string& operator+=(const char* str)

我们在上面写了一个追加字符串的函数,这样函数同样可以调用append函数。如下所示:

string& string::operator+=(const char* str)
{
    append(str);
    return *this;
}

测试代码如下所示:

void string_test8() {
    string s1("hello");
    s1 += " world$$$$$$";
    cout << s1.c_str() << endl;
}

运行结果如下所示:

九、insert的实现

insert我们提供两种版本,可以往一个位置插入一个字符,也可以往一个位置插入一个字符串。

9.1 void insert(size_t pos, char ch)

代码如下:

void string::insert(size_t pos, char ch) {
    assert(pos >= 0 && pos <= _size);
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    size_t end = _size;
    while (end>=pos) {
        _str[end+1] = _str[end];
        end--;
    }
    _str[pos] = ch;
    _size++;
}

我们分别在第一个位置,最后一个位置和中间一个位置插入一个字符,代码如下:

void string_test9() {
    string s1("hello");
    s1.insert(3, '!');
    s1.insert(s1.size(), '@');
    s1.insert(0, '#');
    cout << s1.c_str() << endl;
}

我们发现会运行错误,这是为什么呢?当我们往0这个位置插入字符的时候,end=0,把0这个位置的字符往后挪之后,end--会变成-1,在内存里面是全1.全1肯定会大于pos的值,因此会无限的执行下去。

那我们把end改成int类型可以吗?也是不可以的,当int类型的end=-1,和size_t类型的pos比较,会悄悄地类型转换,会转换为size_t类型的比较。

把函数的参数改为int pos可以解决,但是库里面的pos都是size_t类型的。

方法一:在比较的时候把pos强转为int进行比较

void string::insert(size_t pos, char ch) {
    assert(pos >= 0 && pos <= _size);
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    size_t end = _size;
    while (end>=(int)pos) {
        _str[end+1] = _str[end];
        end--;
    }
    _str[pos] = ch;
    _size++;
}

方法二:我们不让end指向最后一个’\0'的位置,而是指向\0'的下一个位置,让 _str[end] = _str[end-1],当end=1的时候,会把下标为0的元素移到前面,移完之后end=0,并不会出现-1这种情况。

void string::insert(size_t pos, char ch) {
    assert(pos >= 0 && pos <= _size);
    if (_size == _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    size_t end = _size+1;
    while (end>pos) {
        _str[end] = _str[end-1];
        end--;
    }
    _str[pos] = ch;
    _size++;
}

这两种方法都能解决这个问题,运行结果如下所示:

9.2 void insert(size_t pos, const char* str)

代码如下:

void string::insert(size_t pos, const char* str) {
    assert(pos >= 0 && pos <= _size);
    size_t len = strlen(str);
    if (_size+len > _capacity) {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    size_t end = _size + len;
    while (end > pos + len-1) {
        _str[end] = _str[end - len];
        end--;
    }
    for (int i = 0; i < len; i++) {
        _str[pos + i] = str[i];
    }
    _size += len;
}

我们分别在第一个位置,最后一个位置和中间一个位置插入一个字符串,代码如下:

void string_test10() {
    string s1("hello");
    s1.insert(1, "###");
    s1.insert(s1.size(), "###");
    s1.insert(0, "!!");
    cout << s1.c_str() << endl;
}

运行结果如下:

十、erase的实现

在实现erase的时候,为了和库里面保持一致吗,我们也使用一个静态成员npos,

定义如下:

代码如下:void erase(size_t pos, size_t len = npos);//函数声明

void string::erase(size_t pos, size_t len) {
    assert(pos < _size);
    if (len >= _size- pos ) {
        _size = pos;
        _str[pos] = '\0';
    }
    else {
        while (pos <= _size-len) {
            _str[pos] = _str[pos + len];
            pos++;
        }
        _size -= len;
    }
}

测试代码如下:

void string_test11() {
    string s1("hello");
    s1.erase(1, 2);
    cout << s1.c_str() << endl;
    string s2("hello");
    s2.erase(1);
    cout << s2.c_str() << endl;
    string s3("hello");
    s3.erase(1,100);
    cout << s3.c_str() << endl;
   }

运行结果如下:



十一、find的实现

代码如下:size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);//声明

size_t string::find(char ch, size_t pos)
{
    
    for (size_t i = pos; i < _size; i++) {
        if (_str[i] == ch) {
            return i;
        }
    }
    return npos;
}
size_t string::find(const char* str, size_t pos) {
    assert(pos < _size);
    const char* ptr = strstr(_str + pos, str);
    if (ptr == nullptr) {
        return npos;
    }
    else {
        return ptr - _str;
    }
}

十二、substr的实现

这个函数的作用是取一个字符串的字串。

代码如下所示:string substr(size_t pos = 0, size_t len = npos); //声明

string string::substr(size_t pos, size_t len) {
    assert(pos < _size);
    if (len > _size-pos) {
        len = _size - pos;
    }
    string ret;
    ret.reserve(len);
    for (int i = 0; i < len; i++) {
        ret += _str[pos + i];
    }
    return ret;
}

测试代码如下所示:

void string_test12() 
{
    string s1("hello world");
    string s2 = s1.substr(6, 10);
    cout << s2.c_str() << endl;
    string s3 = s1.substr(6);
    cout << s3.c_str() << endl;
    string s4 = s1.substr(0,5);
    cout << s4.c_str() << endl;
}

运行如下所示:

一些编译器可能会做一些优化,把中间的临时对象省略了,会让s2和函数里面的ret指向同一空间,这就会导致substr函数结束之后会释放堆区空间,s2就找不到堆区的字符串了。解决方法之一是写一个深拷贝。

十三、拷贝构造函数

自动生成的拷贝构造函数是一个浅拷贝,可能会带来各种各样的问题,因此,如果类成员有指向堆区的指针,我们都应该自己手动的写一个深拷贝。如下所示:

string(const string& s) {
    _str = new char[s._capacity+1];
    strcpy(_str, s._str);
    _size = s._size;
    _capacity = s._capacity;

}

十四、赋值运算符重载

我们在没有写赋值运算符重载的话,类会自动生成一个赋值运算符重载的函数,但是类自己实现的这个函数也是一个浅拷贝。

例如,string s1("hello");string s2("world");s2=s1;

如果没有写深拷贝的话,s1会一个字节一个字节的拷贝自己的数据给s2。导致s2和s1的_str是一样的,也就是指向堆区的同一块空间。程序结束之后,会析构两个堆区的空间,而导致错误。

所以我们需要自己写一个深拷贝的赋值运算符重载。代码如下:

string& operator=(const string& s) {
    if (this != &s) //用来防止自己给自己赋值
    {
        delete[] _str;//先释放原本的空间
        _str = new char[s._capacity + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

十五、字符串比较

和库里一样,我们将比较函数写为全局函数,代码如下:

bool operator<(const string& s1, const string& s2) {
    return strcmp(s1.c_str(), s2.c_str())<0;
}
bool operator<=(const string& s1, const string& s2) {
    return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2) {
    return !(s1 < s2);
}
bool operator>=(const string& s1, const string& s2) {
    return !(s1 <= s2);
}
bool operator==(const string& s1, const string& s2) {
    return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2) {
    return !(s1 == s2);
}

在库里面的string里面提供了各种各样的比较,如下所示:

但是很多都是多余的

例如“hello"和一个s1比较,字符串“hello”会隐式类型转换为string类型的对象,也同样能调用上面我们所写的运算符重载函数。

十六、流插入运算符<<重载

在上面我们输出string都是通过cout << s4.c_str() << endl;并不能通过cout << s4<<endl;来输出,这是因为我们还没有重载流插入运算符>>,我们可以重载这个运算符,代码如下所示:

ostream& operator<<(ostream& out, const string& s) {
    for (auto ch : s) {
        out << ch;
    }
    return out;
}

这样我们就可以通过cout << s4<<endl;来输出string对象了因为我们这个函数里面没有访问私有成员,所以不用在类里面加友元声明。

十七、流提取运算符>>重载

库里的string类式可以通过cin来直接给string对象来赋值的,因为重载了流提取运算符>>。代码如下:

istream& operator >> (istream& in, string& s) {
    char ch;
    in >> ch;
    while (ch != ' ' && ch != '\n') {
        s += ch;
        in >> ch;
    }
    return in;
}

我们来测试的时候,发现有bug,这是因为cin会忽略空格和换行符,也就会提取不到空格和换行,因此这个函数就会一直执行。

所以我们就需要使用getchar函数或者get函数来一个字符一个字符读取,如下所示:

istream& operator >> (istream& in, string& s) {
    char ch;
    /*in >> ch;*/
    ch = in.get();
    while (ch != ' ' && ch != '\n') {
        s += ch;
        //in >> ch;
        ch = in.get();
    }
    return in;
}

但是我们在测试的时候还是出现了一个小bug,如下所示:

void string_test15() {
    string s1("hello");
    cin >> s1;
    cout << s1 << endl;
}

运行结果如下:

我们输入asd,他会追加到hello的后面,这是因为没有清空原本的数据。所以我们在输入之前,需要写一个clear的函数将这个string对象的内容都删除,将size设置为0;capacity的大小不变。

void clear() {
    _str[0] = '\0';
    _size = 0;
}

在string类里面增加这个函数,然后输入之前调用这个函数,代码如下所示:

istream& operator >> (istream& in, string& s) {
    s.clear();
    char ch;
    /*in >> ch;*/
    ch = in.get();
    while (ch != ' ' && ch != '\n') {
        s += ch;
        //in >> ch;
        ch = in.get();
    }
    return in;
}

运行结果如下:

17.1 优化

当我们输入的字符太多了,例如输入1000个字符a。这个函数就会调用1000次+=运算符。有可能会导致效率及低,这是因为会频繁的插入,会导致扩容,扩容的消耗是很大的。

那我们能提前开好空间嘛??这样也是不行的,因为不知道要输入多少个字符。

如果reserve(1024),可能开的太多,如果reserve(50),开的空间也可能太少。很难确定。

我们可以使用一个buff数组来存放256个字符,每次输出256个字符之后,就把这个字符串加到string里面。代码如下:

istream& operator >> (istream& in, string& s) {
    s.clear();
    const int N = 256;
    char buff[N];
    int i = 0;
    char ch;
    ch = in.get();
    while (ch != ' ' && ch != '\n') {
        buff[i++] = ch;
        if (i == N - 1) {//最后一个位置
            buff[i] = '\0';
            s += buff;
            i = 0;
        }
        ch = in.get();
    }
    if (i > 0) {//输入空格或者换行结束之后,buff里面还有数据,需要加到string里面
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

这样写的好处是:

  1. 如果输入的字符串很长,不会频繁的扩容,每插入255才会扩一次容。
  2. 如果输入的字符串很短,string开的空间也不会很大。

十八、拷贝构造函数的现代写法

我们在上面写过一个深拷贝的拷贝构造函数,那个拷贝构造函数完全是由我们自己开实现的。我们可以有其他更好的写法,代码如下所示:

string(const string& s) {
    string tmp(s._str);
    swap(_str, tmp._str);
    swap(_size, tmp._size);
    swap(_capacity, tmp._capacity);
}

我们使用s的字符串创建一个新的string类型的对象tmp。

然后依次来交换当前对象和tmp对象的每一个成员。

但是这样写是有一个小问题的,因为我们的自定义类型是不会初始化的,把当前对象的_str交换给tmp的_str。我们也不知道这个_str里面是什么内容,当这个函数结束后,会析构tmp对象,,有可能导致错误,所以我们可以在定义类成员的时候给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。如下所示:

private:
    char* _str=nullptr;
    size_t _size=0;
    size_t _capacity=0;

然后我们可以把这个交换给封装起来,如下所示:

void swap(string& s) {
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity,s._capacity);
}
string(const string& s) {
    string tmp(s._str);
    swap(tmp);
}

代码解读:当我们想用一个已经初始化的对象来创建一个新的对象时,就会调用这个拷贝构造函数,然后再使用s的字符串创建一个临时string类型的对象tmp,然后在调用上面上面所写的swap函数来交换。

十九、赋值运算符重载的现代写法

有了上面的铺垫,我们也同样可以使用上面的方法来写赋值运算符重载函数,代码如下所示:

string& operator=(const string& s) {
    if (this != &s) //用来防止自己给自己赋值,也可以不用这个判断了,传统的写法需要判断
    {
        string tmp(s._str); //这个调用有参构造函数

        //string tmp(s);     //这个调用拷贝构造函数
        swap(tmp);
    }
    return *this;
}

这样写就不用自己释放原本的资源了,他会通过swap函数把原来的string全部交换给tmp对象,等到函数结束,tmp会自动的调用析构函数。这样感觉我们什么都没有写,tmp这个临时对象给我们都弄好了。

既然我们在函数里面要再创建一个string对象,为什么不在使用形参来创建一个tmp对象呢?

下面我们来看一个更加优化的写法,如下所示:

//s2=s1
string& operator=(string tmp) {
    swap(tmp);
    return *this;
}

两行代码就搞定了赋值运算符重载函数。

二十、代码

20.1 string.h

#define _CRT_SECURE_NO_WARNINGS 0
#pragma once
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;

namespace zx 
{
	class string {
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin() 
		{
			return _str;
		}
		iterator end() {
			return _str+_size;
		}
		const_iterator begin() const 
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		//string() 
		//	:_str(new char[1] {'\0'})    //或者_str(new char('\0'))
		//	,_size(0)
		//	,_capacity(0)
		//{}
		string(const char* str="")
		{
			_size = strlen(str);
			_capacity = _size;//_capacity不包含‘\0’,因此会在开空间的时候多开一个
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		/*string(const string& s) {
			_str = new char[s._capacity+1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}*/
		/*string(const string& s) {
			string tmp(s._str);
			swap(_str, tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);
		}*/
		void swap(string& s) {
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity,s._capacity);
		}
		string(const string& s) {
			string tmp(s._str);
			swap(tmp);
		}
		//string& operator=(const string& s) {
		//	if (this != &s) //用来防止自己给自己赋值
		//	{
		//		delete[] _str;//先释放原本的空间
		//		_str = new char[s._capacity + 1];
		//		strcpy(_str, s._str);
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}
		////s2=s1
		//string& operator=(const string& s) {
		//	if (this != &s) //用来防止自己给自己赋值
		//	{
		//		string tmp(s._str);
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		//s2=s1
		string& operator=(string tmp) {
			swap(tmp);
			return *this;
		}
		~string() {
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str() const{
			return _str;
		}
		size_t size() const{
			return _size;
		}
		size_t capacity() const {
			return _capacity;
		}
		char& operator[](int pos) {
			assert(pos < _size && pos >= 0);
			return _str[pos];
		}
		const char& operator[](int pos) const{
			assert(pos < _size && pos >= 0);
			return _str[pos];
		}
		void clear() {
			_str[0] = '\0';
			_size = 0;
		}
		void reserve(size_t n);
		void pushback(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);

		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);

		string substr(size_t pos = 0, size_t len = npos);

	private:
		char* _str=nullptr;
		size_t _size=0;
		size_t _capacity=0;

		static const size_t npos;
	};
	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);
	ostream& operator<<(ostream& out, const string& s);
	istream& operator >> (istream& in, string& s);
}

20.2 string.cpp

#include"string.h"

namespace zx
{
	const size_t string::npos = -1;
	void string::reserve(size_t n) {
		if (n > _capacity) {
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void string::pushback(char ch) {
		if (_size == _capacity) {
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity) {
			//大于2倍,需要多少开多少,小于2倍按2倍扩空间。
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}
	string& string::operator+=(char ch)
	{
		pushback(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}

	void string::insert(size_t pos, char ch) {
		assert(pos >= 0 && pos <= _size);
		if (_size == _capacity) {
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size+1;
		while (end>pos) {
			_str[end] = _str[end-1];
			end--;
		}
		_str[pos] = ch;
		_size++;
	}
	void string::insert(size_t pos, const char* str) {
		assert(pos >= 0 && pos <= _size);
		size_t len = strlen(str);
		if (_size+len > _capacity) {
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size + len;
		while (end > pos + len-1) {
			_str[end] = _str[end - len];
			end--;
		}
		for (int i = 0; i < len; i++) {
			_str[pos + i] = str[i];
		}
		_size += len;
	}
	void string::erase(size_t pos, size_t len) {
		assert(pos < _size);
		if (len >= _size- pos ) {
			_size = pos;
			_str[pos] = '\0';
		}
		else {
			while (pos <= _size-len) {
				_str[pos] = _str[pos + len];
				pos++;
			}
			_size -= len;
		}
	}
	size_t string::find(char ch, size_t pos)
	{
		
		for (size_t i = pos; i < _size; i++) {
			if (_str[i] == ch) {
				return i;
			}
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos) {
		assert(pos < _size);
		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr) {
			return npos;
		}
		else {
			return ptr - _str;
		}
	}
	string string::substr(size_t pos, size_t len) {
		assert(pos < _size);
		if (len > _size-pos) {
			len = _size - pos;
		}
		string ret;
		ret.reserve(len);
		for (int i = 0; i < len; i++) {
			ret += _str[pos + i];
		}
		return ret;
	}
	bool operator<(const string& s1, const string& s2) {
		return strcmp(s1.c_str(), s2.c_str())<0;
	}
	bool operator<=(const string& s1, const string& s2) {
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const string& s1, const string& s2) {
		return !(s1 < s2);
	}
	bool operator>=(const string& s1, const string& s2) {
		return !(s1 <= s2);
	}
	bool operator==(const string& s1, const string& s2) {
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator!=(const string& s1, const string& s2) {
		return !(s1 == s2);
	}
	ostream& operator<<(ostream& out, const string& s) {
		for (auto ch : s) {
			out << ch;
		}
		return out;
	}
	//istream& operator >> (istream& in, string& s) {
	//	s.clear();
	//	char ch;
	//	/*in >> ch;*/
	//	ch = in.get();
	//	while (ch != ' ' && ch != '\n') {
	//		s += ch;
	//		//in >> ch;
	//		ch = in.get();
	//	}
	//	return in;
	//}
	istream& operator >> (istream& in, string& s) {
		s.clear();
		const int N = 256;
		char buff[N];
		int i = 0;
		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n') {
			buff[i++] = ch;
			if (i == N - 1) {//最后一个位置
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0) {//输入空格或者换行结束之后,buff里面还有数据,需要加到string里面
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

20.3 test.cpp

#include"string.h"

namespace zx {
	void string_test1() {
		zx::string s1;
		zx::string s2("hello world");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
	}
	void string_test2() {
		zx::string s1("hello world");
		for (int i = 0; i < s1.size(); i++) {
			s1[i] += 2;
		}
		cout << s1.c_str() << endl;
	}
	void string_test3() {
		zx::string s1("hello world");
		for (auto ch : s1) {
			cout << ch;
		}
		cout << endl;
	}
	void string_test4() {
		string s1("hello world");
		string::iterator it = s1.begin();
		while (it != s1.end()) {
			cout << *it;
			it++;
		}
		cout << endl;
	}
	void string_test5() {
		string s1("hello world");
		s1.pushback(' ');
		s1.pushback('$');
		cout << s1.c_str() << endl;
	}
	void string_test6() {
		string s1("hello");
		s1.append(" world$$$$$$");
		cout << s1.c_str() << endl;
	}
	void string_test7() {
		string s1("hello");
		s1 += ' ';
		s1 += '#';
		cout << s1.c_str() << endl;
	}
	void string_test8() {
		string s1("hello");
		s1 += " world$$$$$$";
		cout << s1.c_str() << endl;
	}
	void string_test9() {
		string s1("hello");
		s1.insert(1, '!');
		s1.insert(s1.size(), '@');
		s1.insert(0, '#');
		cout << s1.c_str() << endl;
	}
	void string_test10() {
		string s1("hello");
		s1.insert(1, "###");
		s1.insert(s1.size(), "###");
		s1.insert(0, "!!");
		cout << s1.c_str() << endl;
	}
	void string_test11() {
		string s1("hello");
		s1.erase(1, 2);
		cout << s1.c_str() << endl;
		string s2("hello");
		s2.erase(1);
		cout << s2.c_str() << endl;
		string s3("hello");
		s3.erase(1,100);
		cout << s3.c_str() << endl;
	}
	void string_test12() 
	{
		string s1("hello world");
		string s2 = s1.substr(6, 10);
		cout << s2.c_str() << endl;
		string s3 = s1.substr(6);
		cout << s3.c_str() << endl;
		string s4 = s1.substr(0,5);
		cout << s4.c_str() << endl;
	}
	void string_test13() {
		string s1("hello");
		string s2("helle");
		cout << (s1 < s2) << endl;
		cout << ("hello world" == "hello world") << endl;
	}
	void string_test14() {
		string s1("hello");
		cout << s1 << endl;
	}
	void string_test15() {
		string s1("hello");
		cin >> s1;
		cout << s1 << endl;
	}
	void string_test16(){
		string s1("hello world");
		string s2(s1);
		cout << s2 << endl;
	}

}
int main() {
	//zx::string_test1();
	//zx::string_test2();
	//zx::string_test3(); 
	//zx::string_test4();
	//zx::string_test5();
	//zx::string_test6();
	//zx::string_test7();
	//zx::string_test8();
	//zx::string_test9();
	//zx::string_test10();
	//zx::string_test11();
	//zx::string_test12();
	//zx::string_test13();
	//zx::string_test14();
	//zx::string_test15();
	zx::string_test16();

	return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值