《C和C++代码精粹》——1.4 函数原型

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)按错误处理。
时间: 2024-09-24 08:41:32

《C和C++代码精粹》——1.4 函数原型的相关文章

《C和C++代码精粹》——1.6 引用

1.6 引用 C和C++代码精粹由于C函数的参数是按值传递的,若传递大型结构给函数,既费时又占用空间.大多数C程序员使用指针来代替按值传递,例如,如果struct Foo是一个大型记录结构,可以采用如下方法: void f (struct Foo * fp) { /*通过fp来访问Foo结构*/ fp->x=- 等等. }``` 当然,为了使用这个函数,必须传递Foo结构的地址: struct Foo a;-f (&a);`C++的引用机制是符号上的便捷,这样做可以减少采用指针变量的显式间接

《C和C++代码精粹》——2.6 const指针

2.6 const指针 C和C++代码精粹注意memcpy函数第二个参数中的const关键字.这个关键字告诉编译器此函数将不会改变source指向的任何值(除了强制类型转换).当把指针作为参数传递时,总是合适地使用const限定符是一个很好的习惯,它不仅可以防止你无意中错误的赋值,而且还可以防止在作为参数将指针传递给函数时可能会修改了本不想改变的指针所指向的对象的值.例如,如果在程序清单2.6中的声明是: const int i=7,j=8; 有可能因为下面这条语句而得到警告: swap(&i,

《C和C++代码精粹》——2.12 指向函数的指针

2.12 指向函数的指针 C和C++代码精粹 一个指针可以指向函数也可以指向存储的对象.下面的语句声明fp是一个指向返回值为整型(int)的函数的指针: int(*fp)( ); *ftp的圆括号是必需的,没有它的语句 int *fp( ); 将fp声明为一个返回指向整型(int)指针的函数.这就是将星号与类型声明紧密相连的方式成为逐渐受人们欢迎的方式的原因之一. int fp(); //方式说明fp()返回一个指向整型的指针(int ) 当然,这种方式建议你通常应该每条语句只声明一个实体,否则

《C和C++代码精粹》——1.11 函数重载和函数模板

1.11 函数重载和函数模板 C和C++代码精粹程序清单1.4中的交换函数(swap)只有在交换整数时才有用.如果要交换两个任何系统预定义的数据类型中的对象该么办呢?C++允许定义多个同名函数,只要它们的特征不同.因此就可以为所有系统预定义的数据类型定义一个交换函数: void swap(char &,char &); void swap(int &,int &); void swap(long &,long &); void swap(float &

《C和C++代码精粹》——2.13 指向成员函数的指针

2.13 指向成员函数的指针 C和C++代码精粹如果返调函数是某个类的成员函数将会怎样?获得指向类成员的指针与获得指向非成员实体的指针的方式相似,只存在很小的语法变化.例如,考虑下面类的定义: class C { public: void f ( ) {cout << "C::f\n";} void g( ) {cout << "C::g\n";} }; 可以这样定义一个指向C类成员函数的指针: void (C::*pmf) ( ); //

《C和C++代码精粹》——1.10 操纵器

1.10 操纵器 C和C++代码精粹 当标识符 endl出现在一个输出流中时,一个换行字符就被插入并且流被刷新.标识符endl是操纵器的一个例子,即为了副效应而插入到流的一个对象.在〈iostream〉中被声明的系统预定义的操纵器列于表 1.3中.程序清单1.11里的程序在功能上与程序清单1.10的程序等价,但它是用操纵器来代替显式调用setf函数.操纵器经常可以使代码更为高效. 表1.3 简单的操纵器(〈iostream〉) 程序清单1.11 用操纵器改变数据基数 // base2.cpp:

《C和C++代码精粹》——2.5 普通指针

2.5 普通指针 C和C++代码精粹 通常编写能接收指向任意类型参数的函数是很方便的.这是很有必要的,例如,用标准的库函数memcpy,能够从一个地址向另一个地址拷贝一块内存.你也可能想调用memcpy来拷贝自己创建的结构: struct mystruct a,b; /.../ memcpy(&a,&b,sizeof(struct mystruct)); 为了操作任意类型的指针,memcpy把它头两个参数声明为void型指针.可以不需要强制类型转换将任何类型的指针赋予void类型.也可以在

《C和C++代码精粹》——1.2 循序渐进

1.2 循序渐进 C和C++代码精粹 在没有完全掌握C++的情况下也可以有效地使用它.事实上,面向对象技术承诺如果供应商为重用.可扩展性提供设计好的类库,那么建立应用程序的工作就很容易了.现有的开发环境,及其应用程序框架和可视化组件,正在兑现这一承诺. 如果觉得必须要掌握这种语言,可以一步步地去做,并且在这一过程中可以取得丰硕的成果.已出现的3个"顶峰"是: 1.更好的C: 2.数据抽象: 3.面向对象编程. 由于C++比C更安全.更富于表达,所以可以将它作为一个更好的C使用.这个顶峰

《C和C++代码精粹》导读

前言 C和C++代码精粹 本书适合于那些C和C++的职业程序员.假如你已熟悉这两种语言的语法和基本结构,这本书能够为你创建有效的.实用的程序提供实践性的指导.每一个代码范例或程序范例均标明行之有效的用法和技术,这些用法和技术对C/C++这两种重要编程语言的性能发挥起着重要的作用. 对于那些希望在工作中加强自身技术和提高效率的人来说,本书可以算是一本经验之谈.尽管目前人们对面向对象模式的推崇到了白热状态(本书也包括这方面的丰富内容),可是我没有理由不对C++的基础-C表示尊崇.我发现太多的程序开发