类型系统
程序语言需要提供类型,是因为用它编写的程序要解决的问题会涉及多种多样的数据。类型可以确认一个值或者一组值具有特定的意义和目的(虽然某些类型,如抽象类型和函数类型,在程序运行中,可能不表示为值)。
类型是对内存的抽象,不同的类型会有不同的内存布局和内存分配策略。
类型带来的问题是相同的功能因为类型的不同,需要重复写针对不同类型的代码。当然,这个问题可以使用泛型来解决。
类型系统用于定义如何将编程语言中的数值和表达式归类为许多不同的类型,如何操作这些类型,这些类型如何互相作用。
类型系统的出现主要是对容易混乱的操作加上严格的限制,以避免代码以无效的数据使用方法编译或运行。例如:整数运算不可以作用于字符串;指针操作不可用于整数上等等。
当我们看待一门编程语言的时候,言必谈之类型系统(type system)。它到底是静态类型的(static typing),还是动态类型的(dynamic typing),是显式类型的(explicit typing),还是隐式类型的(implicit typing)。类型检查(type check)是较强的(stronger),还是较弱的(weaker)。它是否支持高阶类型(high-order type),是否支持递归类型(recusive type),是否支持子类型(subtype),是否支持多态(polymorphism)。
类型系统在各种语言之间有非常大的不同,也许,最主要的差异存在于编译时期的语法,以及运行时期的操作实现方式。
根据类型检查的时机,编程语言分为静态类型和动态类型。静态类型又分为显示类型和隐式类型。
根据类型检查的严格程度,编程语言分为强类型和弱类型。强弱是相对的。
静态类型 VS 动态类型
静态类型
静态类型指的是编译器在编译期间(compile time)执行类型检查,即编译时确定类型
。在声明了一个变量之后,一般不能改变它的类型(当然弱类型的静态语言可以强制类型转换和隐式类型转换,这个详见强弱类型部分)。使用数据之前,必须先声明数据类型(int ,float,double等)。相当于使用之前首先要为它们分配好内存空间。
显式类型 VS 隐式类型
静态类型可以分为两种:
如果类型是语言语法的一部分,是显式类型(explicitly typed)。比如:C语言需要明确的指定int a,int b这样的变量类型
如果类型通过编译时推导,是隐式类型(implicity typed), 比如Ocaml和Haskell,就不用明确的写出来,而是编译时推导出来的。
动态类型
动态类型指的是编译器(虚拟机)在运行期间(runtime)执行类型检查,即运行时确定类型
。能够随时改变它的类型(一般需要运行时虚拟机支持)。
动态类型语言必然有一堆诸如is_array()、is_int()、is_string()、typeof()这样的运行时类型检查函数。
两者比较
类型检查时机的区别
静态类型指的是编译器在编译期间执行类型检查(因为类型错误而不能做的事情是语法错误)。编译的时候编译器就知道每一个变量的类型,可以产生优化过后的机器码,运行速度快
。
动态类型指的是编译器(虚拟机)在运行期间执行类型检查(因为类型错误而不能做的事情是运行时错误)。编译器和解译器减少进行检查,并减少解析代码,编译器和解译器可以更快速的运作。从而减少编辑-编译-测试-除错的周期,开发效率高
。但由于无法提前发现类型问题,不可控因素大,当代码量大了之后,由于类型问题出错的情况比较多。
读写代码难易度的区别
静态类型一般需要写明类型,因此写代码时会写的多一些,另外时不时需要做类型转换,而C++这种甚至能给你4种cast。但是读代码的时候就比较容易看出来变更的类型,类型+变量名基本就能确定含义了。写难读易
动态类型一般不需要写明类型,动态类型推导省掉了写时显示申明类型的麻烦,写代码时轻松些。但是读代码时就稍累了,不容易明确含义,而且有些一直要到运行时才给检查,灵活是灵活了,万一写错了不运行到就不会知道。写易读难
强类型 VS 弱类型
弱/强类型指的是语言类型系统的类型检查的严格程度
弱类型相对于强类型来说类型检查更不严格,比如说允许隐式类型转换,允许强制类型转换等等。强类型语言一般不允许这么做
类型转换
类型转换(英语:type conversion)是指将数据从一种类型转换到另一种类型的过程。一个简单的例子是将整数转换成浮点数。
类型转换具有两种形式:显式类型转换和隐式类型转换。
显示类型转换指利用强制类型转换运算符手动进行转换,又称为强制类型转换。
隐式类型转换指数据的类型的转换通常是由编译系统自动进行的,不需要人工干预,又称为自动类型转换。
各类型语言代表
编程语言本质上帮助程序员屏蔽底层机器代码的实现,而让我们更加关注于业务逻辑代码。但是因为编程语言作为机器代码和业务逻辑的粘合层,是让程序员可以控制更多底层的灵活性,还是屏蔽底层细节,让程序员更多关注业务逻辑,这是很难两全的事。
所以,不同的语言在设计上都会做相应的取舍。比如:C语言偏向于让程序员可以控制更多的底层细节,而Java、Python等则让程序员更多的关注业务的实现。而C++则两者都想要,导致语言在设计上的复杂性。
类型 | 代表 |
---|---|
无类型 | 汇编 |
静态类型、弱类型 | C / C++ |
静态类型、强类型 | Java / C# / Golang |
动态类型、弱类型 | Perl / PHP |
动态类型、强类型 | Python / Scheme |
静态显式类型 | 静态显式类型 |
静态隐式类型 | 静态隐式类型 |
各语言数据类型
只写了我会的语言,一个静态一个动态
若一个变量的值的改变会影响另一个变量的值,此类型属于值类型。值类型传副本。
若一个变量的值的改变不会影响另一个变量的值,此类型属于引用类型。引用类型传指针
go
值类型:整型(int、int8、int16、int32、int64、uint、uint8(即byte)、uint16、uint32、uint64、uintptr(同指针))、浮点型(float32、float64)、字符串(String)、布尔型(Boolean)、数组(Array)、指针、结构体(struct)、接口(interface)。
引用类型:切片(slice)、字典(map)、通道(channel )三种。(除此之外,函数类型也是引用类型的)
js
值类型:数字(Number)、字符串(String)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol(ES6引入,表示独一无二的值)。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
变量
从应用层来说,变量是存储数据的容器。不同类型、不同长短大小的数据用不同类型的变量来存储(int类型变量存储数字类型数据,string类型变量存储字符串类型数据;int8、int16都用来存储数字类型数据,但int16存储的容量比int8大)。
比如水杯用来存水;书柜用来放书。水杯和书柜就是两个不同类别的变量。水杯又有不同容量的区别。
从底层来说,变量相当于是对一块数据存储空间的命名。程序可以通过定义一个变量来申请一块数据存储空间,之后可以通过引用变量名来使用这块存储空间。
变量跟数据类型息息相关。对于动态类型的语言(如php、javascript),变量的类型是可变的,即变量的类型是由它存储的值决定的;对于静态类型的语言(如c、go),变量的类型一旦声明就不可变。
//js代码
var test = 1; //test为number类型
var test = "hello"; //test为string类型
//go代码
var test = 1 //test为int类型
var test = "hello" //报错:a redeclared in this block
JavaScript 变量均为对象。当您声明一个变量时,就创建了一个新的对象。
js变量声明提升
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部,这称为变量声明提升。
如果写程序时忘了变量声明提升,可能会导致一些莫名的错误,为了避免js的变量声明提升,通常我们在每个作用域开始前声明这些变量,这也是正常的 JavaScript 解析步骤,易于我们理解。
变量生命周期
变量生命周期也就是变量的作用域,变量只能在自己的生命周期里起作用。一般,各编程语言都分为局部变量和全局变量。
js中,以函数为边界,函数外变量为全局变量,函数内变量为局部变量。全局变量在它声明时初始化,在页面关闭后销毁。局部变量在它声明时初始化,在函数执行完毕后销毁。js在ES6中定义了块级作用域,使用let关键字来声明变量。
go中,以代码块为边界,代码块外变量为全局变量,代码块内变量为局部变量。全局变量在它声明时初始化,在页面关闭后销毁。局部变量在它声明时初始化,在代码块执行完毕后销毁。