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

news2025/1/10 11:51:35

几种传参方式简单对比

  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.coloradmin.cn/o/787469.html

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

相关文章

Mac电脑必备:3款优质系统软件推荐

对于Mac电脑使用者来说&#xff0c;良好的系统软件是确保计算机高效运行和提升使用者体验的关键。无论是日常办公、娱乐还是创意设计&#xff0c;一系列优质的系统软件都能为使用者带来更顺畅、更便捷的操作体验。在本文中&#xff0c;我们将推荐3款在Mac电脑上必备的优质系统软…

如何编写银行转账的测试用例,可以来看这里.....

前言 最近呢有很多的小伙伴问我有没有什么软件测试的面试题&#xff0c;由于笔者之前一直在忙工作上的事情&#xff0c;没有时间整理面试题&#xff0c;刚好最近休息了一下&#xff0c;顺便整理了一些面试题&#xff0c;现在就把整理的面试题分享给大家&#xff0c;废话就不多…

简单易用的DuckDB数据库管理系统

大家好&#xff0c;DuckDB是一个免费的、开源的、嵌入式数据库管理系统&#xff0c;专为数据分析和在线分析处理而设计。这意味着以下几点&#xff1a; 它是免费的开源软件&#xff0c;因此任何人都可以使用和修改代码。 它是嵌入式的&#xff0c;这意味着DBMS&#xff08;数据…

C++中的数学问题---进制转换

二进制转十六进制 string binToHex(string bin){string hex"";if(bin.size()%4!0){for(int i0;i<(4-bin.size()%4);i){bin"0"bin;}}for(int i0;i<bin.size();i4){string tmpbin.substr(i,4);bitset<4>b(tmp);hexb.to_ulong()<10?char(b.t…

定点乘法器优化(3)---华为杯

一. 简介 在上次优化中&#xff0c;针对部分积生成进行了一个优化&#xff0c;将一个部分积生成的门电路数从221减少到了119。虽然减少了很多&#xff0c;但不够。本次将提出另外一种新的编码与部分积生成方式&#xff0c;将门电路的个数大大减少。 二. 新的编码方式 基4 Bo…

fiddler基本使用(上)

目录 1、Fiddler介绍 2、fiddler下载及安装证书 4、抓取手机包 5、fifter 数据包过滤 6、模拟限速 1、Fiddler介绍 本质&#xff1a;位于客户端与服务器端之间的代理。 fiddler会捕获客户端、服务器端的包&#xff0c;发送给彼此。 代理设置&#xff1a; 2、fiddler下载及…

VMware虚拟机无法上网的解决办法

&#xff08;1&#xff09;1、在虚拟机右下角的网络适配器上面观察该图标是否是有绿色的灯在闪烁&#xff0c;如果网络适配器是灰色的证明虚拟机的网络没有打开&#xff0c;而是被禁用了&#xff0c;在适配器上点击鼠标右键&#xff0c;打开【设置】&#xff0c;在【已连接】、…

医院影像PACS系统和放射影像科业务

前言&#xff1a;对于医院的放射科来说&#xff0c;要实现其业务效率&#xff0c;增强患者的就医体验&#xff0c;提升医院的服务质量&#xff0c;那么一个良好的PACS系统能够高效实现这一目标。本文以放射科为例&#xff0c;对PACS系统和就医流程进行一个简单的介绍&#xff0…

Golang time 包以及日期函数

time 包 在 golang 中 time 包提供了时间的显示和测量用的函数。 time.Now()获取当前时间 可以通过 time.Now()函数获取当前的时间对象&#xff0c;然后获取时间对象的年月日时分秒等信息。 示例代码如下&#xff1a; package mainimport ("fmt""time" )…

js实现上下无缝滚动(不卡顿)

效果图如下&#xff1a; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport"…

Java阶段五Day13

Java阶段五Day13 文章目录 Java阶段五Day13回顾昨天思路Redis五种数据类型和类型无关的命令基本类型——String基本类型——hash基本类型——list基本类型——set基本类型——zset&#xff08;sorted set&#xff09; Redis实现分布式锁抢锁的设计思路整改当前消费逻辑添加分布…

前端 | ( 十三)CSS3简介及基本语法(下)| 伸缩盒模型 | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 系列笔记&#xff1a; 【HTML4】&#xff08;一&#xff09;前端简介【HTML4】&#xff08;二&#xff09;各种各样的常用标签【HTML4】&#xff08;三&#xff09;表单及HTML4收尾…

网工内推 | 网络安全工程师,有安全相关证书优先

01 航天四创科技有限责任公司 招聘岗位&#xff1a;网络安全工程师 职责描述&#xff1a; 1、根据项目的投标技术方案、适配测试方案等&#xff0c;制定网络系统、安全系统、主机系统、存储系统等的深化设计方案和实施方案&#xff1b; 2、安装、配置和搭建基于软硬件设备的网…

RocketMQ基本概念与入门

文章目录 MQ基本结构依赖案例:productConsumer 核心概念1.nameserver2.broker3.主题队列4.queue队列5. 生产者6.消费者分组和生产者分组7.消费点位 MQ基本结构 message: 消息数据对象product: 程序代码,生成消息,发送消息到队列consumer: 程序代码,监听(绑定)队列,获取消息,执行…

MPU6050简介

文章目录 简介参数IIC通信的从机地址硬件电路框图 简介 MPU6050是一个6轴姿态传感器&#xff0c;可以测量芯片自身X,Y,Z轴的加速度&#xff0c;角速度&#xff0c;从而得到姿态角&#xff0c;用于平衡车&#xff0c;飞行器等。 内部结构&#xff1a; 3轴加速度计&#xff08;A…

数据库运维作业2

1.理解MySQL主从复制原理。 主要基于MySQL二进制日志 主要包括三个线程&#xff08;2个I/O线程&#xff0c;1个SQL线程&#xff09; 1&#xff09;MySQL将数据变化记录到二进制日志中&#xff1b; 2&#xff09;Slave将MySQL的二进制日志拷贝到Slave的中继日志中&#xff1b; 3…

vscode插件unocss无法正常使用

官网文档&#xff1a;https://alfred-skyblue.github.io/unocss-docs-cn/integrations/vscode#VPSidebarNav 无法使用的原因 很多人在使用的时候会配置uno.config.ts文件&#xff0c;但这个文件不一定放置在根目录下。扩展程序将尝试在您的项目中查找 UnoCSS 配置。如果未找到…

selenium 获取请求响应信息,包括请求的响应头和响应体

在我们使用selenium请求网页时&#xff0c;有时不想从浏览器解析后的html标签获取数据&#xff0c;如果能直接获取url返回的json格式数据会更容易解析。就像request和scrapy爬虫返回的响应数据一样。那么&#xff0c;我们用selenium应该怎么做呢&#xff1f; selenium并不支持…

pytest——断言后继续执行

前言 在编写测试用例的时候&#xff0c;一条用例可能会有多条断言结果&#xff0c;当然在自动化测试用例中也会遇到这种问题&#xff0c;我们普通的断言结果一旦失败后&#xff0c;就会出现报错&#xff0c;哪么如何进行多个断言呢&#xff1f;pytest-assume这个pytest的插件就…

Git的核心概念:探索Git中的提交、分支、合并、标签等核心概念,深入理解其作用和使用方法

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…