【C++STL】一文掌握 String 核心接口:从基础到实用! - 详解

前言:结束了上一阶段的学习,在有了一定的语法基础以后下面我们就要进入C++的STL部分,STL在C++中占有极其重要的地位,下面我们就走进STL的世界,开始STL部分的学习。

在这里插入图片描述

✨ 坚持用 清晰易懂的图解 + 代码语言, 让每个知识点都 简单直观
个人主页MSTcheng · CSDN
代码仓库MSTcheng · Gitee
专栏系列

座右铭“路虽远行则将至,事虽难做则必成!”

一,STL简介

1.1 什么是STL?

STL的概念:STL(Standard Template Library)是C++标准库的核心组成部分,提供了一套通用的模板类和函数,用于实现常见的数据结构和算法。

1.2 STL的六大组件

STL的六大组件是它的核心我们在学习STL的时候也是围绕着这六大组件来进行学习,下面看看六大组件都有哪些:

在这里插入图片描述

1.3 如何学习STL

STL提供了一套通用的模板和函数,用于实现常见的数据结构,那么这些函数和数据结构我们首先要学会使用,其实就是了解一下这些数据结构的底层是如何实现的。
所以学习STL的步骤就是1.学会使用 2.了解底层 (后面的STL学习都会按照这两个步骤来学习)

声明:下面各种成员函数和非成员函数的参数返回值,可以去string文档中查询,链接:cpluspuls.com

二,第一个STL容器—string

2.1 string的4个默认构造的使用

函数名称功能说明
string()构造空的string类对象,即空字符串(重点)
string(const char* s)用C风格字符串(C-string)构造string类对象(重点)
string(size_t n, char c)构造包含n个字符cstring类对象
string(const string& s)通过拷贝另一个string类对象构造新对象(重点)

我们先来看看库里面的一些函数重载:

在这里插入图片描述在这里插入图片描述

void TestString1()
{
string s1;//创建一个空字符串对象
string s2("hello world");//构造
string s3(s2);//拷贝构造
cout << s3 << endl;
string s4(s2, 1, 8);//相当于memcopy
//string s4(s1, 1, 60); 
cout << s4 << endl;
string s6(s2, 1);//不写长度那就从1开始拷贝 直到s2被拷贝完
cout << s6 << endl;
const char* str1 = "hello word";
string s7(str1,5); //使用char* 的字符串构造
cout << s7 << endl;
string s8(100, '*');//将s8初始化成100个*
cout << s8 << endl;
//赋值运算符重载 s1.operator=()
s1 = s2;
s1 = "*******";
cout << s1 << endl;
}

还有一个析构函数用于释放string内部的资源,在模拟实现部分会详细说明。

三,初识迭代器

3.1 使用迭代器遍历string

学习一个数据结构就要学会它的遍历,因为在使用数据结构的时候我们常常会修改数据结构内部的值,如果我们要一一进行修改那么就一定会用到遍历。在学习C语言的时候常见的遍历方式是使用for循环,while循环等,借助循环来遍历。而C++搞了一个新的遍历方式叫迭代器,它是所有容器的主流遍历方式,下面我们重点讲解它。

函数/语法功能说明
begin + endbegin获取字符串第一个字符的迭代器,end获取最后一个字符下一个位置的迭代器
rbegin + rendrbegin获取字符串最后一个字符的逆向迭代器,rend获取第一个字符前一个位置的逆向迭代器
范围for (C++11)支持更简洁的范围遍历方式,无需显式使用迭代器或下标

1、传统遍历方式

void TestString2()
{
string s1("hello word!");
const string s2("hello bite");
//s1[0]++ -> s1.operator[](0)++  使用重载的方括号
//第一种遍历方式 使用循环
//循环遍历字符串 并修改字符串  s1.size()是string类里的成员函数 返回的是字符串的大小
for (size_t i = 0;i < s1.size();i++)
{
s1[i]++;
}
cout << s1 << endl;
}

2、使用迭代器

void TestString3()
{
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
//it就是像指针一样的东西
(*it1)++;//*it就可以拿到对应位置的数据 进行修改
//(*it1)--;
}
cout << s1 << endl;
//将迭代器使用在顺序表(vector)
vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  //这里的vector模板有显示限定 int
  vector<int>::iterator it2 = v.begin();
    while (it2 != v.end())
    {
    cout << *it2 << " ";
    ++it2;
    }
    cout << endl;

在这里插入图片描述

const迭代器与普通迭代器

在这里插入图片描述

正向迭代器和反向迭代器

在这里插入图片描述

3、auto关键字

还有另外一种遍历方式就是使用范围for。但在介绍范围for之前我们先来介绍一下auto关键字

void auto_test()
{
//auto是C++11支持的一种能够自动识别类型的关键字 
auto x = 10;
auto y = 10.1;
cout << x << endl;//自动推出类型为int类型
cout << y << endl;//自动推出类型为double类型
//auto声明引用类型时必须要使用&
int& z = x;
auto m = z;//这一步是赋值 如果想要m++ z和x也跟着++ 就要加上&即auto& m = z;
m++;
//用auto声明指针类型时,用auto和auto*没有任何区别
auto p1 = &x;
// 只能是指针
auto* p2 = &x;
}

在这里插入图片描述

auto的注意事项:

  1. 当使用auto在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错。因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
  2. auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
  3. auto不能直接用来声明数组

4、范围for

void TestString4()
{
string s1("hello world");
for(auto e:s1)
{
//范围for内部可以修改s1
//e--;
cout<<e<<endl;
}
//遍历vector
vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  for (auto e : v)
  {
  cout << e << " ";
  }
  cout << endl;
  }

在这里插入图片描述

范围for的注意事项:

  1. 范围for的三个自动:自动迭代、自动取数据、自动判断结束。这一点相比于上面的迭代器又更加的方便了,原因在于它是自动的。
  2. 范围for可以作用到数组和其他的容器对象上进行遍历。
  3. 范围for的底层其实就是替换为了迭代器,这也是它为什么能够支持遍历其他容器的原因。

总结—迭代器的意义:
1: 统一类似的方式遍历修改容器
2: 算法脱离具体底层结构,和底层结构解耦
3: 算法独立模板实现针对多个容器处理

四、string与容量有关的接口使用

4.1 string成员函数功能说明表

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回字符串分配的空间总大小
empty (重点)检测字符串是否为空串,是则返回 true,否则返回 false
clear (重点)清空字符串中的有效字符
reserve (重点)为字符串预留空间
resize (重点)将有效字符的个数调整为 n 个,多出的空间用字符 c 填充
void stringTest1()
{
string s1("123456");
//无论是s.size() 还是s.capacity()都是不包括\0的
cout << s1.size() << endl;
cout << s1.capacity() << endl; //capacity可能大于或等于字符串的长度 大于因为capacity底层会扩容
cout << s1.length() << endl;
//如果非空就打印
if (!s1.empty())
{
cout << s1 << endl;
}
s1.clear();//清空字符串就是将size(有效数据个数)改成0
cout << s1.size() << endl;
string s2;
size_t old = s2.capacity();
cout << endl;
cout << "s2.capacity:" << old << endl;
//string的底层实际上就是一个顺序表,所以不断的往里面插入数据就会引发扩容逻辑
for (size_t i = 0;i < 100;i++)
{
s2.push_back('x');
if (s2.capacity() != old)
{
cout << "capacity:" << s2.capacity() << endl;
old = s2.capacity();
}
}
//使用reserve 知道要开多少空间提前开好 避免多次扩容,提升效率
s2.reserve(100);//扩容可以
cout <<s2.capacity()<< endl;
s2.reserve(5);//缩容不行
cout <<s2.capacity()<< endl;
//resize 插入数据 让size达到我们定义的n 个
// n > capacity > size;
string s2("123456");
cout << endl;
s2.resize(20, '#');//这里n给20 那么size就从原来的6变成20,第二个参数不给默认用空格填充
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
}

上面的接口在使用中有几点需要注意:

  1. resize接口是修改有效数据个数的接口,在改变元素个数时,如果是将元素个数增多,那么可能会影响capacity导致扩容,如果是将元素减少则底层总空间大小不变。
  2. reserve接口是修改string总空间的接口,它不会影响有效数据个数。在改变空间大小时,如果比原来空间大那么久扩容到指定的这个空间,如果比原来的空间小并不会将空间缩小!!!
  3. clear接口单纯修改有效数据个数,不改变底层的空间大小!

五、元素访问相关的接口

5.1 元素访问函数功能表格

函数名称功能说明区别
operator[] (重点)返回 pos 位置的字符,const string 类对象可调用若越界访问,行为未定义(可能崩溃或返回垃圾值)
at返回 pos 位置的字符,提供边界检查若越界访问,抛出 std::out_of_range 异常,安全性更高

