【C++】容器对象作为函数参数传递时,如何保证外部容器对象不被修改(以vector为例)

几种传参方式简单对比

  1. 传值
    1.1 参数形式:void fun(vector<int> v);
    1.2 函数调用:fun(v);
    1.3 函数内使用:cout << v[1];
    1.4 是否可以改变函数外对象的值:否
    1.5 是否会调用拷贝构造函数:
  2. 传指针
    2.1 参数形式:void fun(vector<int>* p);
    2.2 函数调用:fun(&v);
    2.3 函数内使用:cout << (*p)[1];
    2.4 是否可以改变函数外对象的值:是
    2.5 是否会调用拷贝构造函数:
  3. const+传指针
    3.1 参数形式:void fun(const vector<int>* p);
    3.2 函数调用:fun(&v);
    3.3 函数内使用:cout << (*p)[1];
    3.4 是否可以改变函数外对象的值:否
    3.5 是否会调用拷贝构造函数:
  4. 传引用
    4.1 参数形式:void fun(vector<int>& v);
    4.2 函数调用:fun(v);
    4.3 函数内使用:cout << v[1];
    4.4 是否可以改变函数外对象的值:是
    4.5 是否会调用拷贝构造函数:
  5. const+传引用
    5.1 参数形式:void fun(const vector<int>& v);
    5.2 函数调用:fun(v);
    5.3 函数内使用:cout << v[1];
    5.4 是否可以改变函数外对象的值:否
    5.5 是否会调用拷贝构造函数:

结合代码对比

下面,我们来探讨,各种方式是否会修改到函数外部的v。
如果会,那我就把它定性为一种有风险的行为。如果不会,我就把它定性为一种安全的行为

一、传值

#include <iostream>
#include<vector>void changeValue(std::vector<int> v) {//修改v里面的值v[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << v[0] << std::endl;
}
void changeReference(std::vector<int> v) {//修改v的指向std::vector<int> c;c.push_back(3);v = c;std::cout << "修改指向之后的第一个元素(函数内): " << v[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReference(v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;
}

在这里插入图片描述
可见,传值的话,如果在函数内修改了v的值或者指向,函数外的v也不会受到任何影响。因此是一种安全的行为。

二、传指针

#include <iostream>
#include<vector>void changeValue(std::vector<int>* p) {//修改v里面的值(*p)[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferenced(std::vector<int>* p) {//修改v的指向的指向std::vector<int> c;c.push_back(3);p = &c;std::cout << "修改指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferencedReference(std::vector<int>* p) {//修改v的指向std::vector<int> c;c.push_back(3);*p = c;std::cout << "修改指向的对象的指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(&v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferenced(&v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferencedReference(&v);std::cout << "修改指向的对象的指向之后的第一个元素(函数外): " << v[0] << std::endl;
}

在这里插入图片描述

  • 由changeValue的结果可见,传指针的话,如果在函数内修改了p对应的元素的值,函数外的v会受到一样的影响;
  • 由changeReferenced的结果可见,如果在函数内修改了p的指向,则函数外的v不会受到任何影响,因为此时的p已经不再关联外部的v了
  • 然而,由changeReferencedReference的结果可见,如果不是修改p的指向,而是修改了p指向的对象的指向,那么结果就会相反,此时函数外的v也会受到一样影响!
  • 所有,传递指针,是有影响到外部的v的风险的,是一种有风险的行为

三、const+传指针

#include <iostream>
#include<vector>void changeValue(const std::vector<int>* p) {//修改v里面的值(*p)[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferenced(const std::vector<int>* p) {//修改v的指向std::vector<int> c;c.push_back(3);p = &c;std::cout << "修改指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}
void changeReferencedReference(const std::vector<int>* p) {//修改v的指向的指向std::vector<int> c;c.push_back(3);*p = c;std::cout << "修改指向的对象的指向之后的第一个元素(函数内): " << (*p)[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(&v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferenced(&v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReferencedReference(&v);std::cout << "修改指向的对象的指向之后的第一个元素(函数外): " << v[0] << std::endl;
}

