【C++】STL之string类概述-1

简介: 【C++】STL之string类概述

一、前言

本文我们就要来说一说STL中的string类,这也是我们在写C++代码的时候使用最多的

1、C语言中的字符串

首先要来谈的一点就是为什么要学习string类

  • string意为字符串,那回忆一下我们在C语言阶段所学习的字符串,是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,如果忘记了就再看看 字符串操作函数
  • 但是这些库函数与字符串是分离开的,不太符合 OOP 的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问

因此,在C++中专门搞出了一个与字符串相关的类,我们知道C++是面向对象的,我们可以在类内去写各种成员函数来对外提供操作字符串的接口,这个类就是string

2、string类的使用场景

那这个string有什么实际的应用场景呢,这可多了去了,如果经常刷题的同学应该瞬间能反应过来

  • 从下面的两道OJ题就可以看出有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数


字符串转整形数字

image.png


字符串相加

image.png

二、初步认识string类

1、概述

string类的文档介绍

  • 我们来到string类的文档中可以看到它确实是一个类,而且是由一个模版类basic_string所实例化出来的

image.png

  • 如果你有学习过 C++模版 的话就可以知道这个类即为【类模版】

image.png

  • 然后由这个类模版就实例化出了很多的模版类,其中就包含我们本文所要学习的string类

image.png

那有同学问,下面的这几个【wstring】、【u16string】、【u32string】是什么呢?

  • 这一块的话要涉及到编码相关的知识,如果读者对这一块没什么接触的话就当了解一下

对于编码而言我们谈到最多的就是 ASCLL码 ,它的全称叫做【美国信息交换标准代码】,总共包含了像大小写的中英字母、数字、标点符号等等共128个,用来表示老美的的一些文章字符完全足够了。

而对于ASCLL码来说,是存在一个东西叫做映射表,即一个ASCLL码值就对应一个字符

image.png

我们可以到VS中来观察一下,其实在内存中所存放的都是ASCLL码,只不过呢在显示的时候却转换为了表中所对应的字符。这个63 64 65 66其实是十六进制的写法,转换为十进制即为97 98 99 100,那它们所对应的字符即为a b c d

image.png如果有同学还是不相信的话可以看看下面这个,我给str[0]这个位置放了数值为97,但是呢其为一个string类的对象,所以里面所存放的都是字符,可以看到在打印出来后的结果就是97所对应的ASCLL码值a

image.png

但是呢对于我们的一些中文汉字却是远远不够的,例如说下面这个”比特”,我通过【sizeof】打印出了其在内存中所占字节数,发现有5B,这是为什么呢?原因就在于汉字的存储规则不是按照ASCLL码来的,而是专属于我国的一套编码字符集叫做GBK。在【GB2312-80】中就存储了很多有关汉字的规则

一般来说一个汉字对应的2个字节,这里的”比特”是因为最后还有一个\0

image.png


最后对这个string类的特性做一个总结:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
  4. 不能操作多字节或者变长字符的序列。

⚠ 在使用string类时,必须包含#include头文件以及using namespace std

2、常用接口细述

对string类大体有个了解后我们就要去学着使用这个类里面的一些函数了

1)string类对象的默认成员函数

函数名称 功能说明
constructor 构造函数
destructor 析构函数
operator= 赋值重载

==① 构造函数==

  • 点进第一个成员函数Constructor就可以看到存在7个重载,其中我框出来的三个是重点,要着重记忆,其余的了解一下即可

image.png

  • 我将上面的函数整理了一下,方便观看
string();   // 构造一个空字符串
string (const char* s);       // 用C-string来构造string类对象
string (const char* s, size_t n); // 用C-string的前n个字符来构造string类对象
string (size_t n, char c);    // 生成n个c字符的字符串
string (const string& str);   // 利用原先的字符串做拷贝构造
// 拷贝str字符串中从pos位置开始的len个字符
string (const string& str, size_t pos, size_t len = npos);  
  • 然后我们到VS里演示一下

image.png

  • 前面的几个都好理解,来讲一下最后的这一个,可以看到在函数形参这一块给出了一个缺省值叫做npos,可能有的同学不清楚这个npos是什么东西
// 拷贝str字符串中从pos位置开始的len个字符
string (const string& str, size_t pos, size_t len = npos);  
  • 这个我们也可以到文档中来查看一下,发现它是【无符号整数的最大值】

image.png

  • 那这个值究竟是多少呢,我们可以将其打印出来看看

image.png

  • 也可以到Linux平台下来查看发现不太一样,因为在Linux下使用的是g++编译器,它们底层所定义的规范是不一样的

image.png💬 但是呢,无论这个数值是多少,其实影响都不大,因为我要说明的是即便我们不给出这个参数的话,编译器默认就是从当前的这个【pos】位置开始一直到字符串末尾

  • 继续通过文档来观察一下,substring指的就是子串的意思,意思即为 从pos位置的len个长度去拷贝字符串的一部分(如果str字符串太短或者len为npos则直接到达字符串的末尾)

image.png

那现在的话你应该可以明白最后的这个参数我为何没有传递却拷贝到了后面的所有字符

  • 接下去呢我们来讲一下赋值重载,这个我们在讲 类的六大成员函数 有提到过这个,它只能出现在类内但是不可以出现在类外,否则会产生一个冲突

==赋值重载==

image.png

string& operator= (const string& str);  // 将一个string对象赋值给到另一个
string& operator= (const char* s);    // 将一个字符串赋值给到string对象
string& operator= (char c);       // 将一个字符赋值给到string对象
  • 同样,我们到VS中来做一个测试,可以看到这三种方式都可以构成一个string的对象,不过照这么来看后面的这两种似乎没多大用处,用的最多的还是第一种

image.png

2)string类对象的常见容量操作

接下去我们来讲一讲有关string类中有关容量的一些操作

函数名称 功能说明
size 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
maxsize 返回字符串的最大长度
clear 清空有效字符
empty 检测字符串释放为空串,是返回true,否则返回false
reverse 为字符串预留空间
resize 将有效字符的个数该成n个,多出的空间用字符c填充
shrink_to_fit 收缩到合适大小

① size、length、capacity

  • 首先要来讲的是【size】和【capacity】,这其实就和我们在顺序表中所介绍的意思差不多,前者表示当前字符串已经存放了多少数据,后者表示当前这个字符串可容纳的空间数
  • 我们到VS下通过调试来观察一下,可以看到现在这个str没有任何的数据,而VS为我们开出了大小默认为15的空间,其实这里应该是16,只不过最后的\0也占了一个大小

image.png

  • 然后去构建出一个具体的字符串来进行观察,发现size的值发生了一个变化。不仅如此,我还打印了一下这个字符串的length属性,观察到其和size是一样的

image.png

  • 然后我们点进到这两个函数的定义中可以发现它们的定义竟然是一样的

image.pngimage.png

  • 我们在文档的时候,一定会有一种很奇怪的感觉,在C++的容器里面为什么没有string呢?这里要追溯到STL的一个诞生历史,其实对于string这个类,是不属于STL的,因为它是在STL之前就已经存在了的,属于C++标准库里的内容

image.png

  • 像下面这样来看的话,STL是属于标准库中的一部分,string也是属于标准库的一部分,但是呢二者却没有什么直接的关联。在最早期string的长度接口是定义为【length】的,到后面STL出了之后才改为了【size】,但是这一块我们需要去考虑到一个历史追溯的问题,所以不能将这个接口给删除,因而我们才看到一模一样的两个接口实现

image.png💬 后续我们在使用到时候直接用【size】即可,因为其他STL库中的接口使用的也是【size】

追究完这个【size】之后,我们再来谈谈这个【capacity】

  • 我们使用的是下面这段代码进行测试
void TestCapacity()
{
  string s;
  size_t sz = s.capacity();
  cout << "making s grow:\n";
  for (int i = 0; i < 100; ++i)
  {
    s.push_back('c');
    if (sz != s.capacity())
    {
      sz = s.capacity();
      cout << "capacity changed: " << sz << '\n';
    }
  }
}
  • 以下分别是在Windows和Linux平台下的运行结果,通过不断往这个s中添加字符来扩充他的容量

image.png

image.png💬 可以看到Windows下的VS中,扩容的大小是呈现一个1.5倍大小的趋势;可是在Linux平台下呈现的却是2倍大小的趋势

  • 这其实就是因为不同平台下这个STL库的内部实现是不一样的,甚至都是在VS中,不过呢使用不同版本的VS其大小也是不一样的

② maxsize

接下去呢我们再来说说这个【maxsize】

void TestMaxsize()
{
  string s("hello");
  cout << s.size() << endl;
  cout << s.max_size() << endl;
}
  • 一样,我们放在这个双平台下演示一下,发现结果也是各不相同

image.png

image.png

③ clear

接下去呢,我们来讲【clear】

void TestClear()
{
  string s("hello");
  cout << "size: " << s.size() << endl;
  cout << "capacity: " << s.capacity() << endl;
  s.clear();
  cout << "size: " << s.size() << endl;
  cout << "capacity: " << s.capacity() << endl;
}
  • 这次双平台下演示的结果就一样了,并没有什么差异,对于clear来说,它只会清空字符串中的【size】,但对于【capacity】来说是不会有什么变化的,如果你是这个接口的实现者一定不会去清空这个【capacity】容量的大小,万一后面又需要插入字符呢?此时又需要去进行一个扩容,是非常麻烦的

image.pngimage.png

④ empty

  • 接下去再来看看【empty】这个接口,很简单,我们复用一下上面这段代码,看一下这个字符串在清空前后的【empty】结果即可,于是就可以看到没被清空的时候返回0,代表false不空,而被清空之后返回1,代表true为空

