1.4 函数原型
C和C++代码精粹
在C++中,函数原型不是可选的。事实上,在ANSI C委员会采用原型机制以前,它是为C++发明的。在你第一次使用函数前必须声明或定义每个函数,编译器将检查每个函数调用时正确的参数数目和参数类型。此外,在其应用时将执行自动转换。下列程序揭示一个在C中不使用原型时出现的普通错误。
/* convert1.c */
#include <stdio.h>
main(
{
dprint(123);
dprint(123.0);
return 0;
}
dprint(d)
double d; // 老式的函数定义
{
printf("%f\n",d);
}
/* 输出:
0.000000
123.000000
*/```
函数dprint要求带有一个double型参数,如果不知道dprint的原型,编译器就不知道调用dprint(123)是个错误。当为dprint提供原型时,编译器自动将123变换成double型:
/ convert2.c /
include
void dprint(double); /原型/
main()
{
dprint(123);
dprint(123.0);
return 0;
}
void dprint(double d)
{
printf("%f\n",d);
}
/* 输出:
123.000000
123.000000
*/`
除类型安全外,在C++中关键的新特征是类(class),它将结构(struct)机制扩展到除了数据成员之外,还允许函数成员。与结构标记同名的一个成员函数称为构造函数,并且当声明一个对象时,它负责初始化该对象。由于C++允许定义具有与系统预定义类型一样性能的数据类型,因此,对于用户自定义类型也允许隐式转换。下面的程序定义了一个新类型A,它包含了一个double型的数据成员和一个带有一个double型参数的构造函数。
// convert3.cpp
#include <stdio.h>
struct A
{
double x;
A(double d)
{
printf("A::A(double)\n");
x = d;
}
};
void f(const A& a)
{
printf("f: %f\n", a.x);
}
main()
{
A a(1);
f(a);
f(2);
}
// 输出:
A::A(double)
f: 1
A::A(double)
f:2```
由于struct A的构造函数期望一个double型参数,编译器自动地将整数1转换为所定义的double型用于a。在main函数的第一行调用f(2)函数产生下面的功能:
1.将2转换为double型;
2.用值2.0初始化一个临时的A对象;
3.将对象传递给f。
换句话说,编译器生成的代码等同于:
`f(A(double(2)));`
注意到C++的函数风格的强制类型转换。表达式
`double(2)`
等同于
`(double)2`
然而,在任一转换序列里只允许有一个隐式用户定义的转换。程序清单1.1程序中要求用一个B对象去初始化一个A对象。B对象转而要求一个double型,因为它唯一的构造函数是B::B(double)。表达式
`A a(1)`
变为
`a(B(double(1)))`
它只有一个用户定义的转换。然而,表达式f(3)是非法的,这是因为它要求编译器提供两个自动的用户定义转换:
//不能隐式地既做A的转换又做B的转换
f(A(B(double(3))) //非法`
表达式f(B(3))是允许的,因为它显式地请求转换B(double(3)),因此编译器仅提供剩余的转换到A。
通过单一参数的构造函数的隐式转换对于混合模式表达式是很方便的。例如,标准的字符串类允许将字符串和字符数组混合,如:
string s1=”Read my lips…”; //初始化s1
string s2=s1+”no new taxes.”; //将s1和常字符连接```
程序清单1.1 仅允许一个用户定义的转换
// convert4.cpp
include
struct B;
struct A
{
double x;
A(const B& b);
};
void f(const A& a)
{
printf("f: %f\n", a.x);
}
struct B
{
double y;
B(double d) : y(d)
{
printf("B::B(double)\n");
}
};
A::A(const B& b) : x(b.y)
{
printf("A::A(const B&)\n");
}
main()
{
A a(1);
f(a);
B b(2);
f(b);
// f(3); //将不编译
f(B(3)); // 隐式 B到A的变换
f(A(4));
}
//输出:
B::B(double)
A::A(const B&)
f: 1
B::B(double)
A::A(const B&)
f: 2
B::B(double)
A::A(const B&)
f: 3
B::B(double)
A::A(const B&)
f: 4
第二行等价于:
`string s2=s1 + string("no new taxes,");`
这是因为标准的字符串类提供了一个带有单一const char * 型参数的构造函数,但有时你可能不希望编译器如此轻松,例如,假设有一个字符串构造函数带有一个单一的数字参数(其实没有),也就是说将字符串初始化为一个具体的空格数,那么下面表达式的结果将会是什么呢?
`string s2=s1+5;`
上式右边变为s1+string(5),意思是给s1增加5个空格,这多少是一个让人困惑的“特征”。你可以通过声明单参数构造函数explicit来防止这种隐式转换。由于我们假设了字符串的构造函数是这样声明的,上面的语句就是错误的形式。但是string s (5)这个声明是合法的,因为它显式地调用了构造函数,与此类似,如果用
`explicit A (double d)`
替换程序清单1.3中A的构造函数的声明,编译器将把表达式f(2)按错误处理。