深度剖析C++string(上篇)

目录

前言

1.C++ string类

2.string类中的常见构造

3.string类对象的容量操作

4.. string类对象的访问及遍历操作

5. auto和范围for(补充)

auto关键字

范围for

结束语


前言

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

1.C++ string类

C++中的string类是STL的一个重要组成部分,它提供了对字符串的封装和处理功能。 

在使用string类时,必须包含#include头文件以及using namespace std;

下面是参考的官方链接 

http://www.cplusplus.com/reference/string/string/?kw=string

接下来,我们将进一步学习string类。

2.string类中的常见构造

#include <iostream>
#include <string>
using namespace std;
int main() {string s1;string s2("hello world");string s3(6, 'x');string s4(s3);cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;return 0;
}

 

3.string类对象的容量操作

函数名称功能说明

size(重点)返回字符串有效字符长度

length 返回字符串有效字符长度

capacity返回空间总大

void string1() {string s("I love you!!!");cout << s.size() << endl;cout << s.length() << endl;cout << s.capacity() << endl;cout << s << endl;
}

 

在C++的string类中,size()length()成员函数返回的是字符串中字符的数量,不包括结尾的空字符(\0),因为string类内部管理内存时会自动在字符串末尾添加一个空字符,但这个空字符不计入字符串的长度。

capacity()成员函数返回的是字符串当前分配的内存能够容纳的字符数量,这个值通常大于或等于size()的返回值,以容纳未来可能的字符添加操作,而不会立即触发重新分配内存。

 

empty (重点)检测字符串释放为空串,是返回true,否则返回false

clear (重点)清空有效字符

reserve (重点)为字符串预留空间**

resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充 

void string1() {string s("I love you!!!");cout << s.size() << endl;cout << s.length() << endl;cout << s.capacity() << endl;cout << s << endl;// 使用empty()检查字符串是否为空if (s.empty()) {cout << "s is empty." << endl;}else {cout << "s is not empty." << endl;}//s.clear();//cout << s.size() << endl;///cout << s.capacity() << endl;//cout << s << endl;s.resize(18, 'a');//s.resize(6);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;}

 可根据自己的需求自行更改代码。不难发现capacity 的大小会根据字符串的大小变化可能进行相应的变化,变化情况见上capacity的介绍。linux下gcc和VS的capacity变化规则是略有不同的,大家可以下去尝试下。

void string2()
{string s;// 测试reserve是否会改变string中有效元素个数s.reserve(100);cout << s.size() << endl;cout << s.capacity() << endl;// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小s.reserve(50);cout << s.size() << endl;cout << s.capacity() << endl; //输出原来值或更大,不会缩小
}
void TestPushBack() {string s;size_t sz = s.capacity();cout << "capacity: " << sz << '\n';cout << "making s grow:\n";for (int i = 0; i < 100; ++i){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << '\n';}}
}

 

 当把\0加上时,我们会发现刚开始的变化是2倍,后面是1.5倍,这是VS自己规定的变化规则,刚开始会默认开设一个大小为16字节的数组,后面变化的空间是在堆上的。

而gcc运行此代码都是2倍的变化。

注意:

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不会改变容量大小。

vs和g++下string结构的说明(了解),小编也没有理解的通透。

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

vs下string的结构 string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间:

当字符串长度小于16时,使用内部固定的字符数组来存放。 当字符串长度大于等于16时,从堆上开辟空间

 union _Bxty { // storage for small buffer or pointer to larger one

value_type _Buf[_BUF_SIZE];

pointer _Ptr;

char _Alias[_BUF_SIZE];

// to permit aliasing } _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建 好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量 最后:还有一个指针做一些其他事情。 故总共占16+4+4+4=28个字节。

g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个 指针,该指针将来指向一块堆空间,内部包含了如下字段:

空间总大小 字符串有效长度 引用计数

指向堆空间的指针,用来存储字符串 

4.. string类对象的访问及遍历操作

 

【】既可以访问字符又可以修改字符,非常的方便。

void string3() {string s1("I love you");// 获取begin和end迭代器std::string::iterator it = s1.begin();std::string::iterator it_end = s1.end();cout << s1[3] << endl;s1[1] = 'u';cout << s1 << endl;cout << *it << endl; // 输出 I,因为it指向第一个字符cout << *(it_end-1) << endl;}

 上面是正向迭代器的使用,还有一个反向迭代器

std::string::reverse_iterator it = s1.rbegin();
std::string::reverse_iterator it_end = s1.rend();

接下来我们通过迭代器来实现字符串的遍历

void string4() {string s("I love you!");// 获取begin和end迭代器string::iterator it = s.begin();string::reverse_iterator it1 = s.rbegin();while (it != s.end()) {cout << *it << " " ;++it;}cout << '\n';while (it1 != s.rend()) {cout << *it1 << " " ;++it1; // 移动到下一个字符(实际上是前一个字符)}}

 

C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型

void string4() {string s("I love you!");// 获取begin和end迭代器//string::iterator it = s.begin();auto it = s.begin();//string::reverse_iterator it1 = s.rbegin();auto it1 = s.rbegin();while (it != s.end()) {cout << *it << " " ;++it;}cout << '\n';while (it1 != s.rend()) {cout << *it1 << " " ;++it1; // 移动到下一个字符(实际上是前一个字符)}
}

范围for

	for (auto ch : s){cout << ch << endl;
}

5. auto和范围for(补充)

auto关键字

在这里补充2个C++11的小语法。

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得。

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

auto不能直接用来声明数组

// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };

#include <iostream>
using namespace std;
int func1()
{return 10;
}
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{return 3;
}
int main() {return 0;
}

#include <iostream>
using namespace std;
int func1()
{return 10;
}int main() {int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项//auto e;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;return 0;
}

 其实对于auto的真正常用的用法是用于迭代器方面,让编译器自己推导迭代器类型,对于复杂的迭代器类型可以简化代码。

下面展示的代码为扩展,别处拷贝过来。

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} };// auto的用武之地//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}return 0;
}

