C++学习之路,从0到精通的征途:string类
目录
一.string类的概念
二.string的常见接口
1.string类对象的常见构造
2.string类对象的容量操作
(1)size,length,capacity
(2)reserve
(3)resize
(4)clear,shrink_to_fit
3.string类对象的访问及遍历操作
(1)auto与范围for
auto关键字:
范围for:
(2)遍历1:operator[]/at
(3)遍历2:迭代器
(4)遍历3: 范围for
4.string类对象的修改操作
(1)+=,append,push_back,insert
(2)erase,replace,pop_back
(3)c_str,substr
(4)find,rfind,find_first_of,find_last_of
实际运用:如何从一个网站名中获取协议,域名,资源名?
5.string类的非成员函数
(1)operator+
(2)relational operators
(3)getline
一.string类的概念
源文档:String class
由于string在std命名空间中,所以在使用string类时需要用展开命名空间,并且包含#include头文件。
二.string的常见接口
1.string类对象的常见构造
源文档源文档: 这里只对一些常用的进行演示:
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1; // 创建一个没有初始化的空字符串
string s2("******"); // 创建一个初始值为"******"的字符串
string s3(6, '*'); // 创建一个以6个'*'为初始值的字符串
string s4 = "Hello World"; // (构造+拷贝构造)创建一个以"Hello World"为初始值的字符串
string s5(s4); // 用s2拷贝构造s5
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
return 0;
}
运行结果:
2.string类对象的容量操作
(1)size,length,capacity
void Test_string2()
{
string s1 = "Hello World";
cout << s1.size() << endl; // 返回字符串有效字符长度
cout << s1.length() << endl; // 返回字符串有效字符长度
cout << s1.capacity() << endl; // 返回字符串所申请的空间总大小
}
运行结果:
(2)reserve
void Test_string3()
{
string s1 = "Hello World";
string s2(s1);
cout << s1.capacity() << endl;
// 当n大于字符串对象的容量大小,会进行扩容
s1.reserve(100);
cout << s1.capacity() << endl;
// 当n小于字符串对象的容量大小,取决于编译平台,不推荐使用
s2.reserve(5);
cout << s2.capacity() << endl;
}
运行结果:
可以看到在当前的vs编译环境下,字符串s2的大小没有进行缩容。
(3)resize
void Test_string4()
{
string s1 = "Hello world";
string s2(s1);
string s3(s1);
string s4(s1);
cout << s1.size() << endl; // 11
cout << s1.capacity() << endl; // 15
// n大于size且小于capacity,进行插入数据
s1.resize(13); // 默认用'\0’填充
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s2.resize(13,'x'); // 用'x’填充
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
// n大于capacity,进行扩容操作
s3.resize(30, 'x');
cout << s3 << endl;
cout << s3.size() << endl;
cout << s3.capacity() << endl;
// n小于size,删除数据
s4.resize(5);
cout << s4 << endl;
cout << s4.size() << endl;
cout << s4.capacity() << endl;
}
运行结果:
调制过程中可以看到s1用'\0'填充:
(4)clear,shrink_to_fit
void Test_string5()
{
string s1 = "Hello world";
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
}
运行结果:
void Test_string6()
{
string s1 = "Hello world";
s1.reserve(100); // 扩容
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.shrink_to_fit(); // 缩容
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
}
运行结果:
3.string类对象的访问及遍历操作
(1)auto与范围for
在学习string类对象的访问前,我们在这里补充2个C++11的小语法,方便我们后面的学习。
auto关键字:
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得。
int a = 10;
auto b = a; // b自动推导为int类型
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用。
auto func()
{
return 10;
}
auto不能直接用来声明数组。
范围for:
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象上进行遍历
范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到
(2)遍历1:operator[]/at
// 遍历1
void Test_string8()
{
string s1 = "Hello World";
string s2(s1);
cout << s1 << endl;
for (int i = 0; i < s1.size(); ++i)
{
s1[i] = 'x';
cout << s1[i] << " ";
}
cout << endl;
for (int i = 0; i < s2.size(); ++i)
{
s2.at(i) = 'm';
cout << s2.at(i) << " ";
}
}
运行结果:
(3)遍历2:迭代器
迭代器在现阶段可以理解为像指针一样的东西,它是一种容器通用的访问方式。
void Test_string9()
{
string s1 = "Hello World";
cout << s1 << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
*it = 'x';
cout << *it << " ";
it++;
}
}
运行结果:
(4)遍历3: 范围for
原理:编译时替换为迭代器。
void Test_string10()
{
string s1 = "Hello World";
for (auto e : s1)
{
cout << e << " ";
}
cout << endl;
for (auto e : s1)
{
e--;
cout << e << " ";
}
}
运行结果:
4.string类对象的修改操作
(1)+=,append,push_back,insert
void Test_string11()
{
string s1 = "Hello ";
string s2(s1);
string s3(s1);
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1 += "World"; // 向s1追加"World"
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s2.append("World"); // 向s2追加"World"
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
s1.push_back('x'); // 向s1尾插一个'x'
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s2.insert(5, "xxx"); // 向s2的第五个位置插入字符串"xxx"
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
// 谨慎使用insert,因为底层要挪动数据,再插入,效率不高
s3.insert(6, "World"); // 向s3的尾部追加一个"World"
cout << s3 << endl;
cout << s3.size() << endl;
cout << s3.capacity() << endl;
}
运行结果:
(2)erase,replace,pop_back
void Test_string12()
{
string s1 = "Hello World";
string s2(s1);
string s3(s1);
// 谨慎使用erase,因为底层要挪动数据覆盖删除
s1.erase(5, 6); //把s1第5个位置后的6个字符删除
cout << s1 << endl;
// 谨慎使用replace,因为底层要挪动数据
s2.replace(5, 6, "xxxxx"); // 将s2下标为2的位置往后6个字符替换为"xxxxx"
cout << s2 << endl;
s3.pop_back(); // 尾删
cout << s3 << endl;
}
运行结果:
(3)c_str,substr 
void Test_string13()
{
string s1 = "Hello World";
const char* cstr = s1.c_str(); // 将string类转化为const char*类型,转变为C语言类型的接口
cout << cstr << endl;
string substr = s1.substr(5, 6); // 将s1中第五个位置后面6个字符拷贝到substr中
cout << substr << endl;
}
运行结果:
(4)find,rfind,find_first_of,find_last_of
void Test_string14()
{
string s1 = "Hello World";
// 从前往后查找字符'l'
size_t pos1 = s1.find('l');
cout << pos1 << endl;
// 从后往前找字符'l'
size_t pos2 = s1.rfind('l');
cout << pos2 << endl;
// 从前往后查找"World"中的任意一个出现的字符
size_t pos3 = s1.find_first_of("World");
cout << pos3 << endl;
// 从后往前查找"Hello"中的任意一个出现的字符
size_t pos4 = s1.find_last_of("Hello");
cout << pos4 << endl;
}
运行结果:
实际运用:如何从一个网站名中获取协议,域名,资源名?
void Test_string15()
{
string url = "https://mpbeta.csdn.net/mp_blog/creation/editor/146455672?spm=1000.2115.3001.5352";
// 获取协议名
size_t pos1 = url.find(':');
string sub1;
if (pos1 != string::npos)
{
sub1 = url.substr(0, pos1);
}
cout << sub1 << endl;
// 获取域名
string sub2;
string sub3;
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != string::npos)
{
sub2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
// 获取资源名
sub3 = url.substr(pos2 + 1);
}
cout << sub2 << endl;
cout << sub3 << endl;
}
运行结果:
5.string类的非成员函数
(1)operator+
void Test_string16()
{
string s1 = "Hello ";
string s2 = "world";
// s1与s2拼接
cout << s1 + s2 << endl;
// 字符串与s2拼接
cout << "Goodbye " + s2 << endl;
}
运行结果:
(2)relational operators
(3)getline
void Test_string17()
{
string s1;
cin >> s1; // 这种输入方式只能将'\n'作为输入终止符,并且忽略了空格
cout << s1 << endl;
}
运行结果:
而getline可以输入空格,并且指定任意输入终止符:
void Test_string17()
{
string s2;
getline(cin, s2, '#'); // 指定'#'为输入终止符
cout << s2 << endl;
}
运行结果:
调试中可以看到换行符也在s2中: