目录

string类的模拟实现

string类的模拟实现

string类各函数接口总览

namespace cl
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");         //构造函数
		string(const string& s);              //拷贝构造函数
		string& operator=(const string& s);   //赋值运算符重载函数
		~string();                            //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size();
		size_t capacity();
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		bool empty()const;

		//修改字符串相关函数
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len);
		void clear();
		void swap(string& s);
		const char* c_str()const;

		//访问字符串相关函数
		char& operator[](size_t i);
		const char& operator[](size_t i)const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;
		size_t rfind(char ch, size_t pos = npos)const;
		size_t rfind(const char* str, size_t pos = 0)const;

		//关系运算符重载函数
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		size_t _capacity; //记录字符串当前的容量
		static const size_t npos; //静态成员变量(整型最大值)
	};
	const size_t string::npos = -1;

	//<<和>>运算符重载函数
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
	istream& getline(istream& in, string& s);
}

默认成员函数

构造函数,用于初始化string对象

参数str默认为空字符串"",这样可以在不传入参数时创建一个空的string对象。

如果传入了有效的C风格字符串,则根据该字符串来初始化string对象。

 string(const char* str = "") {
            // 如果str为空指针,创建一个空字符串。
            // 此时_size设为0表示字符串长度为0,_capacity也设为0表示当前容量为0,
            // 分配一个大小为1的字符数组用于存储结束符'\0',并将其赋值给_str。
            if (str == nullptr) {
                _size = 0;
                _capacity = 0;
                _str = new char[1];
                _str[0] = '\0';
            }
            // 如果str不为空,计算其长度并赋值给_size,_capacity初始化为与_size相同。
            // 分配一个大小为_strlen + 1的字符数组(包含结束符'\0'),并将str的内容复制到该数组中。
            else {
                _size = strlen(str);
                _capacity = _size;
                _str = new char[_capacity + 1];
                strcpy(_str, str);
            }
        }

拷贝构造函数

  • 用于从另一个string对象创建新的string对象
  • 采用深拷贝,目的是确保新对象和原对象相互独立,修改新对象不会影响原对象,反之亦然。
  • 传统写法:先为新对象分配与原对象容量相同加1(包含结束符’\0’)的内存空间,再将原对象的字符串内容复制到新分配的内存中,最后更新新对象的_size和_capacity。
 string(const string& s)
            : _str(new char[s._capacity + 1])
            , _size(0)
            , _capacity(0) {
            strcpy(_str, s._str);
            _size = s._size;
            _capacity = s._capacity;
        }

赋值运算符重载函数

用于将一个string对象赋值给另一个string对象

 // 传统写法:先检查是否为自赋值(即当前对象和要赋值的对象是否为同一个对象),
        // 如果不是自赋值,先释放原对象的内存,然后分配与赋值对象容量相同加1的内存空间,
        // 再将赋值对象的字符串内容复制到新分配的内存中,最后更新当前对象的_size和_capacity。
        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;
        }

        // 现代写法:利用值传递的特性,让编译器调用拷贝构造函数创建临时对象
        // 然后通过交换函数将临时对象和当前对象的内容交换,实现赋值操作。
        // 这样写的好处是代码更简洁,并且避免了传统写法中复杂的内存管理操作。
        string& operator=(string s) {
            swap(s);
            return *this;
        }

析构函数

  • 用于释放string对象占用的内存
  • 释放_str指向的堆区内存,防止内存泄漏,然后将_str置为nullptr,
  • 同时将_size和_capacity置为0,以表示对象已被销毁。
 ~string() {
            delete[] _str;
            _str = nullptr;
            _size = 0;
            _capacity = 0;
        }

迭代器相关函数

  • 返回指向字符串起始和结束位置的迭代器
  • begin函数返回指向字符串首字符的迭代器,这样可以方便地从字符串开头开始遍历。
