前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >探索C/C++的奥秘之string类

探索C/C++的奥秘之string类

作者头像
用户11290648
发布2024-11-21 16:19:40
发布2024-11-21 16:19:40
440
举报
文章被收录于专栏:学习学习

string叫串,是一个管理字符数组的类,其实就是一个字符数组的顺序表,通过成员函数对字符串进行增、删、查、改。

C++标准库里面的东西都在std这个命名空间中。

int main() { string s1; std:: string s2; std::string name("xsq"); cout << name << " " << "and" << " "; name = "lsw"; cout << name << endl; return 0; }

1. 为什么学习string类?

1.1 C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

1.2 两个面试题(暂不做讲解)

字符串转整形数字

字符串相加

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本 都使用string类,很少有人去使用C库中的字符串操作函数。

2. 标准库中的string类

2.1 string类(了解)

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;

2.2 string类的常用接口说明(注意下面我只讲解最常用的接口)

学习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的构成“特”。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-11-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 为什么学习string类?
    • 1.1 C语言中的字符串
    • 1.2 两个面试题(暂不做讲解)
  • 2. 标准库中的string类
    • 2.1 string类(了解)
    • 2.2 string类的常用接口说明(注意下面我只讲解最常用的接口)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档