Go——复合数据类型

复合数据类型

顾名思义,复合数据类型就是由其他类型组合而成的类型。Go语言基本的复合数据类型有指针、数组、切片、字典(map)、通道、结构和接口,它们的字面量格式如下:

* pointerType	//指针类型使用*后面跟其指向的类型名
[n]elementType	//数组类型使用[n]后面跟数组元素类型来表示,n表示该数组的长度
[]elementType	//切片类型使用[]后面跟切片元素类型来表示
map[keyType]valueType	//map类型使用map[键类型]值类型来表示
chan valueType	//通道使用chan后面跟通道元素类型来表示

struct {	//结构类型使用st上uct{}将各个结构字段扩起来表示
	feildType feildType
	feildType feildType
}

interface {	//接口类型使用interface{}将各个方法括起来表示
	method1 (inputParams)(returnParams)
	method2 (inputParams)(returnParams)
}

1. 指针

Go语言支持指针,指针的声明类型为*T,Go同样支持多级指针**T。通过在变量名前加&来获取变量的地址。指针的特点如下:

  1. 在赋值语句中,*T出现在“=”左边表示指针声明,*T出现在“=”右边表示取指针指向的值(varName为变量名)。示例如下:
var a 11
p := &a	//*p和a的值都是11
  1. 结构体指针访问结构体字段仍然使用“.”点操作符,Go语言没有“->”操作符。例如:
type User struct {
	name string
	age  int
}
andes := User{
	name: "andes",
	age:  18,
}
ptr := &andes

fmt.Println(ptr.name, "+", ptr.age)
  1. Go不支持指针的运算
    G0由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在C和C++里面指针运算很容易出现问题,因此G0直接在语言层面禁止指针运算。例如:
a := 1234
p := &a
p++	//不允许,报non-numeric type*int错误
  1. 函数中允许返回局部变量的地址。
    Go编译器使用“栈逃逸”机制将这种局部变量的空间分配在堆上。例如:
func sum(a, b int) *int {
	sum := a + b
	return &sum //允许,sum会分配在heap上
}

2. 数组

数组的类型名是[n]elemetType,其中n是数组长度,elementType是数组元素类型。比如一个包含2个int类型元素的数组类型可表示为[2]it。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。

var arr [2]int                       //声明一个有两个整型的数组,但元素默认值都是0,一般很少这样使用
	array := [...]float64{7.0, 8.5, 9.1} //[...]后面跟字面量初始化列表

数组初始化:

	w := [3]int{1, 2, 3}      //指定长度和初始化字面量
	x := [...]int{1, 2, 3}    //不指定长度,但是由后面的初始化列表数量来确定其长度
	y := [3]int{1: 1, 2: 3}   //指定总长度,并通过索引值进行初始化,没有初始化元素时使用类型默认值
	z := [...]int{1: 1, 2: 3} //不指定总长度,通过索引值初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值
fmt.Println(w)//[1 2 3]
fmt.Println(x)//[1 2 3]
fmt.Println(y)//[0 1 3]
fmt.Println(z)//[0 1 3]

数组的特点

  1. 数组创建完长度就固定了,不可以再追加元素。
  2. 数组是值类型的,数组赋值或作为函数参数都是值拷贝。
  3. 数组长度是数组类型的组成部分,[10]int和[20]int表示不同的类型。
  4. 可以根据数组创建切片

数组相关操作

  1. 数组元素访问。示例如下:
aa := [...]int{1, 2, 3}
bb := aa[0]
for i, v := range aa {
	fmt.Println(i, ",", v)
}
  1. 数组长度。示例如下:
cc := [...]int{1, 2, 3}
alengh := len(cc)
for i := 0; i < alengh; i++ {
	fmt.Println(cc[i])
}

3. 切片

Go语言的数组的定长性和值拷贝限制了其使用场景,Go提供了另一种数据类型slice(中文为切片),这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型。例如:

// src/runtime/slice.go (go.19.1)

11 type slice struct {
12 	array unsafe.Pointer
13 	len int
14	 cap int
15 }

Go为切片维护三个元素——指向底层数组的指针、切片的元素数量和底层数组订的容量。具体结构如下图:
Go——复合数据类型_第1张图片

切片的创建:

  • 由数组创建
    创建语法如下:array[b:e],其中,array表示数组名;b表示开始索引,可以不指定,默认是0;e表示结束索引,可以不指定,默认是len(array)。array[b:e]表示创建一个包含e-b个元素的切片,第一个元素是array[b],最后一个元素是aray[e-1]。例如:
var array = [...]int{0, 1, 2, 3, 4, 5, 6} //创建有7个int类型元素的数组
s1 := array[0:4]
s2 := array[:4]
s3 := array[2:]
fmt.Println(s1) //[0 1 2 3]
fmt.Println(s2) //[0 1 2 3]
fmt.Println(s3) //[2 3 4 5 6]
  • 通过内置函数make创建切片
    注意:由make创建的切片各元素被默认初始化为切片元素类型的零值。例如:
//len=10,cap=10
a := make([]int, 10)

