这是我参与更文挑战的第4天,活动详情查看: 更文挑战

二、 golang 复合数据类型特别之处

复合数据类型也是由基础数量类型构造出来的,golang中主要有数组、slice(切片)、map和结构体四种。数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。

1. 数组

数组定义定义及初始化,数组长度是固定的,且为常量,在编译时已确定。

var q [3]int = [3]int{1, 2, 3} 

q := [...]int{1, 2, 3} // 或通过...占位,自动计算个数
复制代码
range
var a [3]int 
for index, value := range a {
    fmt.Printf("%d %d\n", index, value)
}

// 仅获取value
for _, value := range a {
    fmt.Printf(""%d\n", value)
}
复制代码

向函数传递数组时不允许在函数内部修改底层数组的元素。

main(){
	num := [3]int{1, 2, 3}
	myInt := 100
	changeNum(myInt, num)
	fmt.Println(num[0], myInt)

}
func changeNum(myInt int, num [3]int) {
	num[0] = 0
	myInt++
}
//output
1 100
复制代码

我们传入changeNum函数一个变量和数组,底层数组数据均未被修改。在golang中函数参数变量接收的是一个复制的副本,并不是原始调用的变量。如果要通过数组修改底层数组数据可以像其他语言一样传入数组指针。

main(){
	num := [3]int{1, 2, 3}
	changeNumPoint(&num)
	fmt.Println(num[0])

}
func changeNumPoint(num *[3]int) {
	num[0] = 0
}
//output
0
复制代码

2.slice

切片,和python中切片相似度较高,slice相比数组,其长度是可扩展的,slice创建一个合适大小的数组,然后slice的指针指向底层的数组。

slice 内部结构体

struct    Slice
    {    
        byte*    array;        // actual data
        uintgo    len;        // number of elements
        uintgo    cap;        // allocated number of elements
    };
复制代码

第一个指针是指向底层数组,后面是slice的长度和容量。在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。我们可使用内建make创建slice。

make([]T, len, cap) //cap省缺于len值相同
复制代码

slice长度: 切片包含的元素个数。

slice容量: 从它的第一个元素开始数,到其底层数组元素末尾的个数。使用append添加数据时,容量小于长度时会自动扩展为原来两倍。

slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素,上文提到数组传入函数是以副本形式。

main(){
	sliceNum := []int{4, 5, 6}
	changeSlice(sliceNum)
	fmt.Println(sliceNum[0]

}
func changeSlice(sliceNum []int) {
	sliceNum[0] = 0
}
//output
0
复制代码

使用append追加多个元素:

x = append(x, 4, 5, 6)
复制代码

copy(dest,src),对位复制。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{6, 7, 8}
copy(slice2, slice1)
复制代码

3.Map

哈希表,类似于python中dic字典,map是一个无序的key/value对的集合。

通slice一样,可以使用make创建一个map,也可以通过range遍历:

ages := make(map[string]int){"key":value}
复制代码

通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值。我们可以通过这个值来判断key索引的value是否存在。

age, ok := ages["key"]
if !ok { ... }
//可以优化为
if age, ok := ages["bob"]; !ok { ... }
复制代码

map的比较

func equal(x, y map[string]int) bool {
    if len(x) != len(y) {
        return false
    }
    for k, xv := range x {
        if yv, ok := y[k]; !ok || yv != xv {
            return false
        }
    }
    return true
}
复制代码

使用map做统计

var m = make(map[string]int)

func k(list []string) string { return fmt.Sprintf("%q", list) }

func Add(list []string)       { m[k(list)]++ }
func Count(list []string) int { return m[k(list)] }
复制代码

4.结构体

结构体定义

type Point struct {
 
    Salary    int
    ManagerID int
}

var p Point //结构体变量

//另一种写法
type Point struct{ X, Y int }

p := Point{1, 2}
复制代码

和变量、函数一样,构体成员名字是以大写字母开头的,那么该成员就是导出的。一个结构体可能同时包含导出和未导出的成员。

结构体作为函数的参数和返回值:

func Scale(p Point, factor int) Point {
    return Point{p.X * factor, p.Y * factor}
}

fmt.Println(Scale(Point{1, 2}, 5)) // "{5 10}"
复制代码

指针的方式(提高效率),可以在函数中修改结构体成员。

func AwardAnnualRaise(e *Employee) {
    e.Salary = e.Salary * 105 / 100
}
复制代码

结构体嵌套,使定义简化和清晰。

//点
type Point struct {
    X, Y int
}
//圆
type Circle struct {
    Center Point
    Radius int
}
//轮
type Wheel struct {
    Circle Circle
    Spokes int
}
复制代码

访问成员

var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8
w.Circle.Radius = 5
w.Spokes = 20
复制代码
匿名成员

我们修改圆和轮为如下:

type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spokes int
}
复制代码

访问成员:

var w Wheel
w.X = 8           
w.Y = 8         
w.Radius = 5     
w.Spokes = 20
复制代码

从上面来看匿名成员特性只是对访问嵌套成员的点运算符提供了简短的语法糖。,其实在go中匿名成员并不要求是结构体类型;其实任何命名的类型都可以作为结构体的匿名成员。这个机制可以用于将一些有简单行为的对象组合成有复杂行为的对象。组合会在go面向对象中会大展拳脚。

文章有不足的地方欢迎在评论区指出。

欢迎收藏、点赞、提问。关注顶级饮水机管理员,除了管烧热水,有时还做点别的。