:表格对比了两种访问字符串字符的方法,核心差异在于越界处理机制。

void stringTest3()
{
//方括号操作符 本质是函数重载
string s1("hello word!");
s1[0]++;
cout << s1 << endl;
//at用于返回该下标处的内容
//cout << s1.at(2) << endl;
s1.at(0)--;
cout << s1 <<endl;
//访问的下标越界 不属于当前空间 抛异常
//s1.at(15);
}

六、修改相关的接口使用

6.1 修改相关的函数说明表格

函数名称功能说明
push_back在字符串末尾插入字符c
append在字符串后追加一个字符串
operator+=(重点)在字符串后追加字符串str
c_str(重点)返回C格式的字符数组
find + npos(重点)从字符串pos位置开始向后查找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始向前查找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符并返回
void test2()
{
string s1("abcdefg");
//string底层就是一个字符数组 对于string的操作就相当于对数组的操作
s1.push_back('h');//push_back就是尾插往size位置处插入数据
s1.push_back('i');
s1.append("hello!JJJJ");//append就是往s1后面追加一个字符串
cout << s1<< endl;
//c_str()用于当使用C语言想要打开C++的文件时,test.cpp就是一个string类型
//但是C语言又没有string这种东西 但是可以使用c_str返回C语言底层的那个字符数组这样就可以打开文件了
string filename("test.cpp");
FILE* fout = fopen(filename.c_str(), "r");
if (fout == nullptr)
{
cout << "fopen error" << endl;
return;
}
string s2("hello   world")
size_t ret=s1.find(0," ");//find函数,从起始位置开始查找指定的字符,找到了返回该字符所在位置的下标
//size_t ret2=s1.rfind(0," ")//refind接口就是从尾部开始倒着查找
while (pos != string::npos)//npos 是string类里面定义的一个数据 它的类型是size_t 值为-1相当于是整型的最大值   所以npos就代表字符串能存储的最大长度pos!=npos就是说该位置如果不等于最大字符串长度 就说明找到了 否则就说明没找到 
{
s2[pos] = '#';
//s2.replace(pos, 2,"##"); //replace接口 替换指定位置的字符
pos = s2.find(' ',pos+2);
}
cout << s2 << endl;
string s3=s2.substr(0, 5);//从s2中截取5个字符给s3
cout << s3 << endl;
}

七,string类非成员函数(全局函数)

7.1 非成员函数功能表格

运算符/函数功能说明注意事项
operator+字符串拼接,返回新字符串对象传值返回导致深拷贝,频繁调用可能影响性能,建议少用
operator>> (重点)输入运算符重载,从输入流提取内容到字符串,遇空格终止需配合istream使用,注意处理缓冲区及错误状态
operator<< (重点)输出运算符重载,将字符串内容插入到输出流常用于日志打印或控制台输出,无格式限制
getline (重点)从输入流读取一行字符串(包括空格),可指定分隔符需清除换行符残留,避免与operator>>混用时出错
relational operators (重点)比较字符串大小(如==, !=, <, >等),按字典序逐字符对比区分大小写,比较前需确保字符串非空或已初始化
void stringTest8()
{
string s1("hello"), s2("hello");
cout << s1 << " " << s2 << endl;
string s3 = s1 + s2;//operator+用于字符串拼接
cout << s3 << endl;//流插入重载
bool a = s1 == s2;//判断大小关系 有== >= <= < > !=
cout << a << endl;
//cin >> s1 >> s2;//流提取重载
//cout << s1 <<" "<< s2 << endl;
string line;
//cin >> line;//流插入重载
getline(cin, line);//获取一行字符串 区别与scanf scanf识别到空格就结束 
//而getline不同 getline是识别到换行才结束
cout << line << endl;
}

注意:这里的函数之所以搞成全局函数而不搞成string内部的函数是因为,如果搞成string内部函数那么第一个参数就定死了是string类型。因为成员函数中第一个参数要传给this指针,this指针的类型就是该类的类型。这些函数如果重载成成员函数就无法实现const char* 的类型作为参数了!

以上就是本篇文章的所有内容了,感谢各位大佬观看,制作不易还望各位大佬点赞支持一下!有什么问题可以加我私信交流!
posted @ 2025-11-09 22:47  ycfenxi  阅读(74)  评论(0)    收藏  举报