C++深入体验之旅五:C++函数

原创 2013年12月02日 10:15:37

1.什么是函数

在日常生活中,我们经常会用到工具。比如我们画一个表格需要用到直尺,它可以告诉我们线段的长度;我们开瓶子的时候需要用开瓶器,它可以把瓶子打开;我们在做计算题的时候需要用到计算器,它能够告诉我们计算结果。
使用工具有些什么好处呢?首先,如果这个工具是现成的,我们就可以不必要自己去做一个这样的工具,直接拿来就能用(比如开瓶器、计算器)。其次,不管是现成的工具,还是自己做的工具(比如自己做的直尺),一定是能够多次反复使用的(比如直尺不是用完一次就不能再用的),而且是能够在各种合适的情况下使用的。(直尺在量程范围内能量这条线段的长度,也能够量那条线段的长度。)
在程序设计中,我们也会有各种各样的“工具”。你告诉比较大小的“工具”两个不相等的数,这个“工具”能够告诉你哪个数大;你告诉求正弦值的“工具”一个弧度,这个工具能够求出这个弧度对应的正弦值等等……这些工具的名字就是函数(Function)。要注意,在程序设计中的函数和数学中的函数有相似的地方,但是它们却完全是两码事,请不要将两者等同起来。
函数和工具的性质是一样的。如果有一个现成求正弦值的函数,我们就不必自己去“造”一个这样的函数。求正弦值的函数是可以多次使用的,并且可以求出任意实数的正弦值(合适的情况下),但是它却求不出一个虚数的正弦值(不合适的情况下)。

如何定义和使用函数

有时候我们会知道一个工具有什么功能,但是却因为对其陌生而不会使用,这时候要使用它可能会发生一些困难。除了自己有空去摸索一下以外,最有效的办法就是去看说明书了。说明书里会告诉你什么东西放在什么位置上,使用了以后会产生什么效果之类的。
同工具一样,每个函数也有其自己的说明书,告诉用户如何调用(就是使用的意思)这个函数。这份说明书就称为这个函数的原型。它的格式为:
  产生结果类型 函数名(参数1,参数2,……参数n);
函数名相当于工具的名字,比如直尺、计算器等等。产生效果类型相当于使用该工具产生的效果,比如直尺能够读出一个长度,计算器能够显示一个结果等等。而参数(Parameter)则是表示合适的使用情况,比如直尺应该去量长度而不能去量角度,计算器能计算数值而不能去画图等等。
那么我们如何来阅读函数的“说明书”呢?我们先来看两个例子:

⑴int max(int a,int b);
这个函数名称为max,即求出最大的值。运行该函数以后,产生的结果是一个整数。在数学中,我们会有一元函数比如f(x)=2*x+3,也会有多元函数比如g(x,y)=x/4+y等等。我们在使用f(x)或g(x,y)的时候括号内数的位置必须和自变量的字母对应,比如g(4,1)=4/4+1=2,此时x=4并且y=1。我们既不能将其颠倒,也不能写出g(4)或者g(4,2,1,5)之类的表达式,否则就是错误的。程序设计中参数的作用和自变量x,y的作用是类似的。在函数“说明书”中,也交待了哪个位置应该放置什么类型的参数,我们在调用函数的时候要注意参数的类型、顺序、个数都要一一对应。
具体使用请看以下的程序:(程序6.1.1)

#include "iostream.h" 
int max(int a,int b);//函数原型,假设函数已经定义 
int main() 
{ 
   int r=3,s=5,t; 
   t=max(r,s);//使用函数,并记录产生的结果 
   cout<<t <<endl; 
   return 0; 
} 
运行结果:
5 
对于上面这段程序,有两点要说明。首先,调用函数时放入括号内的变量名r和s与函数原型里a和b的名字是可以不一样的。就像我可以用尺量各种各样的纸。但是,它们的数据类型必须相同,如果把一个字符型变量放在这个位置上,就如同用尺去量角度一样,无法成功的。其次,调用函数后的结果可以认为是一个表达式的值。我们可以把这个结果赋值给一个变量或者将其输出。当然,我们也可以不保存不输出这个结果,但是那样的话,就像是量了长度却没有把结果记录下来。

