目录
前言:
STL是C++标准库的重要组成部分;STL有六大组件,其中有一大组件叫做容器,我们今天要介绍的string就是容器之一
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
3.string在底层实际是: basic_string模板类的别名
4.不能操作多字节或者变长字符的序列
string文档:string接口文档
string常用接口
值得一提的是,string比STL出现的要早一些,因此string的很多接口设计时没法参考标准,这就使得string的很多接口设计地有些冗余,我们只需要掌握最常用的一些就可以了~
一、string的创建,拼接与拷贝构造
1.创建
#include<iostream>
using namespace std;
int main()
{
//字符串创建
string s1; //没有显示初始化默认是空字符串
string s2("hello world"); //带参数创建
string s3 = "hello world"; //单参数的构造函数支持隐式类型转化
}
2.拼接
#include<iostream>
using namespace std;
int main()
{
//字符串拼接
//string + string
string s1 = "hello ";
string s2 = "world";
string s3 = s1 + s2;
cout << s3 << endl; // hello world
//自己和自己拼接
string s6 = "hello ";
string s7 = s6 + s6;
cout << s7 << endl; //hello hello
//string + "常量字符串"
string s4 = "hello ";
string s5 = s4 + "world";
cout << s5 << endl; // "hello world"
}
3.拷贝构造
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
//完全拷贝
string s2(s1); //s1对象完全拷贝给s2
cout << s2 << endl; //hello world;
//部分拷贝
string s3(s1, 1, 3); //从s1下标为1的位置开始取3个字符拷贝给s3
cout << s3 << endl; //ell
string s4(s1, 1, 15); //15>字符串长度, 从1开始取整个字符串
cout << s4 << endl; //ello world
string s5(s1, 1); //第三个缺省参数默认是-1(size_t),因此取完整个字符串
cout << s5 << endl; //ello world;
//迭代器区间拷贝构造初始化
string s6(s1.begin(), s1.end()); //s1.begin()获取的是字符串首位置,s1.end()获取的是字符串最后一个有效字符下一个位置('\0'的位置)
cout << s6 << endl; //hello world
string s7(++s1.begin(), --s1.end());
cout << s7 << endl; // ello worl
}
二、string遍历
方式一:operator[ ]重载
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
//读
for (size_t i = 0;i < s1.size();i++) //size()是用来获取字符串中有效字符个数(不含'\0')
{
cout << s1[i] << " ";
}
cout << endl;
//写
for (size_t i = 0;i < s1.size();i++) //size()是用来获取字符串中有效字符个数(不含'\0')
{
s1[i]++;
cout << s1[i] << " ";
}
cout << endl;
}
方式二:迭代器
迭代器提供了一种遍历容器中元素的方法,我们无需关心容器内部的实现细节
1.正向迭代器:
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
//iterator是迭代器的意思,封装在string内部
string::iterator it = s1.begin(); //s1.begin()返回的是字符串起始位置地址
//it是迭代器类型,但是我们可以类似认为是是指针变量
while (it != s1.end())
{
cout << *it << " "; //类似于指针解引用
it++; //类似于指针向后移动
}
cout << endl;
}
2.反向迭代器
除了正向迭代器之外,还有反向迭代器,借助反向迭代器,可以实现逆序遍历
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
}
3.const正向迭代器
若string对象被const修饰了,此时要正向遍历必须用const正向迭代器,否则权限放大会报错
#include<iostream>
using namespace std;
void func(const string& s)
{
string::const_iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
int main()
{
string s1 = "hello world";
func(s1);
}
4.const反向迭代器
#include<iostream>
using namespace std;
void func(const string& s)
{
string::const_reverse_iterator rit = s.rbegin();
//上一行代码太冗余了,此时auto的价值就体现出来了
//auto rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
}
int main()
{
string s1 = "hello world";
func(s1);
}
方式三:范围for
范围for的底层其实是借助迭代器实现的~
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
}
方式四:at()方法
at方法和operator[ ]无本质区别,且operator[ ]更加常用, at()方法很少用到
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
for (size_t i = 0;i < s1.size();i++)
{
cout << s1.at(i) << " ";
}
cout << endl;
}
三、容量与字符个数相关
1. size()
size()用来计算字符串中有效字符的个数,不包含'\0'
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
cout << s1.size() << endl; //11
//ps: length也是求字符串长度的,可以和size()互换,但几乎不用
cout << s1.length() << endl; //11
}
2. capacity()
capacity() 用来计算当前字符串的容量,最多能够存放的有效字符的个数
同样的一个字符串, 不同编译器下给定的capacity的大小可能会不太一样,没有规律~
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
cout << s1.capacity() << endl; //15
string s2 = "come up";
cout << s2.capacity() << endl; //15
string s3 = "weclome to beijing";
cout << s3.capacity() << endl; //31
}
3. resize()
resize()是用来调整容器大小的,n制定了
①n < size
相当于尾删元素,size减小到n,capacity不变
#include<iostream>
using namespace std;
int main()
{
//n < size
string s1 = "hello world";
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //15
s1.resize(5);
cout << s1<< endl; //hello
cout << s1.size() << endl; //5
cout << s1.capacity() << endl; //15
}
②n >= size && n <= capacity
相当于尾插元素,size增加到n, capacity不变, resize()给定了第二个字符参数,就插入该字符,否则默认插入'\0'
#include<iostream>
using namespace std;
int main()
{
//n >= size && n <capacity
//resize不传字符参数
string s1 = "hello world";
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //15
s1.resize(13); //插入两个'\0'
cout << s1 << endl; //hello world
cout << s1.size() << endl; //13
cout << s1.capacity() << endl; //15
//resize()传字符参数
string s1 = "hello world";
s1.resize(13, 'x');
cout << s1 << endl; //hello worldxx
}
③n > capacity
相当于插入元素,size增加到n, capacity增大(编译器决定增大到多少)
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
cout << s1.capacity() << endl; //15
s1.resize(20, 'x');
cout << s1 << endl; //hello worldxxxxxxxxx
cout << s1.capacity() << endl; //31
}
4.reserve()
reserve是用来提前申请空间的,有些情况下使用reserve可以很好地避免频繁扩容
系统实际分配的空间都是略大于我们指定的n个字符的~
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
cout << s1.capacity() << endl; //15
s1.reserve(20);
cout << s1.capacity() << endl; //31
}
ps:我们来观察一下VS2022环境下string扩容的规律
#include<iostream>
using namespace std;
int main()
{
string s;
s.reserve(20);
size_t old = s.capacity();
for (size_t i = 0;i < 1000;i++)
{
s.push_back(i);
if (old != s.capacity())
{
cout << "扩容到:" << s.capacity() << endl;
old = s.capacity();
}
}
}
5.empty()
empty()用于判断字符串是否为空,为空返回1,不为空返回0
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
string s2 = "";
cout << s1.empty() << endl; //0
cout << s2.empty() << endl; //1
}
6.clear()
clear用于清空字符串,使得字符串中有效字符的个数为0,因此size为0,capacity不变
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //15
s1.clear();
cout << s1.size() << endl; //0
cout << s1.capacity() << endl; //15
}
7.shrink_to_fit()
shrink_to_fit()是用来向系统请求把容量capacity减小到和有效字符个数size一样大
ps:注意,这只是一个请求,实际情况下即使用了该方法,capacity也不一定改变
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //15
s1.shrink_to_fit();
cout << s1.size() << endl; //11
cout << s1.capacity() << endl; //15
}
clear()只能清空字符串,不会改变容量,这时再用shrink_to_fit就可以完美地将capacity置成0
四、增删查改
1. push_back()
push_back是用于尾插单个字符的~
#include<iostream>
using namespace std;
int main()
{
string s;
s.push_back('h');
s.push_back('e');
s.push_back('l');
cout << s << endl; //hel
}
2. append()
append是追加的意思,也就是尾插
①追加整个字符串
#include<iostream>
using namespace std;
int main()
{
//尾插整个字符串
string s1 = "hello ";
s1.append("w");
cout << s1 << endl; //hello w
string s2 = "hello ";
s2.append("world");
cout << s2 << endl; // hello world
string s3 = "hello ";
string s4 = "world";
s3.append(s4);
cout << s3 << endl; //hello world
}
②追加字符串的一部分
#include<iostream>
using namespace std;
int main()
{
//尾插字符串一部分
string s1 = "hello ";
string s2 = "world";
s1.append(s2, 0, 2); //尾插s2从下标0开始的2个字符
cout << s1 << endl; //hello wo
string s3 = "hello ";
s3.append(2, 'w'); //追加2个'w'字符在s3后面
cout << s3 << endl; //hello ww
}
3. +=
+=本质也是一种追加字符串的方式,尾插字符串或者字符到string对象末尾
#include<iostream>
using namespace std;
int main()
{
//+=字符
string s1 = "hello ";
s1 += 'w';
cout << s1 << endl; //hello w
//+=字符串
string s2 = "hello ";
s2 += "world";
cout << s2 << endl; //hello world
string s3 = "hello ";
string s4 = "world";
s3 += s4;
cout << s3 << endl; //hello world
}
4. assign()
assign本质就是赋值
#include<iostream>
using namespace std;
int main()
{
//赋值 string对象
string s1 = "hello";
string s2 = "world";
s1.assign(s2); //将s2赋值给s1
cout << s1 << endl; //world
//赋值 常量字符串
string s3 = "hello";
s3.assign("world"); //将"world"赋值给s3
cout << s3 << endl; //world
//赋值 常量字符串的前n个字符
string s4 = "hello";
s4.assign("world", 2); //将"world"的前2个字符赋值给s4
cout << s4 << endl; //wo
//赋值 n个字符
string s5 = "hello ";
s5.assign(2, 'w'); //赋值2个'w'字符给s5
cout << s5 << endl; //ww
//赋值 string的子串
string s6 = "hello";
string s7 = "handsome boy";
s6.assign(s7, 3, 5); //赋值s7从下标为3的位置开始的5个字符给s6
cout << s6 << endl; //dsome
string s8 = "hello";
string s9 = "world";
s8.assign(s9, 2); //赋值s9从下标为2开始的子串赋值给s8
cout << s8 << endl; //rld
}
5. insert()
insert()是在指定位置插入字符或者字符串
①插入单个字符
#include<iostream>
using namespace std;
int main()
{
//插入单个字符
string s1 = "hello world";
s1.insert(2, 3, '*'); //下标为0的位置插入2个'*'
cout << s1 << endl; //he*** llo world
string s2 = "hello world";
s2.insert(s2.begin(), 'w'); //开始位置插入字符'w'
cout << s2 << endl; //whello world
string s3 = "hello world";
s3.insert(s3.begin(), 2, 'w'); //开始位置插入2个字符'w'
cout << s3 << endl; //wwhello world
}
②插入字符串
#include<iostream>
using namespace std;
int main()
{
//插入字符串
//插入整个字符串
string s1 = "hello world";
s1.insert(2, "come"); //下标为2位置开始插入字符串"come"
cout << s1 << endl; //hecomello world
//插入字符串的一部分
string s2 = "hello world";
string s3 = "Never give up";
s2.insert(2, s3, 2, 6); //取s3字符串下标从2开始的6个字符插入s2下标为2的位置
cout << s2 << endl; //hever gillo world
string s4 = "hello world";
s4.insert(2, "world", 2); //取world下标为0(默认)开始的2个字符插入到s4中下标为2的位置
cout << s4 << endl; //hewollo world
}
6. erase()
erase()是用于删除指定位置起的一个或若干个字符
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
s1.erase(2, 5); //删除s1下标从2开始的5个字符
cout << s1 << endl; //heorld
string s2 = "hello world";
s2.erase(7); //删除s1下标从7开始的剩余字符
cout << s2 << endl; //hello w
string s3 = "hello world";
s3.erase(); //删除整个字符串
cout << s3 << endl; //空
}
7.replace()
replace是用新的字符或者字符串替换原字符串部分字符
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
s1.replace(2, 5, "xxxxxxxxxxxx"); //将s1从下标2开始的5个字符替换成"xxxxxxxxxxxx"
cout << s1 << endl; //hexxxxxxxxxxxxorld
string s2 = "hello world";
s2.replace(++s2.begin(), --s2.end(), "xxxxxx"); //将s1第2个字符的位置到倒数第二个字符位置的子串替换成"xxxxxx"
cout << s2 << endl; //hxxxxxxd
string s3 = "hello world";
s3.replace(2, 5, "xxx***&&&", 3, 4); //将s3下标从2开始的5个字符替换成"xxx***&&&"下标从3开始的4个字符
cout << s3 << endl; //he***&orld
string s4 = "hello world";
s4.replace(2, 5, 3, '*'); //将s4下标从2开始的5个字符替换成3个'*'
cout << s4 << endl; //he***orld
string s5 = "hello world";
s5.replace(2, 5, "&*@!", 4); //将s5下标从2开始的5个字符替换成"&*@!"的前两个字符
cout << s5 << endl; //he&*orld
}
8.查找系列
①find()
#include<iostream>
using namespace std;
int main()
{
//find会返回找到的字符串的首字符或者字符的下标
string s1 = "hello world";
size_t pos1 = s1.find("llo", 1); //从s1下标为2开始查找字符串"llo"
cout << pos1 << endl; //2
size_t pos2 = s1.find("llx", 1);
cout << pos2 << endl; //找不到返回npos(size_t类型,值为-1,是一个很大的整数)
string s2 = "hello world";
size_t pos3 = s1.find('l'); //默认从s2的第一个位置开始找'l'字符首次出现的位置
cout << pos3 << endl; //2
}
②rfind()
find()是从左到右查找,而rfind()是从右到左查找
#include<iostream>
using namespace std;
int main()
{
//find会返回找到的字符串的首字符或者字符的下标
string s1 = "hello world";
size_t pos1 = s1.rfind("wor", -3); //从s1倒数第3个位置开始查找"wor"
cout << pos1 << endl; //6
string s2 = "hello world";
size_t pos3 = s1.rfind('l'); //默认从s2的倒数第一个位置开始从右向左找'l'字符首次出现的位置
cout << pos3 << endl; //9
}
③find_first_of()
#include<iostream>
using namespace std;
int main()
{
//find_first_of用于查找指定字符串的所有字符在原字符串中最先出现的字符的位置
string s1 = "hello world";
string s2 = "aeiou";
size_t pos = s1.find_first_of(s2); //查找s2中的所有字符中最先在s1中出现的字符的位置
cout << pos << endl; //1
//find_first_of有何意义?
//假如说想要把s3中的所有元音字母替换成'*'
string s3 = "hello world";
size_t found = s3.find_first_of("aeiou");
while (found != string::npos) //查找完find_first_of会返回npos
{
s3[found] = '*';
found = s3.find_first_of("aeiou", found + 1); //从下标为found+1的位置继续寻找
}
cout << s3 << endl; //h*ll* w*rld
}
④find_last_of()
与find_first_of功能是一样的,不过find_first_of从左往右找,find_last_of从右往左找
⑤find_first_not_of()
从左向右找的不在指定字符串中的字符
#include<iostream>
using namespace std;
int main()
{
//find_first_not_of用于查找不在指定字符串的所有字符在原字符串中最先出现的字符的位置
string s1 = "hello world";
string s2 = "aeiou";
size_t pos = s1.find_first_not_of(s2); //查找不在s2中的所有字符中最先在s1中出现的字符的位置
cout << pos << endl; //0
string s3 = "hello world";
size_t found = s3.find_first_not_of("aeiou");
while (found != string::npos)
{
s3[found] = '*';
found = s3.find_first_not_of("aeiou", found + 1); //从下标为found+1的位置继续寻找
}
cout << s3 << endl; //*e**o**o***
}
⑥find_last_not_of()
从右往左找不在指定字符串中的字符
⑦substr()
substr()用于获取子串,返回获取到的子串
#include<iostream>
using namespace std;
int main()
{
string s1 = "hello world";
string s2 = s1.substr(1, 3); //获取从下标为1开始的3个字符作为原字符串子串返回
cout << s2 << endl; //ell
string s3 = s1.substr(1); //s1从下标为0开始取完赋值给s3
cout << s3 << endl; //ello world
string s4 = s1.substr(); //取完整个s1
cout << s4 << endl; //hello world
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
查找系列综合题目:获取网址的三部分
ps:网址有三部分构成:协议,域名,资源名; ( : 和 / 将网址划分为了三部分)
#include<iostream>
using namespace std;
int main()
{
string s1 = "https://legacy.cplusplus.com/reference/string/string/rfind/";
string sub1, sub2, sub3;
size_t i1 = s1.find(':');
if (i1 != string::npos)
sub1 = s1.substr(0, i1);
size_t i2 = s1.find('/', i1 + 3);
if (i2 != string::npos)
sub2 = s1.substr(i1 + 3, i2 - (i1 + 3));
sub3 = s1.substr(i2 + 1);
cout << sub1 << endl; //https
cout << sub2 << endl; //legacy.cplusplus.com
cout << sub3 << endl; //reference/string/string/rfind/
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
五、输入( getline() )
C++中关于输入我们最常用的是cin, 但是cin有个缺陷,就是无法读取到空格和换行,因此cin输入默认输入的内容是以字符或者换行分割的
因此有些场景下必须用getline()进行输入,getline()可以直接读取一整行
①不指定分隔符,默认以回车结束,因此可以直接读取一行
#include<iostream>
using namespace std;
#include<string>
int main()
{
//getline读取一整行
string s1;
getline(cin, s1);
cout << s1 << endl;
}
②若想要读入回车,则需要手动指定分割符为'\r',要使输入结束: ctrl + z
#include<iostream>
using namespace std;
#include<string>
int main()
{
//如何让getline读取回车呢?
//std::istream& getline(std::istream & is, std::string & str, char delim);
string s1;
getline(cin, s1, '\r'); //第三个参数指定输入内容之间的分隔符
cout << s1 << endl;
}
本篇关于string接口的设计讲解的是非常详细的,这将为下面要学习的string的模拟实现打下坚实的基础~