Go语言的基础知识

149 阅读12分钟

前端尝试转GO学习(第二天)

定义变量

package main

import "fmt"

func variableZeroValue() {
	// 使用 var 来声明,后面为变量名 + 类型
	var zeroNumber int
	var emptyString string
	// 打印 %q 可以空字符串
	fmt.Printf("%d %q\n", zeroNumber, emptyString)
}

func main() {
	variableZeroValue()
}

为变量赋初值,可以一行定义多个变量

func variableInitialValue() {
	var numberOne, numberTwo int = 3, 4
	var valueString string = "abc"
	fmt.Println(numberOne, numberTwo, valueString)
}

可以不需要显示类型,编译器可以自行推断出变量的类型。

func variableTypeDeduction() {
	var test1, test2, test3, test4 = 3, 4, true, "abc"
	fmt.Println(test1, test2, test3, test4)
}
不使用 var 声明变量
func variableShorter() {
	aa, bb, cc := 11, "abc", true
	fmt.Println(aa, bb, cc)
}

后续想要修改的话,不能使用 :=,只能使用 =

func variableShorter() {
	aa, bb, cc := 11, "abc", true
	bb = "def"
	fmt.Println(aa, bb, cc)
}

常量

表示固定的值。在计算机程序运行时,不会被程序修改的。

func consts() {
   const filename = "abc.txt"
   const a, b = 3, 4
   // Cannot assign to filename
   // 常量是不允许被修改的
   filename = "casda"
   fmt.Println(filename)
}

使用 const 来定义枚举类型

func enumsFunc() {
   const (
      React = 0
      Vue   = 1
      Go    = 2
   )

   fmt.Println("React", React)
   fmt.Println("Vue", Vue)
   fmt.Println("Go", Go)
}

使用 iota 可以进行自增。iota 在 const 关键字出现时将被重置为 0。

func enumsFunc() {
   const (
      React = 1 * iota
      Vue
      Go
   )

   fmt.Println("React", React) // 0
   fmt.Println("Vue", Vue)  // 1
   fmt.Println("Go", Go)  // 2
}
func enumsFunc() {
   const (
      React = 60 * iota
      Vue
      Go
   )

   fmt.Println("React", React)  // 0
   fmt.Println("Vue", Vue)  // 60 
   fmt.Println("Go", Go) // 120
}

数据类型

1. 整型

  • 有符号整型:int8、int16、int32、int64、int。
  • 无符号整型:uint8、uint16、uint32、uint64、uint。

2. 浮点型

浮点型表示如 3.1415926。

类型字节数说明
float32432 位的浮点型
float64864 位的浮点型
func showFloat() {
   var num1 float32 = math.MaxFloat32
   var num2 float64 = math.MaxFloat64
   fmt.Printf("num1的类型是%T,num1是%g\n", num1, num1)
   fmt.Printf("num2的类型是%T,num1是%g\n", num2, num2)
}

浮点数能表示的数值很大,精度没有那么大:

  • float32 的精度只能提供大约 6 个十进制数(表示小数点后 6 位)的精度。
  • float64 的精度能提供大约 15 个十进制数(表示小数点后 15 位)的精度。

3. 字符

字符串中的每一个元素叫作“字符”,定义字符时使用单引号。Go 语言的字符有两种,如表所示。

类 型字 节 数说 明
byte1表示 UTF-8 字符串的单个字节的值,表示的是 ASCII 码表中的一个字符,uint8 的别名类型
rune4表示单个 unicode 字符,int32 的别名类型
package main

import (
   "fmt"
   "unsafe"
)

func showChar() {
   var x byte = 65
   var y uint8 = 65
   fmt.Printf("x = %c\n", x) // x = A
   fmt.Printf("y = %c\n", y) // y = A
}

func sizeOfChar() {
   var x byte = 65
   fmt.Printf("x = %c\n", x)
   fmt.Printf("x 占用 %d 个字节\n", unsafe.Sizeof(x))

   var y rune = 'A'
   fmt.Printf("y = %c\n", y)
   fmt.Printf("y 占用 %d 个字节\n", unsafe.Sizeof(y))
}

