Go语言内置类型

对于类型来说,需要清楚两个问题:

  • 需要分配多少内存?
  • 这些内存用于存储什么类型?

1. 整数

对于整型类型来说:

intuint:

  • int8

    • 内存: 1个字节(8 bit)
    • 存储类型: 有符号整数
    • 范围: -128 - 127
  • int16

    • 内存: 2个字节(16- bit)
    • 存储类型: 有符号整数
    • 范围: -32768 - 32767
  • int32

    • 内存: 4个字节(32- bit)
    • 存储类型: 有符号整数
    • 范围: -2147483648 - 2147483647
  • int64

    • 内存: 8字节个字节(64 bit)
    • 存储类型: 有符号整数
    • 范围: -9223372036854775808 - 9223372036854775807
  • uint8

    • 内存: 1个字节(8 bit)
    • 存储类型: 无符号整数
    • 范围:0 - 255
  • uint16

    • 内存: 2个字节(16 bit)
    • 存储类型: 无符号整数
    • 范围: 0-65535
  • uint32

    • 内存: 4个字节(32 bit)
    • 存储类型: 无符号整数
    • 范围: 0-4294967295
  • uint64

    • 内存: 8个字节(64 bit)
    • 存储类型: 无符号整数
    • 范围:0-18446744073709551615

​ 如果我们使用非精确类型

int或者uint来声明变量时,该变量的存储大小实际上取决于用于构建程序的硬件架构。

​ 32位架构: int用4字节存储有符号整数,uint用4字节存储无符号整数

​ 64位架构: int用8字节存储有符号整数,uint用8字节存储无符号整数。

2. float

Go语言遵循IEEE 754二进制浮点算数标准,仅支持两种类型浮点数:

  • float32
    • IEEE 754标准:单精度浮点数
    • 字节数:4字节
    • 指数位长度: 8bit
    • 位数为长度: 23位
    • 有效十进制精度(约): 6-7位

  • float64
    • IEEE 754标准:双精度浮点数
    • 字节数:8字节
    • 指数位长度: 11bit
    • 尾位数为长度: 52位
    • 有效十进制精度(约): 15-16位

未精确指定float的时候默认是float64

浮点数存在固有舍入误差,无法精确表示部分十进制小数,比较时需使用容差判断,避免直接使用 ==

3. bool

// true and false are the two untyped boolean values.

const (

  true  = 0 == 0 // Untyped bool.

  false = 0 != 0 // Untyped bool.

)

bool类型只有两个值,true或者false, 在Go语言中不能将整型变量直接转换成bool类型,也不能将bool类型转换成整型。

4. string

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

字符串类型在go1.20版本中没有在builtin.go中具体实现,但是在builtin.go文件中类型定义了string,在builtin.go文件中递归类型定义了string,递归类型定义只能适用于内置基本类型,普通自定义类型无法复刻。

type int int
//如果类型定义类型本身,则会有错误提示
//invalid recursive type: int refers to itself

builtin.go文件中我们可以知道 字符串是所有8位字节的字符串的集合,通常但不一定表示UTF-8编码的文本。字符串可以是空的,但不是nil。字符串类型的值是不可变的。

字长

​ 字长是指,在某种特定架构下,存储整数及指针所需要分配的内存容量。

​ 32位架构: 字长位4字节,因此需要分配4字节的内存空间。

​ 64位架构: 字长位8字节,因此需要分配8字节的内存空间。

​ 也就意味这指针类型变量,分配4字节或者8字节,根据不同的架构分配不同的大小的内存空间。

​ 所以在相同的架构环境下,对于int、指针或者字的数据来说,为他们分配的内存容量始终相同的。

零值的概念

​ 我们创建的每个变量在其初始化时,其值至少被设置位零,也就是当我们声明变量时,go在编译过程中,会自动位该变量进行初始化,也就是为相应的变量的存储空间内存储相应的零值。如果我们在声明变量的时候就指定初始值,那么该变量在使用的时候,其值就是我们预先指定的值。 所谓“零值”就是指每个字节的每一位都被设置为零。

​ 零值确保了数据的完整性,也就是说在我们使用一个声明的变量过程中,该变量符合我们的预期而不是在使用的时候出现一些未知的值存在我们的变量中。

go语言的内置类型的零值如下:

  • boolean: false
  • Integer: 0
  • Float : 0.0
  • Complex: 0i
  • String: “"(empty)
  • Pointer: nil
  • rune: 0
  • byte: 0

