GO语言的基础数据类型和底层实现原理

本文详细介绍了Go语言的基础数据类型,包括简单数据类型、指针、数组、切片、字典、通道和结构体。重点讲解了切片的扩容过程、字典的底层结构、通道的创建和关闭以及结构体的匿名字段。此外,还深入探讨了接口的定义、继承和使用,以及空接口的应用场景。
摘要由CSDN通过智能技术生成

目录

1,简单数据类型

2,指针(pointer)

3,数组(array)

4,切片(slice),动态数组

4.1,切片的基本用法

4.2,切片的扩容过程

5,字典(map)

5.1,map的基本用法

5.2,map的底层结构

5.3,map的扩容

6,通道(channel)

6.1,channel的创建,接收和发送

6.2,判断channel是否关闭

6.3,无缓存区的channel(阻塞)

6.4,有缓冲区的channel(非阻塞)

6.5,单向channel

6.6,channel取值用法

6.7,channel异常情况

6.8,channel底层原理

7,结构体(struct)

7.1,结构体的基本形式和用法

7.2,结构体方法

7.3,结构体字段,方法的可见性

7.4,结构体的匿名字段

7.5,结构体的嵌套

7.6,结构体tag

7.7,空结构体的作用

8,接口(interface)

8.1,接口的定义

8.2,接口的继承

8.3,接口的嵌套

8.3,接口的使用

8.4,空接口的使用


1,简单数据类型

数据类型关键字占用字节初始默认值范围说明
布尔型bool1字节falsefalse,true不能用0,1表示
整数型int81字节0-128~127
uint8(byte)1字节00~255byte是uint8的别称
int162字节0-32768~32767
uint162字节00~65535
int32(rune)4字节0-2147483648~2147483647rune是int32的别称,rune一般作为字符型,unicode字符集字符
uint324字节00~4294967295
int648字节00~18446744073709551615
uint648字节0-9223372036854775808~ 9223372036854775807
浮点型float324字节0.0-3.403E38~3.403E38
float648字节0.0-1.798E308~1.798E308
复数型complex648字节real()用于取复数的实部,imag()用于取复数的虚部
complex12816字节
字符串型string空字符串使用双引号("")或反引号(``)定义,内部是utf-8编码
int,uint和uintptr这三个与平台相关的整形类型,长度不是固

2,指针(pointer)

在 Go 中存在着三种指针:类型安全指针,unsafe.Pointer(任意类型的变量),uintptr(存指针地址的变量,可以参与计算)。

//类型安全指针
var i int = 32         //定义一个整数  也可以简写 i := 32

var p1 *int = &i       //定义一个int型指针,指向变量i的地址,也可以简写   p1 := &i
var p2 *int = new(int) //定义一个int指针,也可以简写 p2 := new(int)

*p1 = 99                //赋值
*p1 = i                 //赋值

fmt.Println(*p1)        //这个是取指针里的值
fmt.Println(p1)         //这个是取指针的地址

//unsafe.Pointer   指向任意类型的指针,一般配合uintptr一起使用

//uintptr     是一个整数类型,能够存放任意指针,可以参与计算
var a int= int(1)
var b *int64= (*int64)(unsafe.Pointer(&a))   //将*int先转为Pointer,在转为*int64
c := uintptr(unsafe.Pointer(&a))    //将*int先转为Pointer,在转为uintptr

/*
    uintptr(unsafe.Pointer(&u))  先取出u(定义的变量)的地址,变成Pointer,然后再转化为uintptr
    转化为uintptr后才能进行地址的计算,
    unsafe.Offsetof(age) 这个是取age(定义的变量)类型的长度,然后地址加上这个长度
    在把uintptr地址转化为Pointer,然后再转化为*int地址
*/
var ii *int = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&u))+unsafe.Offsetof(age)))

3,数组(array)

Go语言数组是具有相同类型的一组已经编号且长度固定的数据项序列,数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1。数组长度定义的时候确定,后面就不能修改。

//定义数组的第一种方式,var <数组名称> [<数组长度>]<数组元素>
var ar1 [2]int   //这种没有初始化,也可以var ar1 [2]int = [2]int{3, 4}

//第二种不确定长度
var ar3 = [...]int{23, 3, 4, 5, 6}  //根据后面的初始化值数量确定数组长度

