静态类型推导

2023-11-05

前面说泛型的时候,提到了C++模板的实现方式是动态特性静态化,在实际情况中,这是一个提高效率的好办法。动态性的好处是灵活,开发简便,静态性的特性是效率高,编译期检查较好,因此很自然地就有一个问题,能不能各取所长,达到两全其美?应该说,在一定程度上是可以的,比如这篇即将讨论的静态类型推导,简称类型推导,因为动态类型无所谓什么推导。个人认为类型推导是编译原理最复杂的东西之一,其他复杂的有垃圾回收,代码优化等,后面会简单提到 

类型推导,是指一个静态类型语言,在代码中可以不显式声明变量的类型,由编译器根据代码分析来判断出正确的类型,例如: 

i = 1 
j = i * 2 
k = i + 1.234 
print i, j, k 

强调一点,这个代码和本篇下面的代码都是静态类型语言(伪代码形式,能看懂就行了),虽然写起来像是动态类型。对于上面的代码,编译器推导结果如下: 
int i = 1 
int j = i * 2 
double k = i + 1.234; 
print "%d %d %f" % (i, j, k)

这个过程在go和C++中已经有了,一个是:=赋值符号,一个是auto关键字,在定义变量时候不指定类型,由初值表达式的类型得出,在使用上增加了很多方便,不过,这个只是最初级的类型推导 
注:auto关键字在很早的C标准就有了,后面C++修改了它的语义,这是一个不太兼容的地方,不过原本C中auto语义基本用不到 

go和C++这种机制只是在赋初值时候使用,也就是说声明的位置还是确定的,而像上面的伪代码例子则不是,这有一点差别,就是在编译期做类型检查的时候查错机制不同,前者是先确定类型,后者则是检查使用的一致性,比如: 
i = 1 
i = "abc" 
i = 1.234 

编译器如果三次赋值是在三个文件中(假设i是extern变量这类),则编译器并不清楚程序员的真实意图是什么,因此只能报一个类型不一致的错误,当然这是假定了go和C++的这个语法的初值符合程序员意图,否则也可能报一个莫名其妙的错 

由于调用函数时,参数传递可看做是一个给函数的局部变量赋初值过程,所以C++的函数模板的调用,也是上面说的这种类型推导,所不同的是,由于函数只是一段处理代码,所以用不同类型的参数调用,会产生不同的函数实例,只要每个实例都能编译通过即可 

这是通过表达式的值来推导变量类型,顺便讲反过来推导的例子,通过变量类型也可以推导表达式中的某些类型,不过这个很少见,主要是在java的泛型中: 
static <T> Vector<T> f() 
{ 
    return new Vector<T>(); 
} 
Vector<String> v = f();
 
这个f函数的泛型类型T,没有办法通过传入参数来决定,因为就没有参数,同时java好像也不能用f<String>()这样的语法,所以只能通过被其返回值赋值的变量的类型来决定,由于前面讲过的java的泛型实现原理,可以改成这样: 
static Vector f() 
{ 
    return new Vector(); 
} 
Vector<String> v = f(); 

不过之前也说了,java的泛型是为了编译器做更多安全检查,所以这样写会有告警,如果写成这样则会报错,尽管不影响实现原理: 
static <Object> Vector<Object> f() 
{ 
    return new Vector<Object>(); 
} 
Vector<String> v = f(); 

这种推导主要原因大概是java不支持f<String>()这种方式,编译上做一个补充罢了。但它是属于后续补充推导的,也就是说,编译器在编译赋值语句的右值的时候,信息是不完全的,只能知道f大概是返回了一个Vector,而只有在看到左值的时候,才对f的返回值做一个补充。当然,编译器在编译赋值语句的时候,一般应该还是先编译左值,这只是从执行的顺序来看 

假如所有的变量都没有显式声明类型,根据初值来确定类型(go和C++这种),似乎也没什么问题,如果有多个赋值语句,搞不清哪个是所谓“初值”,至少也可以检查不一致性。这个结论对于基本类型还算没错,但一旦引入对象等就有问题了,例如下面的代码: 
i = null 
... 
i = new vector() 
... 
i.add(123) 

