ITEEDU

3.15 带空参数表的函数

在C++中,空参数表可以用void指定或括号中不放任何东西。下列声明:

void   print();

指定函数print不取任何参数,也不返回任何值。图3.18演示了C++声明和使用带空参数表的函数的方法。

编程技巧3.11

虽然函数先定义后使用时可以省略函数原型,最好也提供函数原型。提供函数原型可以避免代码使用时受到函数定义顺序的限制。(这个顺序可能随程序的演变而改变)。

// Fig. 3.18: fig03_l$.cpp 
 // Functions that take no arguments
 #include < iostream.h>
 void functionl( );
 void function2( void );
 int main( )
 {
 functionl( );
 function2( );
 return 0;
 }
 void functionl( )
 {
 cout << "functionl takes no arguments" << endl;
 }
 void function2( void )
 {
 cout << "function2 also takes no arguments" << endl;
 }

输出结果:

functionl takes no arguments 

function2 also takes no arguments 

图3.18 两种声明和使用带空参数表函数的方法

可移植性提示3.3

C++中带空参数表的函数含义和C语言中大不相同。在C语言中,它表示不对所有参数检查(即函数调用可以传入任何变量),而在C++中则表示函数不取任何参数。这样,使用这个特性的C语言程序在C++中编译时可能产生语法错误。

介绍省略问题后应该注意,文件中在任何函数调用之前进行定义的函数不需要另外加上函数原型。这时函数首部就当作函数原型。

常见编程错误3.25
除非提供每个函数的函数原型或先定义再使用每个函数,否刺不能编译C++程序。

3.16 内联函数

从软件工程角度看,将程序实现为一组函数很有好处,但函数调用却会增加执行时的开销。

C++提供了内联函数(inline function)可以减少函数调用的开销,特别是对于小函数。函数定义中函数返回类型前面的限定符inline指示编译器将函数代码复制到程序中以避免函数调用。其代价是会产生函数代码的多个副本并分别插入到程序中每一个调用该函数的位置上(从而使程序更大),而不是只有一个函数副本(每次调用函数时将控制传人函数中)。典型情况下,除了最小的函数以外编译器可以忽略用于其他函数的inline限定符。

软件工程视点3.16

任何对内联函数的改变都可能要求函数的所有客户重新编译。这样可能在有些程序的开发和维护中影响非常大。

编程技巧3. 12

inline限定符只用于经常使用的小函数。 .

性能提示3.9

使用内联函数可以减少执行时间,但会增加程序长度。

图3.19的程序用内联函数cube计算边长为s的立方体体积。函数cube参数表中的关键字const表示函数不修改变量的值s。关键字const将在第4、5、7章详细介绍。

 // Fig. 3.19:fig03 19.cpp
 // Using an inline function to calculate
 // the volume of a cube.
 #include < iostream.h>
 inline float cube( const float s ) { return s * s * s; }
 int main( )
 {
 cout << "Enter the side length of your cube: ";
 float side;
 cin >> side;
 cout << "Volume of cube with side ,,
 << side <<" is "<< cube( side ) << endl;
l8 return O;
 }

输出结果:

Enter the side length Of your cube:3.5 

Volume Of cube with side 3.5 is 42.875 

图3.19 用内联函数计算立方体的体积

软件工程视点3.17
即使被调用函数不修改变量,许多程序员也习惯将数值参数声明为const, const只保留原始参数的副本,而不保留原姑参数本身。

3.17 引用与引用参数

许多编程语言中调用函数的两种方法是按值调用(call—by-value)和按引用调用(call—by—referrence)。参数按值调用传递时,生成参数值副本并传给被调用函数。副本的改变并不影响调用者的原始变量值,这样就可以防止意外的副作用影响开发正确、可靠的软件系统。本章前面的程序中

每个传递的参数都是按值调用传递的。

性能提示3.10

接值调用传递的一个缺点是,如果传递较大的数据项目,则复制这个数据可能要占用相当长的执行时间。

本节介绍引用参数,这是“+提供的两种按引用调用的方法之一。桉引用调用时,调用者让被调用函数能够直接访问调用者的数据,并允许被调用函数能够修改其中的数据。

