【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

相关文章
|
7天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
22 7
|
25天前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
50 4
|
26天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
57 5
|
26天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
45 2
|
1月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
50 0
|
10天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
21 0
|
2月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
82 5
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
27 1
|
2月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
74 1
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
94 5