1.问题的引入

考虑下面不适宜复制实参的例子,该函数希望交换两个实参的值:

<span style="font-size:18px;">void swap(int v1, int v2)
{int temp = v2;v2 = v1;v1 = temp;
}
int main()
{int i = 10;int j = 20;cout << "Before swap():\ti: "<< i << "\tj: " << j << endl;swap(i, j);cout << "After swap():\ti: "<< i << "\tj: " << j << endl;return 0;
}</span>

编译并执行程序,产生如下输出结果:
Before swap(): i: 10  j: 20
After swap():    i: 10  j: 20

这个例子期望改变实参本身的值。但是对于上述的函数定义,swap无法影响实参本身。执行swap时,只交换了实参的局部副本,而传递swap的实参并没有修改。

为了使swap函数以期望的方式进行工作,交换实参的值,需要将形参定义为引用类型:

<span style="font-size:18px;">// ok: swap acts on references to its arguments
void swap(int &v1, int &v2)
{
int tmp = v2;
v2 = v1;
v1 = tmp;
}</span>

与所有的引用一样,引用形参直接关联所绑定的实参,他是实参的一个别名,而不是这些对象所谓的副本。定义引用时,必须用与该引用绑定的对象初始化该引用。引用形参完全以相同的方式进行工作。每次调用该函数时,引用形参被创建并与相应的实参关联。

此时,调用该函数时,swap(i,j);

形参v1只是对象i的另一个名字,而v2则是对象j的另外一个名字。对v1的任何修改实际上也是对i的修改。所以此时可以完成两个值的交换任务。

从C语言背景转到C++的我们习惯通过传递指针来实现对实参的访问。在C++中,使用引用形参更安全和更自然。

2.使用引用形参返回额外的信息

通过对上例的讨论,可以理解如何利用引用形参让函数修改实参的值。引用形参的另一种用法是向主调函数返回额外的结果。函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。例如,定义一个 find_val 函数。在一个整型 vector 对象的元素中搜索某个特定值。如果找到满足要求的元素,则返回指向该元素的迭代器;否则返回一个迭代器,指向该 vector 对象的 end 操作返回的元素。此外,如果该值出现了不止一次,我们还希望函数可以返回其出现的次数。在这种情况下,返回的迭代器应该指向具有要寻找的值的第一个元素。如何定义既返回一个迭代器又返回出现次数的函数?可以定义一种包含一个迭代器和一个计数器的新类型。而更简便的解决方案是给 find_val 传递一个额外的引用实参,用于返回出现次数的统计结果:

