【C++】C++ STL 探索:String的使用与理解(一)

简介: 【C++】C++ STL 探索:String的使用与理解

前文

C语言中,字符串是以’\0’结尾的字符集合。C标准库提供了一系列关于字符串的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。至于C++选择了string类而不是C语言中的字符串库函数,在本章最后揭晓。

一、标准库中的string类

在使用string类过程中,必须包括#include头文件以及using namespace std。string类对象支持直接使用cin和cout进行输入和输出。

string类的文档介绍

  1. string表示字符串类,字符串表示字符序列的类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
  3. string在底层实际为basic_string模板类的别名,typedef basic_stringstring
  4. string类是使用char,即作为它的字符类型,使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
  5. 不能操作多字节或者变长字符的序列

接下来接收string类的常用接口

二、string类对象的常见构造

(constructor)函数名称 功能说明
string() (重点) 构造空的string类对象,即空字符串
string(const char* s) (重点) 用C-string来构造string类对象
string(size_t n, char c) string类对象中包含n个字符c
string(const string&s) (重点) 拷贝构造函数

2.1 string()

string str;

功能构造空string类对象,其中不存在成员对象

2.2 string(const char* s)

int main()
{
  //第一种写法,清晰明了
  const char* s = "hello world";
  string str1(s);//
  
  //第二种写法,比较简洁,常使用
  string str2("hello world");
  return 0;
}

功能使用C-string构造string类对象。在非空字符串中,从s指向位置拷贝一份字符串。

2.3 string(size_t,char c)

int main()
{
  string str1(5, 'x');
  cout << str1 << endl;//xxxxx
  return 0;
}

功能:string类对象初始化n个字符c。从C-string的n个连续字符拷贝填充string类对象。

2.4 string(const string& s)

int main()
{
  string str1("hello world");
  string str2(str1);//拷贝构造str1
  return 0;
}

2.5 string(const string& str,size_t pos,size_t len = npos)

int main()
{
  string str1("helloo world");
  string str2(str1, 5, 6);
  cout << str2 << endl;
  return 0;
}

功能从str中pos指向位置先后拷贝len长度字符。出现两种结果:拷贝到str最后一个字符或没有达到最后一个字符完成拷贝。

说明:第三个参数len类型为size_t,而缺省值npos == -1导致了npos为最大值128(涉及到编码那块)。对于当没有明确len数值,默认是从pos位置拷贝字符串到最后一个字符。

三、string类对象的容量操作

函数名称 功能说明
size(重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty (重点) 检测字符串释放为空串,是返回true,否则返回false
clear (重点) 清空有效字符
reserve (重点) 为字符串预留空间
resize (重点) 将有效字符的个数该成n个,多出的空间用字符c填充

3.1 Size与length

int main()
{
  string str1("hello world");
  
  cout << str1.size() << endl;//11
  cout << str1.length() << endl;//11
  return  0;
}

功能:返回字符串有效字符长度。

3.1.1 关于size与length相关问题

  1. 至于出现两个功能类似接口的原因

由于当时string只考虑字符串,同时使用length表示字符串长度是最合理的。但是这样没有考虑到其他类型,导致具有局限性,在树形结构等数据结构情况中不太适合length表示元素大小,STL添加size表示元素大小。length合理,size统一更规范。

  1. 为什么不删除length,只保留size呢?或者在string容器中只存在length表示大小呢?

在语言中库,一般遵守向前兼容,只错不能改。如果修改会导致之前代码就编译失败,对此不能删除length。其他容器都有size,就你string容器没有,是不是有点不太合适呀。

3.2 capacity

int main()
{
  string str1("hello world");
  cout << str1.size() << endl;
  cout << str1.capacity() << endl;
  return 0;
}
输出结果:11 15

功能:返回空间总大小,一般情况下capacity返回大小中不包含’\0’

3.2.1 capacity返回值比size大

在C++中,std::string底层属于动态数组,数组大小是不固定,根据实际需要进行调正。由于经常性出现频繁插入字符的清空,只存在size情况下,会导致频繁地向系统申请空间,性能降低。

capacity可以有效地解决这问题,直接申请大于size空间大小,避免在每次追加字符中重新分配内存,直接使用capacity空间,减少向系统申请内存次数,提高性能。

3.2.2 capacity扩容机制

在C++中,std::string类中向字符串添加字符。如果出现容量不足去容纳新字符,会自动扩容(不需要手动扩容)。扩容的逻辑通常按照某种策略增加容量,具体实现会跟编译器和指标因子的不同有所差异。虽然string扩容机制没有明确的规定细节,但是不会影响功能。

  • VS:扩容机制是第一次扩容到原来空间的两倍左右,之后则扩容当前空间的1.5倍
  • GCC:扩容机制是以当前空间的两倍

3.3 empty

int main()
{
  string str1;
  if (str1.empty())//判断释放为空
    cout << "为空" << endl;
  else
    cout << "非空" << endl;
  return 0;
}

功能:检测字符串是否释放为空,是空返回true,否则返回false

3.4 clear

int main()
{
  string str1("hello world");
  cout << str1.size() << endl;
  cout << str1.capacity() << endl;
  str1.clear();//清空有效字符
  cout << str1.size() << endl;
  cout << str1.capacity() << endl;
  return 0;
}

功能:清空string有效字符资源,不改变底层空间大小。影响有效元素size,不会影响空间容量大小capacity

3.5 shrink_to_fit

int main()
{
  string str("hello world");
  cout << str.size() << endl;
  cout << str.capacity() << endl;
  cout << endl;
  str.resize(100);
  cout << str.size() << endl;
  cout << str.capacity() << endl;
  cout << endl;
  str.shrink_to_fit();
  cout << str.size() << endl;
  cout << str.capacity() << endl;
  cout << endl;
  return 0;
}

功能:向系统请求字符串缩容到适合大小,但是该函数对于字符串的长度和内容是没有影响的

如果使用shrink_to_fit后,容量没有发生改变,可能字符串对象可能已经使用内存管理策略去避免频繁的内存分配和释放。

3.6 reserve(重要)

int main()
{
  string str1;
  cout << str1.capacity() << endl;//15
  str1.reserve(100);
  cout << str1.capacity() << endl;//111
  string str2(10, 'x');
  cout << str2.capacity() << endl;//10
  str2.reserve();
  cout << str2.capacity() << endl;//10
  return 0;
}

功能:向系统申请预留空间,属于手动扩容

3.6.1 关于reserve与扩容问题

  1. 编译器会根据capacity容量自动扩容,那么为什么还需要reserve实现手段扩容呢?
  • 理由:扩容是需要付出代价的,如果是异地扩容,付出代价更大,需要进行空间开辟和数据拷贝。
  • 如果事先知道所需要的空间大小,使用reverse开辟足够使用的空间,减少频繁对内存的重分配,就算后期出现空间不足,也有自动扩容的机制,不需要担心大小是固定的。虽然自动扩容可以解决容量不足的情况,但是手段扩容可以减少频繁自动扩容的代价,属于一种优化手段。
  1. reverse要求100个字节空间,但却开辟了111个字节空间呢?
  • 理由:在不同编译器下机制是不同的,但是确保了至少满足所需空间。有些编译器开辟多个空间,是对reserve开辟的空间进行了二次开辟,可以灵活调用内存空间分配,在后继需要小空间,避免扩容。
  1. reserve参数部分小于当前空间大小,提出申请空间请求,但是空间大小并没有发生改变
  • 理由:reserve进行扩容必须参数部分比当前空间大,才会改变string的底层空间总大小,否则就是无效扩容。

3.7 resize(重要)

功能:改变字符串的实际长度

3.7.1 resize改变字符串的实际长度有三种情况

第一种:字符串变短(n>size)

int main()
{
  string str1("hello world");//长度为11
  cout << str1.size() << endl;//11
  cout << str1.capacity() << endl;//15
  str1.resize(2);
    cout << str1 << endl;
  cout << str1.size() << endl;//2
  cout << str1.capacity() << endl;//15
  return 0;
}

第二种:字符串在容量内变长(capacity>=n>size)

int main()
{
  string str1("hello world");//长度为11
  cout << str1.size() << endl;//11
  cout << str1.capacity() << endl;//15
  str1.resize(13);
  cout << str1 << endl;
  cout << str1.size() << endl;//13
  cout << str1.capacity() << endl;//15
  return 0;
}

如果需要保留字符串前几个字符(不包括‘\0’),可以使用这个接口。

第三种:字符串修改长度超出容量(n>capcity)

int main()
{
  string str1("hello world");//长度为11
  cout << str1.size() << endl;//11
  cout << str1.capacity() << endl;//15
  str1.resize(50);
  cout << str1 << endl;
  cout << str1.size() << endl;//50
  cout << str1.capacity() << endl;//63
  return 0;
}

当resize修改长度超过capacity,capacity会进行自动扩容。至于最后capacity的值为什么不是50,在reserve中解释了不同编译器扩容机制是不同的。

resize有两个函数重载:resize(size_t n)与resize(size_t n,char c),功能都是将字符串中有效字符个数改变到n个。

不同点:当字符个数增多时

  • resize(n):用’\0’来填充都出的元素空间
  • resize(size_t n,char c):用字符c来填充多出的元素空间

虽然resize的功能很丰富,但是reserve比较多使用,可以提前开好空间,避免频繁扩容,提高了性能。


【C++】C++ STL 探索:String的使用与理解(二)https://developer.aliyun.com/article/1617332

目录
打赏
0
1
1
0
21
分享
相关文章
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
67 2
C++ String揭秘:写高效代码的关键
在C++编程中,字符串操作是不可避免的一部分。从简单的字符串拼接到复杂的文本处理,C++的string类为开发者提供了一种更高效、灵活且安全的方式来管理和操作字符串。本文将从基础操作入手,逐步揭开C++ string类的奥秘,帮助你深入理解其内部机制,并学会如何在实际开发中充分发挥其性能和优势。
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
155 73
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
83 3
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
模拟实现c++中的string
模拟实现c++中的string
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
123 1
|
3月前
|
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
95 21
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
89 1

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等