编译器看到第一句,只知道i是个对象,还不知道具体是什么,看到第二句,才知道i是个vector,但不知道元素是什么, 再看到第三句,才知道i是vector<int>。也就是说,表达式的值是模糊的,不一定有精确的类型能立即推导出变量的类型,可能需要在这个变量所有被赋值或被改变(比如上面的add方法调用)的地方做检查,收集各种信息然后汇总,拼凑出一个完整的轮廓,当然具体这个例子,如果在一个函数的上下文中,似乎还不难推导。一个细节问题是,如果信息缺失怎么办,比如代码里就一个i=null,其他啥都没有,这种情况下可以warning,或者随便给安一个Object类型,反正也没用到 

如果考虑到函数或类,这个问题就更复杂了,首先明确一点的是,一个函数或类应当是一个模板,而不是一个确定类型的,比如说定义一个f(i),则这并不是f(SomeType i)的简写,如果是的话,我们只要找到它的一个调用的地方,就能很容易根据参数确定了,但是实际情况中一个函数应该是能接受各种类型的处理流程,例如: 
func f(a, b): 
    return a + b 
f(1, 2) 
f("hello", "world") 

在编译阶段会将f视作一个模板,根据传入类型的不同将其转换成各种实例,如果和变量做同样处理,上面代码报f的类型不一致(int(int,int)和str(str,str)),则要求在每个确定的程序中,f确定,这是不现实的,最简单的,定义一个func sort(a),就只能一种类型来用,这反而还不如显式指定类型然后重载 

考虑如下代码: 
func f(a): 
    print a 
func g(a, b): 
    a.add(b) 
i = new vector(); 
f(i) 
g(i, "hello") 
f(i) 
j = new vector(); 
j.add("world") 
f(j) 
k = new set(); 
g(k, 123) 
f(k) 

这个代码中,当编译器看到f(i)的时候,i的类型是vector<?>,第一个f的类型是void(vector<?>),然后在看到g(i, "hello")的时候,分析g的代码发现b是字符串,而传入的i会被用来add(b),于是i就是vector<str>,然后返回去第一个f就是void(vector<str>),接下来看到第二个f(i),这个f的类型跟上面一样,于是合并,再下来两句可以推导出j也是vector<str>,第三个f和前面也一样,然后下一句k是set<?>,分析g(k, 123)的代码发现k是set<int>,最后一个f就是void(set<int>),于是最后的代码会成为: 
void f(vector<str> a): 
    print a.to_str() 
void f(set<int> a): 
    print a.to_str() 
void g(vector<str> a, str b): 
    a.add(b) 
void g(set<int> a, int b): 
    a.add(b) 
vector<str> i, j 
set<int> k 
...//下面的代码略 

上面这个例子用了重载来扩展函数,但是有时候还需要考虑返回值,比如: 
func f(): 
    return new vector() 
i = f() 
i.add(123) 
j = f() 
j.add("hello") 

于是经过推导,展开的时候,就得成f_ret_vector_int和f_ret_vector_str这种形式,由于返回值不在重载控制范围。这种情况就是上面那个java的例子的扩展版本,只不过java那个是必须赋值时候推导,这里需要后续根据add行为来推导,更复杂一些 

这里只举了函数的例子,类的情况也类似,考虑到类的方法也是模板,可以说更复杂,大家自己想象 

于是我们看到,如果自动推导跨了类和函数,会比较麻烦,不过也是能做到的,在一个程序编译的时候,我们强制规定main函数只有一个实例,这是合理的,然后从main开始,画出所有函数的调用树,对于未确定的类型,用一个待定号码来表示(例如上面的“?”),等确定了再回填,最后再合并类型相同的函数即可,问题在于,如果只是一棵树还好办,但如果加上递归调用,树就变成了图,例如: 
func f(a, b): 
    ... 
    f(new vector(), b + 1.0) 
    ... 
    f(new vector(), (int)b) 
    ... 
    a.add(b) 
