C++ 学习笔记(string类)

一、学习string类的原因

1. C语言字符串的不足

  • 本质:以'\0'结尾的字符集合,依赖C标准库的str系列函数(如strcpystrlen)操作。

  • 缺陷:函数与字符串分离,不符合面向对象(OOP)思想;底层空间需用户手动管理,易出现内存泄漏或越界访问。

2. 实际应用需求

  • OJ题目中字符串相关问题多以string类形式出现。

  • 工作中使用string类更简洁、高效,无需关注底层空间管理。

二、C++11辅助语法(方便string类使用)

1. auto关键字

(1)核心作用
  • C++11中作为类型指示符,编译器在编译时自动推导变量类型,无需显式声明。
(2)使用规则

|------|----------------------------------|-----------------------------------------------------|
| 规则 | 示例 | 说明 |
| 指针声明 | auto* p = &x;auto p = &x; | 效果一致,均可推导为指针类型 |
| 引用声明 | auto& ref = x; | 必须加&,否则推导为值类型 |
| 多行声明 | auto a = 1, b = 2; | 同一行变量类型必须相同,auto c = 3, d = 4.0; 编译报错 |
| 限制场景 | 不可作为函数参数、不可直接声明数组 | void func(auto a){} 报错;auto arr[] = {1,2,3}; 报错 |
| 适用场景 | 容器迭代器简化 | map<string, string>::iterator it 可简化为 auto it |

2. 范围for循环

(1)语法格式
cpp 复制代码
for (auto 迭代变量 : 可迭代对象) {
    // 循环体
}
  • 第一部分:迭代变量(可加&修改原对象);第二部分:被迭代范围(数组、容器等)。
(2)核心优势
  • 自动迭代、自动取数据、自动判断结束,无需手动控制索引,减少越界错误。

  • 底层原理:替换为迭代器遍历(汇编层面可验证)。

(3)示例
cpp 复制代码
int arr[] = {1,2,3,4,5};
for (auto& e : arr) e *= 2; // 修改原数组元素
for (auto e : arr) cout << e; // 遍历输出
string str = "hello";
for (auto ch : str) cout << ch; // 遍历字符串

三、string类核心知识点

1. 头文件与命名空间

  • 必须包含:#include <string>

  • 需使用命名空间:using namespace std;(否则需写std::string

2. 常见构造函数(重点)

|----------------------------|--------------|---------------------------------|
| 构造函数 | 功能说明 | 示例 |
| string() | 构造空字符串 | string s1; |
| string(const char* s) | 用C风格字符串构造 | string s2("hello"); |
| string(size_t n, char c) | 构造含n个字符c的字符串 | string s3(5, 'a'); // "aaaaa" |
| string(const string& s) | 拷贝构造 | string s4(s2); |

3. 容量操作(重点)

|----------------------------|-----------|-------------------------------------------------------------------------------|
| 函数 | 功能说明 | 注意事项 |
| size() | 返回有效字符长度 | 与length()功能一致,推荐使用size()(与其他容器接口统一) |
| capacity() | 返回总空间大小 | 大于等于size(),预分配空间以减少扩容次数 |
| empty() | 判断是否为空串 | 返回bool值,空串返回true |
| clear() | 清空有效字符 | 仅重置size()为0,不改变capacity()(底层空间不变) |
| reserve(size_t n) | 预留n个字符空间 | 1. 不改变有效字符数;2. n小于当前capacity()时无效果;3. 提前预留空间可避免频繁扩容 |
| resize(size_t n, char c) | 调整有效字符数为n | 1. n>原size():新增部分用c填充(默认用\0),可能扩容;2. n<原size():仅截断有效字符,capacity()不变 |

示例
cpp 复制代码
string s = "hello";
s.reserve(10); // 预留10个空间,capacity()≥10,size()仍为5
s.resize(8, 'x'); // 有效字符变为8个,"helloxxx",size()=8
s.clear(); // size()=0,capacity()仍为10

4. 访问与遍历(重点)

|---------------------|-----------|-------------------------------------------------------------------------|
| 方式 | 功能说明 | 示例 |
| operator[] | 访问pos位置字符 | string s = "hello"; cout << s[1]; // 输出'e'(pos从0开始) |
| begin() + end() | 迭代器遍历 | for (auto it = s.begin(); it != s.end(); ++it) cout << *it; |
| rbegin() + rend() | 反向迭代器遍历 | for (auto it = s.rbegin(); it != s.rend(); ++it) cout << *it; // 反向输出 |
| 范围for | 简洁遍历 | for (auto ch : s) cout << ch; |

5. 修改操作(重点)

|----------------------------------|------------|------------------------------------------|
| 函数 | 功能说明 | 示例 |
| push_back(char c) | 尾插单个字符 | s.push_back('!'); |
| append(const string& str) | 追加字符串 | s.append("world"); |
| operator+= | 追加字符/字符串 | 推荐使用!s += 'a'; s += "bc";(支持多种类型,简洁高效) |
| c_str() | 返回C风格字符串 | 用于兼容C库函数,返回const char*(以'\0'结尾) |
| find(char c, size_t pos=0) | 从pos开始向后找c | 返回字符位置(size_t类型),未找到返回string::npos |
| rfind(char c, size_t pos=npos) | 从pos开始向前找c | 用法同find,反向查找 |
| substr(size_t pos, size_t n) | 从pos截取n个字符 | 返回截取后的子串,n省略则截取到末尾 |

关键注意
  • 尾插效率:push_backappendoperator+=operator+=功能最灵活(支持字符/字符串)。

  • 查找技巧:结合npos判断是否找到,如if (s.find('a') != string::npos)

示例
cpp 复制代码
string s = "hello";
s += " world"; // "hello world"
size_t pos = s.find('w'); // pos=6
string sub = s.substr(6, 5); // "world"

6. 非成员函数(重点)

|------------------------|---------|------------------------------|
| 函数 | 功能说明 | 示例 |
| operator>> | 输入字符串 | cin >> s;(遇到空格/换行结束) |
| operator<< | 输出字符串 | cout << s; |
| getline(cin, s) | 读取一行字符串 | 包含空格,解决cin >> s的局限 |
| relational operators | 字符串比较 | 支持==!=<>等(按字典序比较) |

示例
cpp 复制代码
string s;
getline(cin, s); // 读取整行,包括空格
cout << (s == "hello") << endl; // 比较字符串是否相等

7. 底层结构差异(vs vs g++)

|----------|------|------------------------------------------------------------------------|
| 编译器 | 占用空间 | 核心设计 |
| vs(32位) | 28字节 | 1. 联合体存储:字符串长度<16时用内部固定数组(效率高),≥16时从堆分配;2. 包含sizecapacity、指针字段 |
| g++(32位) | 4字节 | 1. 写时拷贝(Copy-On-Write);2. 仅存一个指针,指向堆空间(包含sizecapacity、引用计数、字符串数据) |

四、OJ实战题目(核心应用)

1. 仅仅反转字母

2. 找字符串中第一个只出现一次的字符

3. 最后一个单词的长度

4. 验证回文串

5. 字符串相加

五、string类模拟实现(面试重点)

1. 核心问题:浅拷贝 vs 深拷贝

(1)浅拷贝(问题)
  • 编译器默认拷贝构造/赋值运算符重载:仅拷贝指针地址,导致多个对象共享同一块内存。

  • 后果:对象销毁时重复释放内存,程序崩溃。

(2)深拷贝(解决方案)
  • 每个对象独立分配内存,拷贝数据内容,不共享资源。

  • 必须显式实现:拷贝构造函数、赋值运算符重载、析构函数。

2. 深拷贝实现(传统版)

cpp 复制代码
class String {
public:
    // 构造函数
    String(const char* str = "") {
        if (nullptr == str) str = ""; // 处理nullptr
        _str = new char[strlen(str) + 1]; // 分配空间(含'\0')
        strcpy(_str, str);
    }

    // 拷贝构造函数
    String(const String& s) {
        _str = new char[strlen(s._str) + 1];
        strcpy(_str, s._str);
    }

    // 赋值运算符重载
    String& operator=(const String& s) {
        if (this != &s) { // 防止自赋值
            char* tmp = new char[strlen(s._str) + 1]; // 先分配新空间
            strcpy(tmp, s._str);
            delete[] _str; // 释放旧空间
            _str = tmp; // 指向新空间
        }
        return *this;
    }

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

private:
    char* _str;
};

3. 深拷贝实现(现代版,更简洁)

cpp 复制代码
class String {
public:
    String(const char* str = "") {
        if (nullptr == str) str = "";
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    // 拷贝构造:利用临时对象交换资源
    String(const String& s) : _str(nullptr) {
        String tmp(s._str); // 构造临时对象
        swap(_str, tmp._str); // 交换指针,临时对象销毁时释放旧空间
    }

    // 赋值运算符重载:传值参数生成临时对象,交换后临时对象销毁
    String& operator=(String s) {
        swap(_str, s._str);
        return *this;
    }

    ~String() {
        delete[] _str;
        _str = nullptr;
    }

private:
    char* _str;
};

4. 写时拷贝(了解)

  • 基于浅拷贝+引用计数:读取时共享资源,写入时才进行深拷贝。

  • 核心:用引用计数记录资源使用者数量,销毁时计数减1,计数为0时释放资源。

  • 缺陷:多线程环境下需处理线程安全问题,现代STL中部分已弃用。

六、课后作业

  1. 翻转字符串II:区间部分翻转(如每2k个字符翻转前k个)。

  2. 翻转字符串III:翻转字符串中的每个单词(单词顺序不变,字母反转)。

  3. 字符串相乘(大数乘法,不使用内置大数库)。

  4. 找出字符串中第一个只出现一次的字符(优化时间/空间复杂度)。

七、复习要点

  1. 核心接口:构造、size()/capacity()resize()/reserve()operator[]find()substr()operator+=

  2. 底层特性:扩容机制、vs/g++结构差异、浅拷贝与深拷贝。

  3. 实战应用:OJ题目中的字符串遍历、反转、查找、拼接、大数运算。

  4. 面试重点:string类模拟实现(深拷贝的两种写法)。

相关推荐
乐观勇敢坚强的老彭2 小时前
c++信奥寒假营集训01
android·java·c++
hetao17338372 小时前
2026-01-27~28 hetao1733837 的刷题记录
c++·笔记·算法
2301_822366352 小时前
C++中的智能指针详解
开发语言·c++·算法
kdniao12 小时前
PHP 页面中如何实现根据快递单号查询物流轨迹?对接快递鸟在途监控 API 实操
android·开发语言·php
郑州光合科技余经理2 小时前
同城配送调度系统实战:JAVA微服务
java·开发语言·前端·后端·微服务·中间件·php
leaves falling2 小时前
c语言-函数讲解
c语言·开发语言
癫狂的兔子2 小时前
【BUG】【Python】【Spider】Compound class names are not allowed.
开发语言·python·bug
望忆2 小时前
关于《Contrastive Collaborative Filtering for Cold-Start Item Recommendation》的学习
学习
css趣多多2 小时前
动态路由,路由重置,常量路由,$ref,表单验证流程
开发语言·javascript·ecmascript