C++ 中 string 类的解析及简易自我实现

目录

引言

标准库中的 string 类

功能概述

常见操作示例

自我实现简易 string 类

代码结构概述

1. String11.h 头文件

 类的成员变量

迭代器相关 

构造函数和析构函数 

 基本访问和修改方法

赋值运算符重载 

内存管理和扩容 

以下代码在.cpp文件中解析: 

 2. String11.cpp 实现文件

字符串修改操作

插入和删除操作 

 查找和截取操作

比较运算符重载 

输入输出运算符重载

3. Teststring.cpp 测试文件 

 

主要功能点

4.特色总结 

总结


引言

在 C++ 编程中,string 类是一个非常常用且重要的类,它位于标准库  中,为我们处理字符串提供了便捷的方式。不过,为了更好地理解 string 类的工作原理,我们可以尝试自己实现一个简易的 string 类。本文将详细解析 C++ 中的 string 类,并结合提供的代码文件,介绍如何自我实现一个简易的 string 类。

标准库中的 string 类

功能概述

C++ 标准库中的 string 类是一个模板类 std::basic_string 的特化版本,它封装了字符数组,提供了一系列操作字符串的方法,如字符串的拼接、查找、替换、插入、删除等。使用 string 类,我们可以避免手动管理内存,减少内存泄漏的风险。

常见操作示例

#include 
#include 

int main() {
    std::string s1 = "Hello";
    std::string s2 = " World";
    std::string s3 = s1 + s2; // 字符串拼接
    std::cout << s3 << std::endl;

    size_t pos = s3.find("World"); // 查找子字符串
    if (pos != std::string::npos) {
        std::cout << "Found at position: " << pos << std::endl;
    }

    s3.insert(5, ","); // 插入字符
    std::cout << s3 << std::endl;

    return 0;
}

自我实现简易 string 类

代码结构概述

提供的代码文件包含了三个主要部分:String11.h 头文件、String11.cpp 实现文件和 Teststring.cpp 测试文件。下面我们将详细分析每个部分。

1. String11.h 头文件

一些短小且常用的函数都可以直接在头文件内实现,调用时直接内联展开。

 类的成员变量
private:
    char* _str = nullptr;
    size_t _size;
    size_t _capacity;
    static const size_t npos;
  • _str:一个指向字符数组的指针,用于存储字符串的实际内容。
  • _size:表示当前字符串的长度,即字符串中字符的个数(不包括字符串结束符 '\0')。
  • _capacity:表示当前分配给字符串的内存容量,即 _str 指向的字符数组的大小(包括字符串结束符 '\0')。
  • npos:一个静态常量,表示无效的位置,通常用于表示查找操作未找到目标时的返回值。
迭代器相关 
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;
    }
  • 功能:定义了迭代器类型 iterator 和 const_iterator,并提供了 begin() 和 end() 方法,使得该 string 类可以像标准容器一样使用迭代器进行遍历。
  • 特点:支持范围 for 循环和其他基于迭代器的算法,增强了代码的通用性和可维护性。
构造函数和析构函数 
string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _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()
{
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
}
  • 默认构造函数:接受一个 C 风格字符串作为参数,若未提供则默认为空字符串。分配足够的内存来存储字符串,并将其复制到 _str 中。
  • 拷贝构造函数:接受一个 string 对象作为参数,创建一个新的 string 对象,其内容和容量与原对象相同。
  • 析构函数:释放 _str 指向的动态分配内存,避免内存泄漏,并将 _size 和 _capacity 置为 0。
 基本访问和修改方法
const char* c_str() const
{
    return _str;
}

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

const size_t size() const
{
    return _size;
}

const size_t capacity() const
{
    return _capacity;
}

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

const char& operator[](size_t pos) const
{
    assert(pos < _size);
    return _str[pos];
}
  • c_str():返回指向字符串实际内容的 C 风格字符串指针。
  • clear():清空字符串,将字符串的第一个字符置为 '\0',并将 _size 置为 0。
  • size():返回字符串的当前长度。
  • capacity():返回字符串当前分配的内存容量。
  • operator[]:重载了下标运算符,允许通过下标访问字符串中的字符。提供了常量和非常量版本,以支持对常量和非常量对象的访问。