f(new vector(), 1) 

这里编译到f的调用的时候,参数是vector<?>和int,然后可以推出传入的a是vector<int>,然后再第一个递归调用的时候,需要新建一个参数为vector<?>和double的f,第二个递归的时候可以复用之前的第一个f的实例,这个推导还是很麻烦的 

实际上,针对f的静态分析本身可以总结出一些特点,比如看到a.add(b)这句,虽然不知道传入的a和b具体类型,但是知道它们的约束,即a必须有一个add(typeof(b) b)的方法,用静态分析先总结约束,然后每次调用时候就能直接检查和推导实例,不用每次分析代码,但我觉得算法和数据结构表示可能太复杂了 

说到这里再提醒一句,vector这个类也是模板,new的时候是不知道它具体类型的,根据后续add来决定,因此上面说的“a必须有一个add(typeof(b) b)的方法”的约束应该是说f的类型是void(vector<T>,T)这样比较合适,这还没完,如果a.add(b)下面再加一个b.add(a),那这个约束该怎么写呢,泛型表示都是无限递归了,还有,假如这个add不是f里面,而是在更深的调用,甚至是回调函数,那写个编译器分析这个我只能呵呵呵了,有心无力啊,还是交给专家来研究吧


考虑到递归,有时候很简单一句话也不一定容易处理,比如: 

class A: 
    A(i): 
        this.i=i 
i = A(i) 

i是一个类A<A>的对象(实际表示形式是无限递归的,用一个单独的A表示自身),但是这个推导就可能需要把这句赋值分解成: 
i = A() 
i.i = i 

这样可能会好点,但如果是A的两个实例互相引用: 
class A: 
    A(i): 
        this.i = i 
        this.j = null 
i = A(123) 
j = A("hello") 
i.j = j 
j.j = i 

这种代码生成的类型系统就很复杂了 

虽然类型推导很复杂,但还是有很多语言实现了,或部分实现了,函数式编程比较多,据说Haskell就做的很好,但是命令式编程里面貌似比较少,或者就是只支持C++和go这种“半吊子”推导,究其原因,我觉得除了上面说的实现的复杂性以外,代码可读性是一个很重要的原因,毕竟一个变量的类型如果要读完代码(甚至要跨越几个库)才能知道,这是非常难以维护的,相对来说多敲几个声明成本是很低的,如果是大项目,打这点字死不了人,如果是小程序,那直接上动态类型语言一般也能接受了,因此,没有必要做很完整的类型推导,不过在理论方面,这仍然是编译原理中一直被深入研究的一个话题

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

