2

STL之string认识

 1 year ago
source link: https://blog.51cto.com/u_15132397/5624312
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们总算是接触到C++的STL了,这是C++模块中很重要的一部分,可以这么说,如果你学C++没有学过STL,那么你的C++很大概率是残缺的,我们学习STL主要学习两个层次,第一个是可以使用,第二个是知道它们的底层,至于更高层次的,现在的我还没有达到,就不和大家分享了。我们先来简单的认识一下STL,有一个大致的了解,今天主要的内容时string。

什么是STL

STL(standard template libaray-标准模板库): C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。我之前和大家分享过数据结构的知识,也和大家用C语言写过,但是它们的可用性有点低,你在写OJ题时,使用C语言,你会发现有的时候还要自己实现数据结构,大佬们也是有这样的困扰,所以出现STL,使用的便是泛型编程,这也是我们前面谈模板的原因。

STL版本

我们学习STL共有三个版本,里面的功能都大致一样。

  • 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,这是最初的STL
  • P. J. 版本 继承HP版本,被微的VS系列使用,代码可读性底
  • SGI版本 继承自HP版 本。被GCC(Linux)采用,可移植性好,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。

STL 组件

STL的六大组件,大家直接看着图片吧,后面的内容都会涉及到,这里大家先来认识一下。

STL之string认识_字符串

string

我们先来认识一下什么是string,简单来说,string在C语言里我们认识的字符串,在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数 。由于我们比较常用,所以大佬们就把这个给变成了模板类了,某种意义上来说,string不属于STL,它的出现要早的多。

认识string

先看看STL里面关于string的分类,我们可能会感到疑惑,这里怎么会有四种string,我们要学习的是哪一种,SLT不是模板吗,为何这里string看着不像...这些问题我们一个一个解决.

STL之string认识_迭代器_02

字符编码表

在谈为何会有四种string之前?我们需要谈谈什么是字符编码表.我们都知道在计算机世界中,它们只认识0和1,但是现实世界中,我们是有类似汉语,英语的语言的,我们该如何把现实世界和计算机世界给联系起来呢?这就是字符编码表的作用.最初的字符编码表是由美国提出的,叫做ASCII码,是不支持中文的.

STL之string认识_字符串_03

为了编码属于各自国家的字符表,中国编出了GBK,使用的是16位的二进制,但是各国还没有一个统一的标准,这时候国际组织ISO制定了Unicode,也叫万国码.

我们已经知道了字符表的历史,所谓的四种string就是为了支持不同位的字符,是的,我们不是只有char这一个字符类型,我们主要学习的就似乎string,他们四个用法大概差不多,学会了一个,另外几个查查文档就可以了.

int main()
{
cout << "string : " << sizeof(char) << endl;
cout << "u16string : " << sizeof(char16_t) << endl;
cout << "u32string : " << sizeof(char32_t) << endl;
cout << "wstring : " << sizeof(wchar_t) << endl;
return 0;
}
STL之string认识_迭代器_04

string 是模板码

是的,我们看到的string是typedef过的,也就是它是参数为char类型的模板.

STL之string认识_字符串_05

string 的底层

string的底层是一个可以动态开辟的字符数组,这一点大家一定要记住,后面我们模拟实现的时候就是按照这个来的.

int main()
{
string s("hello");
return 0;
}
STL之string认识_编译器_06

我们来真正的看看这个数组,避免出现问题.

使用string

现在我们就可以使用string,我们使用的时候需要包头文件,而且还需要放出std里面的string,少说多做,我们看看就明白了.

#include <iostream>
#include <string>

using std::string;

int main()
{
std::string s1; // 或者直接放出
string s2;
return 0;
}

现在我们就可以正式接触string里面的内容了,注意C98中string里面存在七个构造函数,但是我们不是每一个都常用,这里我来简绍的有四个.

STL之string认识_编译器_07

string()

构造一个空字符串

string(const string& str)

拷贝构造,深拷贝

string(const char* s)

通过一个字符串来构造

string(size_t n, char c)

构造一个含有 n 个 c字符的字符串

我们现在分别来演示一下.

string()

int main()
{
string s;
return 0;
}
STL之string认识_迭代器_08

string(const char* s)

int main()
{
string s("hello string");
return 0;
}
STL之string认识_迭代器_09

string(size_t n, char c)

int main()
{
string s(10,'a');
return 0;
}
STL之string认识_字符串_10

string(const string& str)

