Cpp::STL—string类的使用与理解(上)(8)

文章目录

  • 前言
  • 一、string类对象的构造函数
    • string()
    • string(const char* s)
    • string(size_t n, char c)
    • string(const string& s)
    • string(const string& str,size_t pos,size_t len = npos)
  • 二、string类对象的容量操作
    • size与length
    • capacity
      • capacity返回值比size大
      • capacity的扩容机制
    • empty
    • clear
    • shrink_to_fit
    • reserve
      • 关于reserve与扩容的一些问题
    • resize
      • 字符串变短(n>size)
      • 字符串在容量内变长(capacity>=n>size)
      • 字符串修改长度超出容量(n>capcity)
  • 三、string类对象的访问
    • operator[ ]
    • 迭代器(简单介绍)
      • 反向迭代器
  • 四、string类对象的遍历操作
    • for+[ ]
    • 迭代器(begin(),end())
    • 范围for
  • 总结


前言

  C语言中,字符串是以’\0’结尾的一些字符的集合(C-string),为了操作方便,C标准库中提供了一些 str 系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
  这就是我们为什么学习string类的理由,如果你对前面类和对象的内容了解透彻的话,学习STL按道理来说应该不算太难

另外,我还想说,string类其实是当时发布STL时候的前排兵,说白了就是没什么可供参考,所以你在学习它的时候有时候会感觉很挫很冗余,这就对了,这就是正确的感觉


一、string类对象的构造函数

在这里插入图片描述

  事先声明,就像我前言说的一样,string设置的很冗余,所以我挑选几个常见的来讲,甚至于有几个我挑出来的也不多见,下文同理

(constructor)函数名称功能说明
string() (重点)默认构造,创建一个空串,这个空串的长度是0
string(const char* s) (重点)用C-string来构造string类对象
string(size_t n, char c)通过一个字符来构造,一个字符重复n次
string(const string& s) (重点)拷贝构造函数
string(const string& str,size_t pos,size_t len = npos)(重点)从str中pos指向位置先后拷贝len长度字符,两种情况下文给出

string()

功能:构造空string类对象,其长度为0

#include<iostream>
#include<string>
using namespace std;int main()
{string s1;cout<<s1.length()<<endl; // 0return 0;
}

string(const char* s)

功能:使用C-string构造string类对象。在非空字符串中,从s指向位置拷贝一份字符串

#include<iostream>
#include<string>
using namespace std;int main()
{// 相当简便string s1("Hello,world!");cout << s1 << endl; // Hello,world!// 其实我们还有一种清晰明了的方法const char* s = "Hello,world!";string s2(s);cout << s2 << endl; // Hello,world!return 0;
}

string(size_t n, char c)

功能:通过一个字符来构造,一个字符重复n次

#include<iostream>
#include<string>
using namespace std;int main()
{string s1(5,'r');cout << s1 << endl; // rrrrrreturn 0;
}

string(const string& s)

功能:拷贝构造,通过已有对象拷贝构造一个新的对象,这个对象和已有对象在逻辑上是相同的

#include<iostream>
#include<string>
using namespace std;int main()
{// 这两种都称为拷贝构造string s1("Hello,world!");string s2(s1);string s3 = s1;return 0;
}

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

功能:从str中pos指向位置先后拷贝len长度字符。出现两种结果:拷贝到str最后一个字符或没有达到最后一个字符完成拷贝

  第三个参数len类型为 size_t ,而缺省值 npos == -1 导致了 npos 按补码形式是32个比特位1,而又被当作正数还原为原码,就是 INT_MAX(涉及到编码那块,考验你前面学得扎不扎实的时候到了)。所以对于当没有明确 len 数值,默认是从 pos 位置拷贝字符串到最后一个字符,而如果str已经拷贝到最后一个字符了,那就结束拷贝

这是原文翻译,你英语好的话你自己翻译~
Copies the portion of str that begins at the character position pos and spans len characters (or until the end of str, if either str is too short or if len is string::npos).

#include<iostream>
#include<string>
using namespace std;int main()
{string s1("Hello,world!");string s2(s1,2,3);cout << s2 << endl;return 0;
}

