【C++】简述STL——string类的使用
@[toc]
一、STL的简述
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分**,不仅是一个可复用的组件库,而且一个包罗数据结构与算法的软件框架**。
1.STL的框架
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要阅读部分源代码,主要参考的就是这个版本。
对于STL的学习我们可以前去官网看文档,多了解了解cplusplus.com
二、编码铺垫
string类本身就是一个模板,为什么要把string写成模板?是因为字符串的数组涉及编码问题,字符数组编码不同。所以需要模板
u16string:表示两个字节
u32string:表示四个字节
这里简单了解一下编码
- ✨ASCII码
美国信息交换标准码。
ASCII码表是计算机存值和文字符号的对应关系
只有256个字符
- ✨Unicode
万国码
Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码
包括了utf-8,utf-16,utf-32
utf-8兼容了ASCII,utf-8使用比较普遍,也比较节省空间
- ✨gbk
gbk即国标,针对中文而设计的编码。采用双字节编码。
三、string类
根据不同的编码选用不同的string(接口是差不多的),这里我们只需重点学习string(utf-8):
string类模板的大概框架:
template <class T>
//动态增长字符数组
class basic_string
{
private:
T* _str;
size_t _size;
size_t _capacity;
};
使用string类的时候,我们要包含头文件#include <string>
下面我们开始说一说string类常用的接口,对于常用接口我们需要熟练使用,其他的即可查阅学习。
四、常见构造
废话不多说,我们直接来使用一下这些构造函数,形成初步了解:
#include <iostream>
#include <string>
using namespace std;
void test_test1()
{
string s1;//默认构造
string s2("hello string");//带参构造
s2 += "world";
string s3 = "hehe";//构造+拷贝构造----直接构造
string s4(s2);//拷贝构造
string s5 = s2;
string s6(10, 'X');//个数初始化
string s7("hello world", 5);//取前n个
string s8(s7, 2, 3);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
}
int main()
{
test_test1();
return 0;
}
==注意:string类对象可支持直接用cin和cout进行输入和输出,这是因为重载了流插入>>和流提取<<操作符==
对于s8,如果取的长度过大,会发生什么?查看文档:
直到结束,并不会报错:
对于默认值npos:
这里的npos是-1,但是这里是无符号,实际并不是-1,是4294967295。
通过这里的构造函数,我们需要知道,对于一些细节东西我们是要学会去看文档的,对于学习文档是非常重要的。
五、operator[]
operator[] 返回pos位置的字符,const string类对象调用
实际上重载了[],让string类可以像数组一样访问。数组的[]的本质是解引用,而这里是调用函数:
char& operator[](size_t pos)
{
assert(pos<_size);//检查越界
return _str[i];
}
传引用返回,可读可改。支持修改返回值:我们通过代码来实现这一功能
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("123456");
//读
for (size_t i = 0; i < s.size(); i++)
{
cout << s[i] << ' ';
}
cout << endl;
//改
for (size_t i = 0; i < s.size(); i++)
{
s[i] += 1;
}
cout << s << endl;
return 0;
}
当然,对于const修饰的字符串肯定不能修改了。
六、访问及遍历
对于遍历操作,第一个方式就是我们上面所说的operator[],第二个方式就是范围for
七、iterator迭代器
对于遍历,除了上面2种方式,我们也可以采取迭代器。迭代器行为上像指针,但是却不一定是指针。对于string的遍历,最简单的肯定是第一种,为什么还要有迭代器的存在呢
- 为什么要有迭代器❓
迭代器的意义在于通用,所有容器都可以使用迭代器这种方式去进行遍历和修改。而对于string类,[]足矣
1.正向迭代器
迭代器提供了begin和end的函数:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("123456");
//读
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//写
it = s.begin();
while (it != s.end())
{
*it += 1;
++it;
}
cout << s << endl;
return 0;
}
2.反向迭代器
反向迭代器提供了rbegin和rend:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("hello world");
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
3.const迭代器
就是加上const进行修饰。普通的迭代器可读可写,而const迭代器可读不可写,因为const修饰this指向的内容,不支持写
const迭代器也可分为正向迭代器和方向迭代器,提供给const对象使用:
void Print(const string& s)
{
string::const_iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
string::const_reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
cout << endl;
}
int main()
{
string s("hello world");
Print(s);
return 0;
}
c++11还特意区别了普通迭代器和const迭代器提供了下面的接口,作为区分:
八、Capacity容量操作
1.常用接口
函数名称 | 功能说明 |
---|---|
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reserve | 为字符串预留空间(影响capacity) |
resize | 将有效字符的个数该成n个,多出的空间用字符c填充(影响size和capacity) |
//string类对象支持直接用cin和cout进行输入和输出
void Teststring1()
{
string s("hello world!");
cout << s.size() << endl;//12
cout << s.length() << endl;//12
cout << s.capacity() << endl;//15
cout << s << endl;
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl;//0
cout << s.capacity() << endl;//15
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;//10
cout << s.capacity() << endl;//15
// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此时s中有效字符个数已经增加到15个
s.resize(16,'x');
cout << s.size() << endl;//16
cout << s.capacity() << endl;//31,超过15,扩容
cout << s << endl;
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;//5
cout << s.capacity() << endl;//15
cout << s << endl;
}
int main()
{
Test_string1();
return 0;
}
值得说一下的是,size()和length()两个接口底层是完全一样的,length是由于发展历史缘故导致出现的,无法避免。功能自然也是一样,这里推荐使用size(),方便后期的接口一致。
2.扩容问题
此外,我们也可以来看看是怎么扩容的(在vs2019下):
在g++下:
但是,扩容也会有开销,如果我们提前知道要开多少,就可以使用reserve():
void TestPushBackReserve()
{
string s;
s.reserve(1000);
size_t sz = s.capacity();
cout << "capacity changed : " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
int main()
{
TestPushBackReserve();
return 0;
}
注意:
size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
==clear()只是将string中有效字符清空,不改变底层空间大小==
九、Modifiers修改操作
对于这些接口可以查看文档:
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
void Teststring()
{
string str("hello");
str.push_back('X'); // 在str后插入X
str.append("world"); // 在str后追加一个字符"world"
str += 'C'; // 在str后追加一个字符'C'
str += "PP"; // 在str后追加一个字符串"PP"
cout << str << endl;
cout << str.c_str() << endl; // 以C语言的方式打印字符串
}
int main()
{
Teststring();
return 0;
}
void test1()
{
string s1("hello world");
//insert
s1.insert(0, "hwc ");
cout << s1 << endl;
s1.insert(9, "hwc");
cout << s1 << endl;
//erasse
s1.erase(9, 3);
cout << s1 << endl;
s1.erase(0, 4);
cout << s1 << endl;
s1.erase(5,30);
cout << s1 << endl;
}
void test2()
{
string s1("hello world hello world");
string s2("hello world hello world");
string s3("we are family");
string s4(s3);
//assign
s1.assign("hehehe",5);
cout << s1 << endl;
//replace
s2.replace(6,5,"hwc");
cout << s2 << endl;
//find与replace的结合使用
size_t pos = s3.find(' ');
while (pos != string::npos)
{
s3.replace(pos, 1, "%20");
pos = s3.find(' ', pos + 3);
}
cout << s3 << endl;
//+=的使用
string ret;
for (auto ch : s4)
{
if (ch != ' ')
{
ret += ch;
}
else
{
ret += "%20";
}
}
cout << ret << endl;
}
//c_str
void test3()
{
string file("test.cpp");
FILE* fout = fopen(file.c_str(), "r");
assert(fout);
char ch = fgetc(fout);
while (ch != EOF)
{
cout << ch;
ch = fgetc(fout);
}
fclose(fout);
}
//取文件后缀
//rfind()和substr
void test4()
{
string file;
cin >> file;
size_t pos = file.rfind('.');
if (pos != string::npos)
{
string sub = file.substr(pos);
cout << sub << endl;
}
}
十、非成员函数重载
对于流插入和流提取都是以空格、换行作为结束标志的(scanf也是这个样子的)。
为了解决这个问题,我们可以采用getline,
遇到\n才会结束。
这个有什么用呢。比如计算 字符串最后一个单词的长度
#include <iostream>
using namespace std;
#include <string>
int main() {
string str;
getline(cin,str);
size_t pos = str.rfind(' ');
cout<<str.size()-pos-1<<endl;
}
十一、总结
对于string类的常用接口使用我们就先说到这里,另外,值得在这里强调对于一些接口的使用我们可以去查文档进行理解使用。
- 点赞
- 收藏
- 关注作者
评论(0)