⑵void output(char c);
这个函数名为output,即输出。void表示空类型,它同整型、实型一样,也是一种数据类型。它表示调用该函数后,不会产生任何定量的结果。这是什么意思呢?我们知道,例如榔头这种工具,它只能产生一些效果,如把钉子砸进木头里,但是它不会给使用者一个定量的结果。不过我们大可不必担心它是否完成了我们要它完成的工作。如果榔头没把钉子砸进木头里,要么是榔头本身质量有问题,要么就是使用者没有按照要求去使用。若这把榔头不是用户自己造的,那么用户没有任何责任。
下面我们就来尝试一下使用这个函数:(程序6.1.2)

#include "iostream.h" 
void output(char c); //函数原型,假设函数已经定义 
int main() 
{ 
   char temp; 
   cin >>temp; 
   output(temp); 
   return 0; 
} 
运行效果:
T 
T 
虽然函数没有产生什么定量的结果,但是其在屏幕上输出的功能还是达到了。对于产生void(空类型)的函数,我们不必去保存结果了。
程序6.1.1和6.1.2的代码是不完善的,如果仅用这些代码去编译会被告知函数未定义。由于涉及更多的知识,这些代码将在下一节得到完善。

2.系统函数

我们经常在程序的一开始就写#include某个头文件。其实有些头文件中就有不少系统已经造好的函数,它们叫做标准库(Standard Library)函数。我们包含(include)一个头文件,就像是到某个工具库里面去找一个工具一样。所以,要使用系统定义好的一些函数,我们必须知道这些函数在哪个头文件里,就好像使用工具我们必须知道这个工具放在哪个工具库里面。下面是一些函数和相关头文件信息的列举。

其实很多函数系统已经为我们写好,我们只要通过包含头文件就能够使用这些函数。关于更多的函数信息,我们将在附录上作介绍,读者也可以通过网络或者VC++的工具书来查找到这些信息。
下面我们来看一段使用系统造好的函数编写的程序:(程序6.1.3)

#include "iostream.h" 
#include "math.h" 
#include "stdlib.h" 
int main() 
{ 
   const double pi=3.14159265358; 
   double a=90; 
   cout <<"sin(a)=" <<sin(a/360*2*pi) <<endl;//角度与弧度的转换 
   cout <<"cos(a)=" <<cos(a/360*2*pi) <<endl; 
   cout <<"sqrt(a)=" <<sqrt(a) <<endl; 
   cout <<"pow(a,2)=" <<pow(a,2) <<endl; 
   exit(1); 
   return 0; 
} 

运行结果:
sin(a)=1 
cos(a)=4.89659e-012 
sqrt(a)=9.48683 
pow(a,2)=8100 

由于电脑的三角函数都是使用弧度作为单位的,所以我们必须用“a/360*2*pi”将角度转化为弧度。至于为什么cos90°不等于0,则是因为圆周率π无法很精确,所以导致算出来的余弦值是一个接近于0的小数,而不是0。

试试看:
1、根据本节函数和头文件的信息表,尝试输出一个数(角度)的正切值、余切值和绝对值。
2、用VC++打开stdlib.h和math.h,看看里面究竟写了些什么。如果不知道文件存放的位置,请使用Windows的查找功能。

3.函数的声明和定义

在上一节,我们已经学会了如何阅读函数原型和如何调用一个函数。然而,仅靠系统给出的标准库函数是不够用的。我们有时候要根据我们的实际要求,写出一个合适自己使用的函数。
那么,我们如何来自己动手编写一个函数呢?
首先,我们要告诉电脑,我们自己编写了一个函数,即这个函数是存在的,这叫作函数的声明(Declaration)。其次,我们要告诉电脑这个函数是怎么运作的,这叫作函数的定义(Definition)。显然,函数的声明和函数的定义是两个不同的概念。声明表示该函数存在,而定义则是表示该函数怎么去运行。
我们平时做事都是要有先后顺序的,如果把次序颠倒了可能会惹些麻烦出来。编写函数的时候也一样。我们必须在调用一个函数之前就告诉电脑这个函数已经存在了,否则就成了“马后炮”。所以,我们一般把函数的声明放在主函数前面。

函数的声明

