【C++】string类@STL

简介: string类

@toc
学习STL要勤查阅此网站:cplusplus.com - The C++ Resources Network

正文开始@边通书

0. string类

string类实际上是basic_string这个类模板的实例化 ——

<img src=" title="">
它的底层实现和顺序表差不多

template<class T>
class basic_string
{
    // ...
private:
    T* _str; //动态申请的
    size_t _size;
    size_t _capacity;
    // ...
};

可能令人疑惑的是,难道字符串类型中不都是字符吗,为什么还要有类模板呢?这就要说到不同的编码规则。

在ascii编码表中,将值和符号建立映射关系,1byte空间可以表示256个英文字符;再说unicode,是为了表示全世界文字的编码表,其中的utf-16方案,所有字符,无论中英还是啥,都是两字节表示(这样计算字符个数很方便,但是能表示字符也受限)。你可以认识的到,字符可不简单的是char,还可以是wchar宽字符等等。

<img src=" title="">

(关于编码,不是这里的重点,话说昨天的CSAPP课上,老师忽然讲起了编码,讲了好久演示了好多,真的很有意思!让我忽然觉得这门课好棒,然而后来他讲起了执行三条汇编指令计算机发生了什么,太tnd底层了,都把我讲磕头了哎哈哈)

下面介绍string类常用的接口:heart: ,要熟练掌握,其余的用时查阅即可。在使用string类时,需要包含头文件#include<string>以及展开命名空间using namespace std;

1. 构造&析构&赋值重载

:heart: 1. 构造函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5wNZrzHf-1646531818034)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220304191002348.png)]
调试演示 ——

<img src=" title="">

其余的接口简单演示,主要为了演示如何查阅文档。

:black_heart: 功能:从pos开始取对象的一部分(len)。

substring (3)    string (const string& str, size_t pos, size_t len = npos);

其中len给了缺省值nposnpos是string类的一个静态成员变量,值为-1,在补码中就是全1,赋给了无符号数size_t,就是整型的最大值4,294,967,295。因此,如果不传参采用缺省值,那就是有多少取多少。因为这个数字太大了42亿9千万,一个字符串就4G,可能吗?

<img src=" title="">

演示 ——

<img src=" title="">

注:string类对象支持直接用cincout进行输入和输出,因为重载了流插入>>和流提取<<操作符(后文详谈)。

:black_heart: 取字符串前n个

from sequence (5)    string (const char* s, size_t n);

<img src=" title="">

:black_heart: 填充初始化

fill (6)    string (size_t n, char c);

<img src=" title="">

:heart: 2. 析构函数

自动调用释放资源,不用管了。

:heart: 3. 赋值重载

<img src=" title="">

2. Capacity 容量操作

<img src=" title="">

2.1 size vs length

:heart:字符串中有效字符长度,即不包含最后作为结尾标识符的\0

<img src=" title="">

两者底层实现完全一致(length的存在是历史原因),但强烈推荐使用size. 这是为了和后序各种容器接口保持一致(各种容器接口表示多少个数据都用size,没有说你求二叉树的length的吧)

在这里插入图片描述

2.2 capacity

:heart: 容量存多少个有效字符(注意\0没算),要记得string类的底层是顺序表结构

<img src=" title="">

演示 ——

<img src=" title="">

2.3 resize vs reverse

reserveresize 都是改变容量,申请至少n个字符的空间(字符串涉及对齐问题,后续详谈) ,但有所不同 ——

:heart: 1. resize - 开空间,并可以对空间初始化

<img src=" title="">

  • 如果是将元素个数减少,会把多出size的字符抹去,这很符合resize这个函数的名字
  • 如果是将元素个数增多void resize (size_t n);\0来填充多出的元素空间,void resize (size_t n, char c);用字符c来填充多出的元素空间
  • 注:resize在改变元素个数时,如果是将元素个数增多,可能会改变容量的大小;如果是将元素个数size减少,容量不变。

调试可见 ——

#include<string>

using namespace std;

int main()
{
    string s1("more than words");
    s1.resize(5); // 1. size缩小到5,capacity不变

    string s2("more than words"); 
    s2.resize(100); // 2.1 填充\0 size->100, capacity ->111

    string s3("more than words");
    s3.resize(100,'!'); // 2.2 填充! size->100, capacity ->111

    return 0;
}

:heart: 2. reserve - 开空间。在已知需要多少空间时,调用reserve,可以避免频繁增容的消耗。

<img src=" title="">

  • 为字符串预留空间,改变容量。当然了不会改变有效元素个数size。
  • 当给reserve的参数n小于string的容量时,是无效请求,并不会改变容量大小。

调试可见 ——

#include<string>

using namespace std;

int main()
{
    string s1;
    s1.reserve(100); // size - 0,capcacity->111

    string s2("more than words");
    s2.reserve(5);   // capacity和size仍为15

    return 0;
}

