[TOC]
一.string初识
1.STL简介
a.STL的组成
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架。
网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构 以及算法都不需要自己重新造*,站在前人的肩膀上,健步如飞的快速开发。
b.STL和string的关系
推荐一个学习C++的一个文档网站:C++文档说明,看文档也是一种工作必备能力哦
历史上,string出现的比STL出现的早,但是因为功能上string和STL中的容器很像,所以把string纳入到STL中。
2.basic_string
C++plusplus以后我用的时候就称为C++文档了,希望大家理解,C++文档中
所以原理上,下面两种方式都是定义一个字符串:
string str1;
basic_string<char> str2;
那么是否还可以通过其他的类型来完成类模板的实例化?可以!
上述有多种模板实例化的模板类,那和字符集编码有关。我们之所以用的最多的是string那是因为我们通常使用到的是utf-8字符集编码,所以我们一般使用basic_string< char >存储utf-8字符组成的字符串; 如果我们使用的是utf-16字符集编码,我们继续使用basic_string< char >存储utf-16组成的字符串就会出现乱码,所以我们应该使用basic_string< char16_t >
二.构造函数
构造函数的这几种构造方式要非常熟悉,因为string类的其他接口也有用到类似形式的构造参数,我称之为参数可变(个人叫法)
构造函数
|
说明
|
string()
|
重要,无参构造
|
string (const char* s)
|
重要,常量字符串构造
|
string (const string& str)
|
重要,拷贝构造
|
string (size_t n, char c)
|
了解,n个c字符构造
|
string (const char* s, size_t n)
|
了解,s字符串前n个构造
|
string (const string& str, size_t pos, size_t len = npos)
|
了解,str对象的pos下标,跨度len的构造
|
string (InputIterator first, InputIterator last)
|
了解,迭代器相关
|
ps:string类的接口由100多个,但是我们学习的目标是:熟练掌握常用的20余个,其他的要用的时候翻阅文档查看学习使用即可.
void test1()
{
string str1;//空字符串
string str2("hello");//"hello"
string str3(str2);//"hello"
string str4(5, 'x');//"xxxxx"
string str5("XXYYZ", 4);//"XXYY"
string str6(str5, 0, 2);//"XX"
}
三.三种遍历方式
1.下标 + [ ]
2.范围for:实际上auto是模仿其他语言的,底层实现也是迭代器
3.迭代器:迭代器可能可能是指针,也有可能不是指针,但是它使用起来很像指针。(iterator具体实现得看后面了,这里会用就行,不必深究)
void test2()
{
string str("123456");
//1. 下标+[]
for (size_t i = 0; i < str.size(); ++i)
{
str[i]++;
cout << str[i] << " ";
}
cout << endl;
//2. 范围for
for (auto& e : str)
{
e--;
cout << e << " ";
}
cout << endl;
//3. 迭代器
string::iterator it = str.begin();
while (it != str.end())
{
(*it)++;
cout << *it << " ";
it++;
}
cout << endl;
}
既然第一种下标加方括号的形式既支持可读又可写,为什么我们还要学习迭代器呐?
那是因为迭代器是一种通用的访问形式,在string和vector中由于底层实现都是一种顺序表,而顺序表是支持下标加方括号的形式随机访问的,但是如果我没学到后面的list等,下标加方括号显然就不适用了,这个时候叠加器的优点才真正显示出来。
那么接下来我将给大家介绍一下几种迭代器:
- 正向迭代器和反向迭代器
- const迭代器
上面的迭代器都可以两两组合,比如正向非const迭代器,正向const迭代器等
这里的string::const_iterator是不是使用起来不是那么方便,那么我们可以选择使用auto自动识别类型。
上面讲一下反向const迭代器这种类型名最长的组合形式:
到了这里我们也了解到了什么是const的迭代器,什么是非const迭代器,那么为什么迭代器要s设计出const版本和非const版本呢?这是和它的功能有关,他必须要支持读和写的功能。
operate[]运算符重载函数其实设计出了const和非const两种版本
size()函数功能上只需要具备读,所以也就只设计出const版本
push_back()函数功能上只需要具备写,所以也就只设计出非const版本
四.容量相关的函数
函数
|
说明
|
size()
|
返回字符串有效字符长度
|
capacity()
|
返回空间总大小
|
clear()
|
清空有效字符
|
reserve(n)
|
仅改变capacity(),常用于扩容
|
resize(n)
|
改变size(),可能改变capacity(), 将有效字符的个数该成n个,多出的空间用字符c填充
|
empty()
|
检测字符串释放为空串,是返回true,否则返回false
|
1.size()
或许你学了许久C++都不知道为什么C++既有size()又有length(),它们的结果明明是一样的。
原因:string设计早于STL,STL有自己的一套,也就包含size()
string有它的一套,也就是length(),作为STL的设计者把string加入到STL中的时候,为了向前兼容,就保留了原来的一套。
那么我们究竟在使用的时候推荐使用size()还是length()呐?用之疑,肯定是优先选择size(),因为他能和后面的其他容器保持一定的统一性,字符串你能使用length(),但是对于二叉树这种结构能使用length()吗?显然不可以。
另外通过上面的图的运行结果,我们也可以看出size()和length(),计算出的结果都是有效字符个数,是不包含斜杠铃的,因为‘\0’是字符串结尾的标记字符
ps:vs下,capacity()计算的容量结果也没有包含‘\0’
2.reserve()–调整容量
void test6()
{
string str;
size_t sz = str.capacity();
cout << "InitCapacity:"<< sz << endl;
for (int i = 0; i < 100; i++)
{
str.push_back('c');
if (sz != str.capacity())
{
sz = str.capacity();
cout << "capacity changed:" << sz << endl;
}
}
}
首先我们回顾一下:vs下,capacity()计算的容量结果是不包含‘\0’的(包含还是不包含和具体实现有关),那我们可以到Linux下试一试:
对比发现,vs下扩容倍数大概是1.5倍
linux的g++下,扩容倍数大概是2倍(这点上还是linux友好点)
那我为什么要和大家讲上面的这些东西呐?那是想和大家说明vs和Linux下的g++下,扩容是有代价的,所以如果我们在已知大概容量的情况下可以使用reserve() 提前开好适当大小的空间,从而减少扩容。
void test6()
{
string str;
str.reserve(100);
size_t sz = str.capacity();
cout << "InitCapacity:"<< sz << endl;
for (int i = 0; i < 100; i++)
{
str.push_back('c');
if (sz != str.capacity())
{
sz = str.capacity();
cout << "capacity changed:" << sz << endl;
}
}
}
可以看到扩容次数明显减少了!!!!
但是在vs下有一个奇怪的现象,知道就好,不必深究:
3.resize()–调整size
作用:将有效字符的个数该成n个,多出的空间用字符c填充
void test8()
{
string str("hello world");
//str.size()==11 str.capacity()==15
str.resize(5);
cout << "情况1: " << endl;
cout << str.size() << endl;
cout << str.capacity() << endl;
cout << str << endl << endl;
str.resize(13,'a');
cout << "情况2: " << endl;
cout << str.size() << endl;
cout << str.capacity() << endl;
cout << str << endl << endl;
str.resize(20,'b');
cout << "情况3: " << endl;
cout << str.size() << endl;
cout << str.capacity() << endl;
cout << str << endl << endl;
}
五.字符串的增删查改
函数
|
说明
|
push_back()
|
头插
|
pop_back()
|
头删
|
insert()
|
在pos位置插入,插入部分的种类类似构造
|
erase()
|
在pos位置删除
|
opearator+=()
|
重要,在字符串后追加字符串str
|
assign()
|
清空后重新赋新值,赋值部分的种类类似构造
|
replace()
|
替换
|
find()
|
查找子串,返回下标
|
substr()
|
返回子串
|
1.assign
功能和赋值类似
string& assign (const string& str);
参数可变
演示:
void test2()
{
string str1 = "hello world";
string str2 = "bit";
str1.assign(str2);//"bit"
str1.assign("C++");//"C++"
str1.assign("C++", 0, 1);//"C"
str1.assign(str2, 1, 1);//"i"
str1.assign(3, 'x');//"xxx"
cout << str1 << endl;
}
2.replace
功能:把从pos位置开始,替换跨度为span的字符串
,替换为str2(可变)
string& replace (size_t pos, size_t len, const string& str);
第三个参数可变
演示:
void test3()
{
string str1 = "hello world";
str1.replace(1, 3, "bit");//"hbito world"
cout << str1 << endl;
string str2 = "song";
str2.replace(0, 1, "yong", 0, 3);//"yonong"
cout << str2 << endl;
string str3 = "libai";
str3.replace(1, 2, 5, 'x');//"lxxxxxai"
cout << str3 << endl;
}
3.find()
功能:从pos位置开始查找子串(字符串或字符),找到返回下标,没找到返回npos
size_t find (const string& str, size_t pos = 0) const;
ps:pos参数缺省,默认从0开始找(默认是全局查找)
说明缺省值:npos
static const size_t npos = -1;
表面npos是-1,但是因为类型是size_t,无符号整数类型,所以实际上是非常大的数
void test5()
{
string str1 = "hello world C++";
size_t pos = str1.find(' ',11);//11,ps:下标为11是在d的后一个位置' ',也就是我们要找的‘ '的位置
pos = str1.find("ll");//2
pos = str1.find("world", 3);//6
cout << pos << endl;
}
4.substr()
功能:返回从pos位置,跨度为span的字符串
string substr (size_t pos = 0, size_t len = npos) const;
ps:substr()函数是全缺省的,默认从0开始找(默认是全局查找),跨度为npos
void test4()
{
string str1 = "hello world";
string str2=str1.substr();//"hello world"
str2 = str1.substr(1);//"ello world"
str2 = str1.substr(1, 1);//"e"
cout << str2 << endl;
}
5.insert()
功能:从pos位置开始,插入字符串或字符
string& insert (size_t pos, const string& str);
第二个参数可变
演示:
void test6()
{
string str1 = "hello world";
str1.insert(0, "song");//相当于头插 --"songhello world"
str1.insert(str1.size(), "libai");//'\0'的位置就是str1.size(),他的下标看他前面有多少个字符--"songhello worldlibai"
string str2 = "C++";
str1.insert(1, str2);//"sC++onghello world"
cout << str1 << endl;
}
由数据结构我们感知到这insert类头插因为大量挪动数据,效率肯定不高~
6.相关应用
a.替换空格:
方法1:
void test7()
{
string str1 = "hello world C++ ";
size_t pos = str1.find(' ');
while(pos != string::npos)
{
str1.replace(pos, 1, "%20");
pos = str1.find(' ', pos + 3);
}
cout << str1 << endl;
}
方法2:以空间换时间
void test8()
{
string str1 = "hello world C++ ";
string ans;
ans.reserve(str1.size());
for (int i = 0; i < str1.size(); i++)
{
if (str1[i] != ' ')
ans += str1[i];
else
ans += "%20";
}
cout << ans << endl;
}
变式:如果我要把“abcdeffcadgascg”这字符串中的‘a’和‘c’字符都换成“*”,我再使用find就搞不定了,我得使用名字起的很怪的函数接口find_first_of()
作用:在str1中查找出在str2字符串中出现的任意一个字符,返回下标
void test12()
{
string str1 = "abcdefccdefa";
size_t pos = str1.find_first_of("ac", 0);
while (pos != string::npos)
{
str1.replace(pos, 1, "*");
pos = str1.find_first_of("ac", pos + 1);
}
cout << str1 << endl;
}
取出文件后缀:
void test10()
{
//要求取出后缀
string str1 = "test.cpp";
size_t pos = str1.find('.');
if (pos != string::npos)//if找到了
{
string ans = str1.substr(pos);
cout << ans << endl;
}
}
但是如果像Linux中的一些文件后缀:.zip.tgz
这时候find()就搞不定了,我们得用rfind,倒着找,才能找到.tgz
void test11()
{
string str1 = "test.zip.tgz";
size_t pos = str1.rfind('.');
if (pos != string::npos)//if找到了
{
string ans = str1.substr(pos);
cout << ans << endl;
}
}
六.字符串操作函数
1.c_str
功能:返回string类成员变量中_str的地址
const char* c_str() const;
一般在C和C++结合使用的时候能使用到,比如用C语言打开文件
void test9()
{
string file("test.cpp");
FILE* fp = fopen(file.c_str(), "r");
char ch = fgetc(fp);
while (ch != EOF)
{
cout << ch;
ch = fgetc(fp);
}
fclose(fp);
}
2.getline()
功能:cin会读取到键盘输入的数据,当读到空格或者换行的时候,会停止读,所以当我们要键盘输入的字符串是带有空格的时候,不能再使用cin,而是采用getline,这在oj题中经常出现!
istream& getline (istream& is, string& str)
oj题目作为实例:
题目链接:https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
//cin>>str;
getline(cin,str);
size_t pos=str.rfind(' ');
cout<<str.size()-(pos+1)<<endl;
return 0;
}
另外一个注意的点就是:左闭右开才是个数!
相减得到之间个数,左边的值计算在个数内,右边的值不计算在个数内