C++ 控制结构和函数(三)—— 函数II(Functions II)

参数按数值传递和按地址传递(Arguments passed by value and by reference)

到目前为止,我们看到的所有函数中,传递到函数中的参数全部是按数值传递的(by value)。也就是说,当我们调用一个带有参数的函数时,我们传递到函数中的是变量的数值而不是变量本身。 例如,假设我们用下面的代码调用我们的第一个函数addition :

int x=5, y=3, z;
z = addition ( x , y );

在这个例子里我们调用函数addition 同时将xy的值传给它,即分别为53,而不是两个变量:

这样,当函数addition被调用时,它的变量ab的值分别变为53,但在函数addition内对变量a 或b 所做的任何修改不会影响变量他外面的变量x 和 y 的值,因为变量xy并没有把它们自己传递给函数,而只是传递了他们的数值。

但在某些情况下你可能需要在一个函数内控制一个函数以外的变量。要实现这种操作,我们必须使用按地址传递的参数(arguments passed by reference),就象下面例子中的函数duplicate

// passing parameters by reference
#include <iostream.h>

void duplicate (int& a, int& b, int& c)
{
a*=2;
b*=2;
c*=2;
}

int main ()
{
int x=1, y=3, z=7;
duplicate (x, y, z);
cout << "x=" << x << ", y=" << y << ", z=" << z;
return 0;
}
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, yz在调用函数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 ()
{
int x=100, y, z;
prevnext (x, y, z);
cout << "Previous=" << y << ", Next=" << z;
return 0;
}
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 () {
cout << divide (12);
cout << endl;
cout << divide (20,4);
return 0;
}
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) {
return (a/b);
}

float divide (float a, float b) {
return (a/b);
}

int main () {
int x=5,y=2;
float n=5.0,m=2.0;
cout << divide (x,y);
cout << "\n";
cout << divide (n,m);
cout << "\n";
return 0;
}
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){
if (a > 1) return (a * factorial (a-1));
else return (1);
}

int main () {
long l;
cout << "Type a number: ";
cin >> l;
cout << "!" << l << " = " << factorial (l);
return 0;
}
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);
void even (int a);

int main () {
int i;
do {
cout << "Type a number: (0 to exit)";
cin >> i;
odd (i);
} while (i!=0);
return 0;
}

void odd (int a) {
if ((a%2)!=0) cout << "Number is odd.\n";
else even (a);
}

