读编程与类型系统笔记07_子类型

1. 子类型

1.1. 在期望类型T的实例的任何地方,都可以安全地使用类型S的实例,则类型S是类型T的子类型

1.1.1. 里氏替换原则(Liskov substitution principle)

2. 名义子类型

2.1. 明确指定

2.2. 显式声明一个类型是另一个类型的子类型

2.3. 大部分主流编程语言采用的方式

2.3.1. Java

2.3.2. C#

2.4. TypeScript通过使用unique symbol可以模拟名义子类型

3. 结构子类型

3.1. 类型具有相同的结构

3.2. 不需要显式声明子类型关系

3.3. 一个类型的结构与另一个类型相似(具有相同的成员,可能还有额外的成员),自动被视为后者的子类型

3.4. TypeScript使用结构子类型

3.5. 即使类型不在我们的控制范围内,我们在类型之间仍然能建立关系

3.5.1. 例如:我们不能修改的来自外部库的一个类型

4. 极端情况

4.1. 顶层类型

4.1.1. 把任何东西赋值给它的类型

4.1.1.1. 用来存储任何东西

4.1.1.2. C#的Object

4.1.1.3. TypeScript的unknown

4.1.1.3.1. null的类型是null
4.1.1.3.2. undefined的类型是undefined
4.1.1.3.3. Object
4.1.1.3.4. 三者和类型即unknown
4.1.1.3.4.1. Object | null |undefined
4.1.1.3.5. 只有当我们确认一个值具有某个类型时,才能把该值用作该类型
4.1.1.3.5.1. C#提供了is关键字
4.1.1.3.5.2. Java则提供了instanceof

4.1.2. 其他任何类型的父类型

4.1.3. 位于子类型层次结构的顶端

4.2. 底层类型

4.2.1. 可以赋值给任何东西的类型

4.2.1.1. 没有某种类型的实例可用

4.2.1.2. TypeScript的never

4.2.2. 其他任何类型的子类型

4.2.3. 位于子类型层次结构的底端

4.2.4. 始终是一个空类型:这是我们不能为其创建实际值的类型

4.2.5. 允许我们假装有任何类型的一个值,即使我们并不能生成这个值

4.2.6. 很少有主流语言提供底层类型

4.2.6.1. 使一个类型成为空类型,但不能使其成为底层类型

4.2.6.2. 除非在编译器中实现,否则我们无法自定义底层类型

5. 和类型

5.1. 父类型比子类型的类型更多

5.1.1. 例如:Triangle | Square是Triangle | Square | Circle的子类型

5.2. Variant能够封装几个类型中某个类型的值,但是它本身不是其中任何一个类型

6. 可变性

6.1. 协变性

6.1.1. 一个类型保留其底层类型的子类型关系

6.1.2. 数组具有协变性,因为它保留了子类型关系

6.1.3. 当处理集合(如LinkedList)时,不同的语言具有不同的行为

6.1.3.1. 在C#中,必须通过声明接口并使用out关键字(ILinkedList),显式指出一个类型(如LinkedList)的协变

6.1.4. 函数的返回类型具有协变性

6.2. 逆变性

6.2.1. 一个类型颠倒了其底层类型的子类型关系

6.2.2. 大部分编程语言中,函数的实参是逆变的

6.2.2.1. TypeScript是一个例外

6.2.2.1.1. 故意做出的设计决策

6.3. 双变性

6.3.1. 类型的底层类型的子类型关系决定了它们互为子类型

6.3.2. 在TypeScript中函数实参的双变性可能导致错误的代码通过编译

6.4. 不变性

6.4.1. 一个类型不考虑其底层类型的子类型关系

6.4.2. C#中的List具有不变性

7. any类型

7.1. 可以把任何值赋值给any

7.2. 可以把any值绕过类型检查赋值给其他任何类型

7.3. 会绕过类型检查立即把该值用作其他任何类型的值

posted @ 2023-01-14 14:32  躺柒  阅读(62)  评论(0编辑  收藏  举报