//第三种定义索引的数值,根据最后一个索引值确定数组长度
var ar4 = [...]int{0:-1,3:6}   //最后的索引值是3,所以数组长度是4

len(ar4)     //获取数组的长度

4,切片(slice),动态数组

Go语言中的切片(Slice)是一种存储一系列相同类型的元素。切片可以看作是对底层数组的一个引用,它提供了动态大小、自动扩容的能力。

4.1,切片的基本用法
/*切片的定义和数组很相似,但是有不同,切片中的[]中是空的,数组[]里是长度或者...
*/
var ar1 []int                   //定义切片
ar2 := []int{1, 2, 3}           //定义切片,并初始化

var ar3 = make([]int, 5, 5)     //定义切片 var 切片变量 = make([]类型,长度, 容量)
var ar3 = make([]int, 5)        //定义切片 var 切片变量 = make([]类型,长度) 容量默认和长度相同

var ar4 = []int{1:1, 2:6, 5:10} //定义,初始化切片   索引为1的元素为1,索引2为6, 索引5为10

len(ar2)     //获取切片的长度,这个表示放入了多少个元素
cap(slar2)   /*获取切片的容量,这个表示申请了多少个元素的位置,扩容的时候这个值会改变
               开始初始化4个元素,长度,容量都是4,再入一个元素,由于放不下了,就会扩容(2倍的扩), 
               容量变成了8,长度是5*/

nar := append(ar1, 1, 3)     //尾部添加2个元素,返回一个新的切片
nar := append(ar1, ar4...)   //切片ar1后加入ar4切片的全部数据,返回一个新的切片

copy(ar1, ar2)               //复制切片,把ar1的数据全部拷贝到ar2

ar1 = ar1[1:]                //删除切片的第一个元素
​
ar1 = ar1[:len(ar1) - 1]     //删除切片的最后一个元素
​
//切片的底层结构
type SliceHeader struct {
    Data uintptr  //一个数组的指针,这个数值的长度是Cap
    Len int       //长度,放入元素的个数
    Cap int       //容量
}
4.2,切片的扩容过程

fcfeab28543548a5ba253c96fd7f2532.jpeg

5,字典(map)

存储的都是键值对,不能保证顺序性,不能插入键相同的元素。

5.1,map的基本用法
m := map[int]int{1: 11,2: 12,3: 13}//定义并初始化map

m2 := map[int]string{}	           //m2, m3的创建方法是等价的
m3 := make(map[int]string)

m4 := make(map[int]string, 10) 	   //m := make(map[keyT]valueT, cap),第2个参数指定容量
m4[1] = "aa"                       //添加键值对
m4[1] = "bb"                       //由于key为1的键值对已经存在,这个值bb会覆盖原来的aa

val, ok := m4["c1"]                //通过key获取值,如果ok=true,说明有值,ok=false说明没有值

delete(m4, 1)                      //删除m中,key是1的元素
5.2,map的底层结构
type hmap struct {
	count      int            // map中元素的个数,len()返回此值
	flags      uint8          // 状态标识符,key和value是否包指针、是否正在扩容、是否已经被迭代
	B          uint8          // map中桶数组的数量,桶数组的长度的对数,len(buckets) == 2^B,可以最多容纳 6.5 * 2 ^ B 个元素,6.5为装载因子
	noverflow  uint16         // 溢出桶的大概数量,当B小于16时是准确值,大于等于16时是大概的值
	hash0      uint32         // 哈希种子,用于计算哈希值,为哈希函数的结果引入一定的随机性
	buckets    unsafe.Pointer // 指向桶数组的指针,长度为 2^B ,如果元素个数为0,就为 nil
	oldbuckets unsafe.Pointer // 指向一个旧桶数组,用于扩容,它的长度是当前桶数组的一半
	nevacuate  uintptr        // 搬迁进度,小于此地址的桶数组迁移完成
	extra      *mapextra      // 可选字段,用于gc,指向所有的溢出桶,避免gc时扫描整个map,仅扫描所有溢出桶就足够了
}

// 溢出桶结构
type mapextra struct {
	overflow    *[]*bmap // 指针数组,指向所有溢出桶
	oldoverflow *[]*bmap // 指针数组,发生扩容时,指向所有旧的溢出桶
	nextOverflow *bmap // 指向所有溢出桶中下一个可以使用的溢出桶
}