func main() {
   showChar()
   sizeOfChar()
}

中文的话,就得使用 rune 类型

func chineseCharacters() {
   var c rune = '学'
   fmt.Printf("c = %c\n", c)
   fmt.Printf("y 占用 %d 个字节\n", unsafe.Sizeof(c))
}

4. 字符串

字符串在 Go 语言中是以基本数据类型出现的,使用字符串就像使用其他原生基本数据类型 int、float32、float64、bool 一样。

func stringFunc() {
   var study string // 定义名为str的字符串类型变量
   study = "学习学习学习" // 将变量赋值

   study2 := "正在学习中" // 以自动推断方式初始化
   
   // 多行字符串
   var study3 = `

        hhhhhhhh💰
    `
   fmt.Printf(study, study2, study3)
}

5. 布尔类型

布尔(bool)  类型:true 或者 false 。

func showBool() {
   a := true
   b := false
   fmt.Println("a=", a)
   fmt.Println("b=", b)
   fmt.Println("true && false = ", a && b)
   fmt.Println("true || false = ", a || b)
}

在 Go 中,真值是用 true 表示,并且 不与 1 相等;假值是用 false 表示,并且 不与 0 相等。

6. 复数型

复数型用于表示数学中的复数,如 1+2j、1-2j、-1-2j 等。在 Go 语言中提供了两种精度的复数类型:complex64 和 complex128 ,分别对应 float32 和 float64 两种浮点数精度,如表所示。

类 型字 节 数说 明
complex64864 位的复数型,由 float32 类型的实部和虚部联合表示
complex12816128 位的复数型,由 float64 类型的实部和虚部联合表示
func showComplex() {
   // 内置的 complex 函数用于构建复数
   var x complex64 = complex(1, 2)
   var y complex128 = complex(3, 4)
   var z complex128 = complex(5, 6)
   fmt.Println("x = ", x)
   fmt.Println("y = ", y)
   fmt.Println("z = ", z)

   // 内建的 real 和 imag 函数分别返回复数的实部和虚部
   fmt.Println("real(x) = ", real(x))
   fmt.Println("imag(x) = ", imag(x))
   fmt.Println("y * z = ", y*z)

   // 简化书写复数:
   x1 := 1 + 2i
   y2 := 3 + 4i
   z3 := 5 + 6i

   fmt.Println(x1, y2, z3)
   // (1+2i) (3+4i) (5+6i)
}

fmt 格式输出

格式含义
%%一个%字面量
%b一个二进制整数值(基数为 2),或者是一个(高级的)用科学计数法表示的指数为 2 的浮点数
%c字符型。可以把输入的数字按照 ASCII 码相应转换为对应的字符
%d一个十进制数值(基数为 10)
%f以标准记数法表示的浮点数或者复数值
%o一个以八进制表示的数字(基数为 8)
%p以十六进制(基数为 16)表示的一个值的地址,前缀为 0x,字母使用小写的 a-f 表示
%q使用 Go 语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%t以 true 或者 false 输出的布尔值
%T使用 Go 语法输出的值的类型
%x以十六进制表示的整型值(基数为十六),数字 a-f 使用小写表示
%X以十六进制表示的整型值(基数为十六),数字 A-F 使用小写表示

复杂数据类型

数组

数组 是一个由 固定长度 的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组。和数组对应的类型是 slice(切片)  ,它是可以动态的增长和收缩的序列, slice 功能也更灵活。

数组声明

可以使用 [n]Type 来声明一个数组。其中 n 表示数组中元素的数量, Type 表示每个元素的类型。

package main

import "fmt"

func test01() {
   // 声明时没有指定数组元素的值, 默认为零值
   var arr [5]int
   fmt.Println(arr) // [0 0 0 0 0]

   arr[0] = 1
   arr[1] = 2
   arr[2] = 3
   fmt.Println(arr) // [1 2 3 0 0]
}

