初始Go语言Day2|青训营笔记

65 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天


字跳的课真的好快啊,今天已经在讲进阶内容了,而我这个小菜鸟还得啃啃基础语法,今天继续更。。


数组

  • 定义:由若干相同类型的元素组成的序列
  • 数组的长度是固定的,声明后无法改变
  • 数组的长度是数组类型的一部分,eg:元素类型相同但是长度不同的两个数组是不同类型的
  • 需要严格控制程序所使用内存时,数组十分有用,因为其长度固定,避免了内存二次分配操作

示例

package main

import "fmt"

func main() {
	// 定义长度为 5 的数组
	var arr1 [5]int
	for i := 0; i < 5; i++ {
		arr1[i] = i
	}
	printHelper("arr1", arr1)

	// 以下赋值会报类型不匹配错误,因为数组长度是数组类型的一部分
	// arr1 = [3]int{1, 2, 3}
	arr1 = [5]int{2, 3, 4, 5, 6} // 长度和元素类型都相同,可以正确赋值

	// 简写模式,在定义的同时给出了赋值
	arr2 := [5]int{0, 1, 2, 3, 4}
	printHelper("arr2", arr2)

	// 数组元素类型相同并且数组长度相等的情况下,数组可以进行比较
	fmt.Println(arr1 == arr2)

	// 也可以不显式定义数组长度,由编译器完成长度计算
	var arr3 = [...]int{0, 1, 2, 3, 4}
	printHelper("arr3", arr3)

	// 定义前四个元素为默认值 0,最后一个元素为 -1
	var arr4 = [...]int{4: -1}
	printHelper("arr4", arr4)

	// 多维数组
	var twoD [2][3]int
	for i := 0; i < 2; i++ {
		for j := 0; j < 3; j++ {
			twoD[i][j] = i + j
		}
	}
	fmt.Println("twoD: ", twoD)
}

func printHelper(name string, arr [5]int) {
	for i := 0; i < 5; i++ {
		fmt.Printf("%v[%v]: %v\n", name, i, arr[i])
	}

	// len 获取长度
	fmt.Printf("len of %v: %v\n", name, len(arr))

	// cap 也可以用来获取数组长度
	fmt.Printf("cap of %v: %v\n", name, cap(arr))

	fmt.Println()
}

切片

切片组成要素:

  • 指针:指向底层数组
  • 长度:切片中元素的长度,不能大于容量
  • 容量:指针所指向的底层数组的总容量

常见初始化方式

  • 使用 make 初始化
slice := make([]int, 5)     // 初始化长度和容量都为 5 的切片
slice := make([]int, 5, 10) // 初始化长度为 5, 容量为 10 的切片
  • 使用简短定义
slice := []int{1, 2, 3, 4, 5}
  • 使用数组来初始化切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[0:3] // 左闭右开区间,最终切片为 [1,2,3]
  • 使用切片来初始化切片
sliceA := []int{1, 2, 3, 4, 5}
sliceB := sliceA[0:3] // 左闭右开区间,sliceB 最终为 [1,2,3]

长度和容量

package main

import (
	"fmt"
)

func main() {
	slice := []int{1, 2, 3, 4, 5}
	fmt.Println("len: ", len(slice))
	fmt.Println("cap: ", cap(slice))

	//改变切片长度
	slice = append(slice, 6)
	fmt.Println("after append operation: ")
	fmt.Println("len: ", len(slice))
	fmt.Println("cap: ", cap(slice)) //注意,底层数组容量不够时,会重新分配数组空间,通常为两倍
}

以上代码,预期输出如下:

len:  5
cap:  5
after append operation:
len:  6
cap:  12

注意点

  • 多个切片共享一个底层数组的情况

对底层数组的修改,将影响上层多个切片的值

package main

import (
	"fmt"
)

