Golang_2_类型

二、类型

2.1 变量

静态类型语言,Go变量有固定的数据类型。==*只能改变变量值,无法改变类型。*==

类型转换或指针操作,可以用不同的方式修改变量值,但是并不意味改变了变量类型

定义 var

  1. 类型放在后面

  2. 变量会自动初始化为二进制零(zero value),避免出现不可预测行为

    var x int
    
  3. 显示提供初始化值,由编译器推断

    var y = false
    
  4. 一次可以定义多个变量以及初始化

    var x, y int
    var a, s = 100, "abc" //编译器自动推断类型
    
  5. 建议用组的方式整理多行变量的定义

    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) // 重新定义和初始化同名的局部变量
}

有时候也会退化成赋值,前提:

  1. 是在同一作用域内
  2. 至少有一个新变量被定义
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)
}

但是底层一样的结构不一定属于别名,比如 intint64 ,也需要显式转换

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为其提供具体名称,使改变为命名类型


分享: