【C++】string类的使用和模拟实现(带你深刻了解string类)

string类

在C++编程中,string类是处理字符串的核心工具,但想要真正掌握它,模拟实现是最好的方式。下面这篇文章我将以自己模拟实现的完整代码,从底层原理到核心函数的实现,带你一步步掌握string类的使用,理解它的设计思想和使用细节。

string类的基础认知

string本质上是C++对动态字符数组的封装,它不仅封装了字符数组的储存(char *),还管理了其容量(capasity)和大小(size),并提供了丰富的接口(增删改查,比较等)。

  1. 自动管理内存,无需手动分配 / 释放(避免内存泄漏、越界);
  2. 支持动态扩容,长度可灵活调整;
  3. 重载了 +=、==、+等运算符,语法更直观;
  4. 提供大量成员函数,覆盖字符串增删改查、拼接、比较等场景。

为什么要学习string类?

很多初学者会疑惑:为什么不直接用 C 语言的str系列函数?核心原因有两点:

  1. c语言中str库函数中的函数,虽然提供对字符串的修改操作,但是无法检测越界访问,容易产生越界修改并不自知。
  2. 而C++string类重载了[ ],,越界访问时会直接终止程序并提示出现错误的位置,从根源上避免了越界访问的问题。

string类的使用

1. 头文件与命名空间

使用string前必须包含头文件,且由于string位于std命名空间,有两种使用方式:

cpp 复制代码
// 方式1:引入头文件+使用std命名空间(新手推荐)
#include <iostream>
#include <string>
using namespace std;

// 方式2:显式指定std::(工程开发更规范)
#include <iostream>
#include <string>
// 使用时写 std::string

2.字符串的创建与初始化

string支持多种初始化方式

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 1. 空字符串
    string s1;
    // 2. 用常量字符串初始化
    string s2 = "Hello C++";
    // 3. 拷贝初始化
    string s3 = s2;
    // 4. 用指定字符重复n次初始化
    string s4(5, 'a'); // s4 = "aaaaa"
    // 5. 截取其他字符串的部分初始化(起始位置,长度)
    string s5(s2, 6, 3); // 从s2的第6位(索引从0开始)取3个字符,s5 = "C++"
    
    cout << "s1: " << s1 << endl; // 空
    cout << "s2: " << s2 << endl; // Hello C++
    cout << "s3: " << s3 << endl; // Hello C++
    cout << "s4: " << s4 << endl; // aaaaa
    cout << "s5: " << s5 << endl; // C++
    return 0;
}

3.获取长度与判空

  • size()/length():返回字符串长度(字符数,两者功能完全一致);
  • empty():判断字符串是否为空,空返回true,非空返回false;
  • capacity():返回字符串当前分配的内存容量(字节数,一般无需关注)。
cpp 复制代码
string s = "Hello";
cout << "长度:" << s.size() << endl;    // 输出5
cout << "是否为空:" << s.empty() << endl; // 输出0(false)

s.clear(); // 清空字符串
cout << "清空后是否为空:" << s.empty() << endl; // 输出1(true)

4.字符串拼接

支持+=运算符和append()成员函数,前者更简洁,后者支持更灵活的拼接(如截取部分字符串拼接)。

cpp 复制代码
string s1 = "Hello";
string s2 = " World";

// 方式1:+= 运算符(推荐)
s1 += s2; 
cout << s1 << endl; // 输出 Hello World

// 方式2:append() 函数
string s3 = "Hi";
s3.append(" C++", 0, 3); // 拼接" C++"的前3个字符(" C+")
cout << s3 << endl; // 输出 Hi C+

5.字符串比较

支持==、!=、<、>、<=、>=运算符,按ASCII 码值逐字符比较;也可使用compare()成员函数,返回值:
0 :两字符串相等;
正数 :当前字符串大于目标字符串;
负数:当前字符串小于目标字符串。

cpp 复制代码
string s1 = "Apple";
string s2 = "Banana";
string s3 = "Apple";

cout << (s1 == s3) << endl; // 输出1(true)
cout << (s1 > s2) << endl;  // 输出0(false,'A'的ASCII码小于'B')

// compare() 用法
int res = s1.compare(s2);
cout << res << endl; // 输出负数(s1 < s2)

6.字符访问

  • 下标运算符[]:直接访问指定位置的字符(无越界检查,越界会导致未定义行为);
  • at()成员函数:访问指定位置的字符(有越界检查,越界抛出out_of_range异常,更安全)。
