数据类型

值类型和引用类型

在golang中,同其他语言相同,可以将类型大致分为两类:值类型引用类型


其中,值类型存储在栈中,他们在编译期就可以确定变量所占用的空间,并且值类型变量直接指向栈空间的值。使用等号=将一个变量的值赋给另一个变量时,如j = i,实际上是在内存中将 i 的值进行了拷贝。我们可以通过 &i 获取变量 i 的内存地址。在golang中,属于值类型的数据类型包含:

  • 数字类型

  • 布尔类型

  • 字符串类型

  • 数组:数组也存放在栈空间中,这个与Java不同

  • 结构体:因为是值传递,所以在方法之间传递会进行值复制,如果结构体较大,需要使用指针


引用类型拥有更复杂的存储结构,推崇存储在堆内存中,编译时一般无法确定其所占空间:

  1. 需要通过make创建并分配内存

  2. 初始化一系列属性:指针、长度、哈希分布、数据队列等。一个引用类型的变量r1存储的是r1的值所在的内存地址(数字),或内存地址中第一个元素所在的位置,这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个变量中。

在golang中,是引用类型的有:

  • 指针

  • 管道 channel

  • 接口 interface

  • 映射 map

  • 函数 function


**值类型在传递参数时,进行值拷贝,引用类型则是引用拷贝。**主要是赋值的区别。

类型零值

零值也就是默认值,当一个类型声明之后没有被初始化,那么他的值就是零值,每种类型都有对应的零值,其中,引用类型对应的零值都为nil

数据类型零值

数字类型,包含整型、浮点型、复数

0

布尔型

false

字符串

""

结构体

结构体中的每个字段都是零值

引用类型:slicepointermapchannelfunctioninterface

nil

nil详解

在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil

注意,nil是一个值,像0一样,代表不存在

注意,使用nil,要注意下面几点:

  • 不是关键字和保留字

    因为不是关键字和保留字,只是一个标识符,类似int等,所以可以定义一个名称为nil的变量:

    var nil = errors.New("my god") 
  • 没有默认类型

    package main
    import (
        "fmt"
    )
    func main() {
        fmt.Printf("%T", nil) // .\main.go:9:10: use of untyped nil
        print(nil)
    }
  • 引用类型的零值(初始值)都是nil

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var m map[int]string
        var ptr *int
        var c chan int
        var sl []int
        var f func()
        var i interface{}
        fmt.Printf("%#v\n", m) // map[int]string(nil)
        fmt.Printf("%#v\n", ptr) // (*int)(nil)
        fmt.Printf("%#v\n", c) // (chan int)(nil)
        fmt.Printf("%#v\n", sl) // []int(nil)
        fmt.Printf("%#v\n", f) // (func())(nil)
        fmt.Printf("%#v\n", i) // <nil>
    }
  • 不同引用类型的nil值,指针是相同的

    package main
    import (
        "fmt"
    )
    func main() {
        var arr []int
        var num *int
        fmt.Printf("%p\n", arr) // 0x0
        fmt.Printf("%p", num) // 0x0
    }
  • 不同引用类型的nil值,占用大小不同

    package main
    import (
        "fmt"
        "unsafe"
    )
    func main() {
        var p *struct{}
        fmt.Println( unsafe.Sizeof( p ) ) // 8
        var s []int
        fmt.Println( unsafe.Sizeof( s ) ) // 24
        var m map[int]bool
        fmt.Println( unsafe.Sizeof( m ) ) // 8
        var c chan string
        fmt.Println( unsafe.Sizeof( c ) ) // 8
        var f func()
        fmt.Println( unsafe.Sizeof( f ) ) // 8
        var i interface{}
        fmt.Println( unsafe.Sizeof( i ) ) // 16
    }
  • 两个nil字面量比较

    package main
    import (
        "fmt"
    )
    func main() {
        fmt.Println(nil==nil)  // 报错
    }
  • 两个值都为nil的变量不可比较

    package main
    import (
        "fmt"
    )
    func main() {
        // 不同类型变量
        var m map[int]string
        var ptr *int
        fmt.Printf(m == ptr) // invalid operation: arr == ptr (mismatched types []int and *int)
    }
    package main
    import (
        "fmt"
    )
    func main() {
        // 相同类型变量
        var s1 []int
        var s2 []int
        fmt.Printf(s1 == s2) // invalid operation: s1 == s2 (slice can only be compared to nil)
    }
  • 变量(有可能是nil 或者 非nil)可以与nil值比较:

    package main
    import (
        "fmt"
    )
    func main() {
        var s1 []int
        fmt.Println(s1 == nil) // true
    }

