C++ 函数

来源:互联网 发布:简单java代码 编辑:程序博客网 时间:2024/06/02 02:49

函数的变化

正确的抽象机制要求你使用函数调用来隐藏实现代码。为了管理你将用到的所有函数,C++加强了对函数类型的要求。在使用函数之前,你必须声明它,包括它的参数类型。然而这里还有一定的灵活性,你可以赋予两个函数相同的名字,只要能够根据它们的参数将其区分开。

函数声明

函数声明(declaration)或称为函数原型(prototype)说明的是函数接口而不是函数主体。在C++或ANSI/ISO C 中,函数声明如下:

longfoo(char* a);  //declaration of foo():takes char*, returns long

float bar(int, char); //declaration of bar(): takes int and char, returns float

它们由函数名、参数类型和返回值构成。参数类型是可选的。函数定义则指函数体:

int next(int i) {return i+1;}  //definition ofnext()

所谓函数调用(invocation)也就是调用一个函数:

int main()

{

         return next(5); //invocation of next()

}

函数声明就好像是维系函数调用和定义之间的桥梁。在C中,这个桥梁是可选的,编译器允许我们调用直到源代码中的某一点还没有声明(或真正定义)的函数。在C++中,一个函数在被调用之前一定要声明(或真正定义),否则编译器会报错。在函数调用之前必须先声明,这使得C++的编译器能够捕捉到C编译器可能错过的常见错误,例如错误的参数类型或拼写错误的函数名等。

C++和C在函数声明中表示控参数和没有限制的参数表时有所不同,如表2-1所示。

 

表2-1  C和C++中对空参数和没有限制的参数声明

意义

C

C++

空参数

f(void)

f() 或 f(void)

对参数没有限制

f()

f(…)

在C中,声明为f()的函数可以使用任何参数。要说明一个空的参数表,需将函数声明为f(void)。在C++中,前两种方式均表示空参数表。要说明没有限制的参数,需在函数声明中使用省略号,即f(…)。

参数产生的灵活性

为了减轻处理多个函数参数的负担,C++提供了三个技巧,它们在不同的时间内可以略去参数。我们前面已经提到过,在函数声明中参数名是可选的,对函数foo()来说就是这样:

intfoo(char* a); //these are equivalent declarations

int foo(char* );

在函数定义中你同样可以省略一个参数名。这会告诉编译器你并不打算使用那个参数。这里,draw3dpoint()的第三个参数是无用的:

voiddraw3dpoint(int x, int y, int) {

//thirdparameter has no name so the compiler knows we do //not use it

Draw2dpoint(x,y);

}

main(){draw3point( 1, 2, 7);} //arg still required in invocation


函数draw3point()大概暂时只使用它的前两个参数,并且调用函数draw2dpoint()。我们避免了使编译器给出警告,说第三个参数没有命名所以无法使用。这一技巧在程序开发的初始阶段是非常有用的,此时许多函数只不过是占位之用。

最后,函数还可以给参数赋缺省值。在调用函数时,你可以使用缺省的参数值而将其省略掉。这里,函数dist()的最后两个参数有缺省值:

double dist (int x1, int y1, int x2 = 0, int y2 = 0) {

//last two parameters have default values

return sqrt (sqr(x2-x1)+sqr(y2-y1)) };

main () {

         double d1 = dist (1,2,3,4);        //get distbetween (1,2) and (3,4)

         double d2 = dist (1,2,3);           //get dist between (1,2) and (3,0)

        double d2 = dist (1,2);               //get dist between (1,2)and (0,0)

}

 

函数dist()有四个参数,返回值是参数所代表的两个平面点之间的欧几里得距离。后两个参数的缺省值是0。在调用函数时我们提供的参数可以少于四个,如我们初始化d2和d3时所做的那样。

函数重载

一个函数的特征是函数名加参数的数量和类型。下面是函数foo的声明:

void foo(inta, int b);

它的特征是foo(int , char)。C++允许多个函数使用相同的名字,只要它们的特征不同即可:

voidfoo(int a, int b);                  //firstfoo

voidfoo(int a);                             //secondefoo

voidfoo(double a);                     //thirdfoo

main () {

         foo(1,2);            //calls the first foo

         foo(1);                //callsthe second foo

         foo(.5);              //calls the third foo

         //…

}