二、string类对象的容量操作

在这里插入图片描述

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)清空有效字符
reserve (重点)为字符串预留空间
resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

size与length

你可能会想,这两个方法都是返回字符串有效字符的长度,会不会有什么区别?

int main()
{string str1("hello world");cout << str1.size() << endl; // 11cout << str1.length() << endl; // 11return  0;
}

答案是没有

那为什么还会有两个同样作用的方法的存在呢?其实就是我说的,string是STL的前排兵,一开始想着要有个返回长度的方法,命名length也很合理,可是后面vector、map、set都是size,为了统一,只好也跟着加了个size

如果你问为什么不把length给删掉,其实你细想,删掉的话,已经用length这个方法写代码的个人、公司是不是又有意见了,所以一般我们只加不改,这就是向前兼容原则

一言以蔽之,length合理,size统一更规范

capacity

功能:返回当前对象所分配的存储空间的大小,一般情况下capacity返回大小中不包含’\0’

int main()
{string str1("hello world");cout << str1.size() << endl; // 11cout << str1.capacity() << endl; // 15return 0;
}

capacity返回值比size大

  std::string在底层上是属于动态数组,数组大小是不固定,根据实际需要进行调正,由于经常性出现频繁插入字符的清空,只存在size情况下,会导致频繁地向系统申请空间,性能降低,其实size和capacity的这种用法我们在之前也见识过
在这里插入图片描述

capacity的扩容机制

在这里插入图片描述
  我们会发现string类还是很智能的,能自动扩容,不需要我们操心,但是我们可以注意下扩容的机制,这其实跟编译器和指标因子的不同而有所差异

VS(msvc):扩容机制是第一次扩容到原来空间的两倍左右,之后则扩容当前空间的1.5倍
g++:扩容机制是以当前空间的两倍

empty

功能:检测字符串是否释放为空,是空返回true,否则返回false

int main()
{string str1;if (str1.empty()) // 判断释放为空cout << "为空" << endl;elsecout << "非空" << endl;return 0;
}

clear

功能:清空string有效字符资源,不改变底层空间大小。影响有效元素size,不会影响空间容量大小capacity

int main()
{string str1("hello world");cout << str1.size() << endl; // 11cout << str1.capacity() << endl; // 15str1.clear(); // 清空有效字符cout << str1.size() << endl; // 0cout << str1.capacity() << endl; // 15return 0;
}

shrink_to_fit

功能:向系统请求字符串缩容到适合大小,但是该函数对于字符串的长度和内容是没有影响的,如果使用后容量并没有发生变化,那么可能字符串对象可能已经使用内存管理策略去避免频繁的内存分配和释放

int main()
{string str("hello world");cout << str.size() << endl; // 11cout << str.capacity() << endl; // 15cout << endl;str.resize(100);cout << str.size() << endl; // 100cout << str.capacity() << endl; // 111cout << endl;str.shrink_to_fit();cout << str.size() << endl; // 100cout << str.capacity() << endl; // 111cout << endl;return 0;
}

这个方法要少用,甚至其实我觉得都可以不用,因为我们一般认为缩容是释放部分空间从而达到正确大小,可是这是不对的,释放只能整个释放,事实上,正确的流程是,重新开一块空间,拷贝部分内容,进而释放原先的全部空间,损耗极大

reserve

功能:向系统申请预留空间,属于手动扩容

int main()
{string str1;cout << str1.capacity() << endl; // 15str1.reserve(100);cout << str1.capacity() << endl; // 111string str2(10, 'x');cout << str2.capacity() << endl; // 10str2.reserve(); // 缺省参数为0cout << str2.capacity() << endl; // 10return 0;
}