image.png

⑤ reserve

接下去呢我们来讲讲这个【reserve】,可以提前为一个字符串开出指定的空间

  • 这一块我们可以放在之前观察【capacity】的地方进行测试,可以发现在开出预留空间后就不会去执行下面的这一段扩容逻辑了,因为此时空间已经够了

image.png

  • 可以看到我们在VS下给定预留空间为100的时候此时系统给我们的大小为【111】,很明显是要多出一点的,但是在Linux下去运行的话就不是这样了,而是给多少开多少,就给了我们【100】的空间

image.png💬 那有同学问:这是为什么呢?

  • 原因也是一样,平台不同实现不同,所以两个平台都会去做一个演示

可能上面这样还不是很好理解,我们举个例子来理解一下

  • 这个月呢你想买件衣服,但是没有零花钱了,于是就是找妈妈要零花钱,总共要个100块,那你妈妈觉得既然衣服都买了的话,裤子也一起买吧,于是直接给你打了200块
  • 此时这种行为就和VS下是一样的,虽然你只是要了100空间的大小,但是呢编译器却给了你111

image.png

  • 那像Linux里这种行为就是你要多少钱你妈妈就给你打多少,一分不多、一分不少,要开100个容量大空间就给你100

我们也可以到文档中来看看

  • 说如果参数中这个【n】比当前容量的大小来得大的话,那么会增长到n个字符的空间大小

image.png

不过呢这个文档下面还有一部分,那就是不仅会发生【增容】的情况,也会发生【减容】的情况

image.png

  • 我们可以在最后的地方加上s.reserve(10)来试试是否可以,却发生容量没有发生变化

image.png

  • 但是呢,当我到Linux平台下再来演示的时候,却发现有了不一样的变化,确实发生了【减容】的情况
  • 其实在这里就对应了文档中所说的【non-binding】,即不可控的

image.png

可是,我接下来的操作,可能就会让你惊掉下巴(⊙ˍ⊙)

  • 可以看到我在即将【减容】前使用了clear将字符串中的数据清空后,此时再去使用reserve的时候容量就发生了变化,回到了最初的那个默认大小

image.png

  • 那到Linux下来看看发现也会其作用,不过是缩容到指定的大小,而不是默认的大小

image.png

  • reserve(0)一下看看果然也是这样

image.png

从以上的种种结果来看可谓真的是【变化多端】鸭🦆,多然是不好掌控

⑥ resize

void resize (size_t n);
void resize (size_t n, char c);   // 初始化数据为n个c字符
  • 讲完【reserve】后,我们再来讲讲【resize】,它们很类似,但也有不同之处
void TestResize()
{
  string s("abcdef");
  // 开空间
  s.reserve(100);
  cout << s.size() << endl;
  cout << s.capacity() << endl;
  cout << "---------------------" << endl;
  // 开空间 + 填值初始化
  s.resize(200);
  cout << s.size() << endl;
  cout << s.capacity() << endl;
}
  • 通过对它们两个做一个对比可以发现对于【reserve】而言仅仅只是对capacity起作用,size就是初始化的字符串个数;
  • 但是对于【resize】而言sizecapacity会一起发生改变,前者变为传递的参数n,后者则遵循VS下的扩容规则,比给出的参数多一些

image.png

一样,我们通过解读文档的形式来看看这个函数还有哪些内容

  1. 如果 n > 当前的字符串长度,则在末尾插入所需数量的字符以达到n的大小来扩展当前内容
  2. 如果n < 当前的字符串长度,则会去进行一个删除的操作,删除第n个字符之后的字符

image.png

第一点我们刚才已经验证过了,接下去我们再来试试第二点,看看是否真会去做一个删除的操作

  • 可以看到,若是传递了 < n的参数进去的话,再去打印这个字符串就会发生一个截断的现象

image.png


上面我们所使用的都是第一个只传递n,不过下面还有一个重载形式可以【初始化数据为n个c字符】,看到最后这里也有说到如果这个c是特殊的话,就会去以这个c字符去填充后面多开空间的部分image.png

  • 可以看到若是我们没有去制定后面这个字符c的话,默认会以'\0'进行填充的,那如果我们制定出字符呢?

image.png

  • 此时我指定了后面的字符c,此刻后面填充的就是我们所指定的那个字符
s.resize(100, 'a');

image.png

⑦ shrink_to_fit

  • 最后我们再来讲一个,它也可以做到一个减容的效果
void shrink_to_fit();
  • 立马我们就来试一试它究竟能起到什么样的效果,发现在使用后可以让原本在【resize】之后不符合大小的capacity变得适应size的大小, 确实也有一些作用,不过使用的场景不多,读者了解一下即可

image.png

相关文章
|
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++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
87 2
|
3月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
150 5
|
3月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
154 4
|
3月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
236 4