您的位置:首页 > 编程语言 > C语言/C++

读书笔记-Thinking in C++-第7章 函数重载和默认参数Function Overloading &Default Arguments

2008-03-18 23:48 507 查看
7、函数重载和默认参数Function Overloading &Default Arguments

依赖于不同的环境,一个词拥有不同的意思,这就是重载。对于所有的编程语言来说,通常要求每个函数有个唯一的标识,比如打印三种数据int, char, and float,这将需要三个函数print_int( ), print_char( ), and print_float( ),这将很不方便。

在C++中,函数重载的另外一个理由是构造函数,因为构造函数必须和类同名,但当你需要以不同的方式来构造时,可以采用同样的函数名不同的参数。

函数重载允许你以相同的名字调用不同的函数,那么怎么样以不同的形式调用相同的函数呢?可以采用默认参数,如f(“hello”), f(“hi”, 1), and f(“howdy”, 2, ‘c’)将是调用同一个函数,编译器为你添加其他信息。但函数重载和默认参数有时会有冲突。

名字装饰
[align=left]void f(); [/align]
[align=left]class X { void f(); }; [/align]
[align=left]全局的f和类X中的f不会冲突,编译器会根据其范围添加特定的信息,如生成 _f and _X_f. [/align]
[align=left] [/align]
[align=left]void print(char); [/align]
[align=left]void print(float); [/align]
[align=left]这种情况将产生相同的函数名,函数重载的特征就是相同的名字不同的参数,因此编译器必须用参数类型的名字来装饰函数名,生成_print_char and _print_float. 如何装饰函数名,各种编译器之间没有统一的标准,因此不同编译器产生的库可能不能共存。[/align]
[align=left]总之编译器根据函数的范围和参数列表来生成特定的名字以供连接器使用。[/align]
[align=left] [/align]
[align=left]不能通过返回值实现重载[/align]
[align=left]void f(); [/align]
[align=left]int f(); [/align]
[align=left]int x = f( );.这种方式引用时,编译器可以知道返回值的类型,但是多数时候都可以忽略返回值的,此时编译器将无法解析所调用的具体函数,因此在C++中,返回值不能作为重载的依据。[/align]
[align=left] [/align]
[align=left]类型安全链接[/align]
[align=left]在C中,由于函数声明错误,编译器有时会自动推动其声明,一般情况下没有问题,但出问题时这种错误将很难发现。[/align]
[align=left] [/align]
[align=left]在C++中,函数使用前必须声明。[/align]
[align=left]//: C07:Def.cpp {O}[/align]
[align=left]// Function definition[/align]
[align=left]void f(int) {}[/align]
[align=left]///:~[/align]
[align=left]In the second file, the function is misdeclared and then called:[/align]
[align=left]//: C07:Use.cpp[/align]
[align=left]//{L} Def[/align]
[align=left]// Function misdeclaration[/align]
[align=left]void f(char);[/align]
[align=left]int main() {[/align]
[align=left]//![/align]
[align=left]f(1); // Causes a linker error[/align]
[align=left]} ///:~[/align]
[align=left] [/align]
[align=left]因为有显式声明,编译可以通过。在C中,链接也可以成功,因为void f(int)和void f(char)编译后的函数是一样的。但是在C++中却不行,因为编译器为其添加了参数标识,变成了f_int和f_char,这个时候链接将不成功。[/align]

联合类型union
C++中,struct和class的唯一区别是struct默认为public的,而class默认为private的,struct同样可以有构造函数和析构函数。Union同样如此,默认为public,但不能改变,而struct可以显式声明重新改变。
//: C07:UnionClass.cpp
// Unions with constructors and member functions
#include<iostream>
using namespace std;
union U {
private: // Access control too!
int i;
float f;
public:
U(int a);
U(float b);
~U();
int read_int();
float read_float();
};
U::U(int a) { i = a; }
U::U(float b) { f = b;}
U::~U() { cout << "U::~U()/n"; }
int U::read_int() { return i; }
float U::read_float() { return f; }
int main() {
U X(12), Y(1.9F);
cout << X.read_int() << endl;
cout << Y.read_float() << endl;
} ///:~
Class和union的区别在于int和float的存储位置是重叠的。这种类是不安全的,很容易由于参数传递错误而导致调用了不同的构造函数。另外union在继承时不能作为基类。