2.5 clear

:heart: 清空有效字符,容量不变
<img src=" title="">

2.6 empty

:heart: 检测字符串是否为空串
<img src=" title="">

3. operator[]

:heart: 重载了[],使得string类可以像数组一样访问字符。不同的是,数组访问本质是解引用,而这里是调用函数。

它提供了两个版本 ——

<img src=" title="">

:heart: operator[]返回的是每个字符的引用,这使得它可读可写

引用,可以减少拷贝,但这里并不是。这里是做输出型参数,是为了支持修改返回对象

:yellow_heart: 1. 【遍历 + 修改】方法一 ——

#include<iostream>
#include<string>

using namespace std;

// 方式1:[下标]
int main()
{
    string s("more than words");
    // 1.可读
    for (size_t i = 0; i < s.size(); i++)
    {
        cout << s[i] << ' ' ;
        //等价于
        //cout << s.operator[](i) << " " <<; 
    }
    cout << endl;
    
    // 2.可写
    for (size_t i = 0; i < s.size(); i++)
    {
        s[i] += 1;
    }
    cout << s << endl;

    for (size_t i = 0; i < s.size(); i++)
    {
        s.at(i) -= 1;
    }
    cout << s << endl;
    return 0;
}

<img src=" title="">

注意:下面这两个函数功能一致(at的存在还是历史原因),只不过二者检查越界的方式不同,推荐使用[] ——

<img src=" title="">

4. Iterator 迭代器

本节将介绍第二种【遍历 + 修改】的方式:迭代器。迭代器是STL的六大组件之一,用来访问和修改这些数据结构。

看完本节你可能有这样的疑惑,对于string类,无论正着还是倒着走,[下标]的方法都足够好用,为什么还要有迭代器?

事实上,迭代器是一种通用的遍历方式,所有容器都可以使用迭代器这种方式去访问修改,而list、map/set不支持[下标]遍历。结论是,对于string类,我们得会用迭代器,但是我们更喜欢用[下标]

4.1 正向迭代器

正向迭代器提供了两个成员函数 ——

<img src=" title="">

<img src=" title="">

:heart: 迭代器是内嵌类型,想象成指针一样,但又不一定是指针

#include<iostream>
#include<string>

using namespace std;

// 2.迭代器
int main()
{
    string s("more than words");
    // 1.可读
    string::iterator it = s.begin();
    while (it != s.end())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;

    // 2.可写
    it = s.begin();
    while (it != s.end())
    {
        *it += 1;
        it++;
    }
    cout << s <<endl;
    return 0;
}

<img src=" title="">

  • [ ] iterator依然提供了两个版本,第二个是const成员函数,
  • [ ] 关于!=可不可以写成<:答案是可以但不建议。对于string类可以,是因为它的物理空间是连续的,其他容器就不一定了。

4.2 反向迭代器

反向迭代器也提供了两个成员函数 ——
在这里插入图片描述

<img src=" title="">

:heart: 倒着遍历字符串 ——

#include<iostream>
#include<string>

using namespace std;

// 反向迭代器 - 倒着遍历
int main()
{
    string s("more than words");
    // 1.可读
    string::reverse_iterator rit = s.rbegin();
    //auto rit = s.rbegin(); //太长了,可自动推导类型
    while (rit != s.rend())
    {
        cout << *rit << " ";
        rit++;
    }
    cout << endl;

    // 2.可写
    rit = s.rbegin();
    while (rit != s.rend())
    {
        *rit += 1;
        rit++;
    }
    cout << s << endl;
    return 0;
}

<img src=" title="">

4.3 const迭代器

所谓const迭代器,实际上是上面那些成员函数重载的第二个版本。

  • [ ] 上述的普通迭代器可读可写,实际上调用的是第一个接口(相当于string类模板中,类型为T*);
  • [ ] 而const迭代器不可写。这是因为是const成员函数,const修饰this指针指向的内容(相当于string类模板中,类型为const T*)

const迭代器也分正向迭代器和反向迭代器,且就是给const对象用的。这是因为const对象才能调用这里的const成员函数,返回const迭代器,不可写;是普通对象就直接调用普通的重载接口(因为两个重载函数同时存在),返回普通迭代器,可读可写。

<img src=" title="">

它出现的情况往往是这样 ——

#include<iostream>
#include<string>

using namespace std;

void func(const string& s)
{
    // const正向迭代器 - 可读不可写
    string::const_iterator it = s.begin();
    while (it != s.end())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;

    // const反向迭代器 - 可读不可写
    string::const_reverse_iterator rit = s.rbegin();
    while (rit != s.rend())
    {
        cout << *rit << " ";
        rit++;
    }
    cout << endl;
}

int main()
{
    string s("more than words");
    func(s);
    return 0;
}

传参进func中,s是const对象,自动调用第二个接口,返回的是const_iterator,要用const迭代器类型接收,且不能修改。