func main() {
	slice := []int{1, 2, 3, 4, 5}
	newSlice := slice[0:3]
	fmt.Println("before modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
	fmt.Println()

	newSlice[0] = 6
	fmt.Println("after modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
}

以上代码预期输出如下:

before modify underlying array:
slice:  [1 2 3 4 5]
newSlice:  [1 2 3]

after modify underlying array:
slice:  [6 2 3 4 5]
newSlice:  [6 2 3]
  • 使用 copy 方法可以避免共享同一个底层数组

示例代码如下:

package main

import (
	"fmt"
)

func main() {
	slice := []int{1, 2, 3, 4, 5}
	newSlice := make([]int, len(slice))
	copy(newSlice, slice)
	fmt.Println("before modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
	fmt.Println()

	newSlice[0] = 6
	fmt.Println("after modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
}

以上代码预期输出如下:

before modifying underlying array:
slice:  [1 2 3 4 5]
newSlice:  [1 2 3 4 5]

after modifying underlying array:
slice:  [1 2 3 4 5]
newSlice:  [6 2 3 4 5]

Map

在 Go 语言里面,map 一种无序的键值对, 它是数据结构 hash 表的一种实现方式,类似 Python 中的字典。

语法

使用关键字 map 来声明形如:

map[KeyType]ValueType

注意点:

  • 必须指定 key, value 的类型,插入的纪录类型必须匹配。
  • key 具有唯一性,插入纪录的 key 不能重复。
  • KeyType 可以为基础数据类型(例如 bool, 数字类型,字符串), 不能为数组,切片,map,它的取值必须是能够使用 == 进行比较。
  • ValueType 可以为任意类型。
  • 无序性。
  • 线程不安全, 一个 goroutine 在对 map 进行写的时候,另外的 goroutine 不能进行读和写操作,Go 1.6 版本以后会抛出 runtime 错误信息。

声明和初始化

  • 使用 var 声明
var cMap map[string]int  // 只定义, 此时 cMap 为 nil
fmt.Println(cMap == nil)
cMap["北京"] = 1  // 报错,因为 cMap 为 nil
  • 使用 make
cMap := make(map[string]int)
cMap["北京"] = 1

// 指定初始容量
cMap = make(map[string]int, 100)
cMap["北京"] = 1

说明:在使用 make 初始化 map 的时候,可以指定初始容量,这在能预估 map key 数量的情况下,减少动态分配的次数,从而提升性能。

  • 简短声明方式
cMap := map[string]int{"北京": 1}

map 基本操作

cMap := map[string]int{}

cMap["北京"] = 1 //写

code := cMap["北京"] // 读
fmt.Println(code)

code = cMap["广州"]  // 读不存在 key
fmt.Println(code)

code, ok = cMap["广州"]  // 检查 key 是否存在
if ok {
  fmt.Println(code)  
} else {
  fmt.Println("key not exist")  
}

delete(cMap, "北京") // 删除 key
fmt.Println("北京")

循环和无序性

cMap := map[string]int{"北京": 1, "上海": 2, "广州": 3, "深圳": 4}

for city, code := range cMap {
  fmt.Printf("%s:%d", city, code)
  fmt.Println()
}

线程不安全

cMap := make(map[string]int)

var wg sync.WaitGroup
wg.Add(2)

go func() {
	cMap["北京"] = 1
	wg.Done()
}()

go func() {
	cMap["上海"] = 2
	wg.Done()
}()

wg.Wait()

在 Go 1.6 之后的版本,多次运行此段代码,你将遇到这样的错误信息:

fatal error: concurrent map writes

goroutine x [running]:
runtime.throw(0x10c64b6, 0x15)
.....

解决之道:

  • 对读写操作加锁
  • 使用 security map, 例如 sync.map

map 嵌套

provinces := make(map[string]map[string]int)

provinces["贵阳"] = map[string]int{
  "南明区": 1,
  "花溪区": 2,
  "云岩区": 3,
  "乌当区": 4,
}

fmt.Println(provinces["北京"])

结构体

数组、切片和 Map 可以用来表示同一种数据类型的集合,但是当我们要表示不同数据类型的集合时就需要用到结构体。

结构体是由零个或多个任意类型的值聚合成的实体

关键字 typestruct 用来定义结构体:

type StructName struct{
    FieldName type
}

简单示例:定义一个学生结构体

package main

import "fmt"

type Student struct {
	Age     int
	Name    string
}

func main() {
	stu := Student{
		Age:     18,
		Name:    "name",
	}
	fmt.Println(stu)

	// 在赋值的时候,字段名可以忽略
	fmt.Println(Student{20, "new name"})

	return
}

通常结构体中一个字段占一行,但是类型相同的字段,也可以放在同一行,例如:

type Student struct{
    Age           int
    Name, Address string
}

一个结构体中的字段名是唯一的,例如一下代码,出现了两个 Name 字段,是错误的:

type Student struct{
    Name string
    Name string
}

结构体中的字段如果是小写字母开头,那么其他 package 就无法直接使用该字段,例如:

// 在包 pk1 中定义 Student 结构体
package pk1
type Student struct{
    Age  int
    name string
}
// 在另外一个包 pk2 中调用 Student 结构体
package pk2

func main(){
    stu := Student{}
    stu.Age = 18        //正确
    stu.name = "name"  // 错误,因为`name` 字段为小写字母开头,不对外暴露
}

结构体中可以内嵌结构体

但是需要注意的是:如果嵌入的结构体是本身,那么只能用指针。请看以下例子。

package main

import "fmt"

type Tree struct {
	value       int
	left, right *Tree
}

func main() {
	tree := Tree{
		value: 1,
		left: &Tree{
			value: 1,
			left:  nil,
			right: nil,
		},
		right: &Tree{
			value: 2,
			left:  nil,
			right: nil,
		},
	}

	fmt.Printf(">>> %#v\n", tree)
}

结构体是可以比较的

前提是结构体中的字段类型是可以比较的

package main

import "fmt"

type Tree struct {
	value       int
	left, right *Tree
}

func main() {
	tree1 := Tree{
		value: 2,
	}

	tree2 := Tree{
		value: 1,
	}

	fmt.Printf(">>> %#v\n", tree1 == tree2)
}

结构体内嵌匿名成员

声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员

package main

import "fmt"

type Person struct {
	Age  int
    Name string
}

type Student struct {
	Person
}

func main() {
	per := Person{
		Age:  18,
		Name: "name",
	}

	stu := Student{Person: per}

	fmt.Println("stu.Age: ", stu.Age)
	fmt.Println("stu.Name: ", stu.Name)
}

今天就先写到这吧