链接

C++被设计成可以和传统的链接程序(linker)协同工作。传统的链接程序只知道函数名,而对它的参数类型却一无所知,因此它们不能检查你所传递的参数是否正确,更不懂得重载。为解决这些问题,C++在把函数名传递给链接程序之前会将它们替换为函数特征,这称为名字编码(name coding)。当你调用函数时使用的参数错误,或忘记对一个重载函数进行定义时,可能就会明白。链接程序可能会作如下提示:

Undefined sumbol: func_FPi3Foo

这可能是对函数func(int*, Foo)的编码。

C++允许你不使用名字编码,因此你可以链接到C编译器编译的代码上,这样做需要一个特殊声明extern:

extern “C” int func(int *, Foo);

这个函数传递给链接程序的名字中并没有被编码的参数类型。

 

这个例子给出了三个名为foo的函数,但是它们的特征不同。当编译器遇到函数调用foo()时,它就根据列出的参数数量和类型选择正确的函数。一个名字被多个函数使用称为重载(overload)。注意返回类型不是特征的一部分,你不能对两个除了返回类型不同其他都相同的函数进行重载,就像下面所示的两个函数:

voidfoo(int a, int b);      //firstfoo

doublefoo(int a, int b);    //error: samesignature as first foo

当一个函数调用中的参数类型和数量与某一个函数特征完全吻合时,那么调用哪个函数就不言而喻了。如果没有相匹配的函数,编译器会自动进行类型转换使调用和某个函数相匹配。自动类型转换时非常复杂和微妙的。例如:

voidbar(double d);            //first bar

void bar(long l);                 //second bar

main () {

         bar (3);                                 //ambiguous because 3 is an int

         //…

}

在上面的例子中,函数调用bar(3)中的参数是int型的,它与double和long都不匹配,编译器必须决定他们中的哪一个与int型更接近。在这个特殊的例子中,编译器会判断它们中的任何一个都不够接近int型,并且将调用报错函数。

我们建议精确地匹配你的参数,并且在必要的时候添加更多的重载函数。尽量避免类型转换,因为它们是危险的,但是如果你慎用它们,还是好过让编译器胡乱猜测自动转换类型的。这里,我们对bar()的每一个调用都是和它的一个定义恰好匹配的:

main () {

         int I;

         bar (3. 0);        //calls first bar because 3.0 has type double

         bar (3L);           //calls second bar because 3L has type long

         bar((long)i);      //calls second bar because of cast

         //…

}

严格的类型规则

除了加强函数的类型规则之外,c++还作了许多小的改变以在其他方面加强类型系统。严格的类型规则使得C++的编译器可以捕捉到许多bug,它们在C中可能会引起运行错误。本节中所讨论的改变的目的是防止类型的模糊性和类型系统的突变。C++仍然允许使用类型转换,这样就可以随意地避过这一规则。C++防止的是隐式类型转换,而不是显式类型转换。

Const

关键字const声明的值是不能被修改的:

Const int stone = 5;          //stonecan never be modified

Int strlen (const char* s);                  //strlen()will not modify what s

                                                                                    //pointsto

这项规定对类型系统来说是极为有用的,你可能在C中已经使用过它了。使用const,你可以获得编译器的帮助以确认某个值没有改变。不幸的是,对初学者来说,constC++中的意义和它在C中的有所不同。在C++中要正确地使用const也是非常复杂和微妙的。

Void* : 通用指针

像C一样,C++允许你隐式地将一个指针转换为void* 型:

void *v;

Foo *f;

v = f;          //implicit conversion to void*: ok inc and c++

在C和C++中, 我们都必须作类型转换就可以将Foo*型的指针f赋给void*型的指针v。C还允许你隐式地将void* 转换成其他任何类型的指针,但在C++中,你需要一个显式的类型转换:

f = v;        // error in C++, ok in C: implicitconversion from void*

f =(Foo*)v;       // ok in c++ and c:explicit cast from void*

C++在将v赋给f时要求作一个类型转换是因为对void*进行转换是不安全的,而且隐式变化的类型系统也是危险的。幸运的是,C++在很多情况下都省去了对void*的需要。



原创粉丝点击