string 类从入门到深入

【C++】string 类从入门到深入:构造、常用接口、OJ 题与模拟实现

不会 string,等于不会 C++ 字符串

从 C 风格字符串到 C++ string,从使用到手写模拟


一、为什么需要 string 类?

C 语言中的字符串问题

  • \0 结尾的字符数组
  • 操作函数(strcpy/strcat/strlen)与数据分离,不符合 OOP 思想
  • 用户需手动管理内存,极易越界、泄漏

C++ string 的优势

  • 面向对象:字符串与操作封装在一起
  • 自动内存管理
  • 支持赋值、比较、拼接、查找、遍历等丰富接口
  • 安全性、便利性远超 C 字符串

工作中几乎不再直接使用 C 字符串函数


二、C++11 小语法铺垫:auto 与范围 for

1. auto 类型推导

C++11 赋予 auto 新含义:编译器自动推导类型

cpp 复制代码
auto a = 10;       // int
auto b = 'a';      // char
auto c = func();   // 根据返回值推导

auto& ref = a;     // 必须加 &
auto* p = &a;      // auto 和 auto* 等价

注意

  • 同一行多变量类型必须一致
  • 不能作为参数(可作为返回值,但慎用)
  • 不能直接声明数组

典型用法:简化迭代器

cpp 复制代码
map<string, string>::iterator it = dict.begin();
auto it = dict.begin();  // 简洁

2. 范围 for(Range-based for)

自动遍历数组或容器:

cpp 复制代码
int arr[] = {1,2,3};
for(auto& e : arr) e *= 2;   // 修改原值
for(auto e : arr) cout << e << " ";

string str = "hello";
for(char ch : str) cout << ch << " ";

底层本质:替换为迭代器


三、string 常用接口精讲

以下为最常用接口,掌握即可应对 90% 场景

1. 构造

构造方式 示例
空字符串 string s1;
用 C 字符串 string s2("hello");
拷贝构造 string s3(s2);
填充构造 string s4(5, 'A');

2. 容量

方法 含义
size() / length() 有效字符长度
capacity() 当前底层容量
empty() 是否为空
clear() 清空,不释放内存
reserve(n) 预留空间(不改变 size)
resize(n, c) 改变有效字符数

示例

cpp 复制代码
string s("hello");
cout << s.size();      // 5
s.reserve(100);        // 容量 ≥100
s.resize(10, '!');     // hello!!!!!

3. 访问与遍历

方式 示例
operator[] s[0]
迭代器 begin() / end()
范围 for for(char c : s)

4. 修改操作

方法 作用
push_back(c) 尾插字符
append(str) 追加字符串
operator+= 最常用
c_str() 返回 C 字符串
find(c, pos) 正向查找
rfind(c, pos) 反向查找
substr(pos, n) 截取子串

推荐使用 +=

cpp 复制代码
string s = "hello";
s += " world";
s += '!';

5. 非成员函数

  • getline(cin, s):读取一行(含空格)
  • operator>> / operator<<
  • 关系运算符:==<>

四、VS 与 g++ 下的 string 底层结构(面试常问)

VS(32位):28 字节

  • 字符串 ≤ 15:存放在内部数组合法
  • 字符串 ≥ 16:堆上分配
  • 结构:联合体 _Bxty + size + capacity + 额外指针

小字符串优化(SSO)

g++(32位):4 字节

  • 只存一个指针,指向堆区结构
  • 堆区包含:_M_length、_M_capacity、_M_refcount(引用计数)
  • 采用 写时拷贝(COW)

五、牛刀小试:经典 OJ 题(附代码)

1. 仅反转字母

保留非字母位置,反转字母部分