int main()
{
string s1("hello");
string s2(s1);
return 0;
}
STL之string认识_编译器_11

长度 & 容量

我们知道了string底层是一个动态开辟的数组,那么string支持了计算我们string对象的有效长度和容量的函数我们直接看看吧.

size() & length()

这两个都是计算string对象的有效长度的,唯一的区别就是函数名不同罢了,length()是最初的方法,我们字符串的有效长度可以形容为length,但是后面的哈希表等就有点说不过去了,所以增加了size()这个函数,为了保持命名的规范性,仅此而已.

STL之string认识_编译器_12

int main()
{
string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
return 0;
}
STL之string认识_迭代器_13

capacity()

计算是当前对象的容量,也就是数组的长度.

int main()
{
string s("hello");
cout << s.capacity() << endl;
return 0;
}
STL之string认识_迭代器_14

max_size()

这个函数就是我们字符串最大能开多长,一般是没有人用的,这里我就提一下就可以了.

int main()
{
string s;
cout << s.max_size() << endl;
return 0;
}
STL之string认识_字符串_15

operator[\] & at()

string重载了[\]这运算符,可以支持下标访问,这里的at()的作用和[\]作用是一样的,只不过是早期版本,功能上没有什么区别.

int main()
{
string s("hello");
cout << s[1] << endl;
cout << s.at(1) << endl;
return 0;
}
STL之string认识_编译器_16

我们访问的如果是有效字符的下一个位置,返回的是一个\0,大家现在还不知道底层,但是我们知道空字符串里面是包含一个\0 的,这是为了兼容C语言.

int main()
{
string s("hello");
if (s[5] == '\0')
{
cout << "s[5] == \\0 " << endl;
}
return 0;
}
STL之string认识_编译器_17

但是如果我们越界访问了,VS编译器会出现报错.

int main()
{
string s("hello");
s[16];
return 0;
}
STL之string认识_字符串_18

遍历string

遍历string我们存在三种方法,这三种方法也是我们经常用到的。

  • 使用 迭代器 这个我们重点谈
  • 使用 范围for

### 下标访问

string这个类是重载了[\]这个运算符的,再加上string底层是一个数组,它是支持随机访问的,

STL之string认识_迭代器_19

int main()
{
string s("hello");
for (size_t i = 0; i < s.size(); i++)
{
cout << s[i] << " ";
}
cout << endl;
return 0;
}
STL之string认识_迭代器_20

迭代器访问

或许你还对迭代器有点疑惑,不知道它是什么,这样吧,我简单的说一下,所谓的迭代器就是我们遍历STL最常用的方法.是的,你没有看错,不是所有的STL底层都是连续的空间,也就是说使用下标访问是不具有普遍性的.

string中的迭代器就是一个原生指针,其中迭代器又分为四种.我们来看看吧.

STL之string认识_编译器_21
正向迭代器

这里我直接用两种方式来访问,一个是const修饰的,一个可修改的.

下面的是正向迭代器指向的地方,注意end()指向的有效字符的下一个位置.

STL之string认识_迭代器_22

可以修改迭代器指向的内容

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

return 0;
}
STL之string认识_编译器_23

如果我们想要修改迭代器指向的内容,这里就可以知道了.

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

return 0;
}
STL之string认识_字符串_24

如果我们不想修改迭代器指向的数据,可以直接调用const修饰的迭代器

STL之string认识_字符串_25

这个迭代器的主要应用是给那些不想修改的string,我们看看应用.

void func(const string& str)
{
string::const_iterator it = str.cbegin();
while (it != str.cend())
{
cout << *it << " ";
it++;
}
cout << endl;
}
int main()
{
string s("hello");
func(s);
return 0;
}
STL之string认识_迭代器_26
反向迭代器

谈完了正向的迭代器,这里就要谈谈什么是反向迭代器,它的作用也是遍历string,只不过是反这遍历的.

STL之string认识_编译器_27

这个迭代器是可以修改的,这里就不修改了.