性能提示3.11

按引用调用对性能有利,因为它消除了复制大量数据的开销。

软件工程视点3.18

按引用调用的安全性较差,因为被调用函数能够直接访问和修改调用者的数据。

我们将介绍如何利用按引用调用的性能优势同时又满足软件工程的要求(防止破坏调用者数据)。

引用参数是其相应的参数的别名。要表示函数的参数是按引用传递的,只要在函数原型中该参数类型后面加上 &即可,在函数首部中列出参数类型时也要使用相同的规则。例如,函数首部中的下列声明:

int     &count

表示count是int的引用。在函数调用中,只要指定变量名,该变量就会通过引用传递。在被调用函数体中,通过参数名指定的变量实际上就是引用了调用函数中的原始变量,被调用函数可以直接修改原始变量。一般来讲,函数原型和函数首部必须相符。

图3.10比较按值调用、按引用调用与引用参数。调用squareByValue和squareByReference中,所用参数的形式是相同的,都是只指定名称。如果不检查函数原型或函数定义,要判断被调用函数是否修改了该参数是不可能的。由于函数原型是强制的,因此编译器能够顺利解决歧义性。

 // Fig. 3.20: fig0320.cpp
 // Comparing call-by-value and call-by-reference
 // with references.
 #include < iostream.h>
 int squareByValue( int );

 void squareByReference( int& ); int main( )
{
 int x = 2, z = 4;
 Cout << "x = "<< x << "before squareByValue "
 << "Value returned by squareByValue: "
 << squareByValue( x ) << endl
 << "x = "<< x << "after squareByValue " << endl;
 cout << "z =" << z << "before squareByReference" << endl;
 squareByReference( z );
 cout << "z =" << z << "after squareByReference" << endl;
 return 0;
 }
 int squareByvalue ( int a )
 {
 return a *= a; // caller's argument not modified
 }
 void squareByReference( iht &cRef )
 {
 cRef *= cRef; // caller's argument modified
 }

输出结果:

x=2 before squareByValue 

Value returned by squareByValue: 4 

x = 2 after squareByValue 



z = 4 before $quareByReference 

z = 16 after squareSyReference 

图 3.20 按引用调用的举例

常见编程错误3.26

由于引用参数在被调用函数体中只指定名称.因此程序员可能把引用参数当作按值调用的参数。这样,如果调用函数改变变量原始副本,则可能产生预想不到的副作用。

第5章介绍指针时,将会介绍指针提供另一种形式的引用调用,调用样式能明确表示按引用调用(可能修改调用者的参数)。

性能提示3.12

如果要传递较大的对象,用常量引用参数模拟按值调用的情况.避免传递较大对象副本的开销。

要指定引用常量,在参数声明的类型说明符前面加上const限定符。

注意叫”squareByReference”函数参数表中 &的位置。有些C++程序员喜欢写成int& cRef而不是int &cRef。

软件工程视点3.19

为了获得程序的清晰性和高性能,许多C++程序员喜欢通过指针将可修改参数传道给函数,不可修改的小参数按值调用传递,而不可修改的大参数用常量引用传递给函数。

引用也可以用作函数中其他变量的别名。例如下列代码:

int count = 1 // declare integer variable count

int &cRef = count; // create cRef as an alias for count
++cRef; // increment count (using its alias)

用别名eRef递增变量count的值。引用变量应在声明中初始化(见图3.21和图3.22),不能作为其他变量的别名而重新赋值。将引用声明为另一变量的别名后,对该别名(即引用)进行的所有操作实际上是对原始变量本身进行的,别名只是原始变量的另一个名称。获得引用地址和比较引用不会造成语法错误,而且每个操作实际上是对原始变量本身进行的。引用参数应为左值,而不能是常量或返回左值的表达式。

常见编程错误3.27

在一条语句中声明多个引用时会出现一些常见问题。例如,要声明x、y、z变量为整数的引用,表达式 int& x=a,y=b,z=c 或int& x,y,z;都是错误的.正确的应是int &xa, &y=b,&z=c;。