void even (int a) {
if ((a%2)==0) cout << "Number is even.\n";
else odd (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 odd (int a);
void even (int a);

这样使得这两个函数可以在它们被完整定义之前就被使用,例如在main中被调用,这样main就可以被放在逻辑上更合理的位置:即程序代码的开头部分。

尽管如此,这个程序需要至少一个函数原型定义的特殊原因是因为在odd 函数里需要调用even 函数,而在even 函数里也同样需要调用odd函数。如果两个函数任何一个都没被提前定义原型的话,就会出现编译错误,因为或者odd 在even 函数中是不可见的(因为它还没有被定义),或者even 函数在odd函数中是不可见的。

很多程序员建议给所有的函数定义原型。这也是我的建议,特别是在有很多函数或函数很长的情况下。把所有函数的原型定义放在一个地方,可以使我们在决定怎样调用这些函数的时候轻松一些,同时也有助于生成头文件。

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

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

相关文章

oracle XMLType字段使用方法

2019独角兽企业重金招聘Python工程师标准>>> 刚才研究了一下XMLType字段使用方法 &#xff0c;现在给大家介绍一下。 主要是新增、查询、修改XMLType字段 表结构&#xff1a; 建表sql&#xff1a; -- Create table create table T_BOOK ( ID VARCHAR2(32)…

C++ 高级数据类型(一)—— 数组

数组(Arrays) 是在内存中连续存储的一组同种数据类型的元素&#xff08;变量&#xff09;&#xff0c;每一数组有一个唯一名称&#xff0c;通过在名称后面加索引&#xff08;index&#xff09;的方式可以引用它的每一个元素。 也就是说&#xff0c;例如我们有5个整型数值需要存…

DataUml Design 介绍8-DataUML 1.2版本正式发布

为什么80%的码农都做不了架构师&#xff1f;>>> DataUML 1.2版本在软件架构上有了很大的变化&#xff0c;目前DataUML支持Access、SQLite、MY SQL 、ORACLE、MS SERVER2000、MS SERVER2005、MS SERVER2008数据库。 下载 主要更新内容如下&#xff1a;  1、支持S…

C++ 高级数据类型(二)—— 字符序列

前面基础知识部分讲C变量类型的时候&#xff0c;我们已经提到过C的标准函数库提供了一个string类来支持对字符串的操作。然而&#xff0c;字符串实际就是一串连续的字符序列&#xff0c;所以我们也可以用简单的字符数组来表示它。 例如&#xff0c;下面这个数组: char jenny …

C++ 高级数据类型(三)—— 指针

我们已经明白变量其实是可以由标识来存取的内存单元。但这些变量实际上是存储在内存中具体的位置上的。对我们的程序来说&#xff0c;计算机内存只是一串连续的单字节单元(1byte cell)&#xff0c;即最小数据单位&#xff0c;每一个单元有一个唯一地址。 计算机内存就好像城市中…

C++ 高级数据类型(四)—— 动态内存分配

到目前为止&#xff0c;我们的程序中我们只用了声明变量、数组和其他对象&#xff08;objects&#xff09;所必需的内存空间&#xff0c;这些内存空间的大小都在程序执行之前就已经确定了。但如果我们需要内存大小为一个变量&#xff0c;其数值只有在程序运行时 (runtime)才能确…

C++ 高级数据类型(六)—— 自定义数据类型

前面我们已经看到过一种用户&#xff08;程序员&#xff09;定义的数据类型&#xff1a;结构。除此之外&#xff0c;还有一些其它类型的用户自定义数据类型&#xff1a; 定义自己的数据类型 (typedef) C 允许我们在现有数据类型的基础上定义我们自己的数据类型。我们将用关键字…

JSF 2.0/2.1 生命周期简介

2019独角兽企业重金招聘Python工程师标准>>> 标准的生命周期划分为六个阶段&#xff1a;恢复视图、应用请求值、验证、更新模型值、调用应用程序、渲染响应&#xff0c;每一个阶段都可以直接跳转到最后一个阶段或者结束。 转载于:https://my.oschina.net/koulikoro/…

C++ 面向对象(一)—— 类(Classes)

类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class &#xff0c;其功能与C语言中的struct类似&#xff0c;不同之处是class可以包含函数&#xff0c;而不像struct只能包含数据元素。 类定义的形式是&#xff1a; [cpp] view plaincopy class cl…

Seen.js – 使用 SVG 或者 Canvas 渲染 3D 场景

Seen.js 渲染3D场景为 SVG 或者 HTML5 画布。Seen.js 包含对于 SVG 和 HTML5 Canvas 元素的图形功能的最简单的抽象。所有这个库的其它组件都是不用关心将要渲染的上下文的类型。 您可能感兴趣的相关文章你见过吗&#xff1f;9款超炫的复选框&#xff08;Checkbox&#xff09;效…

C++ 面向对象(三)—— 类之间的关系

友元函数(Friend functions) 在前面的章节中我们已经看到了对class的不同成员存在3个层次的内部保护&#xff1a;public&#xff0c; protected 和 private。在成员为 protected 和 private的情况下&#xff0c;它们不能够被从所在的class以外的部分引用。然而&#xff0c;这个…

五年后存储会是什么样子

原文&#xff1a;http://chucksblog.emc.com/chucks_blog/2013/06/what-storage-might-look-like-in-five-years.html注明&#xff1a;本文内容基于 VMware VSAN beta 版本撰写&#xff0c;请访问http://www.vmware.com/products/virtual-san/获得有关正式版本的更新信息。有时…

java.lang.NoClassDefFoundError: javax/transaction/Synchronization (jUnit测试报错)

测试hibernate报错原因项目缺少包在 hibernate 解压目录下找到 jta.jar 文件往项目中添加该 jar 包&#xff0c;即可解决添加方法&#xff1a;【右击项目】-->【构建路径】....来自为知笔记(Wiz)转载于:https://www.cnblogs.com/zhanyao/p/3711322.html

yarn oom问题一例

线上部分job运行失败&#xff0c;报OOM的错误:因为是maptask报错&#xff0c;怀疑是map数量过少&#xff0c;导致oom&#xff0c;因此调整参数&#xff0c;增加map数量&#xff0c;但是问题依然存在。看来和map的数量没有关系。通过jobid查找jobhistory中对应的日志信息&#x…

C++ 高级篇(三)—— 出错处理

本节介绍的出错处理是ANSI-C 标准引入的新功能。如果你使用的C 编译器不兼容这个标准&#xff0c;则你可能无法使用这些功能。 在编程过程中&#xff0c;很多时候我们是无法确定一段代码是否总是能够正常工作的&#xff0c;或者因为程序访问了并不存在的资源&#xff0c;或者由…

C++ 高级篇(一)—— 模板(Templates)

模板(Templates)是ANSI-C 标准中新引入的概念。如果你使用的 C 编译器不符合这个标准&#xff0c;则你很可能不能使用模板。 函数模板( Function templates) 模板(Templates)使得我们可以生成通用的函数&#xff0c;这些函数能够接受任意数据类型的参数&#xff0c;可返回任意类…

PPT到底是天使还是魔鬼?

说老实话&#xff0c;我非常不喜欢那些PPT的培训者一味地鼓吹PPT的重要性&#xff0c;视觉化思维的重要性&#xff0c;PPT在演讲中的重要性等等&#xff0c;我很清楚他们这么说是因为他们必须这么说&#xff0c;因为如果不把PPT的重要性强调一下&#xff0c;谁来上他的课呢&…

ultraedit正则表达式

一般使用ultraedit中的Perl风格的正则表达式&#xff0c;下面是perl正则的基本语法 perl中的元字符如下&#xff1a; ^ 表示一行的开头&#xff1b; $ 表示一行的结尾&#xff1b; ( ) 表示一个匹配块的&#xff0c;可以对匹配上的块通过$1,$2...进行读取&#xff0c;…

微软云介绍

微软云介绍 http://msdn.microsoft.com/zh-cn/ff380142 什么是云开发&#xff1f;&#xff08;概述&#xff09; 云计算是指远程运行并通过 Internet 访问的计算机和应用程序。在云计算中&#xff0c;虚拟机在大型数据中心中运行&#xff0c;并取代了物理 PC 和服务器。通过将许…

IO端口和IO内存的区别及分别使用的函数接口

IO端口和IO内存的区别及分别使用的函数接口 每个外设都是通过读写其寄存器来控制的。外设寄存器也称为I/O端口&#xff0c;通常包括&#xff1a;控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式&#xff0c;可以把CPU分成两大类。一类CPU&#xff08…