func test02() {
   // 直接在声明时对数组进行初始化
   var arr1 = [5]int{1, 2, 3, 4, 5}
   fmt.Println(arr1) // [1 2 3 4 5]

   // 使用短声明
   arr2 := [5]int{1, 2, 3, 4, 5}
   fmt.Println(arr2) // [1 2 3 4 5]

   // 部分初始化, 未初始化的为零值
   arr3 := [5]int{2, 7}
   fmt.Println(arr3) // [2 7 0 0 0]

   // 可以通过指定索引,方便地对数组某几个元素赋值
   arr4 := [5]int{1: 100, 4: 200}
   fmt.Println(arr4) // [0 100 0 0 200]

   // 直接使用 ... 让编译器为我们计算该数组的长度
   arr5 := [...]int{1, 2, 3, 4, 5, 6}
   fmt.Println(len(arr5)) // 6
}

func test03() {
   // 特别注意数组的长度是类型的一部分,所以 [3]int 和 [5]int 是不同的类型
   arr1 := [3]int{1, 2, 3}
   arr2 := [5]int{1, 2, 3, 4, 5}
   fmt.Printf("type of arr1 is %T\n", arr1) // type of arr1 is [3]int
   fmt.Printf("type of arr2 is %T\n", arr2) // type of arr2 is [5]int
}

func test04() {
   // 定义多维数组
   arr := [3][2]string{
      {"前端", "React"},
      {"前端", "Vue"},
      {"后端", "Go"}}
   fmt.Println(arr) // [[前端 React] [前端 Vue] [后端 Go]]
}

func main() {
   test01()
   test02()
   test03()
   test04()
}
数组长度

内置 len 函数将返回数组长度

arr5 := [...]int{1, 2, 3, 4, 5, 6}
fmt.Println(len(arr5)) // 6
数组遍历

使用 for range 循环可以获取数组每个索引以及索引上对应的元素。

func showArr() {
   arr := [...]string{"React", "Vue", "Go"}
   for index, value := range arr {
      fmt.Printf("arr[%d]=%s\n", index, value)
      // arr[0]=React
      // arr[1]=Vue
      // arr[2]=Go
   }

   for _, value := range arr {
      fmt.Printf("value=%s\n", value)
      // value=React
      // value=Vue
      // value=Go
   }
}
数组是值类型

Go 中的数组是值类型而不是引用类型。当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。

如果对新变量进行更改,不会影响原始数组。

func arrCopy() {
   arr := [...]string{"React", "Vue", "Go"}
   copy := arr
   copy[2] = "Golang"
   fmt.Println(arr) // [React Vue Go]
   fmt.Println(copy) // [React Vue Golang]
}

切片(Slice)

切片是对数组的一个连续片段的引用,所以切片是一个引用类型。

切片本身不拥有任何数据,它们只是对现有数组的引用,每个切片值都会将数组作为其底层的数据结构。slice 的语法和数组很像,只是没有固定长度而已。

创建切片

使用 []Type 可以创建一个带有 Type 类型元素的切片。

// 声明整型切片
var numList []int

// 声明一个空切片
var numListEmpty = []int{}

也可以使用 make 函数构造一个切片,格式为 make([]Type, size, cap) 

numList := make([]int, 3, 5)

可以通过对数组进行片段截取创建一个切片。

func silceArr() {
   arr := [5]string{"React", "Vue", "Go", "Java", "Python"}
   var s1 = arr[1:4]
   fmt.Println(arr) // [React Vue Go Java Python]
   fmt.Println(s1)  // [)  // [Vue Go Java]
}
切片的长度和容量

一个 slice 由三个部分构成:指针 、 长度 和 容量 。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。

内置的 len 和 cap 函数分别返回 slice 的长度和容量。

s := make([]string, 3, 5)
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 5

如果切片操作超出上限将导致一个 panic 异常。

s := make([]int, 3, 5)
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3

slice 是引用类型,所以不进行赋值,它的默认值是 nil

var numList []int
fmt.Println(numList == nil) // true

切片之间不能比较,因此不能使用 == 操作符来判断两个 slice 是否含有全部相等元素。特别注意,如果你需要测试一个 slice 是否是空的,使用 len(s) == 0 来判断,而不应该用 s == nil 来判断。

var numList []int
fmt.Print(len(numList) == 0) // true
切片元素的修改

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。

