关于string的‘\0‘与string,vector构造特点加部分特别知识点的讨论

目录

前言:

问题一:关于string的''\0''问题讨论

问题二:C++标准库中的string内存是分配在堆上面吗?

问题三:string与vector的capacity大小设计的特点

问题四:string的流提取问题

问题五:迭代器失效

 问题六:Vector 最大 最小值 索引 位置

前言:

前几篇文章我们已经介绍完了string,vector,list的使用与string的使用原理,但是仅仅知道这些对于我们日常使用来说已经够了,但是在我们日常使用的时候,不免会有报错与相关的疑惑,那么这里我介绍几个我认为有问题的地方,后续有问题的话,还会继续补充。

问题一:关于string的''\0''问题讨论

之前在某篇文章中看到,C语言字符串是以'\0'结尾的,但是C++string类型的字符串并不是以'\0'结尾。话不多说,直接放代码(vsX86环境):

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string b("abc");
	cout << b.capacity() << endl;
	cout << b.size() << endl;

	if (b[3] == '\0')
		cout << "yes" << endl;
	else
		cout << "no" << endl;
	return 0;
}

运行结果:

.

可以看到我们创建的这个string,他的容器大小为15,这个string存储大小为3,但是我们却可以通过越界访问  b[3]   ,并且通过验证字符串的结尾就是'\0'。此时我的内心是疑惑的,心想"abc"是C语言风格的字符串给b构造,肯定会把"abc"后面影藏的'\0'给构造进去,如果不会这样就会在迭代器里面不会遇见结束表示符。那么至于这里的结尾的最后一个'\0',从结果来说是大小size不计算的,所以大小size是3。

但是我们又尝试别的构造的话又会尝试别的疑惑,比如这个代码:

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string b("abcd",3);//这种构造方法是通过字符串abcd,然后只取前3个字符进行构造string
	//但是这个字符串存放的其实是 abcd\0
	cout << b.capacity() << endl;
	cout << b.size() << endl;

	if (b[3] == '\0')
		cout << "yes" << endl;
	else
		cout << "no" << endl;
	return 0;
}

结果跟上面一模一样。此刻我又想,构造函数会在末尾自动添加一个'\0',并且size和capacity函数都不计算'\0'的。

但是我们一开始是假设他跟c语言的风格相似的会把abc后面的'\0'会自动添加上,但是我们这个代码是只取了abcd\0这个字符串的前三个,没有'\0'啊~!

所以此刻,我肯定是矛盾的!!因为最开始说string字符串是不以'\0'结尾的,但是测试下来,确实是以'\0'结尾的。

哎呀~为什么呢?经过查阅资料后,才得知了其中的奥妙,奥妙如下:

std::string:标准中未明确规定需要\0作为字符串结尾。编译器在实现时既可以在结尾加\0,也可以不加。(因编译器不同,就比如vs就不用)

但是,当通过c_str()或data()(二者在 C++11 及以后是等价的)来把std::string转换为const char *时,会发现最后一个字符是\0。但是C++11,string字符串都是以'\0'结尾(这也是c++祖师爷为以前的自己的规定的优化)。



为什么C语言风格的字符串要以'\0'结尾,C++可以不要?

c语言用char*指针作为字符串时,在读取字符串时需要一个特殊字符0来标记指针的结束位置,也就是通常认为的字符串结束标记。而c++语言则是面向对象的,长度信息直接被存储在了对象的成员中,读取字符串可以直接根据这个长度来读取,所以就没必要需要结束标记了。而且结束标记也不利于读取字符串中夹杂0字符的字符串。



这里我们深入一下string的构造时的细节:

#include<iostream>
#include<string>
using namespace std;
int main()
{
	int aa = 0;
	printf("栈区的地址:%p\n", &aa);
	int* pl = new int;
	printf("堆区的地址:%p\n", pl);
	string a("abcddddddddddddddddddddddddd", 20);
	printf("a的地址:    %p\n", &a);
	printf("a[0]的地址: %p\n", &a[0]);
	a[1] = 'X';
	cout << a << endl;
	printf("a的地址:    %p\n", &a);
	printf("a[0]的地址: %p\n", &a[0]);
	string b("abc");
	printf("b的地址:    %p\n", &b);
	printf("b[0]的地址: %p\n", &b[0]);
	return 0;
}

然后通过运行的知,