//这个结构体更好理解
type bmap struct {
	topbits  [8]uint8     // 存放key哈希值的高8位,用于决定kv键值对放在桶内的哪个位置
	keys     [8]keytype   // 存放key的数组
	values   [8]valuetype // 存放value的数组
	pad      uintptr      // 用于对齐内存
	overflow uintptr      // 指向下一个桶,即溢出桶,拉链法
}
5.3,map的扩容

扩容触发条件:1,元素个数>桶个数*6.5(每个桶里可以存放8个键值对),这种情况就是元素太多                                  了,hash冲突的概率增大,通过增量扩容减少冲突概率。

                         2,溢出桶数量太多,当桶总数<2^15时,溢出桶总数>=桶总数,则认为溢出桶过                               多;当桶总数>=2^15时,溢出桶总数>=2^15时,则认为溢出桶太多,这种情况                               就是元素太过集中到某些hash,导致溢出桶多,通过等量扩容,让元素分散。

增量扩容:适用于第一种情况的扩容,把桶的数量增加一倍,然后把数据重新搬迁到新的桶中。

等量扩容:并不扩大桶数量,通过重新计算键值,把元素重新排列一次,让元素分散。

6,通道(channel)

channel是golang在goroutine之间的通讯方式,channel是引用类型,使用的时候必须通过make进行初始化;线程安全,多个goroutine访问时,不需要加锁,也就是channel本身就是线程安全的。

6.1,channel的创建,接收和发送

创建形式:make(chan 元素类型, 容量)  或者   make(chan 元素类型) ,不写容量默认是0

接收:re := <-ch     读取ch的数据赋给re,如果通道关闭了,还能接收通道里的值,直到通道返回对应类型的零值,如果通道没有值,会返回对应的零值。

发送: ch <- 10     向ch中写入10,如果通道关闭了,在发送会导致panic

关闭:close(ch)    关闭ch,如果关闭一个已经关闭的通道会导致panic

ch := make(chan int, 3)	//申明channel,make(chan 元素类型, 容量),容量可以是0
ch <- 10	           //把10传递给ch
x := <-ch	           //从ch中接收值并赋值给变量x
v, ok := <-ch          //如果ok=false,通道被关闭或者通道无值
close(ch)              //关闭管道

ch1 := make(chan int, 1)  //定义一个channel
ch2 := make(chan int , 1) //定义另一个channel

select {                  //select可以监控多个channel,哪个有数据就执行相应的逻辑部分
    case <-ch1:
        fmt.Println("ch1")
    case <-ch2:
        fmt.Println("ch2")
}
6.2,判断channel是否关闭

channel关闭了,任然可以接收值

v, ok := <-ch  

如果ch关闭了,ch里面有内容,v接收的内容,ok=true

如果ch关闭了,ch里面没有内容,v接收的是对应类型的零值,ok=false

如果ch没有关闭,ch里面有内容,v接收的内容,ok=true

如果ch没有关闭,ch里面没有内容,会报错死锁

6.3,无缓存区的channel(阻塞)
//无缓冲区channel
ch := make(chan int)   //或者ch := make(chan int, 0)

使用无缓存区的channel,完全是一种同步化的操作,只有同时有发送者和接收者的时候,才能正常,否则会报错,因为一直处于阻塞状态,会形成死锁。

//会报错,只有发送者,没有接收者
ch1 := make(chan int)
ch1 <- 10

//会报错,只有接收者,无发送者
ch2 := make(chan int)
<- ch2

//不会报错,有接收者,有发送者
ch3 := make(chan int)
go func(){
    re := <-ch3
    fmt.Println(re)
}()
ch3 <- 10
6.4,有缓冲区的channel(非阻塞)
//有缓冲区channel
ch := make(chan int, 2)   //后面不一定是2,可以是大于0的其他整数

如果缓冲区满了,还发送值,会报错死锁;如果缓存区没有内容了,还接收,也会报错死锁

//发送
ch3 := make(chan int, 1)  //有缓冲区,缓冲区大小是1
ch3 <- 10                 //这个发送正常,因为缓冲区有1单位
ch3 <- 10                 //这个会报错,缓存区满了,发送会阻塞,产生死锁报错