在C++中,函数原型就是函数的声明。所以,函数原型除了向用户说明如何使用一个函数以外,还告诉电脑存在这样一个可以使用的函数。
我们已经介绍了函数原型的结构,只不过“产生结果类型”这个名称是为了方便理解而起的。它应该称为“返回值类型”,用任意一种数据类型来表示,比如int或者char等等,当然还包括空类型void。多个参数则构成了“参数表”,表示运行这个函数需要哪些数据。于是,函数原型的结构就是:
  返回值类型函数名(参数表);
函数声明同变量的声明一样,是一句语句。所以在语句结束要加上分号。函数名、参数名的规则和注意事项同变量名一样。
关于“返回”的概念稍后再作介绍,我们先来说说参数表。我们知道,在声明函数的时候,会写一些参数,而在调用函数的时候需要一一对应地填入这些参数。虽然它们都叫参数,但在不同的情况下,它们的含义是不同的。在声明一个函数的时候,参数是没有实际值的,只是起到一个占位的作用,所以称为形式参数,简称“形参”;在调用一个函数的时候,参数必须有一个确定的值,是真正能够对结果起作用的因素,所以称为实际参数,简称“实参”。我们拿数学中的函数作为例子,g(x,y)=x/4+y中的x和y就是形式参数,而g(4,1)=4/4+1=2中的4和1就是实际参数;如果令a=4、b=1,那么g(a,b)中的a和b也是实际参数。

函数的定义

说完了函数的声明,我们来说函数的定义。其实函数的定义对大家来说是比较熟悉的。因为我们之前所写的程序都是对主函数的定义。函数定义的格式为:
没有分号结尾的函数原型
{
    语句块;
}
我们把函数定义中没有分号结尾的函数原型称为函数头,把之后的语句块称为函数体。任何一个函数的定义不能出现在另一个函数体内。但函数体内可以调用任何一个函数,包括其本身。
下面我们先来看一个例子,你就会对函数定义有些了解了。(程序6.2.1)


运行结果:
5
程序在运行的时候从main函数开始,遇到调用一个用户定义的函数max,则去查找这个max函数的定义,然后运行max函数。运行完了以后,回到调用max函数的地方,继续后面的语句,直到程序结束。所以整个程序的运行过程如箭头所示。

不要使用相同的变量和参数

如果在一个班级里有两个同名同姓的同学,那么老师上课点名将是件麻烦事。因为大家都搞不清到底是在叫谁。可是,如果两个不同的班级有两个同名同姓的同学,就不会发生这种尴尬,因为老师在不同的教室点相同的名字,会有反应的只有一个同学。
我们把这个问题套用到函数上来。如果在同一个函数有两个名字相同的变量,那么电脑将无法分清到底要使用哪个变量,导致错误发生。而在不同的函数中,即使有相同名称的变量,由于在某一个函数中该变量的名称是唯一的,电脑也能方便的识别要使用哪个变量。因此,我们得到这样一个结论:一般情况下,在同一个函数中,不能有名称相同的变量或参数;在两个不同的函数中,可以有名称相同的变量或参数。
下面就让我们来看一个实例:(程序6.2.2)

#include "iostream.h" 
int max(int a,int b,int c);//求三个整数的最大者 
int min(int a,int b,int c);//求三个整数的最小者 
void output(int a);//输出功能 
int main() 
{ 
   int a=3,b=4,c=2; 
   output(max(a,b,c));//把max函数的返回值作为output函数的实参 
   output(min(a,b,c)); 
   return 0; 
} 
int max(int a,int b,int c)//不在同一个函数中,参数名重复没关系 
{ 
   if (a>=b && a>=c) return a; 
   if (b>=a && b>=c) return b; 
   return c;//一旦执行了前面的return,这句就不会被执行到 
} 
int min(int a,int b,int c) 
{ 
   if (a<=b && a<=c) return a; 
   if (b<=a && b<=c) return b; 
   return c; 
} 
void output(int a) 
{ 
   cout <<a <<endl; 
   return;//返回空类型 
} 

运行结果:
4 
2 

要注意,一旦函数运行结束,那么该函数中声明的参数和变量都将消失。就像下课了,同学们都回家了,老师叫谁都是叫不应的。

4.函数返回语句return

我们不难发现,在函数原型的参数表里,就像是多个变量声明的语句。我们可以将其视为创建了若干个变量,然后将实参的值一一赋给这些变量。然后再执行函数体内的语句,进行处理和运算。既然是实参把值赋给了形参,那么在函数体中的数据改变不会影响实参。关于这个问题,我们将在后续章节作详细介绍。
return称为返回语句。它的语法格式为:
    return 符合返回值类型的表达式;