在这里插入图片描述

  • 由图可知,我们无法修改p指向的容器的值,也无法修改p指向的容器的指向,因此这两种情况都不会导致外部的v被改变。
  • 但是,我们此时可以修改p的指向,那么我们有没有可能改变函数外的v呢。由上一条实验三我们知道,修改了p的指向之后,并不会影响到外部的v,因为此时p已经脱离了和v的关联了。所以修改p的指向,是不会影响到外部的v的风险的。
  • 综上,const+传指针的方式,是不会影响到外部的v的,是一种安全的行为

四、传引用

#include <iostream>
#include<vector>void changeValue(std::vector<int>& v) {//修改v里面的值v[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << v[0] << std::endl;
}
void changeReference(std::vector<int>& v) {//修改v的指向std::vector<int> c;c.push_back(3);v = c;std::cout << "修改指向之后的第一个元素(函数内): " << v[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReference(v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;
}

在这里插入图片描述
可见,传引用的话,如果在函数内修改了v的值或者指向,函数外的v也会受到一样的影响。因此是一种有风险的行为

五、const+传引用

#include <iostream>
#include<vector>void changeValue(const std::vector<int>& v) {//修改v里面的值v[0] = 2;std::cout << "修改值之后的第一个元素(函数内): " << v[0] << std::endl;
}
void changeReference(const std::vector<int>& v) {//修改v的指向std::vector<int> c;c.push_back(3);v = c;std::cout << "修改指向之后的第一个元素(函数内): " << v[0] << std::endl;
}int main()
{std::vector<int> v;v.push_back(1);std::cout << "原本的第一个元素: " << v[0] << std::endl;changeValue(v);std::cout << "修改值后的第一个元素(函数外): " << v[0] << std::endl;v[0] = 1;std::cout << "\n原本的第一个元素: " << v[0] << std::endl;changeReference(v);std::cout << "修改指向之后的第一个元素(函数外): " << v[0] << std::endl;
}

在这里插入图片描述
如图,由于const,我们无法修改v的元素的值,也无法修改v的指向。所以我们不会改变到函数外的v。也是一种安全的行为

总结
如果我们想要安全(不修改函数外的v的值),只有以下三种方式:

  1. 传值
  2. const+传指针
  3. const+传引用。
  • 这几种方法中,如果从是否调用拷贝构造函数的角度考虑,应该选择 const+指针 ,或者const+引用因为他们是不调用拷贝构造函数的
  • 但是如果从书写的方便性来说,应该选择 传值const+传引用 这两种方式,他们比const+传指针要更加便于书写,因为这两者只需要用v就可以代表传进来的vector对象,而const+传指针需要用(*p)的方式来代表,多了一个*,而且有时候还要加括号(),防止优先级有问题,比如要用[ ]取值的时候,必须写成类似(*p)[0],而不能是*p[0]这样的。
  • 实际使用中,似乎更多人用 const+传引用

const+引用是否调用拷贝构造函数

文章 C++(笔记)容器(vector)作为函数参数如何传参 中却认为,const+传引用调用拷贝构造函数。我认为这应该是错误的。因为大多数其他的文章都认为, const+传引用不会调用拷贝构造函数**。如 C++拷贝构造函数(复制构造函数)详解、C++ 中的 const & (常引用)参数 、C++ 基础之 “引用形参” 和 “利用const引用避免复制” & 等等。所以还是服从多数,const+引用不会调用拷贝构造函数。这也是它相比于直接传值的重要优势。


不调用拷贝构造函数的好处

具体可以学习C++拷贝构造函数(复制构造函数)详解这一篇文章。
我总结一下,好处可能有两个:

  1. 可以减少空间开销。
  2. 可以避免在类中定义了一个不好的拷贝构造函数,从而导致了形参和实参不一致。

补充:使用 const+引用/指针 比单独使用 引用/指针 作为参数的好处

  1. 更加安全。这一点毋庸置疑。加了const以后,不管是指针还是引用都不用担心更改到外部的对象的内容。
  2. 使得函数可以接受常量对象作为参数。除此之外,还有一个很重要的,就是,哪怕你在函数内仔细控制,保证不会修改引用/指针指向的外部对象,也建议加一个const。那是因为,这样就可以向函数传递常量对象,而直接使用 引用/指针 作为参数的话,是无法向函数传递常量对象的。(这里我还是参考了C++拷贝构造函数(复制构造函数)详解这篇文章,虽然文章里只是对拷贝构造函数的参数写法进行的建议,但是我认为道理其实是通用的)

如下,如果我使用 vector<int>& v作为参数(没有加const),那么我是无法传递一个常量vector对象进去的。
在这里插入图片描述
然而,如果我使用了 const vector<int>& v作为参数(加了const),我就可以传递一个常量vector对象。

#include <iostream>
#include<vector>void printValue(const std::vector<int>& v) {std::cout << v[0];
}int main()
{const std::vector<int> v = { 1, 2, 3 };printValue(v);
}

在这里插入图片描述
可以接受常量对象的话,整个函数的可用性会更加强。

参考链接

C++拷贝构造函数(复制构造函数)详解
C++中vector作为参数的三种传参方式(传值 && 传引用 && 传指针)
C++ 中的 const & (常引用)参数
C++ 基础之 “引用形参” 和 “利用const引用避免复制” &
C++(笔记)容器(vector)作为函数参数如何传参 (疑似有误)

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

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

相关文章

快速了解新一轮Moonbeam Grants申请提案

随着Moonbeam Grant第二期计划的发布&#xff0c;超过12个项目同时提交了生态Grant申请的提案。任何大于25万枚GLMR Grant的申请都将会要求项目在Moonbeam社区治理论坛上发布Grant提案&#xff0c;内容包含项目概览、申请金额、Grant使用方案以及背后的原因等等。 Grant的发放…

【软件架构】企业架构4A定义

文章目录 前言战略、BA、DA、AA、TA五者的关系1、业务架构&#xff08;BA&#xff09;2、数据架构&#xff08;DA&#xff09;3、应用架构&#xff08;AA&#xff09;4、技术架构&#xff08;TA&#xff09;总结 前言 业务架构是跨系统的业务架构蓝图&#xff0c;应用架构、数…

Redis

介绍 redis是内存数据结构存储&#xff0c;可用作数据库、缓存、消息代理和流引擎。 提供的数据结构有&#xff1a;String、hash、lists、sets、sorted sets、bitmaps、hyperloglogs、streams 字符串、哈希、列表、集、带有范围查询的排序集、位图、超日志日志、地理空间索引…

【每日运维】RockyLinux8非容器化安装Mysql、Redis、RabitMQ单机环境

系统版本&#xff1a;RockyLinux 8.6 安装方式&#xff1a;非容器化单机部署 安装版本&#xff1a;mysql 8.0.32 redis 6.2.11 rabbitmq 3.11.11 elasticsearch 6.7.1 前置条件&#xff1a;时间同步、关闭selinux、主机名、主机解析host 环境说明&#xff1a;PC电脑VMware Work…

互联网医院系统|互联网医院软件开发|互联网医院搭建方案

随着互联网技术的发展&#xff0c;互联网医院系统逐渐成为医疗服务的新模式&#xff0c;为患者和医生提供了更加方便和高效的医疗体验。下面将介绍互联网医院系统的功能优势。   在线挂号和预约&#xff1a;互联网医院系统可以提供在线挂号和预约功能&#xff0c;患者可以通过…

C++_01_初步认识C++语言

本人博客园亦可见 一、认识 “C语言” 一、首先聊聊什么是语言&#xff1f; 语言是一套具有“语法”、“词法”规律的系统&#xff0c;是思维的工具。   计算程序设计语言是计算机可以识别的语言&#xff0c;用于描述解决问题的方法&#xff0c;供计算机阅读和执行。 语言由…

vue中实现列表自由拖拽排序

元素的 dragable 属性设置 为 true &#xff08;文本 图片 链接 的draggable 属性默认为 true&#xff09;则元素可拖放 <template><transition-group class"list"><uldragstart"dragstart(index)"dragenter"dragenter($event, index)…

【设计模式——学习笔记】23种设计模式——原型模式Prototype(原理讲解+应用场景介绍+案例介绍+Java代码实现)

原型模式 介绍 原型模式指用通过拷贝原型实例创建新的实例&#xff0c;新实例和原型实例的属性完全一致原型模式是一种创建型设计模式工作原理是通过调用原型实例的 clone()方法来完成克隆&#xff0c;原型实例需要实现Cloneable接口&#xff0c;并重写clone()方法需要为每个…

naive-ui的dialog.warning 关闭和阻止关闭