int main()
{
string str("hello");
string::reverse_iterator rit = str.rbegin();
while (rit != str.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
STL之string认识_迭代器_28

现在还有可以反向的const修饰的迭代器,我们用用就可以了,具体的就不谈了.

int main()
{
string str("hello");
string::const_reverse_iterator rit = str.crbegin();
while (rit != str.crend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
STL之string认识_迭代器_29

范围 for

范围 for就比较简单了,但是有一个缺陷,就是一次肯定遍历完

int main()
{
string str("hello");
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
STL之string认识_迭代器_30

它看着比较高大上,实际上底层也是对迭代器的复用,这里的复用的迭代器是可以修改的那种

STL之string认识_编译器_31
STL之string认识_字符串_32

push_back()

string支持尾部插入一个字符,如果容量不够,编译器会自动扩容.

int main()
{
string s;
s.push_back('a');
s.push_back('b');
s.push_back('c');
s.push_back('d');
cout << s << endl;
return 0;
}
STL之string认识_编译器_33

string 扩容机制

我们知道了string的底层是一个动态数组,我们现在想看看它是如何扩容的.

从这里我们可以看出,不同编译器的扩容规则是不一样的,VS下是1.5倍扩容,g++是2倍扩,而且刚开始VS就开辟了15个空间,g++没有开

STL之string认识_字符串_34

reserve()

大家都知道,扩容是代价的,有的时候需要再次开辟空间和进行数组的拷贝,如果我们要是知道总共开辟的空间,那么提前开辟好不是更好吗,这就是这个函数的作用.

  • 只改变容量
  • 不改变 size,也就是你可以理解为它是只扩容.
STL之string认识_字符串_35

注意,reserve()函数开辟的空间不一定和我们要的一样,需要内存对齐的,但是大差不差.

int main()
{
string s;
s.reserve(100);
return 0;
}
STL之string认识_编译器_36

但是这里就有两个疑问了,如果我们的N大于现在存在的存在的容量会怎么样,小于又会怎么样?这里我们来讨论一下.

N > capacity

这个和上面的一样,会自动扩容,只改变capacity,不改变size.

int main()
{
string s("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
int size = s.size();
cout << size << endl;
s.reserve(s.capacity() + 20);
return 0;
}
STL之string认识_迭代器_37

N < capacity

这种情况如果发生,不会出现任何变化.

int main()
{
string s("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
cout << "原始 size " << s.size() << endl;
cout << "原始 capacity() " << s.capacity() << endl;
s.reserve(15);


cout << "size " << s.size() << endl;
cout << "capacity() " << s.capacity() << endl;
return 0;
}
STL之string认识_迭代器_38

resize()

这个函数的作用从名字上面就可以看出它是重置size,也就是说,我们重新指定尾插数据的地方,如果空间不够,编译器会自动扩容.但是这里有两个函数,本质上是一个缺省函数.

STL之string认识_编译器_39
int main()
{
string s;
cout << "size " << s.size() << endl;
cout << "capacity() " << s.capacity() << endl;

s.resize(20);
cout << "size " << s.size() << endl;
cout << "capacity() " << s.capacity() << endl;
return 0;
}
STL之string认识_字符串_40

void resize (size_t n)

这个是重置 N个空间,从开始的那里开始数,数到下标为N的地方,这个及其以后都重置的空间变成\0.这里分为三种情况.这里我们就来讨论两种共情况,第三种就是扩容的情况.

STL之string认识_迭代器_41
int main()
{
string s;
s.push_back('a');
s.push_back('b');
s.push_back('b');
s.push_back('b');
s.push_back('b');
s.resize(2);

return 0;
}
STL之string认识_字符串_42
int main()
{
string s;
s.push_back('a');
s.push_back('b');
s.push_back('b');
s.resize(7);

return 0;
}
STL之string认识_字符串_43

这里我就给一个总结,所谓的resize就是把有效元素的最后一个位置的后一个位置给改变,改成下标是N,凡是和原来的有效位置之间的距离,都给我变成\0.

void resize (size_t n, char c)

这个就更加简单了,没有什么可以说的,也符合上面的两种规则,也符合不够的扩容规则.就是把默认的\0改成我们想要的字符,这里就不解释其他的情况了.

int main()
{
string s;
s.push_back('a');
s.push_back('b');

s.resize(20,'1');
cout << s << endl;
return 0;
}
STL之string认识_迭代器_44

clear()

清理有效字符,VS下不会进行缩容,看编译器自己的选择.

int main()
{
string s("hello");
s.reserve(100);
s.clear(); // 只改变 size 缩容不缩容看编译器
return 0;
}
STL之string认识_编译器_45

append()

我们发现,push_back尾插是可以的,但是它支持插入一个字符,我们想要插入一个字符串,可不可以,string也提供了这个接口.我们把最长用的的几个个大家简绍一下.

STL之string认识_迭代器_46

append()

string& append (const string& str);

尾插一个string对象

string& append (const char* s);

尾插一个字符串

template string& append (InputIterator first, InputIterator last);

使用迭代器尾插

我直接演示一下这几个函数吧,用法还是比较简单的,这里新增的迭代器尾插是我们没有见识的过的,这里单独用一下.

int main()
{
string s("hello");
s.append("qkj");
cout << s << endl;
string s2("你好啊");
s.append(so2);
cout << s << endl;
return 0;
}
STL之string认识_字符串_47

string& append (InputIterator first, InputIterator last)

这个还是挺容易的,它是尾插first到last的内容,没有什么神奇,到后面几个博客,我们会专门谈谈迭代器.

int main()
{
string s("hello");
string s2("你好啊");


s.append(s2.begin(),s2.end());
cout << s << endl;
return 0;
}
STL之string认识_编译器_48

operator+=

相对于上面的append,这个才是我们经常用的,而且更容易理解.

STL之string认识_迭代器_49

这个我们会经常用.

int main()
{
string s("hello");
s += "aaaa";
cout << s << endl;

s += 'c';
cout << s << endl;
string s2("hahahah");

s += s2;
cout << s << endl;
return 0;
}
STL之string认识_字符串_50

insert()

一般情况下,我们是不用这个函数的,都是使用的尾插,但是为了让大家了解一下,这里都给大家拿出来,给大家简绍一下比较常用的.

STL之string认识_编译器_51

insert()

string& insert (size_t pos, const string& str);

在pos位置插入一个字符串

string& insert (size_t pos, const char* s);

iterator insert (iterator p, char c);

大家会用就可以了.

int main()
{
string s1("hello");
string s2(" qkj");
s1.insert(5, s2);
cout << s1 << endl;

s1.insert(2, "xxxxxx");
cout << s1 << endl;
string::iterator it = s1.begin();

s1.insert(it + 2, '1');
cout << s1 << endl;

return 0;
}
STL之string认识_迭代器_52

erase()

STL之string认识_编译器_53

这个是删除下标从pos往后数len个字符,如果不传字符就删除 npos个,这里的npos是一个静态常量,数据很大.

STL之string认识_迭代器_54

int main()
{
string s1("hello");
s1.erase(1,3);
cout << s1 << endl;

return 0;
}
STL之string认识_字符串_55

至于那个传迭代器的就不和大家演示了.

swap()

string里面是存在swap函数的,当然我们std中的那个也可以用,只不过需要发生深拷贝,效率有点低.

int main()
{
string s1("hello");
string s2("qkj");
cout << s1 << endl;
cout << s2 << endl;

s1.swap(s2);

cout << s1 << endl;
cout << s2 << endl;

return 0;
}
STL之string认识_迭代器_56

c_str()

这个函数返回就是这个数组的指针,在工作中,有的是不接容string的指针,这里就要string里面的指针,现在可能还没有什么用处.

int main()
{
string s1("hello");
const char* str = s1.c_str();
cout << str << endl;

return 0;
}
STL之string认识_迭代器_57

find()

find()是查找string里面是不是有我们想要的字符或者字符串,默认是从下标0开始的,返回的是字符的下标或者是字符串的的头位置,找不的话就是返回npos.

STL之string认识_迭代器_58
int main()
{
string s1("hello");
size_t pos = s1.find('e', 3); // 从 下标 3 开始
if (pos != string::npos)
{
cout << s1[pos] << endl;
}
else
{
cout << "没有找到" << endl;
}
return 0;
}
STL之string认识_字符串_59
int main()
{
string s1("hello");
size_t pos = s1.find("lo", 2); // 从 下标 3 开始
if (pos != string::npos)
{
cout << "找到了" << endl;
}
else
{
cout << "没有找到" << endl;
}
return 0;
}
STL之string认识_迭代器_60

还有一个rfind()函数,这个是从string对象后面来查找,这里就不和大家分享了.

substr()

这个可以说字符串截断的函数,从pos位置开始截,len个字符,这里也是一个缺省函数.

STL之string认识_迭代器_61
int main()
{
string s1("hello");
string s2 = s1.substr(1, 2);
cout << s2 << endl;
return 0;
}
STL之string认识_字符串_62

getline()

我们都知道,>>运算符遇到空格就结束了,这里对于string也是一样的.

int main()
{
string s;
cin >> s;
cout << s << endl;
return 0;
}
STL之string认识_编译器_63

这个函数不是string特有的,在std里面封者.

int main()
{
string s;
std::getline(cin,s);
cout << s << endl;
return 0;
}
STL之string认识_编译器_64

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK