double a, double b){} // 2 void fun(double a, double b, char c){} // 3 double b, int c) { return a + b; } int main() { cout << add(1.2, 3.4, 8.9); } 这是一个简单的求和重载函数, 当我传入了3个变量 , 但第三个变量并不符合函数定义形参类型 此时编译器依然选择了形参为3个的重载函数, 并且同时将错误的参数强转 ? 上图可见: 调试结果, 调用了第三个重载函数, 并且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 对象中就包含了 两个 A 类的 子对象 ; 当 访问 A 类的成员时 , 不知道访问哪个 A 类的成员 , 就出现了二义性 ; 3、虚继承原理 使用 虚继承 后 , 在调用 虚继承 父类 构造函数时 ,
一、继承的二义性 1、场景说明 - 继承的二义性 A 类 是 父类 , B 类 和 C 类 继承 A 类 , 是 子类 , D 类 多继承 B 类 和 C 类 , 是 孙子类 ; 假如 A 类中有 成员变量 成员变量 x , D 类 多继承 B 类 和 C 类 , 会 分别从 B 和 C 各自 继承一个 成员变量 x ; D 类中 , 从 B , C 两个父类中继承自 爷爷类 A 的成员变量 , 会出现二义性 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ========== 3、完整代码示例 代码示例 : #include "iostream" using namespace ; 为了应对上述 继承的二义性 问题 , C++ 语言 使用 " 虚继承 " 解决 继承中的 二义性问题 ; C++ 中的 " 虚继承 " 是一种解决 多继承 带来的 菱形问题(diamond problem public A { public: int y; }; // 子类 C 继承了父类 A 的 x 成员 class C : virtual public A { public: int z; }; 3、
name = "yyy"; return 0; } 通过上面的运行结果我们发现,菱形继承存在两个问题:(1)二义性问题;(2)数据冗余问题。 为什么会产生二义性问题? 1.3虚继承 有了多继承就可能有菱形继承,而菱形继承又存在二义性和数据冗余等问题。所以C++就引入了虚继承来解决数据的冗余问题。 ,传入name3("王五")。 因此_name的值由Assistant构造函数中显式指定的Person(name3)决定。 二、总结 共享基类子对象 虚继承确保在多重继承层次结构中,派生类只包含一个共享的基类子对象。
有成员函数 和 成员变量 的 类 , 是不推荐的做法 , 实际开发中 , 绝对禁止 使用 上述类型的 多继承 ; 2、多继承弊端 多继承会带来一系列的问题 , 诸如 : 钻石问题 - 菱形继承结构 / 二义性错误 : 当一个类继承自多个类时 , 如果这些类有共同的基类 , 那么会出现菱形继承结构 , 也称为钻石问题 ; 该场景下 , C++ 编译器 无法确定应该使用哪个基类的成员 , 产生 二义性 ; 成员变量名相同 - 二义性错误 : 子类继承多个父类 , 父类之间没有相同的父类 , 但是 父类中 有相同名称的成员变量 , 此时也会产生二义性问题 , 需要使用域作用符访问父类中相同名称的成员 ; 破坏封装性 : ; 在 菱形继承结构 中 , 虚继承可以使 重复继承 的 父类 , 只继承 依次 ; 多继承的二义性 参考 【C++】继承 ⑫ ( 继承的二义性 | virtual 虚继承 ) 博客 ; 二、代码示例 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ========== 2、代码示例 - 使用虚继承解决菱形继承结构的二义性 在下面的 菱形继承结构 中 , D
==:3型语言或正则语言3型文法是程序设计语言构词规则2.6.1 0型文法对产生式基本无限制2.6.2 1型文法文法左部符号个数不超过右部符号个数2.6.3 2型文法任意产生式A→B,A属于非终结符号, 如上小节给出语法树中,包含根节点S,S1,S2,S3,S4的五棵子树注意叶子结点不算子树短语短语是相对一个句型的,一个句型对应多个短语。短语就是该句型子树的叶子结点如何寻找一个句型短语? 1.画出句型语法树2.找出所有子树3.子树叶子结点组成的符号串为该句型针对子树根节点的短语4.去掉重复的短语找短语的关键还是找子树简单短语与句柄所有短语中,一步推导得来的即为简单短语。 2.9 文法的二义性若一个文法存在某个句子或句型,它存在两棵不同的语法树,则称该句子或句型是二义性的,对应的文法也是二义性的。 二义性不可判定,从底向上看,二义性意味着句柄不唯一,解决二义性的方法是,加以限制,人为避免产生二义性2.9.1 有关文法的实用限制多余规则:指文法中任何句子的推导都不会用到的规则,若有则删去- 不可到达
3.更深层次的原因那么问题来了,为什么 ConcurrentHashMap 的实现源码中,不允许为 key 或者是 value 设置 null 呢? 3.1 什么是二义性问题?所谓的二义性问题指的是代码或表达式存在多种理解或解释,导致程序的含义不明确或模糊。 3.1 可证伪的HashMapHashMap 之所以不怕二义性问题的原因是,HashMap 的设计是给单线程使用的,而单线程下的二义性问题是能被证明真伪的,所以也就不存在二义性问题了(能被证明的问题就不是二义性问题 这样二义性问题就得到了解决,所以 HashMap 的二义性问题可被证明真伪,所以就不怕二义性问题,因此也就可以给 key 或者 value 设置 null 了。 因为在你在证明二义性问题的同时,可能会有另一个线程影响你的执行结果,所以它的二义性问题就一直存在。
问题2:二义性 二义性是指在菱形继承的情况下,派生类可能会有两个或更多的基类提供了相同的函数或数据成员,这在调用时会导致编译器无法确定调用哪个版本。 例如,如果基类A和B都有一个同名的函数,而在派生类中没有明确指定调用哪一个,就会产生二义性错误。 (这个知识点还是比较重要的,因为一些原因,我这里并不会讲,感兴趣的可以自己去网上搜一下视频,或者与我私聊) 3、构造函数和析构函数:当虚继承时,构造函数和析构函数会按照继承顺序依次调用,从而确保虚基类的构造和析构正确地执行 此时,BaseClass 的成员变量 var 在 FinalChild 中只有一份,并且不会发生二义性问题。 总之,C++ 通过虚继承解决了菱形继承中的冗余性和二义性问题,使得在使用继承时更加灵活和安全。
但随之而来的挑战是:多个基类的作用域重叠可能导致名字冲突(二义性,Ambiguity),例如两个基类拥有同名的成员变量或函数。 二、多重继承二义性的典型类型与代码示例 2.1 成员变量的二义性:同名变量冲突 当多个基类定义了同名的成员变量时,派生类对象访问该变量会引发二义性。 若在多重继承的多个基类作用域中找到同名的声明(无论这些声明是否等价),则视为二义性,编译器拒绝编译。 5.2 二义性对赋值的影响 若多个基类存在同名成员,且未显式覆盖,直接赋值会引发二义性。 二义性类型 成员变量、成员函数、虚函数、菱形继承的公共基类均可能引发二义性。 二义性解决方案 显式作用域限定、派生类重写成员、虚继承、using 声明。
什么是文法的二义性? 1)分成四种类型,即0型、1型、2型和3型。 (3)若P中的每一个产生式α→β满足:α是非终结符,β∈(VN∪VT),则此文法称为2型的。(4)若P中的每一个产生式的形式都是A→aB或A→a,其中A和B都是非终结符,a∈VT,则G是3型文法。 2)如果文法G中的某个句子存在不只一棵语法树,则称该句子是二义性的。如果文法含有二义性的句子,则称该文法是二义性的 2. 例如:对于表达式的二义性文法E->E|E-E|E*E|E/E|E↑E|(E)|i 其中任何一个产生式中都不包含两个非终结符相邻的情况,因此该文法为算符文法。 4.什么是3型文法? 什么是文法的二义性?给出一个二义性文法实例 (1)如果文法G中的某个句子存在不只一棵语法树,则称该句子是二义性的。
符号串集V的闭包 V^+ =V^1 ∪ V^2 ∪ V^3 ∪… ,,即 V 上的所有非空符号串(包括空字ε)的集合。 5.4 二义性文法 5.4.1 二义性定义 若一个文法存在某个句型对应两棵不同的语法树,则称这个文法是二义性文法。或者,若一个文法存在某个句型有两个不同的最左(最右)推导,则称这个文法是二义性文法。 二义性一般是有害的,如果一个句子具有二义性,那么对这个句子的结构可能有多种“正确”的解释。通常情况下,我们希望对每个语句的分析是唯一的。 但是,只要我们能够控制和驾驭文法的二义性,文法二义性的存在并不一定是坏事 。 对某文法,若能找出一个句子对应两棵不同的语法树,则该文法必是二义性文法。 二义性文法可以改造为无二义性文法。
protected: string _seminarCourse; // 研究科目 }; void TestPerson() { Student s1; Student s2; Student s3; 3.单继承与多继承 单继承:一个子类只有一个直接父类时称这个继承关系为单继承 如下图所示: 上图中,Student类只有一个直接父类Person,PostGraduate类也只有一个直接父类Student 菱形继承带来的问题 菱形继承可以带来一些问题,主要是关于数据冗余和二义性。 类和Teacher类都有相同的成员,那么当在Assistant类中调用这个成员时,会出现二义性。 ②数据冗余和二义性。 什么是菱形虚拟继承?如何解决数据冗余和二义性的? ①为了解决数据冗余和二义性问题,C++提供了虚继承的机制。
避免二义性。但是最后的派生类不仅负责对直接基类初始化,还要对虚基类初始化。 ---- 看code是最有用的,细节情况已经注释说明。 } }; //D类继承B,C, 因为B,C都继承A,所以D类有2份A,这样就会出现二义性问题。 d.showId(); // 准备验证double da; 产生二义性的情况,这个就只能通过虚基类确定。 // setDa() 是会产生二义性的函数,但是A是虚基类。 // setDa() 是会产生二义性的函数,但是A是虚基类。
图4-3 单继承 4.2.3 继承方式 public:公有继承 private:私有继承 protected:保护继承 作用:控制基类中声明的成员在多大的范围内能被派生类的用户访问 补充:对象只能访问类中 3类的对象无法访问基类的所有成员 4.2.4 总结 ? 图4-12 多继承构造函数调用顺序 4.3 二义性问题 原因:在多继承情况下,造成的对基类中某个成员的访问出现的不唯一的情况 4.3.1 成员函数二义性 ? ,则对该基类中说明的成员进行访问时,可能会出现二义性 4.3.2 成员变量二义性 ? 在类中定义一个同名成员 虚基类 4.3.4 特殊说明 一个类不能从同一个类中直接继承一次以上 二义性检查在访问控制和类型检查之前进行,访问控制和类型检查不能解决二义性问题 4.3.5 示例 ?
类都有相同的成员,那么当在Assistant类中调用这个成员时,会出现二义性。 Assistant a; //不虚继承就会有二义性和数据冗余 a. 3、 public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。 ②数据冗余和二义性。 (2)什么是菱形虚拟继承?如何解决数据冗余和二义性的? ①为了解决数据冗余和二义性问题,C++提供了虚继承的机制。 ②虚拟继承确保在最终派生类中只有一份数据成员和函数,解决了数据冗余与二义性问题。 (3)继承和组合的区别?什么时候用继承?什么时候用组合? ①public继承是一种is-a的关系。
四.语法分析树与二义性 我们发现从一个句型到另一个句型的推导过程不是唯一的。 比如从E->(i+i) 的过程: 对于一个文法,如果它的某些句子对应两棵不同的语法树,这个文法就属于“二义性文法”。 注意,文法的二义性和我们通常所说的语言的二义性不同,我们可能有两个不同的文法G1,G2,一个是二义性,一个是非二义性,但是可能L(G1) = L(G2)。 对于程序语言来说,我们常常希望它的文法是非二义性的,但是,只要我们能够控制和驾驭文法的二义性,文法二义性的存在也不一定是坏事。 现在已经证明了,文法二义性是不可判定的。 也就是说不存在一个算法,在有限步骤内算出一个文法是不是二义性的。我们能做的事儿,就是找一组充分条件来说明非二义性。比如,规定运算符号的优先级和结合性。
答案呼之欲出:多继承的二义性 。 那问题来了,为什么类不能支持 多继承 ,而接口可以支持 多实现 ,继承 和 实现 有什么本质的区别呢? 为什么 实现 不会带来 二义性 的问题,这是理解接口存在关键。 ---- 2. 继承 VS 实现 下面我们来探讨一下 继承 和 实现 的本质区别。 从这里可以看出,接口就是为了解决多继承二义性的问题,而引入的概念,这就是它存在的意义。 ---- 3. 既然普通类可以作为接口,那多实现中的 二义性问题 是必须要解决的,Dart 中是如何处理的呢? 问题一 : 这样,C 就可以实现两个普通类,而避免了二义性问题: class C implements A, B { @override String get name => "C";