用红色标注出来的是在栈上存储的,蓝色标注的时在堆上存储的,然而a,b就与指针类似,他们指向一片空间,空间内存储的对象信息, 对象地址分别是006FF6AC与006FF688,他俩的地址跟栈区地址最为接近所以该对象存储在栈区上。同理a[0]是堆区上,但是b[0]按道理也应该是在堆区上,但是为什么会是是在栈区上呢?其实这是c++的一个特殊处理,这里留下一个小疑问,(下一个问题进行解答,这里先给出为什么的答案:当string内存存储的个数在16以内(包括'\0')(后面解释为什么是16)在栈上,超过以后在堆上。)

所以,string在构造函数的时候,会在堆上开辟一块内存存放字符串,并且指向这块字符串。

(这里给大家提问一个小问题:就是为什么a先定义的,但是a对象地址为什么比b的大?)

解答:a、b是两个局部对象变量,栈是向下增长的,所以先入栈的变量地址高,即&a > &b,



问题二:C++标准库中的string内存是分配在堆上面吗?

例如我声明一个string变量。
string str;
一直不停的str.append("xxxxx");时,str会不停的增长。

我想问的是这个内存的增长,标准库中的string会把内存放置到堆上吗?

另外STL中的其他容器是否遵循相同的规则。

首先我们给出结论:16以内在栈上,超过以后在堆上。(这句话的答案省略上面的问题的前提条件:【在栈上构造的 string 对象】,如果string 是 new 出来的即在堆上构造的,当然内部的缓冲区总是在堆上的)。(vector也是如此,但是细节上略有不同)

为什么要这样做呢?

如果以动态增长来解释就是:

因为栈通常是一种具有固定大小的数据结构,如数组实现的栈在创建时会指定一个固定的容量。因此,一般情况下,栈是不支持动态增长的。 

所以是存储在堆上的。

其实还有另一个原因,那么下一个问题给出解答;

问题三:string与vector的capacity大小设计的特点

在我们设计string与vector的时候,你是否观察过他的capacity的大小呢?就比如vs里面为什么会让string与vector在其存储的内存个数小于16时会将数据存储在栈上,大于16存储在堆上呢?

这是因为string与vector第一次会在栈上开辟空间,直接开辟16个单位空间,然后挨个进行流提取,这样的话就会方便很多 ,就算要再添加数据,也不需要进行动态增长,然后这个16个单位空间就是string与vector的capacity。这里的证明可以通过调试自己查看他的capacity,当然编译器不同,可能这个首次开辟空间大小略有不同,但是不影响。

总的来说这两种解释都是解决的次要问题,他这样设计主要为了解决内存碎片的问题;如果存储的内容大小小于16,他就会先存在栈上的数组里面,当大于16,就会进行拷贝到堆上,然后栈上的数组就会进行浪费,这样达到了利用空间换时间的效果

问题四:string的流提取问题

首先如果我们自己实现string的流提取,我们会下意识认为会挨个提取输入的字符,然后挨个与s进行对接,代码试下如下: (这个代码实现的流提取是完全没有问题的)

istream& operator>>(istream& in, string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

但是这样写会有一个弊端,就是会多次进行扩容,俗话常说:扩容本身就是一件麻烦的时,浅拷贝就不多说了,深拷贝就更麻烦了;

所以后来就进行了优化,会先开辟一个数组,然后将流提取的字符挨个放到数组里面,当数组满的时候(或者流提取的字符提取完了)我们当让s+=数组;这样既保证了存储的数据在堆上,也避免了多次进行扩容;(需要注意的是我们要自己添加 '\0' 在string的末尾)

	istream& operator>>(istream& in, string& s)
	{
		s.clear();

		char buff[129];
		size_t i = 0;
		char ch;
		//in >> ch;
		ch = in.get();
		s.reserve(128);

		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//in >> ch;
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

当然这上面的两个问题都是存在于string于vector上的,因为他们存储的数据是连续的,二list作为链表就不存在这样的问题。 

问题五:迭代器失效

然而迭代器失效就不一样了,string,vector,list都存在。

在我们使用迭代器进行遍历的时候,不免会出现不正当的使用而使其迭代器失效;

失效的主要原因就是:迭代器对应的指针所指向的空间已经被销毁了,而使用一块已经被释放的空间的时候,就会造成程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)。俗话来说就是野指针了。

前面我们都在用string来进行解释,这里我们使用vector来解释,

1

就比如下面这个代码:

include<iostream>
#include<vector>
using namespace std;

int main()
{
    vector<int> v(10, 1);
    auto it = v.begin();
    v.insert(it, 0);
    (*it)++;
    return 0;
}

看起来没有问题,但是我们是先给迭代器赋值,然后进行插入,但是有一点问题就是如果插入时恰好进行扩容,并且时异地扩容,那么这个it就会变为野指针。从而达到迭代器失效的问题。

2

同样插入存在异地扩容,当然删除也存在着迭代器失效的问题;

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    vector<int> v(10, 1);
    auto it = v.end() - 1;
    v.erase(it);
    (*it)++;
    return 0;
}