对于返回,有两层意思。其一是指将表达式的值作为该函数运行的结果反馈给调用函数的地方。例如程序6.2.1中return b就是把b的值作为max函数的运行结果反馈给主函数,即t=max(r,s)的结果就是t=s(因为s=b)。其二是指结束该函数的运行,返回到调用该函数的地方,继续执行后面的语句。所以,如果执行了函数中的某一个return语句,那么之后的语句都不会再被运行。
如果返回值类型不是空类型,那么必须保证函数一定会返回一个值,否则会导致错误。
比如下列函数定义就是有问题的,因为当a<b的时候,函数没有返回值。

int m(int a,int b) 
{ 
   if (a>=b) return a; 
}
 
如果返回类型为空类型,则return语句的用法为:
return;
在返回空类型的函数中可以使用return语句,人为地停止函数的运行,也可以不使用return语句,使其运行完所有语句后自然停止。我们平时在返回空类型的主函数中不使用return语句就属于第二种情况。
要注意,返回值和运行结果是两种概念。返回值是函数反馈给调用函数处的信息,运行结果是函数通过屏幕反馈给用户的信息。

5.主函数main()

主函数是一个特殊的函数,不管把它放在代码的什么位置,每个程序的运行都是从主函数开始的。所以,我们说每个程序有且只能有一个主函数,否则电脑将不知道从何运行。既然电脑知道必须有且只能有一个主函数,那么就没必要去写主函数的函数原型了。
主函数也能返回值。根据最新的ANSI C++标准,主函数应该返回一个整型数值,返回这个值的目的是为了将程序的运行结果告知系统,比如程序是否正常结束,是否异常终止等等。一般地,如果返回0表示程序正常结束,返回其他值则表示程序异常终止。
关于ANSI C++标准
C++有许多不同的编译器,如VC++、BC、gcc等等。由于各种编译器产自不同的公司,在某些细节方面有一些区别。然而这些区别却像C++语法的各种“方言”,让用户掌握起来非常头疼。于是美国国家标准机构(American National Standards Institute)着手制定了C++的国际化标准,按照该标准的C++语法和编译器无关。目前市面上大多数的编译器都尽量向该标准靠拢。

6.为什么要使用函数

在第一节,我们已经知道使用工具的好处,即可以重复使用和在各种适用情况下使用。函数和工具一样具有这些好处。但是除此以外,函数的存在还有着其他的意义。
一、现在要设计一个“学生信息处理程序”,需要完成四项工作,分别是记录学生的基本情况、学生成绩统计、优秀学生情况统计和信息输出。如果我们把四项工作全都写在主函数里面,那么我们就很难分清那一段代码在做什么。多层次的缩进和不能重复的变量名给我们阅读程序带来了困难。
如果我们为每一个功能编写一个函数,那么根据函数名每个函数的功能就很清晰了。如果我们要修改某一个函数的功能,其他的函数也丝毫不会受到影响。所以,函数的存在增强了程序的可读性。
二、需要设计一个规模很大的程序,它有几千项功能,把这些功能都编写在一个主函数里就只能由一个人来编写,因为每个人解决问题的思路是不同的,而且在主函数中的变量名是不能重复的,只有编写者自己知道哪些变量名是可以使用的。这样一来,没有一年半载,这个程序是无法完成的。
如果我们把这几千项功能分拆为一些函数,分给几百个人去编写,那么用不了几天时间这些函数就都能够完成了。最后用主函数把这些完成的函数组织一下,一个程序很快就完工了。所以,函数能够提高团队开发的效率。它就像把各个常用而不相关联的功能做成一块块“积木”。完成了函数的编写,编程就像搭积木一样方便了。
三、程序会占用一定的内存用来存放数据。如果没有函数,那么在程序的任何一个地方都能够访问或修改这些数据。这种数据的非正常改变对程序的运行是有害的,给调试程序也会带来很多麻烦。
如果我们把若干项功能分拆为函数,则只要把函数原型提供出来就可以了,不需要将数据提供出来。一般情况下,别的函数无法修改本函数内的数据,而函数的实现方法对外也是保密的。我们把这种特性称为函数的黑盒特性。
我们认识到一个程序中需要有函数存在,于是一个更完整的程序结构出现了:
预处理头文件
各函数声明
主函数
{
   主函数体 //注释
}
各函数定义