cpp 复制代码
string s = "Hello";
cout << s[1] << endl; // 输出e
cout << s.at(2) << endl; // 输出l

// s[10]; // 越界,程序崩溃(无提示)
// s.at(10); // 越界,抛出std::out_of_range异常(可捕获)

7.查找与替换

  • find():从左到右查找指定字符 / 字符串,返回首次出现的索引; 未找到返回string::npos(一个特殊的无符号常量,表示 "不存在");
  • rfind():从右到左查找,返回最后一次出现的索引;
  • replace():替换字符串中指定范围的字符。
cpp 复制代码
string s = "Hello World, Hello C++";

// 查找 "Hello" 首次出现的位置
size_t pos = s.find("Hello");
if (pos != string::npos) {
    cout << "首次出现位置:" << pos << endl; // 输出0
}

// 从索引7开始查找 "Hello"
pos = s.find("Hello", 7);
if (pos != string::npos) {
    cout << "第二次出现位置:" << pos << endl; // 输出13
}

// 替换:从索引0开始,5个字符替换为"Hi"
s.replace(0, 5, "Hi");
cout << s << endl; // 输出 Hi World, Hello C++

8.截取子串

substr(pos, len):从索引pos开始,截取len个字符;若len省略,则截取到字符串末尾。

cpp 复制代码
string s = "Hello World";
string sub = s.substr(6, 5); // 从索引6开始截取5个字符
cout << sub << endl; // 输出 World

string sub2 = s.substr(0, 5); // 截取前5个字符
cout << sub2 << endl; // 输出 Hello

9.插入与删除

  • insert(pos, str):在索引pos处插入字符串str;
  • erase(pos,len):从索引pos处删除len个字符;若len省略,删除到末尾。
cpp 复制代码
string s = "Hello";

// 插入
s.insert(5, " C++"); // 在索引5处插入" C++"
cout << s << endl; // 输出 Hello C++

// 删除
s.erase(5, 4); // 从索引5处删除4个字符(" C++")
cout << s << endl; // 输出 Hello

10.与 C 语言字符数组的转换

有时需要将string转换为 C 风格字符数组(如调用 C 语言接口),可使用:

  • c_str():返回const char*(只读,不能修改);
  • data():C++11 后与c_str()功能一致,返回const char*;
  • copy():将string内容拷贝到字符数组中(可修改)。
cpp 复制代码
string s = "Hello";

// 转换为const char*
const char* c_str = s.c_str();
cout << c_str << endl; // 输出 Hello

// 拷贝到字符数组(可修改)
char arr[10] = {0};
s.copy(arr, 5, 0); // 从s的索引0开始,拷贝5个字符到arr
cout << arr << endl; // 输出 Hello

11.容量管理

reserve(size_t n)

请求将字符串容量调整为计划大小,如果n大于当前字符串容量,该函数会使其容量增加至n(或更大),如果n小于当前字符串容量,字符串容量不会改变。仅扩容,不缩容

此函数对字符串长度没有任何影响,改变的是capacity,不会改变size。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "Hello";
    cout << "初始状态:" << endl;
    cout << "size: " << s.size() << ", capacity: " << s.capacity() << endl;
    // 输出:size: 5, capacity: 15

    // 预留能容纳100个字符的空间
    s.reserve(100);
    cout << "\nreserve(100)后:" << endl;
    cout << "size: " << s.size() << ", capacity: " << s.capacity() << endl;
    // 输出:size: 5, capacity: 111(编译器可能预留略多,保证≥100)

    // 预留更小的容量,无效果
    s.reserve(50);
    cout << "\nreserve(50)后:" << endl;
    cout << "size: " << s.size() << ", capacity: " << s.capacity() << endl;
    // 输出:size: 5, capacity: 111

    return 0;
}

resize(size_t n , char c = '\0' )

将字符串的长度调整为n个字符的长度

如果n大于当前字符串长度,则扩容到n个字符,大于n的部分用c来初始化;

