PoEdu - C++阶段班- Lesson07 To Lesson10_C to C++
07 重载导致的二义性
问题:为什么一定要重载呢?重载能方便我们注重函数的功能,当参数类型不确定时,我们能很便捷的利用重载的机制达到目的。
重载注意点:二义性
看代码:
#include <cstdio> void MyCout(int num) { printf("%d\n", num); } void MyCout(char c) { printf("%c\n",c); } void MyCout(float f) { printf("%f\n",f); } //MyCout@char* void MyCout(char *str) { printf("%s\n", str); } int main() { long lNum = 100; MyCout(lNum); return 0; }
高亮代码会调用哪个函数?
断点,运行:100
#include <cstdio> void MyCout(int num) { printf("%d\n", num); } void MyCout(long num) { printf("%l\n", num); } void MyCout(char c) { printf("%c\n",c); } void MyCout(float f) { printf("%f\n",f); } //MyCout@char* void MyCout(char *str) { printf("%s\n", str); } int main() { /*int num = 100; char c = 'a'; char *str = "I Love Mark"; float f = 1.0002; MyCout(f); MyCout(str); MyCout(num);*/ short lNum = 100; MyCout(lNum); return 0; }
代码测试,short long 不能匹配的时候,隐式的找int
unsigned long 会调用哪个?
重载的函数调用,它中间有个匹配,匹配有几大潜规则:
1 精准匹配 2 提升匹配 3 类型转换匹配(当1和2都不能匹配时,尝试强转) 4 当匹配规则冲突时 当有了多个匹配时,二义性就出现了。
示例中 usigned long 它匹配的选择过多,产生二义性,所以报错。
如何消除二义性?改成精准匹配就行。
最好是根据业务需求来定义,不能写太多,也不能写太少。重载的时候最好不要用指针。比如(void*)。
再看示例:
#include <cstdio> void f1(char c) { } void f1(long l) { } void f2(char * str) { } void f2(int * n) { } int main() { int n = 100; f1(n); //f2(n); return 0; }
运行:
1>------ 已启动生成: 项目: OverloadeDemo, 配置: Debug Win32 ------
1> main.cpp
1>e:\c_code\overloadedemo\overloadedemo\main.cpp(34): error C2668: “f1”: 对重载函数的调用不明确
1> e:\c_code\overloadedemo\overloadedemo\main.cpp(8): note: 可能是“void f1(long)”
1> e:\c_code\overloadedemo\overloadedemo\main.cpp(3): note: 或 “void f1(char)”
1> e:\c_code\overloadedemo\overloadedemo\main.cpp(34): note: 尝试匹配参数列表“(int)”时
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
这个int找不到精准匹配,也没找到提升匹配,开启强转类型,这里它可以强转为long,又可以强转为char,二义性出现,编译器晕了,糊涂了。
再看示例中的f2()
#include <cstdio> void f1(char c) { } void f1(long l) { } void f2(char * str) { } void f2(int * n) { } int main() { /*int num = 100; char c = 'a'; char *str = "I Love Mark"; float f = 1.0002; MyCout(f); MyCout(str); MyCout(num);*/ int n = 100; //f1(n); f2(n); return 0; }
运行 :
1>------ 已启动生成: 项目: OverloadeDemo, 配置: Debug Win32 ------
1> main.cpp
1>e:\c_code\overloadedemo\overloadedemo\main.cpp(35): error C2665: “f2”: 2 个重载中没有一个可以转换所有参数类型
1> e:\c_code\overloadedemo\overloadedemo\main.cpp(18): note: 可能是“void f2(int *)”
1> e:\c_code\overloadedemo\overloadedemo\main.cpp(13): note: 或 “void f2(char *)”
1> e:\c_code\overloadedemo\overloadedemo\main.cpp(35): note: 尝试匹配参数列表“(int)”时
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
n的地址是一个unsgned int 类型,可以是char * 也可以是int* , 那么二义性产生。
为了避免二义性:要么精准匹配,要么强转类型。
08 默认实参
代码示例:
#include <cstdio> void MyCout(int num,bool line) { printf("%d", num); if(line) printf("\n"); } void MyCout(char c , bool line ) { printf("%c",c); if(line) printf("\n"); } void MyCout(char* str, bool line ) { printf("%s",str); if(line) printf("\n"); } int main() { MyCout(100,true);//参数true 不可缺少 return 0; }
这里MyCout(100,true)两个参数都要写上
代码一变:
#include <cstdio> void MyCout(int num,bool line = true) { printf("%d", num); if(line) printf("\n"); } void MyCout(char c , bool line ) { printf("%c",c); if(line) printf("\n"); } void MyCout(char* str, bool line ) { printf("%s",str); if(line) printf("\n"); } int main() { MyCout(100,true);//可以写
MyCout(100); //也可以不写 return 0; }
如果参数全部默认实参,那么,就可以什么参数也不写入:
#include <cstdio> void MyCout(int num=50,bool line=true) { printf("%d", num); if(line) printf("\n"); } void MyCout(char c , bool line ) { printf("%c",c); if(line) printf("\n"); } void MyCout(char* str, bool line = true) { printf("%s",str); if(line) printf("\n"); } int main() { MyCout(); MyCout(100,true); MyCout("I Love Mark",true); MyCout("I Love Mark"); return 0; }
当全部默认实参的时候,函数可以什么也不传递,当有一个默认实参的时候,这个参数可写入,也可以不写。
当全部默认实参的时候,也要注意二义性的产生:
#include <cstdio> void MyCout(int num=50,bool line=true) { printf("%d", num); if(line) printf("\n"); } void MyCout(char c , bool line ) { printf("%c",c); if(line) printf("\n"); } void MyCout(char* str = "I Love Mark", bool line = true) { printf("%s",str); if(line) printf("\n"); } int main() { MyCout(); MyCout(100,true); MyCout("I Love Mark",true); MyCout("I Love Mark",false); MyCout("I Love Mark"); return 0; }
这里高亮的MyCout(),它可以匹配void MyCout(int num=50,bool line=true),又可以匹配void MyCout(char* str = "I Love Mark", bool line = true)。编译就报错。
要解决二义性有两种手段:1 使之唯一匹配 2 在调用时确定它的精确参数
08-2 补充
默认实参必需出现在整个参数的最右边,从右到左,最右边有了以后,左边的才能依次设置默认实参。
09 inlin函数
在函数之前加上关键字:inline
在编译时展开,好处:不会新建栈,只将代码展开。运行效率会大大提升,生成的体积会膨胀。
内联并不是百分百的形成,会根据编译器的判断来完成,它有几个判断的标准:1 栈的使用
代码的鼓胀常见于以后学习的模版中,泛型中。代码鼓胀会使得调试困难。
宏替换和inline的区别在于:inline会有类型的检测。
09-2 inline 函数展开的情况 ( 补充)
内联形成的重要标准:栈的使用。
复杂度:
有人说10行以内的代码或许内联能100之100成功,但如果10行代码,有9行是调用其它函数呢,这个不好说了。这是个扯蛋的说法。
大量的inline会使得程序编译时间加长。
小结:inline内联能成则成,交给编译器。
10 C++风格 类型转换
隐式转换是有风险的,我们有时把隐式转换,写成强制的转换。比如:在算除法,和百分比的的时候等。
上面是C语言的风格,C++的类型转换是什么样子呢?
static_cast<int> 它不是强制转换,是表示转换 (常见于:基类指针 向派 生类指针 之间的一个转换)
const_cast<> 特定情况下使用,它可以使const变成非const。它的作用是用来移除对象的常量性,但还是不能修改内容;不实现const版本。具体有什么用?重载时,它可以用来作参数的匹配, 使得你语法上不会出现错误。常见于指针或者引用上。
reinterpret_cast<> 强制转换 二进制层面的强制转换 不建议使用
强制转换为一个地址。危险操作,不建议使用。
还有一个转换符:dynamic_cast<> 名称推导转换 写大型框架的时候常见。
转换符4个: 1 static_cast<> 2 const_cast<> 3 reinterpret_cast<> 4 dynamic_cast<>
举例了C语言 void* 交换 数据 与C++ 里面函数重载的小示例。
补充:
1 风格,头文件的风格:适合C++的,就是C++风格
2 C风格的转换,不管怎么样,都转换成功,简单暴力
3 C++风格拆分为几个,有相对安全的static_cast,有完全不安全的reiterpret_cast,但还是开放,以适用更多的场景。