【高级程序设计语言C++】string类的模拟

1. 浅拷贝

string类简单的来说就是一个字符数组+一个’\0’,那么在模拟实现string类的默认成员函数就要小心一点。因为如果你不显式实现默认成员函数,那么将会产生浅拷贝的问题。如下图:

img

两个字符指针指向同一块空间,当s1被析构的时候,空间也会被释放,但是s2却是不知道的,此时s2析构的时候,就没有空间析构,或者说对同一块空间析构了两次,造成了崩溃。

所以在模拟实现string类的时候,我们要自己实现拷贝构造函数和赋值重载函数。

2. 默认成员函数的实现

string类的成员变量

private:
        char* _str;
        size_t _capacity;
        size_t _size;
        const static size_t npos = -1;

其中的npos也是模拟官方库的npos。

2.1 构造函数

string(const char* str = "") {
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}

2.2 拷贝构造函数

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

2.3 赋值重载函数

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

这里利用拷贝构造函数的机制,用临时变量tmp拷贝了s的全部信息,然后再和s2交换。这里要自己实现一个swap函数。

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

2.4 析构函数

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

3. sting类的增

关于string类其实最主要的就是增删查改,那么这里讲解一下增。

void push_back(char c) {
    if (_size >= _capacity) {
        int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
        reserve(newcapacity);
    }
    _str[_size] = c;
    _size++;
    _str[_size] = '\0';
}

string& operator+=(char c) {
    push_back(c);
    return *this;
}

void append(const char* str) {
    int len = strlen(str);
    if (_size + len >= _capacity) {
        size_t newcapacity = _size + len;
        reserve(newcapacity);
    }
    strcpy(_str + _size, str);
    _size += len;
}

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

有两种方式增,一种是增加一个字符,一种是增加一个字符串。对于字符个数的增加,涉及到capacit和size的增加。

size_t size()const {
    return _size;
}

size_t capacity()const {
    return _capacity;
}

bool empty()const {
    return _size == 0;
}

void resize(size_t n, char c = '\0') {
    if (n > _size) {
        reserve(n);
        for (size_t i = _size; i < n; i++) {
            _str[i] = c;
        }
    }
    _size = n;
    _str[_size] = '\0';
}

void reserve(size_t n) {
    if (n > _capacity) {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}
string& insert(size_t pos, char c) {
    assert(pos < _size);
    if (_size >= _capacity) {
        int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
        reserve(newcapacity);
    }
    size_t end = _size;
    while (end > pos) {
        _str[end] = _str[end - 1];
        end--;
    }
    _str[pos] = c;
    _size++;
    _str[_size] = '\0';
    return *this;
}
string& insert(size_t pos, const char* str) {
    assert(pos < _size);
    int len = strlen(str);
    if (_size + len >= _capacity) {
        size_t newcapacity = _capacity == 0 ? len : _size + len;
        reserve(newcapacity);
    }
    size_t end = _size + len;
    while (end > pos + len - 1) {
        _str[end] = _str[end - len];
        end--;
    }
    strncpy(_str + pos, str, len);
    _size += len;
    return *this;
}

最主要的是用到reserve接口,resize接口一般不常用。

4. string类的删

// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len) {
    assert(pos < _size);
    if (len == -1 || pos + len >= _size) {
        _str[pos] = '\0';
        _size = pos;
    }
    else {
        strcpy(_str + pos, _str + pos + len);
        _size = len;
    }
    return *this;
}

该接口就是在pos位置,删除len个字符。

5. string类的查

// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const {
    assert(pos < _size);
    for (size_t i = pos; i < _size; i++) {
        if (_str[i] == c) {
            return i;
        }
    }
    return npos;
}

// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const {
    assert(pos < _size);
    char* tmp = strstr(_str + pos, s);
    if (tmp == nullptr) {
        return npos;
    }
    else {
        return tmp - _str;
    }
}

简单实现两个查找接口,一个是查找字符,一个是查找字符串。

6. string类的改

string类的本质是一个字符数组,可以通过下标的方式来改变。

char& operator[](size_t index) {
    assert(index < _size);
    return _str[index];
}

7. string类的比较函数

//s1 < s2
bool operator<(const string& s) {
    size_t i = 0;
    size_t j = 0;
    while (i < _size && j < s._size) {
        if (_str[i] > s._str[j]) {
            return false;
        }
        else if (_str[i] < s._str[j]) {
            return true;
        }
        i++;
        j++;
    }
    return i < _size ? true : false;
}

bool operator<=(const string& s) {
    return *this < s || *this == s;
}
//s1 > s2
// abcd abcd
// abcd abcde
// abcde abcd
bool operator>(const string& s) {
    return !(*this == s || *this < s);
}

bool operator>=(const string& s) {
    return *this > s || *this == s;

}

bool operator==(const string& s) {
    size_t i = 0;
    size_t j = 0;
    while (i < _size && j < s._size) {
        if (_str[i] != s._str[j]) {
            return false;
        }
        i++;
        j++;
    }
    return true;
}

bool operator!=(const string& s) {
    return !(*this == s);
}

以上函数可以复用,实现了 > == 就基本可以拿来复用改写其他函数了。

8. string类的流提取和流插入

ostream& operator<<(ostream& _cout, const my_string::string& s) {
    for (size_t i = 0; i < s.size(); i++) {
        _cout << s[i];
    }
    return _cout;
}

istream& operator>>(istream& _cin, string& s) {
    s.clear();
    char buffer[128] = { '\0' };
    char c = _cin.get();
    size_t i = 0;
    while (c != ' ' && c != '\n') {
        if (i == 127) {
            s += buffer;
            i = 0;
        }
        buffer[i++] = c;
        c = _cin.get();
    }
    if (i > 0) {
        buffer[i] = '\0';
        s += buffer;
    }
    return _cin;
}

我这里是把这两个函数写成了友元函数,所以参数是少了一个的。其中要注意的是,流提取函数是不在乎’\0’的,就算有也会正常打印,不会停止,和字符串是不同的

流插入函数利用了buffer字符数组,避免了s的多次扩容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值