iterator begin() {
            return _str;
        }

        // 用于const对象的begin函数,返回指向字符串首字符的const迭代器
        // 这样可以保证在不修改字符串的情况下对其进行遍历操作。
        const_iterator begin() const {
            return _str;
        }

        // end函数返回指向字符串末尾字符的下一个位置的迭代器,
        // 用于标识字符串的结束,配合begin函数实现完整的遍历。
        iterator end() {
            return _str + _size;
        }

        // 用于const对象的end函数,返回指向字符串末尾字符的下一个位置的const迭代器
        // 同样是为了保证在不修改字符串的情况下进行遍历。
        const_iterator end() const {
            return _str + _size;
        }

容量和大小相关函数

size()

返回字符串的有效字符个数(不包括’\0’),通过访问_size成员变量实现。

size_t size() const {
            return _size;
        }

capacity()

返回字符串当前已分配的内存容量(不包括’\0’的空间),通过访问_capacity成员变量实现。

size_t capacity() const {
            return _capacity;
        }

reserve()

  • 调整字符串的容量为至少n个字符的空间
  • 如果n大于当前容量,则重新分配内存并复制原字符串内容,以满足新的容量需求。
  • 如果n小于等于当前容量,则不进行任何操作,因为当前容量已经足够。
 void reserve(size_t n) {
            if (n > _capacity) {
                char* new_str = new char[n + 1];
                strcpy(new_str, _str);
                delete[] _str;
                _str = new_str;
                _capacity = n;
            }
        }

resize()

  1. 调整字符串的大小为n个字符
  2. 如果n小于当前大小,则截断字符串,将_size设置为n,并在新的末尾位置添加结束符’\0’。
  3. 如果n大于当前大小,且n大于当前容量,则先调整容量,再用指定字符ch填充新增的空间。
  4. 如果n大于当前大小,但n小于等于当前容量,则直接用指定字符ch填充新增的空间。
 void resize(size_t n, char ch = '\0') {
            if (n < _size) {
                _size = n;
                _str[_size] = '\0';
            }
            else {
                if (n > _capacity) {
                    reserve(n);
                }
                for (size_t i = _size; i < n; ++i) {
                    _str[i] = ch;
                }
                _size = n;
                _str[_size] = '\0';
            }
        }

empty()

判断字符串是否为空,即字符串的有效字符个数是否为0,通过比较_size是否为0来实现。

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

修改字符串相关函数

push_back

第一种

 // 在字符串末尾添加一个字符ch
        // 首先检查当前容量是否足够,如果当前_size等于_capacity,说明容量已满,
        // 此时进行扩容操作,扩容策略为:若当前容量为0则初始化为4,否则扩容为原来的2倍。
        // 然后将字符ch添加到字符串末尾,并更新_size和添加结束符'\0'。
        void push_back(char ch) {
            if (_size == _capacity) {
                reserve(_capacity == 0? 4 : _capacity * 2);
            }
            _str[_size] = ch;
            _str[_size + 1] = '\0';
            ++_size;
        }

第二种

 // 复用insert函数在字符串末尾插入字符,效果与push_back相同
        // 这样做的好处是可以减少代码重复,提高代码的复用性。
        void push_back(char ch) {
            insert(_size, ch);
        }

append

  • 在字符串末尾添加一个C风格字符串str
  • 先计算添加后的总长度,如果总长度超过当前容量,则进行扩容,
  • 然后将str复制到字符串末尾,并更新_size,以反映新的字符串长度。
 void append(const char* str) {
            size_t new_size = _size + strlen(str);
            if (new_size > _capacity) {
                reserve(new_size);
            }
            strcpy(_str + _size, str);
            _size = new_size;
        }

**复用insert函数在字符串末尾插入字符串,效果与append相同

同样是为了提高代码的复用性,减少重复代码。**

void append(const char* str) {
            insert(_size, str);
        }

重载+=运算符

重载+=运算符,用于在字符串末尾添加一个字符ch

调用push_back函数实现字符的添加,并返回当前对象的引用,

