重载、内联、const、vitrual:
const virtual仅用于类的成员函数;
重载和内联机制可用于全局函数也可用于类的成员函数;
重载内联有好处,但也有局限性,所以使用时要注意,不能滥用;
===============================================
重载:
自然语言当中有重载现象,人们可以通过上下文判断该词的真正含义,词的重载可以使得语言更加简练。
C++程序中,将语义、功能相似的几个函数用同一个名字表示,即函数重载。这样做便于记忆,也提高了函数的易用性。
当然重载的另一个理由是:类的构造函数需要重载机制;
因为C++规定构造函数和类同名。构造函数只能有一个名字,如果想用几种不同的方法创建对象,只能用重载机制来实现。
所以类可以有多个同名的构造函数。
重名的函数本质上仍然是不同的函数,那么如何进行区分呢?
靠参数进行区分。编译器根据参数为每个重载函数产生不同的内部标识符。
这又产生一个问题,C++编译器由于重载的因素,对函数内部标识符的命名和C不一样。
所以当C++程序要调用已经被编译后的C函数怎么办?
C++提供了一个C连接交换指定符号extern "c"来解决这个问题。
extern "C"{
void foo(int x, int y);
}
//这就告诉C++编译器 函数foo是一个C连接,应该到库中找名字_foo而不是找_foo_int_int。
C++编译器开发商已经对C标准的头文件采取extern C处理,所以我们可以用#include 直接引用这些头文件;
注意:全局函数和类的成员函数同名不算重载,因为函数的作用域不同。
但是这里又有个问题:隐式类型转换将导致重载函数产生二义性;
void output(int x);
void output(float x);
如果调用output(0.5);将导致编译错误。因为编译器不知道应该讲0.5转换为int还是float。
隐式转换可能会简化程序的书写,但是也可能留下隐患;
成员函数的重载、覆盖、隐藏也很容易混淆;
重载与覆盖:
成员函数被重载的特征;
1)相同的范围(在同一个类中)
2)函数名字相同;
3)参数不同;
4)virtual关键字可有可无;
覆盖是指派生类函数覆盖基类函数:
1)不同的范围(分别位于派生类与基类中)
2)函数名字相同;
3)参数相同;
4)基类函数必须有virtual关键字;
隐藏和覆盖很类似,容易搞混:
如果派生类的函数与基类的函数同名,但是参数不同。此时无论有无virtual。基类函数都将被隐藏;
如果派生类函数与基类函数同名,并且参数也相同,但是没有virtual关键字,此时,基类的函数被隐藏;
其实隐藏和覆盖也很好区分。覆盖的要求比较苛刻,函数名,参数,基类必须有virtual ->其实就是多态;
隐藏机制会引起不必要的麻烦,有时候想调用基类的函数,但是由于继承的原因被隐藏了,就会出现编译错误;
参数的缺省值:
注意缺省值只能在函数的声明中,而不能在定义体内;
void Foo(int x=0, int y=0); //正确
void Foo(int x = 0; int y=0){ //错误
...
}
运算符重载:
在C++中,可以用关键字operator加上运算符来表示函数,叫做运算符重载。
Complex operator +(const Complex &a, cosnt Complex &b);
对于普通函数,函数调用时,参数出现在圆括号内;
对于运算符重载而言,参数出现在其左、右两侧;
例如c = a + b;
如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符;
如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数。因为对象自己是左侧参数;
由于C++支持重载,才能把运算符当做函数来用。
有一些运算符是不能被重载的:. # @ $等
1、难以理解
2、难以确定优先级
3、.对于任何成员都有意义,已经成为标准用法;
=================================================
内联:
内联的目的是为了提高程序的执行效率;
C语言中可以用宏提高执行效率;
提高执行效率原因在于:
省去了函数调用,省去了压栈,生成汇编语言call调用,返回参数,执行return等过程;
从而提高了速度。
宏代码的局限性:
使用宏代码最大的缺点是容易出错。运算符优先级方面会出错
#define MAX(a,b) (a)>(b> ? (a):(b)
result = MAX(i,j)+2;
会被处理为:result = (i)>(j) ? (i):(j) +2; //由于+优先级比:高,
所以上述语句并不等价于期望的 result = ((i)>(j)?(i):(j))+2;
使用宏代码还有一个缺点是:无法操作类的私有数据成员;
函数内联的工作原理:
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字,参数类型,返回值类型)。
如果没有发现内联函数出错,则将函数代码也放进符号表中。
之后在调用内联函数时,编译器首先检查是否调用正确。
如果正确,内联函数的代码就会直接替换函数调用,于是直接省去了函数调用的开销。
这个过程与预处理有显著不同,因为预处理不能进行类型安全检查,自动类型转换,
假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的;
assert是宏不是函数,是Debug版本起作用的宏,用于检查“不应该”发送的情况;
内联函数的编程风格:
必须与定义体放一起才有作用;
跟声明体放一起不起作用;//这体现了一个基本原则:声明与定义不可混为一谈;
内联的使用场景:
1、内联能提高函数的执行效率,但是不能把所有函数都定义成内联;
2、内联的局限性在于以膨胀代码为代价,省去了函数调用的开销,从而提高函数执行效率。
3、如果函数体本身的执行时间相对于函数调用的开销较大,那么效率的收益不大。
4、如果函数体代码较长,将导致内存消耗较高。
5、所以内联适用于简单的函数,这样内存消耗比较小。