//len=10,cap=15
b := make([]int, 15)

fmt.Printf("%v\n", a)
fmt.Printf("%v\n", b)

这里要注意:直接声明切片类型变量是没有意义的。例如:

var a []int
fmt.Printf("%v\n", a) //结果为[]

此时切片a的底层的数组结构如下图:
Go——复合数据类型_第2张图片

切片支持的操作:

  • 内置函数len()返回切片的长度。
  • 内置函数cap()返回切片底层数组容量
  • 内置函数append()对切片追加元素。
  • 内置函数copy()用于复制一个切片。
x := [...]int{0, 1, 2, 3, 4, 5, 6}
y := make([]int, 2, 4)
z := x[0:3]

fmt.Println(len(y)) //2
fmt.Println(cap(y)) //4
y = append(y, 1)
fmt.Println(y)      // [0 0 1]
fmt.Println(len(y)) //3
fmt.Println(cap(y)) //4

y = append(y, z...)
fmt.Println(y)      // [0 0 1 0 1 2]
fmt.Println(len(y)) // 6
fmt.Println(cap(y)) // cap(y)=8,底层数组发生扩展

o := make([]int, 2, 2)
copy(o, z)          //copy只会复制o和z中长度最小的
fmt.Println(o)      // [0 1]
fmt.Println(len(o)) // 2
fmt.Println(cap(o)) // 2

字符串和切片的相互转换:

str := "hello,世界!" //通过字符串字面量初始化一个字符串str
aa := []byte(str)  //转为[]byte类型切片
bb := []rune(str)  //转为[]rune类型切片

4. map

Go语言内置的字典类型叫map。map的类型格式是:map[K]T,其中K可以是任意可以进行比较的类型,T是值类型。map也是一种引用类型。

map的创建:

  • 使用字面量创建。例如:
ma := map[string]int{"a": 1, "b": 2}
fmt.Println(ma["a"])
fmt.Println(ma["b"])
  • 使用内置的make函数创建。例如:
mp1 := make(map[int]string)
mp2 := make(map[int]string, 10)
mp1[1] = "tom"
mp2[1] = "pony"
fmt.Println(mp1[1]) //tom
fmt.Println(mp2[1]) //pony

map支持的操作:

  • map的单个键值访问格式为mapName[key],更新某个key的值时mapName[key]放到等号左边,访问某个key的值时mapName[key]放在等号的右边。
  • 可以使用range遍历一个map类型变量,但是不保证每次迭代元素的顺序。
  • 删除map中的某个键值,使用如下语法:delete(mapName,key)。delete是内置函数,用来删除map中的某个键值对。
  • 可以使用内置的len()函数返回map中的键值对数量。例如:
mmp := make(map[int]string)
mmp[1] = "tom"
mmp[2] = "pony"
mmp[3] = "jaky"
mmp[4] = "andes"
delete(mmp, 3)

fmt.Println(mmp[1])
fmt.Println(len(mmp)) //len函数返回map中的键值对的数量

for k, v := range mmp { //range支持遍历map,但不保证每次遍历次序是一样的
	fmt.Println("key=", k, "value=", v)
}

注意:

  • Go内置的map不是并发安全的,并发安全的map可以使用标准包sync中的map。
  • 不要直接修改map value内某个元素的值,如果想修改map的某个键值,则必须整体赋
    值。例如:
type User struct {
name string
	age  int
}
maa := make(map[int]User)
andes := User{
	name: "andes",
	age:  18,
}

maa[1] = andes
//ma[1].age = 19 //error,不能通过map引用直接修改
andes.age = 19
maa[1] = andes //必须整体赋值
fmt.Printf("%v\n", maa)

6. struct

Go中的suct类型和C类似,中文翻译为结构,由多个不同类型元素组合而成。这里面有两层含义:第一,struct结构中的类型可以是任意类型;第二,struct的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求)。

struct有两种形式:一种是struct类型字面量,另一种是使用type声明的自定义struct类型。

  1. struct类型字面量
    struct类型字面量的声明格式如下:
struct {
	FeildName FeildType
	FeildName FeildType
	FeildName FeildType
}
  1. 自定义struct类型
    自定义struct类型声明格式如下:
type TypeName struct {
	FeildName FeildType
	FeildName FeildType
	FeildName FeildType
}

实际使用struct字面量的场景不多,更多的时候是捅咕otype自定义一个新的类型来实现的。type是自定义类型的关键字,不但支持struct类型的创建,还支持任意其他子定义类型的创建。

  1. struct类型变量的初始化。示例如下:
type Person struct {
	Name string
	Age  int
}

type Student struct {
	*Person
	Number int
}

//按照类型声明顺序,逐个赋值
//不推荐这种初始化方式,一旦struct增加字段,则整个初始化语句会报错
a := Person{"tom", 21}

//土建这种使用Feild名字的初始化方式,没有指定的字段则默认初始化为类型的零值
p := &Person{
	Name: "tata",
	Age:  12,
}

s := Student{
	Person: p,
	Number: 110,
}

你可能感兴趣的:(Go,golang)