C++:String类的使用

     

                                           创作不易,感谢三连!! 

      在C语言中,我们想要存储字符串的话必须要用字符数组

char str[]="hello world"

       这其实是将在常量区的常量字符串拷贝到数组中,我们会在数组的结尾多开一个空间存储\0,这样我们如果想在访问的时候,比如打印,我们总是认为这个字符串是会读取到\0结束的

但是过于依赖\0也会有一系列的问题:

1、如果我是一个很长的字符串,但是中间有几个/0,那么我很难直接打印出来全部的字符串,因为访问到\0就会卡住

2、如果我们想通过键盘输入hello world,我们把它当成一个字符串,但是cin和scanf会默认访问到第一个空格或者是换行符就结束。

也就是说,我们的字符串如果有空格,那么还得分批次打印……

3、如果我想减少、增加、修改……这个字符串中的某些字符,也十分麻烦……如果是静态字符数组,会面临空间不够的问题,如果是动态字符数组,那么我们还要时刻注意空间的管理,稍不留神就会越界访问。

 4、虽然C语言中提供了一系列的str类的库函数,但是这些库函数都是以字符串分离开的,没有把该字符串作为一个整体,并且也容易受到\0的影响。这并不符合C++面向对象的思想。

      基于此,我们的祖师爷在想,能不能把string封装成一个类,把它像顺序表一样管理起来,给他增设一些常用的比如增删查改的函数接口?针对扩容进行检查?利用构造函数和析构函数帮助我们管理内存呢??因而我们的string类就诞生了!!

一、标准库中的string类

想要学习strling类,就要去通过他的文档去了解

string类的文档介绍

       诶!!我们发现string类竟然是一个叫做basic_strling的类模版生成的?难道string类还能有其他版本??没错!!string类有很多版本

 为什么string要有这么多版本呢??原因是这样的:

C语言最早都是由老美发明的,他们研究出了ascii码表

       为什么要发明这个东西呢??因为我们的语言和计算机的语言是不一样的,计算机只能看得懂二进制的语言,所以我们为了能让计算机看懂我们的语言,我们必须要给一个映射表, 比如我们想打印apple,那么计算机通过这个ASCII码表来匹配,比如a对应的是97 p对应的是112 l对应的是108 e对应的是101.我们通过调试转出10进制是可以验证这个结果的。

      一个字节是8个比特位,所以最多其实可以有256个表示的方法。而美国的英文字母才26个,哪怕算上大写,算上数字,算上一些符号,128个,也就是7bit就足够了!!所以ASCII码在使用英文的国家是非常友好的,每个字节都可以存储一个字符,这样就都可以表示出来。

      但是老美也想把技术推广到其他国家啊!!比如中国汉字特别多,如果全部像ASCII码表一样都按照一个字节的方式,那汉字远远不止256个啊!

      随着计算机的发展,不同国家也出现了很多字符编码,但是由于字符编码不同,计算机在不同国家之间的交流变得很困难,经常会出现乱码的问题,比如:对于同一个二进制数据,不同的编码会解析出不同的字符

     当互联网迅猛发展,地域限制打破之后,人们迫切的希望有一种统一的规则, 对所有国家和地区的字符进行编码,于是 Unicode 就出现了 

 unicode又有三个版本:

1、UTF-8

 UTF-8: 是一种变长字符编码,被定义为将码点编码为 1 至 4 个字节,具体取决于码点数值中有效二进制位的数量

UTF-8 的编码规则:

  1. 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的, 所以 UTF-8 能兼容 ASCII 编码,这也是互联网普遍采用 UTF-8 的原因之一
  2. 对于 n 字节的符号( n > 1),第一个字节的前 n 位都设为 1,第 n + 1 位设为 0,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码

 2、UTF-16

UTF-16 也是一种变长字符编码, 这种编码方式比较特殊, 它将字符编码成 2 字节 或者 4 字节

