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

C++进阶 - 函数特性总结(重载与内联)

2017-07-20 19:32 302 查看
个人之言,请持怀疑态度参考。


C++有重载、内联、const和virtual 四种新的机制。本篇博客主要探究重载与内联。const用发可以参考我的另外一篇文章 点击这里

重载和内联机制,既可以用于全局函数也可用于类的成员函数;const 、virtual机制仅仅用于类的成员函数

重载

重载如何实现

通过函数的接口 - 参数 来区分。为什么不用返回值?因为返回值不可以100%保证区分。例如:

void fun(int a);
int  fun(int a);
/******************/
fun(10);


上述,调用fun 谁知道你调用的是哪个? 在C/C++C程序中是可以忽略返回值的

编译器根据内部标识符来区分重载函数。编译器会根据参数为每个重载函数生成不同的内部标识符。 (类似与 _fun_int )

C语言是没有重载的,如果用要在C++中调用已经被编译后的C库,应该使用

extern "C"
{
函数或者其他C头文件
}


告诉C++编译器,函数是C链接,应该按照 C 的函数标识符查找,而不是C++

注意:C++编译器开发商已经对C标准库的头文件做了extern “C” 处理,所以我们可以直接引用。

并不是函数名字相同就能构成重载,必须有函数作用域相同的前提条件

隐式转换导致重载函数产生二义性

int func(int a)
{
cout << "int a" << endl;
return 0;
}

int func(float a)
{
cout << "float a" << endl;
return 0;
}

int main()
{
func(0.5);
getchar();
return 0;
}


上面的代码看起来似乎很合情理,但是编译器会报错( error C2668: “func”: 对重载函数的调用不明确)。原因在于0.5 既可以匹配double 又可以通过隐式转换成 int 。

成员函数的重载、覆盖(重写)、隐藏

对照表

重载覆盖(重写)隐藏
范围相同的范围,在同一个类中不同的范围,位于派生类和基类中不同的范围,位于派生类和基类中
函数名字相同相同相同
参数不同相同不同【相同】
基类virtual关键字可有可无必须有可有可无【没有】
注意:【】代表这隐藏出现的两种情况

举个例子来说明:

#include <iostream>
using namespace std;

class A
{
public:
A(){};
~A(){};

/*重载*/
void dis(int a)
{
cout <<"_dis_int"<< endl;
}
void dis(char a)
{
cout << "_dis_char_" << endl;
}

/*覆盖(重写)*/
virtual void func(int a)
{
cout << "基类func" << endl;
}

/*隐藏*/
void fun()
{
cout << "我在基类中 fun" << endl;
}

virtual void fun_c()
{
cout << "我在基类中 fun_c" << endl;

}

private:

};

class B :public A
{
public:
B(){};
~B(){};
virtual void func(int a)
{
cout << "派生类func" << endl;
}

void fun()
{
cout << "我在派生类中 fun" << endl;
}

void fun_c(int a)
{
cout << "我在派生类中 fun_c" << endl;

}

private:

};

int main()
{
A a;
B b;
/*使用重载*/
a.dis('a');
a.dis(10);

/*使用(覆盖)重写*/
a.func(1);
b.func(2);

/*隐藏  试着解释一下运行结果 */
a.fun_c();
b.fun_c(10);

a.fun();
b.fun();

/*猜猜这个结果是什么?*/

B* bb = &b;
A* aa = &b;

aa->func(1);
bb->func(2);

aa->fun();
bb->fun();

getchar();
return 0;
}


如果你亲自运行过上面的代码,一定会发现 aa->fun(); 结果和你想的不一样,哈哈 ,这就是坑。被隐藏函数的行为取决于指针的类型!!

隐藏存在的意义(了解就好了,写代码还是要避免这么烦人的隐藏): 解决多继承的问题,比如一个类有多个基类,有时也搞不清楚那些基类定义了fun(),直接隐藏就好了,哈哈,自己都给写迷糊了的代码估计是在写BUG.

函数内联

用内联取代宏代码

宏代码的优点:

预处理器用复制宏代码的方式代替函数调用,省去了参数压栈,call调用、参数返回、执行return等步骤(ps:可以去了解一下C/C++翻译成汇编语言后的执行细节),提高了速度。

缺点:

容易掉坑里!哈哈。预理器无脑复制宏代码常常出现一些边际效应如优先级错误等等;还有就是没法操作类的私有成员,这在C_++中是致命的。

内联函数如何工作

如果编译器检查没有发现内联函数有错误,那么他除了会把函数声明放在符号表中,还会把函数的代码也一并放入符号表。在调用内联函数时,编译器会像执行普通函数一样进行类型安全检查、自动类型转换等等。如果检查没有错误,内联代码就会直接替换函数的调用,省去的函数调用的开销,而且还进行了类型检查。

内联的特点

inline
是一个‘用于定义式的关键字’,而不是想其他关键字一样‘用在声明式’。如果你想把一个函数变成内联函数,在函数声明前加
inline
是没什么卵用的!(如果你还弄不清神马是定义式,神马是声明式,那么你需要赶快充电了)

inline
只是对编译器的一个申请,不是强制命令。也就是说你写的函数能不能成为内联函数不是你说了算,最后还的由编译器综合考虑才决定能不能内联

申请可以隐式的提出。将函数定义在类的定义式内其实就是在暗示编译器,来给我把这个函数弄成内联吧。

内联的代价就是造成程序的体积膨胀,导致执行效率减低。所以
inline
只是申请,由编译器来优化决定。其次,申请成为内联的函数应该尽可能的小(没有循环,递归等等重量级的操作)。

内联函数如果修改,将导致该函数的使用者重新编译!在编译一个项目是,其实并不是每次都要把代码全部编译,好的代码设计在大多数情况下仅仅重新链接就好了。如果你要设计一个库,那么你一定要慎重考虑。

大部分调试器对内联函数是没法调试的。因为内联函数直接将本体编进了程序,实际上这个函数并不存在,自然也没法跟踪调试了。(ps:内联函数是没法取到函数地址的)

有时侯编译器在进行内联的时候,还可能产生内联函数的本体。

inline void f(){}

void ( * ps)() =f;

f();   //会被内联
ps();  //不会被内联


前提是编译器同意对f() 采用内联策略

程序中要取f()的地址,编译器会生成一个内联的本体,毕竟不肯呢给你>随便编造一个地址出来。

SO 编译器通常不会对通过指针调用的函数实施内联大法。 正常调用的话还是会被内联的,比如上面代码中 f(); 直接调用。

内联函数的误区

类的构造析构函数使用内联更有效

这个想法是错误的!类的构造析构函数看起来没做什么事,其实是编译器把所有细节隐藏了!C++对‘对象的创建和销毁做了各种保证’,而且对象允许多种方式创建。这些行为其实都是在编译是,编译器自动把代码插在了构造器和析构器中,至少不会凭空发生。

内联函数用的越多越好

错,从他的特性可以看出是要谨慎使用的

最后一段补充 80 - 20经验法则

程序往往把80%的时间化在20%的代码上。我们要做的是找出20%去优化,而不是瞎折腾其他没有什么影响的代码

软件开发根据我的个人经验,当效率不是问题的瓶颈时,更应考虑软件的整体逻辑,而不是抓着某一点死磕到底。所以内联只有在优化阶段需要优化效率时才考虑。

我的个人网站 http://www.breeziness.cn/

我的CSDN http://blog.csdn.net/qq_33775402

转载请注明出处 小风code www.breeziness.cn
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: