上一讲鸡啄米讲了函数的定义和调用,这一讲接着给大家讲函数的相关知识。让大家了解编程入门时需要知道的函数的参数是怎样传递的以及内联函数的概念。
一.函数的参数传递
上一讲中鸡啄米提到了函数的参数有形参和实参两种,那两者到底有什么区别呢?我们在声明及定义函数的时候跟的参数叫做形参,调用函数时传进去的参数称为实参。比如,函数int fun(int a),这里的a就是形参,我们在其他地方调用函数fun,将外部的变量b传进去,即fun(b),这里的b就是实参。实参可以是常量、变量或表达式,它的类型必须和形参相符。函数没有被调用时,形参并不占用内存,只有在调用时才会分配内存空间,然后将实参传进去。函数参数的传递有两种方式,值调用和引用调用。
1.值调用
值调用就是调用函数时,给形参分配内存空间,将实参拷贝给形参,之后的函数执行中形参和实参就脱离了关系,谁都不影响谁。也就是值调用时,只是用实参的值初始化下形参,之后两者互不影响。
鸡啄米给大家个例子就很明白了:
#include <iostream> using namespace std; void Swap(int a, int b); int _tmain(int argc, _TCHAR* argv[]) { int x=5, y=10; cout<<"x="<<x<<" y="<<y<<endl; Swap(x,y); cout<<"x="<<x<<" y="<<y<<endl; return 0; } void Swap(int a, int b) { int t; t=a; a=b; b=t; }
运行结果是:
x=5 y=10
x=5 y=10
从运行结果中,大家可以看出,本来我们想调用Swap函数把x和y的值交换过来,但是事与愿违。这就是因为,这是值调用的方式,x和y分别传值给a和b后,a跟b的值在Swap内怎样变化都影响不到x和y,所以两次输出x和y的值没有变化。
2.引用调用
如果我们想让被调函数中对形参做的修改同样对主调函数中的实参有效,用值调用不行,那怎么办呢?答案是用引用调用。
引用是一种特殊类型的变量,我们可以认为它是另一个变量的别名,利用引用名和原来的变量名访问变量的效果是一样的。引用的形式是:类型标识符 &引用名。比如:
int i, j; int &ri=i; // 建立一个int型的引用ri,并将其初始化为变量i的一个别名 j=10; ri=j; // 相当于 i=j;
使用引用鸡啄米提醒大家必须注意以下问题:声明一个引用时,必须同时对它进行初始化,使它指向一个已存在的对象;一旦一个引用被初始化后,就不能改为指向其它对象。简单说就是引用定义的时候就指定它指向的变量,之后就不能变了。
引用可以作为形参,比如void swap(int& a, int& b) {...}。这个时候引用就不需要对其初始化了,因为形参只是类型说明,主调函数调用这个函数时才会为形参分配内存,也才会用实参来初始化形参。用引用调用后,形参就是实参的别名而已,对形参做的任何改变都会影响实参发生同样的改变。鸡啄米再拿上面那个例子改成引用调用说明下:
#include <iostream> using namespace std; void Swap(int& a, int& b); int _tmain(int argc, _TCHAR* argv[]) { int x=5, y=10; cout<<"x="<<x<<" y="<<y<<endl; Swap(x,y); cout<<"x="<<x<<" y="<<y<<endl; return 0; } void Swap(int& a, int& b) { int t; t=a; a=b; b=t; }
运行结果就跟我们想的一样了:
x=5 y=10
x=10 y=5
引用调用后x和y的值成功的交换了。值调用和引用调用的区别只是函数的形参写法不同,主调函数调用被调函数时的调用语句是一样的。
二.带默认形参值的函数
函数在声明时可以预先定义默认的形参值。调用时若给出实参则用实参初始化形参,如果没有给出实参则采用预先定义的默认形参值。例如:
int add(int x=5,int y=6) // 定义默认形参值 { return x+y; } int main() { add(10,20); // 用实参来初始化形参,实现10+20 add(10); // 形参x采用实参值10,y采用默认值6,实现10+6 add(); // x和y都采用默认值,分别为5和6,实现5+6 return 0; }
默认参数值必须按照从右向左的顺序定义。在有默认值的形参右面,不能出现无默认值的形参。也就是说应该把有默认值的形参都一块放到右边,不能让有默认值的跟没默认值的形参穿插着放。因为在调用时,实参初始化形参是按从左向右的顺序。比如:
int add(int x,int y=5,int z=6); //正确 int add(int x=1,int y=5,int z); //错误 int add(int x=1,int y,int z=6); //错误
调用出现在函数体实现之前时,默认形参值必须在函数原型中也就是声明时给出;而当调用出现在函数体实现之后时,默认形参值需在函数实现时给出。例如:
调用在实现前时:
int add(int x=5,int y=6); int main() { add(); //调用在实现前 return 0; } int add(int x,int y) { return x+y; }
调用在实现后时:
int add(int x=5,int y=6) { return x+y; } int main() { add(); //调用在实现后 return 0; }
在相同的作用域内,默认形参值的说明应保持惟一,但如果在不同的作用域内,允许说明不同的默认形参。这里的作用域是指直接包含函数原型说明的大括号所界定的范围,具体鸡啄米会在后面跟大家讲。例:
int add(int x=1,int y=2); int main() { int add(int x=3,int y=4); add();//使用局部默认形参值(实现3+4) return 0; } void fun() { ... add();//使用全局默认形参值(实现1+2) }
三.内联函数
函数虽然有很多优点,比如代码复用,便于维护等。但是调用函数时需要保存现场和返回地址,被调函数执行完后还要取出这些值继续执行,这些过程在时间和空间方面都有开销。对于一些规模小、功能简单的函数可以定义成内联函数。内联函数在调用时不需要那些转移带来的开销,它是在编译的时候把函数体代码嵌入到所有调用它的语句处,我们可以认为直接把函数体的代码放那里了,当然也不完全一样,毕竟它可能有参数。
内联函数定义时使用关键字inline,语法形式如下:
inline 类型标识符 被调函数名(含类型说明的形参表)
鸡啄米提醒大家使用内联函数时必须注意:内联函数体内不能有循环语句和switch语句;内联函数的定义必须出现在内联函数第一次被调用之前;对内联函数不能进行异常接口声明,就是不能声明可能抛出的异常,异常接口声明鸡啄米以后会讲到。
内联函数应该是语句比较少、结构比较简单的函数,不应该是复杂的函数,因为它对编译器来说就是代码,如果很复杂会造成代码膨胀,反而增大开销,这种情况下其实多数编译器就都会自动把它作为普通函数来处理了。一般来说,包含循环语句的函数一定不能定义成内联函数。鸡啄米再给大家一个内联函数的例子:
#include <iostream> using namespace std; inline double CalArea(double radius) { return 3.14*radius*radius; } int _tmain(int argc, _TCHAR* argv[]) { double r(3.0); double area; area=CalArea(r); cout<<area<<endl; return 0; }
运行结果是:28.26
这一讲鸡啄米给大家的源代码的例子比较多,大家看看这些代码就会更好的理解鸡啄米所讲的内容,希望大家在编程入门的时候不要着急,好好消化教程。