C++ 控制结构和函数(三)—— 函数II(Functions II)
参数按数值传递和按地址传递(Arguments passed by value and by reference)
到目前为止,我们看到的所有函数中,传递到函数中的参数全部是按数值传递的(by value)。也就是说,当我们调用一个带有参数的函数时,我们传递到函数中的是变量的数值而不是变量本身。 例如,假设我们用下面的代码调用我们的第一个函数addition :
z = addition ( x , y );
在这个例子里我们调用函数addition 同时将x和y的值传给它,即分别为5和3,而不是两个变量:
这样,当函数addition被调用时,它的变量a和b的值分别变为5和3,但在函数addition内对变量a 或b 所做的任何修改不会影响变量他外面的变量x 和 y 的值,因为变量x和y并没有把它们自己传递给函数,而只是传递了他们的数值。
但在某些情况下你可能需要在一个函数内控制一个函数以外的变量。要实现这种操作,我们必须使用按地址传递的参数(arguments passed by reference),就象下面例子中的函数duplicate:
// passing parameters by reference #include <iostream.h> void duplicate (int& a, int& b, int& c) int main () |
x=2, y=6, z=14 |
第一个应该注意的事项是在函数duplicate的声明(declaration)中,每一个变量的类型后面跟了一个地址符ampersand sign (&),它的作用是指明变量是按地址传递的(by reference),而不是像通常一样按数值传递的(by value)。
当按地址传递(pass by reference)一个变量的时候,我们是在传递这个变量本身,我们在函数中对变量所做的任何修改将会影响到函数外面被传递的变量。
用另一种方式来说,我们已经把变量a, b,c和调用函数时使用的参数(x, y和 z)联系起来了,因此如果我们在函数内对a 进行操作,函数外面的x 值也会改变。同样,任何对b 的改变也会影响y,对c 的改变也会影响z>。
这就是为什么上面的程序中,主程序main中的三个变量x, y和z在调用函数duplicate 后打印结果显示他们的值增加了一倍。
如果在声明下面的函数:
void duplicate (int& a, int& b, int& c)
时,我们是按这样声明的:
void duplicate (int a, int b, int c)
也就是不写地址符 ampersand (&),我们也就没有将参数的地址传递给函数,而是传递了它们的值,因此,屏幕上显示的输出结果x, y ,z 的值将不会改变,仍是1,3,7。
这种用地址符 ampersand (&)来声明按地址"by reference"传递参数的方式只是在C++中适用。在C 语言中,我们必须用指针(pointers)来做相同的操作。
按地址传递(Passing by reference)是一个使函数返回多个值的有效方法。例如,下面是一个函数,它可以返回第一个输入参数的前一个和后一个数值。
// more than one returning value #include <iostream.h> void prevnext (int x, int& prev, int& next) { prev = x-1; next = x+1; } int main () |
Previous=99, Next=101 |
参数的默认值(Default values in arguments)
当声明一个函数的时候我们可以给每一个参数指定一个默认值。如果当函数被调用时没有给出该参数的值,那么这个默认值将被使用。指定参数默认值只需要在函数声明时把一个数值赋给参数。如果函数被调用时没有数值传递给该参数,那么默认值将被使用。但如果有指定的数值传递给参数,那么默认值将被指定的数值取代。例如:
// default values in functions #include <iostream.h> int divide (int a, int b=2) { int r; r=a/b; return (r); } int main () { |
6 5 |
我们可以看到在程序中有两次调用函数divide。第一次调用:
divide (12)
只有一个参数被指明,但函数divide允许有两个参数。因此函数divide 假设第二个参数的值为2,因为我们已经定义了它为该参数缺省的默认值(注意函数声明中的int b=2)。因此这次函数调用的结果是 6 (12/2)。
在第二次调用中:
divide (20,4)
这里有两个参数,所以默认值 (int b=2) 被传入的参数值4所取代,使得最后结果为 5 (20/4).
函数重载(Overloaded functions)
两个不同的函数可以用同样的名字,只要它们的参量(arguments)的原型(prototype)不同,也就是说你可以把同一个名字给多个函数,如果它们用不同数量的参数,或不同类型的参数。例如:
// overloaded function #include <iostream.h> int divide (int a, int b) { float divide (float a, float b) { int main () { |
2 2.5 |
在这个例子里,我们用同一个名字定义了两个不同函数,当它们其中一个接受两个整型(int)参数,另一个则接受两个浮点型(float)参数。编译器 (compiler)通过检查传入的参数的类型来确定是哪一个函数被调用。如果调用传入的是两个整数参数,那么是原型定义中有两个整型(int)参量的函数被调用,如果传入的是两个浮点数,那么是原型定义中有两个浮点型(float)参量的函数被调用。
为了简单起见,这里我们用的两个函数的代码相同,但这并不是必须的。你可以让两个函数用同一个名字同时完成完全不同的操作。
Inline 函数(inline functions)
inline 指令可以被放在函数声明之前,要求该函数必须在被调用的地方以代码形式被编译。这相当于一个宏定义(macro)。它的好处只对短小的函数有效,这种情况下因为避免了调用函数的一些常规操作的时间(overhead),如参数堆栈操作的时间,所以编译结果的运行代码会更快一些。
它的声明形式是:
inline type name ( arguments ... ) { instructions ... }
它的调用和其他的函数调用一样。调用函数的时候并不需要写关键字inline ,只有在函数声明前需要写。
递归(Recursivity)
递归(recursivity)指函数将被自己调用的特点。它对排序(sorting)和阶乘(factorial)运算很有用。例如要获得一个数字n的阶乘,它的数学公式是:
n! = n * (n-1) * (n-2) * (n-3) ... * 1
更具体一些,5! (factorial of 5) 是:
5! = 5 * 4 * 3 * 2 * 1 = 120
而用一个递归函数来实现这个运算将如以下代码:
// factorial calculator #include <iostream.h> long factorial (long a){ int main () { |
Type a number: 9 !9 = 362880 |
注意我们在函数factorial中是怎样调用它自己的,但只是在参数值大于1的时候才做调用,因为否则函数会进入死循环(an infinite recursive loop),当参数到达0的时候,函数不继续用负数乘下去(最终可能导致运行时的堆栈溢出错误(stack overflow error)。
这个函数有一定的局限性,为简单起见,函数设计中使用的数据类型为长整型(long)。在实际的标准系统中,长整型long无法存储12!以上的阶乘值。
函数的声明(Declaring functions)
到目前为止,我们定义的所有函数都是在它们第一次被调用(通常是在main中)之前,而把main 函数放在最后。如果重复以上几个例子,但把main 函数放在其它被它调用的函数之前,你就会遇到编译错误。原因是在调用一个函数之前,函数必须已经被定义了,就像我们前面例子中所做的。
但实际上还有一种方法来避免在main 或其它函数之前写出所有被他们调用的函数的代码,那就是在使用前先声明函数的原型定义。声明函数就是对函数在的完整定义之前做一个短小重要的声明,以便让编译器知道函数的参数和返回值类型。
它的形式是:
type name ( argument_type1, argument_type2, ...);
它与一个函数的头定义(header definition)一样,除了:
- 它不包括函数的内容, 也就是它不包括函数后面花括号{}内的所有语句。
- 它以一个分号semicolon sign (;) 结束。
- 在参数列举中只需要写出各个参数的数据类型就够了,至于每个参数的名字可以写,也可以不写,但是我们建议写上。
例如:
// 声明函数原型 #include <iostream.h> void odd (int a); int main () { void odd (int a) { void even (int a) { |
Type a number (0 to exit): 9 Number is odd. Type a number (0 to exit): 6 Number is even. Type a number (0 to exit): 1030 Number is even. Type a number (0 to exit): 0 Number is even. |
这个例子的确不是很有效率,我相信现在你已经可以只用一半行数的代码来完成同样的功能。但这个例子显示了函数原型(prototyping functions)是怎样工作的。并且在这个具体的例子中,两个函数中至少有一个是必须定义原型的。
这里我们首先看到的是函数odd 和even的原型:
void even (int a);
这样使得这两个函数可以在它们被完整定义之前就被使用,例如在main中被调用,这样main就可以被放在逻辑上更合理的位置:即程序代码的开头部分。
尽管如此,这个程序需要至少一个函数原型定义的特殊原因是因为在odd 函数里需要调用even 函数,而在even 函数里也同样需要调用odd函数。如果两个函数任何一个都没被提前定义原型的话,就会出现编译错误,因为或者odd 在even 函数中是不可见的(因为它还没有被定义),或者even 函数在odd函数中是不可见的。
很多程序员建议给所有的函数定义原型。这也是我的建议,特别是在有很多函数或函数很长的情况下。把所有函数的原型定义放在一个地方,可以使我们在决定怎样调用这些函数的时候轻松一些,同时也有助于生成头文件。
C++ 控制结构和函数(三)—— 函数II(Functions II)相关推荐
- 10_InfluxDB常用函数(三)变换类函数(DERIVATIVE, DIFFERENCE,ELAPSED,MOVING_AVERAGE,NON_NEGATIVE_DERIVATIVE)等
10.InfluxDB学习之InfluxDB常用函数(三)变换类函数 转自:https://www.yisu.com/zixun/36847.html 10.1.DERIVATIVE()函数 作用:返 ...
- C++程序设计(三)—— 函数和函数模板
一.函数的参数及其传递方式 C++的函数传递有两种传递方式:传值和伟引用.传值分为传"对象值"和"对象地址值","对象值"是指对象的数据成员 ...
- mysql自定义函数的分号_MySQL基础(三)—函数、自定义函数
上一篇 MySQL基础(二)-操作表记录 这一篇是对函数的笔记,其中操作的数据库在上一篇文章中有代码,可以去看一下. 1.函数 1.1:函数的分类 字符函数 数值运算符与函数 比较运算符与函数 日期时 ...
- mysql自定义函数的分号_MySQL基础(三)―函数、自定义函数
MySQL基础(二)―操作表记录 这一篇是对han的笔记,其中操作的数据库在上一篇文章中有代码,可以去看一下. 字符函数 数值运算符与函数 比较运算符与函数 日期时间函数 信息函数 聚合函数 加密函数 ...
- 一起学习C语言:函数(三)
上一篇<一起学习C语言:函数(二)> 中,我们了解了内部函数和外部函数,以及变量的声明周期与作用域.本章节,我们分析函数的存储类别与声明方式,以及函数的递归调用原理. 章节预览: 6. 变 ...
- 浅谈js函数三种定义方式 四种调用方式 调用顺序
在Javascript定义一个函数一般有如下三种方式: 函数关键字(function)语句: function fnMethodName(x){alert(x);} 函数字面量(Function Li ...
- flink表聚合函数(Table aggregate Functions)
用户定义的表聚合函数(User-Defined Table Aggregate Functions) ,可以把一个表中数据,聚合卫具有多行和多列的结果表用户定义表聚合函数,是通过继承TableAggr ...
- C基础(三)函数的使用
目录 一.库函数的使用 1.1 随机数rand与srand 1.2 scanf函数 1.3 gets函数 1.4 fgets函数 1.5 puts函数 1.6 strlen函数 1.7 strcat函 ...
- Hook函数三步走(SetWindowsHookEx、UnhookWindowsHookEx、CallNextHookEx)
文章目录 Hook(Windows系统机制) Hook定义 Hook原理 系统钩子与线程钩子 钩子函数 设置钩子: SetWindowsHookEx 参数说明: 释放钩子: UnhookWindows ...
最新文章
- 车联网APP,安全设施薄弱的山寨品
- 数据中心机房常用通信管道塑料管材
- ITK:对图像中的结构进行分割
- java 递归 堆栈_尾递归函数仍在Java吹堆栈
- Twitter的分布式自增ID算法snowflake (Java版)
- C++刷称号——2707: 素数与要素
- sap的ides和ecc分别是什么意思
- 一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之数据篇
- 一则非常巧合的ORA-15042恢复
- duilib学习 --- 360demo 学习
- keras训练一个简单的模型
- 一次redis乱用导致的事故现场
- windows10 1909 X64位 精简优化珍藏版
- fatal:unable to access ‘https://github....‘:Failed to conect to github.com port 443:拒绝连接的解决方法
- 首台自主创新全空冷机组在三峡运行,图扑数字孪生机体
- CSS 绘制太阳系行星运行轨迹
- web im环信陌生人聊天或客服聊天功能
- 快手自研直播多码率标准对行业发布
- Programming Assignment 4: Boggle
- java连接达梦数据库_【达梦数据库】Activiti连接达梦数据库
热门文章
- 食品安全--牛奶和蛋白质浅谈
- 面向Tableau开发人员的Python简要介绍(第2部分)
- leetcode910. 最小差值 II(贪心)
- 如何使用1Password,Authy和Privacy.com外包您的在线安全性
- react 使用 mobx_如何使用React和MobX状态树构建基于状态的路由器
- vue生成静态js文件_如何立即使用Vue.js生成静态网站
- 程序员实际情况_程序员实际上是做什么的?
- 项目案例:qq数据库管理_2小时元项目:项目管理您的数据科学学习
- .NET面试题解析(02)-拆箱与装箱
- 【贪心】Vijos P1615 旅行