如果n小于当前字符串长度,则保留前n个字符,删除其余字符。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "Hello";
    cout << "初始状态:" << endl;
    cout << "size: " << s.size() << ", capacity: " << s.capacity() << ", 内容:" << s << endl;
    // 输出:size: 5, capacity: 15(不同编译器capacity值可能不同), 内容:Hello

    // 情况1:n < 当前size → 截断
    s.resize(3);
    cout << "\nresize(3)后:" << endl;
    cout << "size: " << s.size() << ", capacity: " << s.capacity() << ", 内容:" << s << endl;
    // 输出:size: 3, capacity: 15, 内容:Hel

    // 情况2:n > 当前size → 扩展,用指定字符填充
    s.resize(6, '!');
    cout << "\nresize(6, '!')后:" << endl;
    cout << "size: " << s.size() << ", capacity: " << s.capacity() << ", 内容:" << s << endl;
    // 输出:size: 6, capacity: 15, 内容:Hel!!!

    return 0;
}

string类的完整模拟实现代码

1.头文件string.h

cpp 复制代码
#include<iostream>
#include<assert.h>
using namespace std;

namespace Mystring
{
    class string
    {
        public:
            //迭代器定义
            typedef char* iterator;
            typedef const char* const_iterator;
            //运算符重载
            char& operator[](size_t index);
            const char& operator[](size_t index)const;
            //迭代器相关
            iterator begin()
            {
                return _str;
            }

            iterator end()
            {
                return _str + _size;
            }

            const_iterator begin() const
            {
                return _str;
            }

            const_iterator end() const
            {
                return _str + _size;
            }
            
            //构造函数
            string(const char* str = "")
            {
                _size = strlen(str);
                _capacity = _size;
                _str = new char [_capacity + 1];//+1储存'\0'

                strcpy(_str, str);
            }

            //拷贝构造(现代写法:利用临时对象交换)
            string(const string& s)
            {
                string tmp(s._str);//创建临时对象
                swap(tmp);//交换临时对象和当前对象的资源
            }

            //赋值运算符重载(值传递+交换)
            string& operator=(string tmp)
            {
                swap(tmp);
                return *this;
            }
           
            //析构函数
            ~string()
            {
                if (_str)
                {
                    delete[] _str;
                    _size = 0;
                    _capacity = 0;
                    _str = nullptr;//避免野指针
                }
            }


            //基础工具函数
            void clear()//清空字符串
            {
                _str[0] = '\0';
                _size = 0;
            }

            void swap(string& s)//交换两个string对象的资源
            {
                std::swap(_str, s._str);
                std::swap(_capacity, s._capacity);
                std::swap(_size, s._size);
            }

            const char* str()const
            {
                return _str;
            }

            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');// resize:调整字符串长度
            void reserve(size_t n);//reserve:调整容量(仅扩容,不缩容)
             
            //增删改查
            void push_back(char c);//尾插一个字符

            void append(const char* str);//追加一个字符串
            
            // 返回c在string中第一次出现的位置
            size_t find(char c, size_t pos=0) const;

            // 返回子串s在string中第一次出现的位置
            size_t find(const char* s, size_t pos=0) const;

            // 在pos位置上插入字符c/字符串str,并返回该字符的位置
            string& insert(size_t pos, char c);

            string& insert(size_t pos, const char* str);

            // 删除pos位置上的元素,并返回该元素的下一个位置
            string& erase(size_t pos, size_t len = npos);

            string& operator+=(char ch);

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

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

        private:
            size_t _size = 0;//有效字符长度(不包含'\0')
            size_t _capacity=0;//容量大小(不包含'\0')
            char* _str = nullptr;//储存字符串的动态数组
            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);
}
  1. 函数实现(string.cpp)
cpp 复制代码
#include "string.h"

namespace Mystring
{ 
    // 静态常量初始化
    const size_t string::npos = -1;
     
