函数重载
C++的函数重载机制为我们程序员提供了很大的便利,但同时也会带来一些问题,这时我们会发现,当我们调用重载函数时,会出现编译不通过的现象,还有给别人提够函数接口也会出现一些问题。
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char c)
{
printf("%c\n", c);
}
void MyPrintf(int i)
{
printf("%d\n", i);
}
int main()
{
char c = 'A';
MyPrintf(c);
short s = 100;
MyPrintf(s);
return 0;
}
居然可以编译通过,并将正确答案显示出来,是不是太神奇了???
然而细心的朋友会发现,我们并没有重载short的函数,而为什么能够正确编译和输出呢?这是因为进行了提升匹配,short类型自动提升到了int类型,所以程序能够成功编译和运行。
但是重载有些时候还是会出问题的比如下面的例子:
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char c)
{
printf("%c\n", c);
}
void MyPrintf(int i)
{
printf("%d\n", i);
}
void MyPrintf(float f)
{
printf("%f\n", f);
}
int main()
{
char c = 'A';
MyPrintf(c);
short s = 100;
MyPrintf(s);
unsigned int ui = 200;
MyPrintf(ui);
return 0;
}
此时的MyPrintf(ui);就会编译不通过,会提示下面的信息:
从这个错误提示上可以看出,我们调用unsigned int类型的变量时,它可以有多重调用方式,这样就会使得编译器很困惑,所以就会导致编译错误。那么怎样才能修改这样的代码,使它能够通过编译呢?很简单的一种就是再增加一个unsigned int类型的重载函数,代码如下:
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char c)
{
printf("%c\n", c);
}
void MyPrintf(int i)
{
printf("%d\n", i);
}
void MyPrintf(float f)
{
printf("%f\n", f);
}
void MyPrintf(unsigned int ui)
{
printf("%d\n", ui);
}
int main()
{
char c = 'A';
MyPrintf(c);
short s = 100;
MyPrintf(s);
unsigned int ui = 200;
MyPrintf(ui);
return 0;
}
这样就能够很顺利的通过编译,但是我们应该如何去写重载函数呢?究竟写多少个重载函数才好呢?这个应该看具体项目需求,需要写几个就写几个,并不是说重载函数写得越多越好,因为多了也没用。
重载函数的总结
重载函数的匹配规则:
1.精准匹配,比如 int -> int
2.提升匹配,比如 char -> int -> double
3.类型转换匹配,int -> unsigned int
所以当重载函数的匹配规则发生冲突或者有多个匹配函数时,就会出现二义性。定义太多或太少的重载函数时容易出现问题
特别需要注意的是,千万不要使用指针类型进行重载函数
请看这样指针重载的例子
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char *str)
{
printf("%s\n", str);
}
void MyPrintf(int *pI)
{
printf("%d\n", *pI);
}
int main()
{
char *str = "AAA BBB CCC";
MyPrintf(str);
int i = 100;
int *pI = &i;
MyPrintf(pI);
return 0;
}
此时的重载和调用都没有问题,可以编译通过和正确运行,但是如果我有一个万能指针void*的变量时,程序又会出现什么情况呢?
还是先看代码吧
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char *str)
{
printf("%s\n", str);
}
void MyPrintf(int *pI)
{
printf("%d\n", *pI);
}
int main()
{
char *str = "AAA BBB CCC";
MyPrintf(str);
int i = 100;
int *pI = &i;
MyPrintf(pI);
void *p = nullptr;
MyPrintf(p);
return 0;
}
上面的代码在编译时,就会出现问题。编译的错误提示信息如下:
为什么会出现这样的错误呢?原因很简单,因为void*的变量是万能指针嘛,它可以转换成任意的指针的类型,所以此时就会出现二义性,编译器也不知道该调用哪个函数了。
默认参数
C++的特性中,有一个默认参数的特性,就是我们可以给函数参数默认的赋值,代码如下:
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char c, bool bLine = false)
{
printf("%c", c);
if (bLine)
printf("\n");
}
int main()
{
char c = 'A';
MyPrintf(c, true);
c = 'B';
MyPrintf(c);
c = 'C';
MyPrintf(c, false);
return 0;
}
首先,肯定一点是上面的代码没有错误,它的输出结果是:
从输出结果上可以看出,当我们给它传递一个true参数时,就表示有换行,而不传递参数,那么它就会使用默认参数false,这和我们显示的传递false的效果是一样的。
需要注意的时,默认参数还是有条件的,比如默认参数必须要从右到左赋值默认参数,不允许有默认值的参数右边是没有默认值的参数。
比如:
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
void MyPrintf(char c, bool bLine = false, int i)
{
printf("%c", c);
if (bLine)
printf("\n");
}
int main()
{
/*char c = 'A';
MyPrintf(c, true);
c = 'B';
MyPrintf(c);
c = 'C';
MyPrintf(c, false);
*/
return 0;
}
此时,我们就是不调用这个函数,它也不会编译通过,其实这也没有什么好解释的,记住就可以了。
内联函数
内联函数的写法很简单,直接在函数名前直接加上inline关键字就可以了,代码如下:
#include "stdafx.h"
#include <iostream>
// 以输出不同类型的参数为例的重载函数
inline void MyPrintf(char c, bool bLine = false)
{
printf("%c", c);
if (bLine)
printf("\n");
}
int main()
{
char c = 'A';
MyPrintf(c, true);
c = 'B';
MyPrintf(c);
c = 'C';
MyPrintf(c, false);
return 0;
}
从代码上可以看出,内联函数的使用和普通函数一样,没有任何的差别。
内联函数的好处
内联函数不会新建栈,而是直接将代码展开,这样就会大大的提高了函数的执行效率,因为减少了创建栈,销毁栈的过程。需要注意的是,内联函数只是针对栈空间来说的,比如说在堆空间申请的内存和释放跟内联是没有关系的。只有在栈空间上执行的函数才有可能被内联。
内联函数既然有好处,肯定也有它的缺点
内联函数的缺点是,它会造成代码的膨胀,使生成的可执行程序变得庞大。
内联函数的作用
inline关键字只是告诉编译器这个函数要内联,但是具体能不能成功的内联,是由编译器来决定。具体的规则也不太好说清,据听说是内联的规则是经常变化的,所以也没有一个固定的准则。但是大概有一些规则,只能说是一般性的规则。
通常情况下判定标准:
1.代码量很庞大
2.不加inline,他不会变成内联函数,但是加上的话,也不一定内联
内联和宏定义的区别
内联函数和宏定义都是直接展开,那它们之间有什么区别呢?
宏定义是赤裸裸的替换,不带半点儿的修饰,没有一点儿类型检测;
内联函数,它毕竟还是函数,所以它有类型检测;
类型转换
C++的类型转换与C语言的类型转换不同,C语言的类型转换都是强制转换的。而C++中有四中类型转换,只有一种类型转换等同于C语言的强制类型转换,其它的三种转换都不是强制转换。他们分别是:
1.static_cast它不是强制转换,只是简单的转换,是C++的转换风格
2.const_cast表示去常量操作,但是它还是不能真正的去掉参数的常量,我们可以认为只是蒙骗编译器通过编译,没有其他的作用了。
3.dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换
4.reinterpret_cast此标识符的意思即为数据的二进制形式重新解释,但是不改变其值
我们先了解这几种转换关系,以后再做深入的解释和说明。