赋值运算符重载 
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;
}
  • 功能:实现了对象之间的赋值操作。如果目标对象和源对象不是同一个对象,释放目标对象的原有内存,分配新的内存并复制源对象的内容。
  • 特点:使用了自我赋值检查,避免了不必要的内存释放和重新分配。
内存管理和扩容 
void reserve(size_t n)
{
    cout << "reserve:" << n << endl;
    if(n > _capacity)
    {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}
  • 功能:预留至少 n 个字符的内存空间。如果 n 大于当前容量,则分配新的内存,将原字符串复制到新内存中,并释放原内存。
  • 特点:避免了频繁的内存重新分配,提高了性能。
以下代码在.cpp文件中解析: 
void push_back(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);
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);
istream& getline(istream& in, string& s);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);

 2. String11.cpp 实现文件

先给定static变量npos值:

const size_t string::npos = -1;

 接下来实现剩下的代码:

字符串修改操作
void string::push_back(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(len + _size > _capacity)
        {
            // 大于2倍,需要多少开多少,小于2倍按2倍扩
            reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
        }
        strcpy(_str + _size, str);
        _size = len;
    }

    string& string::operator+=(char ch)
    {
        push_back(ch);
        return *this;
    }
    string& string::operator+=(const char* str)
    {
        append(str);
        return *this;
    }
  • push_back(char ch):在字符串末尾添加一个字符。如果当前容量不足,则进行扩容。
  • append(const char* str):在字符串末尾添加一个 C 风格字符串。如果需要,会进行扩容。
  • operator+=(char ch) 和 operator+=(const char* str):重载了 += 运算符,分别用于添加单个字符和 C 风格字符串,调用 push_back 和 append 方法实现。
插入和删除操作 
 void string::insert(size_t pos, char ch)
    {
        assert(pos <= _size);
        if(_size == _capacity)
        {
            reserve(_capacity == 0 ? 4 : _capacity * 2);
        }
        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 <= _size);
        size_t len = strlen(str);
        if(len + _size > _capacity)
        {
            reserve(len + _size > 2 * _capacity ? len + _size : _capacity * 2);
        }
        size_t end = _size + len;
        while(end > pos + len - 1)
        {
            _str[end] = _str[end - len];
            end--;
        }
        for(size_t i = 0; i < len; i++)
        {
            _str[pos + i] = str[i];
        }
        _size += len;
    }
    void string::erase(size_t pos, size_t len)
    {
        assert(_size > pos);
        if(len >= _size - pos) // 不能写pos + len , 因为len == npos时为INT_MAX越界
        {
            _size = pos;
            _str[pos] = '\0';
        }
        else
        {
            size_t res = pos + len;
            while(res <= _size)
            {
                _str[pos++] = _str[res++];
            }
            _size -= len;
        }
    }
  • insert(size_t pos, char ch):在指定位置 pos 插入一个字符。如果容量不足,会进行扩容。
  • insert(size_t pos, const char* str):在指定位置 pos 插入一个 C 风格字符串。同样,若需要会进行扩容。
  • erase(size_t pos, size_t len = npos):从指定位置 pos 开始删除 len 个字符。如果 len 大于等于从 pos 到字符串末尾的字符数,则删除从 pos 开始的所有字符。
 查找和截取操作
size_t string::find(char ch, size_t pos)
    {
        assert(pos < _size);
        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* res = strstr(_str + pos, str);
        if(res == nullptr)
        {
            return npos;
        }
        return res - _str;
    }

    string string::substr(size_t pos, size_t len)
    {
        assert(pos < _size);
        //len大于字符串长度,更新一下len
        if(len > _size - pos)
        {
            len = _size - pos;
        }

        string sub;
        sub.reserve(len);

        for(size_t i = 0; i < len; i++)
        {
            sub += _str[i + pos];
        }
        return sub;
    }
  • find(char ch, size_t pos = 0):从指定位置 pos 开始查找字符 ch,返回其第一次出现的位置。如果未找到,则返回 npos
  • find(const char* str, size_t pos = 0):从指定位置 pos 开始查找 C 风格字符串 str,返回其第一次出现的位置。若未找到,返回 npos
  • substr(size_t pos = 0, size_t len = npos):从指定位置 pos 开始截取长度为 len 的子字符串。如果 len 大于从 pos 到字符串末尾的字符数,则截取到字符串末尾。