这时候如果再进行使用it,那么就会报错。

注意:

  1. vs 对于迭代器失效检查很严格,如使用了 erase 之后,之前的迭代器就不允许使用,只有重新给迭代器赋值,才可以继续使用
  2. Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

 问题六:Vector 最大 最小值 索引 位置

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    vector<double> v{ 1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0 };

    vector<double>::iterator biggest = max_element(begin(v), end(v));
    cout << "Max element is " << *biggest << " at position " << distance(begin(v), biggest) << endl;

    auto smallest = min_element(begin(v), end(v));
    cout << "min element is " << *smallest << " at position " << distance(begin(v), smallest) << endl;

    return 0;
}

运行结果:



到这里就完了,写作不易还请点赞;

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

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

相关文章

运筹说 第118期|存储论奠基人——肯尼斯·约瑟夫·阿罗

1.导读 前面我们已经了解了存储论的相关内容&#xff0c;相信大家一定也有所收获&#xff0c;下面我们将带着大家继续了解存储论的相关内容&#xff0c;在本次文章中我们将一起走近存储论的奠基人之一——肯尼斯约瑟夫阿罗Kenneth J&#xff0e;Arrow&#xff0c;希望能给大家…

In Search of Lost Online Test-time Adaptation: A Survey--论文笔记

论文笔记 资料 1.代码地址 https://github.com/jo-wang/otta_vit_survey 2.论文地址 https://arxiv.org/abs/2310.20199 3.数据集地址 1论文摘要的翻译 本文介绍了在线测试时间适应(online test-time adaptation,OTTA)的全面调查&#xff0c;OTTA是一种专注于使机器学习…

科技创新引领水利行业升级:深入分析智慧水利解决方案的核心价值,展望其在未来水资源管理中的重要地位与作用

目录 引言 一、智慧水利的概念与内涵 二、智慧水利解决方案的核心价值 1. 精准监测与预警 2. 优化资源配置 3. 智能运维管理 4. 公众参与与决策支持 三、智慧水利在未来水资源管理中的重要地位与作用 1. 推动水利行业转型升级 2. 保障国家水安全 3. 促进生态文明建设…

顺序表--续(C语言详细版)

2.9 在指定位置之前插入数据 // 在指定位置之前插入数据 void SLInsert(SL* ps, int pos, SLDataType x); 步骤&#xff1a; ① 程序开始前&#xff0c;我们要断言一下&#xff0c;确保指针是有效的&#xff0c;不是NULL&#xff1b; ② 我们还要断言一下&#xff0c;指定的…

智慧灌区信息化系统完整解决方案

一、背景 随着科技的快速发展&#xff0c;智慧灌区信息化系统正逐渐成为提高农业灌溉效率、优化水资源配置的重要手段。本文将详细介绍智慧灌区信息化系统的完整解决方案&#xff0c;包括系统、功能、应用以及优势分析等方面&#xff0c;旨在为灌区的现代化和高效管理提供有力…

靶场练习 手把手教你通关DC系列 DC1

DC1靶场通关教程 文章目录 DC1靶场通关教程前言一、信息收集1.主机存活2.端口收集3.网页信息收集4.目录收集4.1 Nikto4.2 Dirb 信息收集总结 二、漏洞发现与利用1. 发现2. 利用 三、FlagFlag1Flag2Flag3Flag4Flag5(提权) 前言 本次使用的kali机的IP地址为192.168.243.131 DC1的…

倒计时 2 周!CommunityOverCode Asia 2024 IoT Community 专题部分

CommunityOverCode 是 Apache 软件基金会&#xff08;ASF&#xff09;的官方全球系列大会&#xff0c;其前身为 ApacheCon。自 1998 年以来&#xff0c;在 ASF 成立之前&#xff0c;ApacheCon 已经吸引了各个层次的参与者&#xff0c;在 300 多个 Apache 项目及其不同的社区中探…

给数组/对象添加一个(key-value)对象

需要将一个value值前面加上key值&#xff0c;放进数组/对象中 this.$set(res.data[0],type,1) this.$set( target, key, value ) target&#xff1a;要更改的数据源(可以是对象或者数组) key&#xff1a;要更改的具体数据 value &#xff1a;重新赋的值。 结果&#xff1a;…