在类中可以安全的封装union,如下:
//: C07:SuperVar.cpp
// A super-variable
#include <iostream>
using namespace std;
class SuperVar {
enum {
character,
integer,
floating_point
} vartype;// Define one

union {
// Anonymous union
char c;
int i;
float f;
};
public:
SuperVar(char ch);
SuperVar(int ii);
SuperVar(float ff);
void print();
};
SuperVar::SuperVar(char ch) {
vartype = character;
c = ch;
}
SuperVar::SuperVar(int ii) {
vartype = integer;
i = ii;
}
SuperVar::SuperVar(float ff) {
vartype = floating_point;
f = ff;
}
void SuperVar::print() {
switch (vartype) {
case character:
cout << "character: " << c << endl;
break;
case integer:
cout << "integer: " << i << endl;
break;
case floating_point:
cout << "float: " << f << endl;
break;
}
}
int main() {
SuperVar A('c'), B(12), C(1.44F);
A.print();
B.print();
C.print();
} ///:~

在上述代码中,enum没有类型名,当以后不需要在利用其类型名时不需要,立即定义一个实例instance即可。
而union既没有类型名也没有变量名,此称为匿名union,同样分配内存,但引用时无须变量名和“。”,如
//: C07:AnonymousUnion.cpp
int main() {
union {
int i;
float f;
};
// Access members without using qualifiers:
i = 12;
f = 1.22;
} ///:~

利用union的目的是为了省内存,但添加的vartype占用了一个字的内存,因为枚举类型默认为整型,并没有节省空间,但是枚举常量不占用类的内存空间的,其在编译时求值,保存在常量区。但为了区分union的具体对象,通常都要添加类型标识。此类型变量用enum类型声明时更提高了程序的健壮性,比直接int type好。尤其是网络数据包传输时,要在面试种好好体系这种特点。

默认参数default arguments
默认参数为在声明中给定的值,当进行函数调用时未指定时,编译器将自动为你插入这个值。
[align=left]Stash(int size); [/align]
[align=left]// Zero quantity [/align]
[align=left]Stash(int size, int initQuantity);[/align]
[align=left]上述两个函数可以用一个代替[/align]
[align=left]Stash(int size, int initQuantity = 0); [/align]
[align=left]Stash A(100), B(100, 0); [/align]
[align=left]上述两种调用方式的效果一样,但是对于A,编译器会用默认的值替换第二个参数。[/align]
[align=left] [/align]
[align=left]默认参数和函数重载都是一个函数名用在不同的场合,但上述描述的情况用默认参数好些,只有当函数的特性差异太大时可以用函数重载。[/align]
[align=left] [/align]
[align=left]默认参数的两个规则,只有尾部的才能默认,二某个默认后其后的所以参数默认。[/align]
[align=left] [/align]
[align=left]默认参数只是放在函数的声明中,通常是在头文件中。有时为了给出注释,会在定义处添加相关评论,如[/align]
[align=left]void fn(int x /* = 0 */) { //[/align]
[align=left]因为默认参数的替换是在编译阶段,由编译器替换的,只和编译器见到的声明有关,而定义只有在链接时才用到。声明变化时,默认参数的值可以变化,与定义实现没有关系,在此声明之后,所有的默认参数都将采用新值,直到你改变其声明。[/align]

Placeholder arguments
函数声明中,默认参数可以没有标识符。如:
[align=left]void f(int x, int = 0, float = 1.1); [/align]
同样在C++中,定义中也可以没有标识符,如
[align=left]void f(int x, int, float flt) { /* ... */ } [/align]
[align=left]在函数内部,你可以引用reference x和flt,但不能引用第二个参数,因为其没有名字。[/align]
[align=left]尽管可以省略标识符,但调用时仍然需为其赋值,如f(1,2,3.0).[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: