zoukankan      html  css  js  c++  java
  • 《Go学习笔记 . 雨痕》类型

    一、基本类型

    清晰完备的预定义基础类型,使得开发跨平台应用时无须过多考虑符合和长度差异。

    类型长度默认值说明
    bool 1 false  
    byte 1 0 uint8
    int, uint 4, 8 0 默认整数类型,依据目标平台,32 或 64 位
    int8, uint8 1 0 -128 ~ 127,0 ~ 125
    int16, uint16 2 0 -32,768 ~ 32,767,0 ~ 65,535
    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, int32
    uintptr 4, 8 0 足以存储指针的 uint
    string   "" 字符串,默认值为空字符串,而非 NULL
    array     数组
    struct     结构体
    function   nil 函数
    interface   nil 接口
    map   nil 字典,引用类型
    slice   nil 切片,引用类型
    channel   nil 通道,引用类型

    支持八进制、十进制以及科学计数法。标准库 math 定义了各数字类型的取值范围。

    import (
    	"fmt"
    	"math"
    )
    
    func main()  {
    	a, b, c := 100, 0144, 0x64
    
    	fmt.Println(a, b, c)
    	fmt.Printf("0b%b, %#o, %#x
    ", a, a, a)
    
    	fmt.Println(math.MinInt8, math.MaxInt8)
    }

    输出:

    100 100 100
    0b1100100, 0144, 0x64
    
    -128 127

    标准库 strconv 可在不同进制(字符串)间转换。

    import (
    	"strconv"
    )
    
    func main()  {
    	a, _ := strconv.ParseInt("1100100", 2, 32)
    	b, _ := strconv.ParseInt("0144", 8, 32)
    	c, _ := strconv.ParseInt("64", 16, 32)
    
    	println(a, b, c)
    
    	println("0b" + strconv.FormatInt(a, 2))
    	println("0" + strconv.FormatInt(a, 8))
    	println("0x" + strconv.FormatInt(a, 16))
    }

    输出:

    100 100 100
    0b1100100
    0144
    0x64

    使用浮点数时,须注意小数位的有效精度,相关细节可参考 IEEE-754 标准。

    func main()  {
    	var a float32 = 1.1234567899	// 注意:默认浮点数类型是 float64
    	var b float32 = 1.12345678
    	var c float32 = 1.123456781
    
    	println(a, b, c)
    	println(a == b, a == c)
    	fmt.Printf("%v %v, %v
    ", a, b, c)
    }

    输出:

    +1.123457e+000 +1.123457e+000 +1.123457e+000
    true true
    1.1234568 1.1234568, 1.1234568

    别名

    在官方的语言规范中,专门提到 两个 别名。

    byte        alias for unit8
    rune        alias for unit32

    别名类型无须转换,可直接赋值。

    func test(x byte) {
    	println(x)
    }
    
    func main()  {
    	var a byte = 0x11
    	var b uint8 = a
    	var c uint8 = a + b
    
    	test(c)
    }

    但这并不表示,拥有相同底层结构的就属于别名。就算在 64位 平台上 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)			// 错误:cannot use y (type int64) as type int in argument to add
    }
    

    二、引用类型

    所谓引用类型(reference type),特指 slice 、map 、channel 这三种预定义类型。相比 数字 、数组 等类型,引用类型 拥有更复杂的存储结构。除分配内存外,他们还须初始化一系列属性,诸如 指针 、长度 ,甚至包括哈希分布、数据队列等。

    内置函数 new() 按指定类型长度分配零值内存,返回指针,并不关心类型内部结构和初始化方式。而 引用类型 则必须使用 make() 函数创建,编译器会将 make() 转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。

    // test.go 文件

    package main
    
    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()
    	println(m["a"])
    
    	s := mkslice()
    	println(s[0])
    }

    输出:

    $ go build -gcflags "-l"  // 禁用函数内联
    
    $ go tool objdump -s "main.mk" test
    
    TEXT main.mkslie(SB) test.go
        CALL runtime.makeslice(SB)
    
    TEXT main.mkmap(SB) test.go
        CALL runtime.makemap(SB)    

    除 new() / make() 函数外,也可用 初始化表达式,编译器生成的指令基本相同。

    当然,new() 函数也可为引用类型分配内存,但这是不完整创建。以字典(map)为例,它仅分配了字典类型本身(实际就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作。

    import "fmt"
    
    func main() {
    	p := new(map[string]int)  // 函数 new 返回指针
    	m := *p
    	m["a"] = 1  // 报错:panic: assignment to entry in nil map [运行期错误]
    	fmt.Println(m)
    }
    

    三、类型转换

    隐式转换造成的问题远大于它带来的好处。

    常量别名类型 以及 未命名类型 外,Go 强制要求使用显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义。

    func main() {
    	a := 10
    	b := byte(a)
    	c := a + int(b) // 混合类型表达式必须确保类型一致
    	fmt.Println(c)
    }

    同样不能讲 非bool 类型结果当作  true/false 使用。

    func 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
    	}
    }

    语法歧义

    如果转换的目标 指针单向通道没有返回值的函数 类型,那么必须使用 括号(),以避免造成语法分解错误。

    func main() {
    	x := 100
    	p := *int(&x)	// 报错:cannot convert &x (type *int) to type int
    					// invalid indirect of int(&x) (type int)
    	println(p)
    }

    正确的做法是用括号,让编译器将 *int 解析为指针类型。

    (*int)(p)            --> 如果没有括号 -->      *(int(p))
    (<-chan int)(c)                               <-(chan  int(c))
    (func())(x)                                   func() x

    func() int (x)       --> 有返回值的函数类型可省略括号,但依然建议使用。
    (func() int) (x)     使用括号后,更易阅读

    四、自定义类型

    使用关键字 type 定义用户自定义类型,包括基于现有基础类型创建,或者是 结构体 、函数类型 等。

    type flags byte
    
    const (
    	read flags = 1 << iota
    	write
    	exec
    )
    
    func main() {
    	f := read | exec
    	fmt.Printf("%b
    ", f)  // 输出二进制标志位
    }

    输出:

    101

    和 var 、const 类似,多个 type 定义可以合并成组,可在 函数 或 代码块内定义局部类型。

    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 {
    		println(s)
    		return s != ""
    	}
    
    	f("abc")
    }

     输出:

    {Tom 20}
    abc

    即便指定了基础类型,也只表明它们有相同底层数据结构,两者间不存在任何关系,属完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视作别名,不能隐式转换,不能直接用于比较表达式。

    func main() {
    	type data int
    	var d data = 10
    
    	var x int = d  // 错误:annot use d (type data) as type int in assignment
    	println(x)
    
    	println(d == x) // 错误:invalid operation: d == x (mismatched types data and int)
    }

    未命名类型

    与有明确标识符的 bool 、int 、string 等类型相比,数组 、切片 、字典 、通道 等类型与具体元素类型或长度等属性有关,故称作 未命名类型(unnamed type)。当然,可用 type 为其提供 具体名称,将其改变为 命名类型(named type)

    具有相同声明的未命名类型视作同一类型。

    • 具有相同基类型的指针;
    • 具有相同元素类型 和 长度的数组(array);
    • 具有相同元素类型的切片(slice);
    • 具有相同键值类型的字典(map);
    • 具有相同数据类型及操作方向的通道(channel);
    • 具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体(struct);
    • 具有相同签名(参数和返回值列表,不包括参数名)的函数(func);
    • 具有相同方法集(方法名、方法签名,不包括顺序)的接口(interface);

    容易被忽视的是 struct tag,它也属于类型组成部分,而不仅仅是元数据描述。

    func main() {
    	var a struct { // 匿名结构类型
    		x int `X`
    		s string `S`
    	}
    
    	var b struct {
    		x int
    		s string
    	}
    
    	b = a // 错误:cannot use a (type struct { x int "X"; s string "S" }) as type struct { x int; s string } in assignment
    
    	fmt.Println(b)
    }

    同样,函数的参数顺序也属签名组成部分。

    func main() {
    	var a func(int, string)
    	var b func(string, int)
    
    	b = a // 错误:cannot use a (type func(int, string)) as type func(string, int) in assignment
    
    	b("s", 1)
    }

    未命名类型转换规则:

    • 所属类型相同;
    • 基础类型相同,且其中一个是未命名类型
    • 数据类型相同,将双向通道赋值给单向通道,且其中一个为未命名类型;
    • 将默认值 nil 赋值给 切片、字典、通道、指针、函数 或 接口;
    • 对象实现了目标接口;
    func main() {
    	type data [2]int
    	var d data = [2]int{1, 2} // 基础类型相同,右值为 未命名类型
    
    	fmt.Println(d)
    
    	a := make(chan int, 2)
    	var b chan<- int = a // 双向通道 转换为 单向通道,其中 b 为 未命名类型
    
    	b <- 2
    }
    
  • 相关阅读:
    安装mysql到服务器的linux环境下
    Junit
    shell编程控制结构:expr、let、for、while、until、shift、if、case、break、continue、函数、select
    JavaScript组合继承的一点思考
    JavaScript-2.内置对象---简单脚本之弹出对话框显示当前时间 ---ShinePans
    Python之美[从菜鸟到高手]--2+2=5
    Flex中配置FusionCharts
    [WHY]Hello, Worktile~
    数据库学习之中的一个: 在 Oracle sql developer上执行SQL必知必会脚本
    Remove Nth Node From End of List
  • 原文地址:https://www.cnblogs.com/52php/p/6349272.html
Copyright © 2011-2022 走看看