【C++】C++标准模板库STL (一) string类的使用详解
创始人
2024-06-01 23:54:54
0

前言

在前一章种我们介绍了C++中的模板的使用,这是一种泛型编程,模板的使用能让我们减少大量的相似代码,减少我们的代码量与工作量,写出更加高效简洁的代码,模板如此好用,但还是要我们先出写一个泛型类或函数,才能使用。有没有办法直接不写泛型类或函数就能使用一些常见的数据结构与算法的代码呢?答案是有的!C++给我们提供了标准模板库STL,这是一个相当重要的库,它就完美解决了上面的问题,相信学完以后你会对它爱不释手!

C++标准模板库STL (一)

  • 一、STL的基础介绍
    • 1. 什么是STL
    • 2. STL的版本
    • 3. STL的六大组件
  • 二、string类的介绍
    • 1. 为什么学习string类
    • 2. 标准库中的string类
  • 三、string类的常用接口说明
    • 1. string类对象的常见构造函数
    • 2. string类对象的容量操作
    • 3. string类对象的单个元素访问
    • 4. string类对象的元素的遍历
    • 5.string类对象的修改操作
    • 6.string类对象的一些其他操作
    • 7.getline函数
  • 四、结语


一、STL的基础介绍

1. 什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

2. STL的版本

  • 原始版本
    Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖。
  • P. J. 版本
    由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
  • RW版本
    由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
  • SGI版本
    由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。如果你要深入学习STL想要阅读部分源代码,可以主要参考这个版本。

3. STL的六大组件

在这里插入图片描述

二、string类的介绍

string类是STL中的容器,也是我们学习STL的开始。

1. 为什么学习string类

  • C语言中的字符串
    C语言中基本类型有char类型没有字符串类型,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数(如:strlen, strcmp,strcat,strcpy 等),但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

因此为了更加高效的使用和管理字符串,C++提出了string类的概念,关于string类的学习我们先学习string类的使用,后面我们进一步深入时我们再去动手模拟实现string类,在这里我们首先知道string类的底层其实就是char类型的顺序表就行了。

2. 标准库中的string类

string类的文档介绍
从文档中我们能看到:
在这里插入图片描述

  1. string类basic_string模板类的一个实例,它使用char来实例化basic_string模板类。
  2. string在底层实际是:basic_string模板类的别名,typedef basic_stringstring;
  3. 不能操作多字节或者变长字符的序列。(如英文使用ASCII编码使用一个字节表示一个字母,汉字如果采用GBK编码占用两个字节表示一个汉字)
  4. 处理多字节或者变长字符的序列要用到以下三个类:
    在这里插入图片描述
  5. 在使用string类时,必须包含 #include以及using namespace std;

三、string类的常用接口说明

在上面的string类的文档介绍中我们可以看到string类接近有140个不同的接口函数,在这么多的成员函数中,很多都是冗余不必要的,因此下面我只讲解最常用的接口。

在讲解这些函数接口之前,我们先来介绍nposnposstring类里面的一个静态成员变量,因此用string类创建的所有对象访问的都是同一个npos,其次我们来看pos的定义:

在这里插入图片描述

首先npos的值是被const修饰了,其值不能更改,然后size_t是无符号整形unsignedtypedef,定义将-1的值赋值给了npos,但npos是无符号整形,因此此时nops的值就是无符号整形的最大值65535。

1. string类对象的常见构造函数

在这里插入图片描述

  1. 缺省构造函数,用空字符串初始化string对象
  2. 拷贝构造函数,用string类的对象另一个对象
  3. string类的对象中的字符串的子串初始化一个对象,子串的起始位置是pos,结束位置是pos向后的len个字符,如果len没有给缺省值,默认为npos即从pos位置后面有多少拿多少。
  4. 用C风格的字符串初始化string类的对象
  5. 用C风格的字符串前n个字符初始化string类的对象
  6. 用n个字符初始化string类的对象
#include
#include
using namespace std;
int main()
{string s0("hello world!");string s1;					//default (1)		缺省构造函数   //用空字符串初始化s1对象	string s2(s0);				//copy (2)			拷贝构造函数   //用string类的对象s0初始化s2对象string s3(s0, 6, 5);		//substring (3)		用string类的对象s0的字符串的子串"world"初始化s3  //这里pos参数表示位置,len表示的是个数string s4("hello string");	//from c-string (4)	//C风格的字符串初始化string类的对象s4string s5("hello world",5);	//from sequence(5)	//用C风格的字符串前五个字符"hello"初始化string类的对象s5string s6(5, 'x');			//fill (6)	//用5个'x'初始化string类的对象s6cout << s0 << endl;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;
}