//接收
ch4 := make(chan int, 1)  //有缓冲区,缓冲区大小是1
ch4 <- 10                 //这个发送正常,因为缓冲区有1单位
<-ch4                     //这个接收正常,缓冲区有内容
<-ch4                     //这个会报错,因为缓冲区已经没有内容了,会阻塞,产生死锁报错

如果不想报错,可以使用cap,len先获取下元素树,比较后在决定。cap(ch4)获取的是channel的容量,len(ch4)获取的是元素个数,如果len返回的大于0,接收是没有问题的,如果len返回的值小于cap返回的值,发送是没有问题的。

6.5,单向channel

只能发送值的channel或者只能接收值的channl,一般是作为方法参数修饰,表示在方法里只能发送或者只能接收。

//只能接收的channel
func inChannel(inch <-chan int){
}

//只能发送的channel
func outChannel(outch chan<- int){
}
6.6,channel取值用法

channel取值分别有for range,for,select三种。

//for range 方式遍历,如果ch4处于未关闭状态,获取完后最后会阻塞,直到死锁报错
for v := range ch4{
    fmt.Println(v)
}

//for方式遍历
for {
    <-ch3
    if(len(ch3) == 0){
        break
    }
}

//chan1,chan2那个channel有数据,执行那部分逻辑
select {
    case <-chan1:
        fmt.Println("chan1 ready.")
    case <-chan2:
        fmt.Println("chan2 ready.")
    default:
        fmt.Println("default")
}
6.7,channel异常情况
channel

nil(例如定义了

没有初始化)

非空空的满了没满
接收阻塞接收值阻塞接收值接收值
发送阻塞发送值发送值阻塞发送值
关闭panic

关闭成功,读完

数据后返回零值

关闭成功,返回零值

关闭成功,读完数据后返回零值

关闭成功,读完数据后返回零值
6.8,channel底层原理
//channel底层结果
type hchan struct {
  closed uint32      // 标识关闭状态:表示当前通道是否处于关闭状态。创建通道后,该字段设置为0,即通道打开; 通过调用close将其设置为1,通道关闭。
  
  qcount uint        // 当前队列列中剩余元素个数,len返回的
  dataqsiz uint      // 环形队列长度,即可以存放的元素个数,即make(chan T,N)的N值,cap的值
  buf unsafe.Pointer // 环形队列列指针,ring buffer 环形队列
  elemsize uint16    // 每个元素的⼤⼩
  elemtype *_type    // 元素类型:用于数据传递过程中的赋值;
  sendx uint         // 队列下标,指示元素写⼊入时存放到队列列中的位置 x
  recvx uint         // 队列下标,指示元素从队列列的该位置读出  
  
  recvq waitq        // 等待读消息的goroutine队列
  sendq  waitq       // 等待写消息的goroutine队列
  
  lock mutex         // 互斥锁,chan不允许并发读写
} 

channel底层存储的数据就是一个固定长度的数组,可以看成是一个环状的,分别用两个下标标记发送,读取位置。发送一个数据,元素个数加1,sendx下标加1,如果到数组尾部,就重新定为0;接收一个数据,数组元素减1,recvx下标加1,如果到数组尾部,重新定为0,如果元素数量为0就是没有元素了,如果元素数量等于容量,就是数组满了。

7,结构体(struct)

Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体。Go语言中没有class,是通过struct来实现面向对象。

7.1,结构体的基本形式和用法

go语言的结构体和c语言的结构体形式差不多。结构体中属性名必须唯一,可以是简单类型,也可以是其他结构体。

/*
结构体定义类型,可以是基础类型,也可以是符合类型,或者其他的结构体,属性的定义
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}
*/
//定义了一个Person结构体,包括name,age两个属性
type Person struct {
    id int
    name string        
    age  int8         
}

//结构体初始化

//初始化的值顺序和定义的顺序一致,且每个属性都需要初始化
var per1 Person = Person{2,"name",25}

//部分属性初始化,通过key:v这种形式,不用关心顺序
var per2 Person = Person{name:"name",age:26}

//结构体实例化   取地址实例化
per3 := &Person{}
per3.mame = "version"

//实例化和初始化一块进行
per4 := &Person{name:"name123"}   //取地址实例化

//类似类的方式
var per5 Person
per5.id=12
per5.name="name"
per5.age = 27

