前言:
学习任何一门语言都需要掌握它都数据类型,就像java语言中包含8种基本数据类型、有变量常量之分,go语言也如此。文档主要记录《GO语言学习笔记》一书都学习笔记、demo,以便后期回顾学习。
2.类型篇
2.1变量
go语言用关键字var定义变量,跟java不同,go语言类型放在变量名后。如果显示提供初始化值,则可以省略变量类型,由编译器推断。同时,可一次定义多个变量,包括用不同初始值定义不同类型。
1 | var x int //定义变量,指定类型为int,如果没有初始化值,运行时会自动初始化为二进制零值 |
除var关键字外,还可以使用更加简短的变量定义和初始化语法。
1 | func main(){ |
简短模式定义变量时,需要注意以下限制:
- 定义变量,同时显示初始化
- 不能提供数据类型
- 只能用在函数内部
2.2命名
同其他编程语言一样,对变量、常量、函数、自定义类型进行命名,通常优先选用有实际含义,易于理解对字母或单词组合。
命名建议:
- 以字母或下划线开始,由多个字母、数字和下划线组合而成
- 区分大小写
- 使用驼峰命名格式
- 局部变量优先使用短名
- 不可以使用go语言保留的关键字
- 不建议使用与预定义常量、类型、内置函数相同的名字
- 专有名词通常会全部大写,例如HTML
go语言没有java语言中的访问修饰符(public、protected、private),因此采用首字母大小写来决定其作用域。首字母大写的为导出成员,可被包外引用,首字母小写仅能在包内访问。
空标识符
和Python类似,Go也有哥名为“_”的特殊成员。通常作为忽略占位符使用,可作为表达式左值,用“_”接收的值,表示舍弃不用,故无法读取其内容。
1 | import "strconv" |
2.3常量
同java语言一样,go语言也有常量,常量用来表示运行时恒定不变的值。常量值必须是编译期可确定的字符、字符串、数字或布尔值。可指定常量类型,或由编译器通过初始化值推断。
go语言常量值由const修饰:
1 | const x,y int=123,0x22 //定义两个常量值x,y 其中y为16进制表示 |
可在函数代码块中定义常量,不曾使用的常量不会引发编译错误(变量定义未使用,编译器会报错)。
1 | func main(){ |
如果显示指定类型,必须确保常量左右值类型一致,需要时可以做显示类型转换。右值不能超出左边类型取值范围,否则报溢出错误。常量值也可以是某些编译器能计算出结果的表达式,如unsafe.Sizeof、len、cap等。
1 | import "unsafe" |
在常量组中,如果不指定常量的类型和初始化值,则与上一行非空常量右值相同。
1 | import "fmt" |
输出结果:
1 | uint16,120 string,abc |
关于占位符的介绍,请参考:Go语言fmt格式“占位符”
枚举:
Go并没有明确意义上的enum定义,不过可借助iota标识符实现一组自增常量值来实现枚举类型。
1 | const( |
自增作用范围为常量组。可在多常量定义中使用多个iota,它们各自单独计数,只须确保组中每行常量的列数量相同即可。
1 | const( |
如中断iota自增,则必须显示恢复。且后续自增值按行序递增,而非C语言 enum那样按上一取值递增。
1 | const( |
自增默认数据类型为int,可显示指定类型
1 | const( |
常量除“只读”外,和变量究竟有什么不同?
1 | var x = 0x100 |
不同于变量在运行期分配存储内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。数字常量不会分配存储空间,无法像变量那样通过内存寻址来取值,因此无法获取地址。
2.4基本类型
类型 | 长度(字节数) | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | 布尔型的值只可以是常量true或false,默认值false |
byte | 1 | 0 | 类似uint8 |
int | 4或8 | 0 | 默认整数类型,根据目标平台,32位或64位 |
uint | 4或8 | 0 | 默认无符号整数类型,依据目标平台,32位或64位 |
int8,uint8 | 1 | 0 | int8取值范围-128~127,uint8取值范围0-255 |
int16,uint16 | 2 | 0 | int16取值范围-32768 |
int32,uint32 | 4 | 0 | int32取值范围-2147483648 |
int64,uint64 | 8 | 0 | int64取值范围-9223372036854775808 ~ 9223372036854775807,uint64取值范围0 ~ 18446744073709551615 |
float32 | 4 | 0.0 | 32位浮点数 |
float64 | 8 | 0.0 | 64位浮点数(默认浮点数类型) |
complex64 | 8 | 32位实数和虚数 | |
complex128 | 16 | 64位实数和虚数 | |
rune | 4 | 0 | Unicode Code Point,类似int32 |
uintptr | 4,8 | 0 | 无符号整型,用于存储指针 |
string | “” | 字符串,默认值为空字符串而非NULL | |
array | 数组 | ||
struct | 结构体 | ||
func | nil | 函数类型 | |
interface | nil | 接口 | |
map | nil | 字典,引用类型 | |
slice | nil | 切片,引用类型 | |
channel | nil | 通道,引用类型 |
支持八进制、十六进制以及科学记数法。标准库math定义了各数字类型的取值范围。
1 | import("fmt" "math") |
输出结果:
1 | 100,100,100 |
标准库strconv可以在不同进制(字符串)间转换
1 | a, err := strconv.ParseInt("1100100",2,32) |
输出结果:
1 | 100,100,100 |
别名:在官方的语言规范中,专门提到两个别名。byte 是 uint8别名;rune是int32别名。别名类型无须进行类型转换,可直接赋值。
1 | func test(x byte){ |
但这并不表示,拥有相同底层结构但就属于别名。就算在64位平台上int和int64结构完全一致,也分属不同类型,须显示转换。
1 | func add(x,y int) int { |
2.5引用类型
所谓引用类型(reference type) 特指 slice、map、channel这三种预定义类型。
相比数字、数组等类型,引用类型拥有更复杂但存储结构。除分配内存外,它们还须初始化一系列属性,诸如指针、长度,甚至包括哈希分布、数据队列等。
内置函数new按指定类型长度分配零值内存,返回指针,并不关心类型内部构造和初始化方式。而引用类型则必须使用make函数创建,编译器会将make转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。
1 | //slice |
2.6类型转换
- 隐式转换造成的问题远大于它带来的好处。。。
除常量、别名类型以及未命名类型外,Go强制要求使用显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义。1
2
3a := 10
b := byte(a)
c := a + int(b) //混合类型表达式,必须确保类型一致,也可写成 c := byte(a) + b - 同样不能将非bool类型结果当作true/false使用
1
2
3
4
5
6func main(){
x := 100
var b bool = x //错误:cannot use x(type int)as type bool in assignment
if x { //错误:non-bool x(type int)used as if condition
}
} - 语法歧义
如果转换的目标是指针、单向通道或没有返回值的函数类型,那么必须使用括号,以免造成语法分解错误。1
2
3
4
5
6func main(){
x := 100
//p := *int(&x) //错误:Invalid indirect of 'int(&x)' (type 'int')
//Cannot convert expression of type *int to type int
p := (*int)(&x) //转换的目标是指针,必须使用括号
}
2.7自定义类型
使用关键字type定义用户自定义类型,包括基于现有基础类型创建,或者是结构体、函数类型等。
1 | import ("fmt") |
输出:
1 | 101 |
和var、const类似,多个type定义可合并成组,可在函数或代码块内定义局部类型。
1 | func main(){ |
未命名类型
与有明确标识符等bool、int、string等类型相比,数组、切片、字典、通道等类型与具体元素类型或长度等属性相关,故称作未命名类型(unnamed type)。当然,可用type为其提供具体名称,将其改变为命名类型(named type)。
具有相同声明等未命名类型被视为同一类型。
- 具有相同基类型的指针
- 具有相同元素类型和长度的数组(array)
- 具有相同元素类型的切片(slice)
- 具有相同健值类型的字典(map)
- 具有相同数据类型及操作方向的通道(channel)
- 具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体(struct)
- 具有相同签名(参数和返回值列表,不包括参数名)的函数(func)
- 具有相同方法集(方法名、方法签名,不包括顺序)的接口(interface)
容易被忽视的是struct tag,它也属于类型组成部分,而不仅仅是元数据描述。
1 | func main(){ |