cpp 复制代码
class Solution {
public:
    bool isLetter(char ch) {
        return (ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z');
    }
    string reverseOnlyLetters(string S) {
        int l=0, r=S.size()-1;
        while(l<r){
            while(l<r && !isLetter(S[l])) ++l;
            while(l<r && !isLetter(S[r])) --r;
            swap(S[l++], S[r--]);
        }
        return S;
    }
};

2. 字符串中第一个只出现一次的字符(哈希思想)

cpp 复制代码
class Solution {
public:
    int firstUniqChar(string s) {
        int count[256] = {0};
        for(char ch : s) count[ch]++;
        for(int i=0; i<s.size(); ++i)
            if(count[s[i]] == 1) return i;
        return -1;
    }
};

3. 验证回文串(忽略非字母数字 + 大小写)

cpp 复制代码
class Solution {
public:
    bool isPalindrome(string s) {
        for(char& ch : s) 
            if(ch>='A' && ch<='Z') ch += 32;
        int l=0, r=s.size()-1;
        while(l<r){
            while(l<r && !isalnum(s[l])) ++l;
            while(l<r && !isalnum(s[r])) --r;
            if(s[l]!=s[r]) return false;
            ++l; --r;
        }
        return true;
    }
};

4. 字符串相加(大数加法)

cpp 复制代码
string addStrings(string num1, string num2) {
    int i=num1.size()-1, j=num2.size()-1, carry=0;
    string res;
    while(i>=0 || j>=0 || carry){
        int sum = carry;
        if(i>=0) sum += num1[i--]-'0';
        if(j>=0) sum += num2[j--]-'0';
        carry = sum/10;
        res += (sum%10)+'0';
    }
    reverse(res.begin(), res.end());
    return res;
}

六、string 模拟实现(面试必考)

1. 浅拷贝的问题(经典错误)

cpp 复制代码
class String {
    char* _str;
};
String s1("hello");
String s2(s1);  // 默认拷贝 → 两个对象指向同一块内存 → 析构两次 → 崩溃

2. 深拷贝(传统版)

显式实现拷贝构造、赋值、析构

cpp 复制代码
String(const String& s) {
    _str = new char[strlen(s._str)+1];
    strcpy(_str, s._str);
}
String& operator=(const String& s) {
    if(this != &s) {
        delete[] _str;
        _str = new char[strlen(s._str)+1];
        strcpy(_str, s._str);
    }
    return *this;
}

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

cpp 复制代码
String(const String& s) : _str(nullptr) {
    String tmp(s._str);
    swap(_str, tmp._str);
}
String& operator=(String s) {  // 传值,直接复用拷贝构造
    swap(_str, s._str);
    return *this;
}

4. 写时拷贝(了解)

  • 引用计数 + 浅拷贝
  • 只有"写"时才真正复制
  • g++ 早期采用,现代已较少使用

七、总结

知识点 核心要点
auto 类型推导,简化迭代器
范围 for 容器/数组自动遍历
string 接口 size, resize, reserve, +=, find, substr
OJ 常见题 回文、首个唯一字符、大数加法
深拷贝 解决浅拷贝资源重复释放
现代版写法 传值 + swap,简洁优雅

建议:把上面的 OJ 题手写 2~3 遍,string 模拟实现至少写一遍传统版 + 现代版。


📌 延伸阅读

  • 面试中 string 的一种正确写法
  • STL 中的 string 类到底怎么了?

如有疑问或想深入某一部分,欢迎留言讨论。

相关推荐
眠りたいです5 小时前
现代C++:C++14中的新语言特性和库特性
c语言·开发语言·c++
浅念-6 小时前
LeetCode 回溯算法题——综合练习
数据结构·c++·算法·leetcode·职场和发展·深度优先·dfs
楼田莉子8 小时前
C++17新特性:__had_include/属性/求值顺序规则
开发语言·c++·后端
h_a_o777oah9 小时前
状态机+划分型 DP :深度解析K-划分问题下 DP 状态的转移逻辑(洛谷P2679 P2331 附C++代码)
c++·算法·动态规划·acm·状态机dp·划分型dp·滚动数组优化
雪度娃娃11 小时前
Asio异步读写——连接的安全回收问题
开发语言·c++·安全·php
不吃土豆的马铃薯11 小时前
Spdlog 进阶:日志基本控制、日志格式控制、异步记录器
linux·服务器·开发语言·前端·c++
liulilittle11 小时前
TCP UCP:基于卡尔曼滤波的BBR增强型拥塞控制算法
linux·网络·c++·tcp/ip·算法·c·通讯
咩咦12 小时前
C++学习笔记26:static 静态成员
c++·学习笔记·static·静态成员变量·静态成员·静态成员函数
秋落风声12 小时前
内存池仿Nginx C++实现
c++·nginx