比较运算符重载 
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 strcmp(s1.c_str(), s2.c_str()) <= 0;

    }
	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 对象的大小。使用 strcmp 函数进行比较。
  • 特点:使得 string 对象可以像标准类型一样进行比较,方便进行排序和条件判断。
输入输出运算符重载
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;
    //     ch = in.get();
    //     while(ch != ' ' && ch != '\n')
    //     {
    //         s += ch;
    //         ch = in.get();
    //     }
    //     return in;
    // }
    优化istream
    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[i] = '\0';
            s += buff;
        }
        return in;
    }

    istream& getline(istream& in, string& s)
    {
        s.clear();

        const int N = 256;
        char buff[N];
        int i = 0;

        char ch;
        ch = in.get();
        while(ch != '\n')
        {
            buff[i++] = ch;
            if(i == N - 1)
            {
                buff[i] = '\0';
                s += buff;
                i = 0;
            }
            ch = in.get();
        }
        if(i > 0)
        {
            buff[i] = '\0';
            s += buff;
        }
        return in;
    }
  • getline(istream& in, string& s):从输入流中读取一行字符串,直到遇到换行符 '\n',并将其存储到 s 中。
  • operator<<(ostream& out, const string& s):重载了输出运算符,将 string 对象的内容输出到输出流中。
  • operator>>(istream& in, string& s):重载了输入运算符,从输入流中读取一个单词(以空格或换行符分隔),并将其存储到 s 中。

3. Teststring.cpp 测试文件 

 

#include "String11.h"

namespace lmr
{
    void string_test()
    {
        string s("haha");
        cout << s.c_str() << endl;
        s += "sss";
        s += 's';
        cout << s.c_str() << endl;
        cout << s.size() << " " << s.capacity() <<  endl;
        s.insert(2, "sss");
        cout << s.c_str() << endl;
        s.insert(5, '*');
        cout << s.c_str() << endl;

        s.erase(2);
        cout << s.c_str() << endl;

        string s1("test.cpp");
        size_t pos = s1.find('.');
        string buffix = s1.substr(pos);
        cout << buffix.c_str() << endl;   
    }

    void string_test1()
    {
        string s("Hello World");
        string s2(s);
        bool sign = s2 == s;
        if(sign == true)
        {
            // cin >> s;
            getline(cin, s);
            cout << s << endl;
            string s3;
            cin >> s3;
            cout << s3 << endl;
        }
        cout << endl;
    }

}

int main()
{
    lmr::string_test1();
    return 0;
}
主要功能点
  • 测试用例:提供了多个测试用例,测试了字符串的拼接、插入、删除、查找、截取等操作。

4.特色总结 

  1. 动态内存管理:通过 new 和 delete 运算符动态分配和释放内存,避免了固定大小数组的限制。
  2. 模拟标准库接口:提供了与 std::string 类似的接口,如迭代器、构造函数、赋值运算符等,方便用户使用。
  3. 内存优化:使用 reserve 方法进行内存预分配,减少了频繁的内存重新分配,提高了性能。
  4. 异常处理:使用 assert 进行边界检查,确保操作的安全性。但在实际应用中,建议使用更健壮的异常处理机制。
  5. 可扩展性:可以方便地添加更多的功能,如替换、大小写转换等。

总结

通过自我实现一个简易的 string 类,我们可以更深入地理解 C++ 中 string 类的工作原理和内存管理机制。同时,我们也学习了如何使用类和对象的概念,以及如何重载运算符和使用迭代器。在实际编程中,建议使用标准库中的 string 类,因为它经过了严格的测试和优化,具有更高的性能和可靠性。

你可能感兴趣的:(c++,开发语言,string,类,基础语法)