• 【C++】STL简介 | string类的常用接口


    目录

    STL简介

    学string类前的铺垫

    概念

    为什么要学string类

    string类的底层(了解)

    编码表的故事

    string类的常用接口与应用

    3个必掌握的构造

    读文档必掌握的npos

    赋值

    访问字符operator[](必掌握)

    初识迭代器(iterator)

    反向迭代器

    用范围for遍历

    string类对象的修改操作

    插字符push_back()

    插字符串append()

    超好用的operator+=(必掌握)

    指定位置插字符insert()

    string类对象的字符串操作

    把string返回成char*的c_str

    查找位置的find()

    截取子字符串的substr()

    string类对象的容量操作

    只开空间的reserve()

    改变size+初始化的resize()

    string与其他类型之间的转换


    STL简介

    什么是STL?

    全称为Standard Template Library(标准模板库),是C++标准库的重要组成部分。

    它不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

    通俗来说,STL就是将常见的数据结构(如顺序表,链表,栈,队列,二叉树,哈希等)以模板的形式进行封装,使用时,不用人为再

    去写,可以直接调用。并且,一些常见的通用的算法也不用自己实现,可以直接从STL调用。

    六大组件:

    这个目前了解即可,后面会具体学到。

    STL的众多组件中,我们从容器里的string类学起。

    学string类前的铺垫

    概念

    String类是用于处理字符串的类。String类提供了一系列的方法,用于处理字符串,包括连接字符串、查找子字符串、获取字符串长度等。

    为什么要学string类

    处理字符串的方法,我们之前在C语言中也学到过。如字符串函数strlen、strcpy、strcat……,对于字符串的处理主要是求长度、追加、查找等,这些就是string要代替那些字符串函数完成的操作。

    那已经有了字符串函数,为什么还要搞一个string类出来呢?

    这时因为,字符串函数与字符串是分离开的,不太符合面向对象编程 的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

    所以说,我们需要更好用的string类。

    string类的底层(了解)

    实际上,编C++的大佬并没有直接写一个string类,而是写了一个basic_string模板类。

    string类在底层上实际是:basic_string 模板类的别名。

    typedef basic_string<char, char_traits, allocator>string;

    string就是basic_string。是basic_string的char类型 的实例化。

    ❓问题来了:为什么不直接实现sring类,而是写一个模板呢?

    这与编码表有关,我们接着往下看。

    编码表的故事

    计算机最开始是从美国发源的。美国人的交流是建立在ABCD的字母组合上,而计算机只认得01串。

    为了在计算机上交流,美国人发明了一套ASKII编码,统一规定了字母和常用符号用哪些二进制数来表示。

    这样一来,如果我们写进了一个'A',那计算机中实际存储的是用01表示出的65:

    1. int main()
    2. {
    3. char a = 'A';
    4. return 0;
    5. }

    如果我们要存储"happy"这个单词的话,那计算机中实际存储的分别是h、a、p、p、y对应的ASKII码值。

    如果存数字1234,那会开4个char字节,依次存入1、2、3、4的ASKII值:

    补充:所以说,string类的123+string类的1,不等于124!而是1231:

    但ASKII编码表并不是万能的。对于美国等来说,字母加上常用字符有一百来个,一个字节八个比特位(最多表示256位)完全够用了。

    但对于文化博大精深的中国来说,汉字、繁体字的数量庞大,一个字节根本不够表示,这要怎么办呢?

    中国科学家齐心协力,自定了一套编码表,名叫GB2312。这时用两个字节,16个比特位,来表示常用的汉字。后来,经过不断更新,又出台了GBK标准,能表示更多的汉字。

    这里补充一点:在中国的编码表中,读音相同的字是编在一起的。所以,净网行动下,跟国粹读音相同的词都会被屏蔽。如果你发“卧槽”,结果是“* *”,如果改发“握草” “沃槽”,结果还是“ * *”。

    但是问题又来了:不同国家的人用不同的编码标准,一个美国人给一个中国人发文件,因为编码表译出来的不同,打开就是乱码了,这要怎么办?

    为了解决这个问题,科学家发明了Unicode(万国码)。它包含了世界上所有的符号,所有语言都可以互通,一个网页上也可以显示多国语言。

    但问题又来了:像中国这样文字多的国家,要用两个字节去表示字符,那对于原本一个字节就够用的语言,现在也要跟着用两个字节。这对计算机空间会造成极大的浪费。

    对此的解决方案是:UTF-8!

    “UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。”

    (源自:百度百科)

    所以说,这些字符不一定只占一个字节,也可能占两个字节甚至更多。因此,不能简单地实现string,需要一个模板,即basic_string。

    “basic_string是一个模板类,它是C++标准库中用于处理字符串的通用工具类。它提供了一种灵活且可扩展的方式来处理字符串,允许使用不同的字符类型(例如char、wchar_t(宽字节)等)和分配策略。"

    string类的常用接口与应用

    string的这些接口,不用全记。主要是多用,在运用中,常见的那几个就都掌握了。

    3个必掌握的构造

    下面三个标”重点“的, 要掌握

    注:用string类要包头文件#include

    函数名称功能说明
    string() (重点)构造空的string类对象,即空字符串
    string(const char* s) (重点)将char类型的字符串转换成string类型的字符串
    string(size_t n, char c)string类对象中包含n个字符c
    string(const string& s) (重点)拷贝构造函数

    演示:

    1. #include
    2. #include    
    3. using namespace std;
    4. int main()
    5. {
    6. string s1; //构造空的string类对象
    7. string s2("hello"); //用C-string来构造string类对象
    8. string s3(s2);   //拷贝构造
    9. cout << s1 << endl;
    10. cout << s2 << endl;
    11. cout << s3 << endl;
    12. return 0;
    13. }

    其他构造就不一一演示了,最重要的是得学会看文档。通过文档,我们能快速上手它的用法。

    下面演示,通过看文档,掌握substring:

    用起来:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");  
    7. string s2(s1, 0, 2); //从s1的第0位开始,往后拷贝2位
    8. cout << s1 << endl;
    9. cout << s2 << endl;
    10. return 0;
    11. }

    读文档必掌握的npos

    在读文档时,我们常能看到“npos"。啥意思?

    npos是一个常数,表示size_t的最大值。容器里出现npos,表示不存在的位置。

    npos的一些用法:

    1.string::npos作为函数返回类型,表示找不到。

    例:

    1. if(pos==string::npos){     //表示pos不存在
    2. cout<<"cannot found";
    3. }
    4. if(pos!=string::npos){   //表示pos存在
    5. ……
    6. }

    赋值

    有三种赋值方式:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello"),s2;
    7. s2 = s1;         //Way 1
    8. cout << s2 << endl;
    9. s2 = "happy";   //Way 2
    10. cout << s2 << endl;
    11. s2 = 'h';       //Way 3
    12. cout << s2 << endl;
    13. return 0;
    14. }

    访问字符operator[](必掌握)

    string实现了operator[],可以访问字符串的字符。

    1.修改

    因为返回的是引用,所以我们可以对返回的该字符进行修改。这使字符串用起来就像数组一样:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. cout << s1[1] << endl;
    8. s1[1] = 'p';
    9. cout << s1 << endl;
    10. return 0;
    11. }

    2.遍历

    也可以遍历数组,配合size()使用:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. for (int i = 0; i < s1.size(); i++) {
    8. cout << s1[i] << " ";
    9. }
    10. return 0;
    11. }

    补充说明一点:其实有个跟size()功能差不多的函数:length(),也能求字符串长度。

    但是更推荐用size(),因为它更通用。许多容器里不能用length(),但可以用size()。

    3.检查越界

    operator[]内部会检查越界,一旦越界就会报错。

    示例:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. cout << s1[10] << endl; //越界
    8. return 0;
    9. }

    4.(了解) 功能相同的at()

    at()的功能跟operator()相同,也是访问字符。

    at()是像函数一样去调用:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. for (int i = 0; i < s1.size(); i++) {
    8. cout << s1.at(i) << " "; //注意调用方式
    9. }
    10. return 0;
    11. }

    那at()和operator()的区别是什么呢?

    两者检查越界的方式不一样。opreator()是断言,激进一点的处理方式,断言会把程序直接终止掉。at()是抛异常,比较温和的处理方式。一般用try catrch来捕获异常,程序还能继续跑。

    初识迭代器(iterator)

    迭代器是STL框架设计的六大组件之一,非常重要。现阶段先对迭代器做一个大致的了解。

    什么是迭代器 (iterator)?

    "迭代器是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。

    迭代器修改了常规指针的接口,迭代器是一种概念上的抽象,那些行为上像迭代器的东西都可以叫做迭代器。迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。"

    (源自百度百科)

    现阶段我们对迭代器的理解是:像指针一样的类型,有可能就是指针,也可能不是指针,但用法像指针一样。(内部细节得后面我们自己实现了才知道)

    那我们就先认为它是指针。

    迭代器都是在类的内部定义的,属于这个类域。

    如果要用的话,记得标明类域:

    string::iterator

    一个类里面有四种迭代器:

    1. iterator
    2. const_iterator
    3. reverse_iterator
    4. const_reverse_iterator

    接下来,每种迭代器都用来遍历一下。

    ➡️用迭代器iterator去遍历:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. string::iterator it = s1.begin();
    8. while (it != s1.end()) { //Q:这里用<可以吗? A:于string可以,但是其他容器可能就不行了。!=是通用的写法
    9. cout << *it << " "; //用法像指针
    10. it++;
    11. }
    12. return 0;
    13. }

    begin获取一个字符的迭代器 ,end获取最后一个字符后一个位置的迭代器:

    ➡️用const_iterator去遍历:

    1. #include
    2. #include
    3. using namespace std;
    4. void PrintString(const string& s) {
    5. string::const_iterator it = s.begin(); //形参被const修饰,此时不能再用iterator,不然是权限的放大
    6. while (it != s.end()) {
    7. cout << *it << " ";
    8. it++;
    9. }
    10. cout << endl;
    11. }
    12. int main()
    13. {
    14. string s1("hello");
    15. PrintString(s1);
    16. return 0;
    17. }

    补充:

    其实string不太用迭代器,因为operator[]更好用。

    那用迭代器遍历的意义是什么呢?

    因为iterator是所有容器通用的访问方式,并且用法类似。某些容器如map/list/set……只能用迭代器访问。

    反向迭代器

    (先了解一下,后面会手把手实现反向迭代器)

    反向迭代器,顾名思义,可以实现反向遍历数组。

    reverse_iterator   //反向迭代器

    ➡️使用reverse_iterator遍历:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. string::reverse_iterator rit = s1.rbegin();   //使用反向迭代器
    8. while (rit != s1.rend()) {
    9. cout << *rit << " ";
    10. rit++;
    11. }
    12. cout << endl;
    13. return 0;
    14. }

    右闭 (区间) 左开 (区间) :

    ➡️使用const_reverse_iterator遍历:

    1. #include
    2. #include
    3. using namespace std;
    4. void PrintString(const string& s) {
    5. string::const_reverse_iterator it = s.rbegin();   //注意得用const_reverse_iterator
    6. while (it != s.rend()) {
    7. cout << *it << " ";
    8. it++;
    9. }
    10. cout << endl;
    11. }
    12. int main()
    13. {
    14. string s1("hello");
    15. PrintString(s1);
    16. return 0;
    17. }

    用范围for遍历

    遍历字符串,除了上面讲到的两种方法,还可以用范围for去遍历:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello");
    7. for (auto e : s1) {   //把s1的每个字符取出来,依次赋给e
    8. cout << e << " ";
    9. }
    10. cout << endl;
    11. return 0;
    12. }

    范围for遍历的好处在于:既能自动迭代,又能自动判断结束。

    不过,范围for在底层实现上没啥技术含量,其实就是迭代器。(下一篇会模拟实现范围for)

    string类对象的修改操作

    插字符push_back()

    示例:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello ");
    7. s1.push_back('u');   //只能插入一个字符,不能插字符串!
    8. cout << s1 << endl;
    9. return 0;
    10. }

    插字符串append()

    这里用得最多的是(1)和(3):

    1. string& append(const string& str)
    2. string& append(const char*s)

    示例:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello ");
    7. s1.append("world");   //只能插字符串,不能插字符
    8. cout << s1 << endl;
    9. return 0;
    10. }

    超好用的operator+=(必掌握)

    刚刚那两位,在operator+=面前,就相形见绌了。operator+=不仅能加字符、字符串,甚至还能加对象!

    示例:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello ");  
    7. s1 += "world";   //插字符串
    8. s1 += '!';       //插字符
    9. string s2("see u soon");
    10. s1 += s2;         //插对象
    11. cout << s1 << endl;
    12. return 0;
    13. }

    指定位置插字符insert()

    要掌握(1)、(3)、(6):

    练习:

    力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台(字符串相加)

    这里提一嘴,insert、erase(删除)、replace这样的尽量少用,效率堪忧。

    string类对象的字符串操作

    把string返回成char*的c_str

    某些情况下,字符串需要兼容c的接口,这时就需要用c_str():

    注意看,它把string类的字符串用char*的形式返回回来了。也就是说,返回一个指向数组的指针。

    大部分情况下,string类型的字符串和char类型的字符串用着没差别。

    但下面这种是例外,用此例展示string和char的区别:

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. string s1("hello ");
    7. cout << s1 << endl;     //调用重载的流插入运算符打印的
    8. cout << s1.c_str() << endl;     //调用字符串打印的
    9. cout << endl;
    10. s1 += '\0';
    11. s1 += "world";
    12. cout << s1 << endl;         //string打印结束以size为准,不受'\0'的影响
    13. cout << s1.c_str() << endl;   //遇'\0'则停
    14. return 0;
    15. }

    应用场景:

    我们学过C语言中的fopen,他的左操作数就得为char*类型

    FILE * fopen ( const char * filename, const char * mode );

    那要用到fopen的场景下,string类就不能直接作为左操作数,而是要先转化成char*类型。

    1. int main()
    2. {
    3. string filename("text.txt");
    4. // FILE*pf=fopen(filename, "r");   //×
    5. FILE* pf = fopen(filename.c_str(), "w"); //√
    6. return 0;
    7. }

    查找位置的find()

    find:从pos位置开始,往后查找字符s,找到了返回其下标。如果不指定pos,默认从下标0开始找。

    1. size_t find (const string& str, size_t pos = 0) const;
    2. size_t find (const char* s, size_t pos = 0) const;
    3. size_t find (const char* s, size_t pos, size_t n) const;
    4. size_t find (char c, size_t pos = 0) const;

    示例:

    1. int main()
    2. {
    3. string s1("hello");
    4. cout << s1.find('e',0) << endl;  
    5. return 0;
    6. }

    镜像接口rfind:查找指定字符最后一次出现的位置,返回下标。

    截取子字符串的substr()

    substr: 从 pos 位置开始,截取 len 个字符 拷贝为子字符串,然后将子字符串返回。

    string substr (size_t pos = 0, size_t len = npos) const;

    示例:

    1. int main()
    2. {
    3. string s1("hello");
    4. cout << s1.substr(1,2) << endl;  
    5. return 0;
    6. }

    string类对象的容量操作

    只开空间的reserve()

    注意:是reserve(保留)不是reverse(逆置)。

    void reserve (size_t n = 0);

    当n大于当前字符串容量时,会扩容至n;

    当小于,则容量不变。

    此函数对字符串长度没有影响,也不能更改其内容。

    注意看capacity的变化:

    改变大小的resize()

    刚刚的reserve负责更改_capacity,现在的resize负责更改 _size,并且在改变大小的同时,resize也能追加字符。

    1. void resize (size_t n);     //⑴
    2. void resize (size_t n, char c);   //⑵

    示例⑴:注意看,多开的空间都用'\0'填充。

    示例⑵:

    string与其他类型之间的转换

    string类中提供了类型转换的功能,十分好用:

    stoi表示string to int,其他名称同理。

    示例:

    1. int main()
    2. {
    3. int val = 99;
    4. string str=to_string(val); //将int类型的99转成string类型
    5. str = "999";
    6. val = stoi(str);       //将string类型的999转成int类型
    7.                     //注:转变的是内容的类型,即99在string和int之间转换。而非val、str的类型
    8. return 0;
    9. }

  • 相关阅读:
    Spring-Aop面向切面编程
    pycharm打开远程宿主机或远程docker文件夹目录方法,以及设置代码同步
    Google Earth Engine(GEE)—— NDVI的CannyEdgeDetector边缘检测适用性分析
    线性混合模型(Linear Mixed Models)与R语言 lmer() 函数
    网络安全工程师薪资到底怎么样?_网络安全薪水一般多少
    抽象轻松的java——简单的购物车系统
    MySQL中比较运算符的使用
    从B-21轰炸机看美空军作战战略趋势
    CompletableFuture 异步调用,获取返回值
    CC1310F128RSMR Sub-1GHz超低功耗无线微控制器芯片
  • 原文地址:https://blog.csdn.net/2301_76540463/article/details/132998145