Bootstrap

C++:string类的简单使用

1.简单介绍几种编码

在学习string之前,我们先来了解一下各种编码。

1.1 ASCII

关于ASCII码,百度百科给出了这样的解释:
ASCII (American Standard Code for Information Interchange):美国信息交换标准代码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。
在这里插入图片描述
ASCII的长度为一个字节,共8个比特位,理论上能够表示256个字符,至于为什么只有128个,是因为:第一位比特位是符号位,为了能够兼顾数字和字符,就不使用第一位,这样就剩下7位可以编码。后来为了加入更多字符,又把第一位用上了,就形成了扩展ASCII。
在这里插入图片描述

1.2Unicode码

统一码(Unicode),也叫万国码、单一码,由统一码联盟开发,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。
统一码是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
其中包括:UTF-8,UTF-16,UTF-32,最常使用的是UTF-8。
在这里插入图片描述

1.3 GBK

GBK全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification) ,中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司、电子工业部科技与质量监督司1995年12月15日联合以技监标函1995 229号文件的形式,将它确定为技术规范指导性文件。2000年已被GB18030-2000《信息交换用 汉字编码字符集 基本集的扩充》国家强制标准替代。 2005年GB18030-2005发布,替代了GB18030-2000。

2 了解string类

  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, char_traits, allocator>string;
  4. 不能操作多字节或者变长字符的序列。

上述说法太过繁琐,我们先知道它就是一个字符串类型,然后内置了许多成员函数可以对其进行操作就行。

3 string的基本使用

3.1 构造函数

在这里插入图片描述
1.创建一个空字符串。
2.拷贝构造一个字符串。
3.从pos位置开始,拷贝构造长度为len的字符串。
4.使用C风格字符串来构造字符串。
5.使用C风格字符串重复n次来构造字符串。
6.用n个字符c构造字符串。
7.使用迭代器构造字符串(先不示范)

int main()
{
	string s1;				// (1)
	string s2(s1);			// (2)
	string s3("abcdefg");	// (4)
	// string s3 = "abcdefg";	// 也可以这样写,发生隐式类型转换
	string s4(s3, 3, 4);	// (3) 这个位置的len的缺省值npos,实际上就是-1,如果不给len传值,就默认从pos位拷贝到末尾
	string s5("123", 3);	// (5)
	string s6(5, 'n');		// (6)
	cout << s1 << endl;		// string类中还重载了流插入和流提取,可以很方便地打印字符串,提高了代码的可读性
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	return 0;
}

在这里插入图片描述

3.2 string的组成

在这里插入图片描述
通过这张图片可以看出string类是基于类模板basic_string的,除此以外,还有wstring,u16string,u32string,参数分别为wchar_t,char16_t,char32_t。

在这里插入图片描述
因此我们可以模拟出以下string类:

template<class T>
class my_basic_string
{
private:
	T* _str;
	size_t _size;		// 长度
	size_t _capacity;	// 容量
};
typedef my_basic_string<char> my_string;

这里的成员变量都是私有的,我们想要访问需要通过成员函数。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
length()和size()使用起来是一样的,原本string类只有length(),但是用在其他容器中就显得奇怪,比如树的长度?所以为了能得到一个所有容器都能使用的,就引入了size()。

int main()
{
	string str = "abcdef";
	cout << str.size() << endl;			// 打印字符串长度
	cout << str.length() << endl;		// 打印字符串长度
	cout << str.capacity() << endl;		// 打印字符串容量
	cout << str.c_str() << endl;		// 打印C风格字符串
	return 0;
}

在这里插入图片描述

3.3 string类的元素访问

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
operator[]和at都是通过下标访问,不过当发生越界访问时,at会抛异常,operator[]则会assert断言。
front和back分别用来返回字符串的首尾元素,不是很常用。

string str("abcdefg");
cout << str[3] << endl;
cout << str.at(4) << endl;
cout << str.front() << endl;
cout << str.back() << endl;

在这里插入图片描述

3.4 三种遍历string的方法

3.4.1 下标 + []

string str = "abcdef";
for (int i = 0; i < str.size(); ++i)
{
	++str[i];
	cout << str[i] << " ";
}
cout << endl;

在这里插入图片描述

3.4.2 迭代器

对于上述下标+[]的遍历方法,我们在string时可以很方便的使用,但是等到之后的链表、二叉树等就无法使用了,为此C++提供了一个具有普适性的遍历方法,就是用迭代器。
迭代器在使用上类似于指针,定义是要指定类域,比如:string::iterator。
begin()会返回第一个字符的迭代器。
end()会返回最后一个字符下一个位置的迭代器。
通过解引用迭代器可以获得其指向的字符。
使用:

string str = "hello world!";
string::iterator it = str.begin();
while (it != str.end())
{
	cout << *it;
	++it;
}
cout << endl;

在这里插入图片描述
除此之外,还提供了rbegin和rend,这两个需要用反向迭代器reverse_iterator来接收,使用方法和普通的正向迭代器一样。

string str = "hello world!";
string::reverse_iterator it = str.rbegin();
while (it != str.rend())
{
	cout << *it;
	++it;
}
cout << endl;

在这里插入图片描述
还有const迭代器,修饰的是const指向的内容,用const_iterator或const_reverse_iterator来接收,当需要字符串内容不可修改的时候使用。
在C++11中引入了cbegin,cend,crbegin,crend,用来针对const修饰的对象,但实际并没有什么用处。

3.4.3 范围for

范围for是C++11引入的新特性,使用方法如下。
范围for的底层实现其实就是迭代器。

string s1 = "abcdef";
for (auto& ch : s1)
{
	++ch;
}
cout << s1 << endl;

在这里插入图片描述

3.5 string类的容量操作