<img src=" title="">

C++11为了区分const迭代器和普通迭代器还提供了以下接口,不然调用时容易混淆,实际上用的不多。

<img src=" title="">

4.* 范围for遍历更改

顺便介绍【遍历 + 更改】的第三种,范围for是C++11提供的语法糖:candy:,实际上底层编译器也会替换成迭代器。

:candy: 把s中的每个字符取出来,赋值e

  • [ ] 自动向后迭代
  • [ ] 自动判断结束
#include<iostream>
#include<string>

using namespace std;

int main()
{
    string s("more than words");
    for (auto& e : s)
    {
        cout << e << " ";
    }
    cout << endl;

    for (auto& e : s)
    {
        e += 1;
    }
    cout << s << endl;
    return 0;
}

注:

  • [ ] 要修改,auto记得带上引用&。若s中的每个对象比较大,也最好加&
  • [ ] 范围for也可以不写auto,直接写类型也可。

5. Modifiers 修改

5.1 追加

<img src=" title="">

:heart: +=更常用,因为既可以追加字符、也可追加字符串 ——

int main()
{
    string s("more than words");

    s.append(" is all you have to do to make it real");
    s.push_back('~');
    cout << s << endl;

    s += "then you wouldn't have to say that you love me, cause I'd already know";
    s += "~";
    cout << s << endl;

    return 0;
}

<img src=" title="">

:heart: 下面来探究尾插扩容容量变化 ——

#include<iostream>
#include<string>

using namespace std;

int main()
{
    string s;
    //s.reserve(1000);
    size_t sz = s.capacity();
    cout << "capacity:" << sz << endl;
    for (size_t i = 0; i < 1000; i++)
    {
        s += '~';
        // 若容量发生变化
        if (capa != s.capacity())
        {
            capa = s.capacity();
            cout << "capacity changed:" << sz << endl;
        }
    }
    return 0;
}

可以看到在vs下,第一次是2倍,后面是约等于1.5倍的增容 ——

<img src=" title="">

注:在知道需要是多少空间,可以调用reserve预留空间,避免频繁增容的消耗。

<img src=" title="">

5.2 插入 & 删除

:black_heart: 尽量少用头部和中间的插入删除,因为要挪动数据,O(N)效率低。

<img src=" title="">
<img src=" title="">

6. String operations

6.1 c_str

:heart: 返回C格式字符串

<img src=" title="">

打印字符串,都能打印,但意义不同 ——

<img src=" title="">

前者是string类的流插入运算符的重载,size是多少打印多少;后者是按字符串类型打印,遇到\0结束。

主要作用还是与函数接口接合,like this——

    string file("test.txt");    
    FILE* fout = fopen(s.c_str(), "w");

<img src=" title="">

6.2 substr 子串

:heart: 取当前串的一个子串

<img src=" title="">

len:如果len比能取到的串长或使用缺省值npos,都是能取多少取多少。

6.3 查找 find & rfind

:heart: 1. 从字符串pos位置从前向后找字符c/字符串,返回该字符在字符串中的位置

<img src=" title="">

:heart: 2. 从字符串pos位置从后向前找字符c/字符串,返回该字符在字符串中的位置

<img src=" title="">

:yellow_heart: 现在我要file的后缀名 ——

#include<iostream>
#include<string>

using namespace std;

int main()
{
    string file("test.txt");
    // 获取file后缀
    size_t pos = file.rfind('.');
    if (pos != string::npos)
    {
        //string suffix = file.substr(pos, file.size() - pos);
        string suffix = file.substr(pos);
        cout << suffix << endl;
    }
    return 0;
}

<img src=" title="">

:yellow_heart: 解析出网址的这三个部分:协议 - 域名 - 资源

#include<iostream>
#include<string>

using namespace std;

int main()
{
    string url("https://cplusplus.com/reference/string/string/find/");
    size_t pos1 = url.find(':');
    string proctol = url.substr(0, pos1); //取协议子串

    size_t pos2 = url.find('/', pos1 + 3);
    string domain = url.substr(pos1 + 3, pos2 - (pos1+3)); //取域名

    string uri = url.substr(pos2); //取资源

    cout << proctol << endl;
    cout << domain << endl;
    cout << uri << endl;
    return 0;
}

<img src=" title="">

7. Non-member function overloads

7.1 流插入&流提取

注意,流插入和流提取都是以空格、回车作为结束标志的。这意味着如果想要输入一个字符串,最终可能只读入了一个单词。

于是我们引入getline.题目中就会遇到。

7.2 getline

<img src=" title="">

持续更新~@边通书

相关文章
|
30天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
67 19
|
30天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
46 13
|
30天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
48 5
|
30天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
38 5
|
30天前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
46 4
|
30天前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
32 3
|
3月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
123 5
|
3月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
88 2
|
3月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
150 5
|
3月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
156 4