基本数据类型

数值型

整型

无符号整型

数据类型存储空间(字节)值范围数据级别默认值

uint8

1

0 ~ 255

0

uint16

2

0 ~65535

6万多

0

uint32

3

0 ~ 4294967295

40多亿

0

uint64

4

0 ~ 18446744073709551615

大到没有概念

0

uint

系统决定,无符号整型

32位系统位int32,64位系统位int64

0

var ui2 uint8 = 1
fmt.Printf("类型 uint8, 长度=%d字节 \n", unsafe.Sizeof(ui2))
var ui3 uint16 = 1
fmt.Printf("类型 uint16, 长度=%d字节 \n", unsafe.Sizeof(ui3))
var ui4 uint32 = 1
fmt.Printf("类型 uint32, 长度=%d字节 \n", unsafe.Sizeof(ui4))
var ui5 uint64 = 1
fmt.Printf("类型 uint64, 长度=%d字节 \n", unsafe.Sizeof(ui5))

有符号整型

数据类型存储空间(字节)值范围数据级别默认值

int8

1

-128 ~ 127

正负百

0

int16

2

-32768 ~ 32767

正负3万多

0

int32

3

-2147483648 ~ 2147483647

正负大20多亿

0

int64

4

-9223372036854775808 ~ 9223372036854775807

正负大到没有概念

0

int

系统决定,有符号整型

32位系统位int32,64位系统位int64

0

var i2 int8 = 1
fmt.Printf("类型 int8, 长度=%d字节 \n", unsafe.Sizeof(i2))
var i3 int16 = 1
fmt.Printf("类型 int16, 长度=%d字节 \n", unsafe.Sizeof(i3))
var i4 int32 = 1
fmt.Printf("类型 int32, 长度=%d字节 \n", unsafe.Sizeof(i4))
var i5 int64 = 1
fmt.Printf("类型 int64, 长度=%d字节 \n", unsafe.Sizeof(i5))

字符整型

数据类型存储空间(字节)作用

byte

uint8的别名

用与表示一个ASCII

rune

int32的别名

用于表示一个Unicode代码点

// 获取对应字符的ASCII码值
var a byte = 'a'
fmt.Println(a)

// 将字符串转换为Unicode码点
first := "Hello, 世界"
fmt.Println([]rune(first))

表示一个ASCII:

var ch byte = 65
var ch byte = '\x41' // 16进制
var ch byte = 'A'

表示一个Unicode代码点:

var ch byte ='\u0041' // ASCII码值 六十进制41 十进制65 对应大写字母A
fmt.Printf("%c", ch) // ASCII对应单个字节,故可以使用byte类型接收

var ch2 rune = '\u266b' // 对应ASCII字符 🎵
fmt.Printf("%c", ch2) // 占用四个字节,故使用rune接收

var ch3 int64 = '\U0001F4B8' // 部分ASCII码值(比如生僻汉字),会占用四个字节以上,💸
fmt.Printf("%c", ch3)  // 所以使用`\U`大写U字母来表示一个Unicode字符,占用8个字节,不够前面补0

浮点型

数据类型存储空间(字节)值范围数据级别默认值

float32(不常用)

3

IEEE-754 1.401298464324817070923729583289916131280e-45 ~ 3.402823466385288598117041834516925440e+38

精度6位小数

0

float64

4

