今天,我们开始学习string类,我们将通过它的文档来了解它的内容及操作。
首先我们观察一下string类文档的框架。
一、string类的声明
- string类是管理字符的顺序表,字符串是表示字符序列的类。
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- 通过图一,我们可以看到**typedef basic_string string; **,string 类是 basic_string 类模板的实例化,string在底层实际是basic_string模板类的别名。
- 不能操作多字节(在某些编码系统(如UTF-8)中,一个字符可能由多个字节组成。对于这样的字符,如果字符串操作不当,可能会导致字符被错误地分割或者解释。)或者变长字符(字符串是由可变长度的单元组成的,这意味着不同的字符可能占用不同数量的存储空间。在处理这样的字符串时,需要特别注意字符间的边界)的序列。
二、string类的成员类型
string类的成员类型,我们看一眼了解一下即可。
其中重要的是 iterator(迭代器),我们在后续的接口说明中会学习到。
string类的接口说明(常用的和需要了解的)
在学习string类时的重点是对它的接口学习了解。我们要熟悉它们的功能和操作,同时在观察它们的功能和操作时可以思考,如果让我们模拟实现string类,我们要怎么实现?
三、成员常量
npos的定义值为-1,因为size_t是一个无符号整数类型,所以它是该类型可能表示的最大值。
四、string类对象的常见构造
(destructor)—— 构造函数
(destructor)—— 析构函数
operator= —— 赋值
(一)构造函数
先自己根据参数的类型和个数来分析它们的作用是什么。
string类的函数数量众多,我们只学习常用的和需要了解的,在使用的过程中,大家可以时刻查看文档加深记忆。
这里只介绍四种常用的构造函数。
其中(3)中的size_t len = npos; 是缺省参数,当使用默认值时,npos就是最大值,也就是说我们拷贝的字符串从pos位置开始有多大就拷贝多少。
(5)中就是用C-string中的n个字符来构造string类对象。
(7)InputIterator:输入迭代器。满足“InputIterator”概念的一些类型示例包括C++标准库中的“std::vector<T>::迭代器”、“std::list<T>::迭代器”和“std::string::迭代器”。用户定义的类型也可以通过实现必要的操作和成员函数来满足“InputIterator”概念。
void TestString1()
{
//创建字符串的操作
string s0; //构造空的string类对象
string s1 = "hello "; //构造+拷贝构造(隐式类型转换)
string s2("oooooooooo"); //用C格式字符串构造string类对象s2
string s3(s2); //拷贝构造s3
cout << s1 << endl;
cout << s0 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
在学习下列知识前,我们先来了解以下,string内部的组成。
char _str;*
size_t _size;
size_t _capacity;
(二)string类对象的容量操作
仍然是选择我们常用的函数进行详细说明
函数名称 | 功能说明 |
---|---|
size(length:为了方便模板使用, 新定义的名字,功能与size一致) | 返回字符串有效字符长度 |
capacity | 返回空间总大小(大小不算’\0’) |
reserve | 为字符串预留空间(请求更改容量(公共成员函数)) |
resize | 调整字符串大小,多出的空间用字符c填充 |
clear | 清除字符串 |
empty | 测试字符串是否为空 |
详细解释:
1.size(),capacity()和reserve()
void TestString4()
{
string s0;
string s1 = "hello world ";
string s2("oooooooooo");
string s3(s2);
cout <<"s1.size(): "<< s1.size() << endl;
cout << "s1.capacity(): " << s1.capacity() << endl;
s1.reserve(100);
cout << "s1.reserve(100):" << endl;
cout<<"s1.size(): " << s1.size() << endl;
cout << "s1.capacity(): " << s1.capacity() << endl;
s1.reserve(10);
cout << "s1.reserve(10):" << endl;
cout << "s1.size(): " << s1.size() << endl;
cout << "s1.capacity(): " << s1.capacity() << endl;
}
size()和capacity()的功能我们如图所示。
接下来需要主要了解的是:reserve().
reserve(size_t res_arg = 0): 为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间大小时,不会改变容量capacity的大小。
注意到reserve出的空间是大于想要空间的,这说明编译器为我们想要的空间又增加的空间,而reserve出的空间大小主要看编译器内部规定。
我们可以看到reserve预留出的空间100,再调整为10,capacity是不会发生改变的。
reserve的使用提高了效率,在知道我们需要空间大小时,提前开好空间,可以减少频繁扩容(频繁扩容是有代价的)。
2.resize的功能
注意,在使用resize时,要考虑到n的大小。
resize(size_t n);与resize(size_t n, char c); 都是将字符串中有效字符个数改变到n个。
- 当n小于s. size时,有效字符就改为n个,相当于删除了n后的字符,字符内容是不会发生改变,s.size会发生改变,但是s.capacity不会发生改变。
- 当n在范围2中,即n>s.size && n<s.capacity 时,**resize(size_t n);**用0来填充,**resize(size_t n, char c);**用字符c来填充多出来的空间。
- 当n在范围3中,resize在改变元素个数时,将元素个数增多时会改变底层容量的大小。,s.size会发生改变,s.capacity也会发生改变。
看代码,验证:
void TestString5()
{
string s0;
string s1 = "hello world ";
string s2("Good morning ");
string s3(s2);
//1. n < s2.size()
s1.resize(5);
cout << "s1.resize(5): " << s1 << endl;
cout << "s1.size(): " << s1.size() << endl;
cout << "s1.capacity(): " << s1.capacity() << endl;
cout << endl;
//2. n > s2.size && n < s2.capacity
s2.reserve(20);
cout << "s2.size(): " << s2.size() << endl;
cout << "s2.capacity(): " << s2.capacity() << endl;
cout << endl;
s2.resize(15,'!');
cout << "s2.resize(15): " << s2 << endl;
cout << "s2.size(): " << s2.size() << endl;
cout << "s2.capacity(): " << s2.capacity() << endl;
cout << endl;
//3. n > s3.capacity
s3.reserve(20);
cout << "s3.size(): " << s3.size() << endl;
cout << "s3.capacity(): " << s3.capacity() << endl;
cout << endl;
s3.resize(125, '!');
cout << "s3.resize(15): " << s3 << endl;
cout << "s3.size(): " << s3.size() << endl;
cout << "s3.capacity(): " << s3.capacity() << endl;
}
3.clean()和empty()
clean(): 只是将string中有效字符清空,不改变底层空间大小。
empty(): 检测字符串释放为空串,是返回true,否则返回false。
void TestString6()
{
string s2("Good morning ");
s2.clear();
cout << "s2.size(): " << s2.size() << endl;
cout << "s2.capacity(): " << s2.capacity() << endl;
if (s2.empty())
{
cout << "s2为空" << endl;
}
}
(三)string类对象的迭代器和元素访问操作
对string类对象的访问及遍历操作。
函数名称 | 功能说明 |
---|---|
begin/end | begin获取一个字符的迭代器,end获取最后一个字符下一个位置的迭代器 |
rbegin/rend | begin获取一个字符的反向迭代器,end获取最后一个字符下一个位置的反向迭代器,需要注意,指针的变化任然是++ |
operator[] | 返回pos位置的字符,const string类对象调用 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
1.begin/end —— iterator(迭代器)
均拥有const修饰的函数和非const的函数。
迭代器的使用:
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}
思考:此处的 while (it != s2.end()) 能否更改为while (it < s2.end())?
答案:可以,但是不建议使用,我们在C++中使用模板时,考虑的是多种类型而并非单独的string类,要考虑代码的通用性。那么为什么此处可以使用呢?
从上图中我们可以了解到,当物理空间不连续时,指针的位置并不知道是否都在end()前方,用<比较是思考不全面的。
2.rbegin/rend
反向迭代器。
cout << endl << "反向迭代器s2: ";
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
rit++;
}
3.operator[ ]
返回pos位置的字符,const string类对象调用。越界中止程序。
与at的功能重叠,区别是后者失败时会抛异常,不常使用。
//operator[]
cout << endl << "operator[]读,s1: ";
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << " "; //读
}
cout << endl << "operator[]写,s1: ";
for (int i = 0; i < s1.size(); i++)
{
s1[i]++; //写
}
cout << s1 << " ";
4.范围for
范围for不支持逆序遍历,不支持修改(若要修改字符串,需要auto& 给引用)
cout << endl << "范围for,s2: ";
for (auto ch : s2)//auto& ch:s2 可以对s2修改?
{
cout << ch << " ";
//ch++; 无法实现
}
cout << endl << "范围for,修改s2: ";
for (auto& ch : s2)//auto& ch:s2 可以对s2修改 注意前置++和后置++ 会对打印结果产生影响
{
cout << ++ch << " ";
//cout << ch++ << " ";//对结果没有影响
/*cout << ch << " ";
ch++; //对结果没有影响
++ch; //对结果没有影响*/
}
cout << endl << s2 << endl;
5.程序结果展示
void TestString2()
{
//创建字符串的操作
string s1 = "hello ";
string s2("Good morning ");
cout << "s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
//遍历 [] 迭代器begin end 范围for
//
//operator[]
cout << endl << "operator[]读,s1: ";
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << " "; //读
}
cout << endl << "operator[]写,s1: ";
for (int i = 0; i < s1.size(); i++)
{
s1[i]++; //写
}
cout << s1 << " ";
cout << endl << "迭代器s2: ";
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}
cout << endl << "反向迭代器s2: ";
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl << "范围for,s2: ";
for (auto ch : s2)//auto& ch:s2 可以对s2修改?
{
cout << ch << " ";
//ch++; 无法实现
}
cout << endl << "范围for,修改s2: ";
for (auto& ch : s2)//auto& ch:s2 可以对s2修改 注意前置++和后置++ 会对打印结果产生影响
{
cout << ++ch << " ";
//cout << ch++ << " ";
/*cout << ch << " ";
ch++; //对结果没有影响
++ch; //对结果没有影响*/
}
cout << endl << s2 << endl;
}
(四)string类对象的修改操作
函数名称 | 功能说明 |
---|---|
operator+= | 在字符串后追加字符串str |
append | 在字符串后追加一个字符串 |
push_back | 在字符串后尾插字符c |
pop_back | 在字符串后删除最后字符c |
assign | 将内容分配给字符串(可以作为赋值使用) |
insert | 插入(字符,字符串)到字符串中 |
erase | 从字符串中删除字符 |
replace | 替换字符串中的一部分 |
swap | 交换字符串值 |
代码演示:
1.operator+=(常用)
string s1 = "hello ";
string s2("Good morning");
string s3 = "world";
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
cout << "s3: " << s3 << endl;
//附加
s3 += '.';
s1 += s3;
cout << "s1 += s3 : " << s1 << endl;
s2 += "!!!";
cout << "s2 += !!! : " << s2 << endl;
2.insert/erase
//插入
s1.insert(5, "world."); //注意参数
cout << "s1.insert: " << s1 << endl;
s1.erase(3);
cout << "s1.erase: " << s1 << endl;
s1.insert(0, 8, 'x');
cout << "s1.insert: " << s1 << endl;
s1.erase(5,3);
cout << "s1.erase: " << s1 << endl;
3.append/push_back/pop_back
s1.append("world");
cout << "s1.append : " << s1 << endl;
s1.push_back('.');
cout << "s1.push_back : " << s1 << endl;
s1.append(s2);
cout << "s1.append : " << s1 << endl;
s1.pop_back();
cout << "s1.pop_back : " << s1 << endl;
4.swap
5.replace( 字符串操作中的函数搭配使用也可达到此效果)
// replacing in a string
#include <iostream>
#include <string>
int main ()
{
std::string base="this is a test string.";
std::string str2="n example";
std::string str3="sample phrase";
std::string str4="useful.";
// replace signatures used in the same order as described above:
// Using positions: 0123456789*123456789*12345
std::string str=base; // "this is a test string."
str.replace(9,5,str2); // "this is an example string." (1)
str.replace(19,6,str3,7,6); // "this is an example phrase." (2)
str.replace(8,10,"just a"); // "this is just a phrase." (3)
str.replace(8,6,"a shorty",7); // "this is a short phrase." (4)
str.replace(22,1,3,'!'); // "this is a short phrase!!!" (5)
// Using iterators: 0123456789*123456789*
str.replace(str.begin(),str.end()-3,str3); // "sample phrase!!!" (1)
str.replace(str.begin(),str.begin()+6,"replace"); // "replace phrase!!!" (3)
str.replace(str.begin()+8,str.begin()+14,"is coolness",7); // "replace is cool!!!" (4)
str.replace(str.begin()+12,str.end()-4,4,'o'); // "replace is cooool!!!" (5)
str.replace(str.begin()+11,str.end(),str4.begin(),str4.end());// "replace is useful." (6)
std::cout << str << '\n';
return 0;
}
(五)string类对象的字符串操作
了解:
// string::find_first_of
#include <iostream> // std::cout
#include <string> // std::string
#include <cstddef> // std::size_t
int main ()
{
std::string str ("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
while (found!=std::string::npos)
{
str[found]='*';
found=str.find_first_of("aeiou",found+1);
}
std::cout << str << '\n';
return 0;
}
函数名称 | 功能说明 |
---|---|
find_first_of | 在字符串中查找字符 |
find_last_of | 从末尾查找字符串中的字符 |
find_first_not_of | 查找字符串中缺少字符 |
find_last_not_of | 从末尾查找字符串中不匹配的字符 |
常用:
函数名称 | 功能说明 |
---|---|
c_str | 返回C格式字符串 |
find | 在字符串中查找内容。从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 查找字符串中内容的最后出现次数。从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 生成子字符串。在str中从pos位置开始,截取n个字符,将其返回 |
find
substr
//find+rfind+substr
//find+rfind+substr
void TestString7()
{
string s1("legacy.cplusplus.com");
//size_t i = s1.find('.');
size_t i = s1.rfind('.');
string s2 = s1.substr(i);
cout << s2 << endl;
//string s3("https://legacy.cplusplus.com/reference/string/string/?kw=string");
string s3("https://legacy.cplusplus.com/reference/string/string/find/");
// 协议
// 域名
// 资源名
string sub1, sub2, sub3;
size_t i1 = s3.find(':');
if (i1 != string::npos)
sub1 = s3.substr(0, i1);
else
cout << "没有找到协议" << endl;
size_t i2 = s3.find('/', i1 + 3);
if (i2 != string::npos)
sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
else
cout << "没有找到域名" << endl;
sub3 = s3.substr(i2 + 1);
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
}
(六)string类非成员函数重载
函数名称 | 功能说明 |
---|---|
operator+ | 少用,传值返回,深拷贝效率低,连接字符串 |
operator>> | 输入运算符重载,从流中提取字符串 |
operator<< | 输出运算符重载,将字符串插入流 |
swap | 交换两个字符串的值 |
relational operators | 字符串的关系运算符(大小比较) |
getline | 将行从流获取到字符串(可以读取’ ’ 空格,cin读取不了空格) |
relational operators
//relational operators
// string comparisons
#include <iostream>
#include <vector>
int main ()
{
std::string foo = "alpha";
std::string bar = "beta";
if (foo==bar) std::cout << "foo and bar are equal\n";
if (foo!=bar) std::cout << "foo and bar are not equal\n";
if (foo< bar) std::cout << "foo is less than bar\n";
if (foo> bar) std::cout << "foo is greater than bar\n";
if (foo<=bar) std::cout << "foo is less than or equal to bar\n";
if (foo>=bar) std::cout << "foo is greater than or equal to bar\n";
return 0;
}