这样可以支持连续的+=操作,例如s += ‘a’ += ‘b’;。

 string& operator+=(char ch) {
            push_back(ch);
            return *this;
        }
// 重载+=运算符,用于在字符串末尾添加一个C风格字符串str
        // 调用append函数实现字符串的添加,并返回当前对象的引用,
        // 同样支持连续的+=操作,如s += "abc" += "def";。
        string& operator+=(const char* str) {
            append(str);
            return *this;
        }

insert

  • 在字符串的指定位置pos插入一个字符ch
  • 先检查位置pos的合法性,如果pos大于_size则抛出异常,因为位置超出了字符串的范围。
  • 再检查当前容量是否足够,不够则进行扩容,扩容策略与push_back中相同。
  • 然后将pos及之后的字符向后移动一位,为插入字符ch腾出位置,插入字符ch,并更新_size。
  string& insert(size_t pos, char ch) {
            if (pos > _size) {
                throw std::out_of_range("Invalid position");
            }
            if (_size == _capacity) {
                reserve(_capacity == 0? 4 : _capacity * 2);
            }
            for (size_t i = _size; i > pos; --i) {
                _str[i] = _str[i - 1];
            }
            _str[pos] = ch;
            ++_size;
            return *this;
        }

在字符串的指定位置pos插入一个C风格字符串str

先检查位置pos的合法性,如果pos大于_size则抛出异常,因为位置超出了字符串的范围。

计算插入后的总长度,若超过当前容量则进行扩容,

将pos及之后的字符向后移动str的长度位,为插入字符串str腾出位置,插入字符串str,并更新_size。

 string& insert(size_t pos, const char* str) {
            if (pos > _size) {
                throw std::out_of_range("Invalid position");
            }
            size_t len = strlen(str);
            if (_size + len > _capacity) {
                reserve(_size + len);
            }
            for (size_t i = _size; i >= pos; --i) {
                _str[i + len] = _str[i];
            }
            strncpy(_str + pos, str, len);
            _size += len;
            return *this;
        }

erase

删除从位置pos开始的len个字符

先检查位置pos的合法性,如果pos大于等于_size则抛出异常,因为位置超出了字符串的范围。

如果len大于等于剩余字符数,则直接截断字符串,将_size设置为pos,并在新的末尾位置添加结束符’\0’。

否则将pos + len之后的字符复制到pos位置,并更新_size,以反映删除后的字符串长度。

    string& erase(size_t pos, size_t len) {
            if (pos >= _size) {
                throw std::out_of_range("Invalid position");
            }
            if (len >= _size - pos) {
                _size = pos;
                _str[_size] = '\0';
            }
            else {
                strcpy(_str + pos, _str + pos + len);
                _size -= len;
            }
            return *this;
        }

clear

// 清空字符串,将_size设置为0,并在字符串开头添加结束符'\0',
        // 这样就相当于清空了字符串的内容。
        void clear() {
            _size = 0;
            _str[0] = '\0';
        }

swap

交换两个string对象的内容

使用标准库的swap函数交换两个对象的_str、_size和_capacity成员,

这样可以高效地实现两个对象内容的交换,并且不会涉及复杂的内存分配和复制操作。

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

c_str

 // 返回一个指向字符串的C风格字符串(以'\0'结尾)
        // 用于与需要C风格字符串的函数进行交互,例如一些C标准库函数。
        const char* c_str() const {
            return _str;
        }

访问字符串相关函数

重载[]运算符,用于通过下标访问字符串中的字符

非const版本 ,返回字符的引用,允许对字符进行修改,

先检查下标i的合法性,如果i大于等于_size则抛出异常,因为下标超出了字符串的范围,

否则返回_str[i]的引用。

 char& operator[](size_t i) {
            if (i >= _size) {
                throw std::out_of_range("Invalid index");
            }
            return _str[i];
        }

const版本operator[]

重载[]运算符,用于通过下标访问字符串中的字符