在这里插入图片描述
size,length,capacity前面已经演示过了,这里就不再赘述。

3.5.1 max_size

max_size这个函数没有什么意义,是一个写死了的函数,返回值的大小根据不同编译器和x86/x64都有所不同。

int main()
{
	string s1("123456");
	string s2;
	cout << s1.max_size() << endl;
	cout << s2.max_size() << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述

3.5.2 容量修改函数

clear():用于删除字符串中所有数据,会将size置零,但容量不变。

string s1("123456");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;

在这里插入图片描述
reserve():
在学习reserve之前,我们先来了解一下string的扩容机制:
通过如下代码来研究不同平台下的机制:

string s1;
int capacity = s1.capacity();
cout << "start capacity:" << s1.capacity() << endl;
for (int i = 0; i < 1000; ++i)
{
	s1.push_back('a');
	if (s1.capacity() != capacity)
	{
		cout << "capacity change:" << s1.capacity() << endl;
		capacity = s1.capacity();
	}
}

在vs编译器中:
在这里插入图片描述
可以看出,起始容量为15,第一次扩容为2倍扩容,后续都是1.5倍扩容
g++编译器下:
在这里插入图片描述
g++编译器下没有初始容量,每次扩容都是2倍扩容

但是这样扩容的代价是很大的,为了能一次性扩容,便创建了reserve()函数。
在这里插入图片描述

string s1;
int capacity = s1.capacity();
cout << "start capacity:" << s1.capacity() << endl;

s1.reserve(1000);
for (int i = 0; i < 1000; ++i)
{
	s1.push_back('a');
	if (s1.capacity() != capacity)
	{
		cout << "capacity change:" << s1.capacity() << endl;
		capacity = s1.capacity();
	}
}

在这里插入图片描述
由于对其因素,vs实际开辟的空间会大一些。
在这里插入图片描述
linux开辟的空间就是指定的空间。

另外一件需要注意的事情是:reserve的参数如果小于目前的容量,不会缩容。

resize():
在这里插入图片描述
n>capacity:扩容+填充数据
n<capacity && n>size:填充数据
n<size:删除数据
但无论如何都不会缩容。

string s1 = "hello world";
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(15, 'a');
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(20, 'c');
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(5);
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;

在这里插入图片描述
strink_to_fit():
C++11新引入的函数,用于将capaci减少至与size相等。在这里插入图片描述

3.6 string类的修改操作

在这里插入图片描述

3.6.1 插入操作

push_back:用于尾插单个字符 。
在这里插入图片描述
append():使用方式有很多,参数类似构造函数,使用起来也很相似。
在这里插入图片描述
operator+=:既可以尾插字符,也可以尾插字符串,而且可读性很高。在这里插入图片描述
insert():在指定位置插入或字符串,好用,但是效率很低,因为需要移动数据。在这里插入图片描述

string s1("hello");
s1.push_back('a');
cout << s1 << endl;
s1.append("abcdef", 2, 3);
cout << s1 << endl;
s1 += "world";
cout << s1 << endl;
s1.insert(3, "ddd");
cout << s1 << endl;

在这里插入图片描述

3.6.2 删除操作

erase():在这里插入图片描述

string s1("hello world");
s1.erase(4, 1);		
cout << s1 << endl;
s1.erase(4);		// 不传len或者len值过大,则直接删到末尾
cout << s1 << endl

在这里插入图片描述
pop_back():C++11新引入的,就是普通的尾删。
在这里插入图片描述

3.6.3 替换函数

assign():类似于赋值重载,用新的字符串替换旧的字符串。
在这里插入图片描述

string s1("abcdef");
string s2("hello world");
s1.assign(s2, 3, 5);
cout << s1 << endl;

在这里插入图片描述
replace():用新的子串替换旧的子串在这里插入图片描述

string s1("abcdef");
string s2("hello world");
s1.replace(2, 3, s2, 3, 5);
cout << s1 << endl;

在这里插入图片描述

3.6.4 交换函数

swap():用于两个string的交换

string s1("abcdef");
string s2("hello world");
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;

在这里插入图片描述

3.6.5 其他操作

find():从pos位置开始找,返回索引值,一般搭配insert或者erase使用。在这里插入图片描述
rfind():和find类似,这不过这个是从后向前找。在这里插入图片描述

substr():提取字符串的一部分,不修改原字符串。
在这里插入图片描述

string s1("abcdefg");
string s2 = s1.substr(3, 3);
cout << s1 << endl;
cout << s2 << endl;

在这里插入图片描述

3.7 string类的非成员函数

在这里插入图片描述
operator+:就是将两个字符串合并,但由于是传值返回,导致深拷贝效率低。
operator>>和operator<<:流提取和流插入,用来方便的打印string类型的变量。
getline()
如果我们有一个字符串“hello world”想要输入给s1变量,尝试用cin试一下。

string s1;
cin >> s1;
cout << s1 << endl;

在这里插入图片描述
我们发现没有全部输入进去,这是因为不论是cin还是scanf都是用空格和换行符来作为分割的。
那我们像得到一个带空格的字符串该怎么办呢?
就只能用getline了

string s1;
//cin >> s1;
getline(cin, s1);
cout << s1 << endl;

在这里插入图片描述

relational operators:这是一些字符串比较函数在这里插入图片描述

4 vs和g++下string的结构

4.1 vs下string的结构

vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

  • 当字符串长度小于16时,使用内部固定的字符数组来存放
  • 当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

剩下的还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量,还一个指针做一些其他事情。
所以总共占28个字节。

4.2 g++下string的结构

g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

  • 空间总大小
  • 字符串有效长度
  • 引用计数

就是将这几个元素同时放在一个连续的空间中,后面就是字符串的数据。在这里插入图片描述

;