IEEE-754 4.940656458412465441765687928682213723651e-324 ~ 1.797693134862315708145274237317043567981e+308

精度15位小数

0

fmt.Println("---------------- 浮点型 ------------------")
f0 := 3.14
fmt.Printf("类型推断: %T \n", f0)
var f1 float32 = 1
fmt.Printf("类型 float32, 长度=%d字节 \n", unsafe.Sizeof(f1))
var f2 float64 = 1
fmt.Printf("类型 float64, 长度=%d字节 \n", unsafe.Sizeof(f2))
// float32可能会有精度丢失,所以通常使用float64
var f3 float32 = 314e-2 // 314 / 10^2
fmt.Printf("指数方式表示: %f \n", f3)
var f4 float32 = 0.0314e2 // 0.0314 * 10^2
fmt.Printf("指数方式表示: %f \n", f4)

复数

我们把形如 z=a+bi(a、b均为实数)的数称为复数。其中,a 称为实部,b 称为虚部,i 称为虚数单位。当 z 的虚部 b=0 时,则 z 为实数;当 z 的虚部 b≠0 时,实部 a=0 时,常称 z 为纯虚数。复数域是实数域的代数闭包,即任何复系数多项式在复数域中总有根。

复数的运算:

  • 加法法则:(a+bi)+(c+di)=(a+c)+(b+d)i`

  • 减法法则:(a+bi)-(c+di)=(a-c)+(b-d)i`

  • 乘法法则:(a+bi)(c+di)=(ac-bd)+(bc+ad)i

数据类型存储空间(字节)默认值

complex64

8

(0+0i)

complex128

16

(0+0i)

var name complex128 = complex(x, y)

复数的值由三部分组成 RE + IMi,其中 RE 是实数部分,IM 是虚数部分,REIM 均为 float 类型,而最后的 i 是虚数单位。

复数的定义和运算:

var x complex128 = complex(1, 2) // 1. 入门+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x + y)               // "(4+6i)" 加法运算 
fmt.Println(x * y)               // "(-5+10i)"  惩罚运算
fmt.Println(real(x * y))         // "-5"  获取实部
fmt.Println(imag(x * y))         // "10"  获取虚部

布尔型

数据类型存储空间(字节)值范围默认值

bool

1

true,false

false

var a bool = true
fmt.Printf("bool的类型为: %T, 长度为 %d \n", a, unsafe.Sizeof(a))

字符串

  • 字符串是一个不可变的UTF-8字符序列,并且每个属于ASCII码的字符占用单个字节,其他字符则是占用2-4个字节

  • 不同于C、C++、Java,他们的UTF-8字符长度至少会占用两个字节

  • Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码

  • 双引号字符串支持转义字符

访问字符

var a string = "Hello 世界"
fmt.Printf("%c \n", []byte(a)[0]) // H
fmt.Printf("%c \n", []rune(a)[7]) // 界

字符拼接 "+"

fmt.Println("a" + "b") // 注意,其他类型不会进行字符串转换

多行字符串

多行字符串中的内容都会原样输出,转义字符不会生效。通常用语内嵌代码或者数据

const str = `第一行
第二\n行
第三行`
fmt.Println(str)

输出结果为:

第一行
第二\n行
第三行

类型转换

数值类型相互转换

// GO语言数据类型转换,只有显示转换(强制类型转换),没有隐式转换
fmt.Println("----------- 基本数据类型互相转换 -------------")
var i int = 100
var f float32 = float32(i)
fmt.Println(f)

// 注意,将大范围值转换为小范围值,可能会造成数据损失
var i1 int64 = 8888
var i2 int8 = int8(i1)
fmt.Println(i2)

// 赋值左右数据类型一致,否则会报错
var n1 int32 = 12
var n2 int64 = int64(n1) + 31
fmt.Println(n2)

基本类型 -> string

fmt.Println("--------------基本数据类型转换为string------------")
// 使用fmt.Sprintf(), S代表返回string,printf代表格式化打印
var s = fmt.Sprintf("%d", 100)
fmt.Println(s)