静态类型推导 的相关文章

  • 一门新的编程语言ecere简介

    ecere 简称eC 是加拿大学者jerome历时十二年开发的一门编译型编程语言 拥有C 项目的性能 Java的跨平台性以及Python语法的简洁性 ecere在C语言的基础上加入了面向对象的支持 但与C Java相比 它更像是一个C语言的
  • QT实现动态翻译和语言切换

    QT GUI提供语言动态转换机制并辅以相应的工具方便programmer实现界面的多语言实时动态切换功能 实现语言动态切换的方法 一个注意 五个步骤 一个注意 实现QT工程的语言切换功能的一个关键点是所有的字符串都需要tr修饰符 例如 m
  • 月薪1000到游戏创业赚百万,程序员到自媒体达人,他是怎么做到的?

    深圳雷哥 一位8年游戏开发经验的程序员游戏创业赚了百万从程序员转战自媒体写作与职场教练目前副业每月收入高达4K全网粉丝2 万 预计年底破5万下面来听听雷哥的传奇故事 希望对大家有所启发和帮助 01 我的学生时代 我叫雷巍 今年32岁 来自湖
  • 与Miriam Suzanne一起进行Sass,Susy,单元测试和寻找声音

    In this episode of the Versioning Show Tim and David are joined by Miriam Suzanne best known for Susy a responsive layou
  • 8个适合新手的Python小项目

    这是我挑出来的8个适合新手的小项目 涉及了爬虫 多线程 selenium PhantomJs itchat 邮件发送 crontab 命令行颜色显示 excel操作 PIL 识别验证码 首先说明 天下没有免费的午餐 每个项目平均下来2元多一
  • 二进制在数学中的妙用

    二进制在数学中的妙用 goal00001111搜集整理 十 八世纪初 莱布尼茨发明了二进制数 当时的他肯定没有预料到二进制在信息时代会有着如此广泛的应用 二进制数以其工作可靠 运算简单 逻辑严密 容易实现 等特点 成为了计算机的专用语言 在
  • Gavin Wood Web3峰会最新演讲:波卡不是智能合约平台,而是平台的平台(全文)...

    在波卡上 每个平台都在用高性能 高效率和最优的方式做着自己擅长的事 而不必让它们的用户用底层平台的货币进行支付 从而将可定制性和灵活性提高了一个台阶 本文谨代表作者个人观点 不代表火星财经立场 该内容旨在传递更多市场信息 不构成任何投资建议
  • 程序员如何做副业?35岁前,千万别让死工资绊住你赚钱的步伐

    近年来互联网行情下降 好多人都在思考要不要搞个副业来抵御风险 这不又来事了 这两天又爆了互联网大裁员 继阿里 向社会输送人才 之后 京东又搞了个 毕业礼 整的小伙伴们人心惶惶 副业的关注度又一波升级 那今天我们就来聊聊 程序员做副业这件事
  • 【编译原理】 CS143 斯坦福大学公开课 第一周:简介

    youtube 1 1 Introduction to Compilers and interpreters 1 1 Introduction to Compilers and interpreters 编译器解释器介绍 两种主要的实现编程
  • n行Python代码系列:两行代码去除抖音快手短视频尾部Logo

    老猿Python博文目录 https blog csdn net LaoYuanPython 一 引言 最近看到好几篇类似 n行Python代码 的博文 看起来还挺不错 简洁 实用 传播了知识 带来了阅读量 撩动了老猿的心 决定跟风一把 推
  • 模板类成员函数特化写法

    昨天有对模板类的函数成员特化需求 目的是为了对不同模板参数实现不同的操作 结果在写过程中碰到already defined的问题 貌似是模板新手最容易碰到的问题了 类外的成员函数和同在类外的特化版本成员函数冲突了 因为对模板用法不是很熟悉
  • 一文带你从IntelliJ IDEA中一键生成Controller、Service、Dao、Model层代码,真的不看看吗?

    前言 EasyCode插件介绍与安装 简介EasyCode是基于IntelliJ IDEA开发的代码生成插件 支持自定义任意模板 Java html js xml 只要是与数据库相关的代码都可以通过自定义模板来生成 支持数据库类型与java
  • python字典中如何添加键值对

    添加键值对 首先定义一个空字典 gt gt gt dic 直接对字典中不存在的key进行赋值来添加 gt gt gt dic name zhangsan gt gt gt dic name zhangsan 如果key或value都是变量也
  • 程序员升职记 全关卡攻略&通俗思路 Human Resource Machine

    程序员升职记 全过关方法 通俗思路 博主本着能过就过的思想 写出的解答必然不是最优解 但是可以给大家提供一点思路来参考 其中17和22的解答整理自网络 特别是17的解答 要比博主的原解答巧妙不少 1 收发室 模拟程序输入输出 HUMAN R
  • SourceInsight

    1 开胃菜 初级应用 1 1 选择美丽的界面享受工作 虽然不能以貌取人 但似乎从来没有人责备以貌取软件的 SI的华丽界面 绝对符合现代花花世界的人的审美趣味 在SI中 我们可以轻松地把各种类型关键字 变量 标志符 函数 宏 注释等定义为不同
  • 【PAT】B1032 挖掘机技术哪家强 (20 分)_C语言实现

    1 挖掘机技术哪家强 20 分 为了用事实说明挖掘机技术到底哪家强 P A T PAT PAT 组织了一场挖掘机技能大赛 现请你根据比赛结果统计出技术最强的那个学校 输入格式 输入在第 1
  • 快速学习Python基础知识(3)

    一 输入输出 1 1 input输入函数的使用 input函数 是获取键盘输出 保存成一个字符串 注意 input 函数的返回值是一个字符串类型 即便你输入的是数字 返回的也会以一个字符串的形式返回给我们 inputStr input 提示
  • C#学习笔记 控制流

    C 是一门命令式的语言 默认语句以顺序方式执行 利用控制流语句可以改变程序的执行流程 以实现复杂的算法 条件语句 if语句 如果如果是单条件判断 可以使用if语句 if语句的执行体 既可以是单条语句也可以是由 花括号括起来的语句块 bool
  • CodeBlocks+wxWidgets

    之前也安装过CodeBlocks 只是当时没有安装wxWidgets 试着新建一个wxWidgets工程后没有看到界面设计的东东就放弃了 今天发现群里的南果梨也在用CodeBlocks 在他的帮助也终于成功的安装了wxWidgets 到ww
  • C 语言文件读取全指南:打开、读取、逐行输出

    C 语言中的文件读取 要从文件读取 可以使用 r 模式 FILE fptr 以读取模式打开文件 fptr fopen filename txt r 这将使 filename txt 打开以进行读取 在 C 中读取文件需要一点工作 坚持住 我