std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };:定义一个std::map对象dict,它包含三个键值对,键是std::string类型的字符串,值也是std::string类型的字符串。

范围for

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。

for循环后的括号由冒号“ :”分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

范围for可以作用到数组和容器对象上进行遍历

范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

#include <iostream>
#include <string>
using namespace std;
int main() {int arr[] = { 1,2,3,4,5 };for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {arr[i] *= 2;}for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {cout << arr[i] << endl;}// C++11的遍历for (auto &e : arr) {e *= 2;}for (auto e : arr) {cout << e << endl;}string s("hello world!");for (auto it : s) {cout << it << " " ;}return 0;
}

 

结束语

本期讲解就到此结束了,下节我们将继续扩展string的其他接口和函数。

最后感谢各位友友的支持,友友们点个赞吧!!! 

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

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

相关文章

C++如何为枚举量生成对应的解释:4种常见的方法

C如何为枚举量生成对应的解释 在 C 中&#xff0c;你可以通过几种方法为枚举量生成对应的解释或描述文本。以下是几种常见的方法&#xff1a; 1. 使用 switch 语句 这是最直接的方法&#xff0c;通过 switch 语句为每个枚举值返回一个对应的字符串。 #include <iostream…

day45-dynamic programming-part12-8.16

tasks for today: 1. 115.不同的子序列 2. 583.两个字符串选的删除操作 3. 72.编辑距离 4. 总结编辑两个序列关系的问题 ------------------------------------------------------------------- 1. 115.不同的子序列 In this practice, it is necessary to compare with t…

10 Java数据结构:包装类、数组(Array工具类)、ArrayList

文章目录 前言一、包装类1、Integer&#xff08;1&#xff09;基本用法&#xff08;2&#xff09;JDK5前的包装类用法&#xff08;了解即可&#xff0c;能更好帮助我们理解下面的自动装箱和自动拆箱机制&#xff09;&#xff08;3&#xff09;自动装箱与自动拆箱机制 --- 导致&…

【学习笔记】Day 21

一、进度概述 1、机器学习常识19-22&#xff0c;以及相关代码复现 二、详情 19、矩阵分解 矩阵分解是一个纯数学问题&#xff0c;但当给矩阵赋予现实意义后&#xff0c;矩阵分解就成为了使用数学应对机器学习问题的一类典型而巧妙的方法。 在线性回归分析中&#xff…

esp32c3 luaos

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、介绍二、相关介绍2.1helloworld——2.2任务框架2.3消息传递 与消息订阅2.4uart2.5二进制数据/c结构体的打包与解析2.6 zbuffer库2.8 uart 485 数据解析2.9 …

深入探讨 ElementUI 动态渲染 el-table

在前端开发中&#xff0c;表格是不可或缺的一部分。无论是数据展示、数据录入&#xff0c;还是数据分析&#xff0c;表格都扮演着重要的角色。而在 Vue.js 生态系统中&#xff0c;ElementUI 提供了一个强大且灵活的表格组件——el-table。本文将带你深入了解如何使用 ElementUI…

缓存实现方式

缓存是一个常见的话题&#xff0c;因为它对于提高应用程序性能至关重要。缓存是一种存储数据的临时地方&#xff0c;以便快速访问数据&#xff0c;减少对原始数据源&#xff08;如数据库或文件系统&#xff09;的访问次数&#xff0c;从而提高应用程序的响应速度和吞吐量。 Jav…

【运维】Linux如何解压.rar文件

