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

简介:

本节书摘来自异步社区出版社《C和C++代码精粹》一书中的第1章,第1.4节,作者: 【美】Chuck Allison,更多章节内容可以访问云栖社区“异步社区”公众号查看。

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)按错误处理。
相关文章
|
5天前
|
C语言 C++ 开发者
深入探索C++:特性、代码实践及流程图解析
深入探索C++:特性、代码实践及流程图解析
|
28天前
|
IDE Java Linux
【CMake】CMake构建C++代码(一)
【CMake】CMake构建C++代码(一)
|
28天前
|
C++ 计算机视觉 Windows
【C++】由于找不到xxx.dll,无法继续执行代码,重新安装程序可能会解决此问题。(解决办法)
【C++】由于找不到xxx.dll,无法继续执行代码,重新安装程序可能会解决此问题。(解决办法)
|
1天前
|
编译器 C语言 C++
【C++入门学习指南】:函数重载提升代码清晰度与灵活性
【C++入门学习指南】:函数重载提升代码清晰度与灵活性
9 0
|
2天前
|
编译器 C++
【C++进阶】引用 & 函数提高
【C++进阶】引用 & 函数提高
|
5天前
|
设计模式 编译器 数据安全/隐私保护
C++ 多级继承与多重继承:代码组织与灵活性的平衡
C++的多级和多重继承允许类从多个基类继承,促进代码重用和组织。优点包括代码效率和灵活性,但复杂性、菱形继承问题(导致命名冲突和歧义)以及对基类修改的脆弱性是潜在缺点。建议使用接口继承或组合来避免菱形继承。访问控制规则遵循公有、私有和受保护继承的原则。在使用这些继承形式时,需谨慎权衡优缺点。
17 1
|
6天前
|
设计模式 存储 Java
C++从入门到精通:3.5设计模式——提升代码可维护性与可扩展性的关键
C++从入门到精通:3.5设计模式——提升代码可维护性与可扩展性的关键
|
6天前
|
编译器 程序员 C++
C++从入门到精通:3.1模板编程——提高代码的复用性和灵活性
C++从入门到精通:3.1模板编程——提高代码的复用性和灵活性
|
6天前
|
C++
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
|
6天前
|
存储 C++
C++从入门到精通:2.1.1函数和类
C++从入门到精通:2.1.1函数和类