【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

相关文章
|
11天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
27 7
|
28天前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
54 4
|
29天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
72 5
|
29天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
14天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
27 0
|
3月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
48 0
java基础(13)String类
|
2月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
69 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
2月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
55 2
|
3月前
|
安全 Java
String类-知识回顾①
这篇文章回顾了Java中String类的相关知识点,包括`==`操作符和`equals()`方法的区别、String类对象的不可变性及其好处、String常量池的概念,以及String对象的加法操作。文章通过代码示例详细解释了这些概念,并探讨了使用String常量池时的一些行为。
String类-知识回顾①
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1