在这里插入图片描述


2. string类对象的容量操作

在这里插入图片描述

  • 我们先来看看不涉及扩容的函数
#include
#include
using namespace std;
int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.length() << endl;cout << s1.capacity() << endl;cout << s1.max_size() << endl;s1.clear();cout << s1.empty() << endl;return 0;
}

在这里插入图片描述

  • 我们再来看看涉及扩容的函数
  1. std::string::reserve()函数与std::string::shrink_to_fit()函数
//std::string::reserve()函数
#include
#include
using namespace std;
int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.capacity() << endl;s1.reserve(200);	//提前把空间扩容到200字节,但不初始化//由于string类内部的一些原因(如:对齐等原因),虽然向系统申请的的200字节,//但不一定就是200字节,总之系统申请的空间会 >= 你给的指定值cout << s1.size() << endl;cout << s1.capacity() << endl;s1.shrink_to_fit();	//缩减容量让 size == capacitycout << s1.size() << endl;cout << s1.capacity() << endl;return 0;
}

在这里插入图片描述

观察会发现,经过reserve()函数的处理后size(字符个数)没有变,但是capacity(容量)改变了。
经过shrink_to_fit()函数处理后size(字符个数)没有变,但是capacity(容量)改变了。(虽然,shrink_to_fit()函数能够减少空间的浪费,但是我们还是一般不缩容,因为缩容的消耗是很大的)

  1. 我们再来看另一个扩容有关的函数:std::string::resize()函数
    在这里插入图片描述
    此函数有两个版本,这两个版本构成函数重载。
    第一个参数是:调整后容量的大小,第二个参数是用什么字符来初始化新申请的空间中多余的没有被初始化部分空间,如果不给此参数,就默认用’\0’来初始化。
    如果第一个参数给的没有原来的大,那就是缩容,里面的字符串就变成了只保留原先字符串从0位置开始到n位置的字符串。
//std::string::resize()函数
#include
#include
using namespace std;
int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.capacity() << endl;s1.resize(30);	//提前把空间与字符数量扩容到50字节,但不初始化//由于string类内部的一些原因(如:对齐等原因),虽然向系统申请的的50字节,//但不一定就是50字节,总之系统申请的空间会 >= 你给的指定值cout << s1.size() << endl;cout << s1.capacity() << endl;return 0;
}

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

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小。
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用’\0’来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t n=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

3. string类对象的单个元素访问

在这里插入图片描述

//string 里面关于单个元素的访问
#include
#include
using namespace std;
int main()
{string s1("hello world!");char ch1 = s1[1];		//operator[]的运算符重载char ch2 = s1[2];		//operator[]的运算符重载char ch3 = s1.at(6);cout << ch1 << endl;cout << ch2 << endl;cout << ch3 << endl;cout << "---------------" << endl;cout << s1.front() << endl;cout << s1.back() << endl;cout << "---------------" << endl;//捕获异常try{s1.at(100);}catch (const std::exception& e){//显示异常原因cout << e.what() << endl;}return 0;
}

在这里插入图片描述


4. string类对象的元素的遍历

对于string类对象的遍历有以下四种方式:

在这里插入图片描述

  • 第一种operator运算符重载我们已经学会了,我们可以利用如下代码进行遍历字符串:
#include
#include
using namespace std;
int main()
{string s1("hello world");for (int i = 0; i < s1.size(); ++i){cout << s1[i];}cout << endl;return 0;
}

在这里插入图片描述

  • 第二种begin end就需要借助STL里面的迭代器了,iterator是迭代器类型,是一种自定义类型,现在你可以暂时把它理解为类似于指针的类型。
    想要遍历字符串我们就应该这样遍历:
    在这里插入图片描述
#include
#include
using namespace std;
int main()
{string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it;++it;}cout << endl;return 0;
}

在这里插入图片描述

  • 第三种是rbegin rend 它们其实也是迭代器,不同的是,它们是反向迭代器,所以我们应该这样写才能遍历string对象。在这里插入图片描述
#include
#include
using namespace std;
int main()
{string s1("hello world");string::reverse_iterator it = s1.rbegin();while (it != s1.rend()){cout << *it;++it;}cout << endl;return 0;
}

