6.2 参数传递

news2024/11/15 17:39:52

文章目录

    • 传值参数
      • 指针形参
    • 传引用参数
      • 使用引用避免拷贝
      • 使用引用形参返回额外信息
    • const形参和实参
      • 指针或引用形参与const
      • 尽量使用常量引用
    • 数组形参
      • 使用标记指定数组长度
      • 使用标准库规范
      • 显式传递一个数组的长度
      • 数组形参和const
      • 数组引用形参
      • 传递多维数组
    • main:处理命令行选项
    • 含有可变形参的函数
      • initializer_list形参
      • 省略符形参

如前所述,每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。

和其他变量一样,形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。当形参是引用类型时,我们说它对应的实参被引用传递(或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是它对应的实参的别名。

当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用。

传值参数

当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响对应的初始值。

指针形参

指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值

#include<iostream>
using namespace std;

void result(int* p)
{
    cout << "前:result内部的*p:" << *p << endl;
    cout << "前:result内部的p:" << p << endl;
    *p = 0;
    p = NULL;
    cout << "后:result内部的p:" << p << endl;
}

int main()
{
    int i = 42;
    int* q = &i;
    result(q);
    cout << "外部的*q:"<< * q << endl<<"外部的q:" << q << endl;
    return 0;
}

输出结果:
在这里插入图片描述
所以对于上述情况下,有很多未知的情况会被我们直接忽略:

熟悉C的程序员常常使用指针类型的形参访问函数外部的对象。在C++语言中,建议使用引用类型的形参替代指针。

传引用参数

回忆过去所学的知识,我们知道对于引用的操作实际上是作用在引用所引的对象上

#include<iostream>
using namespace std;

void result(int& p)
{
    cout << "前:result内部的p:" << p << endl;
    p = 0;
    cout << "后:result内部的p:" << p << endl;
}

int main()
{
    int i = 42;
    cout << "前:外部的p:" << i << endl;
    result(i);
    cout << "前:外部的p:" << i << endl;
    return 0;
}

输出结果:
在这里插入图片描述
在上述调用过程中,形参p仅仅是的i的一个名字。在result内部对p的使用即是对i的使用。

使用引用避免拷贝

拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括I0类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。

举个例子,我们准备编写-一个函数比较两个string对象的长度。因为string对象可能会非常长,所以应该尽量避免直接拷贝它们,这时使用引用形参是比较明智的选择。又因为比较长度无须改变string 对象的内容,所以把形参定义成对常量的引用:

样例:

void my_function(const string&s1,const string& s2)
{
    if (s1.size()>s2.size())
    {
        cout << "前者大";
    }
    else if (s1.size()==s2.size())
    {
        cout << "一样大";
    }
    else
    {
        cout << "后者大";
    }
}

使用引用形参返回额外信息

一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。举个例子,我们定义一个名为find char 的函数,它返回在string对象中某个指定字符第一次出现的位置。同时,我们也希望函数能返回该字符出现的总次数。

该如何定义函数使得它能够既返回位置也返回出现次数呢?一种方法是定义一个新的数据类型,让它包含位置和数量两个成员。还有另一种更简单的方法,我们可以给函数传入一个额外的引用实参,令其保存字符出现的次数:

样例:

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

int my_function(const string&s1,char c,int& num)
{
    num = 0;
    int ret = 0;
    for (size_t i = 0; i < s1.size(); i++)
    {
        if (s1[i] == c)
        {
            num++;
            if (num==1)
            {
                ret = i+1;
            }
        }
    }
    return ret;
}

int main()
{
    int num;
    string s1 = "adqwdawdwadwadaw";
    if (my_function(s1, 'd', num)!=0)
    {
        cout << "出现第一次d是第" << my_function(s1, 'd', num) << "位数(从1开始)" << endl;
        cout << "出现的次数是:" << num<<endl;
    }
    else
    {
        cout << "找不到该元素"<<endl;
    }
    return 0;
}

输出结果:
在这里插入图片描述

const形参和实参

当形式参数是const类型的时候,我们需要注意的是关于顶层const的讨论。

void fcn(const int i)
void fcn(int i)
/*上面两个重复了,虽然看的出来一个是可以修改内部的值,一个是不能,
但对于程序来说,这两个就是重复的,原因大家学完顶层const和函数重载,
可以自己想想为什么,还是很好理解的。*/

指针或引用形参与const

形参的初始化方式和变量的初始化方式是一样的,所以回顾通用的初始化规则有助于理解本节知识。我们可以使用非常量初始化一个底层 const 对象,但是反过来不行,同时一个普通的引用必须用同类型的对象初始化。

尽量使用常量引用

把函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。就像刚刚看到的,我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。

总而言之,不采用常量引用可能造成传入的类型错误,比如 string& s="hello"类似的这一种错误,还有就是修改了该string内部的值,导致此string后续再也用不了。

为了解决需要修改时,我们可以不采用引用,如此就相当于拷贝构造桉树,或者形参是const &类型的内部进行一次拷贝构造。

数组形参

数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:不允许拷贝数组以及使用数组时(通常)会将其转换成指针。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。

void function(const int*)
void function(const int[])
void function(const int[number])

尽管表现形式不同,但上面的三个函数是等价的:每个函数的唯一形参都是constint类型的。当编译器处理对function函数的调用时,只检查传入的参数是否是const int类型:

int i = 1 , j[2] = {1,2} ;
function(&i);//正确
function(j);//正确,即使我们传入的是一个数组,也会自动转化为指针进行传入

和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界。

因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。管理指针形参有三种常用的技术。

使用标记指定数组长度

管理数组实参的第一种方法是要求数组本身包含一个结束标记,使用这种方法的典型示例是C风格字符串。C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符。函数在处理C风格字符串时遇到空字符停止。

这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况,但是对于像int这样所有取值都是合法值的数据就不太有效了。

使用标准库规范

管理数组实参的第二种技术是传递指向数组首元素和尾后元素的指针,这种方法受到了标准库技术的启发。

void function(const int* beg,const int* end)
{
	while(beg!=end)
	{
		cout<<*beg++<<endl;
	}
}

而这种情况下对于函数的使用就需要:

int j[2] = {1,2};
function(begin(j),end(j));

这也是本文比较推荐的方式。

显式传递一个数组的长度

第三种管理数组实参的方法是专门定义一个表示数组大小的形参,在C程序和过去的C++程序中常常使用这种方法。使用该方法,可以将function函数重写成如下形式:

void function(const int* p,size_t size)
{
	for(size_t i = 0;i != size;i++)
	{
		cout<<*i<<endl;
	}
}

数组形参和const

我们的三种方式下函数都把数组形参定义成了指向const的指针,关于引用的讨论同样适用于指针。当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。

数组引用形参

C++语言允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上:

void function(const int(&arr)[2])
{
    for (int i : arr)
    {
        cout<<i<<endl;
    }
}

值得注意的是,此时内部的2必须填上,这样才会被当作数组。

而此时我们在使用这个函数的时候就一定要传入一个大小为2的数组,比如上文的j数组。

PS:

int j[2] ={1,2}; 
int (&r)[2] = &j;//正确,r是数组j的别名
int &r[2] = &j;//错误,引用的数组,不存在引用的数组。

传递多维数组

我们曾经介绍过,在C++语言中实际上没有真正的多维数组,所谓多维数组其实是数组的数组。

和所有数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理的是数组的数组,所以首元素本身就是一一个数组,指针就是一一个指向数组的指针。数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略:

void function(const int (*arr)[10]);
void function(const int arr[][10]);

上述两种做法也都是一致的,但是值得注意的是10那个数字是一定要填上的,第二种做法下的[]可填可不填(建议不填)。

main:处理命令行选项

第二个形参argv是一个数组,它的元素是指向C风格字符串的指针;第一个形参 argc表示数组中字符串的数量。

而具体理解,实际上就是运行main函数之前,使用者输入的字符串的数目,以及具体的是什么,值得注意的是:对于argv[0]来说存储的一定是main函数的执行地址。所以实际上是从argv[1]开始储存的。

实际上这里的用法主要体现在linux的c表达上,这里只是简单的介绍一下(用处不大):

#include<iostream>

using namespace std;

int main(int argc , char* argv[])
{
    int i;
    string s = "aaa";
    cout << "argc=" << argc<<endl;
    for ( i = 0; i < argc; i++)
    {
        cout << "argv[" << i << "]=" << argv[i]<<endl;
    }
    return 0;
}

这段代码可以自行运行一下,促进一下理解。

含有可变形参的函数

有时我们无法提前预知应该向函数传递几个实参。例如,我们想要编写代码输出程序产生的错误信息,此时最好用同一个函数实现该项功能,以便对所有错误的处理能够整齐划一。然而,错误信息的种类不同,所以调用错误输出函数时传递的实参也各不相同。为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_ list 的标准库类型;如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板,关于它的细节将在之后进行介绍。

C++还有一种特殊的形参类型(即省略符),可以用它传递可变数量的实参。本节将简要介绍省略符形参,不过需要注意的是,这种功能一般只用 于与C函数交互的接口程序。

initializer_list形参

如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_ list类型的形参。initializer_ list是一种标准库类型,用于表示某种特定类型的值的数组。initializer_ list类型定义在同名的头文件中,它提供的操作如下表。

格式意义
initializer_list<T> p ;默认初始化,T类型元素的空列表
initializer_list<T> p{a,b,c···} ;拷贝初始化,对应元素相整合,内部元素的类型是const的
p1(p2)或者p1=p2拷贝或者赋值
p.size()返回列表中元素的数量
p.begin()返回头指针
p.end()返回尾指针

和vector一样,initializer_list 也是一种模板类型。和vector不一样的是,initializer_ list 对象中的元素永远是常量值,我们无法改变initializer_ list 对象中元素的值。

给个样例:

#include<iostream>
#include<initializer_list>
#include<string>

using namespace std;

void my_function(initializer_list<string> list)
{
	for (auto beg = list.begin();beg!=list.end();beg++ )
	{
		cout << *beg << " ";
	}
	cout << endl;
}

int main()
{
	bool flag;
	cin >> flag;
	if (flag)
	{
		my_function({ "正确的输出",":","Hello World" });
	}
	else
	{
		my_function({ "错误的输出","helloworld" });
	}
    return 0;
}

输出结果:
在这里插入图片描述
在这里插入图片描述

省略符形参

省略符形参是为了便于C++程序访问某些特殊的C代码而设置的这些代码使用了名为varargs的C标准库功能。通常,省略符形参不应用于其他目的。你的C编译器文档会描述如何使用varargs。

省略符形参应该仅仅用于C和C++通用的类型。特别应该注意的是,大多数类型的对象在传递给省略符形参时都无法正确拷贝。

但实际上用处很小,建议还是用上面的用法。

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

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

相关文章

142.环形链表II

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数…

java开发社区活动预约系统

简介 本系统主要是社区活动预约系统网站&#xff0c;社区管理员可以发布活动&#xff0c;社区居民和游客均可进行活动预约&#xff0c;管理员后台审核预约是否通过&#xff0c;居民可以填写活动感受&#xff0c;管理员查看感受后可以进行反馈。居民最多取消三次预约&#xff0…

我用Python做了个动图生成器,把一千个MM生成了GIF设置桌面,只为每天愉悦心情

文章目录序言代码实战序言 现在的年轻人都开始每天保温杯里泡枸杞&#xff0c;这怎么能行呢&#xff1f; 想要每天过的好&#xff0c;美女必然少不了&#xff0c;每天看美女&#xff0c;只为了愉悦心情&#xff0c;心情好了&#xff0c;才长寿。 于是怀揣着愉悦心情的想法&am…

【leetcode】剑指offer1

&#x1f308;1.Pow(x,n) -100.0 < x < 100.0-2^31 < n < 2^31-1n 是一个整数-10^4 < x^n < 10^4思路分析&#xff1a; 暴力求解直接一个for循环n个x相乘解决&#xff0c;但是你拿那代码怎么好意思拿高薪&#xff1f; 所以而且那个的时间复杂度是O(n),效率并…

[阿里云] 10分钟带你玩转阿里云ECS和云盘 (大数据上云必备)

前言 由于准备做一些离线计算和实时计算的模拟, 发现某些教程内的阿里云还挺好用的, 在这里把相关的经验分享给大家. 简单的心路历程: 起先笔者搭建了一套本地集群. 但是后来发现, 因为没用网络IP的反穿, 本地的集群的网络访问非常不便. 其次, 集群的启停, 网络和磁盘管理都非…

缓存原理的学习

在如今这个微服务分布式的大环境下,集群分布式部署 几乎 已经是我们每个人都熟知的了。 缓存也一样&#xff0c;对我们来说 &#xff0c;如果只是一个单体应用 &#xff0c; 那只要 有本地缓存就足以了&#xff0c;但是倘若分布式部署了很多台机器上&#xff0c;那我们该如何缓…

软考-操作系统

【考点梳理】 【进程管理】 考点1、进程的状态&#xff08;★★&#xff09; 【考法分析】 本考点主要考查形式主要是根据图示判断相关状态位置或状态变迁条件。 【要点分析】 操作系统三态模型如下图所示&#xff1a;操作系统五态模型&#xff1a;【备考点拨】 掌握操作…

vue2.0和vue3.0创建项目

由于vue项目依赖于nodejs&#xff0c;所以需要先安装它。没有nodejs去官网下载。 npm install --global vue-cli 国内npm网站很慢&#xff0c;可以使用淘宝镜像 npm install --registryhttps://registry.npm.taobao.org vue2.0创建项目&#xff1a; 进入到自己需要创建项目…

SAP给微信推送消息

导语&#xff1a;最近领导下发指令&#xff0c;要求研究SAP与微信&#xff0c;企业微信&#xff0c;钉钉&#xff0c;邮件推送消息的平台&#xff0c;类似于采购订单审批之后&#xff0c;可以通过以上软件给用户发消息&#xff0c;我认领了微信的部分。 整个研究过程是很痛苦的…

华为静态NAT、动态NAT、PAT端口复用

一、网络环境及TOP 1.1 R1 相当于内网的一台PC&#xff0c; IP&#xff1a;192.168.1.10 网关为 192.168.1.254 [R1]ip route-static 0.0.0.0 0 192.168.1.254 # R1配置默认路由&#xff08;网关&#xff09; 1.2 R2为出口路由器&#xff0c;分别连接内网R1及外网R3 1&…

7. 整数反转

题目链接&#xff1a;力扣 解题思路&#xff1a; 题目要求中有一句话&#xff1a;假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 也就是说不能使用long类型来判断是否int溢出&#xff0c;只能使用int类型进行运算 首先对于一个整数的翻转比较简单…

学openCV,不会数字图像系统可不行

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 前言 在光照的情况下&#xff0c;通过成像系统将大自然中的物体拍摄出来&#xff0c;成像系统手机自带有&#xff0c;这里面我们关心的是分辨率&#xff0c;成像系统显示的点数越多&#xff0c;阵列越大&am…

HCIA静态试验(12.30-31复习)

目标实现&#xff1a; 2、首先进行子网划分 基于192.168.1.0 24划分 ‘一共7个路由器需要7个网段还有7个主干网 192.168.1.0/24 ----用于骨干 192.168.1.32/27 ----R1环回 192.168.1.32/28 192.168.1.48/28 192.168.1.64/27 --- R2环回 192.168.1.64/28 192.168.1.80/28 …

Java之网络相关概念

寒假又开始更新java了&#xff0c;之后更新的是b站教程韩顺平老师的课&#xff0c;编译器我从idea换成eclipse&#xff08;因为蓝桥杯只有eclipse&#xff0c;要先熟悉&#xff09; 1.网络相关概念 网络通信 网络 ip地址 1.简单来说ip地址是每一台主机的标识 类似于我们现…

Vue 疑难扎症(一)有时候取不到Vue对象中值怎么办?对象值发生改变了但是页面没有刷新怎么办?

目录 有时候取不到对象中值怎么办&#xff1f; 问题截图 问题代码 问题分析 情况1 情况2 情况3 问题解决 对象值发生改变了但是页面没有刷新怎么办&#xff1f; 为什么&#xff1f; 常见错误写法&#xff1a; 怎么办&#xff1f; 有时候取不到对象中值怎么办&…

Node.js--》如何在Node.js中使用中间件,看这一篇就足够了

目录 中间件 中间件函数使用 中间件的作用 中间件分类 使用中间件的注意事项 编写接口 跨域问题及其解决方案 中间件 中间件特指业务流程的中间处理环节。当一个请求到达 Express 的服务器之后&#xff0c;可以连续调用多个中间件&#xff0c;从而对这次请求进行预处理…

【网络排查】用于接口不通,mysql,kafka等数据库介质连不上的排查

这篇文章记录生产实践中遇到的网络不通的例子 文章目录前言1. 网络协议1.1 应用层找到有问题的服务端 IP总结前言 接口调用不同了了怎么办&#xff1f; 就找接口服务提供方&#xff0c;肯定是提供方的问题的&#xff0c;跟调用方有啥关系~ kafka&#xff0c;mysql等数据库介质…

JAVAGUI编程初识之Swing

文章目录一 常用窗口1.1 JFrame框架窗口1.2 演示-JFRame,JLable的使用1.3 JDialog标签1.3.1 演示-JDialog标签二 标签组件2.1 标签2.2 图标2.2.1 ICon接口简介2.2.2 演示-用Icon接口创建图标2.3 图片图标2.3.1 演示-图片图标三 布局管理器3.1 绝对布局3.1.1 绝对布局简介3.1.2 …

年末再看指针。看来搞C/C++,如影随形的指针就得门清~~~

继上篇博文因内核页表引出的指针问题&#xff0c;后来又研究了一番&#xff0c;这次应该比较清楚了&#xff0c;这里再总结一下。 目录 0 前言 1 普通指针&#xff1a; 2 指针的指针&#xff1a; 3 普通指针参数&#xff1a; 4 指针的指针参数&#xff1a; 5 函数指针&a…

[Kettle] 认识Kettle

1.初识Kettle Kettle是ETL数据整合与处理工具&#xff0c;翻译成中文是"水壶"的意思&#xff0c;可理解为希望把各种数据放到一个壶里&#xff0c;像水一样以一种指定的格式流出&#xff0c;表达数据流的含义 ETL(Extract - Transform - Load)是将数据从数据来源端…