const版本,返回字符的const引用,不允许对字符进行修改,

先检查下标i的合法性,如果i大于等于_size则抛出异常,因为下标超出了字符串的范围,

否则返回_str[i]的const引用。

        const char& operator[](size_t i) const {
            if (i >= _size) {
                throw std::out_of_range("Invalid index");
            }
            return _str[i];
        }

从位置pos开始查找字符ch在字符串中第一次出现的位置

如果找到则返回该位置的下标,否则返回npos(定义为-1),表示未找到。

先检查位置pos的合法性,如果pos大于等于_size则抛出异常,因为位置超出了字符串的范围,

然后从pos位置开始遍历字符串,找到字符ch则返回其位置,遍历结束未找到则返回npos。

        size_t find(char ch, size_t pos = 0) const {
            if (pos >= _size) {
                throw std::out_of_range("Invalid position");
            }
            for (size_t i = pos; i < _size; ++i) {
                if (_str[i] == ch) {
                    return i;
                }
            }
            return npos;
        }
 size_t find(const char* str, size_t pos = 0) const {
            if (pos >= _size) {
                throw std::out_of_range("Invalid position");
            }
            const char* result = strstr(_str + pos, str);
            if (result) {
                return result - _str;
            }
            return npos;
        }

从位置pos开始反向查找字符ch在字符串中最后一次出现的位置

如果找到则返回该位置的下标,否则返回npos(定义为-1),表示未找到。

先检查位置pos的合法性,如果pos大于等于_size则将pos设为_size - 1,

然后将字符串逆置,调用正向查找函数,再将结果转换为原字符串的位置。

        size_t rfind(char ch, size_t pos = npos) const {
            if (pos >= _size) {
                pos = _size - 1;
            }
            string tmp(*this);
            std::reverse(tmp.begin(), tmp.end());
            size_t rev_pos = _size - 1 - pos;
            size_t ret = tmp.find(ch, rev_pos);
            if (ret != npos) {
                return _size - 1 - ret;
            }
            return npos;
        }

关系运算符重载函数

解释:重载的输入运算符»从输入流in中读取字符,直到遇到空格或换行符,

将读取的字符逐个添加到字符串s中,实现字符串的输入功能。

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

解释:重载的输出运算符«将字符串s的C风格字符串(通过c_str函数获取)输出到输出流out中,实现字符串的输出功能。

 std::ostream& operator<<(std::ostream& out, const string& s) {
        out << s.c_str();
        return out;
    }

getline

getline函数用于读取一行含有空格的字符串。实现时于»运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符。

//读取一行含有空格的字符串
istream& getline(istream& in, string& s)
{
	s.clear(); //清空字符串
	char ch = in.get(); //读取一个字符
	while (ch != '\n') //当读取到的字符不是'\n'的时候继续读取
	{
		s += ch; //将读取到的字符尾插到字符串后面
		ch = in.get(); //继续读取字符
	}
	return in;
}

注:为了防止与标准库当中的string类产生命名冲突,模拟实现时需放在自己的命名空间当中。

测试代码

#include <iostream>
#include <cstring>
#include <stdexcept>

// 上面的 my_ns::string 类定义代码

int main() {
    my_ns::string s1("Hello");
    my_ns::string s2(" World");

    // 测试 += 运算符
    s1 += s2;
    std::cout << "s1 after += s2: " << s1 << std::endl;

    // 测试 insert 函数
    s1.insert(5, ",");
    std::cout << "s1 after insert: " << s1 << std::endl;

    // 测试 erase 函数
    s1.erase(5, 1);
    std::cout << "s1 after erase: " << s1 << std::endl;

    // 测试 find 函数
    size_t pos = s1.find("World");
    if (pos != std::string::npos) {
        std::cout << "Found 'World' at position: " << pos << std::endl;
    } else {
        std::cout << "Did not find 'World'" << std::endl;
    }

    return 0;
}