Golang_2_类型
二、类型
2.1 变量
静态类型语言,Go变量有固定的数据类型。==*只能改变变量值,无法改变类型。*==
类型转换或指针操作,可以用不同的方式修改变量值,但是并不意味改变了变量类型
定义 var
-
类型放在后面
-
变量会自动初始化为二进制零(zero value),避免出现不可预测行为
var x int
-
显示提供初始化值,由编译器推断
var y = false
-
一次可以定义多个变量以及初始化
var x, y int var a, s = 100, "abc" //编译器自动推断类型
-
建议用组的方式整理多行变量的定义
var ( x, y int a, s = 100, "abc" )
简洁模式 :=
x := 10
简洁模式有限制
- 定义变量,同时显式初始化
- 不能提供数据类型
- 只能用在函数内部
所以有时会犯以下错误,并没有改变全局变量,而是重新定义了一个同名局部变量
var x = 100
func main(){
fmt.Println(&x, x) // 全局变量
x := "abc"
fmt.Println(&x, x) // 重新定义和初始化同名的局部变量
}
有时候也会退化成赋值,前提:
- 是在同一作用域内
- 至少有一个新变量被定义
func main(){
x := 123
fmt.Println(&x, x)
x, y := 200, "abc" // 同域 + 新变量被定义
// x := 200 会报错,因为没有新变量被定义
fmt.Println(&x, y)
// 重新定义,括号内是另外一个域
//{
// x := 200
//}
}
多变量赋值
先计算完右边,再赋值到左边的变量
func main() {
x, y := 1, 2
x, y = y+3, x+2
fmt.Println(x, y)
}
未使用错误
未使用的局部变量会被当做错误
2.2 命名
有实际含义 + 易于阅读 的单词或字母组合
命名建议
- 由字母或下划线开始,由多个字母、数字、下划线组成
- 区分大小写
- 驼峰型
- 局部变量 优先使用 短名
- 不要使用保留关键字
- 不建议使用和 预定义常量、类型、内置函数相同的名字
- 专有名词大写,比如
escapeHTML
支持用汉字等Unicode字符命名,但不是最好的选择
var c int // count 用 c 代替
for i := 0; i < 10; i++ { // index 用 i 代替
c++
}
fmt.Println(c)
空标识符
作为忽略占位符使用,可作表达式左值,无法读取内容
x, _ := strconv.Atoi("12")
fmt.Println(x)
可用来临时规避编译器对未使用变量和导入包的错误检查
2.3 常量
表示恒定不变的值,常量值必须是可确定的 字符、字符串、数字或布尔值。称为 “只读”
const x, y int = 123, 0x32
const s = "hello world"
const c = '我'
const (
i, f = 1, 0.123
b = false
)
也是域内生效,域外可以被定义为其它同名常量
如果显示指定,需要保持两侧类型一致,且右侧需要在常量取值范围内
x int = -999
n = uint8(x) // -999超过uint8范围
uint 前面的u相当于 unsigned
int8 范围是
-128~127
常量数组中如不指定类型和初始化值,则与上一行非空常量右值(表达式文本)相同
const (
x uint16 = 120
y
s = "abc"
z
)
fmt.Printf("%T %v\n", y, y)
// 输出 uint16 和 120
fmt.Printf("%T %v\n", z, z)
// 输出 string 和 abc
枚举
枚举类型是某类数据可能取值的集合,如一周内星期可能取值的集合为:
{ Sun, Mon, Tue, Wed, Thu, Fri, Sat}
用枚举类型定义的枚举变量只能取集合中的某一元素值
由于枚举类型是导出数据类型,因此,必须先定义枚举类型,然后再用枚举类型定义枚举型变量
Go没有明确意义上的枚举定义,但是可以借助iota标识符实现一组自增常量值来实现。
const (
x = iota // 0
y // 1
z // 2
)
const (
_ = iota // 0
KB = 1 << (10 *iota) // 1 << (10 * 1)
MB // 1 << (10 * 2)
GB // 1 << (1 * 3)
)
还可以在多常量定义中使用多个itoa
,各自计数,只需要保证每行向量列数量相同即可
const (
_, _ = iota, iota*10 // 0, 0*10
a, b // 1, 1*10
c, d // 2, 2*10
)
如果iota
被中断,那么需要显示得被恢复,且恢复后,==按照行序自增==,而不是按上一取值自增
const (
a = iota // 0
b // 1
c = 100 // 100
d // 100 默认和上值相同
e = iota // 4 根据行自增,而不是从2开始
f // 5
)
自增值默认数据类型为int
,可显式指定类型
const (
a = iota // int
b = iota // int
c float32 = iota // float 32
d = iota // int 不显式指定,则还是默认int
)
fmt.Printf("%v %v %v %v\n", a, b, c, d)
fmt.Printf("%T %T %T %T", a, b, c, d)
// 0 1 2 3
// int int float32 int
建议使用自定义类型实现用途明确的枚举类型,但并不能限定取值范围
type color byte
const (
black color = iota
red
blue
)
func test(c color) {
println(c)
}
func main() {
test(red)
test(100)
x := 2
test(x) // 错误:不能使用int类型作为test的函数类型
}
展开
var x = 0x100
const y = 0x200
func main() {
fmt.Println(&x, x)
fmt.Println(&y, y) // 报错,can't take the adress of y
}
常量通常会被编译器在 预处理阶段 直接展开,作为指令数据使用(变量是在运行期间分配储存内存(非优化状态))
数字常量不会分配存储空间,无须像变量一样通过内存来寻址,因此无法获取地址
2.4 基本类型
基本类型表间书本P28,下面罗列几个常见的
类型 | 长度 | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | uint8 |
int, uint | 4, 8 | 0 | 默认的整数类型,根据平台,32或64位 |
int8, uint8 | 1 | 0 | -128~127 , 0~255 |
int16, uint16 | 2 | 0 | -32768~32767, 0~65535 |
int32, uint32 | 4 | 0 | -21亿~21亿 ,0~42亿 |
int64, uint64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | 默认的浮点数类型 |
complex64 | 8 | ||
complex128 | 16 | ||
rune | 4 | 0 | Unicode Code Point, int 32 |
uintptr | 4, 8 | 0 | 足以存储指针的uint |
string | "” | 字符串,默认空,而不是NULL | |
array | 数组 | ||
struct | 结构体 | ||
function | nil | 函数 | |
interface | nil | 接口 | |
map | nil | 字典,引用类型 | |
slice | nil | 切片,引用类型 | |
channel | nil | 通道,引用类型 |
math库
定义了各类数字类型的取值范围
func main() {
a, b, c := 100, 0144, 0x64
fmt.Println(a, b, c) // 默认按照十进制输出, 100 100 100
fmt.Printf("0b%b, %#o, %#x\n", a, a, a) // 二进制、八进制、十六进制
// 0b1100100, 0144, 0x64
fmt.Println(math.MinInt8, math.MaxInt8) // -128 和 127
}
strconv
实现在不同进制(字符串)之间转换
func main() {
a, _ := strconv.ParseInt("1100100", 2, 32)
b, _ := strconv.ParseInt("0114", 8, 32)
c, _ := strconv.ParseInt("64", 16, 32)
fmt.Println(a, b, c) // 输出100 76 100
fmt.Println("0b"+strconv.FormatInt(a, 2)) // 0b1100100
fmt.Println("0"+strconv.FormatInt(a, 8)) // 0144
fmt.Println("0x"+strconv.FormatInt(a, 16)) // 0x64
}
==别名==
语言规范中,专门有两个别名
byte alias for unit8
rune alias for int32
别名类型无须转换,可以直接赋值,比如下方的 uint8
可以直接用 byte
func test(x byte){
fmt.Println(x)
}
func main() {
var a byte = 0x11
var b uint8 = a
var c uint8 = a + b
test(c)
}
但是底层一样的结构不一定属于别名,比如 int
和 int64
,也需要显式转换
func add(x, y int) int {
return x + y;
}
func main() {
var x int = 100
var y int64 = x // 错误 cannot use x (type int) as type int64 in assignment
add(x, y) // 错误 use y (type int64) as type int in argument to add
}
2.5 引用类型
引用类型特指 slice、map、channel
这三种预定义类型
除了分配内存,它们还须要初始化一系列属性,诸如指针、长度,甚至包括哈希分布、数据队列等
引用类型需要使用make函数创建,编译器会将make转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化
package main
import "fmt"
func mkslice() []int {
s := make([]int, 0, 10)
s = append(s, 100)
return s
}
func mkmap() map[string]int {
m := make(map[string]int)
m["a"] = 1
return m
}
func main(){
m := mkmap()
fmt.Println(m["a"])
s := mkslice()
fmt.Println(s[0])
}
也可以使用初始化表达式,编译生成的指令基本相同
new 也可以作为引用类型分配内存,但是并不是完整创建。以字典(map)为例,它仅分配了字典类型本身(实际就是个指针)所需内存,并没有分配键值存储内存,无法正常工作
func main(){
p := new(map[string]int)
m := *p
m["a"] = 1
fmt.Println(m) // 运行期错误
}
2.6 类型转换
Go强制要求使用显式类型转换
a := 10
b := byte(a)
c := a + int(b) // 混合类型表达式要求类型一致
语法歧义
如果转换目标是指针、单向通道或没有返回值的函数类型,那么必须使用括号,以避免造成语法分解错误
(*int)(p) // *(int(p))
==P33==
2.7 自定义类型
使用关键字type
type flags byte
const (
read flags = 1 << iota
write
exec
)
func main(){
f := read | exec
fmt.Printf("%b\n", f) // 输出 101
}
type可以合并成组
package main
import "fmt"
func main(){
type (
user struct {
name string
age uint8
}
event func(string) bool
)
// 使用结构体类型
u := user{"Tom", 20}
fmt.Println(u)
// 使用结构体内方法
var f event = func(s string) bool {
fmt.Println(s)
return s != ""
}
f("abc")
}
二者有相同的底层数据结构,二者中间不存在任何关系,仍然时两种不同的类型。除了操作符,自定义类型不会继承基础类型的其他信息。不能用作别名,不能隐式转换,不能直接用于比较表达式
type data int
var d data = 10
var x int = d // 出错,两种类型
fmt.Println(d == x) // 出错,两种类型不能进行比较
==未命名类型==
数组、切片、字典、通道等类型与具体元素类型或长度等属性有关,所以称作未命名类型。
当然可以使用type为其提供具体名称,使改变为命名类型