目录
1.什么是string类?
2.string类对象的常见构造
2.1 string();
2.2 string (const char* s);
2.3 string (const string& str);
2.4 string (const string& str, size_t pos, size_t len = npos);
2.5 string (const char* s, size_t n);
2.7 验证:
3.string类对象的遍历及访问
3.1 下标+ [] 访问
3.2 iterator迭代器访问
3.3 at访问
4. string类对象的容量操作
4.1 size与length
4.2 capacity
4.3 reserve
4.4 resize
4.5 clear
4.6 empty
5.string类对象的修改操作
5.1 增
1. append
2. +=
3.insert
5.2 删
1. pop_back
2. erase
5.3 查
1.substr
2.find
5.4 改
1. replace
2. swap
5.5 查改结合完成替换操作
1.什么是string类?
在官网中,string类有这样的介绍:
Strings are objects that represent sequences of characters.
即:string类表示的对象是字符串类。
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
4. 不能操作多字节或者变长字符的序列。
(在使用string类时,必须包含#include头文件以及using namespace std;)
2.string类对象的常见构造
2.1 string();
在官网中,对string();的解释为:
Constructs an empty string, with a length of zero characters.
即:构造一个空字符串,长度为零字符。
int main()
{
//string();定义一个空的string类
string s1;
return 0;
}
2.2 string (const char* s);
在官网中,对于string (const char* s);的解释为:
Copies the null-terminated character sequence (C-string) pointed by s.
即:复制s指向的以空字符结尾的字符串。
int main()
{
//string (const char* s);复制s指向的以空字符结尾的字符串
string s2("hello world!");
return 0;
}
2.3 string (const string& str);
在官网中,对string (const string& str);的解释为:
Constructs a copy of str.
即可以理解为拷贝构造。
int main()
{
//拷贝构造s2
string s3(s2);
string s4 = s2;
return 0;
}
2.4 string (const string& str, size_t pos, size_t len = npos);
在官网中,对string (const string& str, size_t pos, size_t len = npos);的解释为:
Copies the portion of str that begins at the character position pos and spans len characters.
即:拷贝str从pos位置到len个字符的一部分。
int main()
{
//string (const string& str, size_t pos, size_t len = npos);
//拷贝str从pos位置到len个字符的一部分。
string s5(s2, 1, 7);
return 0;
}
可在官网里括号里还有一句话: (or until the end of str, if either str is too short or if len is string::npos).
这句话可以理解为:如果str太短或者len太长,那就只拷贝到str的结尾。
2.5 string (const char* s, size_t n);
在官网中,对string (const char* s, size_t n);的解释为:
Copies the first n characters from the array of characters pointed by s.
即:拷贝s指向的字符数组的前n个字符。
int main()
{
//string (const char* s, size_t n);
//拷贝s指向的字符数组的前n个字符
string s6("hello world!", 5);
return 0;
}
2.6 string (size_t n, char c);
在官网中,对string (size_t n, char c);的解释为:
Fills the string with n consecutive copies of character c.
即:用n个C语言字符填充。
int main()
{
//string (size_t n, char c);
//用n个C语言字符填充
string s7(10, 'l');
}
2.7 验证:
<<与>>已实现重载,所以可以直接用。
故验证代码如下:
#include<iostream>
#include<string>
using namespace std;
int main()
{
//string();定义一个空的string类
string s1;
//string (const char* s);复制s指向的以空字符结尾的字符串
string s2("hello world!");
//string (const string& str);拷贝构造s2
string s3(s2);
string s3_1 = s2;
//string (const string& str, size_t pos, size_t len = npos);
//拷贝str从pos位置到len个字符的一部分。
string s4(s2, 1, 7);//拷贝s2从下标1位置开始到7个字符的一部分
//string (const char* s, size_t n);
//拷贝s指向的字符数组的前n个字符
string s5("hello world!", 5);//拷贝"hello world!"的前5个字符
//string (size_t n, char c);
//用n个C语言字符填充
string s6(10, 'l');//拷贝10个‘l’
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s3_1 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
return 0;
}
结果如图:
3.string类对象的遍历及访问
3.1 下标+ [] 访问
①计算string类的长度或大小
(size与length意义相同,建议用size)用法如下:
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
cout << s1.length() << endl;
return 0;
}
(由结果得知,size与length不包含'\0')
② 知道了string的长度,我们就可以这样遍历string:
int main()
{
string s1("hello world!");
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << endl;//重载了s1.operator[](i)
}
return 0;
}
③实现逆置string
方法一:手搓交换
int main()
{
string s1("hello world!");
cout << "逆置前s1="<< s1 << endl;
size_t begin = 0;//左边界
size_t end = s1.size() - 1;//右边界
while (begin < end)
{
char tmp = s1[begin];
s1[begin] = s1[end];
s1[end] = tmp;
++begin;
--end;
}
cout << "逆置后s1=" << s1 << endl;
return 0;
}
方法二:使用swap
int main()
{
string s1("hello world!");
cout << "逆置前s1="<< s1 << endl;
while (begin < end)
{
swap(s1[begin], s1[end]);
++begin;
--end;
}
cout << "逆置后s1=" << s1 << endl;
return 0;
}
3.2 iterator迭代器访问
int main()
{
string s1("hello world!");
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << endl;
++it;
}
return 0;
}
iterator的用法与指针类似:
二者的区别:
下标+[]只适用于部分容器,底层物理有一定连续,如链式结构、树形、哈希结构,就只能用迭代器。迭代器才是容器访问的主流形式。
3.3 at访问
at访问与下标+[]访问的功能相同,区别主要体现在越界访问时的报错形式:
如①下标+[]的越界访问:
int main()
{
string s1("hello world!");
cout << s1[20] << endl;
return 0;
}
②at的越界访问:
int main()
{
string s1("hello world!");
cout << s1.at(20) << endl;
return 0;
}
(即下标+[]是暴力地终止,at是温柔地终止)
4. string类对象的容量操作
4.1 size与length
size与length返回字符串有效字符长度
前面已经说过,size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
代码演示:
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
return 0;
}
4.2 capacity
capacity返回空间总大小
代码如下:
int main()
{
string s1("hello world!");
cout << s1.capacity() << endl;
return 0;
}
设计程序检测string的扩容机制:
int main()
{
string s1("hello world!");
//检测string的扩容机制
size_t old = s1.capacity(); //令old=原本的容量大小
cout << old << endl;
for (size_t i = 0; i < 500; i++)
{
s1.push_back('l'); //尾插l的过程中string的容量大小一定会发生变化
if (old != s1.capacity()) //当发生扩容时,打印新的string容量大小
{
cout << s1.capacity() << endl;
old = s1.capacity();
}
}
return 0;
}
4.3 reserve
reserve为字符串预留空间(即可以理解为需要多少空间提前开好,不用边用边开)
reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。(在vs2019下reserve只扩容不缩容,在g++下reserve会缩容,但只会缩到现有数据的大小)
用法如下:
int main()
{
string s1("hello world!");
cout << s1.capacity() << endl;
s1.reserve(500);
cout << s1.capacity() << endl;
return 0;
}
4.4 resize
resize将有效字符的个数改变成n个,多出的空间用字符c填充
resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用'\0'来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
①如果要扩容的空间>capacity,则扩容+尾插。验证如下:
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
//如果要扩容的空间>capacity
s1.resize(100);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
return 0;
}
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
//如果要扩容的空间>capacity
s1.resize(100, 'x');
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
return 0;
}
②如果size<n<capacity,则只尾插不扩容,验证如下:
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
//如果size<n<capacity
s1.resize(13,'x');
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
return 0;
}
③如果n<size,只删除、保留前n个,不缩容,验证如下:
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
//如果n<capacity
s1.resize(6);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
return 0;
}
(reserve与resize的区别可以概括为一句话:reserve只影响容量,不影响数据,resize既影响容量又影响数据)
4.5 clear
清空有效字符
clear()只是将string中有效字符清空,不改变底层空间大小。
验证如下:
int main()
{
string s1("hello world!");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
return 0;
}
4.6 empty
检测字符串释放为空串,是返回true,否则返回false(注意:空为真,不空为假)
验证如下:
int main()
{
string s1("hello world!");
cout << s1.empty() << endl;
string s2;
cout << s2.empty() << endl;
return 0;
}
5.string类对象的修改操作
修改操作无非就是增删查改,这里只介绍比较常用的操作
5.1 增
1. append
增操作其实已经有了一个我们熟悉的push_back,可是push_back每次只能尾插一个字符,为了方便就有了append:
比如我们二者兼用:
int main()
{
string s1("hello");
s1.push_back(' ');
s1.append("world!");
cout << s1 << endl;
return 0;
}
append的其他常用用法如下:
①string& append (const char* s);插入常量字符串(上面的用法)
②string& append (const string& str);插入string:
int main()
{
string s1("hello");
string s2(s1);
return 0;
}
③string& append (const string& str, size_t subpos, size_t sublen);插入string从subpos位置起到sublen个字符止的那部分:
int main()
{
string s1("hello world!");
string s2(s1,1,3);
return 0;
}
④string& append (const char* s, size_t n);插入常量字符串的前n个:
int main()
{
string s1("hello world!", 3);
return 0;
}
⑥string& append (size_t n, char c);插入n个字符c:
int main()
{
string s1(4,'x');
return 0;
}
⑦string& append (InputIterator first, InputIterator last);插入string的一部分:
插入s1:
int main()
{
string s1("hello world!");
string s2;
s2.append(s1.begin(), s1.end());
return 0;
}
插入去头去尾的s1:
int main()
{
string s1("hello world!");
string s2;
s2.append(++s1.begin(), --s1.end());
return 0;
}
2. +=
①string& operator+= (const string& str);插入string:
int main()
{
string s1("hello world!");
string s2;
s2 += s1;
return 0;
}
②string& operator+= (const char* s);插入字符串:
int main()
{
string s1;
s1 += "hello world!";
return 0;
}
③string& operator+= (char c);插入字符:
int main()
{
string s1;
s1 += '!';
return 0;
}
3.insert
观察push_back与append,二者都是尾插,那有没有不是尾插的方法呢?当然有!
①string& insert (size_t pos, const string& str);在pos位置插入string:
int main()
{
string s1("hello ");
string s2("world!");
s2.insert(0, s1);
return 0;
}
②string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);在pos位置插入string的一部分:
int main()
{
string s1("hello ");
string s2("world!");
s2.insert(0, s1, 1, 3);//ellworld!
return 0;
}
③string& insert (size_t pos, const char* s);在pos位置插入字符串:
int main()
{
string s1("world!");
s1.insert(0, "hello ");//hello world!
return 0;
}
④string& insert (size_t pos, const char* s, size_t n);在pos位置插入字符串的前n个:
int main()
{
string s1("world!");
s1.insert(0, "hello ",3);//helworld!
return 0;
}
5.2 删
1. pop_back
尾删:
int main()
{
string s1("hello world!");
s1.pop_back();//hello world
return 0;
}
2. erase
①string& erase (size_t pos = 0, size_t len = npos);从第pos个位置删除len个字符:
int main()
{
string s1("hello world!");
s1.erase(5, 1);//helloworld!
return 0;
}
②iterator erase (iterator p);删除string的第p个位置:
int main()
{
string s1("hello world!");
s1.erase(s1.begin()+1);//hllo world!
return 0;
}
5.3 查
1.substr
string substr (size_t pos = 0, size_t len = npos) const;从pos位置开始,取len个字符:
int main()
{
string s1("hello world!");
string s2 = s1.substr(6, 5);//world
return 0;
}
2.find
关于find的返回值:
The position of the first character of the first match.
If no matches were found, the function returns string::npos.
即:如果找到了就返回找到的第一个的下标,如果没有找到就返回整型的最大值。
①size_t find (char c, size_t pos = 0) const;从pos位置开始找c,没有给pos默认从头开始找:
int main()
{
string s1("hello world!");
size_t pos1 = s1.find('l');//2
size_t pos2 = s1.find('l', 5);//9
size_t pos3 = s1.find('x');//npos
return 0;
}
例:取string指定的一部分:
int main()
{
//取文件名后缀
string s1("test.cpp");
size_t pos1 = s1.find('.');
if (pos1 != string::npos)
{
/*string s2 = s1.substr(pos1, s1.size() - pos1);*/
string s2 = s1.substr(pos1);
cout << s2 << endl;
}
return 0;
}
5.4 改
1. replace
①string& replace (size_t pos, size_t len, const string& str);在pos位置的len个字符替换成str:
int main()
{
string s1("hello world!");
s1.replace(5, 1, "?");//hello?world!
return 0;
}
2. swap
与另一个string交换:
int main()
{
string s1("hello world!");
string s2;
s2.swap(s1);//hello world!
return 0;
}
5.5 查改结合完成替换操作
例:将s1的空格全部替换为?
方法一:
int main()
{
string s1("have a good time!");
cout << "替换前s1=" << s1 << endl;//have a good time!
size_t pos = s1.find(' ', 0);
while (pos != string::npos)
{
s1.replace(pos, 1, "?");
pos = s1.find(' ', pos + 1);
}
cout << "替换后s1=" << s1 << endl;//have?a?good?time!
return 0;
}
实际中replace效率太低,因此尽量少用replace
方法二:
int main()
{
string s1("have a good time!");
cout << "替换前" << s1 << endl;//have a good time!
string s2;
for (auto ch : s1)
{
if (ch == ' ')
{
s2 += "?";
}
else
{
s2 += ch;
}
}
s1.swap(s2);
cout << "替换后" << s1 << endl;//have?a?good?time!
return 0;
}