关于reserve与扩容的一些问题

  1. 既然我们前面说了编译器会自动扩容,为什么还要我们自己手动扩容呢?
    理由:扩容是需要付出代价的,如果是异地扩容,付出代价更大,需要进行空间开辟和数据拷贝,如果事先知道所需要的空间大小,使用reverse开辟足够使用的空间,减少频繁对内存的重分配,就算后期出现空间不足,也有自动扩容的机制,不需要担心大小是固定的。虽然自动扩容可以解决容量不足的情况,但是手段扩容可以减少频繁自动扩容的代价,属于一种优化手段
  2. reverse要求100个字节空间,但却开辟了111个字节空间呢?
    理由在不同编译器下机制是不同的,但是确保了至少满足所需空间。有些编译器开辟多个空间,是对reserve开辟的空间进行了二次开辟,可以灵活调用内存空间分配,在后继需要小空间,避免扩容
  3. reserve参数部分小于当前空间大小,提出申请空间请求,但是空间大小并没有发生改变
    理由:reserve进行扩容必须参数部分比当前空间大,才会改变string的底层空间总大小,否则就是无效扩容

resize

功能:改变字符串的实际长度
这里我们以"hello world"为例子来讲解三种不同情况
在这里插入图片描述

字符串变短(n>size)

int main()
{string str1("hello world"); // 长度为11cout << str1.size() << endl; // 11cout << str1.capacity() << endl; // 15str1.resize(2);cout << str1 << endl; // hecout << str1.size() << endl; // 2cout << str1.capacity() << endl; // 15return 0;
}

字符串在容量内变长(capacity>=n>size)

int main()
{string str1("hello world"); // 长度为11cout << str1.size() << endl; // 11cout << str1.capacity() << endl; // 15str1.resize(13);cout << str1 << endl; // hello worldcout << str1.size() << endl; // 13cout << str1.capacity() << endl; // 15return 0;
}

字符串修改长度超出容量(n>capcity)

int main()
{string str1("hello world"); // 长度为11cout << str1.size() << endl; // 11cout << str1.capacity() << endl; // 15str1.resize(50);cout << str1 << endl; // hello worldcout << str1.size() << endl; // 50cout << str1.capacity() << endl; // 63return 0;
}

  当resize修改长度超过capacity,capacity会进行自动扩容。至于最后capacity的值为什么不是50,在reserve中解释了不同编译器扩容机制是不同的

其中resize有两个重载,功能都是将字符串中有效字符个数改变到n个,不同点在于:
resize(size_t n):用’\0’来填充都出的元素空间
resize(size_t n,char c):用字符c来填充多出的元素空间

  一样的,相比之下我们鼓励用reserve,提前开好空间,避免频繁扩容

三、string类对象的访问

operator[ ]

在这里插入图片描述

我们只要了解第一个operator[ ]就行,其他都是为了规范性
我们不妨来看下这两个重载:
char& operator[ ] (size_t pos); // 读写
const char& operator[ ] (size_t pos) const; // 只读
其实,这里第一个重载体现了引用返回的一大用处:可供修改