7.函数重载

我们在开瓶瓶罐罐的时候,经常会遭遇因各种瓶口规格不同而找不到合适的工具的尴尬。所以有时候就为了开个瓶,家里要备多种规格的开瓶器。同样是开个瓶子嘛,何必这么麻烦?于是有人发明了多功能开瓶器,不管啤酒瓶汽水瓶还是软木塞的红酒瓶都能轻松打开。
然而开瓶器的问题也会发生到程序设计中。比如我们要编写一个函数来求一个数的绝对值,然而整数、浮点型数、双精度型数都有绝对值,但为它们编写的函数返回值类型却是各不相同的。比如:

int iabs(int a); 
float fabs(float a); 
double dabs(double a); 

这样是不是有点备了多种开瓶器的感觉?我们能不能在程序设计中也做一个多功能的开瓶器,把所有数据类型的求绝对值都交给abs这一个函数呢?
在C++中,我们也能够把具有相同功能的函数整合到一个函数上,而不必去写好多个函数名不同的函数,这叫做函数的重(音chóng)载(Overload)。重载的本质是多个函数共用同一个函数名。
我们先来看一个函数重载的实例:(程序6.3)
#include "iostream.h" 
int abs(int a);//当参数为整型数据时的函数原型 
float abs(float a);//当参数为浮点型数据时的函数原型 
double abs(double a);//当参数为双精度型数据时的函数原型 
int main() 
{ 
   int a=-5,b=3; 
   float c=-2.4f,d=8.4f; 
   double e=-3e-9,f=3e6; 
   cout <<"a=" <<abs(a) <<endl <<"b=" <<abs(b) <<endl;//输出函数返回的结果 
   cout <<"c=" <<abs(c) <<endl <<"d=" <<abs(d) <<endl; 
   cout <<"e=" <<abs(e) <<endl <<"f=" <<abs(f) <<endl; 
   return 0; 
} 
int abs(int a)//函数定义 
{ 
   cout <<"int abs" <<endl;//显示运行了哪个函数 
   return (a>=0?a:-a);//如果a大于等于零则返回a,否则返回-a。 
} 
float abs(float a) 
{ 
   cout <<"float abs" <<endl; 
   return (a>=0?a:-a); 
} 
double abs(double a) 
{ 
   cout <<"double abs" <<endl; 
   return (a>=0?a:-a); 
} 

运行结果:
int abs 
int abs 
a=5 
b=3 
float abs 
float abs 
c=2.4 
d=8.4 
double abs 
double abs 
e=3e-009 
f=3e+006 

运行结果表明,abs函数果然能够处理三种不同数据类型的数据了。那么我们怎样才能自己造一个“多功能工具”呢?
其实要编写一个重载函数并不是很麻烦。首先,我们要告诉电脑,同一个函数名存在了多种定义,所以,我们要给同一个函数名写上多种函数原型(如程序6.3的第二到第四行);其次,我们要对应这些函数原型,分别写上这些函数的定义(如程序6.3的主函数体之后,对三个abs函数的定义)。
然而电脑又是如何来识别这些使用在不同环境下的“工具”的呢?
在日常生活中使用到多功能工具,如果我们不知道具体应该使用哪个工具,我们会把每个工具放上去试一试,如果只有唯一一个工具适合,那么我们就毫无疑问地能够确定就是使用它了。但是如果出现了两个或者两个以上工具都能适合,我们就分不清到底应该使用哪个是正确的了。
电脑的做法和我们是类似的。电脑是依靠函数声明时参数表中参数个数、各参数的数据类型和顺序来判断到底要运行哪个函数的。因此,当重载函数参数表完全相同的时候,电脑便无法判断应该运行哪个函数,于是程序就出错了。
我们了解了电脑是如何识别重载函数以后,发现要编写一个重载函数还是需要注意一些地方的,那就是:在重载函数中,任意两个函数的参数表中的参数个数、各参数的数据类型和顺序不能完全一样。例如int func(int a,char b)和float func(int c,char d)就不能重载,因为它们的参数个数、各参数的类型和顺序完全一样,即使形参名不同、返回值类型不同也是无济于事的。
在调用一个重载函数时,可能会发生找不到一个完全合适的函数。这时候,就需要进行数据类型的转换。由于这种方法可能导致数据丢失或数据类型不严格符合,且在充分考虑问题后,这种情况是可以尽量避免的,所以这里不再就这个问题展开论述。有兴趣的读者可以查阅其他C++的参考资料。