func modifySlice() {
   var arr = [...]string{"React", "Vue", "Go", "Java", "Python"}
   s := arr[:]
   fmt.Println(arr) // [React Vue Go Java Python]
   fmt.Println(s)   // [React Vue Go Java Python]

   s[2] = "Golang"
   fmt.Println(arr) // [React Vue Golang Java Python]
   fmt.Println(s)   // [React Vue Golang Java Python]
}

arr[:] 没有起始值和结束值,默认就是 0 和 len(arr) 

追加切片元素

使用 append 可以将新元素追加到切片上。

append 函数的定义是

func append(slice []Type, elems ...Type) []Type

其中 elems ...Type 在函数定义中表示该函数接受参数 elems 的个数是可变的。这些类型的函数被称为可变函数。

func appendSliceData() {
   s := []string{"Vue"}
   fmt.Println(s)
   fmt.Println(cap(s))

   s = append(s, "React")
   fmt.Println(s)
   fmt.Println(cap(s))

   s = append(s, "Java", "Go")
   fmt.Println(s)
   fmt.Println(cap(s))

   s = append(s, []string{"C++", "Python"}...)
   fmt.Println(s)
   fmt.Println(cap(s))
}

当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。

Map

在 Go 语言中,map 是散列表(哈希表)的引用。它是一个拥有键值对元素的无序集合,在这个集合中,键是唯一的,可以通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量时间完成的。所有可比较的类型,如 整型 ,字符串 等,都可以作为 key 。

创建 Map

使用 make 函数传入键和值的类型,可以创建 map 。具体语法为 make(map[KeyType]ValueType) 。

// 创建一个键类型为 string 值类型为 int 名为 scores 的 map
scores := make(map[string]int)
steps := make(map[string]string)

也可以用 map 字面值的语法创建 map ,同时还可以指定一些最初的 key/value :

func makeMap() {
   var test01 map[string]string = map[string]string{
      "1": "Vue",
      "2": "React",
      "3": "Go",
   }
   fmt.Println(test01) // map[1:Vue 2:React 3:Go]
}

或者

test02 := map[string]string{
   "1": "Vue",
   "2": "React",
   "3": "Go",
}

fmt.Println(test02) 
Map 操作

添加元素

map[key] = value 向 map 添加元素。

test01["4"] = "Java"

更新元素

key 已存在,使用 map[key] = value 可以直接更新对应 keyvalue

test01["4"] = "python"

获取元素

直接使用 map[key] 即可获取对应 keyvalue 值, 如果 key 不存在,会返回其 value 类型的零值。

fmt.Print("获取元素", test01["4"]) // 获取元素python

删除元素

使用 delete(map, key) 可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。

delete(test01, "4")

判断 key 是否存在

map 中的某个 key 是否存在,可以使用下面的语法:value, ok := map[key]

v1, ok := test01["3"]
fmt.Println(ok) // true
fmt.Println(v1) // Go

v2, ok := test01["4"]
fmt.Println(ok)      // false
fmt.Printf("%q", v2) // ""

这个语句说明 map 的下标读取可以返回两个值,第一个值为当前 key 的 value 值,第二个值表示对应的 key 是否存在,若存在 ok 为 true ,若不存在,则 ok 为 false 。

遍历 map

遍历 map 中所有的元素需要用 for range 循环。

for key, value := range test02 {
   fmt.Printf("key: %s, value: %s\n", key, value)
}

获取 map 长度

使用 len 函数可以获取 map 长度

fmt.Println(len(test02)) // 3
map 是引用类型

当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。

func mapReference() {
   test03 := map[string]string{
      "1": "Vue",
      "2": "React",
      "3": "Go",
   }
   fmt.Println("test03: ", test03) // map[1:Vue 2:React 3:Go]
   test04 := test03
   test04["1"] = "1111"
   test04["2"] = "222"
   test04["3"] = "3333"
   fmt.Println("test03: ", test03) //  map[1:1111 2:222 3:3333]
   fmt.Println("test04: ", test04) //  map[1:1111 2:222 3:3333]
}

本文正在参加技术专题18期-聊聊Go语言框架