// 相当于是自定义类型给数组化了,其实string还是蛮特殊的
// 这点我们在后面的学习会有更深的体会int main()
{string str1("hello world");for (int i = 0 ; i < str1.size() ; i++){// cout << str1.operator[](i) << endl;cout << str1[i] << endl;}const string str2("hello world");for (int i = 0 ; i < str2.size() ; i++){// str2[i]++; const修饰的话,没有修改的权限cout << str2[i] << endl;}return 0;
}

迭代器(简单介绍)

  迭代器(Iterator)是一种用于遍历容器(如列表、字典、集合等)元素的对象,它提供了一种统一的访问容器内部元素的方式,而不必暴露容器的具体实现细节。迭代器通常用于循环结构中,让程序员能够逐个访问容器中的元素,在讲解string类对象的遍历前,我想先简要讲解下这一概念

int main()
{string str1("hello world");string::iterator it = str1.begin();while (it != str1.end()) // 左闭右开{cout << *it << endl;it++;}return 0;
}

  在string里,你可以暂时把它当成是类似指针的东西

  我们有很多种遍历类对象的方式,但是迭代器才是主流。对于链表、树等数据结构,迭代器不在乎底层实现,是通用的遍历容器。迭代器是一种像指针的东西,他可以是指针也可以不是指针,具体还是看不同编译器的底层实现,迭代器有两种类型分别:可读可修改,可读不可修改,但是我们要注意存储迭代器的变量类型应该与容器的迭代器类型相匹配,以确保类型的一致性,避免编译器报错或者意外行为

反向迭代器

定义:string::reverse_iterator

int main()
{string str1("hello world");string::reverse_iterator rit = str1.rbegin();while (rit != str1.rend()){cout << *rit << "";++rit; // 请注意是++}cout << endl;return 0;
}

其实,这个反向迭代器的要求应该很少,我觉得正向的就可以满足所需了

四、string类对象的遍历操作

for+[ ]

前文已讲过这两个方法
// str.operator[ ](size_t pos)
// str[pos]

我前面也说了,这很像数组,所以我们很自然的写出这个遍历

int main()
{string str("Hello,world!");for (size_t i = 0; i < str.size(); i++) {cout << str[i]; // Hello,world!}cout << endl;return 0;
}

迭代器(begin(),end())

前文说,这很像指针,这样一提醒后该怎么遍历,也是很自然而然就能写出来

int main()
{string str("Hello,world!");string::iterator it1 = str.begin();while (it1 != str.end()) {cout << *it1;it1++;}cout << endl;return 0;
}

范围for

我们说范围for能够自动遍历所有元素,其实哪有什么自动化,范围for的底层还是迭代器,这点结论大家可自行查看汇编代码得出

int main()
{string str("Hello,world!");for (const auto& ch : str) {cout << ch;}cout << endl;return 0;
}

总结

  我真的要花大篇幅来讲string,这是因为其是我们学习STL的第一课,并且就像我在正文一再吐槽的,string设计的是真的冗余

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

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

相关文章

Docekrfile和docker compose编写指南及注意事项

Dockerfile 基础语法 我们通过编写dockerfile,将每一层要做的事情使用语法固定下来&#xff0c;之后运行指令就可以通过docker来制作自己的镜像了。 构建镜像的指令&#xff1a;docker build /path -t imageName:tag 注意&#xff0c;docker build后的path必须是dockerfile…

阿里云对象存储OSS 速学

目录 1.创建一个Bucket 2.创建密钥AccessKey 3.在文档中心打开阿里云对象存储OSS 4.参考上传文件示例 以官网的文档为主&#xff0c;我的文章教学为辅 官网有详细的视频介绍&#xff1a; OSS快速入门_对象存储(OSS)-阿里云帮助中心 (aliyun.com)https://help.aliyun.com/…

25考研咨询周开启,西安电子科技大学是否改考408??

学长这几天帮大家问了西安电子科技大学是否会从833、834、953改考为408&#xff1f; 西电老师回复&#xff1a;根据上级文件要求&#xff0c;招生简章以及专业目录会在网上报名开始前公布&#xff0c;专业课不会又大变动&#xff01; 因为大家安心复习即可&#xff0c;保证今…

java解决跨域问题时的403报错

什么是跨域问题&#xff1f; 当一个请求的url的协议&#xff0c;域名&#xff0c;端口三者之间任意一个与当前页面url不同 即为跨域 问题背景&#xff1a; 如图&#xff0c;前端端口为8090&#xff0c;而后端端口为8099&#xff0c;形成跨域&#xff0c;无法对接 试图利用Spr…

爬虫——爬取小音乐网站

爬虫有几部分功能&#xff1f;&#xff1f;&#xff1f; 1.发请求&#xff0c;获得网页源码 #1.和2是在一步的 发请求成功了之后就能直接获得网页源码 2.解析我们想要的数据 3.按照需求保存 注意&#xff1a;开始爬虫前&#xff0c;需要给其封装 headers {User-…

linux基础指令的认识

在正式学习linux前&#xff0c;可以简单认识一下linux与win的区别 win&#xff1a;是图形界面&#xff0c;用户操作更简单&#xff1b;在刚开始win也是黑屏终端 指令操作&#xff0c;图形界面就是历史发展的结果。Linux&#xff1a;也存在图形界面比如desktop OS&#xff1b;但…

【C++】模拟实现红黑树

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:实战项目集 ⚙️操作环境:Visual Studio 2022 目录 一.了解项目功能 二.逐步实现项目功能模块及其逻辑详解 &#x1f4cc;实现RBTreeNode类模板 &#x1f38f;构造RBTreeNode类成员变量 &#x1f38f;实现RBTreeNode类构…

异常场景分析

优质博文&#xff1a;IT-BLOG-CN 为了防止黑客从前台异常信息&#xff0c;对系统进行攻击。同时&#xff0c;为了提高用户体验&#xff0c;我们都会都抛出的异常进行拦截处理。 一、异常处理类 Java把异常当做是破坏正常流程的一个事件&#xff0c;当事件发生后&#xff0c;…

Spring Boot新闻推荐:实时数据处理

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

JSR303微服务校验

一.创建idea 二.向pom.xml添加依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.7.RELEASE</version></parent><properties><java.vers…

职业技术学校开设无人机培训技术详解

职业技术学校开设无人机培训技术&#xff0c;是一个涉及多个方面的综合性教学过程。以下是对该培训技术的详细解析&#xff1a; 一、培训目标 无人机培训技术的目标在于培养学员掌握无人机的基本原理、组装调试、飞行操作、安全规范及维修保养等技能&#xff0c;使其成为具备…

基于SSM的定制衣服系统的设计与实现(定制衣服管理平台的设计与开发、智慧服装定制系统的设计与实现、定制衣服管理系统的设计与实现(源码+定制+参考文档)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

机器人的性能指标

1. 负荷能力 负荷能力负荷能力是指机器人在满足其他性能要求的情况下,能够承载的负荷重量。例如,一台机器人的最大负荷能力可能远大于它的额定负荷能力,但是达到最大负荷时,机器人的工作精度可能会降低,可能无法准确地沿着预定的轨迹运动,或者产生额外的偏差。机器人的负荷量与…

【redis-05】redis保证和mysql数据一致性

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩htt…

不只是前端,后端、产品和测试也需要了解的浏览器知识(一)

目录标题 一、我们为什么要了解浏览器&#xff1f;1. 对于前端开发者2. 对于后端开发者 二、浏览器发展概述1. 宏观发展2. 微观发展 三、浏览器核心部件1. 浏览器界面介绍2. 目前浏览器的使用的渲染引擎和解释器总结3. 浏览器的解释器 四、各家浏览器目前的市场占比五、整体总结…

索尼MDR-M1:超宽频的音频盛宴,打造沉浸式音乐体验

在音乐的世界里&#xff0c;每一次技术的突破都意味着全新的听觉体验。 索尼&#xff0c;作为音频技术的先锋&#xff0c;再次以其最新力作——MDR-M1封闭式监听耳机&#xff0c;引领了音乐界的新潮流。 这款耳机以其超宽频播放和卓越的隔音性能&#xff0c;为音乐爱好者和专…

tornado

Tornado通过使用非阻塞网络I/O&#xff0c;可以扩展到数以万计的开放链接&#xff0c;非常适合 长时间轮询&#xff0c;WebSockets和其他需要与每个用户建立长期连接的应用程序。 特点 注重性能优越&#xff0c;速度快解决高并发异步非阻塞websockets 长连接内嵌了HTTP服务器…

04 B-树

目录 常见的搜索结构B-树概念B-树的插入分析B-树的插入实现B树和B*树B-树的应用 1. 常见的搜索结构 种类数据格式时间复杂度顺序查找无要求O(N)二分查找有序O( l o g 2 N log_2N log2​N)二分搜索树无要求O(N)二叉平衡树无要求O( l o g 2 N log_2N log2​N)哈希无要求O(1) 以…

IO模型介绍

一、理解IO 网络通信的本质就是进程间通信&#xff0c;进程间通信本质就是IO TCP中的IO接口&#xff1a;read / write / send / recv&#xff0c;本质都是&#xff1a;等 拷贝 所以IO的本质就是&#xff1a;等 拷贝 那么如何高效的IO&#xff1f; 减少“等”在单位时间的…

CORDIC算法笔记整理

CORDIC算法有两种模式&#xff0c;分别为旋转模式和向量模式。而在数字硬件实现混频处理时&#xff0c;CORDIC算法是比较好的方法&#xff0c;使用的是CORDIC的旋转模式&#xff0c;只需通过移位操作和加法就可以实现频谱搬移的乘法操作。 1 CORDIC算法理解 1.1 单次旋转 对…