算法时间:重载函数
从某种意义上说,重载函数是方便了函数的使用者。在前一节我们知道,如果完成了所有函数的编写,那么完成一个程序就像搭积木一样简单了。然而如果功能相似名字却不同的函数太多,那么多“积木”搭起来也未必简单。当函数的编写者充分考虑了不同情况下应该运行稍有不同的函数,函数的使用者就不必为这些小细节而烦恼了。不过重载函数的函数名还是应该符合其功能,如果把功能完全不同的函数重载,那么就大大影响了程序的可读性。

8.函数的默认参数

现在有很多电器都很人性化,比如自动洗衣机,如果想偷个懒你就可以直接把衣服扔进去,使用自动功能,它就能帮你全都搞定;如果哪天要洗个什么大件物品,你也可以人工对其进行设置,同样让你用得得心应手。
我们在调用函数时,可能会要填写很多的参数,那么电脑能否像自动洗衣机一样,让我们偷个懒,帮我们把参数都自动填好呢?
我们知道,所谓自动洗衣功能就是使用其预置好的程序进行洗涤。如果我们也将函数的参数预置好,那么我们同样可以不必填写参数就能让函数运作起来。这些预置的参数称为默认参数。
下面我们先来看一个程序,熟悉一下如何来定义默认参数:(程序6.4)

#include "iostream.h" 
void create(int n=100);//在函数声明中定义默认参数 
int main() 
{ 
   create();//默认实参为100 
   create(5);//人工设置实参 
   return 0; 
} 
void create(int n)//假设该函数的作用是创建空间 
{ 
   cout <<"要创建" <<n <<"个空间" <<endl; 
} 
运行结果:
要创建100个空间 
要创建5个空间 
当调用create函数,不填写参数时,电脑自动将参数n设置为100了。而当我们填写参数时,函数也能够按照我们的意愿正常运行。
在定义默认参数时,必须在函数声明中定义。不过,当对多个参数设置默认参数时,会有一些麻烦的情况。

定义默认参数的顺序

当一个函数具有多个参数时,定义默认参数的方向是从右向左的,即以最后一个参数定位的;而匹配参数的方向是从左向右的,即以第一个参数定位的,如下图所示:
如果我们要定义默认参数,那么我们必须从最后一个参数定义起,并且逐渐向前(左)定义,不可以跳过某个参数,直到所有的形参都被定义了默认值。
如果我们调用一个定义了默认参数的函数,那么我们填写的第一个参数一定是和最左边形参匹配,并且逐渐向后(右)匹配,不可以中途省略某一个参数,直到所有未被设置默认值的形参都已经有参数。
于是,在调用函数时,用户向右自定义的实参至少要和向左来的已定义默认参数的形参相邻,函数才能够被成功调用。否则这个函数就是缺少参数的。

默认参数和重载函数的混淆

我们在上一节讲了重载函数这个有用的工具,这一节的默认参数也会给我们的程序设计带来方便,然而我们把这两样有用的东西放在一起,却会带来不小的麻烦。我们来看下面这些函数原型:

int fn(int a); 
int fn(int a,int b=2); 
int fn(int a,int b=3,int c=4); 
这些函数不论是从重载的角度看,还是从默认参数的角度看都是合法的。然而,这样的写法却是不合理的。
当我们调用函数fn(1)的时候,三个函数都是可以匹配的,因为电脑可以认为我们省略了后面的参数;当我们调用函数fn(1,1)的时候,后两个函数也都是可以匹配的……由于电脑无法确认我们到底想要调用哪个函数,所以导致了错误的发生。
因此,我们在同时使用重载函数和默认参数的时候,要注意到这一点。

9.函数变量的引用

给别人起绰号是件不好的事情,但是在程序设计中,给变量或参数起个绰号却会给我们带来一些方便。绰号,就是另一种称呼。绰号和本名都是指同一样东西,绰号也是个名称,所以它的命名规则其他的命名规则一样,详情可参见3.1。另外,“绰号”显然也不能和“本名”相同。
这种给变量起“绰号”的操作称为引用(Reference),“绰号”称为引用名。声明引用的语法格式为:
  变量数据类型 &引用名=已声明的变量名;
我们对变量使用了引用以后,对引用的操作就如同对被引用变量的操作。这就好像叫一个人的绰号和叫一个人的本名有着同样的效果。在声明一个引用时,必须告知电脑到底是哪个变量被引用,否则这个“绰号”就显得有些莫名其妙了。
下面我们来看一段简单的程序:(程序6.5.1)

#include "iostream.h" 
int main() 
{ 
   int a=2; 
   int &b=a;//给变量a起了个绰号叫b 
   cout <<"a=" <<a <<endl; 
   cout <<"b=" <<b <<endl; 
   a++; 
   cout <<"a=" <<a <<endl; 
   cout <<"b=" <<b <<endl; 
   b++;//对b的操作也就是对a的操作,所以b++就相当于a++ 
   cout <<"a=" <<a <<endl; 
   cout <<"b=" <<b <<endl; 
   return 0; 
} 
运行结果:
a=2 
b=2 
a=3 
b=3 
a=4 
b=4 
我们在这个程序中,能够验证对引用的操作相当于对被引用变量的操作。或许你还没看出引用到底派了什么大用处,不过马上你就会恍然大悟了。

用引用传递参数

其实引用最有用的地方还是在函数当中,下面我们先来看一个简单的例子:

#include "iostream.h" 
void swap(int x,int y); 
int main() 
{ 
   int a=2,b=3; 
   swap(a,b); 
   cout <<"a=" <<a <<endl; 
   cout <<"b=" <<b <<endl; 
   return 0; 
} 
void swap(int x,int y) 
{ 
   int temp; 
   temp=x; 
   x=y; 
   y=temp; 
} 
运行结果:
a=2 
b=3 
在以上这段程序中,swap函数的语句是我们熟悉的交换语句,可是为什么执行了这个swap函数以后,a和b的值并没有交换呢?
在6.2中,我们介绍过,函数是将实参的值赋给了形参。这就像本来我们想交换a碗和b碗里的水,调用了swap函数则是拿来了x碗和y碗,然后把a碗里的水分一点到x碗里,b碗里的水分一点到y碗里,再把x碗和y碗里的水交换。可是,这样的操作有没有将a碗里的水和b碗里的水交换呢?没有。而且,我们还知道,一旦函数运行结束,函数中定义的参数和变量就都会消失,所以就连x碗和y碗也都没有了。
问题到底在于哪里呢?在于我们传给函数的是“水”,而不是“碗”。如果我们直接把a碗和b碗交给函数,那么这个任务就能够完成了。下面我们就来看看如何把“碗”来传递给函数:(程序6.5.2)
#include "iostream.h"  
void swap(int &x,int &y);//用引用传递参数 
int main() 
{ 
   int a=2,b=3; 
   swap(a,b); 
   cout <<"a=" <<a <<endl; 
   cout <<"b=" <<b <<endl; 
   return 0; 
} 
void swap(int &x,int &y)//函数定义要和函数原型一致 
{ 
   int temp; 
   temp=x; 
   x=y; 
   y=temp; 
} 
运行结果:
a=3 
b=2 
如果我们把没有使用引用的参数认为是int x=a和int y=b(把变量a和变量b的值分别传给参数x和参数y),那么使用了引用的参数就是int &x=a和int &y=b了。也就是说x和y成为了变量a和变量b的“绰号”,对参数x和y的操作就如同对变量a和b的操作了。
除了在引用变量和通过引用传递参数外,引用还能被函数返回。关于这些内容,有兴趣的读者可以查阅相关书籍资料。
6.6*

10.函数的递归调用

在高中数学中,我们学习过数列。我们知道数列有两种表示方法,一种称为通项公式,即项an和项数n的关系;还有一种称为递推公式,即后一项an和前一项a(n-1)之间的关系。通项公式能够一下子把an求解出来,而递推公式则要根据首项的值多次推导才能把第n项的值慢慢推导出来。如果有一个较为复杂的数列的递推公式,人工将其转化为通项公式或者将其的每一项求出实在非常麻烦,那么我们能不能直接把这个递推公式交给计算机,让它来为我们求解呢?
我们说过,在任何一个函数体内不能出现其它函数的定义。但是,在任一个函数体内可以调用任何函数,包括该函数本身。直接或者间接地在函数体内调用函数本身的现象称为函数的递归。正是函数的递归,能够帮我们解决递推公式求解的问题。
现有一个数列,已知an=2*a(n-1)+3,并且a1=1,求解a1到a8的各项值。我们把数列问题转化为函数问题,认为an=f(n),a(n-1)=f(n-1)……于是f(n)=2*f(n-1)+3,f(n-1)=2*f(n-1-1)+3……直到f(1)=1。我们根据前面的描述写出以下程序:(程序6.6)

#include "iostream.h" 
int f(int n);//看作数列an 
int main() 
{ 
   for (int i=1;i<=8;i++)  
   { 
      cout <<"f(" <<i <<")=" <<f(i) <<endl;//输出a1到a8的值 
   } 
   return 0; 
} 
int f(int n) 
{ 
   if (n==1) 
   { 
      return 1;//告知a1=1 
   } 
   else 
   { 
      return 2*f(n-1)+3;//告诉电脑f(n)=2*f(n-1)+3 
   } 
} 
运行结果:
f(1)=1 
f(2)=5 
f(3)=13 
f(4)=29 
f(5)=61 
f(6)=125 
f(7)=253 
f(8)=509






版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

SugarCRM开发入门

入门(探讨请加微信:JiangHuKeyKe)概述Sugar最初是基于LAMP(Linux、Apache、MySQL和PHP)运行的。1.0版本以来,Sugar开发团队增加支持多种操作系统(包括Win...

asp.net mvc(参考asp.net开发指南)

什么是Model Model可翻译为“模型”,笔者认为译成“数据模型”会更贴切一些,因为Model负责所有与“数据”相关的任务,大致如下: 定义数据结构 负责与数据库沟通 从数据库中读取数据 ...

c#方法重载,可选参数,命名参数。

其实这里没什么可说哦,c++的语法大同小异。先看一段代码。 class Program { public static void Test(int a) { Console...
  • tabe123
  • tabe123
  • 2015年08月21日 15:57
  • 758

C++深入体验之旅九:程序调试

1.头文件的奥秘 如何创建一个头文件 在第二章中,我们看到一个C++的工程里面除了源文件还有头文件。根据以下步骤便能创建一个头文件: 首先要创建或打开一个工程,然后按File菜单中的new...

C++深入体验之旅六:数组

1.数组的声明和初始化 我们知道,在程序设计中,大多数数据都是存放在变量里的。如果我们要处理较多的数据,增加存放数据的空间最简单的方法就是多开设一些变量。然而,变量多了就难以管理了。这就好像一个...

C++深入体验之旅八:枚举类型和结构体

1.什么是枚举类型 在基本的数据类型中,无外乎就是些数字和字符。但是某些事物是较难用数字和字符来准确地表示的。比如一周有七天,分别是Sunday、Monday、Tuesday、Wednesday...

C++深入体验之旅三:分支结构

1.if语句 对于可能发生的事情,我们平时经常会说“如果……,那么……”。语文里,我们叫它条件复句。“如果”之后的内容我们称为条件,当条件满足时,就会发生“那么”之后的事件。我们来看这样一句英语:If...

C++深入体验之旅二:变量和数据

1.C++变量简介   1、什么是变量? 电脑具有存储的功能。我们可以通过Word打开一个保存的文章,也可以通过FPE(整人专家,一款游戏修改软件)来查看或锁定内存中保存的游戏人物的生命值。那么,一...

C++深入体验之旅七:指针

1.什么是指针 在我们的桌面上,往往有这样一些图标:在它们的左下角有个小箭头,我们双击它,可以调出本机内的一些程序或文件。然而我们发现这些图标所占的存储空间很小,一般也就几百到几千字节。可是那么小的...

C++深入体验之旅十一:类(上)

1.类是一种数据类型 我们已经了解了数据类型和变量的关系。数据类型是各个变量的归类,而变量则是某个数据类型的具体表现,我们称变量为数据类型的一个实例(Instance)。各个变量都有他们的属性:...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++深入体验之旅五:C++函数
举报原因:
原因补充:

(最多只允许输入30个字)