    //resize:调整字符串长度
    void string::resize(size_t n, char c )
    {
        if (n < _size)//如果n小于当前字符串长度,则保留前n个字符,删第n个字符之外的字符
        {
            _str[n] = '\0';
            _size = n; 
        }
        else//如果n大于当前字符串长度,则扩容到n个字符,大于_size的部分用字符c来初始化
        {
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);
            _capacity = n;
            delete[]_str;
            _str = tmp;
            for (size_t i = _size; i < n; i++)
            {
                _str[i] = c;
            }
            _size = n;
            _str[n] = '\0';
        }
    }

    //调整容量,仅扩容不缩容
    void string::reserve(size_t n)
    {
        if (n > _capacity)
        {
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
            _capacity = n;
        }
    }


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

    // 查找子串s在string中第一次出现的位置
    size_t string::find(const char* s, size_t pos) const
    {
        assert(pos < _size);

        char* tmp = strstr(_str + pos, s);

        if (tmp == nullptr)
        {
            return npos;
        }
        else
        {
            return tmp - _str;
        }

    }
    
    //尾插字符c
    void string::push_back(char c)
    {
        if (_size == _capacity)
        {
            reserve(_capacity == 0 ? 4 : 2 * _capacity);
        }

        _str[_size] = c;
        _size++;
        _str[_size] = '\0';

    }
     
    //尾插字符串str
    void string::append(const char* str)
    {
        size_t len = strlen(str);
        if (_size + len > _capacity)
        {
            //大于两倍,需要多少开多少,小于两倍,扩容至两倍
            reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
        }
        strcpy(_str + _size, str);
        _size += len;
    }


    // 在pos位置上插入字符c/字符串str,并返回该字符的位置
    string& string::insert(size_t pos, char c)
    {
        assert(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] = c;
        _size++;
        return *this;
    }

    //指定位置插入字符串str
    string& string::insert(size_t pos, const char* str)
    {
        assert(pos <= _size);
        size_t len = strlen(str);
        if (_size + len > _capacity)
        {
            reserve(_size + len > 2 * _capacity? _size + len: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;
        return *this;
    }


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

            _size -= len;
        }
    
        return *this;
    }

    //截取子串
    string string::substr(size_t pos,size_t len)
    {
        assert(pos < _size);
        if (len > _size - pos)
        {
            len = _size - pos;
        }

        string sub;
        sub.reserve(len);
        for (int i = 0; i < len; i++)
        {
            sub += _str[pos + i];
        }

        return sub;
    }
     
    //+=字符ch 
    string& string::operator+=(char ch)
    {
        push_back(ch); 
        return *this;
    }

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

    //重载运算符[]
    char& string::operator[](size_t index)
    {
        assert(index < _size);
        return _str[index];
    }

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

    bool operator<(const string& s1, const string& s2)
    {
        return strcmp(s1.str(), s2.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 || s1 == s2;
    }
    bool operator==(const string& s1, const string& s2)
    {
        return strcmp(s1.str(), s2.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();// 清空原有内容
        const int N = 256;
        char buff[N];

        char ch;
        int i = 0;

        ch = in.get();
	    // 读取字符,直到空格/换行
        while (ch != ' ' && ch != '\n')
        {
            buff[i++] = ch;
            // 缓冲区满时追加到string
            if (i == N - 1)
            {
                buff[i] = '\0';
                s += buff;
                i = 0;
            }

            ch = in.get();
        }
        // 追加剩余字符
        if (i > 0)
        {
            buff[i] = '\0';
            s += buff;
        }
        return in;
    }
}

3.测试示例

cpp 复制代码
#include "string.h"
using namespace Mystring;

int main()
{
    // 构造函数测试
    string s1("hello");
    cout << "s1: " << s1 << endl; // 输出:hello

    // 拷贝构造测试
    string s2 = s1;
    cout << "s2: " << s2 << endl; // 输出:hello

    // reserve测试
    s1.reserve(10);
    cout << "s1 capacity: " << s1.capacity() << endl; // 输出:10

    // resize测试
    s1.resize(8, 'x');
    cout << "s1: " << s1 << endl; // 输出:helloxxx

    // 尾插测试
    s1.push_back('!');
    cout << "s1: " << s1 << endl; // 输出:helloxxx!

    // 查找测试
    size_t pos = s1.find('x');
    cout << "x的位置:" << pos << endl; // 输出:5

    // 插入测试
    s1.insert(5, "yy");
    cout << "s1: " << s1 << endl; // 输出:helloyyxxx!

    // 删除测试
    s1.erase(5, 2);
    cout << "s1: " << s1 << endl; // 输出:helloxxx!

    // 比较运算符测试
    cout << (s1 == s2 ? "相等" : "不相等") << endl; // 输出:不相等

    // 输入测试
    string s3;
    cin >> s3;
    cout << "输入的s3:" << s3 << endl;

    return 0;
}

掌握string类的使用,能大幅提升 C++ 字符串处理的效率和代码可读性,是 C++ 入门的必备技能。建议结合实际场景多练习,比如字符串分割、格式化、替换等,加深对string类的理解。

相关推荐
ZHOUPUYU4 小时前
PHP 8.3网关优化:我用JIT将QPS提升300%的真实踩坑录
开发语言·php
寻寻觅觅☆8 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc8 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
l1t8 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿9 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar1239 小时前
C++使用format
开发语言·c++·算法
码说AI10 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS10 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
lanhuazui1010 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee4410 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索