C++ 函数的高级特性 20131002
C++函数增加了重载(override), inline, const, virtual四种机制,重载和内联机制既可以用于全局函数,也可以用于类的成员函数。const和virtual机制只是可以用于成员函数。
1.函数重载的概念
将功能和语义相似的几个函数使用同一个函数名字,便于记忆,同时类的构造函数确实需要使用到重载,但是C++的构造函数只能有一个名字,只能使用重载来实现。
虽然在语言层面上是相同的函数名称,但是在最终的二进制层面上是不允许出现同名函数的,因为所有的函数最终都将转换成一个等效的全局函数,调用语句也会作相应的转换。如何将他们区分开来:根据函数的返回值和参数列表。但是一般不使用返回值实现多态,只是根据参数的列表。编译器使用Name-Mapping技术重命名机制。
不要使用默认的隐式转换机制匹配重载函数:
void output(int x){
cout << "output int " << x<< endl;
}
void output(float x){
cout << "output float " << x << endl;
}
int main()
{
output(1);
output(0.5);// error 编译错误
return 0;
}
2.成员函数的重载(overloading)、覆盖(override)和隐藏
重载特征:
相同的作用域;函数名字相同;参数类型、顺序、数目不同;virtual关键字可有可无
覆盖的特征:
不同的作用域;函数名称相同;参数类表完全相同;基函数中必须是virtual关键字修饰的函数。同时虚函数的覆盖有两种方式:一个是完全重写,一种是扩展,扩展就是先调用基类的虚函数,在实现自己新增加的功能。
隐藏规则:
C++中有隐藏的规则使问题的复杂性陡然增加。这里的隐藏就是指派生类的成员函数遮蔽了和他同名的基类成员函数:
派生类的函数和基类中的函数同名,但是参数列表有所差异,此时无论是否有virtual关键字,基类的函数在派生类中将被隐藏,而不是重载,也不是多态。
派生类的行数和基类的函数同名,参数列表也相同,但是基类函数中没有使用关键字virtual关键字修饰。此时基类的函数在派生类中将被隐藏,但不是覆盖。
有时候把它们称为“跨越类边界的重载”。
class Base{
public:
virtual void f(float x){
cout << "Base::f(float)" << endl;
}
void g(float x){
cout <<"Base::g(float)" << x << endl;
}
void h(float x){
cout << "Base::h(float)" << x <<endl;
}
};
class Derived:public Base{
public:
virtual void f(float x){
cout << "Derived::f(float)" << endl;
}
virtual void f(int x){
cout << "derived::f(int)" << endl;
}
void g(int x){
cout << "derived::g(int)" << x << endl;
}
void h(float x ){
cout << "derived::h(float)" << x <<endl;
}
};
int main()
{
Derived derived;
Base *pb = &derived;
//动态绑定,多态,是覆盖
pb->f(10.0f);
derived.f(10);
cout << endl;
//隐藏,但是不是重载,这里 是静态绑定,根据的是指针的类型,
pb->g(3.14f); // base::g(float) 3.14
derived.g(3.14f);// derived::g(int) 3 将参数强制转换成int 3
// 隐藏,名称相同,参数相同,这里也是根据指着类型进行的静态绑定
pb->h(3.14f);
derived.h(3.14f);
return 0;
}
3.参数的默认值
参数的默认值有编译器在编译的时候自动插入。编写代码的时候将默认的参数值放在函数的声明中,而不要放在函数定义体中。
void Foo(int x = 0, int y = 0);
实现函数的时候就没有必要将默认的参数中写在里面了。
同时如果函数有多个参数那么参数之能够从后面想前面一次默认,不能中间使用没有默认的参数,前面又有默认的参数值。
void Foo(int x = 0, int y, int z = 0); error
void Foo(int x , int y = 0, int z = 0); right
注意不要出现而二义性的函数,使用默认参数容易出现这种情况。
4.运算符的重载
C++中使用关键字operator加上运算符来表示函数,叫做运算符的重载
Complex Add(const Complex& a, const Complex & b);
Complex operator+(const Complex &a, const Complex &b);
重载++,两种方式
Integer& operator++(){} 前置
Integer& operator++(int){}后置
++ 尽量使用前置版本,效率比较高,因为后置操作,对于大对象的时候,会创建一个临时变量,这样会产生额外的开销。
5.内联函数
C++支持的函数内联机制,提高代码的执行效率。C中使用的宏提高执行的效率,但是宏本省不是代码,只是字符串的替换。这样编译器省却了参数压栈的操作、生成汇编代码的call调用、返回函数,执行return等等,提高了执行速度。最大的确定便是容易出现代码错误。
字符穿替换的时候出现问题
#define MAX(a,b) a>b?a:b
result = MAX(i,j)+2;
改变优先级使用(), #define MAX(a,b) (a)> (b) ?(a):(b)
result = MAX(i++,j); result = (i++)>(j)?(i++):(j);
内联函数解决了这个问题,同时函数被内联之后,编译器通过上下文进行代码的进一步优化。
6.类型转换
static_cast<dest_type>(source_obj);
dynamic_cast<dest_type>(source_obj);
const_cast<dest_type>(source_obj);
7.const成员函数,就是在函数中不会修改数据成员的代码,如果在程序中调用了其他非const成员函数或者修改数据成员的时候,编译不通过。
使用const修饰函数放在函数的最后面修饰。
Static成员函数不可以是const修饰的,因为static成员函数只是全局函数的一个形式上的封装,而全局函数不存在const之说,何况static成员函数不能够访问类的非静态成员,也就无法修改非静态的成员变量。
追梦的飞飞
于广州中山大学20131002