复合类型由一个或多个基本类型组成,其零值遵循【成员各自取对应的类型零值】规则。

  • 数组: 数组的每个元素都取该元素类型的零值,与数组长度无关。

  • 结构体: 结构体的零值是【结构体每个字段都取该字段类型的零值】,嵌套结构体也遵循此规则,逐层递归赋零值。

  • 引用类型:

    引用类型的零值都是nil

    • 切片 []int, nil切片的len()、cap()均为0,可直接使用append()无需手动初始化。
    	// 查看空结构体的值
    	var nilSlice []int
    	if nilSlice == nil {
    		fmt.Println("nil slice")
    	} else {
    		fmt.Printf("%+v\n", nilSlice)
    	}
    	// output : nil slice
    
    	//查看nil slice len() cap()
    	fmt.Printf("len=%d, cap=%d\n", len(nilSlice), cap(nilSlice))
    	// output:len=0, cap=0
    
    	// 对nil slice进行append
    	nilSlice = append(nilSlice, 1)
    	fmt.Printf("len=%d, cap=%d\n", len(nilSlice), cap(nilSlice))
    	fmt.Println(nilSlice)
    	// output:
    	//  		len=1, cap=1
    	//			[1]
    
    	var nilSlice1 []int
    	// 使用函数添加元素,但是传入的参数是nil
    	AppendElement(nilSlice1, 1)
    	if nilSlice1 == nil {
    		fmt.Println("nil slice")
    	} else {
    		fmt.Printf("len=%d, cap=%d\n", len(nilSlice), cap(nilSlice))
    		fmt.Println(nilSlice1)
    	}
    	// output: nil slice
    	// 由于形参是值传递,而通过函数传递给的nilSlice1最终添加了元素并没有返回给main。
    
    • 字典map[k]v , nilmap无法直接添加键值对,必须通过make进行初始化,或者字面量初始化map[int]int{}

      	var nilmap map[int]int
      
      	fmt.Println(len(nilmap)) // output : 0
      	//直接添加键值对
      	nilmap[1] = 12
      	if nilmap == nil {
      		fmt.Println("nil map")
      	} else {
      		fmt.Println("len=", len(nilmap))
      		fmt.Println(nilmap)
      	}
      	// output:
      	// 			panic: assignment to entry in nil map [recovered]
      	// 		panic: assignment to entry in nil map
      
      //所以为了程序的健壮性,所以在使用map的时候尽量先对map进行nil判断,然后在进行业务处理
      
    • 通道 chan T nil通道无法进行发送/接受,必须通过make进行使用。

    • 函数 nil的直接调用会引发panic。

    • 指针 nil指针无法解引用,如果解引用会引发panic。

    • 接口 nil接口的零值要求动态类型和动态值都为nilnil接口无法调用任何方法。

声明与初始化

1 var

关键字var可以用于所有类型的变量设置为初始状态,零值。

type User struct{
    Name string
    Age  uint8
}
var i8 int8
var u8 uint8
var condition bool
var user User

go语言中字符串底层结构由两个字组成:

type stringStruct struct {
	str unsafe.Pointer
	len int
}

如果string被设置为nil, 那么字段str则是nil,len为0。

2 短变量声明操作符

短变量声明操作符:=

该操作符包含了声明变量,初始化变量,赋值变量三个步骤。

如果一个变量已经声明了或者定义了,就无法再次使用短变量声明操作符。

转换与类型转换

go语言不支持隐式类型转换,并且显示类型转换不是将原有类型的转换为需要的类型,而是创建一个目标的新变量变量,对其进行赋值,然后将该变量赋值到我们指定的变量中(不是被转换的变量)。

显示转换形式:

// 格式 目标类型(源变量/值)
// string([]byte)
// []byte("string value")


source := "string value"
	fmt.Println("source address: ", &source)
	target := []byte(source)
	fmt.Println("source address: ", &source)
	fmt.Println("target address: ", &target)

	//output :
	//	source address:  0xc000026300
	//	source address:  0xc000026300
	// target address:  &[115 116 114 105 110 103 32 118 97 108 117 101]

接口

面向接口编程,面向接口编程,面向接口编程

解耦: 解耦意味着减少组件之间及其所使用数据类型之间的依赖关系。进而提升程序的正确性、质量及可维护性。

接口使我们能够根据数据的功能将其归类组合。重点在于数据具备何种功能,而非数据本身是什么。用户可以通过接口来获取具体的数据的方式,让代码免受变化的影响,也就是说无论我们底层的实现如何改变,调用方是不会感知到变化的,只要行为没有变化即可。

接口应描述行为而非状态,应该是动词而非名词。

在以下情况下使用接口:

  • API的使用者需要提供实现细节
  • API有多种实现方式,相关方需要在内部维护这些实现细节
  • 已确定API中可发生变化的部分,这些部分需要进行解耦处理。

在一下情况下不要使用接口:

  • 为了使用接口而使用接口。
  • 对算法进行泛化处理。
  • 当用户能够定义自己的接口时。
  • 如果不清楚该接口时如何让代码更优化的。
  • 接口毫无价值。

接口是无值类型,他只规定了一组方法,具体数据必须实现这些方法才能满足接口的要求。接口本身没有任何具体内容。

type Reader interface{
    Read(b []byte)(int,error)
}

func main(){
    var r Reader
}

像上述的r变量在没有对它进行赋值实现该接口的类型实例之前,该变量没有任何意义,其为nil。

我们从不处理接口值,只处理具体值。接口具有编译器层面的表现形式(内部类型),但从我们的编程模型来看,接口本身是无价值的。

多态性

多态性是指代码的行为会根据其处理的具体数据而发生变化。

type Mover interface {
	Move()
}

type Jumper interface {
	Jump()
}

type NormalPerson struct{}

func (n NormalPerson) Move() {
	fmt.Println("我正以每小时5公里的速度前进")
}

func (n NormalPerson) Jump() {
	fmt.Println("我每次跳跃30cm")
}

type AthlecticPerson struct{}

func (a AthlecticPerson) Move() {
	fmt.Println("我正以每小时10公里的速度前进")
}

func (a AthlecticPerson) Jump() {
	fmt.Println("我每次跳跃60cm")
}


// 定义一个多态函数
func OperatePersonMove(mover Mover) {
	mover.Move()
}

比如上述代码,我想操作不同的人物进行移动或者跳跃,由于不同类型的人物的行为都为走或者跳跃,但是其具体行为细节是不一样,但是我操作人物具体逻辑是一样的,所以OperatePersonMove函数参数为一个Mover接口类型,这里我需要传递的类型不是接口类型,而是实现该接口的具体类型实例。这样在调用Move函数的时候根据不同的具体类型会产生不同的移动效果。