序&#xff1a; 1、如果你卡到 了&#xff0c;博主没写博客&#xff0c;可以在博主的公众号&#xff1a;“程序员野区” 留言。博主看到有时间再帮你去试 2、博主主要讲的怎么 主动关闭dialog和阻止dialog 自动关闭。 注意&#xff01;&#xff01;&#xff01;&#xff01;来&…

Spring Data Redis操作Redis

在Spring Boot项目中&#xff0c;可以使用Spring Data Redis来简化Redis操作&#xff0c;maven的依赖坐标&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></…

5分钟搞懂池化的本质

大家好啊&#xff0c;我是董董灿&#xff01; 在很多与计算机视觉相关的神经网络中&#xff0c;我们往往都会看到池化这一算法&#xff0c;它一般跟在卷积层后面。 神经网络中用到最多的池化方式无外乎是最大池化和平均池化。两者运算接近&#xff0c;区别在于是在kernel范围…

在使用《快递批量查询高手》时从TXT文本导入的快递单号出现乱码如何解决。

在日常 工作中&#xff0c;有没有单号用TXT 文档保存吗&#xff1f;那么没有出现这种情况呢&#xff0c;打开TXT文档进去看都是正常没有问题&#xff0c;一但导入软件中就出现乱码的&#xff1f;遇到这个种情况需要什么解决呢&#xff1f;小编今天就教 您一招解决好方法&#x…

电脑微信空间占用简便清理

1、打开电脑版微信、点击左下角的三根横线 2、点击左侧的“设置” 3、弹出层左侧点击“通用设置”->“存储空间管理” 4、点击清理缓存&#xff0c;或者管理 5、点击“管理”后&#xff0c;根据选择的筛选条件&#xff0c;勾线需要清理的&#xff0c;最后点击清理

WormGPT – 网络犯罪分子用来犯罪的人工智能工具

WormGPT – 网络犯罪分子用来发起商业电子邮件泄露攻击的生成式人工智能工具 前言 什么是蠕虫GPT&#xff08;WormGPT&#xff09; WormGPT是基于EleutherAI于2021年创建的大型语言模型GPT-J的AI模型。它具有无限的字符支持、聊天记忆保留和代码格式化功能。 如果未部署适当…

C#|无法打开cs文件设计窗口

报错信息&#xff1a;To prevent possible data loss before loading the designer, the following errors must be resolved: 解决方案&#xff1a;实不相瞒我把项目解决方案名称改短了就可以了。。有其他原因或者解决方案望不吝赐教。。

electron dialog.showMessageBox使用案例

electron 版本&#xff1a;25.3.1 index.html <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>Hello World!</title><meta http-equiv"Content-Security-Policy" content"script-src self unsa…

计算机视觉(三)未有深度学习之前

文章目录 图像分割基于阈值、基于边缘基于区域、基于图论 人脸检测Haar-like特征级联分类器 行人检测HOGSVMDPM 图像分割 把图像划分成若干互不相交的区域。经典的数字图像分割算法一般是基于灰度值的两个基本特征之一&#xff1a;不连续性和相似性。 基于阈值、基于边缘 基于…

《论文阅读》具有特殊Token和轮级注意力的层级对话理解 ICLR 2023

《论文阅读》具有特殊Token和轮级注意力的层级对话理解 前言简介问题定义模型构建知识点Intra-turn ModelingInter-turn Modeling分类前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失望? 小白如何从零读懂论文?和我一…

MySQL事务

目录 前言 1.为什么存在事务 2.什么是事务 3.事务的版本支持 4.事务提交方式 5.事务常见操作方式 6.事务隔离级别 6.1如何理解隔离性 6.2隔离级别 6.3隔离性的查看与设置 6.4读未提交 6.5读提交 6.6可重复读 6.7串行化 7.多版本并发控制 7.1 3个记录隐藏列字段…

面向对象编程:从创建类到封装与构造方法的探索

1. 代码如何创建类&#xff1f; 在面向对象编程中&#xff0c;类是对一类事物的抽象&#xff0c;包含了静态的属性&#xff08;成员变量&#xff09;和动态的行为&#xff08;成员方法&#xff09;。在Java中&#xff0c;创建类的格式如下&#xff1a; 修饰词 class 类名 {//…