GoLang之类型系列一(类型分类)

GoLang之类型系列一(类型分类)

注:本文基于Windos系统上Go SDK v1.18进行讲解

1.builtin.go

type bool bool
type uint8 uint8
type uint16 uint16
type uint32 uint32
type uint64 uint64
type int8 int8
type int16 int16
type int32 int32
type int64 int64
type float32 float32
type float64 float64
type complex64 complex64
type complex128 complex128
type string string
type int int
type uint uint
type uintptr uintptr
type byte = uint8
type rune = int32
type any = interface{}

2.内置类型built-in、自定义类型type

built-in下的都是内置类型,通过type的都是自定义类型;
给内置类型定义方法事不被允许的,但是可以对自定义类型进行定方法(接口类型是无效的方法接收者);
所以我们不能像类型T这样给内置类型和接口定义方法

image-20220303170013038

image-20220303170029289
在这里插入图片描述

3.预先声明类型defined type

例如:int, int64, float32, string, bool等。这些已经是GO中预先声明好的类型;
在Go语言规范中对做了明确规定:
所有数值类型都是defined type;(这里面就包含int)(注:rune、byte是alias类型,不是defined type)
字符串类型string是defined type;
布尔类型bool是defined type;
这就意味着map、数组、切片、结构体、channel等原生复合类型(composite type)都不是defined type

在Go中,使用下述类型声明语句定义的类型T1、T2被称为defined type。

//在Go中,我们定义一个类型一般通过type关键字进行,比如以下两行
type T1 int
type T2 T1

4.命名类型named type

命名类型:有名字的类型
一个命名类型一定和其它类型不同!
注:预先声明类型defined type属于命名类型;
alias type也属于命名类型;
var的也是命名类型

var i int // named type
type myInt int // named type
var b bool // named type

5.未命名类型unnamed type、类型字面量type literal

数组,结构体,指针,函数,接口,切片,map,通道 都是未命名类型;
虽然他们没有名字,但却有一个类型字面量(type literal)来描述他们由什么构成
注意:类型字面量(type literal)==未命名类型

[]string // unnamed type
map[string]string // unnamed type
[10]int // unnamed type

6.alias type

我们基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias,这就是他们最大的区别。

type MyInt1 int//defintion
type MyInt2 = int//alias

7.基础类型underlying type

任何类型T都有基本类型;
如果T 是预先声明类型:boolean, numeric, or string(布尔,数值,字符串)中的一个,或者是一个类型字面量(type literal),他们对应的基础类型就是T自身。

//他们的类型声明为 string的预先声明的类型,所以他们的基础类型就是T它自身: string
type A string
type S string 
type M map[string]int  //有类型字面量,所以他的基础类型也是T它自身:map[string]int
type N M     //N引用了M, 因此N的基础类型是M的类型声明:map[string]int
type P *N  //有类型字面量,所以他的基础类型也是T它自身:*N指针
type A string
type B A //B引用了A,因此B的基础类型是A的类型声明:string
type  S string
type T map[S]int  //T的基础类型应该是 map[string]int 并非map[S]int; 我们讨论的是基础未命名类型map[S]int,因为基础类型只追溯到它的未命名类型的最外层(或者就像说明上说的:如果T是一个类型字面量,它的基础类型就是T自身),
type U T    //U的基础类型是map[S]int

8.预先声明类型defined type赋值规则

问题所在:
MyInt与int是不同的两个类型,MyMap与map[string]int也是不同的两个类型,为何将int型变量赋值给MyInt型变量时需要做显式转型,而将map[string]int变量赋值给MyMap型变量就不需要显式转型呢?
Go是强调类型安全的静态编译型语言,在Go语言中,不同类型变量是不能在一起进行混合计算的,这是因为Go希望开发人员明确知道自己在做什么,这与C语言的“信任程序员”原则完全不同,因此你需要以显式的方式通过转型统一参与计算各个变量的类型。比如:上面问题中MyInt虽然底层类型(underlying type)是int,但MyInt与int是两个不同的类型,因此它们之间的相互赋值需要通过显式转型来进行,否则Go编译器将报错,这个没有任何疑问;
但是MyMap与map[string]int也是两个不同的类型,却可以,为什么呢?

package main

type MyInt int
type MyMap map[string]int

func main() {
	var x MyInt
	var y int
	x = y // 会报错: cannot use y (type int) as type MyInt in assignment;  
    _ =x  

	var m1 MyMap
	var m2 map[string]int
	m1 = m2 // 不会报错
	m2 = m1 // 不会报错
}
package main

type MyInt int
type MyMap map[string]int

func main() {
	var x MyInt
	var y int
	y = x
	_ = y

	var m1 MyMap
	var m2 map[string]int
	m1 = m2 // 不会报错
	m2 = m1 // 不会报错
}
package main

type MyInt int
type MyMap map[string]int

func main() {
	var x MyInt
	var y int
	//如果使用x = MyInt(y) 的话则不会报错
	x = MyInt(y)
	_ =x

	var m1 MyMap
	var m2 map[string]int
	m1 = m2 // 不会报错
	m2 = m1 // 不会报错
}

预先声明类型defined type赋值规则:
x’s type V and T have identical underlying types and at least one of V or T is not a defined type:
如果x的类型V与类型T具有相同的底层类型,并且V和T至少有一个不是defined type,那么x可以赋值给类型T的变量;
我们用问题中的代码来套一下这个规则。我们有一个MyMap类型的变量m1,MyMap类型与map[string]int类型具有相同的底层类型map[string]int,并且map[string]int类型不是一个defined type,那么我们可以将m1直接赋值给map[string]int类型的变量m2,反之亦可

注:”V和T至少有一个不是defined type”是正确的,“V和T至少有一个不是命名类型”是错误的

//以下代码是可以通过的
package main

type a = int

func main() {
	var x a
	var y int
	y = x
	_ = y
}

总结:
Go总体来说是推崇显式哲学的,那怎么来理解这种隐式转型呢?我觉得至少有两点:
(1)首先这种转型更多是在编译器保证类型安全性的前提下进行的,不会出现溢出或未定义行为;
(2)其次,这种隐式转型一定程度减少了代码输入,对开发体验的提升有帮助

例子中这些赋值都不会报编译错误

package main

type MyMap map[string]int
type MySlice []byte
type MyArray [10]int
type MyStruct struct {
    a int
    b string
}
type MyChannel chan int

func main() {
    var m1 MyMap
    var m2 map[string]int
    m1 = m2 // 不会报错
    m2 = m1 // 不会报错
    
    var sl1 MySlice
    var sl2 []byte
    sl1 = sl2 // 不会报错
    sl2 = sl1 // 不会报错
    
    var arr1 MyArray
    var arr2 [10]int
    arr1 = arr2 // 不会报错
    arr2 = arr1 // 不会报错
    
    var s1 MyStruct
    var s2 struct {
        a int
        b string
    }
    s1 = s2 // 不会报错
    s2 = s1 // 不会报错
    
    var c1 MyChannel
    var c2 chan int
    c1 = c2 // 不会报错
    c2 = c1 // 不会报错
}

package main

import "fmt"

type aInt int

func main() {
    var i int = 10 
    var ai aInt = 100
    i = ai//在这里会编译报错
    printAiType(i)
}

func printAiType(ai aInt) {
    fmt.print(ai)
}

以下代码编译通过,因为m是未命名类型,同时mmMap有相同的基础类型

package main

import "fmt"

type MyMap map[int]int

func main() {
    m := make(map[int]int)
    var mMap MyMap
    mMap = m
    printAiType(mMap)
    fmt.Print(m)
}

func printAiType(mMap MyMap) {
    fmt.print(mMap)
}

因为Meter Centimeter 的基础类型都是integers类型,所以他们的基础类型是可以相互转换,所以编译通过

package main

type Meter int64

type Centimeter int32

func main() {
    var cm Centimeter = 1000
    var m Meter
    m = Meter(cm)
    print(m)
    cm = Centimeter(m)
    print(cm)
}

10.结构体类型转换规范

忽略结构体的tags,只要结构体X和T拥有相同的基础类型,就可以转换
字段 Meter.value 的基础类型是 int64 ,字段Centimeter.value 的基础类型是 int64。一致的基础类型,所以可以相互转换,编译通过

package main

type Meter struct {
    value int64
}

type Centimeter struct {
    value int64
}

func main() {
    cm := Centimeter{
        value: 1000,
    }

    var m Meter
    m = Meter(cm)
    print(m.value)
    cm = Centimeter(m)
    print(cm.value)
}

因为字段 Meter.value 的基础类型是 int64 , 字段Centimeter.value 的基础类型是 int32 ,因为一个命名类型一定和其它任意类型都不同,即int32与int64一定不同。所以他们不一致,不是相同的基础类型,所以不能转换

package main

type Meter struct {
	value int64
}

type Centimeter struct {
	value int32
}

func main() {
	cm := Centimeter{
		value: 1000,
	}

	var m Meter
	m = Meter(cm)//这里编译出错
	print(m.value)
	cm = Centimeter(m)//这里编译出错
	print(cm.value)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值