【C++STL:string(上)】:手把手教你使用官方文档高效学习string,并熟练使用string的常用接口
前言
之前我们介绍了类和对象(四个常用的默认成员函数)、STL概述(包罗数据结构和算法的软件框架)、编码表(UTF-8、ASCII、GBK)和两个现现代C++语法auto关键字和范围for循环。那么本篇博主就带大家探索第一个STL容器——string。
目录
(一)为什么学习string类
1.c语言中字符串的弊端
C语言中字符串是以‘\0’结尾的一些字符的集合,为了操作方便,C语言库中提供了一些str系列的库函数,但是这些库函数和字符串还是分离的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不注意可能就会引起越界访问。
注释:OOP(Object-Oriented Programming)指面向对象编程。
2.两个面试题
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
例:
PS:这两道题目会在后续string类的题目总结中分享,请读者持续关注!
(二)标准库里的string类
1.string - C++ Reference
该标题为string类的文档介绍,我们无论是在以后的C++学习中还是在未来工作中都必须具备独立看文档的能力,大多数权威网站都是英文的,大家务必按住性子逐字逐句分析语法语义。
在前期我们可以借助一些翻译软件,如:有道词典、彩云小译,实在不行也可以借助大模型(浏览器自带的翻译实在是太垃圾了)但尽量自己看文档,因为STL设计接口名字时都不会乱起名,基本上都是相通的,有些接口的参数设置也是有规律的,有一定经验后基本不需要翻译软件就能看懂个七七八八。
以string类为例,对比一下浏览器自带翻译和彩云小译:

彩云小译 译为:

浏览器自带翻译 译为:

注意:在使用string类时,必须包含#include头文件以及using namespace std;
2.auto关键字和范围for
简单介绍
auto 让编译器根据初始化表达式自动推导变量的类型。
范围for是基于范围的 for 循环用于遍历容器或数组,其内部原理是编译器将其展开为基于迭代器的普通循环。自动取容器数据赋值,自动迭代++,自动判断结束。
详细介绍见上篇最后一部分:
【C++初阶】STL 开篇:站在巨人肩膀上,先聊聊编码和现代语法-CSDN博客
3.string类的常用接口说明

在文档中我们可以看出它把string类的接口分为了以下几类:
1.Member functions(成员函数)
2.Iterators(迭代器)
3.Capacity(容量相关接口)
4.Element access(元素访问)
5.Modifiers(修改字符串内容的操作)
6.String operations(字符串运算)
1.Member functions(成员函数)
默认构造、拷贝构造:
| string::string - C++ Reference函数名称 | 功能说明 |
| string() | 构造空的string对象,即空字符串 |
| string(const char* s) | 用C-string来构造string类对象 |
| string(size_t n,cahr c) | string 类独享中包含n个字符c |
| string(const string&s) | 拷贝构造函数 |
使用方式:
void Test_String()
{
string s1;
string s2("abcde");
string s3(4,'x');
string s4(s3);
}
运行结果:

赋值运算符重载:
void Test(){
string s1="aaabbb";
}
2.Iterators(迭代器)
定义
迭代器是一种统一访问容器元素的方式,它像一个“通用的指针”,让你可以遍历容器(如 vector、string、list 等)中的元素,而无需关心容器的内部结构。
格式
string::iterator it=str.begin();

begin():指向字符串第一位有效字符。
end():指向'\0'的位置。
rbegin():从后往前遍历,指向字符串最后一位有效字符('\0'前一位)。
rend():指向字符串第一位有效字符的前一个内存单元。
const对象如何使用迭代器
const string s2="hello world";
string::const_iterator it2 = s2.begin();
注意:
首先,s2 是一个 const string 对象,表示其内容不可修改。
其次,对于 const 对象,调用 begin() 会返回 const_iterator(而不是普通的 iterator)。const_iterator 只能读取元素,不能修改。
最后,string::const_iterator it2 = s2.begin(); 用返回的 const_iterator 初始化 it2,类型匹配,合法。