<span style="font-size:18px;">// returns an iterator that refers to the first occurrence of value
// the reference parameter occurs containn a second return value
vector<int>::const_iterator find_val(vector<int>::const_iterator beg, vector<int>::const_iterator end, <span style="color:#cc0000;"><strong> int value</strong></span>, // the value we want<strong><span style="color:#ff0000;">vector<int>::size_type &occurs</span></strong>) // number of times it occurs
{// res_iter will hold first occurrence, if anyvector<int>::const_iterator res_iter = end;<span style="color:#ff0000;"><strong>occurs = 0;</strong></span> // set occurrence count parameter ///引用,即别名,我们可以在主函数体中查到for ( ; beg != end; ++beg)if (*beg == value) {// remember first occurrence of valueif (res_iter == end){ res_iter = beg;}++occurs; // increment occurrence count}return res_iter; // count returned implicitly in occurs
}</span>

it = find_val(ivec.begin(), ivec.end(), 42, ctr);

调用 find_val 时,需传递四个实参:一对标志 vector 对象中要搜索的元素范围的迭代器,所查找的值,以及用于存储出现次数的size_type 类型对象。假设 ivec 是 vector<int>类型的对象,it 是一个适当类型的迭代器,而 ctr 则是 size_type 类型的变量。

调用后,ctr 的值将是 42 出现的次数,如果 42 在 ivec 中出现了,则 it将指向其第一次出现的位置;否则,it 的值为 ivec.end(),而 ctr 则为 0

3. 利用const引用避免复制(const int&)

向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率太低了;此外,也会注意某些类类型是无法复制的。使用引用形参,函数可以直接访问实参对象,而无须复制它。

举个例子:

我们需要编写这样一个功能:我们要比较两个string对象长度。这个函数需要访问每个string对象的大小,但是没有必要修改这些对象。由于string对象可能相当长,所以应该避免采用复制操作。此时,采用const 引用就非常合适。
<span style="font-size:18px;">// compare the length of two strings
bool isShorter(<strong><span style="color:#ff0000;">const string&</span></strong> s1, <span style="color:#ff0000;"><strong>const string&</strong></span> s2)
{return s1.size() < s2.size();
}</span>

注意:如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用

4.更灵活的指向const的引用

如果函数具有普通的非const引用形参,则显然不能通过从const对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的const特性。但比较容易忽略的是,调用这样的函数(非const引用形参)时,传递一个右值或具有需要转换的对象同样是不允许的
<span style="font-size:18px;">// function takes a non-const reference parameter
int incr(int &val)
{return ++val;
}
int main()
{short v1 = 0;const int v2 = 42;int v3 = incr(v1); // error: v1 is not an intv3 = incr(v2); // error: v2 is const<strong><span style="color:#ff0000;">v3 = incr(0); // error: literals are not lvalues</span></strong>v3 = incr(v1 + v2); // error: addition doesn't yield an lvalueint v4 = incr(v3); // ok: v3 is a non const object type int
}</span>

问题的关键在于:非const引用形参只能与完全同类型的非const对象关联!!!

举一个例子:
<span style="font-size:18px;">// returns index of first occurrence of c in s or s.size() if c isn't in s
// Note: s doesn't change, so it should be a reference to const
string::size_type find_char(<span style="color:#ff0000;"><strong>string &s</strong></span>, char c)
{string::size_type i = 0;while (i != s.size() && s[i] != c)++i; // not found, look at next characterreturn i;
}</span>

if (find_char("Hello World", 'o')) // ...

虽然字符串字面值可以转换为string对象,但是上述的调用仍然会导致编译失败。尽管函数并没有修改这个形参的值,但是这样的定义带来的问题是不能通过字符串字面值调用函数

应该说,更鼓励将不加以修改的实参定义为const引用,因为将形参定义为非const引用,将毫不必要地限制了这些函数的使用(比如字面值字符串查找某一元素)!

注意:应当将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时非常不灵活。非const形参既不允许const对象初始化,也不能用字面值或产生右值的表达式实参初始化,大大地限制了函数功能。

5.传递指向指针的引用

假设想编写一个与前面交换两个整数的 swap 类似的函数,实现两个指针的交换。已知需用 * 定义指针,用 & 定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。这里给出一个例子:

<span style="font-size:18px;">// swap values of two pointers to int
void ptrswap(int* &v1, int* &v2)
{int *tmp = v2;v2 = v1;v1 = tmp;
}</span>

形参: int* &v1;
应该从右至左理解:v1是一个引用(别名),与指向int型对象的指针相关联。也就是说,v1只是传递金ptrswap函数的任意指针的别名。

<span style="font-size:18px;">int main()
{int i = 10;int j = 20;int *pi = &i; // pi points to iint *pj = &j; // pj points to jcout << "Before ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;ptrswap(pi, pj); // now pi points to j; pj points to icout << "After ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;return 0;
}</span>

解释:形参实参关系可以理解为 int*  &v1 (pi);  实际上,传进来了两个指针,最终完成的是指针的交换!

即指针的值被交换了。在调用 ptrswap 时,pi 指向 i,而 pj 则指向 j。在 ptrswap 函数中, 指针被交换, 使得调用 ptrswap 结束后, pi 指向了原来 pj所指向的对象。换句话说,现在 pi 指向 j,而 pj 则指向了 i。

C++Primer:函数(参数传递:引用形参)相关推荐

  1. C++Primer:函数(参数传递-非引用形参)

    1.参数传递 每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参. 形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则他只是 ...

  2. C++ Primer 第五版 第6章 6.2——函数参数传递习题答案

    理论讲解请参考:C++ Primer 第五版 第6章 6.2--函数参数传递阅读笔记 目录 6.10 指针形参交换两个数 6.12 引用形参交换两个数 6.13 6.14 6.15 6.16 6.17 ...

  3. C++ Primer 第五版 第6章 6.2——函数参数传递阅读笔记

    习题答案请参考:C++ Primer 第五版 第6章 6.2--函数参数传递习题答案 目录 6.2 函数参数传递 6.2.1 传值参数(值传递) 指针形参 6.2.2 传引用参数(引用传递) 使用引用 ...

  4. C++ 函数参数传递:传值,传指针,传引用

    PS:首先理解形参   实参概念.形参是在函数定义的括号内定义的专用变量,它们的目的是保存按实参传递给它们的信息,实参被列在函数调用语句的括号内. int func(int x)//x是形参 {ret ...

  5. 【中级软考】函数参数传递传值与传引用的区别(global关键字,函数内定义全局变量)

    传值调用最显著的特征就是被调用的函数内部对形参的修改不影响实参的值. 引用调用是将实参的地址传递给形参,使得形参的地址就是实参的地址. (对于python而言,普通的实参传个变量(或常量)进去就相当于 ...

  6. 函数参数传递三种方式(传值方式,地址传递,引用传递)

    函数参数传递三种方式(传值方式,地址传递,引用传递) 形参与实参 形参即形式上的参数,对实参的一种抽象类型描述只是声明一个函数能够接受什么类型的实参,而不确定接受的实参的具体内容是什么 实参即传递给函 ...

  7. c++之指针和引用作为函数参数传递时的区别

    之前写过c++之值传递.引用传递.指针传递,今天再单独区分一下指针和引用作为函数参数传递时的区别. 本文参考浅谈C++中指针和引用的区别 一.指针作为函数参数传递时 1.类似于值传递,传入函数的指针只 ...

  8. Python函数参数传递:传值还是传引用

    引子 首先来看一个列子: def change(val):val.append(100)val = ['T', 'Z', 'Y'] nums = [0, 1] change(nums) print(n ...

  9. Python的函数参数传递:传值?引用?

    From: http://blog.csdn.net/winterttr/article/details/2590741 作者:winterTTr (转载请注明) 我想,这个标题或许是很多初学者的问题 ...

最新文章

  1. 委托(一个主窗体统计多个从窗体的按钮单击的次数)
  2. chineseocr
  3. 把报表的数据导出Excel
  4. python考试pass or fail_python-pytest学习(十二)-标记失败xfail
  5. Python都被用在哪?都有哪些人在用Python呢?
  6. 小米usb3.0修复补丁_今日热闻 | Redmi 10X系列发布、小米手环5产品外观曝光、Win10补丁导致蓝屏、AXON 11 SE 6月发布...
  7. Eyoucms代理授权统计插件源码
  8. linux图形驱动安装失败,红旗Linux 下NVIDIA的驱动安装问题
  9. map的几种遍历方法
  10. linux服务器用户组和权限管,linux 用户管理,用户权限管理,用户组管理
  11. Myeclipse学习总结(16)——MyEclipse CI 2018.8.0首次更新,全新来袭!(内附破解激活文件,亲测破解100%)
  12. python -图例设置
  13. linux中文输入法配置
  14. cesium-加载DEM数据(可拉伸)
  15. 互联网人必看的免费引流方法:十八种免费引流小技巧
  16. 【教学类-20-01】20221203《世界杯16强国旗-随机版》(大班)
  17. ERNIE-Enhanced Language Representation with Informative Entities 阅读笔记
  18. Eclipse设置项目编码格式的两种方式
  19. 信息系统高级项目管理师英语词汇(一)-常见计算机技术词汇
  20. 巴斯扩散模型-Bass Diffusion Model

热门文章

  1. IPsec-×××基本技术挖掘
  2. liunx 下巧妙使用代理服务器(squid)
  3. Css3-锚链接和伪类tartet
  4. 老股民经验之谈 这些股票买入必死无疑
  5. hdu 5464(简单dp)
  6. nyoj 998(欧拉定理的运用)
  7. k8s创建pod的步骤
  8. Oracle定时任务执行存储过程备份日志记录表
  9. Visual C# 2010从入门到精通
  10. jq实现点击导航栏中的任意一个跳转后被点击的定位到第一个