r1 := strconv.FormatInt(678, 10) // 将int数字678转换为10进制字符串
r2 := strconv.FormatInt(678, 16) // 将int数字678转换为16进制字符串
fmt.Println(r1)
fmt.Println(r2)

// 参数1 要进行format的浮点数
// 参数2 格式化格式,参见API文档
// 参数3 保留小数点为3位
// 参数4 代表该浮点数的bit大小,如果是float64则为64
f1 := strconv.FormatFloat(10.02, 'f', 3, 64)
fmt.Println(f1)

r3 := strconv.FormatBool(true)
fmt.Println(r3)

string->基本类型

fmt.Println("--------------string转换为基本类型------------")
b, _ := strconv.ParseBool("true")
fmt.Println(b)
i, _ := strconv.ParseInt("A", 16,64) // 将字符串转换, 第二个参数代表该字符串是16进制,第三个值是值数据的类型int64
fmt.Println(i)
f, _ := strconv.ParseFloat("12.00865", 64)
fmt.Println(f)

// 无效类型转换,将会返回对应类型的默认值
b2, _ := strconv.ParseBool("e")
fmt.Println(b2)

派生数据类型

派生数据类型又称为复合数据类型,是由基本类型复合而来。

指针

基本类型也有对应的指针类型

*int
*float32
*bool
...

通过&创建指针变量

var i = 100
// 通过取地址符&定义一个指针变量,指针变量通过 *type表示
var ptr *int = &i
// 指针变量是存放指定变量值得内存地址的引用
fmt.Println(ptr) // 0xc00001a0b8

// 初始化指针变量值的时候,初始值一定是地址
// 下面代码会报错
//var b = 89
//var ptrb *int = b

通过*获取指针变量对应的值

fmt.Println(*ptr) // 100,会根据指针变量的地址0xc00001a0b8,找到这个地址对应的内存空间中的值

改变指针变量指向的值

*ptr = 200
fmt.Println(i) // 200
fmt.Println(&i) // 再次查询地址将会发现地址已经发生变化

// 什么类型的指针,接收的一定是相应类型的值,如果不是会报错
// 下面代码会报错
//var c int32 =16
//var ptrc *float32 = &c

通过new()函数创建指针变量

str := new(string)
*str = "你好"
fmt.Println(*str)

数组

定义数组

// 定义数组变量,不初始化
var balance [10]float32
// 初始化数组,没有赋值的元素将会赋予类型默认值
balance = [10]float32{0, 1, 2, 3, 4, 5}
fmt.Println(balance)

// 定义并初始化数组
var balance2 = [10]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance2)

var balance3 [10]float32 = [10]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance3)

balance4 := [10]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance4)

// 定义数组,不显式指定数组长度
balance5 := [...]float32 {1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance5)

下标访问

// 访问数组元素:下标直接访问
fmt.Println(balance5[0])

遍历数组:for

// 访问数组元素:遍历数组
for i := 0; i < len(balance5); i++ {
  fmt.Printf("%f ", balance5[i])
  if i == len(balance5) - 1 {
    fmt.Println()
  }
}

遍历数组:for range

// for range 遍历
for index, value := range balance5 {
  fmt.Printf("[%d]=%f ", index, value)
  if index == len(balance5) - 1 {
    fmt.Println()
  }
}

数组比较

必须保证数组类型长度相同,才能进行数组比较:

a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // 编译错误:无法比较 [2]int == [3]int

多维数组

// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1. 入门 和 3 的元素
array = [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化数组中指定的元素
array = [4][2]int{1: {0: 20}, 3: {1: 41}}

切片

  • 切片是对数组连续片段的引用

  • 这个片段可以是整个数组,也可以是指定起始位置的子集

  • 切片内部主要包括三个部分

    • data,指向切片所在数组的开始元素位置的指针

    • len,切片的长度

    • cap,切片的容量(底层数组的长度)

定义切片:从已有数组生成

package main
import "fmt"

func main() {
	// 切片建立在一个数组中,需要先定义一个数组
	var intarr [6]int = [6]int{3,4,4,1,2,7}
	// 根据上面的数组,构建一个切片
	// 切片的长度是不固定的,所以不需要写切片长度
	var slice0 []int = intarr[1:3] // 这个切面是针对数组intarr的切片,其切取元素位置范围为[1. 入门, 3)
	// 输出数组
	fmt.Println("数组为:", intarr) // [3 4 4 1. 入门 2 7]
	fmt.Println("切片为:", slice0) // [4 4]
	fmt.Println("切片的容量为:", cap(slice0)) // 5
}

从开始切到结尾

var slice []int = intarr[:]

从指定位置切到结尾

var slice []int intarr[2:]

从开始切刀指定位置

var slice []int intarr[:5]

重置切片:空切片

var slice []int intarr[0:0]

定义切片:直接定义

切片声明与数组声明很像,只不过切片的声明没有长度;这种方式声明切片与make()函数效果一致,底层都会创建一个不可访问的数组

var strList []string = []string{} // 声明一个空的切片

定义切片:使用make()函数构造切片

make( []Type, size, cap ) // 创建一个切片,指定类型、长度、初始容量

a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))

切片容量:cap()

cap(切片)

切片长度:len()

len(切片)

添加元素:append()

  • 在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变

  • 切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,也就是扩容因子为2

package main

import "fmt"

func main() {
	var a []int

	// 在切片尾部追加元素
	a = append(a, 1) // 追加1个元素
	fmt.Println(a)
	a = append(a, 2, 3, 4) // 追加多个元素, 手写解包方式
	fmt.Println(a)
	a = append(a, []int{5, 6}...) // 追加一个切片, 切片需要解包,三个点代表对数组/切片解包
	fmt.Println(a)

	// 在切片头部追加元素
	a = append([]int{0}, a...) // 在开头添加1个元素,
	fmt.Println(a)
	a = append([]int{-3, -2, -1}, a...) // 在开头添加1个切片
	fmt.Println(a)

	// 在切片指定位置插入元素
	// 在切片第四个位置插入数字100
	a = append(a[:4], append([]int{100}, a[4:]...)...)
	fmt.Println(a)
}
// 切片可以进行链式调用

复制:copy()

复制需要保证切片类型一致

copy( destSlice, srcSlice []T) int

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

删除元素

切片的删除实际上仍然是根据当前切片切割,比如删除头部元素:

a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素

删除中间位置:

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

删除尾部元素:

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

遍历切片:range

// 创建一个整型切片,并赋值
slice := []int{10, 20, 30, 40}
// 迭代每个元素,并显示其值
for _, value := range slice {
    fmt.Printf("Value: %d\n", value)
}

多维切片

var sliceName [][]...[]sliceType

//声明一个二维切片
var slice [][]int
//为二维切片赋值
slice = [][]int{{10}, {100, 200}}

映射

map,键值对映射结构,一种元素对(pair)的无序集合,pair 对应一个 key(索引)和一个 value(值),所以这个结构也称为关联数组或字典,这是一种能够快速寻找值的理想结构,给定 key,就可以迅速找到对应的 value

  • key不可以重复

  • key的类型不能是:slicemapfunction

  • value的不做限制

定义map

package main

import "fmt"

func main() {
	var m1 map[string]int = make(map[string]int, 10) // map的容量不受限制,该参数10代表此map创建时的初始容量
	m1["语文"] = 88
	m1["数学"] = 98
	fmt.Println(m1) // map[数学:98 语文:88]

	m2 := map[string]int{
		"语文": 88,
		"数学": 98,
	}
	fmt.Println(m2) // map[数学:98 语文:88]
}

遍历map: range

scene := make(map[string]int)
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
for k, v := range scene {
    fmt.Println(k, v)
}

添加/更新键值对

scene["china"] = 960

删除键值对: delete()

scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
delete(scene, "brazil")
for k, v := range scene {
    fmt.Println(k, v)
}

