我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++为什么不能修改set里的值?非要修改怎么办?

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++为什么不能修改set里的值?非要修改怎么办?

在上一期C++中 set的用法文章当中讲解了set的一些常规用法和api,最后末尾的时候留了一个问题,如何修改set中的元素?今天就来聊聊这个问题。

很多同学估计会说,这还不简单,不是有迭代器么。我们把迭代器当做指针,去修改它指向的值不就行了吗?

like this:


set<string> st{"hello", "world", "good"};
set<string>::iterator it = st.begin();
*it = "test";

但是很遗憾,你真这么做了,会得到一个报错:

报错的意思是set的迭代器并没有重载等于符号,也就是说我们没办法使用等于符号来为它赋值。说白了,也就是编译器进行了限制,不允许我们对set迭代器的内容进行修改。

Effective C++当中也明确说了,不要对set集合中的元素进行修改。

不知道有没有小伙伴去尝试,可能有些小伙伴尝试了之后会说不对啊,在我电脑上怎么能运行?

也很简单,大概率因为你用的是vc编译器,比如臭名昭著的VC6.0或者是visual studio IDE(不是VSCode)。微软的编译器没有严格遵循C++的标准,在很多地方有些瑕疵和随意。这也是不推荐使用VC6.0进行C++学习的原因,因为时间久了,就把错的当成对的了。

吐槽完毕,回到正题。既然已经知道了这样修改会引发报错,是不是就已经得到了答案了呢?

其实并没有,因为如果我们真的去阅读C++的标准或者是翻阅set的源码,会发现其中是没有明确说明set中的元素是定义成const的。

实际上,std::set<T>声明一个allocator_type,默认为std::allocator<T>。std::allocator_traits<allocator_type>::construct将它传递给T *,从而构造一个T,而不是const T

说人话就是std::set<T>其实不允许将元素定义成const,既然元素不是const类型,那么就说明理论上是可以修改的。也就是说C++规范里说不能改,Effective C++中说建议不要改,但实际上底层的实现里并没有严格禁止。我们非要改还是有办法的,那是什么办法呢?

老梁纵观全网博客,也没有看到一篇把这个问题说清楚。

在我们开始之前,首先思考一个问题,既然set底层源码当中的元素并不是定义成const,那么当我们去用迭代器去修改的时候为什么会报错呢?

要回答这个问题,我们只需要查看一下set迭代器的源码定义即可。

老梁在大牛的源码分析当中找到了一行关键的代码:

原来迭代器的定义是一个const_iterator,搞了半天,其实并不是set底层限制了禁止修改,而是通过迭代器限制的。所以要想修改set当中的元素,我们只需要绕开迭代器的这个限制即可。

进一步研究可以发现,它这里使用的是一个const_iterator,它表示一个指向常量的迭代器,和const iterator不同。后者表示迭代器本身是一个常量,即迭代器本身指向的位置不能修改。而前者表示迭代器指向的位置是一个const常量,迭代器本身可以修改,指向不同的位置,但我们不能修改它指向的位置的值。

const_iterator并没有严格限制只能指向const修饰的变量,这也就能解释为什么set当中的元素没有const修饰也不会报错的原因,因为const_iterator兼容这种情况。

const_iterator解引用之后是一个const修饰的变量的引用,所以我们要对它指向的内容进行修改,只需要将它解引用的结果去除const限制即可。那具体怎么操作呢,我们可以使用const_cast操作符解除const的限制。

但它也不是万能的,它只能使用在引用和指针当中,用来去掉const属性。

这里有必要说明一下,在C++当中const修饰符出现的位置不同有不同的含义。以指针举例,const T* pT* const p是两种完全不同的指针,前者表示不能通过指针去修改指向对象的内容。如p->x = 100;这样的操作都是非法的。而后者表示指针只能在初始化时设置指向的内容,之后不能修改指向,如p=&t;是非法的。

在当前问题当中,我们想要修改set当中的元素值,遇到了const限制,显然是第一种情况。

有些同学可能会觉得疑惑,我们加上const的目的不就是为了对变量做限制,从而可以在编译的时候通过编译器来替我们检查一些非法的操作吗?既然如此,又为什么需要去掉呢?

主要的原因是有时候我们手上的变量有const修饰,但是我们想要调用一个函数,而函数的内部会对指针或引用指向的值进行修改。这个时候我们就没办法传入我们手上已有的参数了,const_cast操作符设计的初衷就是为了应对这种情况。

我们来举个例子:


void test(int *x) {
 *x = 5;
}

int main() {
 int a = 3;
 const int *p = &a;

 test(p);
 return 0;
}

如果我们编译上面这段代码就会遇到编译器无情地报错,因为我们在test函数内部修改了指针p的指向。