//结构体实例化   格式   ins := new(T)
per6 := new(Person)
per6.name = "Canon"
7.2,结构体方法
//定义的结构体
type Person struct {
    id int
    name string        
    age  int8         
}

//给结构体Person添加方法,方法名Info,返回值类型string
func (r Person) Info() string {
    return r.name
}

//调用方法
p := Person{name:"name123"}    //相当于类的实例化
p.Info()                       //调用类方法

结构体没有构造方法之说,但是可以通过定义方法,实现类似的构造方法功能。

//定义的结构体
type Person struct {
    id int
    name string        
    age  int8         
}

//给结构体Person添加方法,方法名Info,返回值类型string
func (r Person) Info() string {
    return r.name
}

//可以看作构造方法
func NewPerson(id int, name string, age int8) *Person{
    return &Person{
        id:id,
        name:name,
        age:age,             //最后一个,不能少
    }
}

p := NewPerson(12, "name123", 28)  //相当于调用构造方法,返回Person指针
p.Info()                           //调用Person的方法Info
7.3,结构体字段,方法的可见性

其他语言中通过public,protected,private控制变量和方法的可见性,在Go语言中并没有这些关键字,它是通过结构体名称,字段名,方法名首字母大小写来控制公共,私有(这个私有是指当前结构体所在的包都能访问,和其他语言的private有区别)。结构体名称,字段名,方法名首字母小写,只能在该结构体的包中可以访问,结构体名称,字段名,方法名首字母大写,本包和其他包都可以访问。

//结构体名Person,首字母大写,可以在包外访问,如果是小写,只能在本包中访问
type Person struct{
    name string     //字段名name首字母小写,只能所在的包中可以访问
    Age  int8       //字段名Age首字母大写,其他包,本包中都可以访问
}

func (p Person) getName() string{  //方法名getName首字母小写,只能所在包中访问
    return p.name
}

func (p Person) GetAge() string{   //方法名GetAge首字母大写,包中,包外都可以访问
    return p.Age
}
7.4,结构体的匿名字段

匿名字段是一种特殊的字段类型,它没有字段名,只有字段类型。在结构体中,可以使用匿名字段来表示继承关系或者组合关系。

type Address struct{
    addr  string     //这个是正常字段,有类型,有名称,不是匿名字段
    int              //这个地方是匿名字段,只有类型,没用名称
}

//匿名调用访问,匿名字段,以它的类型作为名进行访问
p := Address{
    addr:"beijin",
    int:45,          //匿名字段
}
p.int = 85           //匿名字段赋值
fmt.Println(p.int)   //访问匿名字段
7.5,结构体的嵌套

通过结构体的嵌套,可以实现其他语言的类似与类继承,覆盖等功能。

type Address struct{
    addr  string
}

func (ad Address) GetAddr() string{
    return ad.addr
}

func (ad Address) Ress() string{
    return "ress"
}

type Person struct{
    Address         //嵌套结构体  需要匿名
    name string
}

func (p Person) MyInfo() string{
    return p.name + p.addr        //由于Address是匿名,Address的addr可以直接访问,属性实现继承
}

func (p Person) GetAddr() string{
    return "p addr"
}

p := Person{
    Address:Address{"beijin",},    //Address是匿名的,用类型作为名称
    name:"name",
}

fmt.Println(p.Ress())            //这个是Address的方法,实现继承调用
fmt.Println(p.GetAddr())         //这个覆盖了Address的GetAddr方法,调用的是自己的方法
fmt.Println(p.Address.GetAddr()) //这个是Address的GetAddr方法
fmt.Println(p.MyInfo())          //调用自己的方法
7.6,结构体tag

tag可以给结构体属性添加说明或者备注标签,这些说明可以通过反射获取到,类似其他语言的注解。tag格式  `key01:"value01" key02:"value02" key03:"value03"` ,多个之间有空格。

type Person struct{
    Name string   `label:"name label"`            //包裹的就是tag
    Age int8      `label:"age label" type:"int8"` //多个之间,中间要有空格
}

type Man struct{
    Name string  `json:"name"`  //这个是struct和json转换的时候经常用到,json的字段名和结构体字段名可以不一样
    Age int      `json:"age"`
}
7.7,空结构体的作用

结构体是没有没有任何成员的就叫空结构体,它的大小是0,也不包含任何信息。