具体的编码规则如下:

  1. 对于 Unicode 码小于 0x10000 的字符, 使用 2 个字节存储,并且是直接存储 Unicode 码,不用进行编码转换
  2. 对于 Unicode 码在 0x10000 和 0x10FFFF 之间的字符,使用 4 个字节存储,这 4 个字节分成前后两部分,每个部分各两个字节,其中,前面两个字节的前 6 位二进制固定为 110110,后面两个字节的前 6 位二进制固定为 110111, 前后部分各剩余 10 位二进制表示符号的 Unicode 码 减去 0x10000 的结果
  3. 大于 0x10FFFF 的 Unicode 码无法用 UTF-16 编码

 3、UTF-32

      UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 码即可,不需要任何编码转换。虽然浪费了空间,但提高了效率。

       UTF-8、UTF-16、UTF-32 是 Unicode 码表示成不同的二进制格式的编码规则,同样,通过这三种编码的二进制表示,也能获得对应的 Unicode 码,有了字符的 Unicode 码,按照上面介绍的 UTF-8、UTF-16、UTF-32 的编码方法 就能转换成任一种编码了

    总结:由于UTF-8兼容ASCII,这是他的最大优势,并且在我们日常编程中很多情况只需要一个字节就可以,虽然字节存储的时候需要用一些比特位来区分该字符是几个字节的,但是从效率上来说,他可以根据具体的情况去决定用几个字节去存储,所以互联网大多采用UTF-8,而UTF-16也是变长,他的起点就是两个字节,两个字节其实可以表示完大部分国家的字符了,只有极少数古老的文字可能会需要用到4个字节。UTF-32就很粗暴,无论什么都是用4个字节,所以足够容纳所有的Unicode字符,虽然浪费了空间,但是不需要任何的编码转换,效率会比较高。但是使用得很少,在C11的时候引入了u32string。

 简单介绍GBK:

    但是微软使用的主要还是GBK,Windows支持GBK的时候UTF-8还没有普及,而微软是一家及其看重存量客户和兼容性的公司,形成了路径依赖不能轻易改变。

     GBK是大部分用两个字节表示,一些比较生僻的用三个字节表示

并且一般都是把读音类似的编在一起

      以上只是浅浅了解为什么string有这么多个版本,下面开始研究string类!!

二、string类的接口说明

2.1 string对象常见构造(constructor)

 我们研究几个比较重要的

1、string()

构造空的string对象,即空字符串

2、string(const char* s)

用C-string来构造string类对象

 其实相当于将常量字符串拷贝到str中

3、string(const string&s) 

拷贝构造函数

 

拷贝构造其实是深拷贝,因为str1和str2指向的是不同的空间

4、string(size_t n, char c)

意思是用几个相同的字符去构造

5、string (const string& str, size_t pos, size_t len = npos);

意思是,给一个string类,读取从他的第pos个位置开始往后的len个字符。

如果len给大了,就全部打印完

 我们还注意到len如果不给,会给一个缺省值nops,nops是什么呢?

 我们可以看到npos是string类里面的一个静态成员变量,他的值是-1,但是由于是size_t类型,所以转化成无符号整型是一个特别大的数

所以如果我们不传的话,也是直接拷贝完。跟传太大是一样的

 6、string (const char* s, size_t n);

 

 给一个常量字符串,从第一个位置拷贝到第n个位置

总结: 5和6易混淆,第一个传str类,然后从第pos个位置开始拷贝len个字符,第二个是传常量字符串,从第一个位置拷贝到n位置。

 2.2 string类对象的容量操作(Capacity)

 1、size和length

size和length其实是一样的, 都代表字符串的长度,但是早期STL还没出现的时候,strling类用的是length,但是后来STL出来后,里面大部分都是用的size,所以为了保持一致性又造了一个size出来,平时用哪个都可以的。

 2、capacity

表示string当前的容量,一般来说是默认不算上/0

 这边其实是16,但是capacity不会吧/0算进去。相当于capacity是实际容量-1

那string类究竟是如何扩容的呢?我们来用一段代码观察一下:

 不同编译器存在差异,因为用的STL版本可能不一样,我们看看g++

对比后我们可以看到区别:

    vs用的是PJ版STL,字符串会先被存在_Bx数组中,超过之后才会去动态开辟空间,第一次扩容是从32开始进行1.5倍扩容。而g++是SGI版STL,直接就是从0开始,然后根据情况直接开始扩容,从0开始进行2倍扩容

3、reverse

作用是提前去增加容量,扩容的效率是很低的,所以如果我们知道这个string类最多需要空间,我们一次性开好,可以减少拷贝。如果是减少的话,不会缩容。

他根据1.5倍的扩容规则,至少会扩容到超过你的要求。如果是g++的话,就是刚好到你要求的

 4、resize

会去改变size,减少的话就是变少(不会改变容量),如果增多的话就可能会扩容顺便帮助我们初始化,第一个版本的话初始化补\0,第二个版本的话就是补自己想要初始化的内容

 5、empty

 字符串为空返回1,不为空返回0

6、clear

 清楚字符串,变成空字符串(不会缩容)

7、shrink_to_fit

 意思是缩容到刚好够容纳他的有效字符个数

如上图一开始的空间是32,然后我们将size变成5,然后进行缩容到刚好够容纳他的大小

 所以前面的resize和reverse如果减少的话,都是不会改变容量大小的!!clear也不会

7、小总结

1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一
致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用\0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于
string的底层空间总大小时,reserver不会改变容量大小。

5.缩容的shrink_to_fit尽量不要用,意义不大,而且任何一个接口都不会帮我们缩容

2.3 string类对象的访问及遍历操作(Iterators

1、operator[ ] / at
 

访问下标为pos的字符,at和他是一样的功能 

 2、begin+end

获取一个字符的迭代器+获取最后一个字符的迭代器,要用到iterator(迭代器)

可以把迭代器理解成指针,begin是开始的指针,end是指向\0的指针,注意是左闭右开!! 

如果是用const类型的迭代器,就不能修改 

至此我们知道了三种可以访问的字符串的方法: 1、下标遍历 2、迭代器遍历 3、范围for

3、rbegin+rend

跟上面差不多,只不过是从尾开始访问

有没有感觉string::reverse_iterator有点难写,那用auto是不是更香

 2.4 string类对象的修改操作(Modifiers)

1、push back

尾插一个字符

2、 string::operator+= 

 3.append

(1) string& append (const string& str);

直接尾插一个str对象

(2)string& append (const string& str, size_t subpos, size_t sublen);

从这个字符串的subpos位置往后的sublen个字符尾插

(3)string& append (const char* s)

尾插一个字符串

(4)string& append (const char* s, size_t n);

尾插字符串中的前n个字符

 (5)string& append (size_t n, char c);

尾插n个字符

 一般来说我们更喜欢用+=,除非是一些特殊情况才会去用append

4,insert 

(1) string& insert (size_t pos, const string& str)和string& insert (size_t pos, const char* s);

从第pos个位置开始插入字符

(2)string& insert (size_t pos, const string& str, size_t subpos, size_t sublen)

是从被插入字符串中的subpos位置往后选len个字符插入

(3) string& insert (size_t pos, const char* s, size_t n);

是从被插入字符串中选前n个字符插入

(4)string& insert (size_t pos, size_t n, char c);

从第pos个位置开始插入n个相同字符

5.erase 

 从pos位置开始往后删除len个字符,不穿nops默认就pos后面全删

一般来说insert和erase都可能设计到大量数据的移动,所以不建议使用!! 

6,pop_back

尾删一个字符

7,replace

(1)string& replace (size_t pos, size_t len, const string& str)和string& replace (size_t pos, size_t len, const char* s);

将pos位置开始后面的len个字符替换成别的字符串

 (2)string& replace (size_t pos, size_t len, const string& str, size_t subpos, size_t sublen);

是从被替换字符串中的subpos位置往后选sublen个字符插入

(3)string& replace (size_t pos, size_t len, const char* s, size_t n);

是从被替换字符串中选前n个字符插入

 (4)string& replace (size_t pos, size_t len, size_t n, char c);

替换n个相同字符

8.swap

交换两个字符串

思考:

明明全局swap也可以达到交换的效果,那string里面也实现一个swap的成员函数有必要吗?? 

 

综上,要尽量使用成员函数的swap 

2.5 string类对象的操作(operations)

1、c_str(重点)

 返回一个指向C类型的字符串指针,下面介绍他的用处:

     我们可以观察到,s1.c_str()返回的其实是一个char*指针,但是为什么打印出来的不是地址呢??因为cout可以自动识别类型,对于char*类型的指针他会把它当成是字符串去处理,只要指针不是char*类型的,都会当成打印地址。

     我们会发现,当我们尾插‘\0’后再插入一些字符,打印出来的结果就不一样了,因为对于c语言来说,字符串默认是读取到\0停止,但是对于string来说,读取多少是取决于他的成员变size!!

如果string类我们想用C语言的方法处理文件,就可以用c_str 

 2、find

 找一个字符里的子串是否存在,如果存在,返回对应的第一个字符的下标,如果不存在,就会返回string::npos。

(1)(3)(4)版本差不多,区别是一个是找string类,一个是找常量字符串,一个是找字符。pos的缺省值0,不传的话就是从头开始遍历往后找,我们也可以通过pos来缩小查找的范围。

(2)版本就是 找常量字符串从pos位置开始的n个字符

 3、refind

 和find的区别就是默认是pos开始从后往前找

 4.substr

 从pos位置开始截取len个字符返回,不传len就是默认全部返回(经常和find以及rfind配合使用)

比如我们想打印出.com,我们可以先用find去定位'.'然后再打印

 如果我们不知道几个字符,s1.size()-pos刚好是剩下所有的字符,或者就干脆不传,这时候相当于就是把后面的全部打印完

有些时候要从后往前找(rfind)

 如果我们想打印中间怎么办??比如"http://www.cplusplus.com/reference/string/string/find/"我想打印出www.cplusplus.com

5.find_first_of

 找到第一个匹配的子串中任何一个字符的下标

 练习:将Please, replace the vowels in this sentence by asterisks中的abcdef全部换成*

6,find_last_of

找到最后一个与子字符串任意一个字符匹配的下标。其实可以理解从后往前找第一个

 7,find_first_not_of   / find_last_not_of

跟前两个类似,区别就是找第一个不匹配的和找最后一个不匹配的

2.6 string类对象的非成员函数

1,operator+ (string)

尽量少用,因为传值返回导致深拷贝效率很低

2,relational operators (string)

我们可以通过这些重载的操作符来比较字符串!! 

3,operator>>(string)和operator<< (string)

       值得注意的是,从c的字符串数组到c++的string类,原先读取字符串是默认读取到\0,但是封装乘string类后他有了自己的size,所以会根据size去打印,因此是可以打印出\0的,但是>>还是跟之前的scanf一样,默认以换行或者是空格作为标识,如果我们想打印出有空格的字符串,是行不通的!!

 因此我们想要流插入有空格的字符串,就得用getline

4.getline

 注意要包含string的头文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/419790.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

52.2k star! 自己部署gpt4free, 免费使用各种GPT

GPT4Free是一个由开发者Xtekky在GitHub上发布的开源项目&#xff0c;它可以免费地使用GPT-3.5、GPT-4、llama、gemini-pro、bard、claude等多种大模型。截止到当前(2024.1.30)已经有52.2k star&#xff0c;可见其受欢迎程度。 github地址&#xff1a;https://github.com/xtekky…

如何解决代理ip服务器连接问题

在当今的数字化时代&#xff0c;互联网连接已成为生活和工作中不可或缺的一部分。然而&#xff0c;在尝试访问互联网资源时&#xff0c;用户有时会遇到“代理服务器可能有问题&#xff0c;或地址不正确(你尚未连接)”的错误提示。这种情况通常表明计算机的网络设置存在问题&…

根据二层封装协议决定—网络类型

目录 一、网络类型的分类 二、数据链路层协议 MA网络 以太网协议 P2P网络 一、网络类型的分类 P2P --- point to point --- 点到点网络 MA --- Multi-Access Network --- 多点接入网络 BMA --- Broadcast Multi-Access Network --- 广播型多点接入网络 NBMA --- Non-Bro…

云计算 2月20号 (认识操作系统)

1、认识操作系统 计算机系统的组成 知识点1&#xff1a;没有软件系统的计算机称之为"裸机" 知识点2&#xff1a;裸机提供基本的可计算性资源 知识点3&#xff1a;操作系统是最靠近硬件的软件层&#xff0c;负责管理和控制计算机硬件。 计算机硬件组成五大部件 运算器…

Variant AutoEncoder(VAE)和 VQVAE 学习笔记和代码

参考&#xff1a; [1] VAE1 [2] https://lilianweng.github.io/posts/2018-08-12-vae/ [3] VAE Code 进食顺序 1 VAE1.1 VAE的直观理解1.2 VAE数学推导1.2.1 混合高斯模型角度理解VAE&#xff08;李宏毅ML课的说法&#xff09;1.2.2 隐空间角度理解以及ELBO&#xff08;变分下界…

登录页设计新选择:毛玻璃和新拟态风格,非2.5D和插画风

登录页给潜在用户传递了产品的品牌调性&#xff0c;是非常重要的一类页面&#xff0c;之前2.5D和插画风格的登录页流行一时&#xff0c;不过这阵风好像过去了&#xff0c;新的风格开始涌现了。 一、越来越流行的毛玻璃设计风格 毛玻璃风格是指将背景模糊处理&#xff0c;使得…

【算法】长短期记忆网络(LSTM,Long Short-Term Memory)

这是一种特殊的循环神经网络&#xff0c;能够学习数据中的长期依赖关系&#xff0c;这是因为模型的循环模块具有相互交互的四个层的组合&#xff0c;它可以记忆不定时间长度的数值&#xff0c;区块中有一个gate能够决定input是否重要到能被记住及能不能被输出output。 原理 黄…

Sophon AutoCV推动AI应用从模型生产到高效落地

随着技术市场和应用方向的逐渐成熟&#xff0c;人工智能与各行各业的结合和落地逐渐进入了深水区。 虽然由于行业规模化和应用普及度的限制&#xff0c;人工智能在“传统”行业的落地不如消费互联网行业&#xff0c;但是借助人工智能为“传统”行业的发展注入新能量一直是相关…

Windows系统x86机器安装龙芯(loongarch64)3A5000虚拟机系统详细教程

本次介绍在window系统x86机器上安装loongarch64系统的详细教程。 1.安装环境准备。 首先&#xff0c;你得有台电脑。 配置别太差&#xff0c;至少4核8G内存&#xff0c;安装window10或者11都行&#xff08;为啥不能是Window7&#xff0c;你要用也不是不行&#xff0c;你先解决…

边缘计算与任务卸载基础知识

目录 边缘计算简介任务卸载简介参考文献 边缘计算简介 边缘计算是指利用靠近数据生成的网络边缘侧的设备&#xff08;如移动设备、基站、边缘服务器、边缘云等&#xff09;的计算能力和存储能力&#xff0c;使得数据和任务能够就近得到处理和执行。 一个典型的边缘计算系统为…

未来已来:智慧餐饮点餐系统引领餐饮业的数字化转型

时下&#xff0c;智慧餐饮点餐系统正在引领着餐饮业迈向更高的位置。今天&#xff0c;小编将与大家共同探讨智慧餐饮点餐系统的发展趋势、优势以及对餐饮业的影响。 一、智慧餐饮点餐系统的发展趋势 智慧餐饮点餐系统的出现填补了这一空白&#xff0c;它通过引入数字化技术&a…

学习助手:借助AI大模型,学习更高效!

在当今的数字时代&#xff0c;人工智能&#xff08;AI&#xff09;的崛起已经彻底改变了我们获取信息、处理数据以及学习新知识的方式。AI大模型&#xff0c;特别是如OpenAI开发的GPT-4这类先进的技术&#xff0c;已成为学习和教育领域的一大助力。本文旨在探索如何借助AI大模型…

5G时代对于工业化场景应用有什么改善

5G 不仅仅是 4G 的技术升级&#xff0c;而是将平板电脑和智能手机的技术升级。除了更好的高清视频流和其他高带宽应用&#xff0c;消费者不会注意到很多性能差异。然而&#xff0c;在工业领域&#xff0c;5G 代表着巨大的飞跃。 在工厂和厂房内&#xff0c; 设备的Wi-Fi 网络经…

Python+Selenium+Unittest 之Unittest1--简介

Unittest属于是一种单元测试框架&#xff0c;主要用于对代码中写好的单元内容进行验证&#xff0c;比如写好一个函数&#xff0c;可以使用unittest去进行验证该函数的代码逻辑是否有问题&#xff0c;对于自动化来说&#xff0c;可以去检验每条用例的内容是否符合预期。 Unittes…

Goose:Golang中的数据库迁移工具

Goose&#xff1a;Golang中的数据库迁移工具 在Golang开发中&#xff0c;数据库迁移是一个常见的任务&#xff0c;用于管理数据库模式的演化和版本控制。Goose是一个轻量级的、易于使用的数据库迁移工具&#xff0c;专为Golang开发者设计。本文将介绍Goose的基本概念、用法和优…

php基础学习之错误处理(其二)

在实际应用中&#xff0c;开发者当然不希望把自己开发的程序的错误暴露给用户&#xff0c;一方面会动摇客户对己方的信心&#xff0c;另一方面容易被攻击者抓住漏洞实施攻击&#xff0c;同时开发者本身需要及时收集错误&#xff0c;因此需要合理的设置错误显示与记录错误日志 一…

代码随想录-回溯算法

组合 //未剪枝 class Solution {List<List<Integer>> ans new ArrayList<>();Deque<Integer> path new LinkedList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n, k, 1);return ans;}public void back…

Python:关于数据服务中的Web API的设计

搭建类似joinquant、tushare类似的私有数据服务应用&#xff0c;有以下一些点需要注意&#xff1a; 需要说明的是&#xff0c;这里讨论的是web api前后端&#xff0c;当然还有其它方案&#xff0c;thrift&#xff0c;grpc等。因为要考虑到一鱼两吃&#xff0c;本文只探讨web ap…

Android之UI Automator框架源码分析(第九篇:UiDevice获取UiAutomation对象的过程分析)

前言 学习UiDevice对象&#xff0c;就需要看它的构造方法&#xff0c;构造方法中有UiDevice对象持有一些对象&#xff0c;每个对象都是我们分析程序的重点&#xff0c;毕竟UiDevice对象的功能&#xff0c;依赖这些组合的对象 备注&#xff1a;当前对象持有的对象&#xff0c;初…

Linux调试器-gdb使用与冯诺依曼体系结构

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 Linux调试器-gdb使用 1. 背景 2. 开始使用 冯诺依曼体系结构 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#xff0c;一种是正在努力学…