C++ string 入门(一)

C++ string 模板入门


🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 《C语言基础》 《数据结构》 《机器学习导论》 《前端基础》 《python基础》 ✨ 数据即知识,压缩即智能


目录

  • [C++ string 模板入门](#C++ string 模板入门)
  • [一、什么是 STL?](#一、什么是 STL?)
    • [1.1 STL 的全称](#1.1 STL 的全称)
    • [1.2 STL 解决了什么问题?](#1.2 STL 解决了什么问题?)
  • [二、STL 的版本演变](#二、STL 的版本演变)
    • [2.1 原始版本:HP STL](#2.1 原始版本:HP STL)
    • [2.2 P.J. 版本](#2.2 P.J. 版本)
    • [2.3 RW 版本](#2.3 RW 版本)
    • [2.4 SGI 版本](#2.4 SGI 版本)
  • [三、STL 的六大组件](#三、STL 的六大组件)
    • [3.1 容器:用来存数据](#3.1 容器:用来存数据)
    • [3.2 算法:用来处理数据](#3.2 算法:用来处理数据)
    • [3.3 迭代器:连接容器和算法](#3.3 迭代器:连接容器和算法)
    • [3.4 仿函数:像函数一样使用的对象](#3.4 仿函数:像函数一样使用的对象)
    • [3.5 适配器:换一种接口包装已有组件](#3.5 适配器:换一种接口包装已有组件)
    • [3.6 空间配置器:负责内存管理](#3.6 空间配置器:负责内存管理)
  • [四、STL 为什么重要?](#四、STL 为什么重要?)
    • [4.1 刷题中很重要](#4.1 刷题中很重要)
    • [4.2 面试中很重要](#4.2 面试中很重要)
    • [4.3 工作中很重要](#4.3 工作中很重要)
  • [五、如何学习 STL?](#五、如何学习 STL?)
    • [5.1 第一层:能用](#5.1 第一层:能用)
    • [5.2 第二层:明理](#5.2 第二层:明理)
    • [5.3 第三层:能扩展](#5.3 第三层:能扩展)
  • [六、为什么先学 string?](#六、为什么先学 string?)
    • [6.1 C 语言字符串的问题](#6.1 C 语言字符串的问题)
    • [6.2 string 解决了什么问题?](#6.2 string 解决了什么问题?)
  • [七、string 其实是什么?](#七、string 其实是什么?)
    • [7.1 string 是 basic_string<char>](#7.1 string 是 basic_string)
    • [7.2 为什么说 string 是 STL 学习入口?](#7.2 为什么说 string 是 STL 学习入口?)
  • [八、使用 string 前要包含什么?](#八、使用 string 前要包含什么?)
  • 九、auto:让类型推导更轻松
    • [9.1 auto 的新含义](#9.1 auto 的新含义)
    • [9.2 auto 必须初始化](#9.2 auto 必须初始化)
    • [9.3 auto 和指针、引用](#9.3 auto 和指针、引用)
    • [9.4 auto 在 STL 中很好用](#9.4 auto 在 STL 中很好用)
  • [十、范围 for:更自然地遍历容器](#十、范围 for:更自然地遍历容器)
    • [10.1 普通遍历](#10.1 普通遍历)
    • [10.2 范围 for 写法](#10.2 范围 for 写法)
    • [10.3 修改元素时要用引用](#10.3 修改元素时要用引用)
  • [十一、string 的常见构造方式](#十一、string 的常见构造方式)
    • [11.1 构造空字符串](#11.1 构造空字符串)
    • [11.2 用 C 字符串构造](#11.2 用 C 字符串构造)
    • [11.3 拷贝构造](#11.3 拷贝构造)
    • [11.4 构造 n 个相同字符](#11.4 构造 n 个相同字符)

一、什么是 STL?

1.1 STL 的全称

STL 的全称是:

cpp 复制代码
Standard Template Library

中文一般叫:

标准模板库。

从名字上看,它有三个关键词:

  • Standard:标准
  • Template:模板
  • Library:库

简单理解:

STL 是 C++ 标准库的重要组成部分,是一套基于泛型编程思想的数据结构与算法框架。

它不是只提供"容器",也不是只提供"算法",而是把容器、算法、迭代器、函数对象、适配器、空间配置器等组件组合成了一套完整体系。


1.2 STL 解决了什么问题?

在没有 STL 的情况下,我们经常要自己写很多基础设施。

比如:

  • 动态数组
  • 链表
  • 队列
  • 哈希表
  • 二叉搜索树

这些东西当然可以自己写。

而且在学习数据结构时,确实应该自己手写一遍。

但是在真实开发中,如果每做一个项目都从零写一套数据结构,那效率会非常低,而且还容易出 bug。

STL 解决的问题就是:

把常见的数据结构和算法标准化、模板化、通用化。

比如你想存一组整数,可以用:

cpp 复制代码
vector<int> v;

想存一组字符串,可以用:

cpp 复制代码
vector<string> v;

想维护一组自动排序且不重复的数据,可以用:

cpp 复制代码
set<int> s;

想建立键值映射,可以用:

cpp 复制代码
map<string, int> dict;

你不用关心底层每一行内存管理怎么写,先把问题解决出来。

这就是 STL 的价值。


二、STL 的版本演变

2.1 原始版本:HP STL

STL 的原始版本由 Alexander Stepanov 和 Meng Lee 在惠普实验室完成。

这个版本可以理解为 STL 的源头。

它最重要的贡献不是某几个具体容器,而是提出了一套非常优雅的泛型编程框架:

用模板把数据结构和算法解耦,用迭代器把容器和算法连接起来。

这也是 STL 后来能成为 C++ 标准库核心组成部分的重要原因。


2.2 P.J. 版本

P.J. 版本由 P.J. Plauger 开发,被 Windows Visual C++ 采用。

这个版本继承自 HP STL,但代码可读性相对一般,符号命名比较复杂。

初学者如果直接阅读这类源码,可能会觉得压力比较大。


2.3 RW 版本

RW 版本由 Rogue Wave 公司开发,也继承自 HP STL。

它曾被一些 C++ 编译环境采用。

这个版本在历史上也有影响,但对我们现在学习 STL 来说,不是最主要的阅读对象。


2.4 SGI 版本

SGI STL 由 Silicon Graphics Computer Systems 开发,也继承自 HP STL。

它被 GCC 采用过,可移植性好,风格相对清晰,源码阅读价值比较高。

很多 C++ 学习资料在讲 STL 源码时,会以 SGI STL 作为主要参考版本。

对于初学者来说,先不急着直接啃源码,但可以知道:

STL 不只是会用接口,背后还有非常经典的泛型编程设计思想。


三、STL 的六大组件

STL 通常可以分为六大组件:

  • 容器
  • 算法
  • 迭代器
  • 仿函数
  • 适配器
  • 空间配置器

3.1 容器:用来存数据

容器就是用来存放数据的类模板。

常见容器有:

cpp 复制代码
string
vector
list
deque
map
set

还有一些容器适配器:

cpp 复制代码
stack
queue
priority_queue

不同容器适合不同场景。


3.2 算法:用来处理数据

算法是对数据进行处理的一组函数模板。

常见算法有:

cpp 复制代码
find
sort
reverse
swap
merge
count
copy

这些算法通常不直接依赖某一种具体容器,而是通过迭代器访问容器中的元素。

比如排序:

cpp 复制代码
sort(v.begin(), v.end());

这里 sort 不直接关心 v 的内部结构,它只需要一段可操作的区间。

STL 算法的一个重要特点是:

算法尽量和容器解耦。

这就是泛型编程的体现。


3.3 迭代器:连接容器和算法

迭代器可以理解成一种"泛化指针"。

它的作用是:

让算法能够以统一方式访问不同容器中的元素。

比如:

cpp 复制代码
vector<int> v = {1, 2, 3, 4};

auto it = v.begin();
while (it != v.end())
{
    cout << *it << " ";
    ++it;
}

这里的 it 就是迭代器。

对算法来说,只要能通过迭代器拿到元素,就不必关心底层容器到底是数组、链表还是树。


3.4 仿函数:像函数一样使用的对象

仿函数也叫函数对象。

它本质上是重载了 operator() 的类对象。

比如:

cpp 复制代码
struct Greater
{
    bool operator()(int x, int y)
    {
        return x > y;
    }
};

这个对象可以像函数一样调用:

cpp 复制代码
Greater greater;
cout << greater(3, 2) << endl;

在 STL 中,仿函数经常用于控制算法行为。

比如排序时指定升序或降序:

cpp 复制代码
sort(v.begin(), v.end(), greater<int>());

仿函数让算法更灵活。


3.5 适配器:换一种接口包装已有组件

适配器可以理解成"包装器"。

它会把已有组件包装成另一种使用方式。

常见的容器适配器有:

cpp 复制代码
stack
queue
priority_queue

比如 stack 底层可以用 deque 实现。

但是对外只暴露栈应该有的接口:

cpp 复制代码
push
pop
top
empty
size

你不能随便遍历它,也不能随便访问中间元素。

这就是适配器的特点:

底层借用已有容器,对外提供特定接口。


3.6 空间配置器:负责内存管理

空间配置器,也就是 allocator,用来负责 STL 容器中的内存分配和释放。

平时写代码时,我们很少直接操作 allocator。

但它是 STL 底层非常重要的一部分。

比如 vector 扩容时,底层需要申请更大的空间、搬移旧数据、释放旧空间。

这些和内存相关的动作,都会涉及空间配置器的思想。

入门阶段先知道它存在即可


四、STL 为什么重要?

4.1 刷题中很重要

在笔试和 OJ 中,STL 几乎是 C++ 选手的基础工具箱。

比如:

二叉树层序遍历可以用:

cpp 复制代码
queue<TreeNode*> q;

判断括号匹配可以用:

cpp 复制代码
stack<char> st;

统计字符出现次数可以用:

cpp 复制代码
unordered_map<char, int> count;

字符串题基本离不开:

cpp 复制代码
string

如果完全不用 STL,很多题的代码量会明显增加。

而且自己写数据结构还容易引入额外 bug。


4.2 面试中很重要

面试中经常会问 STL 相关问题,比如:

  • vectorlist 的区别
  • mapunordered_map 的区别
  • set 的底层结构
  • vector 扩容机制
  • 迭代器失效问题
  • string 的常见接口
  • stackqueuepriority_queue 的使用场景

这些问题不是单纯考背诵,而是在考你是否理解容器背后的数据结构。

所以学 STL 不能只停留在"会调接口"。

至少要知道:

这个容器适合什么场景,不适合什么场景,底层大概是什么结构。


4.3 工作中很重要

真实开发中,STL 的价值更明显。

比如你要管理一组对象:

cpp 复制代码
vector<User> users;

要根据用户 ID 查询对象:

cpp 复制代码
unordered_map<int, User> userMap;

要保存自动排序的数据:

cpp 复制代码
set<int> nums;

要处理字符串:

cpp 复制代码
string name;

这些都是高频场景。

STL 的意义在于:

让我们少重复造底层轮子,把精力放在业务逻辑和问题建模上。

当然,这不意味着底层不重要。

恰恰相反,只有理解底层,才能更好地选择正确的 STL 组件。


五、如何学习 STL?

5.1 第一层:能用

第一层目标是能用。

也就是看到题目或项目需求时,知道应该选哪个容器。

比如:

  • 需要动态数组:vector
  • 需要栈:stack
  • 需要队列:queue
  • 需要优先级队列:priority_queue
  • 需要字符串:string

这一阶段重点是接口熟练。


5.2 第二层:明理

第二层目标是明理。

也就是理解容器背后的结构和适用场景。

比如:

vector 为什么随机访问快?

因为它底层是连续空间。

list 为什么中间插入删除方便?

map 为什么有序?

unordered_map 为什么平均查询快?


5.3 第三层:能扩展

第三层目标是能扩展。

比如:

  • 能模拟实现简单的 vector
  • 能模拟实现简单的 string
  • 能理解迭代器的基本设计
  • 能读懂部分 STL 源码
  • 能写出自己的容器或适配器
  • 能理解内存配置和对象构造销毁的配合

这一层更偏向进阶学习。

对初学者来说,不必一开始就追求源码级掌握,但要知道未来可以往这个方向走。


六、为什么先学 string?

6.1 C 语言字符串的问题

在 C 语言中,字符串本质上是以 '\0' 结尾的字符数组。

比如:

cpp 复制代码
char str[] = "hello";

它在内存中大致是:

cpp 复制代码
'h' 'e' 'l' 'l' 'o' '\0'

C 标准库提供了一些字符串处理函数:

cpp 复制代码
strlen
strcpy
strcat
strcmp
strstr

这些函数能用,但也有明显问题:

第一,字符串和操作函数是分离的。

数据是字符数组,函数在外面。

第二,空间需要程序员自己管理。

如果空间不够,容易越界。

第三,很多函数对 '\0' 非常敏感。

一旦字符串没有正确结尾,就可能出问题。

第四,不符合 C++ 面向对象思想。

字符串作为一种对象,理应自己管理自己的空间和行为。


6.2 string 解决了什么问题?

std::string 把字符串封装成了一个类。

它可以自己管理底层空间,并且提供大量成员函数。

比如:

cpp 复制代码
string s = "hello";
s += " world";
cout << s.size() << endl;
cout << s[0] << endl;

你不需要自己手动计算空间,也不需要频繁调用 strlenstrcpy

相比 C 字符串,string 更符合 C++ 的写法:

字符串对象自己管理自己的数据,并通过成员函数提供操作接口。


七、string 其实是什么?

7.1 string 是 basic_string

严格来说,string 不是凭空出现的特殊类型。

它通常是下面这个类模板的类型别名:

cpp 复制代码
std::basic_string<char>

也就是说,标准库真正提供的是一个更通用的字符串模板:

cpp 复制代码
template<
    class CharT,
    class Traits = std::char_traits<CharT>,
    class Allocator = std::allocator<CharT>
>
class basic_string;

string 可以理解成:

cpp 复制代码
using string = basic_string<char>;

所以从模板角度看:

basic_string<char> 用来表示普通窄字符字符串。

类似地,还有:

cpp 复制代码
wstring
u16string
u32string

它们对应不同字符类型。


7.2 为什么说 string 是 STL 学习入口?

string 很适合作为 STL 的入口,因为它有几个特点:

第一,它是类。

它封装了数据和操作。

第二,它是模板的实例。

它背后体现了 basic_string 的模板设计。

第三,它有容器特征。

它可以存储一组字符,支持下标访问、迭代器、范围 for

第四,它在刷题和工程中都非常常用。

字符串处理题、输入输出、文本解析、日志处理,都离不开它。

所以学 string,不是只学字符串,而是在为后面的 vectorlistmap 等 STL 容器打基础。


八、使用 string 前要包含什么?

使用 string 时,需要包含头文件:

cpp 复制代码
#include <string>

如果要输出,还需要:

cpp 复制代码
#include <iostream>

常见写法:

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

int main()
{
    string s = "hello";
    cout << s << endl;
    return 0;
}

如果不写:

cpp 复制代码
using namespace std;

就需要写完整命名空间:

cpp 复制代码
std::string s = "hello";
std::cout << s << std::endl;

入门练习可以使用 using namespace std;

但大型项目和头文件中,更建议避免直接展开整个 std 命名空间。


九、auto:让类型推导更轻松

9.1 auto 的新含义

C++11 之后,auto 的含义发生了重要变化。

它不再主要表示自动存储类型,而是表示:

让编译器根据初始化表达式自动推导变量类型。

比如:

cpp 复制代码
int a = 10;
auto b = a;

这里 b 会被推导成 int

再比如:

cpp 复制代码
auto c = 'a';
auto d = 3.14;

cchar

ddouble


9.2 auto 必须初始化

因为 auto 需要根据右边的值推导类型,所以必须初始化。

错误写法:

cpp 复制代码
auto x;

编译器不知道 x 应该是什么类型。

正确写法:

cpp 复制代码
auto x = 10;

9.3 auto 和指针、引用

指针可以这样写:

cpp 复制代码
int x = 10;

auto p1 = &x;
auto* p2 = &x;

这里 p1p2 都是 int*

但是引用要显式写 &

cpp 复制代码
auto& r = x;

如果写:

cpp 复制代码
auto r = x;

那么 r 是一个新的 int 变量,不是引用。

这点在范围 for 中尤其重要。


9.4 auto 在 STL 中很好用

STL 中很多类型写起来很长。

比如:

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

可以简化成:

cpp 复制代码
auto it = dict.begin();

这不是偷懒,而是合理使用类型推导。

尤其是迭代器类型很长时,auto 能明显减少代码噪音。


十、范围 for:更自然地遍历容器

10.1 普通遍历

以前遍历数组可能会写:

cpp 复制代码
int arr[] = {1, 2, 3, 4, 5};

for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
    cout << arr[i] << " ";
}

这种写法没问题,但比较啰嗦,而且边界容易写错。


10.2 范围 for 写法

C++11 引入了范围 for

cpp 复制代码
for (auto e : arr)
{
    cout << e << " ";
}

string 也可以这样遍历:

cpp 复制代码
string s = "hello";

for (auto ch : s)
{
    cout << ch << " ";
}

这段代码会依次取出字符串中的每个字符。


10.3 修改元素时要用引用

如果只是读取,可以写:

cpp 复制代码
for (auto ch : s)
{
    cout << ch << " ";
}

如果要修改字符串中的字符,就要写引用:

cpp 复制代码
for (auto& ch : s)
{
    ch = toupper(ch);
}

因为:

cpp 复制代码
auto ch

拿到的是元素副本。

cpp 复制代码
auto& ch

拿到的才是原元素引用。

这点非常关键。


十一、string 的常见构造方式

11.1 构造空字符串

cpp 复制代码
string s1;

这会构造一个空字符串。

可以理解为:

cpp 复制代码
""

11.2 用 C 字符串构造

cpp 复制代码
string s2("hello");

或者:

cpp 复制代码
string s2 = "hello";

这是最常见的写法。

它会用 C 风格字符串构造一个 string 对象。


11.3 拷贝构造

cpp 复制代码
string s3(s2);

或者:

cpp 复制代码
string s3 = s2;

这里用已有的 string 对象构造新对象。


11.4 构造 n 个相同字符

cpp 复制代码
string s4(5, 'x');

结果是:

cpp 复制代码
"xxxxx"

这个构造方式在刷题中偶尔很有用。

比如需要创建一段固定长度的字符串。


相关推荐
旖-旎1 小时前
《LeetCode 200 FloodFill 岛屿数量DFS解法》
c++·算法·深度优先·力扣·floodfill
hnult1 小时前
2026在线笔试平台选型指南:考试云九重防作弊与六大AI能力解析
人工智能·笔记·microsoft·课程设计
zdr尽职尽责1 小时前
Unity录像功能
学习·ui·unity·游戏引擎
skywalk81631 小时前
继续推进心语项目6.15 @CodeArts
开发语言·算法·编程
前进吧-程序员1 小时前
反转链表完全指南:辅助容器、三指针、头插法
数据结构·c++·链表
我不是懒洋洋1 小时前
从零实现一个分布式配置中心:服务发现与热更新
c++
嵌入式-老费1 小时前
esp32开发与应用(看门狗测试)
java·开发语言·数据库
省四收割者1 小时前
从硬件中断到分布式协程:全景解构高并发机制与 C / Golang 的巅峰对决
c++·分布式·嵌入式硬件·golang
闫有尽意无琼1 小时前
qt控件未指定父对象或delete致堆内存泄露
开发语言·qt