3.Capacity(容量相关接口)

1.size
size和 length的功能在string类里一致,是因为在STL出现前string类已被开发,string类使用的是length,但STL统一规范的大小是用size表示。size为通用接口,所以这里仅介绍size。
int main()
{
string s1("abcde");
cout << s1.size() << endl;//输出5
return 0;
}
说明:size大小不包括' \0',仅表示有效字符的个数。
2.resize

int main()
{
string s1("abcde");
string s2(s1);
cout << s1.size() << endl;
s1.resize(2);
cout << s1 << endl;
s2.resize(10, 'x');
cout << s2 << endl;
return 0;
}
运行结果

3.reserve

接下来博主将会使用vs和gcc两种编译器向你展示不同编译器是如何处理reserve请求的容量变更的。
vs环境下:
void Test_String4()
{
string s1("hello world");
cout << s1.capacity() << endl;
s1.reserve(20);//变更大
cout << s1.capacity() << endl;
s1.reserve(14);//不做修改
cout << s1.capacity() << endl;
}
运行结果

linux环境下:

总结:如果用reserve缩小容量,vs直接无视缩容指令,而gcc会酌情缩容,但一定会保证容量大小大于size。
作用:若确定知道需要多少大小,可以直接reserve,一步到位,避免多次扩容,提高效率。
4.clear
擦除字符串的内容,使其成为空字符串 (长度为 0 个字符)。
void Test_String4()
{
string s1("hello world");
cout << s1.size() << endl; // 11
cout << s1.capacity() << endl; // 15
s1.clear();
cout << s1.size() << endl; // 0
cout << s1.capacity() << endl; // 15
}
通过输出结果可以看出:clear会使size清零,但不会影响capacity。
5.empty
返回字符串是否为空 (即其长度是否为 0)。
4.Element access(元素访问)
1.operator [ ]
void Test_String2()
{
//[]运算符重载
string s1 = "abcde";
cout << s1 << endl;
s1[0] = 'x';
cout << s1 << endl; //xbcde
//s1[6]; //越界访问, 触发断言终止
//遍历,修改
for (size_t i = 0;i < s1.size();i++)
{
s1[i]++;
}
cout << s1 << endl; //ycdef
}

意义:使对象像数组一样使用,string、vector底层都是数组。C语言数组的月结检查是一种抽查,在数组的结束位置设置几个标志位,C++在operator [ ]实现时加上了断言,对内存隐私更有保障。

2.at