空结构体作用:
    1,实现set集合,可以使用map[KeyType]struct{}的形式实现一个set集合,其大小为0,可以避免内存损耗。
    2,实现通道信号,在并发编程中,如果在不同的goroutine之间进行状态(有无)传递,可以使用struct{}作为通道信号。
    3,只有方法的结构体,结合type可以实现只有方法的结构体。

type Emp struct {  //空结构体
}

8,接口(interface)

接口实际上是一组方法的集合,是否继承这个结构,不需要明确的指定,如果某个类型实现了接口的所有方法,就可以说该类型实现了该interface。

8.1,接口的定义

接口的作用是规范代码,实现复杂的功能,接口里的方法只需要定义就可以,不能实现。

//接口的定义格式
/*
 type 接口名 interface{
     方法名1(参数列表1) 返回值列表1
     方法名2(参数列表2) 返回值列表2
     …
 }
*/
//定义了一个名为CarInterface的接口
type CarInterface interface{
    Run()                     //Run方法,无参数,无返回值
    getName() string          //getName方法,无参数,返回值string类型
    setName(s string)         //setName方法,有参数,无返回值
}
8.2,接口的继承

GO语言中的接口继承支持多继承的,继承关系不需要明确指定,不是耦合的,只要某个接口中的方法全部实现了,就认为继承了这个接口,这点和其他语言是不一样的。

type Child interface{
    cry()
}

type Person interface{
    speak()
}

type Man struct{
}

func (m Man) cry(){
    fmt.Println("cry")
}

func (m Man) speak(){
    fmt.Println("speak")
}

/*上面定义了两个接口,分别是Child,Person,定义了一个结构体Man,
由于结构体实现了Child的全部方法(cry),所以说结构体Man继承了接口Child,
由于结构体实现了Person的全部方法(speak),所以说结构体Man继承了接口Person
*/
8.3,接口的嵌套

接口的嵌套也可以看成接口的继承,嵌套后就会继承嵌套接口的方法

/*定义一个新的接口,里面嵌套了Child,Person两个接口,增加了一个方法charge()
由于是嵌套,只有实现了Child全部方法,Person全部方法,以及自己的方法charge,才能说继承了这个接口
*/
type Robot interface{
    Child
    Person
    charge()
}
8.3,接口的使用

GO语言的接口的使用,和其他语言的接口使用方法类似。

type Child interface{
    cry()
}

type Person interface{
    speak()
}

type Robot interface{
    Child
    Person
    charge()
}

type Man struct{
}

func (m Man) cry(){
    fmt.Println("cry")
}

func (m Man) speak(){
    fmt.Println("speak")
}

func (m Man) charge(){
    fmt.Println("charge")
}

var c Child = Man{}   
c.cry()          //由于Child接口只有cry()方法,只能调用这个方法,不能调用speak方法,因为这个方法不属于Child

var r Robot = Man{}  //Rebot继承了其他两个接口,拥有了他们的方法定义,所以方法都可以调用
r.cry()
r.speak()
r.charge()
8.4,空接口的使用

interface{} 是一个空的 interface 类型, 空的 interface 没有方法,所以可以认为所有的类型都实现了 interface{}。如果定义一个函数参数是 interface{} 类型, 这个函数应该可以接受任何类型作为它的参数。

空接口可以作为通用类型,不确定具体什么类型;作为方法接收者,实现泛型;用于channel传递不确定类型的值。

//interface{}可以代表任何类型

m := make(map[string]interface{})   //定义一个map,key字符串,值是任何类型
m["name"] = "John"
m["age"] = 30


func printValue(v interface{}) {    //参数可以是任何类型,实现类似泛型的功能
    fmt.Println(v)
}


var data []interface{}       //实现泛型切面,可以放入任何类型元素
data = append(data, "Hello")
data = append(data, 123)

泛型类型的转换需要用到断言,格式 ret, ok := interface.(type) 

a := 1
v, ok := interface{}(a).(int)	// 将整数a转换为接口类型


//Go 中的 interface 类型是不能直接转换成其他类型的,需要使用到断言。
var itf interface{} = 1    //接口类型
j, ok := itf.(int)         //通过断言,转换为int类型

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

geegtb

只希望写的东西能够帮助到你

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

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

打赏作者

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

抵扣说明:

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

余额充值