string叫串,是一个管理字符数组的类,其实就是一个字符数组的顺序表,通过成员函数对字符串进行增、删、查、改。
C++标准库里面的东西都在std这个命名空间中。
int main() { string s1; std:: string s2; std::string name("xsq"); cout << name << " " << "and" << " "; name = "lsw"; cout << name << endl; return 0; }
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
字符串转整形数字
字符串相加
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本 都使用string类,很少有人去使用C库中的字符串操作函数。
string类的文档介绍
1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;
学习STL不要想着每个都掌握,不然会很难受,我们掌握最常用的,剩下的我们知道string可以怎么怎么样,用的时候查文档。
int main() { string s1; //构造空的string类对象,即空字符串 string s2("hello"); //用C-string来构造string类对象 string s3(10, '*'); //n个字符去初始化 string s3(s2); //拷贝构造 return 0; }
拷贝一个字符对象的内容,从pos个位置开始,拷贝npos个字符。npos是string这个类里面的一个静态成员变量,这个地方不是-1,反而是整型的最大值。
int main() { //字符串大小的比较 string s1("hello world"); string s2(s1, 6, 5); cout << (s1 == s2) << endl; cout << (s1 > s2) << endl; return 0; }
和C语言一样,比较字符串的大小都是按照ASCI码比较。
int main() { string s1("hello world"); string s2(s1, 6);//后面没给参数,表示的是一个很大的值 cout << s2 << endl; return 0; }
https://cplusplus.com/reference/string/string/string/ 中https:是网络协议,cplusplus.com是域名
这三个是运算符重载,同时这三个函数又构成函数重载。
int main() { string s1; string s2; s1 = s2; s1 = "1111"; s2 = '1'; return 0; }
字符串的增:
如果我们想尾插一个字符,可以调用上面的成员函数。
如果我们想尾插一个字符串,可以调用上面的成员函数。
int main() { string s1("hello"); //尾插一个字符 s1.push_back(' '); //尾插一个字符串 s1.append("world"); cout << s1 << endl; return 0; }
string的好处是空间不够,push_back、append可以扩容。
+=可以+=一个字符,+=可以+=一个字符串,+=可以+=一个string对象。
int main() { string s1("hello"); s1 += ' '; s1 += "world"; cout << s1 << endl; return 0; }
本质还是尾插,只是在string类中重载了+=,operator+=一个字符,就是调用push_back, operator+=字符串,就是调用append。
把x转成string对象:
int main() { //要求x转成string对象 size_t x = 0; //cin >> x; string xstr; while (x) { size_t val = x % 10; xstr += ('0' + val); x /= 10; } cout << xstr << endl; return 0; }
字符是存储的ASCII码,数字1对应的ASCII码不是字符1,需要加上字符'0'才是字符'1'的ASCII码。
.sln是打开项目的额文件。
但是上述的结果不符合我们的需求,我们还需要逆置一下,这里就不做过多讲解。
这里重载了一个运算符operator[],重载了一个[],[]这个运算符本质是一种解引用,是数组来访问它的数据的。重载[]之后string字符串可以当作数组使用。
int main() { string s1("hello"); //字符串的修改 for (size_t i = 0; i < s1.size(); i++) { s1[i]++; } s1[0]--; for (size_t i = 0; i < s1.size(); i++) { cout << s1[i]; } cout << endl; return 0; }
迭代器是如何访问的呢?迭代器是像指针一样的东西,但不一定是指针。迭代器是增加一种访问的方式,迭代器的代码是怎么写的,任何容器的迭代器都是这样的,下节课我们会学string的底层,其实string底层就是指针。
begin对于任何的容器都是第一个位置的迭代器,
下面这两句代码看似相同,实际上底层有很大差别。
s3[1]; //*(s3+1); s1[1]; //s1.operator[](1);
int main() { //string对象的两种实例化方式 string s0; string s1("hello"); //遍历string的三种方式 cout << s1 << endl; //下标 + [],用于string和vector cout << s1.size() << endl;//size()不算字符串末尾的\0 //\0不算有效字符,\0是一个特殊字符,标识字符串结束的特殊字符 //有些地方\0不会显示的打印出来,其实已经访问到了 for (size_t i = 0; i < s1.size(); i++) { cout << s1[i]; } cout << endl; //迭代器 string::iterator it = s1.begin(); while (it != s1.end())//end返回的是最后一个数据的下一个位置,end指向的是\0 { (*it)--; it++; } //迭代器也可以修改数据 it = s1.begin(); while (it != s1.end()) { cout << *it; it++; } cout << endl; //第三种访问string的方式,范围for,自动迭代,自动判断结束 //范围for从底层的角度来说,底层是替换为迭代器 for (auto e : s1)//依次取s1的数据赋值给给变量e,这个变量自动推导出类型char { cout << e;//这里的e是*it的拷贝 } cout << endl; //其实也可以下面这样写 for (char e : s1) { cout << e; } cout << endl; for (auto& e : s1) { e++;//这里的e是*it的别名 } cout << endl; //根本就没有范围for,实际底层都是迭代器,有了迭代器才有了范围for //任何容器都支持迭代器,并且用法是类似的 vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); vector<int>::iterator vit = v.begin(); while (vit != v.end()) { cout << *vit; vit++; } cout << endl; //下面这两个是函数模板 reverse(s1.begin(), s1.end()); reverse(v.begin(), v.end()); //范围for只支持顺着遍历,不支持倒着遍历 for (auto e:s1) { cout << e; } cout << endl; for (auto e: v) { cout << e; } cout << endl; sort(v.begin(), v.end()); for (auto e : v) { cout << e; } return 0; }
总结:iterator提供了一种统一的方式访问和修改容器的数据,这种统一的方式可以去访问简单的数组,稍微复杂的链表、更复杂的树、哈希表等等这样的结构。
是链表、树啥的都可以逆置。
sort要求传的还是迭代器,链表不支持用sort。
迭代器除了提供普通迭代器,还提供了反向迭代器,就是有时候我们需要倒着遍历。
rbegin()在最后一个位置,rend()在第一个数据的前一个位置。范围for不能倒着遍历,只有反向迭代器能倒着遍历。
int main() { string s1("hello world"); //有了迭代器后,auto的价值就更明显了 //string::reverse_iterator vit = s1.rbegin(); auto vit = s1.rbegin(); while (vit != s1.rend()) { //cout << *vit; //vit++; //也可以修改数据 *vit += 3; //cout << *vit; //vit++; } cout << endl; return 0; }
//下面的代码编不过 void Func(const string& s) { string::iterator vit = s.begin(); while (vit != s.end()) { cout << *vit; vit++; } } int main() { string s1("hello world"); Func(s1); return 0; }
const对象不能用普通的迭代器,要用const迭代器。
这相当于一个权限的放大问题,普通迭代器可以读和写,const迭代器只能读不能写。
//下面这样就可以了 void Func(const string& s) { string::const_iterator vit = s.begin(); while (vit != s.end()) { cout << *vit; vit++; } } int main() { string s1("hello world"); Func(s1); return 0; }
int main() { //size和length的返回结果是一样的,size的意思是数据个数,length是长度的意思。 string s1("hello world"); cout << s1.size() << endl; cout << s1.length() << endl; return 0; }
大家可以发现,string不在容器这个位置上,严格来说,string不属于STL,属于标准库,STL属于标准库的一部分,string属于标准库,string产生的比较早。string最开始定义的接口是length,STL出来以后被迫加了一个size,顺序表、string、甚至链表都可以叫长度。
返回字符串的最大长度。不同的编译器或者不同版本的编译器得到的结果是不一样的,不稳定。
capacity可以看容量。
同样的代码,在Linux下的capacity是11。
clear是清理数据的意思,clear会让size改变为0,不能让capacity改变。
会对我们的容量进行改变:
这里要插入字符串就不会扩容了,空间已经开好了。reserve的应用场景:提前知道要开多少空间,提前把空间开好。
int main() { string s2; s2.reserve(200); cout << s2.capacity() << endl; return 0; }
vs下给出的空间会比提前开的空间大,在Linux下开多少,空间就是多少。
假如reserve100个空间,它可能给的不是100,可能会比100大,而且如果显示的是空间大小是111,它的实际大小会是112,\0也会占用一个空间。如果我们用clear清理数据,再reserve个空间,那么它的容量就会缩小。
reserve是单纯的开空间,也就是对空间进行改变,resize是开空间+填值初始化,初始化的值是"\0"。
空间是不会轻易的缩小,如果缩容也是开辟一块新的空间,把原有的数据部分拷贝到新的空间中,把原来的空间释放掉。
at跟[]的功能是一样的,但是我们平常不太使用,at是早些时候没有支持运算符重载的时候提供的。at如果访问的数据超出会抛异常,[]是断言,这两个读和写都会报错。
int main() { string s1("hello world"); s1.at(0) = 'x'; cout << s1 << endl; return 0; }
assign是一个赋值的意思,就是我对你覆盖了。
int main() { string s1("hello world"); s1.append("sssssss"); cout << s1 << endl; s1.assign("1111111"); cout << s1 << endl; return 0; }
int main() { string s1("hello world"); s1.append("sssssss"); cout << s1 << endl; s1.assign("1111111"); cout << s1 << endl; s1.insert(0, "hello"); cout << s1 << endl; s1.insert(0, 10, 'x'); cout << s1 << endl; s1.insert(s1.begin(), 10, 'y'); cout << s1 << endl; s1.insert(s1.begin() + 10, 10, 'z'); cout << s1 << endl; return 0; }
insert虽然好,但是也不要多用,因为有效率的问题。
int main() { string s1("hello world"); s1.erase(5, 1); cout << s1 << endl; s1.erase(5); cout << s1 << endl; string s2("hello world"); s2.erase(s2.begin()); cout << s2 << endl; return 0; }
int main() { string s1("hello world hello bit"); s1.replace(6, 5, "xxxxxxxxxxx"); cout << s1 << endl; //讲s2中的空格替换为20% string s2("hello world hello bit"); string s3; for (auto ch : s2) { if (ch != ' ') { s3 += ch; } else { s3 += "20%"; } } s2 = s3; cout << s2 << endl; return 0; }
int main() { string s1("hello world hello bit"); s1.replace(6, 5, "xxxxxxxxxxx"); cout << s1 << endl; //讲s2中的空格替换为20% string s2("hello world hello bit"); string s3; for (auto ch : s2) { if (ch != ' ') { s3 += ch; } else { s3 += "20%"; } } s2 = s3; //这两个打印的结果虽然一样,但是第一个调用的是string中重载的流插入和流提取 //第二个是识别成char* ,是调用库里面的对这个的支持 cout << s2 << endl; cout << s2.c_str() << endl; return 0; }
c_str是更好的跟C的一些接口进行配合,C++要兼容C,你需要用某些库,不排除那个库是用C语言写的,比如做个简单的比方,C++里面有些要访问数据库的时候,它会给你提供一些api,方便去访问、连接数据库等等,对于C/C++它没有专门去访问C++的版本,它只提供了一个C的版本,它的这个C版本,如果你是纯C的项目可以用,C++的项目也可以用。
这两个还是字符,只是下标为0和1的构成“比”,下标为2和3的构成“特”。