清空map

go没有提供清空map的方法,有两种方法可以清空map:

  • 遍历调用delete()

  • 给变量重新赋予一个新创建的map

结构体

结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。

定义结构体

type 类型名 struct {
    字段1 字段1类型
    字段2 字段2类型

}

type用于自定义类型,定义type可以对结构体进行复用

type Point struct {
    X int
    Y int
}

同类型变量可将字段放在一行

type Color struct {
    R, G, B byte
}

空结构体,没有意义

type Ept struct {}

字段类型不受限制

结构体的字段类型不受限制,可以是基本类型,也可以是复合类型,比如函数等:

// Go program to illustrate the function
// as a field in Go structure
package main

import "fmt"

// Finalsalary of function type
type Finalsalary func(int, int) int

// Creating structure
type Author struct {
	name	 string
	language string
	Marticles int
	Pay	 int

	// Function as a field
	salary Finalsalary
}

// Main method
func main() {

	// Initializing the fields
	// of the structure
	result := Author{
		name:	 "Sonia",
		language: "Java",
		Marticles: 120,
		Pay:	 500,
		salary: func(Ma int, pay int) int {
			return Ma * pay
		},
	}

	// Display values
	fmt.Println("Author's Name: ", result.name)
	fmt.Println("Language: ", result.language)
	fmt.Println("Total number of articles published in May: ", result.Marticles)
	fmt.Println("Per article pay: ", result.Pay)
	fmt.Println("Total salary: ", result.salary(result.Marticles, result.Pay))
}

实例化结构体

基本实例化形式

type Point struct {
    X int
    Y int
}
var p Point
p.X = 10
p.Y = 20

实例化指针类型结构体(常用)

type Player struct{
    Name string
    HealthPoint int
    MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300

取地址符的实例化(常用)

对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作

type Command struct {
    Name    string    // 指令名称
    Var     *int      // 指令绑定的变量
    Comment string    // 指令的注释
}
var version int = 1
cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"

初始化结构体

键值类型初始化

type People struct {
    name  string
    child *People
}
relation := &People{
    name: "爷爷",
    child: &People{
        name: "爸爸",
        child: &People{
                name: "我",
        },
    },
}

多值列表初始化

  1. 字段顺序要保持一致

  2. 所有字段必须要初始化

package main

import _ "fmt"

func main() {
	type People struct {
		name  string
		child *People
	}
	relation := &People{
		"爷爷",
		&People{
			"爸爸",
			&People{
				name: "我",
			},
		},
	}
}

匿名结构体的初始化

package main
import (
    "fmt"
)
// 打印消息类型, 传入匿名结构体
func printMsgType(msg *struct {
    id   int
    data string
}) {
    // 使用动词%T打印msg的类型
    fmt.Printf("%T\n", msg)
}
func main() {
    // 实例化一个匿名结构体
    msg := &struct {  // 定义部分
        id   int
        data string
    }{  // 值初始化部分
        1024,
        "hello",
    }
    printMsgType(msg)
}

管道

参见:并发编程/channel

函数

接口

参照:面向对象/接口

类型定义

// 定义了一个person类型,
type Person struct {
  Name string
  Age  int
}

无论是类型还是类型别名,都可以为他们指定方法

类型别名

类型别名是Go1.9版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。再重构、升级代码时,经常会遇到变量名称变更,C/C++选择使用宏快速定义一段新的代码,而Golang则使用类型别名:

// Go1.9之前,类型byte、rune通过自定义类型定义
type byte uint8
type rune int32

// Go1.9 之后,类型byte、rune则通过类型别名定义
type byte = uint8
type rune = int32

定义类型别名:

// 类型别名定义的格式:
type TypeAlias = Type

// 根据内置类型创建类型别名
type myInt = int64

// 根据自定义类型创建类型别名
type myDuration = time.Duration

// 根据其他类型别名创建类型别名
type myInt2 = myInt

无论是类型还是类型别名,都可以为他们指定方法

最后更新于