目录
前言
学习库一定要学会看文档:
标准库中的String类
包含头文件:#include <string>
注意事项:
1、string是表示字符串的字符串类
2、string类的接口和常规容器的接口基本相同,同时也添加了一些专门用来操作string的常规操作
3、string不能操作多字节或变长字节字符的序列
String类中常见构造操作
1、默认构造(default constructor)
格式:string()
功能:构造一个空字符串
2、拷贝构造(copy constructor)
格式:string(const string& str)
功能:拷贝源字符串
3、子串构造(substring constructor)
格式:string(const string& str,size_t pos,size_t len = npos)
功能:从字符串的pos开始向后复制len个字符,如果字符串太短或len的大小为string类中的npos,则复制到str的末尾(不给len时默认从pos位置起拷贝至字符串末尾止)
nops is a static member constant value with the greatest possible value for an element of type size_t(nops是一个静态成员常量,具有size_t类型元素的最大可能值(2^32)- 1)
#include <iostream>
#include <string>
using namespace std;
void test_string1()
{
string s0;//构造空的string类的对象,即空字符串
string s1("hello world");
string s2(s1);
string s3(s1, 5, 3);
string s4(s1, 5, 10);
string s5(s1, 5);
cout << "默认构造 >" << s0 << endl;
cout << "拷贝构造 >" << s1 << endl;
cout << "拷贝构造 >" << s2 << endl;
cout << "子串构造(从第五个字符开始向后拷贝三个字符) >" << s3 << endl;
cout << "子串构造(从第五个字符开始向后拷贝十个字符) >" << s4 << endl;
cout << "子串构造(从第五个字符开始不指定向后拷贝字符数)>" << s5 << endl;
}
int main()
{
test_string1();
return 0;
}
可以直接打印的原因是string中已经提前写好了流插入流提取运算重载函数
4、指针拷贝(from c-string)
格式:string(const char* s)
功能:复制指针s指向的c字符串(包含\0的字符串)
5、指针限定拷贝(from buffer)
格式:string(const char* s,size_t n)
功能:复制s指向的字符串的前n个字符
6、填充构造(fill constructor)
格式:string(size_t n,char c)
功能:将n个字符(单引号的)填充至空字符串中
#include <iostream>
#include <string>
using namespace std;
void test_string1()
{
const char* s = "abcdef";
string s1(s);
string s2(s, 5);
string s3(5, '#');
cout << "指针拷贝 >" << s1 << endl;
cout << "指针限定拷贝 >" << s2 << endl;
cout << "填充构造 >" << s3 << endl;
}
int main()
{
test_string1();
return 0;
}
结论
(constructor)函数名称 | 功能说明 |
string()(重点) | 构造空的string类对象,即空字符串 |
string(const char* s)(重点) | 用C-string来构造string类对象 |
string(size_t n,char c) | string类对象中国包含n个字符c |
string(const string&s)(重点) | 拷贝构造函数 |
string类对象的访问及遍历操作
string类中的构成(仅展示暂时用的):
//[]运算符重载函数在string类中
class string
{
public:
char& operator[](size_t pos) //(string* this,size_pos)
{
return _str[pos];
}
const char& operator[](size_t pos)const //(const string* this,size _pos)
{
return _str[pos];
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
str指向的字符串中都会包含\0,但是_size和_capacity不会将该\0纳入计数范围
1、operator[]
格式:char& operator[](size_t pos)、const char& operator[](size_t pos)const
二者构成运算符重载和函数重载,因为隐藏的this指针类型一个是string*和const string*
string s2("hello world");
s2[0]++; //right
const string s2("hello world");
s2[0]++; //wrong
功能:读写pos位置的值
#include <iostream>
#include <string>
using namespace std;
void test_string1()
{
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " "; //s1.opeaator[](i)
}
for (size_t i = 0; i < s1.size(); i++)
{
s1[i]++;
}
cout << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
}
int main()
{
test_string1();
return 0;
}
2、正向迭代器begin和end
基本概念:迭代器(iterator)是一种检查容器内元素并遍历元素的数据类型,通常用于对C++中各种容器(string、vector、list等)内元素的访问,不同的容器有不同的迭代器(迭代器是一个行为像指针一样的类型)
格式:容器名::iterator 变量名 = 容器类型的变量名.begin()、容器类型的变量名.end()
功能:正向遍历容器
begin()是指向容器第一个元素的迭代器,end()是指向容器最后一个元素的下一个位置的迭代器
#inlcude <iostream>
using namespace std;
int main()
{
vector<int> vtr;
//初始化容器
for (int i = 0; i < 10; ++i)
{
vtr.push_back(i);
}
//利用迭代器遍历容器
cout << "方式1:";
for (vector<int>::iterator it = vtr.begin(); it != vtr.end(); ++it)//it就是迭代器类型的变量
{
cout << *it << " ";
}
cout << "\n方式1:";
for (vector<int>::iterator it = begin(vtr); it != end(vtr); ++it)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
3、反向迭代器rbegin+rend
格式:容器名::reverse_iterator 变量名 = 容器类型的变量名.rebegin()、容器类型的变量名.rend()
功能:反向遍历容器
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s3("hello world");
string::reverse_iterator rit = s3.rbegin();
while (rit != s3.rend())
{
cout << *rit << " ";
++rit;
}
return 0;
}
4、const修饰的迭代器
功能:被const修饰的迭代器只能读取字符串中的内容
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s3("hello world");
const string s4("hello world");
string::iterator rit = s3.begin();
while (rit != s3.end())
{
*rit += 3;
cout << *rit << " ";
++rit;
}
cout << endl;
string::const_iterator rit2 = s4.begin();
while (rit2 != s4.end())
{
*rit2 += 3; //wrong
cout << *rit2 << " ";
++rit2;
}
return 0;
}
迭代器可被const修饰,是因为string类中对于begin等迭代器提供了无const和有const的实现:
如果字符串是被const修饰的,那么就需要调用被const修饰的迭代器
5、范围for
基本概念:范围for除了可以遍历数组还可以遍历容器
范围for的底层就是迭代器
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s3("hello world");
for (auto e : s3)
{
cout << e << " ";
}
cout << endl;
return 0;
}
结论
函数名称 | 功能说明 |
operator[] | 返回pos位置的字符,const string类对象调用 |
begin + end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
string类对象的容量操作
简单的容量操作
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s3("hello world");
cout << "字符串有效长度 >" << s3.size() << endl;
cout << "字符串有效长度 >" << s3.length() << endl;
cout << "字符串的最大大小 >" << s3.max_size() << endl;
cout << "字符串占用的总空间 >" << s3.capacity() << endl;
cout << "检测字符串是否为空 >" << s3.empty() << endl;
cout << "清除有效字符 >" << endl;
s3.clear();
cout << "字符串有效长度 >" << s3.size() << endl;
cout << "字符串占用的总空间 >" << s3.capacity() << endl;
return 0;
}
注意事项:
1、size()于length()方法底层实现原理完全相同,引入size() 的原因是为了与其它容器的接口保持一致(向前兼容),一般情况下基本都是用size(),且二者都不会计算字符串后的\0
2、clear只会清除有效字符不会清理空间
缩容(C++11)
功能:将字符串的容量减少至它当前有效字符的个数
注意事项:因为string中有一个缓冲区(buffer),所以string类的字符串对象只能缩容到15(因为\0未被记录在内所以实际是16),且一般情况下缩容也是有代价的(异地缩容,开空间、拷贝数据)
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s3("hello world");
s3.clear();
cout << "此时s3字符串的内容 >"<<s3 << endl;
cout << "字符串有效长度 >" << s3.size() << endl;
cout << "字符串占用的总空间 >" << s3.capacity() << endl;
s3.shrink_to_fit();
cout << "清理后字符串有效长度 >" << s3.size() << endl;
cout << "清理后字符串占用的总空间 >" << s3.capacity() << endl;
return 0;
}
查看扩容机制
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
size_t sz = s.capacity();
cout << "字符串初始有效长度 >" << s.size() << endl;
cout <<"字符串初始空间大小 >" << s.capacity()<<endl;
cout << endl;
for (int i = 0; i < 100; ++i)
{
s.push_back('c');//向字符串中尾插一个字符C
if (sz != s.capacity())
{
sz = s.capacity();
cout << "当前字符串所占空间大小为: " << sz << endl<<endl;
}
}
return 0;
}
起始size为0,capacity为15,利用for循环向字符串中插入字符c,当插入15个字符后空间满了,再次插入第十六个字符时系统就会自动扩容,需要注意的是,第一次扩容的大小是原空间大小的2倍,后续扩容的是原空间大小的1.5倍(因为\0不会被size和capacity方法统计,所以虽然显示的起始空间大小为15,但实际空间大小是16,同理,31 + 1 = 32 = 16 * 2、47 + 1 = 48 = 32 * 1.5、70 + 1 = 71 = 48 * 1.5、105 + 1 = 106 ≈ 71 * 1.5)
不同编译器有不同的扩容机制,如果是linux的话都是进行2倍扩容
reserve和resize
reserve:储备;储存物;保留;留出(提前预定)
resize:调整…的大小;重定…的大小(用时扩容)
一般情况下扩容都是异地扩容,realloc可以异地扩容也可以本地扩容
reserve方法
功能:改变capacity
注意事项:
1、频繁的异地扩容代价大,可以用reserve来向系统提前预定一片空间,避免后续扩容:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s3;
cout << "初始空间 >" << s3.capacity() << endl;
s3.reserve(100);
size_t sz = s3.capacity();
cout << "reserve后的空间 >" <<s3.capacity() <<endl;
for (int i = 0; i < 100; ++i)
{
s3.push_back('c');//向字符串中尾插一个字符C
if (sz != s3.capacity())
{
cout << "进行了扩容" << endl;
sz = s3.capacity();
cout << "当前字符串所占空间大小为: " << sz << endl << endl;
}
}
cout << "未进行扩容" << endl;
return 0;
}
在vs中指定reverse100字节大小的空间它会给你开112个(带\0),但是在g++就只会给你101个
2、reverse提前申请的空间要比原来的空间大才可以申请成功
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello worldxxxxxx");
cout << s1.size() << endl;
cout << s1.capacity() << endl<<endl;
cout << "申请失败:" << endl;
s1.reserve(20);
cout << s1.size() << endl;
cout << s1.capacity() << endl<<endl;
cout << "申请成功:" << endl;
s1.reserve(200);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
return 0;
}
3、reserve是手动扩容,push_back是自动扩容(容量不够自动扩容)
resize方法
功能:改变size或者capacity
注意事项:
1、resize方法针对三种参数的情况,有三种处理方法(假设原size = 17,capacity = 32)
resize(n) | 范围 | 操作 |
n = 10 | n<size | 删除 |
n = 20 | size < n <capacity | 插入 |
n = 40 | capacity < n | 扩容 + 插入 |
①n<size,删除了7个有效字符,只保留了10个有效字符:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello worldxxxxxx");
cout << "当前的size >" << s1.size() << endl;
cout << "当前的capacity >" << s1.capacity() << endl << endl;
cout << "n<size,执行删除操作:" << endl;
s1.resize(10);
cout << "当前的size >" << s1.size() << endl;
cout << "当前的capacity >" << s1.capacity() << endl << endl;
return 0;
}
②size < n <capacity,原本只有17个现在开了20个,多出来的部分(三个)填充'\0'
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello worldxxxxxx");
cout << "当前的size >" << s1.size() << endl;
cout << "当前的capacity >" << s1.capacity() << endl;
cout << "当前的字符串 >" << s1 << endl<<endl;
cout << "size < n < capacity,执行插入操作:" << endl;
s1.resize(20);
cout << "当前的size >" << s1.size() << endl;
cout << "当前的capacity >" << s1.capacity() << endl;
cout << "当前的字符串 >" << s1 << endl << endl;
return 0;
}
③ capacity < n,原空间不够系统自动扩容,扩容剩余部分默认由'\0'填充
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello worldxxxxxx");
cout << "当前的size >" << s1.size() << endl;
cout << "当前的capacity >" << s1.capacity() << endl;
cout << "当前的字符串 >" << s1 << endl<<endl;
cout << "size < n < capacity,执行插入操作:" << endl;
s1.resize(100);
cout << "当前的size >" << s1.size() << endl;
cout << "当前的capacity >" << s1.capacity() << endl;
cout << "当前的字符串 >" << s1 << endl << endl;
return 0;
}
2、可以自定义要填充的字符:对象名.resize(n,自定义字符),但是n必须大于size
结论
函数名称 | 功能说明 |
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
max_size | 返回字符串的最大大小 |
capacity | 返回空间总大小 |
empty | 检测字符串是否为空串,是返回true,否返回false |
clear | 清空有效字符 |
shrink_to_fit | 将字符串的容量减少至它当前有效字符的个数 |
reserve | 为字符串预留空间(提前开空间) |
resize | 将有效字符的个数改成n个(扩容),多出的空间用字符c填充 |
string类对象的修改操作
#include <iostream>
#include <string>
using namespace std;
int main()
{
cout << "在字符串后尾插字符" << endl;
string s1("hello world");
s1.push_back('!');
cout << s1 << endl<<endl;
cout << "在字符串后追加字符串" << endl;
s1.append("hi bit");
cout << s1 << endl << endl;
cout << "在字符串后追加n个字符" << endl;
s1.append(10, 'x');
cout << s1 << endl << endl;
cout << "利用迭代器对字符进行++--操作" << endl;
string s2(" applec");//两个空格 + apple +一个字符c
s1.append(++s2.begin(), --s2.end());//begin迭代器向前移动前面少打印一个空格,end迭代器向前移动后面少打印一个c
cout << s1 << endl << endl;
string s3("hello world");
s3 += ' ';//追加单个字符
s3 += "apple";//追加字符串
cout << s3 << endl;
return 0;
}
一般情况下更倾向于使用opeartor+=对字符串进行操作
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s4("hello world");
cout << s4 << endl<<endl;
cout << "覆盖字符串" << endl;
s4.assign("xxxxxx");
cout << s4 << endl<<endl;
cout << "在指定位置插入字符串" << endl;
s4.insert(1,"yyyyyy");
cout << s4 << endl << endl;
cout << "删除字符串n位置后的m个字符" << endl;
s4.erase(5, 3);
cout << s4 << endl << endl;
cout << "无参数时的erase类似于clear,清空有效字符" << endl;
s4.erase();
cout << s4 << endl << endl;
cout << "将字符串第n个位置的m个字符替换成另外的字符" << endl;
string s5("hello world hello bit");//注意h是字符串下标为0的位置,下标为5的位置是空格
s5.replace(5,1,"%20");
cout << s5 << endl << endl;
cout << "循环寻找字符串中的空字符,找到后将其替换成=" << endl;//以空间换时间
string s6("hello xsfewwf bit xqfq");
size_t pos = s6.find(' ');//寻找字符串对象s6的第一个空字符
while (pos != string::npos)//find方法找到了空字符就返回该字符所在位置,没找到就返回string::npos
{
s6.replace(pos,1,"====");//依据存放空字符位置信息的pos,将该位置的一个字符替换成====
pos = s6.find(' ');//接着向后寻找空字符
}
cout << s6 << endl<<endl;
//优化版本的符号查找与替换(以空间换时间)
cout << "优化后版本" << endl;
string s7;
s7.reserve(s6.size());//提前开辟好,一定程度上减少开辟所需时间
for (auto ch : s6)
{
if (ch != ' ')
{
s7 += ch;
}
else
{
s7 += "====";
}
}
cout << s7 << endl;
cout << "利用swap使得原字符串也可发生改变" << endl;
s6.swap(s7);
cout << s6 << endl;//改变后的s6
return 0;
}
insert、erase、replace能不用就不用,因为基本都要挪动数据,效率不高
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s8("hello world");
string filename("test.cpp");
FILE* fout = fopen(filename, "r");
return 0;
}
fopen函数的原型是,但是filename中存放的字符串类型不是const char*:
FILE * fopen ( const char * filename, const char * mode );
什么是c_ctr
string类的filename对象中存放的字符串不会以'\0'结尾,但是c语言类型的字符串都以'\0'结尾:
为此需要用c_str方法完成这一转换过程
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s8("hello world");
string filename("test.cpp");
/*FILE* fout = fopen(filename.c_str(), "r");*/
for (size_t i = 0; i < filename.size()+1; i++)//原来的size不会将\0统计在内,所以需要+1
{
const char* ch = filename.c_str();
if (ch[i] == '\0')
cout << "有\\0" << endl;
}
return 0;
}
find与substr
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("file.cpp.c.zip");
//寻找文件后缀
size_t pos1 = s1.find(".");
size_t pos2 = s1.rfind(".");
if (pos1 != string::npos)
{
string suffix = s1.substr(pos1);
cout << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
if (pos2 != string::npos)
{
string suffix = s1.substr(pos2);
cout << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
return 0;
}
- find方法的返回值是size_t,从前往后找
- substr没有提供len的值时,默认返回pos位置后的所有字符
- rfind从后往前找
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string url1("https://blog.csdn.net/m0_73975164?spm=1011.2124.3001.5343");
string url2("https://cplusplus.com/reference/string/string/?kw=string");
cout << "网址 >" << url1 << endl;
string protocol, domain, uri;//domain:域名、protocol:协议、uri:要获取的内容
size_t i1 = url1.find(":");//第下标为5的位置找到,i1 = 5
if (i1 != string::npos)
{
protocol = url1.substr(0, i1 - 0);//从下标为0的位置开始向后读取(i1-0)个字符
cout << "协议 >" << protocol << endl;
}
size_t i2 = url1.find("/", i1 + 3);//第下标为5的位置找到,i1 = 5,find还可以自主选择要查找的起始位置
if (i2 != string::npos)
{
domain = url1.substr(i1 + 3, i2 - (i1 + 3));//从下标为(i1+3)的位置开始向后读取(i2-(i1+3))个字符
cout << "域名 >" << domain << endl;
uri = url1.substr(i2 + 1);//不读取/
cout << "剩余部分 >" << uri << endl;
}
size_t i3 = url1.find("blog");//find也可以查找子串的起点位置
cout << "子串blog的起点位置是在下标为:" << i3<< "处" << endl;
cout << (url1 < url2) << endl;//string类中还提供了运算符重载,用于比较两个字符串
return 0;
}
注意:对于一个类来说,如果它的某个构造函数 只接受一个实参 ,那么其实定义了该类类型的隐式转换机制,这种构造函数称为 转换构造函数 (converting constructor)。 这句话,也就是说,转换构造函数(只接受一个实参的构造函数)定义了一条从 参数类型 向 类类型 隐式转换的规则
结论
函数名称 | 功能说明 |
operator+= | 在字符串后追加字符串str |
append | 在字符串后追加一个字符串 |
push_back | 在字符串后尾插字符 |
assign | 写入新字符串覆盖原先字符串 |
insert | 在字符串的指定位置插入 |
erase | 删除字符串指定位置后的几个字符 |
replace | 替换字符串的一部分 |
swap | 交换字符串值 |
pop_back | 尾删字符串 |
c_str | 将string类型的字符串转换为以\0结尾的 C 风格字符串 |
find | 从前向后查找字符串内容 |
rfind | 从后向前查找字符串内容 |
substr | 生成子串 |
~over~