函数可以返回引用,但却会经常出现问题。函数返回被调用函数中声明的变量的引用时,变量应在函数中声明为static,否则引用指的是函数终止时删除的动态变量,这个变量是未定义的,程序的执行情况将无法预测(有些编译器会对此发出警告)。引用未定义变量称为悬挂引用(danglingreference)。

常见编程错误3.28

声明引用变量而不对其进行初始化是个语法错误。

 // References must be initialized
 #include < iostream.h>
 int main( )
 {
 int x = 3, &y = x; // y is now an alias for x
 cout << "x =" << x << endl << "y =" << y << endl;
 y=7;
 cout << "x -" << x << e.dl << "y -" << y << endl;
 retur. 0;
 }

输出结果:

x = 3

y = 3

x = 7

y = 7

图 3.21 使用初始化的引用

 // References must be initialized
 #include < iostream.h>
 int main( )
( 
 int x = 3, &y; // Erroz: y must be initialized
 cout << "x =" << x << endl << "y = "<< y << endl;
 y = 7;
 cout << "x =" << x << endl << "y -" << y << endl;
 return 0;
 }

输出结果:

Compiling FIG0321.CPP:

Error FIG03_21,CPP 6:Reference variable 'y' must be

initialized

图 3.22 使用未初始化的引用

常见编程错误3.29

将前面声明的引用重新变为另一变量的别名是个逻辑错误,只是将已经是别名的引用的地址赋给另一变量。

常见编程错误3.30
被调用函数中返回自动变量的指针或引用是个逻辑错误。有些编译器会对此发出警告。

3.18 默认参数

函数调用可能通常传递参数的特定值。程序员可以将该参数指定为默认参数,程序员可以提供这个参数的默认值。当函数调用中省略默认参数时,默认参数值自动传递给被调用函数。

默认参数必须是函数参数表中最右边(尾部)的参数。调用具有两个或多个默认参数的函数时,如果省略的参数不是参数表中最右边的参数,则该参数右边的所有参数也应省略。默认参数应在函数名第一次出现时指定,通常是在函数原型中。默认值可以是常量、全局变量或函数调用。默认参数也可以用于内联函数中。

图3.23演示用默认参数计算箱子的容积。第5行boxVolume的函数原型指定所有三个参数的默认值均为1。注意,默认值只能在函数原型中定义,另外,我们在函数原型中提供变量名以增加可读性,当然变量名在函数原型中不是必需的。

 // Fig. 3.23:fig03 23.cpp
 // Using default arguments
 #include < iostream.h>
 int boxVolume( int length = 1, int width = 1, int height = 1 );
 int main( )
 {
 cout << "The default box volume is: "<< boxVolume( )
 << " \mThe volume of a box with length 10, "
 << "width 1 and height 1 is: "<< boxVolume( 10 )
 << " The volume of a box with length 10, "
 << "width 5 and height i is: "<< boxVolume( 10, 5 )
 << " The volume of a box with length 10, "
 << "width 5 and height 2 is: "<< boxVolume( 10, 5, 2 )
 << endl;

 }
 // Calculate the volume of a box
 iht boxVolume{ int length, iht width, int height )
 {
 return length * width * height;
 }

输出结果:

The default box volume is: 1

The volume of a box with length 10,

width 1 and height 1 is: 10

The volume of a box with length 10,

width 5 and height 1 is: 50

The volume of a box with length 10,

width 5 and height 2 is: 100

图3.23 用默认参数计算箱子的容积

首次调用函数boxvolume(第9行)时不指定参数,因此三个参数都用默认值。第二次调用函数boxVolume(第11行)时只传递length参数,因此width和height参数用默认值。第三次调用函数boxVolume(第13行)时只传递length和width参数,因此height参数用默认值。最后一次调用函数boxVolume(第15行)时传递length、width和height参数,因此不用默认值。

编程技巧3.13

使用默认值能简化函数调用,但有些程序员认为显式指定所有参数更清楚。

常见编程错误3.31
指定和试图使用的默认参数不是最右边的参数(即没有把该参数右边的参数指定为默认参数)。