在这里插入图片描述

  • 第三种是范围for,在前面的文章中我们也谈论过范围for,利用范围for,也可以遍历string类(范围for的底层实现就是迭代器)。
#include
#include
using namespace std;
int main()
{string s1("hello world");for (auto& e : s1){cout << e;}cout << endl;return 0;
}

在这里插入图片描述


在前面已经简单的介绍了两种迭代器,(正向迭代器与反向迭代器)下面我们就把另外两种迭代器进行简单介绍补充:

  • const_iterator类型的迭代器
    此类型的迭代器,不能通过迭代器来改变指向的对象里的内容。
#include
#include
using namespace std;
int main()
{const string s1("hello world");string::const_iterator it = s1.begin();while (it != s1.end()){(*it)++;	//此行为非法!it++;		//此行为合法!}return 0;
}
  • const_reverse_iterator类型的迭代器
    此类型的迭代器是反向迭代器,并且不能通过迭代器来改变指向的对象里的内容。
#include
#include
using namespace std;
int main()
{const string s1("hello world");string::const_reverse_iterator it = s1.rbegin();while (it != s1.rend()){(*it)++;	//此行为非法!it++;		//此行为合法!}return 0;
}

5.string类对象的修改操作

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

#include
#include
using namespace std;
int main() 
{string s0("hello ");string s1("world");s0 += s1;cout << s0 << endl;s0 += " string";cout << s0 << endl;s0 += '!';cout << s0 << endl;return 0;
}

在这里插入图片描述

在这里插入图片描述

#include
#include
using namespace std;
int main()
{string s0("hello ");string s1("world");//substring (2)s0.append(s1, 0, 3);cout << s0 << endl;//buffer (4)	s0.append("string", 3);cout << s0 << endl;//fill (5)s0.append(5, '!');cout << s0 << endl;return 0;
}

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

#include
#include
using namespace std;
int main()
{string s0("hello ");s0.push_back('!');cout << s0 << endl;
}

在这里插入图片描述

在这里插入图片描述
代码与append()函数代码类似,不再演示

注意:

  1. 在string尾部追加字符时,s.push_back( c ) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般
    情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

    在这里插入图片描述
#include
#include
using namespace std;
int main()
{string s0("hello ");string s1("world");//string (1)	s0.insert(0, s1);cout << s0 << endl;//c-string (3)	s0.insert(3, "!!!!!!");cout << s0 << endl;
}

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

#include
#include
using namespace std;
int main()
{string s0("hello ");s0.erase(2, 2);cout << s0 << endl;
}

在这里插入图片描述

在这里插入图片描述

#include
#include
using namespace std;
int main()
{string s0("hello ");//c-string (3)s0.replace(2, 2,"!!!!!");cout << s0 << endl;
}

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

#include
#include
using namespace std;
int main()
{string s0("hello ");string s1("world");s0.swap(s1);cout << s0 << endl;cout << s1 << endl;return 0;
}

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

#include
#include
using namespace std;
int main()
{string s0("hello");s0.pop_back();cout << s0 << endl;return 0;
}

在这里插入图片描述


6.string类对象的一些其他操作

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

#include
#include
using namespace std;
int main()
{string s1("hello world");const char* str1 = s1.c_str();cout << str1 << endl;return 0;
}

在这里插入图片描述

在这里插入图片描述

#include
#include
using namespace std;
int main()
{string s1("hello world");/*const char* str1 = s1.c_str();cout << str1 << endl;*/char ch = 'a';char* str1 = &ch;s1.copy(str1, 3, 2); // 注意此函数不会拷贝完后追加字符串,打印字符串时要小心越界。return 0;
}

在这里插入图片描述

#include
#include
using namespace std;
int main()
{//buffer (3)	string s1("hello world");const char* str = "world";int index = s1.find(str, 0, 3);cout << index << endl;return 0;
}

在这里插入图片描述

在这里插入图片描述

#include
#include
using namespace std;
int main()
{//c-string (2)	string s1("hello world");const char* str = "world";int index = s1.rfind(str);cout << index << endl;return 0;
}

在这里插入图片描述

在这里插入图片描述

#include
#include
using namespace std;
int main()
{string s1("hello world");int index = s1.find_first_of("wor");cout << index << endl;return 0;
}

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

#include
#include
using namespace std;
int main()
{string s1("hello world");string s2 = s1.substr(2, 3);cout << s2 << endl;return 0;
}

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

#include
#include
using namespace std;
int main()
{//string (1)	string s1("hello world");string s2("iello");cout << s1.compare(s2) << endl;return 0;
}

在这里插入图片描述


7.getline函数

getline的作用是读取一整行,直到遇到换行符才停止读取,期间能读取像空格、Tab等的空白符。
在这里插入图片描述

getline函数和cin一样,也会返回它的流参数,也就是cin,所以可以用getline循环读取带空格的字符串。

注意: getline 是非成员函数!

#include
#include
using namespace std;
int main()
{string s1;//这里我们输入"hello world!"getline(cin, s1);cout << s1 << endl;return 0;
}

在这里插入图片描述

四、结语

本章的内容都不是很难,但是琐碎的函数与细节特别多,想要掌握好它们还需要多加练习。多去使用它们,相信你会越用越熟悉的。

相关内容

热门资讯

歌颂母亲的诗歌 歌颂母亲的诗歌(15篇)  无论在学习、工作或是生活中,许多人都接触过一些比较经典的诗歌吧,诗歌具有...
小学生诗歌朗诵春天 小学生诗歌朗诵春天  春天像小姑娘,花枝招展的,笑着,走着。  1、《春天的集会》  冬天刚过,  ...
诗词之源早于《诗经》 诗词之源早于《诗经》  大量史料说明,中国诗歌的星宿之海应上溯一千年,虞舜时代的《卿云歌》和《南风歌...
在我最想你的时候爱情诗歌 在我最想你的时候爱情诗歌  在我最想你的时候  默然夕拾  夜深了  几乎绣花针掉到地上  都能发出...
谢灵运山水诗对李白的创作影响 谢灵运山水诗对李白的创作影响  东晋诗人谢灵运,作为中国古典诗歌山水诗派的开创者之一,在晋宋之交,为...
永远的守巢人诗歌_歌颂老师   又到毕业季,又有不少的孩子即将离开那个呵护了他们三年的小乐园,开始第一次学会和朋友们说再见。下面...
小学生抗疫情诗歌 小学生抗疫情诗歌  无论是身处学校还是步入社会,大家一定没少看到经典的诗歌吧,诗歌语言言简义丰,具有...
忘不掉你的承诺诗歌 忘不掉你的承诺诗歌  当自己心中的那个他走了之后,  让人最难忘记的是“记忆”  因为,  那一段记...
你是我心中永远的痛爱情诗歌 你是我心中永远的痛爱情诗歌  你我曾经  肩并肩、手牵着手徜徉在花丛中  一起观赏人间的四季美景  ...
爱情的诗歌 有关爱情的诗歌  在平时的学习、工作或生活中,大家都看到过许多经典的诗歌吧,诗歌是按照一定的音节、韵...
适合过年听的欢快的歌曲 适合过年听的欢快的歌曲  过年期间都是一个很喜庆的时间,很多店铺商场都开始放一些音乐去吸引客人或者说...
醉生梦死诗歌 醉生梦死诗歌醉生梦死诗歌1  禁果的滋味  苦涩而甜蜜  人生第一次爱过  不知不觉已成悲伤之人  ...
《雨说》诗歌 《雨说》诗歌  在平日的学习、工作和生活里,许多人都接触过一些比较经典的诗歌吧,诗歌饱含丰富的感情和...
蜗行诗歌 蜗行诗歌  在生活、工作和学习中,大家总免不了要接触或使用诗歌吧,诗歌在形式上,不是以句子为单位,而...
国庆节的诗歌 关于国庆节的诗歌再过几天就是十月一号的国庆节了,自古以来,吟诗作赋是文人对节日的表达感情方式之一。下...
歌颂伟大的母亲党成立100周... 弹指间,2021年来了,我们伟大的母亲中国共产党也走过了100年,那歌颂伟大的母亲中国共产党成立10...
欧阳修的醉翁亭记 欧阳修的醉翁亭记  《醉翁亭记》不仅是一首千古传诵的游记,散文中饶有诗情画意,别具清丽格调,是一篇具...
关于中秋节的诗歌大全 关于中秋节的诗歌大全,小编为你推荐下文,希望可以帮助到你。欢迎你的阅读参考。1、《中秋》唐·李朴皓魄...
《落日怅望》诗歌鉴赏 《落日怅望》诗歌鉴赏  落日怅望  马戴  孤云与归鸟,千里片时间。  念我何留滞,辞家久未还。  ...
小学画课文原文 小学画课文原文  画为初唐诗人王维所做的一首五言绝句。下面就随小编一起去阅读小学画古诗原文,相信能带...