05.C1W4.Machine Translation and Document Search

往期文章请点这里 目录 OverviewWhat you’ll be able to do!Learning Objectives Transforming word vectorsOverview of TranslationTransforming vectors Align word vectorsSolving for RFrobenius normFrobenius norm squaredGradient K nearest neighborsFinding the tr…

Open3D 点对面的ICP算法配准(精配准)

目录 一、概述 1.1核心思想 1.2实现步骤 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2配准后点云 3.3计算数据 一、概述 基于点对面的ICP&#xff08;Iterative Closest Point&#xff09;配准算法是ICP的一种变体&#xff0c;它通过最小化源…

骏网一卡通之类的游戏卡有什么用?

感觉现在打端游的人越来越少了 而且游戏充值卡显得就很鸡肋&#xff0c;在家里整理东西&#xff0c;翻出来好多骏网一卡通&#xff0c;但是我又不打游戏 想着把这卡送给有需要的朋友&#xff0c;不然也是浪费&#xff0c;问了一圈送不出去 还好最后在收卡云上卖掉了&#xf…

H桥驱动器芯片详解

H桥驱动器芯片详解 上一篇文章讲解了H桥驱动器的控制原理&#xff0c;本文以汽车行业广泛应用的DRV8245芯片为例&#xff0c;详细讲解基于集成电路的H桥驱动器芯片。 1.概述 DRV824x-Q1系列器件是德州仪器&#xff08;TI&#xff09;的一款专为汽车应用设计的全集成H桥驱动器…

【本地docker启动私有大模型】

一、最终效果 中英文对话 生成代码 二、资源配置 本文选择的模型运行内存需要 4G&#xff0c;因此宿主机建议内存大于8G&#xff0c;CPU建议 6 核以上&#xff1b; 参考博主该mac配置可以相对流畅运行。只需要 CPU资源&#xff0c;不需要 GPU。 三、搭建步骤 启动docker容…

羊大师:探索羊奶奥秘,解锁免疫力提升新篇章

在浩瀚的自然界中&#xff0c;羊奶以其独特的营养价值和健康益处&#xff0c;悄然成为提升免疫力的新宠。自古以来&#xff0c;羊奶就被视为珍贵的滋补佳品&#xff0c;而今&#xff0c;随着科学的深入探索&#xff0c;其提升免疫力的奥秘正逐渐揭开面纱。 羊奶中富含的免疫球蛋…

MQTT教程--服务器使用EMQX和客户端使用MQTTX

什么是MQTT MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎&#xff0c;能够实现传感器、执行器和其它设备…

字典树(Tire树)

字典树(Tire树) 字典树是一种多叉树&#xff0c;又称为前缀树。核心思想是利用字符串的公共前缀。 字典树的根节点为空&#xff0c;从根节点到某一节点路径上的字符连接起来构成字符串&#xff0c;完整的字符串在链上而非结点上&#xff0c;一个节点的所有子节点都具有相同公…

用Vue3和Plotly.js绘制交互式3D散点图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Plotly.js 创建 2D 密度图 应用场景介绍 密度图是一种可视化数据分布的图表&#xff0c;它显示了数据点的密度在不同区域的变化情况。在许多科学和工程领域中&#xff0c;密度图被广泛用于探索和分析数据…

产品经理/项目经理管理项目使用最多的12款项目软件对比

盘点不同行业、项目类型的下的12款主流的项目管理软件&#xff1a;PingCode、Worktile、Teambition、TAPD、广联达、Asana、Basecamp、Jira、Microsoft Project、ClickUp、Redmine、Trello。 在这个项目管理工具层出不穷的时代&#xff0c;选择一个合适的软件似乎成了一个令许多…

当CNN遇上Mamba,高性能与高效率通通拿下!

传统视觉模型在处理大规模或高分辨率图像时存在一定限制&#xff0c;为解决这个问题&#xff0c;研究者们就最近依旧火热的Mamba&#xff0c;提出了Mamba结合CNN的策略。 这种结合可以让Mamba在处理长序列数据时既能够捕捉到序列中的时间依赖关系&#xff0c;又能够利用CNN的局…

工业一体机为数字化工厂带来高效作业指导

随着工业4.0的浪潮席卷全球&#xff0c;数字化工厂的概念深入人心。在这一背景下&#xff0c;工业一体机作为数字化转型的重要一环&#xff0c;凭借其强大的功能和灵活的应用&#xff0c;为工厂实现高效作业指导提供了强大的助力。 一、工业一体机的优势&#xff1a;赋能数字化…