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的头文件

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端构建之CERT_HAS_EXPIRED和certificate has expired解决方案

问题 2024年 1 月 22 日&#xff0c;淘宝原镜像域名&#xff08;registry.npm.taobao.org&#xff09;的 HTTPS 证书正式到期。如果想要继续使用&#xff0c;需要将 npm 源切换到新的源&#xff08;registry.npmmirror.com&#xff09;&#xff0c;否则会报错。 报错信息为&a…

Consul服务注册与发现 Consul配置步骤

Consul服务注册与发现 Consul配置步骤 consul下载地址 Install | Consul | HashiCorp Developer 启动需要在 下载好的文件夹里 用cmd 运行consul agent -dev启动consul Consul配置 配置pom <!--SpringCloud consul config--> <dependency><groupId>org…

【leetcode】回文子串 动态规划

/*** param {string} s* return {number}*/ var countSubstrings function(s) {let dpnew Array(s.length).fill().map(()>new Array(s.length).fill(false));let num0;for(let i0;i<s.length;i){for(let j0;j<i;j){//在首尾相等时&#xff0c;如果长度时1或者2&…

C++笔记(三)--- 函数重载

目录 子类继承父类重载 类成员函数重载 继承和组合的三种方式请看我上一篇文章 C笔记&#xff08;二&#xff09;--- 继承和组合-CSDN博客 子类继承父类重载 当子类继承父类之后&#xff0c;子类重新定义了一个和父类完全相同函数名称的函数时&#xff0c;会将父类所有相同…

Pegasus智能家居套件样例开发--软定时器

样例简介 此样例将演示如何在Pegasus Wi-Fi IoT智能家居套件上使用cmsis 2.0 接口进行定时器开发。 工程版本 系统版本/API版本&#xff1a;OpenHarmony 3.0 releaseIDE版本&#xff1a;DevEco Device Tool Release 3.0.0.401 快速上手 准备硬件环境 预装windows系统的PC…

『大模型笔记』RAG应用的12种调优策略指南

RAG应用的12种调优策略指南 文章目录 一. 概要二. 数据索引2.1. 数据清洗2.2. 分块2.3. 嵌入模型2.4. 元数据(或未向量化的数据)2.5. 多索引2.6. 索引算法三. 推理阶段(检索和生成)3.1. 检索参数3.2. 高级检索策略3.3. 重新排序模型3.5. 大语言模型(LLM)

26、Qt调用.py文件中的函数

一、开发环境 Qt5.12.0 Python3.7.8 64bit 二、使用 新建一个Qt项目&#xff0c;右击项目名称&#xff0c;选择“添加库” 选择“外部库”&#xff0c;点击“下一步” 点击“浏览”&#xff0c;选择Python安装目录下的libs文件夹中的“python37.lib”文件&#xff0c;点击“下…

退休开便利店真的靠谱吗?2024比较赚钱的创业项目排行

近日多个退休后开便利店赚钱的新闻登上热搜&#xff0c;但是&#xff0c;小编对此有疑问&#xff0c;退休的老年人开便利店真的是一个好选择吗&#xff1f; 第一、便利店最基本的转让费&#xff0c;装修费&#xff0c;进货等等&#xff0c;这笔开支非常大&#xff0c;足以掏空老…

H5下拉刷新分页

对于分页需求&#xff0c;分页数据的请求触发十分重要&#xff0c;监听滑动到底的触发也有很多种。 1.IntersectionObserver监听 IntersectionObserver 接口&#xff08;从属于 Intersection Observer API&#xff09;提供了一种异步观察目标元素与其祖先元素或顶级文档视口&a…

终结数据混乱!开发者必学的GraphQL秘籍,高效API只需一步

在数字世界中&#xff0c;API就如同城市中的道路&#xff0c;连接着各种服务和数据。然而&#xff0c;传统的API&#xff08;如RESTful&#xff09;虽然功不可没&#xff0c;但随着技术复杂性和需求多样性不断攀升&#xff0c;它们显露出的局限性也呼唤着新的可能出现。此时&am…

Unity中,activeInHierarchy 和 activeSelf

activeInHierarchy&#xff1a; activeInHierarchy 属性表示游戏对象是否在场景中处于激活状态&#xff0c;并且是否在层次结构中的激活状态。它考虑了游戏对象以及其所有父对象的激活状态。如果 activeInHierarchy 为 true&#xff0c;表示该对象在场景中处于激活状态且其所有…

LaMa Image Inpainting 图像修复 Onnx Demo

目录 介绍 效果 模型信息 项目 代码 下载 LaMa Image Inpainting 图像修复 Onnx Demo 介绍 gihub地址&#xff1a;https://github.com/advimman/lama &#x1f999; LaMa Image Inpainting, Resolution-robust Large Mask Inpainting with Fourier Convolutions, WAC…

《PyTorch深度学习实践》第十三讲RNN进阶

一、 双向循环神经网络&#xff08;Bidirectional Recurrent Neural Network&#xff0c;BiRNN&#xff09;是一种常见的循环神经网络结构。与传统的循环神经网络只考虑历史时刻的信息不同&#xff0c;双向循环神经网络不仅考虑历史时刻的信息&#xff0c;还考虑未来时刻的信息…

wireshark过滤和tcpdump抓包指令

Wireshark 过滤器的表达式&#xff0c;用于过滤源 IP 地址为 10.184.148.247 并且目标 TCP 端口为 1883 的数据包。启用抓包后过滤 ip.addr 10.184.148.247 && tcp.port 1883 主机位10.184.148.19和目标端口为 8080 的操作目标 抓包前过滤 host 10.184.148.19 &…

软件说明书怎么写?终于有人一次性说清楚了!

每次写软件说明书&#xff0c;你是不是总是毫无头绪&#xff0c;不知道从何下手&#xff1f;到各网站找资料&#xff0c;不仅格式不规范&#xff0c;甚至可能遗漏关键内容&#xff01;挨一顿批不说&#xff0c;还浪费大把时间。别着急&#xff0c;编写软件说明书&#xff0c;关…

PostgreSQL开发与实战(2)常用命令

作者&#xff1a;太阳 1、连库相关 #连库 $ psql -h <hostname or ip> -p <端口> [数据库名称] [用户名称] #连库并执行命令 $ psql -h <hostname or ip> -p <端口> -d [数据库名称] -U <用户名> -c "运行一个命令;"备注&#xff1…

从理论到落地,大模型评测体系综合指南

1956年夏&#xff0c;“人工智能” 这一概念被提出。距今已有近70年的发展历史。中国科学院将其划分为六个阶段&#xff1a;起步发展期&#xff08;1956年—1960s&#xff09;&#xff0c;反思发展期&#xff08;1960s-1970s&#xff09;,应用发展期&#xff08;1970s-1980s),低…

SpringBoot集成Activiti案例

前言 Activiti项目是一项新的基于Apache许可的开源BPM平台&#xff0c;从基础开始构建&#xff0c;旨在提供支持新的BPMN 2.0标准&#xff0c;包括支持对象管理组&#xff08;OMG&#xff09;&#xff0c;面对新技术的机遇&#xff0c;诸如互操作性和云架构&#xff0c;提供技…

3.1log | 62.不同路径,63. 不同路径 II,343. 整数拆分,96.不同的二叉搜索树

62.不同路径 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m,vector<int>(n,0));for(int i0;i<n;i) dp[0][i]1;for(int i0;i<m;i) dp[i][0]1;for(int i1;i<m;i){for(int j1;j<n;j){dp[i][j]dp[i][j-1]dp[i-…

c++八股文:c++编译与内存管理

文章目录 1. c内存管理2. 堆与栈3.变量定义与生命周期4.内存对齐5.内存泄露6.智能指针7.new 和 malloc 有什么区别8.delete和free的区别9.什么野指针&#xff0c;怎么产生的&#xff0c;如何避免野指针10.野指针和指针悬浮的区别11.字符串操作函数参考 1. c内存管理 c在运行程…