随机推荐

  • 快速基于nodeJS+vue+vuex+mysql+redis建立一个后台管控系统

    structure admin structure admin是一个后台管控系统的架子 技术栈 nodeJS vue vuex mysql redis 前端使用vue的element ui的组件库 后端使用nodeJS的服务 数据库mysq
  • 求一行字符串的长度。(C语言)

    代码 include
  • hadoop集群搭建

    文章目录 一 基本配置 所有节点 一 配置静态网络 二 修改主机名和修改host文件 三 禁用SELINUX 四 关闭防火墙 并取消开机自启动 五 配置NTP时间同步 集群所有节点 六 下载一下vim编辑器 七 安装JDK1 8 八 创建h
  • 【Mysql高级】【第十五章】【锁】

    锁 1 概述 2 Mysql并发实务访问相同记录 2 1 读 读 2 2 写 写 2 3 读 写或者写 读 2 4 并发问题的解决方案 3 锁的不同角度分类 3 1 从数据操作的类型划分 读锁 写锁 0 行级别x锁 和 s锁的兼容性问题 1
  • qt connect多次

    1 坑的现象 有时项目中一个信号发出 对应连接的槽函数会执行多次 普通刷新界面都不会有问题 但是特别频繁的就会影响性能 如果是改变数据的 更有甚者会异常崩溃 2 遇坑的原因 qt中同一实例的同一信号和槽 connect多次 当信号发出时 槽
  • AD9 PCB文件黑色区域如何改变?

    PCB的大小是由KeepOut层定义的 用机械层定义有时候 黑色区域无这方面的意义 改变黑色区域大小可以用Design BoardShape RedefineBoardSharp来完成
  • 国内外常用公共NTP网络时间同步服务器地址

    目录 太长不看 NTP Pool Project NTP ORG CN NTP授时快速域名服务 HSDN Home Server Data Network 本地服务器数据网络 企业 阿里巴巴 腾讯 微软 苹果 谷歌 Facebook Clo
  • 从零开始学C++之STL(一):STL六大组件简介

    一 STL简介 一 泛型程序设计 泛型编程 generic programming 将程序写得尽可能通用 将算法从数据结构中抽象出来 成为通用的 C 的模板为泛型程序设计奠定了关键的基础 二 什么是STL 1 STL Standard Te
  • 高校圆桌派-解惑关于IT行业的3+N个问题

    高校圆桌派 话题风暴等你来 即日起参与 高校圆桌派 活动 就有机会获得CSDN高校圆桌大礼包和CSDN周边礼品免费包邮送到家 关于高校圆桌派 高校圆桌派活动是由CSDN高校俱乐部官方发起 征集同学们感兴趣的IT行业问题或大家最关心的热门话题
  • matlab中imrote,基于MATLAB的车牌识别系统的设计与研究

    基于MATLAB的车牌识别系统的设计与研究 基于MATLAB的车牌识别系统的设计与研究 摘要 汽车牌照自动识别系统是智能交通系统的重要组成部分 主要包括图像采集 图像预处理 车牌定位 字符分割 字符识别等五个核心部分 并提出了一套基于MAT
  • html左边多级菜单导航栏,精美的多级侧边栏导航菜单jQuery插件

    这是一款基于bootstrap的精美多级侧边栏导航菜单jQuery插件 该导航菜单在bootstrap样式的基础上 通过jQuery来为导航菜单绑定菜单点击事件 生成非常漂亮的多级侧边栏导航菜单 使用方法 在页面中引入bootstrap样式
  • prometheus监控k8s kube-proxy target down

    prometheus kube proxy target down 解决 修改配置 kubectl edit cm kube proxy n kube system metricsBindAddress 0 0 0 0 10249 删除 k
  • 2050年全部人口的86%集中到城市,智慧城市的五项关键技术

    本文翻译至 http readwrite jp cities 32108 人口的城市化毫不停息 人们的住所越来越多地从地方移动到城市 到2050年为止预计发达国家人口的86 发展中国家人口的64 将住在城市 数量有限的城市要负担如此多的人口
  • 查看并设置Linux的IP地址

    ip addr 查看网卡分配情况 如发现IP地址为 127 0 0 1 这里要修改ip地址 修改IP地址方法 1 进入 etc sysconfig network scripts 注 不同版本ifcfg ens33文件名可能会不一样 2 修
  • Visual C++ MFC的图形绘制——常见问题汇总

    Visual C MFC的图形绘制 常见问题汇总 目录 一 常见问题 1 菜单界面制作 2 命令响应函数 3 添加私有变量 4 消息响应函数 二 后记 三 补充代码 一 常见问题 1 菜单界面制作 题目描述 新建一个单文档类型的MFC Ap
  • 别再写满屏的 if、else 了,试试策略模式

    你还在写满屏的 if else switch 之类的判断逻辑吗 栈长在开发人员的代码中看过太多这样的低级代码了 真的太 low 极不好维护 本文栈长就教你如何用策略模式干掉 if else switch 让你的代码更优雅 什么是策略模式 比
  • 一起自律打卡社群第3期

    如果你愿意 你可以变得更好 社群大家都知道是怎么回事 建这个群组主要就是互相鼓励 一起前进 不要在生活或工作学习中处于一种颓废的状态 干啥都提不上劲 对生活也没有多大的期望 其实都是懒散惯了 导致对生活缺少一种积极的能量 从而想伪躺平当个咸
  • 电脑系统重装后触控板用不了了(消失了)

    问题 win10系统重装后发现触控板用不了 消失了 如图 正常的情况应该如图下 造成这种情况的原因 1 可能是误删触控板驱动 2 可能是重装系统的时候触控板驱动没打上 3 可能是触控板因进水 撞击损坏 4 略 可能因素太多了 这次我主讲华硕
  • express+websocket实现线上聊天

    1 webSocket简介 WebSocket是一种通信协议 可在单个TCP连接上进行全双工通信 WebSocket使得客户端和服务器之间的数据交换变得更加简单 允许服务端主动向客户端推送数据 在WebSocket API中 浏览器和服务器
  • 静态类型推导

    前面说泛型的时候 提到了C 模板的实现方式是动态特性静态化 在实际情况中 这是一个提高效率的好办法 动态性的好处是灵活 开发简便 静态性的特性是效率高 编译期检查较好 因此很自然地就有一个问题 能不能各取所长 达到两全其美 应该说 在一定程