每种编程语言都设计了自己的数据类型系统,数据类型的本质是什么?

我上大学才开始接触计算机的概念(太晚了),老师说现代计算机叫存储程序式计算机(冯诺依曼体系结构),现在我更直白的理解就是计算机是由CPU + IO设备组成的。IO设备就是数据,CPU就是对这些数据进进出出一顿算。我们常说的内存是IO设备的一种,IO设备种类繁多不好说清楚问题,这里我们假设计算机只有CPU和内存;CPU不停从内存取数据,再将计算结果写到内存,这个过程乐此不疲的进行着,而且速度极快。

编程语言的数据类型本质就是内存空间。

当然一样的内存空间,在不同CPU体系架构下意义可能是不一样的,要看约定规则是什么。

有了内存空间和解析规则,里面由bit位(0或者1)组合表示的数据就有意义了。他们可能用来表示数值、指令、或者其它内存的地址等。

PS:不是所有的值都有地址,因为可能只存在于CPU寄存器中;但是所有的变量都有地址。

计算机内存管理相关知识,参考这篇文章,很详细:https://www.jianshu.com/p/0851a951a46e

Go语言的数据类型

按一般感观分成四大类:

  1. 基础类型:boolean、number、string
  2. 聚合类型:array、struct
  3. 引用类型:pointer、slice、map、channel、func
  4. 接口类型:interface

除了基础数据类型number之外,其它类型都可以看做是基础数据类型的派生类型。

如果按变量存放的数据意义,1、2为值类型,3、4为引用类型。

常量

所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go语言程序中,常量可定义为数值、布尔值或字符串等类型。

Boolean

布尔值的类型为bool,值是truefalse,默认为false

数值

数值(number)分整型和浮点型;整型又分有符号和无符号两种。同时支持intuint,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go语言里面也有直接定义好位数的类型:rune,int8,int16,int32,int64byte,uint8,uint16,uint32,uint64。其中runeint32的别称,byteuint8的别称。

类型 描述
uint 32位或64位
uint8 无符号 8 位整型 (0 到 255)
uint16 无符号 16 位整型 (0 到 65535)
uint32 无符号 32 位整型 (0 到 4294967295)
uint64 无符号 64 位整型 (0 到 18446744073709551615)
int 32位或64位
int8 有符号 8 位整型 (-128 到 127)
int16 有符号 16 位整型 (-32768 到 32767)
int32 有符号 32 位整型 (-2147483648 到 2147483647)
int64 有符号 64 位整型 (-9223372036854775808到9223372036854775807)
byte uint8 的别名 (type byte = uint8)
rune int32 的别名 (type rune = int32),表示一个 unicode
uintptr 类似uint ;底层编程时使用,特别是Go与C函数库或操作系统接口交互的地方
float32 IEEE-754 32位浮点型数
float64 IEEE-754 64位浮点型数
complex64 32 位实数和虚数
complex128 64 位实数和虚数

字符串

字符串都是采用UTF-8字符集编码。字符串是用一对双引号(")或一对反引号(`)括起来定义,它的类型是string。

数组

数据表示某类型固定长度的空间,一旦声明长度不可改变。

1
2
3
4
5
6
var array [5]int
array =  [5]int{1,2,3,4,5}
array := [5]int{1,2,3,4,5}
array := [...]int{1,2,3,4,5}
array := [5]int{0,1,0,4,0}
array := [5]int{1:1,3:4}

结构

1
2
3
4
type Apple struct {
	price int
	color int
}

指针

1
2
var ppp *Apple
ppp = new(Apple)

切片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sliceInt8 := make([]int8, 16, 32)

// 切片 和 切片指针
var ls *[]int
ls = new([]int)

*ls = append(*ls, 1, 2, 3, 4, 5)
logx.Infof("len: %d, cap: %d", len(*ls), cap(*ls))

*ls = append(*ls, 6, 7, 8)
logx.Infof("len: %d, cap: %d", len(*ls), cap(*ls))

*ls = (*ls)[0:0]
logx.Infof("len: %d, cap: %d", len(*ls), cap(*ls))

映射

1
mapTest := make(map[string]int, 64)

通道

1
2
chainNoBuf := make(chan int, 1)
chainBuf := make(chan int, 100)

函数

1
2
3
4
5
6
7
8
9
// 函数
func printName(name string) {
    fmt.Printf("Name is: %s", name)
}

// 方法
func (u *User) printName() {
    fmt.Printf("Name is: %s", u.name)
}

接口

1
2
3
type Food interface {
	canEat() bool
}

error类型

内置error类型是一个interface,专门用来处理错误信息,Go的package里面还专门有一个包errors来处理错误。

1
2
3
4
5
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

值类型和引用类型

值类型

变量直接存储的值,内存通常在栈中分配。

引用类型

指变量存储的是实际数据的描述信息,这段信息被称作标头(header)。标头中通常有实际值的起始地址指针,当前大小、最大容量等信息。实际值通常在堆中分配,当实际值没有任何标头引用的时候,其对应的内存空间将被GC自动回收。

每个引用类型创建的标头值是包含一个指向底层数据结构的指针。每个引用类型还可能包含一组独特的字段,用于管理底层数据结构。因为标头值是为复制而设计的,所以永远不需要共享一个引用类型的值。标头值里包含一个指针,因此通过复制来传递一个引用类型的值的副本,本质上就是在共享底层数据结构。

引用类型声明变量通常有两种方式:make 或 newmake用于内建类型(map、slice、channel等)的内存分配;new实际可以用于各种类型的内存分配;按照Go的约定,用make和new声明的变量都会自动初始化为各自基础数据类型的零值。对声明引用类型变量来说:

  • make只能用在(map|channel|slice),返回类型标头,标头至少包含一个值地址指针,有的还包含其它描述字段。
  • new返回的是类型指针,指针指向类型值的起始地址

看一下源代码的定义就知道了:

1
2
3
// builtin.go
func make(t Type, size ...IntegerType) Type
func new(Type) *Type

(完)