【C++ STL】string 容器(介绍、使用、深浅拷贝、模拟实现、写时拷贝)

前言

为什么学习 string 类呢?

C语言中是没有字符串类型的,字符串是以 ‘\0’ 结尾的一些字符的集合(即字符数组),为了操作方便,C 标准库 <string.h> 中提供了一些 str 系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象 OOP 的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

C++ STL string 是对字符串进行管理的类。实际上就是一个管理字符数组的顺序表

在常规工作中,为了简单、方便、快捷,基本都使用 string 类,很少有人去使用 C 语言库中的字符串操作函数。


一、STL - string 的介绍

文档介绍:string - C++ Reference (cplusplus.com)

  1. 字符串是表示字符序列的对象
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string 类是使用 char (即作为它的字符类型,使用它的默认 char_traits 和分配器类型(关于模板的更多信息,请参阅 basic_string)。
  4. string 类是 basic_string 模板类的一个实例,它使用 char 来实例化 basic_string 模板类,并用 char_traits 和 allocator 作为 basic_string 的默认参数(关于更多的模板信息请参考 basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结

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

补充

  • 编码:计算机中只存储二进制数(0 / 1),那如何去表示文字呢,需要制定对应的编码表,规定用哪些二进制数字表示(映射)哪个符号,当然每个人都可以约定自己的一套,而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,比如美国有关的标准化组织就出台了 ASCII 码表(基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言)

  • ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符,到目前为止共定义了 128 个字符。

  • 所以早期的计算机中只能表示英文,不能表示其它国家的文字,当全世界各个国家都开始使用计算机了,就需要建立出对应语言的编码表。

  • UTF - 8 是对不同范围的字符使用不同长度的编码,这样能够适应不同的语言,比如有些语言单字节编码就够了,有些语言需要多字节编码才够。

    image-20220501105903743
  • 还有其它编码表,GBK码(对多达2万多的简繁汉字进行了编码,简体版的Win95和Win98都是使用GBK作系统内码)等等。

注意:使用 string 类需要包含头文件


二、string 的使用(常用接口介绍)

string 类的成员函数(接口)非常的多,我们学习一些常用的就行了,其它不常用的,需要时去查文档就好了。

2.1 常见构造

构造函数:

string();                         // 默认构造,构造空的string类对象,即空字符串"" ⭐
string (const string& str);       // 拷贝构造,用已有的string类对象去构造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对象
template <class InputIterator>    // 用迭代器[first,last)范围内的字符序列构造string类对象
  string  (InputIterator first, InputIterator last);

👉Example:

// string constructor
#include <iostream>
#include <string>

int main()
{
   
   
    std::string s0 ("Initial string");

    std::string s1;                                    // s1: ""
    std::string s2 (s0);                               // s2: Initial string
    std::string s4 ("A character sequence");           // s4: A character sequence
    std::string s5 ("Another character sequence", 12); // s5: Another char
    std::string s6 (10, 'x');                          // s6: xxxxxxxxxx
    std::string s7 (s0.begin(), s0.begin() + 7);       // s7: Initial
    return 0;
}

👉思考:空串是什么都没有吗,存储空间为空吗?

image-20220501113316060

2.2 容量操作
函数名称 功能说明
size 返回字符串有效字符长度(为了统一设计,所有容器都是用 size 表示有效元素个数)
length 返回字符串有效字符长度(这是早期提供的接口)
resize 将字符串大小调整为 n 个有效字符的长度
capacity 返回有效字符的最大容量(即已分配 size 的大小)
reserve 更改容量(capacity)的大小
clear 清空字符串的内容,变为空字符串(size 变为 0,不改变 capacity 的大小)
empty 检测字符串是否为空串,是返回 true,否则返回 false

👉Example1:

resize 函数的两种重载形式:

void resize (size_t n);
void resize (size_t n, char c);
void Test1()
{
   
   
	string s("hello");

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// "helloaaaaa"
	s.resize(10, 'a');
	cout << s.size() << endl;     // 10
	cout << s.capacity() << endl; // 15
	cout << s << endl;            // s: "helloaaaaa"

	// 将s中有效字符个数增加到20个,多出位置用缺省值'\0'进行填充
	// 如果resize参数大于原有 capacity 大小,会进行增容
	// "helloaaaaa\0\0\0\0\0\0\0\0\0\0"
	s.resize(20);
	cout << s.size() << endl;     // 15
	cout << s.capacity() << endl; // 31
	cout << s << endl;            // s: "helloaaaaa"

    // 如果resize参数x原有 capacity 大小
	// 将s中有效字符个数缩小到2个
	// "he"
	s.resize(2);
	cout << s.size() << endl;     // 2
	cout << s.capacity() << endl; // 31
	cout << s << endl;            // s: "he"

	// 将s的内容清空,注意清空时只是将size置0,不改变capacity的大小
	s.clear();
	cout << s.size() << endl;     // 0
	cout << s.capacity() << endl; // 31
	cout << s << endl;            // s: ""

	return 0;
}

👉Example2:

reserve 函数介绍:

void reserve (size_t n = 0);
  • 如果 n 大于当前字符串容量,则该函数使容器将其容量增加到 n 个字符(或更大)。

  • 在所有其他情况下,缩小字符串容量被视为非绑定请求:容器可以自由实现优化,但要保留容量大于 n 的字符串

  • 此函数对字符串长度没有影响,并且不能更改其内容。

void Test2()
{
   
   
	string s("hellohellohello");

	// 如果reserve参数大于原有 capacity 大小,会进行增容
	s.reserve(20);
	cout << s.size() << endl;     // 15
	cout << s.capacity() << endl; // 31

	// 测试reserve参数小于原有 capacity 大小,是否会将空间缩小呢?
	// VS2019下,如果字符串有效长度size大于参数10,不会缩小,如果字符串长度小于参数10,会缩小
	// 当然,这个也和编译器平台有关系
	s.reserve(10);
	cout << s.size() << endl;     // 15
	cout << s.capacity() << endl; // 31(VS2019)
}

测试:reserve 是如何进行增容呢?

void Test3()
{
   
   
	string s;

	cout << "initial value: " << s.capacity() << endl;

	size_t sz = s.capacity();
	for (size_t i = 0; i < 500; i++)
	{
   
   
		s.push_back('a');
		if (s.capacity() != sz)
		{
   
   
			sz = s.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
}

经过测试,VS2019 下大概是 1.5 倍增容:

image-20220501163254263

Linux g++下是 2 倍增容:

image-20220501165304621

思考:resize 和 reserve 的意义在哪里呢?

reserve 的作用:如果知道需要多大的空间,可以利用 reserve 提前一次性把空间开好,避免增容带来的开销

resize 的作用:既要开好空间,还要对这些空间初始化,就可以使用 resize


2.3 访问操作

有了这个运算符重载,我们就可以像使用数组一样去使用 string 类对象

函数名称 功能说明
operator[] 返回对字符串中 pos 位置处的字符的引用(string 类对象支持随机访问)(一般物理地址是连续的才支持)

operator[] 函数的两种重载形式:

char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

operator[] 函数会检查越界(pos 必须小于 size)


2.4 迭代器及遍历操作

所有容器都有迭代器,迭代器提供了用统一类似的方式去访问容器

函数名称 功能说明
begin(iterator) 返回指向第一个有效字符的迭代器
end 返回指向字符串末尾字符的迭代器(即最后一个有效字符的下一个位置)
rbegin(reverse_iterator) 反向迭代器(可以反向遍历对象)
rend 反向迭代器
范围 for C++11支持更简洁的范围 for 的新遍历方式(底层其实是被替换成迭代器,所以支持迭代器就支持范围 for)
image-20220512185848868

迭代器有两个版本:普通迭代器和 const 迭代器

iterator begin();             // 可读可写
const_iterator begin() const; // 只读

👉Example:

void test(const std::string& s) {
   
   
    // const对象必须要用const迭代器
	std::string::const_iterator it = s.begin();
	while (it != s.end()) {
   
   
		std::cout << *it;
        it++;
	}
}

int main()
{
   
   
	std::string s1;
	std::string s2("hello");
    
	// for+operator[]遍历
    for (size_t i = 0; i < s.size(); ++i)
		cout << s2[i] << endl;
    
    // 正向迭代器遍历
    // 注意:这里不建议写成it < s2.end(),比如链式结构的容器,就没法用了
    // 所以统一写成 it != s2.end()
	std::string::iterator it = s2.begin();
	while (it != s2.end()) {
   
   
		std::cout << *it;
		it++;
	}
	// 反向迭代器遍历
    for (std::string::reverse_iterator rit = s2.rbegin(); rit != s2.rend(); ++rit)
		std::cout << *rit;
    for (auto rit = s2.rbegin(); rit != s2.
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值