与operator [ ]相比,at越界访问的表现更温和,抛异常但不会强制终止程序。
void Test_String3()
{
string s1 = "abcde";
cout << s1 << endl;
s1[0] = 'x';
cout << s1 << endl;
//s1[6]; //触发断言终止
//at 接口: 抛异常
s1.at(6) = 'y';
cout << s1 << endl;
}
int main()
{
try
{
Test_String3();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
运行结果:

3.使用迭代器实现元素遍历和修改
//迭代器:行为类似于指针
//正向遍历,修改
//string::iterator it1 = s1.begin();
auto it1 = s1.begin();
for (size_t i = 0;it1 != s1.end();i++)
{
(*it1)--;
it1++;
}
cout << s1 << endl;
(三)本文完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<list>
#include<algorithm>
using namespace std;
void Test_String1()
{
//1
string s1 = "come on";//拷贝初始化
string s2("come on");//直接初始化,与s1效果一样
//3
string s3(s2);
//4 string (const string& str, size_t pos, size_t len = npos);
string s4(s2, 0);
string s5(s4, 2, 5);
//5 string (const char* s, size_t n);
string s6("I love C++", 6);
cout << s1 << endl << s2 << endl << s3 << endl << s4 << endl << s5 << endl << s6;
}
void Test_String2()
{
//[]运算符重载
string s1 = "abcde";
cout << s1 << endl;
s1[0] = 'x';
cout << s1 << endl;
//s1[6]; //触发断言终止
//at 接口: 抛异常
s1.at(1) = 'y';
cout << s1 << endl;
//遍历,修改
for (size_t i = 0;i < s1.size();i++)
{
s1[i]++;
}
cout << s1 << endl;
//迭代器:行为类似于指针
//正向遍历,修改
//string::iterator it1 = s1.begin();
auto it1 = s1.begin();
for (size_t i = 0;it1 != s1.end();i++)
{
(*it1)--;
it1++;
}
cout << s1 << endl;
//const对象调用迭代器
const string s2="hello world";
string::const_iterator it2 = s2.begin();
//const反向迭代
string::const_reverse_iterator tit2 = s2.rbegin();
list<int> lit;
lit.push_back(1);
lit.push_back(2);
lit.push_back(3);
string::iterator ret = find(s1.begin(), s1.end(), 'x');
if (ret != s1.end())
{
cout << "找到x" << endl;
}
list<int>::iterator ret2 = find(lit.begin(), lit.end(), 2);
//char* ret3= find(s1.begin(), s1.end(), 'x');
// 引用
int i = 0;
int& r1 = i;
// r2不是int&引用,是int
auto r2 = r1;
//r3是int& 引用
auto& r3 = r1;
cout << &r2 << endl;
cout << &r1 << endl;
cout << &i << endl;
cout << &r3 << endl;
}
void Test_String3()
{
//string s1 = "引用";
//s1[0]++;
//cout << s1 << endl;
string s1 = "abcde";
cout << s1 << endl;
s1[0] = 'x';
cout << s1 << endl;
//s1[6]; //触发断言终止
//at 接口: 抛异常
s1.at(6) = 'y';
cout << s1 << endl;
}
void Test_String4()
{
string s1("hello world");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
//s1.reserve(20);
//cout << s1.capacity() << endl;
//s1.reserve(14);
//cout << s1.capacity() << endl;
}
void Test_String5()
{
//[]运算符重载
string s1 = "abcde";
cout << s1 << endl;
s1[0] = 'x';
cout << s1 << endl;
//s1[6]; //越界访问, 触发断言终止
//遍历,修改
for (size_t i = 0;i < s1.size();i++)
{
s1[i]++;
}
cout << s1 << endl;
}
int main()
{
try
{
Test_String3();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
/*Test_String3();*/
//string s1("abcde");
//string s2(s1);
//cout << s1.size() << endl;
//s1.resize(1);
//cout << s1 << endl;
//s2.resize(10, 'x');
//cout<<s2<<endl;
//Test_String5();
return 0;
}
小结
本篇作为 std::string 的入门篇,我们从 C 语言字符串的弊端出发,明确了学习 string 类的必要性,并给出了两道经典的面试题作为引子。
接着,我们简要回顾了 auto 关键字和范围 for 的基本原理。
在 string 类的常用接口部分,我们重点学习了:
成员函数:默认构造、拷贝构造、赋值运算符重载,掌握了如何创建和复制字符串对象。
迭代器:理解了迭代器作为“通用指针”的作用,知道了 begin()/end() 的用法,并区分了 const_iterator 与普通迭代器的使用场景。
容量相关接口:size() 获取字节数,resize() 改变有效字符个数,reserve() 预分配内存以避免频繁扩容,clear() 清空内容,empty() 判空。
元素访问:operator[] 和 at() 的区别(后者边界检查),以及如何利用迭代器遍历和修改字符串。
由于篇幅有限,string 的 修改器(Modifiers)(如 append、insert、erase、replace、push_back、pop_back 等)以及 字符串操作(String operations)(如 c_str、find、rfind、substr 等)尚未展开。这些内容将在下一篇博客中详细讲解,并会结合大量示例展示如何高效地操作字符串。
创作不易,如果你觉得有帮助,欢迎点赞、收藏、评论。我们下篇见!
更多推荐


所有评论(0)