这个时候我们就可以在传参的时候,使用const_cast操作符来解除掉const的限制。


test(const_cast<int*>(p));

尖括号中是我们要转换的类型,只能是指针或引用。如果我们输出指针p指向的值,会得到5,因为在test函数当中进行了修改。

看起来好像很简单,对吧?

但是我们接下来看两个例子,可能会令人有些费解:


const int a = 3;
int *r = const_cast<int*>(&a);
(*r)++;
cout << a << endl;

int i = 3;
const int b = i;
int *r2 = const_cast<int*>(&b);
(*r2)++;
cout << b << endl;

这两段代码做的事情非常类似,也就是通过const_cast修改了一个const修饰的int。唯一的不同是int a是直接赋值成了3,而int b是赋值成了另外一个也等于3的int。这两者其实并没有什么区别,对吧?但是当我们运行代码之后,神奇的事情发生了,

屏幕上输出的结果是这样的:


为什么一个是3,另外一个是4呢?这两者的逻辑明明是一样的!

老梁发现这个问题的时候是完全震惊的,查了好久的资料,才从大牛博客的只言片语当中找到了一点描述。原来是编译器针对第一种情况做了优化,因为a初始化时给的是一个常量,所以当我们输出的时候,编译器就直接取了3代替了它实际原本应该的值。

关于这个解释老梁也不能完全确认,如果有知道的小伙伴不妨在下方留言。

最后, 我们回到正题,如果我们想要修改set当中的元素,可以怎么操作呢?

我们知道了const_cast操作符之后就完全没有悬念了。


set<string> st{"hello", "world", "good"};
set<string>::iterator it = st.begin();
const_cast<string&>(*it) = "test";

for (auto it = st.begin(); it != st.end(); it++) {
    cout << *it << endl;
}

但是我们需要注意一点,我们这样强行修改其实是没有经过set原本的机制的。也就是说我们虽然改了元素的值,但是它在红黑树中的位置其实是没有变的。这样的结果就是会导致元素失去有序性,比如上面的结果输出的顺序是:"test","hello","world",按道理应该是按照字典顺序排序的。

这也是为什么C++ Primer里强烈建议大家不要修改set中元素值的原因,如果真的要修改,只能先删除再添加了。虽然这样会牺牲一点点性能,但至少可以保证set里的数据都是安全有序的。

到此这篇关于C++为什么不能修改set里的值?非要修改怎么办?的文章就介绍到这了,更多相关C++修改set里的值内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

注:文章转自微信公众号:Coder梁(ID:Coder_LT)

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++为什么不能修改set里的值?非要修改怎么办?

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

win10不能修改为正常启动怎么办

如果Windows 10无法正常启动,以下是一些可能的解决方法:1. 使用安全模式启动:重启电脑,按下F8键进入高级启动选项,选择“安全模式”启动计算机。在安全模式下,可以尝试修复启动问题。2. 重置电脑:如果安全模式也无法启动,可以尝试重
2023-09-11

SAP SD VL02N为什么不能修改delivery里的storage location

SAP SD VL02N为什么不能修改delivery里的storage location,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。SAP SD VL02N 不能修改del
2023-06-05

uniapp不能修改input的高度怎么办

Uniapp是一种基于Vue.js开发的跨平台应用开发框架,是当前移动应用开发中备受关注的技术。在开发Uniapp应用的过程中,有时候我们需要对input组件的样式进行一些调整,比如改变input的高度。然而,我们会发现在Uniapp中无法直接修改input的高度。本文将探讨这个问题,并提供几种解决方案。一、为何Uniapp无法直接修改input的高度在传统web开发中,我们可
2023-05-14

阿里云数据库为什么不能修改数据

简介阿里云数据库是一种可靠、安全、高性能的云数据库服务,广泛应用于各种企业和个人的应用场景中。然而,很多用户在使用阿里云数据库时会遇到无法修改数据的问题,这给用户的业务带来了一定的困扰。本文将探讨为什么阿里云数据库不能修改数据,并提供一些解决方案。无法修改数据的原因阿里云数据库不能修改数据的主要原因是出于数据一致性和安
阿里云数据库为什么不能修改数据
2024-01-20

Golang中的字符串类型为什么不能修改

今天小编给大家分享一下Golang中的字符串类型为什么不能修改的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。字符串定义字符串
2023-07-05

详解为什么说Golang中的字符串类型不能修改

在接触Go这么语言,可能你经常会听到这样一句话。对于字符串不能修改,可能你很纳闷,日常开发中我们对字符串进行修改也是很正常的,为什么又说Go中的字符串不能进行修改呢?本文就来通过实际案例给大家演示一下
2023-03-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录