一、string的基本概念
1.1string是管理字符数组的类
常见的初始化使用场景:无参构造和拷贝构造
string s1; //无参构造
string s2("hello world"); //有参构造
对string类的总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
二、string类模板
2.1重要的运算符重载
2.1.1 string::size
2.1.2 string::operator[]
string::operator[]底层原理是在堆上开一个字符数组,在字符数组里面存。
这两个的结合可以解决string类字符串的单个遍历,像数组一样进行遍历。
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << endl;
}
我们发现虽然底层在字符数组里面存,但是在打印的时候是\0不会显示打印出来?
for(int i=0;i<s2.size()+1;i++)
{
cout<<s2[i]<<" ";
}
通过编写一段上面的代码发现是还是没有显示打印出来,再监视s2发现当i=11时,寄存器临时变量没有数据进行输出打印。
2.1.3 比较思考
那么string这类的这个字符串和char a[]这些字符数组有没有区别?
比如:s1("hello world"); char a[]="hello world"
差别在哪儿:底层差别 s1本质是一个类调用的string::operator[],a[]本质是数组的首地址也就是一个解引用+地址。
2.2 迭代器
理解成像指针一样的东西。
如下:
string::iterator it = s1.begin();
iterator是标准库类模板的关键字,需要自己指定限定符,it相当于是s1这个string类字符串的第一个字符。
常见的使用
#include<iostream>
using namespace std;
int main()
{
string s1("hello world");
//使用迭代器的对s1的遍历
string::iterator it=s1.begin();
while(it!=s1.end())
{
cout<<*it<<endl;
it++;
}
//另外一个重要的c++11 可以用的遍历方法
for(auto i:s1) //这个的auto其实是自动识别的迭代器类型 <=> string:iterator i:s1
{
cout<<i<<" ";
}
return 0;
}
那么迭代器可以遍历string的字符串,那么能修改嘛?
标准库的字符串容器std::string是可以进行修改的。
for (auto i : s1)
{
i = 'x';
cout << i << " ";
}
三、string类的常用接口
3.1string类对象的常见构造
1.string() ----->调用的构造函数初始化为空字符串
2.string(const string& str) ----->调用的拷贝构造函数
3.string(const string& str,size_t pos,size_t len=npos) ---->将从str这个字符串的pos位置后len个长度进行拷贝构造函数
4.string(const char* s) ----->用一个字符数组(c-string)来构造对象
5.string(const char* s,size_t n) ---->选取s字符数组前n个进行拷贝构造
6.string(size_t n,char c) ---->将string对象初始化为n个字符c
三个重点掌握的string类构造运行实例
#include<iostream>
using namespace std;
int main()
{
string s1;
cout<<s1<<endl;
const char a[]="hello world"; //const可加可不加 不加的话就是权限缩小
string s2(a);
cout<<s2<<endl;
string s3(s2);
cout<<s3<<endl;
return 0;
}
3.2string类对象的容量操作
3.2.1 size
功能:返回字符串有效字符长度
string s1("hello world");
cout<<s1.size();
//输出结果为10
3.2.2 length
功能:返回字符串有效字符长度
string s1("hello world");
cout<<s1.length();
//输出结果为10
size()和length()的底层实现是一样的,然后实现的功能也一样,觉得有点冗余,但是size()这个设置是为了与其他标准库保持一样的函数接口设置的。
3.2.3 capacity
功能:返回空间总大小(换句话说,就是返回开辟的空间大小)
string s1("hello world");
cout<<s1.capacity();
//输出15
为什么是输出15呢,我们知道string实际也是顺序表,顺序存储结构,那么也就是扩容的路线。vs下和linux下的扩容策略不一样,vs下是1.5倍。
3.2.4 empty
功能:检测字符串释放为空串,是返回true,否则返回false。
int main()
{
string s1;
if (s1.empty())
{
cout << s1.empty() << endl;
}
string s2("hsq");
if (!s2.empty())
{
cout << s2.empty() << endl;
}
return 0;
}
3.2.5 clear
功能:清空有效字符
string s2("hsq");
s2.clear();
cout << s2 << endl;
//输出为空
思考:我把有效字符清空了,那么我的空间会有影响?
测试:
string s2("hsq");
cout << "clear前容量" << s2.capacity() << endl;
s2.clear();
cout << s2 << endl;
cout << "clear后容量" << s2.capacity() << endl;
可见,clear只进行有效字符的清理工作,对string对象字符串的开辟的内存空间是不影响的。
3.2.6 reserve
功能:为字符串预留空间(适用于已知需要多少空间的情况,减少扩容操作)
int main()
{
string s1;
cout << s1.capacity() << endl;
s1.reserve(100);
cout<< s1.capacity() << endl;
return 0;
}
可见,reserve函数可以提前预留空间,改变的底层的内存空间。
那么预留空间小于当前string对象的空间会发生什么情况呢?
测试:
int main()
{
string s1("hello world");
cout << s1.capacity() << endl;
s1.reserve(2);
cout << s1.capacity() << endl;
return 0;
}
当预留空间小于string对象的内存空间,是不会进行预留空间的操作,也不会缩减空间,但是在其他环境下可能会进行缩容。
3.2.7 resize
功能:将有效字符的个数改成n个,多出的空间用字符c填充
int main()
{
string s1("hello world");
cout << "原始的有效字符个数" << s1.size() << endl;
cout <<"原始的内存空间容量" << s1.capacity() << endl;
//把有效字符个数改成100个
s1.resize(100);
cout <<"字符串" << s1 << endl;
cout << "有效字符个数" << s1.size() << endl;
cout << "内存空间容量" << s1.capacity() << endl;
cout << "-------------------------------" << endl;
//把有效字符个数改成100个 未填充的填充为'a'
string s2("hello world");
cout << "原始的有效字符个数" << s2.size() << endl;
cout << "原始的内存空间容量" << s2.capacity() << endl;
s2.resize(100,'a');
cout << "字符串" << s2 << endl;
cout << "有效字符个数" << s2.size() << endl;
cout << "内存空间容量" << s2.capacity() << endl;
cout << "-------------------------------" << endl;
return 0;
}
通过两次相同函数的重载调用发现,当进行了resize那么会改变size大小是进行增大size大小的话,capacity相应的按照自己扩容的策略进行扩容;当是进行size减少(有效字符数据可能丢失),那么底层的capacity容量不变。当传入第二个字符参数‘a’,那么字符串中未被填充的就会被填充为a;如果不填,那么就是用0来天聪多出的元素空间。
3.2.8 resize和reserve的对比区别:
#include<iostream>
using namespace std;
int main()
{
string s1("hello world");
s1.reserve(100); //单纯的开空间 改变的capacity
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(100); //开空间+填值
cout << s1.size() << endl;
cout << s1.capacity() << endl;
return 0;
}
reserve:单纯的开空间 改变的capacity,当是要缩小的时候是不改变capacity和size的。
resize:开空间+填值(默认为\0) 或者 删除数据 不缩容(空间大小还在)
3.3string类对象的访问及遍历操作
3.3.1operator[]
功能:返回pos位置的字符,const string类对象调用
这里在上面进行了分析,是string重要的运算符重载,使得string对象的字符串,能像数组一样进行直接访问和遍历,极大的解决很多操作的难点
int main()
{
string s1("hello world");
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
return 0;
}
3.3.2 begin+end /rbegin+rend
begin+end :
功能:begin获取一个字符的迭代器,+end获取最后一个字符下一个位置的迭代器。
rbegin+rend:
功能:begin获取一个字符的迭代器,+end获取最后一个字符下一个位置的迭代器。
int main()
{
string s1("hello world");
auto it = s1.begin();
//访问
cout << *it << endl;
//遍历+访问
while (it != s1.end())
{
cout << *it << " ";
it++;
}
return 0;
}
s1.begin() 是指向s1这个string对象的第一个字符。可以差不多理解为c语言的指针,但是从根本上不是指针。
3.3.3 范围for
c++11支持的新的遍历方式
实现的底层其实还是用的迭代器。
for(auto i:x)
{
cout<<i<<" ";
}
3.4 string类对象的修改操作
3.4.1 push_back(尾插)
push_back这个在标准模板库里面是不陌生的,很多都有这个接口函数。
功能:在字符串后尾插字符c
int main()
{
string s1("hello world");
s1.push_back('a');
cout << s1 << endl;
return 0;
}
3.4.2 append
相比push_back这个是在字符串后面追加一个字符串
int main()
{
string s1("hello world");
s1.append("hsq");
cout << s1 << endl;
return 0;
}
3.4.3 operator+=
功能:在字符串后面追加字符串str
这里的运算符重载,简直就是完美的设计,用一个运算符重载+函数重载解决了前面push_back和append所做的事情。
功能:在字符串后面追加字符串str
int main()
{
string s1("hello world");
s1 += 'a'; //等价于 s1.push_back('a');
cout << s1 << endl;
s1 += "hsq"; //等价于 s1.append("hsq");
cout << s1 << endl;
return 0;
}
3.4.4 c_str
功能:返回C格式字符串
在我看来string的内置成员就是用c_str来存储字符数组。
int main()
{
string s1("hello world");
const char* arr = s1.c_str();
for (int i = 0; i < s1.size(); i++)
{
cout << arr[i] << " ";
}
return 0;
}
注意:用一个字符数组接收的时候要加const,不加就是权限扩大,那就会报错。
3.4.5find/rfind
find功能:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind功能:从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
有时候,在做oj题目的时候,find可以解决很多棘手的问题,哈哈哈我只能说find大法好。find可以解决需要通过遍历来找到对应的字符。
用find的简单查找方法:
int main()
{
string s1("hsq");
cout << s1.find('q') << endl;
cout << s1.rfind('q') << endl;
return 0;
}
复杂的查找方法:
for (int i=0;i<s1.size();i++)
{
if (s1[i] == 'q')
{
cout << i << endl;
}
}
3.4.6 substr
功能:在str中从pos位置开始,截取n个字符,然后将其返回。
如果没有第二个位置的参数,那么默认截取是从pos位置开始到结束
int main()
{
string s1("hello world");
cout<<s1.substr(2)<<endl; //从2这个位置开始往后到结束全部截取并返回
cout<<s1.substr(1, 2)<<endl; //从1这个位置后两位进行截取并返回
return 0;
}
3.5string非成员函数
3.5.1 operator+
功能:运算符重载,加法运算,尽量少用,传值返回,导致深拷贝效率低。
#include<iostream>
using namespace std;
int main()
{
string a("hsq");
cout << a + "hsq" << endl; // +字符串
cout << a + 'a' << endl; // +字符
string b("hello world");
cout << (a + b) << endl; //+string对象
return 0;
}
3.5.2 operator>>
功能:输入运算符重载
int main()
{
string a;
cin >> a; //读到空格停止
cout << a << endl;
return 0;
}
3.5.3 operator<<
功能:输出运算符重载
int main()
{
string a;
cin >> a; //读到空格停止
cout << a << endl;
return 0;
}
3.5.4 getline
功能:获取一行字符串
为什么有getline?用cin不就好了?
cin特性是当读到空格就会停止,那么当需要包括空格之外的一行都要读,那么就得用getline了。
#include<iostream>
#include<string> //注意这个要加string这个头文件
using namespace std;
int main()
{
string s1;
getline(cin, s1);
cout << s1 << endl;
return 0;
}
3.5.5 relational operators
功能:比较大小
实际运用的是>、>=、<、<=这些符号,比较正确就是返回1,不正确就是返回0
int main()
{
string a("123");
string b("124");
cout <<( a > b) << endl;
return 0;
}