de风——【从零开始学C++】(八):string的模拟实现

目录

[📚 前言](#📚 前言)

[(一)经典的 string 类问题](#(一)经典的 string 类问题)

[1. 浅拷贝](#1. 浅拷贝)

简介作用

代码例子:浅拷贝的问题演示

[⚠️ 经典 Bug 和问题](#⚠️ 经典 Bug 和问题)

[2. 深拷贝](#2. 深拷贝)

简介作用

代码例子:深拷贝的正确实现

[⚠️ 经典 Bug 和问题](#⚠️ 经典 Bug 和问题)

(二)常用接口的实现

[1. size () 和 capacity ()](#1. size () 和 capacity ())

简介作用

[代码例子:size 和 capacity 的实现](#代码例子:size 和 capacity 的实现)

[2. operator[]](#2. operator[])

简介作用

[代码例子:operator [] 的实现](#代码例子:operator [] 的实现)

[⚠️ 经典 Bug 和问题](#⚠️ 经典 Bug 和问题)

[3. c_str()](#3. c_str())

简介作用

[代码例子:c_str 的实现](#代码例子:c_str 的实现)

[4. reserve()](#4. reserve())

简介作用

[代码例子:reserve 的实现](#代码例子:reserve 的实现)

[5. 构造函数](#5. 构造函数)

简介作用

代码例子:各种构造函数的实现

[⚠️ 经典 Bug 和问题](#⚠️ 经典 Bug 和问题)

[6. 析构函数](#6. 析构函数)

简介作用

代码例子:析构函数的实现

[7. clear()](#7. clear())

简介作用

[代码例子:clear 的实现](#代码例子:clear 的实现)

[8. 迭代器的简单模拟](#8. 迭代器的简单模拟)

简介作用

代码例子:迭代器的实现

(三)其他接口的实现

[1. push_back()](#1. push_back())

简介作用

[代码例子:push_back 的实现](#代码例子:push_back 的实现)

[⚠️ 经典 Bug 和问题](#⚠️ 经典 Bug 和问题)

[2. append()](#2. append())

简介作用

[代码例子:append 的实现](#代码例子:append 的实现)

[3. operator+=](#3. operator+=)

简介作用

[代码例子:operator+= 的实现](#代码例子:operator+= 的实现)

[4. insert()](#4. insert())

简介作用

[代码例子:insert 的实现](#代码例子:insert 的实现)

[⚠️ 经典 Bug 和问题](#⚠️ 经典 Bug 和问题)

[5. erase()](#5. erase())

简介作用

[代码例子:erase 的实现](#代码例子:erase 的实现)

[6. find()](#6. find())

简介作用

[代码例子:find 的实现](#代码例子:find 的实现)

[7. substr()](#7. substr())

简介作用

[代码例子:substr 的实现](#代码例子:substr 的实现)

[8. 六个比较运算符](#8. 六个比较运算符)

简介作用

代码例子:比较运算符的实现

[9. 流输入 流提取](#9. 流输入 流提取)

简介作用

代码例子:流运算符的实现

[🎯 面试重点总结](#🎯 面试重点总结)

[必背考点(90% 概率考)](#必背考点(90% 概率考))

常见坑点提醒

总结啦~



📚 前言

大家好!欢迎来到【从零开始学 C++】专栏的第八篇文章!今天我们要干一件大事 ------手撕 string 类

为什么要模拟实现 string?因为:

  • 面试必考题:90% 的 C++ 面试都会考 string 的模拟实现

  • 理解深浅拷贝:这是 C++ 内存管理的核心知识点

  • 掌握类的设计:学习如何设计一个完整的类

💡 新手提示:本文所有代码都可以直接运行,建议边看边敲代码!


(一)经典的 string 类问题

1. 浅拷贝

简介作用

浅拷贝是什么? 大白话讲就是:两个对象共用同一块内存

就像两个人共用同一个钱包,一个人花了钱,另一个人的钱也少了。这就是浅拷贝的本质 ------ 只拷贝指针地址,不拷贝指针指向的内容。

代码例子:浅拷贝的问题演示

例子说明:演示默认拷贝构造函数造成的浅拷贝问题

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

// 有问题的string类(浅拷贝版本)
class BadString {
private:
    char* _str;
    int _size;
    int _capacity;

public:
    // 构造函数
    BadString(const char* str = "") {
        _size = strlen(str);
        _capacity = _size;
        _str = new char[_capacity + 1];  // +1是为了存'\0'
        strcpy(_str, str);
    }

    // 析构函数
    ~BadString() {
        cout << "析构函数被调用,释放内存:" << (void*)_str << endl;
        delete[] _str;
        _str = nullptr;
    }

    // 打印字符串
    void print() {
        cout << _str << endl;
    }
};

int main() {
    BadString s1("hello");
    BadString s2 = s1;  // 调用默认拷贝构造(浅拷贝!)
    
    cout << "s1 = "; s1.print();
    cout << "s2 = "; s2.print();
    
    return 0;
}

运行结果(程序崩溃!)

cpp 复制代码
s1 = hello
s2 = hello
析构函数被调用,释放内存:0x...
析构函数被调用,释放内存:0x... (同一块内存被释放两次!)
程序崩溃!
⚠️ 经典 Bug 和问题

浅拷贝的三大坑(面试必问!)

  1. 同一块内存被多次释放 ⭐⭐⭐⭐⭐

    1. s1 析构释放了内存,s2 析构时又释放一次

    2. 这是最严重的问题,直接导致程序崩溃

  2. 一个对象修改影响另一个对象

    cpp 复制代码
    s1[0] = 'H';  // 修改s1
    // s2也变成了"Hello"!因为它们指向同一块内存
  3. 内存泄漏隐患

    1. 如果其中一个对象先被销毁,另一个对象就成了 "野指针

2. 深拷贝

简介作用

深拷贝是什么? 大白话讲就是:每个对象都有自己独立的内存

就像每个人都有自己的钱包,你的钱花了不影响我的钱。深拷贝会:

  1. 为新对象申请新的内存空间

  2. 把原对象的数据复制到新内存中

  3. 两个对象完全独立,互不影响

代码例子:深拷贝的正确实现

例子说明:实现深拷贝的拷贝构造函数

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

class GoodString {
private:
    char* _str;
    int _size;
    int _capacity;

public:
    // 构造函数
    GoodString(const char* str = "") {
        _size = strlen(str);
        _capacity = _size;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }

    // ✅ 深拷贝构造函数(重点!)
    GoodString(const GoodString& s) {
        cout << "深拷贝构造被调用!" << endl;
        // 1. 申请独立的内存空间
        _str = new char[s._capacity + 1];
        // 2. 复制数据
        strcpy(_str, s._str);
        // 3. 复制其他成员变量
        _size = s._size;
        _capacity = s._capacity;
    }

    // ✅ 深拷贝赋值运算符重载(重点!)
    GoodString& operator=(const GoodString& s) {
        cout << "深拷贝赋值被调用!" << endl;
        
        // 防止自赋值(比如 s1 = s1)
        if (this != &s) {
            // 1. 释放自己原来的内存
            delete[] _str;
            // 2. 申请新内存
            _str = new char[s._capacity + 1];
            // 3. 拷贝数据
            strcpy(_str, s._str);
            _size = s._size;
            _capacity = s._capacity;
        }
        return *this;
    }

    // 析构函数
    ~GoodString() {
        delete[] _str;
        _str = nullptr;
    }

    void print() {
        cout << _str << endl;
    }
};

int main() {
    GoodString s1("hello");
    GoodString s2 = s1;      // 深拷贝构造
    GoodString s3("world");
    s3 = s1;                 // 深拷贝赋值
    
    cout << "s1 = "; s1.print();
    cout << "s2 = "; s2.print();
    cout << "s3 = "; s3.print();
    
    return 0;  // 正常析构,不会崩溃!
}

运行结果

cpp 复制代码
深拷贝构造被调用!
深拷贝赋值被调用!
s1 = hello
s2 = hello
s3 = hello
(程序正常退出,没有崩溃!)
⚠️ 经典 Bug 和问题

深拷贝的常见坑

  1. 忘记检查自赋值 ⭐⭐⭐

    cpp 复制代码
    s1 = s1;  // 如果不检查,会先释放内存,然后从已释放的内存拷贝!
  2. 先释放内存再申请内存

    1. 如果 new 失败抛出异常,原来的数据也丢了

    2. 现代写法:先申请新内存,成功后再释放旧内存

  3. 忘记给 '\0' 预留空间

    1. 字符串长度是 n,但需要 n+1 个字节存 '\0'

🎯 面试重点总结

  • 浅拷贝:值拷贝,共用内存 → 多次释放崩溃

  • 深拷贝:每个对象独立内存 → 安全但有开销

  • 只要类里有指针成员,必须自己写拷贝构造和赋值重载!


(二)常用接口的实现

1. size () 和 capacity ()

简介作用
  • size() :返回字符串实际存储的字符个数(不包括 '\0')

  • capacity() :返回字符串最多能存多少个字符(容量)

就像一个水杯:

  • size = 杯子里现在有多少水

  • capacity = 杯子总共能装多少水

代码例子:size 和 capacity 的实现

例子说明:实现获取字符串大小和容量的接口

cpp 复制代码
class string {
private:
    char* _str;
    int _size;      // 实际字符数
    int _capacity;  // 容量

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

    // ✅ 返回有效字符个数
    int size() const {
        return _size;
    }

    // ✅ 返回容量
    int capacity() const {
        return _capacity;
    }
};

int main() {
    string s("hello");
    cout << "size = " << s.size() << endl;      // 输出:5
    cout << "capacity = " << s.capacity() << endl;  // 输出:5
    return 0;
}

2. operator[]

简介作用

让 string 对象能像数组一样用[]访问字符,支持读和写。

代码例子:operator [] 的实现

例子说明:实现下标访问运算符,支持 const 和非 const 版本

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    // ✅ 非const版本:支持读写
    char& operator[](int pos) {
        // 边界检查(断言,调试模式有效)
        assert(pos >= 0 && pos < _size);
        return _str[pos];
    }

    // ✅ const版本:只读(const对象调用)
    const char& operator[](int pos) const {
        assert(pos >= 0 && pos < _size);
        return _str[pos];
    }
};

int main() {
    string s("hello");
    
    // 读
    cout << s[0] << endl;  // h
    
    // 写
    s[0] = 'H';
    cout << s[0] << endl;  // H
    
    // const对象只能读
    const string s2("world");
    cout << s2[0] << endl;  // w,正确
    // s2[0] = 'W';  // 编译错误!
    
    return 0;
}
⚠️ 经典 Bug 和问题
  1. 越界访问:一定要加 assert 检查!

  2. 返回值类型 :必须返回引用char&,否则无法修改

  3. const 版本:const 对象只能调用 const 成员函数


3. c_str()

简介作用

返回 C 风格的字符串指针(const char*),用于和 C 语言接口兼容。

比如要调用printfstrcpy等 C 函数时,就需要用 c_str ()。

代码例子:c_str 的实现

例子说明:实现返回 C 风格字符串的接口

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    // ✅ 返回C风格字符串指针
    const char* c_str() const {
        return _str;
    }
};

int main() {
    string s("hello world");
    
    // 和C函数兼容
    printf("%s\n", s.c_str());  // 输出:hello world
    
    // 用strlen计算长度
    cout << strlen(s.c_str()) << endl;  // 11
    
    return 0;
}

4. reserve()

简介作用

扩容:提前申请更大的容量,避免频繁扩容影响性能。

注意:reserve 只扩不缩,如果 n 小于当前 capacity,什么都不做。

代码例子:reserve 的实现

例子说明:实现扩容接口,只扩大容量

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    // ✅ 扩容
    void reserve(int n) {
        // 只有n大于当前容量才扩容
        if (n > _capacity) {
            // 1. 申请新空间
            char* tmp = new char[n + 1];
            // 2. 拷贝旧数据
            strcpy(tmp, _str);
            // 3. 释放旧空间
            delete[] _str;
            // 4. 指向新空间
            _str = tmp;
            // 5. 更新容量
            _capacity = n;
        }
    }

    int capacity() const { return _capacity; }
    int size() const { return _size; }
};

int main() {
    string s("hello");
    cout << "扩容前 capacity = " << s.capacity() << endl;  // 5
    
    s.reserve(100);  // 扩容到100
    cout << "扩容后 capacity = " << s.capacity() << endl;  // 100
    cout << "size不变 = " << s.size() << endl;  // 5(size不变!)
    
    s.reserve(50);  // 50 < 100,什么都不做
    cout << "再次扩容 capacity = " << s.capacity() << endl;  // 还是100
    
    return 0;
}

5. 构造函数

简介作用

创建 string 对象,初始化成员变量。我们需要实现几个常用的构造函数。

代码例子:各种构造函数的实现

例子说明:实现无参、带参、拷贝构造函数

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

public:
    // ✅ 1. 无参构造(空字符串)
    string()
        : _str(new char[1]{'\0'})
        , _size(0)
        , _capacity(0)
    {}

    // ✅ 2. 带C字符串构造
    string(const char* str) {
        _size = strlen(str);
        _capacity = _size;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }

    // ✅ 3. 拷贝构造(深拷贝)
    string(const string& s) {
        _str = new char[s._capacity + 1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
    }

    // 现代写法:利用swap(更安全)
    // string(const string& s)
    //     : _str(nullptr), _size(0), _capacity(0)
    // {
    //     string tmp(s._str);  // 构造临时对象
    //     swap(tmp);           // 交换
    // }
};

int main() {
    string s1;           // 空字符串
    string s2("hello");  // 带参构造
    string s3 = s2;      // 拷贝构造
    
    return 0;
}
⚠️ 经典 Bug 和问题
  1. 空字符串的处理:无参构造也要给 '\0' 分配空间!

  2. 初始化列表顺序:成员变量声明顺序就是初始化顺序

  3. 现代写法更安全:先构造临时对象再 swap,异常安全


6. 析构函数

简介作用

对象销毁时释放内存,防止内存泄漏。

代码例子:析构函数的实现

例子说明:实现安全的析构函数

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    // ✅ 析构函数
    ~string() {
        // 安全写法:先判断再释放
        if (_str) {
            delete[] _str;
            _str = nullptr;  // 置空,避免野指针
            _size = 0;
            _capacity = 0;
        }
    }
};

7. clear()

简介作用

清空字符串,把 size 置为 0,但 capacity 不变。

就像把杯子里的水倒掉,但杯子大小不变。

代码例子:clear 的实现

例子说明:实现清空字符串的接口

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    // ✅ 清空字符串
    void clear() {
        _str[0] = '\0';  // 第一个字符设为结束符
        _size = 0;       // size置0
        // capacity不变!
    }

    const char* c_str() const { return _str; }
    int size() const { return _size; }
    int capacity() const { return _capacity; }
};

int main() {
    string s("hello");
    cout << "清空前:" << s.c_str() << ", size=" << s.size() << endl;
    
    s.clear();
    cout << "清空后:" << s.c_str() << ", size=" << s.size() << endl;
    cout << "capacity不变:" << s.capacity() << endl;
    
    return 0;
}

运行结果

cpp 复制代码
清空前:hello, size=5
清空后:, size=0
capacity不变:5

8. 迭代器的简单模拟

简介作用

迭代器就是指针的封装,让我们可以用统一的方式遍历各种容器。

对于 string 来说,迭代器本质就是char*

代码例子:迭代器的实现

例子说明:实现简单的迭代器,支持范围 for

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

public:
    // ✅ 迭代器类型重定义(本质就是char*)
    typedef char* iterator;
    typedef const char* const_iterator;

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

    // ✅ 返回第一个字符的迭代器
    iterator begin() {
        return _str;
    }

    const_iterator begin() const {
        return _str;
    }

    // ✅ 返回最后一个字符下一个位置的迭代器
    iterator end() {
        return _str + _size;
    }

    const_iterator end() const {
        return _str + _size;
    }
};

int main() {
    string s("hello");

    // 方式1:迭代器遍历
    string::iterator it = s.begin();
    while (it != s.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 方式2:范围for(底层就是迭代器!)
    for (char ch : s) {
        cout << ch << " ";
    }
    cout << endl;

    return 0;
}

运行结果

cpp 复制代码
h e l l o 
h e l l o

💡 小知识:C++11 的范围 for 语法,编译器会自动转换成迭代器调用!


(三)其他接口的实现

1. push_back()

简介作用

在字符串末尾追加一个字符

代码例子:push_back 的实现

例子说明:实现尾插一个字符的功能

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

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

    // ✅ 尾插一个字符
    void push_back(char ch) {
        // 检查是否需要扩容
        if (_size == _capacity) {
            // 2倍扩容,空串特殊处理
            int newCapacity = _capacity == 0 ? 4 : _capacity * 2;
            reserve(newCapacity);
        }
        
        _str[_size] = ch;      // 插入字符
        _size++;               // size++
        _str[_size] = '\0';    // 补'\0'!重要!
    }

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

int main() {
    string s;
    s.push_back('h');
    s.push_back('e');
    s.push_back('l');
    s.push_back('l');
    s.push_back('o');
    
    cout << s.c_str() << endl;  // hello
    
    return 0;
}
⚠️ 经典 Bug 和问题

忘记补 '\0' ⭐⭐⭐⭐⭐

这是最容易犯的错误!插入字符后一定要在最后位置放 '\0',否则字符串就 "断" 了!


2. append()

简介作用

在字符串末尾追加一个字符串

代码例子:append 的实现

例子说明:实现尾插字符串的功能

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

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

    // ✅ 追加字符串
    void append(const char* str) {
        int len = strlen(str);
        
        // 检查容量
        if (_size + len > _capacity) {
            reserve(_size + len);
        }
        
        // 从'\0'位置开始拷贝
        strcpy(_str + _size, str);
        _size += len;
    }

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

int main() {
    string s("hello");
    s.append(" world");
    cout << s.c_str() << endl;  // hello world
    
    return 0;
}

3. operator+=

简介作用

重载+=运算符,支持追加字符或字符串,用起来更方便。

代码例子:operator+= 的实现

例子说明:实现 += 运算符重载,支持追加字符和字符串

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    void reserve(int n) { /* 同上 */ }

    void push_back(char ch) { /* 同上 */ }

    void append(const char* str) { /* 同上 */ }

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

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

    // ✅ += string对象
    string& operator+=(const string& s) {
        append(s._str);
        return *this;
    }

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

int main() {
    string s("I");
    s += " love";       // 加C字符串
    s += ' ';           // 加字符
    s += string("C++"); // 加string对象
    
    cout << s.c_str() << endl;  // I love C++
    
    return 0;
}

💡 设计技巧:复用已有的 push_back 和 append,代码不重复!


4. insert()

简介作用

任意位置插入字符或字符串。

代码例子:insert 的实现

例子说明:实现在指定位置插入字符或字符串

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    void reserve(int n) { /* 同上 */ }

    // ✅ 在pos位置插入字符
    string& insert(int pos, char ch) {
        assert(pos >= 0 && pos <= _size);
        
        // 检查扩容
        if (_size == _capacity) {
            int newCapacity = _capacity == 0 ? 4 : _capacity * 2;
            reserve(newCapacity);
        }

        // 挪动数据:从后往前挪
        int end = _size;
        while (end >= pos) {
            _str[end + 1] = _str[end];
            end--;
        }

        // 插入
        _str[pos] = ch;
        _size++;

        return *this;
    }

    // ✅ 在pos位置插入字符串
    string& insert(int pos, const char* str) {
        assert(pos >= 0 && pos <= _size);
        int len = strlen(str);

        if (_size + len > _capacity) {
            reserve(_size + len);
        }

        // 挪动数据
        int end = _size;
        while (end >= pos) {
            _str[end + len] = _str[end];
            end--;
        }

        // 拷贝插入
        strncpy(_str + pos, str, len);
        _size += len;

        return *this;
    }

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

int main() {
    string s("world");
    s.insert(0, "hello ");  // 在开头插入
    cout << s.c_str() << endl;  // hello world
    
    s.insert(5, ',');  // 在第5位插入逗号
    cout << s.c_str() << endl;  // hello, world
    
    return 0;
}
⚠️ 经典 Bug 和问题

挪动数据方向搞反 ⭐⭐⭐⭐

插入时必须从后往前挪数据!如果从前往后挪,数据会被覆盖!


5. erase()

简介作用

从指定位置开始删除 n 个字符。

代码例子:erase 的实现

例子说明:实现删除指定位置字符的功能

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

    // 静态成员:表示"直到结尾"
    static const int npos;

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

    // ✅ 删除:从pos开始删len个
    string& erase(int pos, int len = npos) {
        assert(pos >= 0 && pos < _size);

        // 1. 如果删到末尾,或len太大,直接删到结尾
        if (len == npos || pos + len >= _size) {
            _str[pos] = '\0';
            _size = pos;
        }
        // 2. 删中间部分:往前覆盖
        else {
            strcpy(_str + pos, _str + pos + len);
            _size -= len;
        }

        return *this;
    }

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

// 静态成员初始化
const int string::npos = -1;

int main() {
    string s("hello world");
    
    s.erase(5, 1);  // 从第5位删1个(空格)
    cout << s.c_str() << endl;  // helloworld
    
    s.erase(5);  // 从第5位删到末尾
    cout << s.c_str() << endl;  // hello
    
    return 0;
}

6. find()

简介作用

查找字符或字符串第一次出现的位置,找到返回下标,找不到返回 npos。

代码例子:find 的实现

例子说明:实现查找字符和字符串的功能

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;
    static const int npos;

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

    // ✅ 查找字符
    int find(char ch, int pos = 0) const {
        assert(pos >= 0 && pos < _size);
        
        for (int i = pos; i < _size; i++) {
            if (_str[i] == ch) {
                return i;
            }
        }
        return npos;
    }

    // ✅ 查找字符串(BF算法)
    int find(const char* str, int pos = 0) const {
        assert(pos >= 0 && pos < _size);
        
        const char* p = strstr(_str + pos, str);
        if (p == nullptr) {
            return npos;
        }
        return p - _str;  // 指针相减得到下标
    }
};

const int string::npos = -1;

int main() {
    string s("hello world");
    
    cout << s.find('w') << endl;      // 6
    cout << s.find("world") << endl;  // 6
    cout << s.find('x') << endl;      // -1(npos)
    
    if (s.find("hello") != string::npos) {
        cout << "找到了!" << endl;
    }
    
    return 0;
}

7. substr()

简介作用

截取子串:从 pos 开始截取 len 个字符,返回新的 string。

代码例子:substr 的实现

例子说明:实现截取子串的功能

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;
    static const int npos;

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

    // ✅ 截取子串
    string substr(int pos = 0, int len = npos) const {
        assert(pos >= 0 && pos < _size);

        int realLen = len;
        // 计算实际截取长度
        if (len == npos || pos + len > _size) {
            realLen = _size - pos;
        }

        // 临时数组存子串
        char* tmp = new char[realLen + 1];
        strncpy(tmp, _str + pos, realLen);
        tmp[realLen] = '\0';

        string sub(tmp);  // 构造子串对象
        delete[] tmp;     // 释放临时内存

        return sub;
    }

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

const int string::npos = -1;

int main() {
    string s("hello world");
    
    string sub1 = s.substr(0, 5);
    cout << sub1.c_str() << endl;  // hello
    
    string sub2 = s.substr(6);
    cout << sub2.c_str() << endl;  // world
    
    return 0;
}

8. 六个比较运算符

简介作用

实现字符串的大小比较,按 ASCII 码字典序比较。

代码例子:比较运算符的实现

例子说明:实现 <<= ==> >= != 六个运算符

cpp 复制代码
class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

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

// ✅ < 运算符
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 || 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 !(s1 == s2);
}

int main() {
    string s1("apple");
    string s2("banana");
    
    cout << boolalpha;
    cout << "apple < banana: " << (s1 < s2) << endl;   // true
    cout << "apple == apple: " << (s1 == s1) << endl;  // true
    cout << "apple > banana: " << (s1 > s2) << endl;   // false
    
    return 0;
}

💡 设计技巧 :只需要实现<==,其他 4 个都可以复用!


9. 流输入 流提取

简介作用

让 string 支持cout输出和cin输入,用起来和内置类型一样方便。

代码例子:流运算符的实现

例子说明:实现 <<和>> 运算符重载

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

class string {
private:
    char* _str;
    int _size;
    int _capacity;

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

    void reserve(int n) { /* 同上 */ }
    void push_back(char ch) { /* 同上 */ }
    void clear() { _str[0] = '\0'; _size = 0; }

    const char* c_str() const { return _str; }
    int size() const { return _size; }

    char& operator[](int pos) { return _str[pos]; }
};

// ✅ 流输出 <<
ostream& operator<<(ostream& out, const string& s) {
    for (int i = 0; i < s.size(); i++) {
        out << s[i];
    }
    return out;
}

// ✅ 流输入 >>
istream& operator>>(istream& in, string& s) {
    s.clear();  // 先清空原有内容
    
    char ch;
    ch = in.get();
    
    // 跳过前面的空格、换行
    while (ch == ' ' || ch == '\n') {
        ch = in.get();
    }
    
    // 读取字符,遇到空格/换行停止
    while (ch != ' ' && ch != '\n') {
        s.push_back(ch);
        ch = in.get();
    }
    
    return in;
}

int main() {
    string s;
    
    cout << "请输入一个字符串:";
    cin >> s;
    
    cout << "你输入的是:" << s << endl;
    
    return 0;
}

🎯 面试重点总结

必背考点(90% 概率考)

  1. ⭐⭐⭐⭐⭐ 深浅拷贝的区别

    1. 浅拷贝:共用内存,多次释放崩溃

    2. 深拷贝:独立内存,安全但有开销

  2. ⭐⭐⭐⭐ 拷贝构造和赋值重载

    1. 只要类有指针成员,必须自己实现

    2. 注意自赋值检查

  3. ⭐⭐⭐ 扩容机制

    1. 一般 2 倍扩容

    2. reserve 只扩不缩

  4. ⭐⭐⭐ 迭代器本质

    1. string 迭代器就是 char*

常见坑点提醒

  • ❌ 忘记给 '\0' 预留空间

  • ❌ push_back 后忘记补 '\0'

  • ❌ insert 时数据挪动方向搞反

  • ❌ 赋值重载忘记检查自赋值

  • ❌ 析构函数重复释放


总结啦~

恭喜你!看完这篇文章,你已经掌握了 string 的模拟实现!

string 是 C++ 最重要的容器之一,它的实现涵盖了:

  • 类的设计思想

  • 内存管理(深浅拷贝)

  • 运算符重载

  • 迭代器设计

这些都是 C++ 的核心知识点!

下一篇预告:我们将进入 vector 的学习啦,敬请期待哦!


如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注我! 有问题欢迎在评论区留言,我会一一回复!

相关推荐
运维行者_1 小时前
ITOps自动化:全面解析
java·服务器·开发语言·网络·云计算
Chase_______1 小时前
【Java杂项】为什么 b += 1 可以,但 b = b + 1 会报错?类型提升与复合赋值详解
java·开发语言·python
basketball6162 小时前
C++ 面向对象编程:思想、原则与实践
开发语言·c++
曹牧2 小时前
C#:DataGridView控件中展示JSON内容
开发语言·c#·json
AIFQuant2 小时前
JavaScript 前端集成贵金属 K 线图:10 分钟快速实现
开发语言·前端·javascript·websocket·金融·期货api
范什么特西2 小时前
idea里面jsp找不到图片
java·开发语言·servlet
吃好睡好便好2 小时前
在Matlab中绘制三维直方图
开发语言·学习·算法·matlab·信息可视化
爱炸薯条的小朋友2 小时前
C#的详细应用和讲解池化为什么能提升 OpenCvSharp / Mat 的整体效率
开发语言·opencv·c#
不是山谷.:.2 小时前
websocket的封装
开发语言·前端·网络·笔记·websocket·网络协议