上图可见: 调试结果, 调用了第三个重载函数, 并且c ( 9.8 ) 值强转为 int类型 ( 8 ) 相同方式测试 char 等类型也如此 函数重载二义性 问题来了!! 错误 以上这种情况就叫做二义性 解决二义性: 加入新的重载函数, 使用double类型形参 明确调用时实参强转类型: 传参前把数值强转为想要的类型 注: 编译器总是会把基本数据大的转为小的
在编程逻辑世界,有因必有果,如果一个结果含糊不定(二义性),显然是我们不想要的。C++11中引入nullptr是为了解决NULL的二义性问题。 NULL二义性的体现 void func(int) {} void func(int *) {} 当函数调用func(NULL)时会是怎样执行?
答案只有一个:消除函数的二义性。 函数的二义性 那什么是函数的二义性呢? ){} 这个时候就会出现歧义,因为这个函数有两种调用方式: function user(){}; // 普通方式调用 user(); // 当做构造函数调用 new user(); 这就是函数的二义性 因为函数的二义性,导致 JS 函数的复杂度直线上升,因为函数在创建的时候,创建者不知道未来的调用者如何调用,可能直接调用,也有可能通过 new 方法调用,这就会存在很大的安全隐患。 new.target){ throw('Uncaught TypeError: User is not a constructor') } } 所以调用者压根就不清楚函数的调用方式,这个函数的二义性 官方一直都知道这个问题,只是一直没解决,后来ECMAScript 6在给 JS 打补丁的时候,引入了两个概念: 箭头函数 class实例 它们的作用都是为了消除函数的二义性。
另外在 C++11 中也可以使用 Uniform initialization(统一初始化)来处理这种歧义: String ss{string(t)}; // ok 3.总结 这是一个经典的二义性问题
一、虚继承原理 1、虚继承解决继承二义性问题 继承的二义性 : 如果 一个 子类 ( 派生类 ) 继承多个 父类 ( 基类 ) , 这些父类 都继承了 相同的父类 , 那么 子类 访问 父类的父类 中的成员 , 就会产生 二义性 ; 报错 : error C2385: 对“x”的访问不明确 ; 使用 " 虚继承 " 可以解决上述问题 , 子类 继承父类时 , 在 访问限定符 之前使用 virtual 关键字 即可将 普通继承 改为 虚继承 ; 下面的代码中 A 是父类 ; B 类 和 C 类 虚继承 A 类 , 这样当 某个类 同时 多继承 B 类 和 C 类时 , 访问 A 类中的成员时 , 不会出现 二义性 ; 由于 B 和 C 虚继承 A , D 类访问 A 中的成员 , 不会产生二义性 ; class A { public: int x; }; // 子类 B 继承了父类 A 的 x 成员 class , public C { public: int k; D() { cout << "D 构造函数" << endl; } }; int main() { D d; d.x = 10
一、继承的二义性 1、场景说明 - 继承的二义性 A 类 是 父类 , B 类 和 C 类 继承 A 类 , 是 子类 , D 类 多继承 B 类 和 C 类 , 是 孙子类 ; 假如 A 类中有 成员变量 成员变量 x , D 类 多继承 B 类 和 C 类 , 会 分别从 B 和 C 各自 继承一个 成员变量 x ; D 类中 , 从 B , C 两个父类中继承自 爷爷类 A 的成员变量 , 会出现二义性 public B, public C { public: int k; }; int main() { // 定义 D 类对象 d D d; // 访问继承自 B 类的 y 成员 d.y = 10 ; 为了应对上述 继承的二义性 问题 , C++ 语言 使用 " 虚继承 " 解决 继承中的 二义性问题 ; C++ 中的 " 虚继承 " 是一种解决 多继承 带来的 菱形问题(diamond problem public B, public C { public: int k; }; int main() { // 定义 D 类对象 d D d; // 访问继承自 B 类的 y 成员 d.y = 10
_name = "peter"; // 需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决 a.Student::_name = "xxx"; a.Teacher::_ name = "yyy"; return 0; } 通过上面的运行结果我们发现,菱形继承存在两个问题:(1)二义性问题;(2)数据冗余问题。 为什么会产生二义性问题? 解决二义性的办法:指定类域(是Teacher类的Person还是Student类的Person) 数据冗余问题很好理解,就是Assistant中包含了两个Person类的信息,正常情况下应该只包含一份 1.3虚继承 有了多继承就可能有菱形继承,而菱形继承又存在二义性和数据冗余等问题。所以C++就引入了虚继承来解决数据的冗余问题。
有成员函数 和 成员变量 的 类 , 是不推荐的做法 , 实际开发中 , 绝对禁止 使用 上述类型的 多继承 ; 2、多继承弊端 多继承会带来一系列的问题 , 诸如 : 钻石问题 - 菱形继承结构 / 二义性错误 : 当一个类继承自多个类时 , 如果这些类有共同的基类 , 那么会出现菱形继承结构 , 也称为钻石问题 ; 该场景下 , C++ 编译器 无法确定应该使用哪个基类的成员 , 产生 二义性 ; 成员变量名相同 - 二义性错误 : 子类继承多个父类 , 父类之间没有相同的父类 , 但是 父类中 有相同名称的成员变量 , 此时也会产生二义性问题 , 需要使用域作用符访问父类中相同名称的成员 ; 破坏封装性 : ; 在 菱形继承结构 中 , 虚继承可以使 重复继承 的 父类 , 只继承 依次 ; 多继承的二义性 参考 【C++】继承 ⑫ ( 继承的二义性 | virtual 虚继承 ) 博客 ; 二、代码示例 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ========== 2、代码示例 - 使用虚继承解决菱形继承结构的二义性 在下面的 菱形继承结构 中 , D
2.9 文法的二义性若一个文法存在某个句子或句型,它存在两棵不同的语法树,则称该句子或句型是二义性的,对应的文法也是二义性的。 二义性不可判定,从底向上看,二义性意味着句柄不唯一,解决二义性的方法是,加以限制,人为避免产生二义性2.9.1 有关文法的实用限制多余规则:指文法中任何句子的推导都不会用到的规则,若有则删去- 不可到达
代码示例:成员变量的二义性 #include <iostream> // 基类A:包含成员变量x class BaseA { public: int x = 10; }; // 基类B:包含同名成员变量 代码示例:同名不同类型的成员 class BaseA { public: int x = 10; // 成员变量x }; class BaseB { public: void x() 代码示例:显式限定作用域 #include <iostream> class BaseA { public: int x = 10; }; class BaseB { public: int x = 代码示例:using 声明的使用 #include <iostream> class BaseA { public: int x = 10; }; class BaseB { public: int 显式调用BaseB的赋值运算符 return *this; } }; int main() { Derived d1, d2; d1.BaseA::x = 10
最后一个类D又继承于B和C,这样形式的继承称为菱形继承 菱形继承的缺点: 数据冗余:在D中会保存两份A的内容 访问不明确(二义性):因为D不知道是以B为中介去访问A还是以C为中介去访问A,因此在访问某些成员的时候会发生二义性 如果D1和D2都没有x的定义:此时对x的访问不会产生二义性,因为只含有x的一个实例 如果D1中有x的定义而D2没有:同样没有二义性,派生类的x比虚基类B的x优先级更高(或者D1中没有x的定义而D2有x的定义 ) 如果D1和D2都有x的定义:对x的访问会产生二义性 ? public: B(int a):A(10) {} }; class C :public B { public: C() :B(10) {} //可以不为A进行构造,因为A的构造已经交给B了 }; 10), C(20) {} 错误的,必须显式为A进行构造 D() :A(5), B(10), C(20) {} //正确 }; 构造函数的执行顺序 规则:虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关
定义 不同 的 函数参数列表 ; 判定标准 : 只有 函数参数 的 个数 / 类型 / 顺序 的不同 是 " 函数重载 " 的判断标准 , 函数 的 返回值 不是 " 函数重载 " 的 判断标准 ; 二义性 : 如果 函数重载 与 默认参数 结合使用 , 出现了二义性 , 编译直接失败 ; 一、函数重载 1、重载函数调用分析 重载函数 调用查询 分析 : 调用一个重载函数 , 如何从多个重载函数中找出自己要调用的函数 函数参数 的 个数 / 类型 / 顺序 的不同 是 " 函数重载 " 的判断标准 , 函数 的 返回值 不是 " 函数重载 " 的 判断标准 ; 二、函数重载与默认参数 1、函数重载与默认参数出现的二义性分析 如果只是调用 fun(1, 2, 3) , 也是可以执行成功的 ; 因为可以唯一定位 函数 1 void fun(int i, int j, int k = 10) , 不会出现二义性 ; // 函数 如果执行 fun(1, 2) , 会同时匹配 函数 1 和 函数 2 , 此时出现了 二义性 , 在编译时 , 就会报错 ; 代码示例 : // 包含 C++ 头文件 #include "iostream
问题2:二义性 二义性是指在菱形继承的情况下,派生类可能会有两个或更多的基类提供了相同的函数或数据成员,这在调用时会导致编译器无法确定调用哪个版本。 例如,如果基类A和B都有一个同名的函数,而在派生类中没有明确指定调用哪一个,就会产生二义性错误。 6、多继承时的虚继承:当多个类同时virtually继承同一个虚基类时,虚基类的成员变量和成员函数在子类中只会存在一份,避免了冗余性和二义性问题。 此时,BaseClass 的成员变量 var 在 FinalChild 中只有一份,并且不会发生二义性问题。 总之,C++ 通过虚继承解决了菱形继承中的冗余性和二义性问题,使得在使用继承时更加灵活和安全。
3.1 什么是二义性问题?所谓的二义性问题指的是代码或表达式存在多种理解或解释,导致程序的含义不明确或模糊。 3.1 可证伪的HashMapHashMap 之所以不怕二义性问题的原因是,HashMap 的设计是给单线程使用的,而单线程下的二义性问题是能被证明真伪的,所以也就不存在二义性问题了(能被证明的问题就不是二义性问题 这样二义性问题就得到了解决,所以 HashMap 的二义性问题可被证明真伪,所以就不怕二义性问题,因此也就可以给 key 或者 value 设置 null 了。 ,所以二义性问题是真实存在的。 因为在你在证明二义性问题的同时,可能会有另一个线程影响你的执行结果,所以它的二义性问题就一直存在。
.}; 派生类同时继承多个基类的成员,更好的软件重用 可能会有大量的二义性,多个基类中可能包含同名变量或函数 多重继承中解决访问歧义的方法: 基类名::数据成员名(或成员函数(参数表 ; error //sofaBed.weight_ = 20; error sofaBed.Bed::weight_ = 10; sofaBed.Sofa::weight_ = 只能通过 sofaBed.Bed::weight_ = 10; 访问,但实际上一个sofaBed理应只有一个weight_,下面通过虚基类和虚继承可以解决这个问题。 二、虚继承与虚基类 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性,可以采用虚基类来解决。 虚基类的引入 用于有共同基类的场合 声明 以virtual修饰说明基类 例:class B1:virtual public BB 作用 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题
图4-10 多继承 ? 图4-12 多继承构造函数调用顺序 4.3 二义性问题 原因:在多继承情况下,造成的对基类中某个成员的访问出现的不唯一的情况 4.3.1 成员函数二义性 ? ,则对该基类中说明的成员进行访问时,可能会出现二义性 4.3.2 成员变量二义性 ? 图4-14 成员变量二义性 解决办法:1区别出是通过类B1或类B2调用类A的a,c1.B1::a或c1.B2::a 2虚基类 4.3.3 解决办法 利用成员名限定法消除二义性 在类中定义一个同名成员 虚基类 4.3.4 特殊说明 一个类不能从同一个类中直接继承一次以上 二义性检查在访问控制和类型检查之前进行,访问控制和类型检查不能解决二义性问题 4.3.5 示例 ?
<< endl; //打印局部变量 cout << ::a << endl; //打印全局变量 } int main() { test01(); return 0; } 最后结果 10 b = 20; } //命名空间可以重名 namespace B { int c = 100; } 4)命名空间可以嵌套命名空间 //命名空间可以嵌套 namespace C { int a = 10 test03(); return 0; } 三、 using的指令 1 using的声明 usinng 的声明可以使得指定标识符可用 注意: 当using声明的标识符和其他同名标识符有作用域的冲突时,会产生二义性 //注意当using指定声明标识符和其他标识符作用域有作用域的冲突时,会产生二义性 //int a = 100 using nameA::a; using nameA::foo; cout < foo() { cout << "Hello using" << endl; } } void test01() { //注意当using指定声明标识符和其他标识符作用域有作用域的冲突时,会产生二义性
例: ∑={0,1} 是字母表,其中 0,1 为符号,则D={0,1} 其中 0,1 为符号串,E= {ε, 0,1,00,01,10,11,000, …}是 ∑ 上的符号串集合。 5.4 二义性文法 5.4.1 二义性定义 若一个文法存在某个句型对应两棵不同的语法树,则称这个文法是二义性文法。或者,若一个文法存在某个句型有两个不同的最左(最右)推导,则称这个文法是二义性文法。 二义性一般是有害的,如果一个句子具有二义性,那么对这个句子的结构可能有多种“正确”的解释。通常情况下,我们希望对每个语句的分析是唯一的。 但是,只要我们能够控制和驾驭文法的二义性,文法二义性的存在并不一定是坏事 。 对某文法,若能找出一个句子对应两棵不同的语法树,则该文法必是二义性文法。 二义性文法可以改造为无二义性文法。
调用print(10)时,实参是基本类型int(无命名空间),ADL 不触发,仅搜索全局作用域。 3.2 using指示的二义性风险 若多个命名空间中存在同名且参数列表相同的函数,using指示会导致重载集包含多个候选函数,调用时可能因二义性报错。 )无法确定选择哪个函数,导致二义性。 ②避免跨命名空间的参数列表相同函数 若不同命名空间中存在同名且参数列表相同的函数,using指示会导致二义性。设计时应确保跨命名空间的函数参数列表不同。 using指示:批量引入命名空间成员,可能扩展重载集但需警惕二义性。 模板与命名空间:模板函数的重载需考虑特化的可见性,ADL 可触发命名空间内的模板特化。
9 10 class Son(Father): 11 """A simple example class""" 12 i = 12345 13 def __init__(self #python方法二义性问题 第四点,由于Python支持多重继承,因此就有可能出现方法二义性问题[1]。然而由于Python遵循深度优先的搜寻法则,很好地避免了方法二义性的问题。 [1] 方法二义性:由于一个类同时继承于两个或者多个父类,而在这些父类当中存在着signature完全相同的方法,那么编译器将无法判断子类将继承哪个父类中的方法,从而导致方法二义性问题。