在Linux系统中解压.rar文件,你可以使用unrar或rar工具。如果系统中还没有安装它们,可以先通过包管理器进行安装。以下是具体步骤: 1. 安装 unrar 对于基于Debian的发行版(如Ubuntu):sudo apt-get install unrar对于基于Red Hat的发行版(如CentOS、Fedora):sudo yum i…

【Nodejs】六、express框架

目录 一、express 介绍 二、express 使用 2.1 express 下载 2.2 express 使用 三、express 路由 3.1 什么是路由 3.2 路由的使用 3.3 获取请求参数 3.4 获取路由参数 四、express 响应设置 五、express 中间件 5.1 什么是中间件 5.2 中间件的作用 5.3 中间件的类…

如何应对突发技术故障和危机:开发团队的应急策略

开发团队如何应对突发的技术故障和危机&#xff1f; 在数字化时代&#xff0c;软件服务的稳定性对于企业至关重要。然而&#xff0c;即使是大型平台&#xff0c;如网易云音乐&#xff0c;也可能遇到突发的技术故障。网页端出现502 Bad Gateway 报错&#xff0c;且App也无法正常…

如何在VMware ESXI中创建Linux虚拟机并实现异地SSH远程访问

目录 ⛳️推荐 前言 1. 在VMware ESXI中创建Ubuntu虚拟机 2. Ubuntu开启SSH远程服务 3. 安装Cpolar工具 4. 使用SSH客户端远程访问Ubuntu 5. 固定TCP公网地址 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不…

生产环境docker nginx+php8.0镜像

生产环境docker nginxphp8.0镜像 自定义创建php8.0镜像&#xff0c;创建dockerfile FROM php:8.0-fpm# 安装系统依赖 RUN sed -i s|http://deb.debian.org/debian|http://mirrors.aliyun.com/debian|g /etc/apt/sources.list && \apt-get update && apt-get i…

重塑“我店”平台:绿色积分引领的数字消费新纪元

在数字化转型的洪流中&#xff0c;“我店”平台凭借其创新的绿色积分体系异军突起&#xff0c;成为市场中的璀璨新星。本文将深度剖析“我店”的运营模式、市场效应及其如何通过绿色积分机制开创消费新潮流。 一、崛起之路与市场震撼力 自2021年盛夏在上海启航以来&#xff0c…

docker-实战——consul集群

使用docker方式安装consul集群 环境准备 操作系统openEuler 23.09docker环境docker-compose环境本次计划部署的consul集群有3个节点,都是server类型 docker pull registry.cn-hangzhou.aliyuncs.com/bcbx/consul:1.7.8 网络不通的情况下使用上述方式进行镜像拉取 host网络节点…

WPS宏实现Sheet页拆分功能

源表格首列名称中一样的分别创建该名称的Sheet页&#xff0c;首先把首列复制导致Sheet2页&#xff0c;根据去重后的值创建新的Sheet页&#xff0c;把源表格数据复制到新建的Sheet页&#xff0c;遍历删除不需要的留下需要的就完成了。 function JIn521() { //设置工作…

ffmpeg读取时长、读取视频格式

ffmpeg读取时长、读取视频格式 ffmpeg读取时长ffmpeg读取视频格式 ffmpeg读取时长 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接 or 视频路径 2>&1 | grep Duration ffmpeg读取视频格式 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接…

Java CompletableFuture:你真的了解它吗?

文章目录 1 什么是 CompletableFuture&#xff1f;2 如何正确使用 CompletableFuture 对象&#xff1f;3 如何结合回调函数处理异步任务结果&#xff1f;4 如何组合并处理多个 CompletableFuture&#xff1f; 1 什么是 CompletableFuture&#xff1f; CompletableFuture 是 Ja…

<数据集>商品条形码识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3748张 标注数量(xml文件个数)&#xff1a;3748 标注数量(txt文件个数)&#xff1a;3748 标注类别数&#xff1a;1 标注类别名称&#xff1a;[Barcode] 序号类别名称图片数框数1Barcode37484086 使用标注工具&am…

探索Qotom Q51251OPS迷你电脑:功能与广泛应用

Qotom Q51251 OPS&#xff08;开放可插拔规范&#xff09;迷你电脑是一款设计紧凑且功能强大的设备&#xff0c;旨在满足不同领域的多样化需求。基于英特尔Core i5-12450H Alder Lake H处理器&#xff0c;这款设备不仅具备出色的计算性能&#xff0c;还提供了丰富的连接选项&am…

【MySQL】数据库基础(库的操作)

目录 一、MySQL安装、连接、修改密码操作 二、库的操作 2.1 创建数据库 2.2 字符集和校验规则 2.3 操控数据库 2.4 修改数据库 2.5 删除数据库 2.6 数据库的备份和恢复 2.7 查看连接情况 前情提要&#xff1a; 我的服务器操作系统是Ubuntu20.04&#xff0c;安装的是M…