【GO语言编程】(二)

第一个go程序

注释

package main   // 声明 main 包

import "fmt"   // 导入 fmt 包,打印字符串时需要用到

func main(){   // 声明 main 主函数
    fmt.Println("Hello, Go!")  // 打印 Hello Go!
}

在这里插入图片描述
与C语言一样,GO的注释分为两种:

  • 单行注释
fmt.Println("Hello, Go!")  // 这里的部分作为说明,一般不跨行
  • 多行注释
func main() {
	/*
		以下两行代码都是将信息打印在屏幕上
	 */
	fmt.Println("可爱淞淞超爱学习!")
	fmt.Printf("仿生程序员会梦见代码羊吗")
}

package,包的创建

对于GO语言,package,也就是包,是其管理单位。
每个GO源文件必须先声明它所属的包,所以我们会看到每个GO源文件的开头都是一个package声明。

package name   //其中,package是声明 包 名的关键字,name则是包的名字

Go 语言的包与文件夹是一一对应的,
它具有以下几点特性:

-在 一个目录下的同级文件属于同一个包。

  • 包的名称可以与其目录名不同。
  • main 包是 Go 语言程序的入口包,一个 Go 语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包,那么编译时将会出错,无法生成可执行文件。

import,包的导入

import语句用于导入程序中所依赖的包,导入的包名使用双引号包围,格式如下:

import "name"   //  import 是导入包的关键字,name 为所导入包的名字。

另外有一点需要注意,导入的包中不能含有代码中没有使用到的包,否则 Go 编译器会报编译错误,例如 imported and not used: "xxx",”xxx” 表示包名。

变量与常量

变量

声明变量的一般形式是使用 var 关键字。

  • 方法一:声明一个变量, 默认的值是 0

    package main
    
    import "fmt"
    
    func main(){
        // 方法一:声明一个变量, 默认的值是0
        var a int
        fmt.Println("a = ", a)
        fmt.Printf("a的类型是: %T\n", a)
    }
    

在这里插入图片描述

  • 方法二:声明一个变量, 并初始化一个值

    package main
    
    import "fmt"
    
    func main(){
        // 方法二:声明一个变量, 初始化一个值
        var b int = 100
        fmt.Printf("b = %d, type of b = %T\n", b, b)
    
        var bb string = "千朵玫瑰带来的黎明"
        fmt.Printf("bb = %s, bb的类型是: %T\n", bb, bb)
    }
    

在这里插入图片描述

  • 方法三:在初始化的时候,可以省去数据类型,通过值去自动匹配当前变量的数据类型

    package main
    
    import "fmt"
    
    func main(){
    
        // 方法三:在初始化的时候,可以省去数据类型,通过值去自动匹配当前变量的数据类型
        var c = 100
        fmt.Printf("c = %d, type of c = %T\n", c, c)
    
        var cc = "赤土之王与三朝圣者"
        fmt.Printf("cc = %s, cc的类型是: %T\n", cc, cc)
    }
    

    在这里插入图片描述

  • 短声明,只能在函数内

    package main
    
    import "fmt"
    
    func main(){
    
        // 方法四:(常用的方法) 省去var关键字,使用:=,既推导数据类型又赋值
        // 注: 短声明是在函数或方法内部使用, 不支持全局变量声明!!!!
        e := 100
        fmt.Printf("e = %d, e的类型是: %T\n", e, e)
    
        f := "为了果实、种子还有树"
        fmt.Printf("f = %s, f的类型是: %T\n", f, f)
    }
    
  • 多变量声明

    package main
    
    import "fmt"
    
    func main(){
    	// 声明多个变量
        var xx, yy int = 100, 200
        var kk, wx = 300, "write_code_666(电子羊)"
    
        var (
            nn int = 100
            mm bool = true
        )
    }
    

常量

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

  • 定义一个常量,使用 const 关键字。常量定义的时候就要赋值。

    package main
    
    import "fmt"
    
    func main(){
        // 常量(只读属性)
        const length int = 10
        // length = 100  // 常量是不允许被修改的
        fmt.Println("length = ", length)
    }
    

    在这里插入图片描述

  • 使用 const 来定义枚举类型

    package main
    
    import "fmt"
    
    // const来定义枚举类型
    const (
        BEIJING = 0
        SHANGHAI = 1
        SHENZHEN = 2
    )
    
    func main() {
        fmt.Println("BEIJING = ", BEIJING)      // 0
        fmt.Println("SHANGHAI = ", SHANGHAI)    // 1
        fmt.Println("SHENZHEN = ", SHENZHEN)    // 2
    }
    

在这里插入图片描述

  • iota

    iota 是 Go 语言的常量计数器,只能在常量的表达式中使用。
    iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。使用 iota 能简化定义,在定义枚举时很有用

    package main
    
    import "fmt"
    
    // const来定义枚举类型
    const (
        // 可以在const()中添加一个关键字iota, 每行的iota都会累加1, 第一行的iota默认是0
        BEIJING = 10 * iota   // iota = 0
        SHANGHAI			  // iota = 1
        SHENZHEN			  // iota = 2
    )
    
    func main() {
        fmt.Println("BEIJING = ", BEIJING)      // 0
        fmt.Println("SHANGHAI = ", SHANGHAI)    // 10
        fmt.Println("SHENZHEN = ", SHENZHEN)    // 20
    }
    

在这里插入图片描述

关键字

关键字是 Go 语言中预先保留的单词,在程序中有特殊含义,不能用来定义变量或常量。

Go 语言中有 25 个关键字:

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

基础数据类型

与C语言基本相同

整型

package main

import (
	"fmt"
	"math"

)

// 有符号整型
func Integer() {
	var num8 int8 = 127
	var num16 int16 = 32767
	var num32 int32 = math.MaxInt32
	var num64 int64 = math.MaxInt64
	var num int = math.MaxInt
	fmt.Printf("num8的类型是 %T, num8的大小 %d, num8是 %d\n",
		num8, unsafe.Sizeof(num8), num8)
	fmt.Printf("num16的类型是 %T, num16的大小 %d, num16是 %d\n",
		num16, unsafe.Sizeof(num16), num16)
	fmt.Printf("num32的类型是 %T, num32的大小 %d, num32是 %d\n",
		num32, unsafe.Sizeof(num32), num32)
	fmt.Printf("num64的类型是 %T, num64的大小 %d, num64是 %d\n",
		num64, unsafe.Sizeof(num64), num64)
	fmt.Printf("num的类型是 %T, num的大小 %d, num是 %d\n",
		num, unsafe.Sizeof(num), num)
}

// 无符号整型
func unsignedInteger() {
	var num8 uint8 = 128
	var num16 uint16 = 32768
	var num32 uint32 = math.MaxUint32
	var num64 uint64 = math.MaxUint64
	var num uint = math.MaxUint
	fmt.Printf("num8的类型是 %T, num8的大小 %d, num8是 %d\n",
		num8, unsafe.Sizeof(num8), num8)
	fmt.Printf("num16的类型是 %T, num16的大小 %d, num16是 %d\n",
		num16, unsafe.Sizeof(num16), num16)
	fmt.Printf("num32的类型是 %T, num32的大小 %d, num32是 %d\n",
		num32, unsafe.Sizeof(num32), num32)
	fmt.Printf("num64的类型是 %T, num64的大小 %d, num64是 %d\n",
		num64, unsafe.Sizeof(num64), num64)
	fmt.Printf("num的类型是 %T, num的大小 %d, num是 %d\n",
		num, unsafe.Sizeof(num), num)
}

func main() {
	Integer()
	println("---------------------------------------")
	unsignedInteger()
}

在这里插入图片描述

  • 除非对整型的大小有特定的需求,否则你通常应该使用 int 表示整型宽度,在 32 位系统下是 32 位,而在 64 位系统下是 64 位。表示范围:在 32 位系统下是 -2147483648 ~ 2147483647 ,而在 64 位系统是 -9223372036854775808 ~ 9223372036854775807
  • 对于 int8int16 等这些类型后面有跟一个数值的类型来说,它们能表示的数值个数是固定的。所以,在有的时候:例如在二进制传输、读写文件的结构描述(为了保持文件的结构不会受到不同编译目标平台字节长度的影响)等情况下,使用更加精确的 int32int64 是更好的。

浮点型

浮点型表示存储的数据是实数,如 3.145。关于浮点型的说明,如表所示。

类型字节数说明
float32432 位的浮点型
float64864 位的浮点型

示例程序:

package main

import (
	"fmt"
	"math"

)
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)
}

func main() {
     showFloat()
}

在这里插入图片描述

  • 通过上面的程序,我们知道浮点数能表示的数值很大,但是浮点数的精度却没有那么大:
    • float32 的精度只能提供大约 6 个十进制数(表示小数点后 6 位)的精度。
    • float64 的精度能提供大约 15 个十进制数(表示小数点后 15 位)的精度。

字符类型

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

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

声明示例如下:

package main

import "fmt"
import "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();
}

在这里插入图片描述

byte 类型只能表示 28个值,所以你想表示其他一些值,例如中文的话,就得使用 rune 类型

var y rune = '原'

字符串

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

示例程序:

var study string  	 		// 定义名为str的字符串类型变量
study = "《阿赫玛尔的故事 》"		// 将变量赋值

study2 := "《森林书》"		// 以自动推断方式初始化

有些字符串没有现成的文字代号,所以只能用转义字符来表示。常用的转义字符如表所示。

转 义 字 符含 义
\r回车符 return,返回行首
\n换行符 new line, 直接跳到下一行的同列位置
\t制表符 TAB
\'单引号
\"双引号
\\反斜杠

定义多行字符串的方法如下。

  • 双引号书写字符串被称为字符串字面量(string literal),这种字面量不能跨行。
  • 多行字符串需要使用反引号“`”,多用于内嵌源码和内嵌数据。
  • 在反引号中的所有代码不会被编译器识别,而只是作为字符串的一部分。

多行字符串定义方式如例所示:

package main
import "fmt"

func main() {
  var s1 string
	s1 = `
    		study := '你好,谢谢,最后是再见'
    		fmt.Println(study)
			`
	fmt.Println(s1)
}

在这里插入图片描述

布尔类型

关于 布尔(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)
}

func main() {
    showBool()
}

Tip:

  • 如果你学过其他编程语言或许会发现,布尔型可以参与数值运算,也可以与其他类型进行转换。但是在 Go 中,真值是用 true 表示,并且 不与 1 相等;同样的,假值是用 false 表示,并且 不与 0 相等。

复数型

复数型用于表示数学中的复数,如 1+2j、1-2j、-1-2j 等。在 Go 语言中提供了两种精度的复数类型:complex64complex128 ,分别对应 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)
}

func main() {
   showComplex()
}

在这里插入图片描述

  • 当然,我们可以对声明进行简化,使用自然的方式书写复数:

    x := 1 + 2i
    y := 3 + 4i
    z := 5 + 6i
    

格式化输出

%%一个%字面量
%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 功能也更灵活,下面我们再讨论 slice

数组声明

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

package main

import "fmt"

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

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

func test02() {
	// 直接在声明时对数组进行初始化
	var arr1 = [5]int{15, 20, 25, 30, 35}
	fmt.Println(arr1)

	// 使用短声明
	arr2 := [5]int{15, 20, 25, 30, 35}
	fmt.Println(arr2)

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

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

	// 直接使用 ... 让编译器为我们计算该数组的长度
	arr5 := [...]int{15, 20, 25, 30, 35, 40}
	fmt.Println(arr5)
}

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

func test04() {
	// 定义多维数组
	arr := [3][2]string{
		{"1", "你好"},
		{"2", "谢谢"},
		{"3", "最后是 再见"}}
	fmt.Println(arr) // [[15 20] [25 22] [25 22]]
}

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

使用内置的 len 函数将返回数组中元素的个数,即数组的长度。

func arrLength() {
	arr := [...]string{"为了果实", "种子", "还有树"}
	fmt.Println("数组的长度是:", len(arr)) //数组的长度是: 3
}

在这里插入图片描述

数组遍历

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

func showArr() {
	arr := [...]string{"你好", "谢谢", "最后是再见"}
	for index, value := range arr {
		fmt.Printf("arr[%d]=%s\n", index, value)
	}

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

在这里插入图片描述

数组是值类型

Go 中的数组是值类型而不是引用类型。当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,不会影响原始数组。

func arrByValue() {
	arr := [...]string{"兰拉娜", "兰罗摩", "兰百梨迦"}
	copy := arr
	copy[0] = "Golang"
	fmt.Println(arr)
	fmt.Println(copy)
}

在这里插入图片描述

切片(Slice)

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

创建切片

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

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

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

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

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

当然,我们可以通过对数组进行片段截取创建一个切片。

arr := [5]string{"兰罗摩", "兰穆护昆达", "兰利遮", "兰娜库拉", "兰宁巴"}
var s1 = arr[1:4]
fmt.Println(arr) //
fmt.Println(s1)  //

在这里插入图片描述

切片的长度和容量

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

内置的 lencap 函数分别返回 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

Tips:

  • 由于 slice 是引用类型,所以你不对它进行赋值的话,它的默认值是 nil

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

切片元素的修改

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

func modifySlice() {
	var arr = [...]string{"正法炬书", "水天供书", "吉祥具书"}
	s := arr[:] //[0:len(arr)]
	fmt.Println(arr) //
	fmt.Println(s) //

	s[0] = "森林书"
	fmt.Println(arr) //[
	fmt.Println(s) //
}

在这里插入图片描述

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

追加切片元素

使用 append 可以将新元素追加到切片上。append 函数的定义是 func append(slice []Type, elems ...Type) []Type 。其中 elems ...Type 在函数定义中表示该函数接受参数 elems 的个数是可变的。这些类型的函数被称为可变函数。

  func appendSliceData() {
	s := []string{"终天的闭幕曲"}
	fmt.Println(s)
	fmt.Println(cap(s))

	s = append(s, "岩壑之崩")
	fmt.Println(s)
	fmt.Println(cap(s))

	s = append(s, "冰风回荡", "冰封交响曲 ")
	fmt.Println(s)
	fmt.Println(cap(s))

	s = append(s, []string{"哀息之茧", "燃尽之舞"}...)
	fmt.Println(s)
	fmt.Println(cap(s))
}

在这里插入图片描述

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

多维切片

类似于数组,切片也可以有多个维度。

func mSlice() {
	numList := [][]string{
		{"1", "捕风的异乡人"},
		{"2", "辞别久远之躯"},
		{"3", "千手百眼天下人间"},
	}
	fmt.Println(numList)
}

在这里插入图片描述

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 :

var steps2 map[string]string = map[string]string{
		"第一步": "穿越雾霭与森林",
		"第二步": "千朵玫瑰带来的黎明",
		"第三步": "赤土之王与三朝圣者",
}
fmt.Println(steps2)

在这里插入图片描述

或者

steps3 := map[string]string{
		"第一步": "穿越雾霭与森林",
		"第二步": "千朵玫瑰带来的黎明",
		"第三步": "赤土之王与三朝圣者",
}
fmt.Println(steps3)
Map 操作
  • 添加元素

    // 可以使用 `map[key] = value` 向 map 添加元素。
    steps3["第四步"] = "总监"
    
  • 更新元素

    // 若 key 已存在,使用 map[key] = value 可以直接更新对应 key 的 value 值。
    steps3["第四步"] = "CTO"
    
  • 获取元素

    // 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。
    fmt.Println(steps3["第四步"] )
    
  • 删除元素

    //使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。
    delete(steps3, "第四步")
    
  • 判断 key 是否存在

    // 如果我们想知道 map 中的某个 key 是否存在,可以使用下面的语法:value, ok := map[key]
    v3, ok := steps3["第三步"]
    fmt.Println(ok)
    fmt.Println(v3)
    
    v4, ok := steps3["第四步"]
    fmt.Println(ok)
    fmt.Println(v4)
    

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

  • 遍历 map

    // 遍历 map 中所有的元素需要用 for range 循环。
    for key, value := range steps3 {
        fmt.Printf("key: %s, value: %d\n", key, value)
    }
    
  • 获取 map 长度

    // 使用 len 函数可以获取 map 长度
    func createMap() {
      	//...
         fmt.Println(len(steps3))    // 4
    }
    
map 是引用类型

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

func mapByReference() {
		steps4 := map[string]string{
		"第一步": "穿越雾霭与森林",
		"第二步": "千朵玫瑰带来的黎明",
		"第三步": "赤土之王与三朝圣者",
	}
	fmt.Println("steps4: ", steps4)
	
	newSteps4 := steps4
	newSteps4["第一步"] = "穿越雾霭与烟林"
	newSteps4["第二步"] = "花神之舞"
	newSteps4["第三步"] = "千柱沙原"
	fmt.Println("steps4: ", steps4)
  
	fmt.Println("newSteps4: ", newSteps4)
 
}

在这里插入图片描述

map 作为函数参数传递时也会发生同样的情况。

指针

指针也是一种类型,也可以创建变量,称之为指针变量。指针变量的类型为 *Type,该指针指向一个 Type 类型的变量。指针变量最大的特点就是存储的某个实际变量的内存地址,通过记录某个变量的地址,从而间接的操作该变量。

Pointers-in-Golang

创建指针

创建指针有三种方法。

  • 首先定义普通变量,再通过获取该普通变量的地址创建指针:

    GO
    // 定义普通变量 x
    x := "可爱电子羊!"
    // 取普通变量 x 的地址创建指针 p
    ptr := &x
     fmt.Println(ptr)
     fmt.Println(*ptr)
    
  • 先创建指针并分配好内存,再给指针指向的内存地址写入对应的值:

    GO
    // 创建指针
    ptr2 := new(string)
    // 给指针指向的内存地址写入对应的值
    *ptr2 = "更可爱的电子羊!!"
    fmt.Println(ptr2)
    fmt.Println(*ptr2)
    
  • 首先声明一个指针变量,再从其他变量获取内存地址给指针变量:

    GO
    // 定义变量 x2
    x2 := "超可爱的仿生电子羊!!!"
    // 声明指针变量
    var p *string
    // 指针初始化
    p = &x2
    fmt.Println(p)
    

在这里插入图片描述

Tip:

上面举的创建指针的三种方法对学过 C 语言的人来说可能很简单(比如在下),但没学过指针相关知识的人可能不太明白,特别是上面代码中出现的指针操作符 &*

  • & 操作符可以从一个变量中取到其内存地址。
  • * 操作符如果在赋值操作值的左边,指该指针指向的变量;* 操作符如果在赋值操作符的右边,指从一个指针变量中取得变量值,又称指针的解引用。

通过下面的例子,你应该就会比较清楚的理解上面两个指针操作符了。

GO
package main

import "fmt"

func main() {
	x := "山巅雪国的记忆"
	ptr := &x
	fmt.Println("x = ", x)   
	fmt.Println("*ptr = ", *ptr) 
	fmt.Println("&x = ", &x) 
	fmt.Println("ptr = ", ptr)   
}

在这里插入图片描述

指针的类型

*(指向变量值的数据类型) 就是对应的指针类型。

GO
func pointerType() {
	mystr := "雪葬的星银"
	myint := 1
	mybool := false
	myfloat := 3.2
	fmt.Printf("type of &mystr is :%T\n", &mystr)
	fmt.Printf("type of &myint is :%T\n", &myint)
	fmt.Printf("type of &mybool is :%T\n", &mybool)
	fmt.Printf("type of &myfloat is :%T\n", &myfloat)
}

func main() {
	//...
	pointerType()
}

在这里插入图片描述

指针的零值

如果指针声明后没有进行初始化,其默认零值是 nil

GO
func zeroPointer() {
	x := "这两周小组作业怎么这么多"
	var ptr *string
	fmt.Println("ptr is ", ptr)
	ptr = &x
	fmt.Println("ptr is ", ptr)
}
func main() {
	//...
	zeroPointer()
}

在这里插入图片描述

函数传递指针参数

在函数中对指针参数所做的修改,在函数返回后会保存相应的修改。

GO
package main

import (
	"fmt"
)

func changeByPointer(value *int) {
	*value = 200
}

func main() {
	x3 := 99
	p3 := &x3
	fmt.Println("执行changeByPointer函数之前p3是", *p3)
	changeByPointer(p3)
	fmt.Println("执行changeByPointer函数之后p3是", *p3)
}

在这里插入图片描述

运行程序输出如上,函数传入的是指针参数,即内存地址,所以在函数内的修改是在内存地址上的修改,在函数执行后还会保留结果。

指针与切片

切片与指针一样是引用类型,如果我们想通过一个函数改变一个数组的值,可以将该数组的切片当作参数传给函数,也可以将这个数组的指针当作参数传给函数。但 Go 中建议使用第一种方法,即将该数组的切片当作参数传给函数,因为这么写更加简洁易读。

GO
package main

import "fmt"

// 使用切片
func changeSlice(value []int) {
	value[0] = 200
}

// 使用数组指针
func changeArray(value *[3]int) {
	(*value)[0] = 200
}

func main() {
	x := [3]int{10, 20, 30}
	changeSlice(x[:])
	fmt.Println(x) // [200 20 30]

	y := [3]int{100, 200, 300}
	changeArray(&y)
	fmt.Println(y) // [200 200 300]
}

在这里插入图片描述

Go 中不支持指针运算

学过 C 语言的人肯定知道在 C 中支持指针的运算,例如:p++ ,但这在 Go 中是不支持的。

GO
package main

func main() {
	x := [...]int{20, 30, 40}
	p := &x
	p++ // error
}

结构体

结构体(struct) 是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。学过 C 或 C++ 的人都知道结构体,但在 Go 中,没有像 C++ 中的 class 类的概念,只有 struct 结构体的概念,所以也没有继承。

结构体的声明

在 Go 语言 中使用下面的语法是对结构体的声明。

GO
type struct_name struct {
    attribute_name1   attribute_type
    attribute_name2   attribute_type
    ...
}

例如下面是定义一个名为 Lesson(课程) 的结构体。

GO
type Lesson struct {
	name   string //名称
	target string //学习目标
	spend  int    //学习花费时间
}

上面的代码声明了一个结构体类型 Lesson ,它有 nametargetspend 三个属性。可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑,如下面的代码所示。

GO
type Lesson2 struct {
    name, target    string
    spend             int
}

上面的结构体 Lesson 称为 命名的结构体(Named Structure) 。我们创建了名为 Lesson 的新类型,而它可以用于创建 Lesson 类型的结构体变量。

声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure)

GO
var Lesson3 struct {
    name, target    string
    spend             int
}

上面的代码创建了一个匿名结构体 lesson

创建命名的结构体

GO
package main

import "fmt"

type Lesson struct {
	name, target    string
	spend             int
}

func main() {
	// 使用字段名创建结构体
	lesson1 := Lesson{
		name: "《森林树》",
		target: "森林会记住一切",
		spend:  5,
	}
	// 不使用字段名创建结构体
	lesson2 := Lesson{"《森林书》", "有点饿了", 5}

	fmt.Println("lesson1 ", lesson1)
	fmt.Println("lesson2 ", lesson2)
}

在这里插入图片描述

上面的例子使用了两种方法创建了结构体,第一种是在创建结构体时使用字段名对每个字段进行初始化,而第二种方法是在创建结构体时不使用字段名,直接按字段声明的顺序对字段进行初始化。

创建匿名结构体

GO
package main

import "fmt"

func main() {
	// 创建匿名结构体变量
	lesson3 := struct {
		name, target string
		spend          int
	}{
		name:   "水天供书",
		target: "大大铁块不懂道理,兰百梨迦就让它懂道理",
		spend:   3,
	}

	fmt.Println("lesson3 ", lesson3)
}

在这里插入图片描述

结构体的零值(Zero Value)

当定义好的结构体没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。

package main

import "fmt"

type Lesson struct {
	name, target    string
	spend             int
}

func main() {
	// 不初始化结构体
	var lesson4 = Lesson{}

	fmt.Println("lesson4 ", lesson4)
}

初始化结构体

package main

import "fmt"

type Lesson struct {
	name, target    string
	spend             int
}

func main() {
	// 为结构体指定字段赋初值
	var lesson5 = Lesson{
		name: "正法炬书",
		target: "森林会记住一切",
	}

    // 上面的结构体变量 lesson5 只初始化了 name 和 target 字段, spend字段没有初始化,所以会被初始化为零值
	fmt.Println("lesson5 ", lesson5)
}

在这里插入图片描述

访问结构体的字段

点操作符 . 用于访问结构体的字段。

GO
package main

import "fmt"

type Person struct {
	name, gender    string
	age             int
}

func main() {
	var lesson6 = Lesson{
		name: "森林书",
		target: "森林与树的孩子们",
		spend: 50,
	}

	fmt.Println("lesson6 name: ", lesson6.name)
	fmt.Println("lesson6 target: ", lesson6.target)
	fmt.Println("lesson6 spend: ", lesson6.spend)
}

当然,使用点操作符 . 可以用于对结构体的字段的赋值。

GO
package main

import "fmt"

type Lesson struct {
	name, target    string
	spend             int
}

func main() {
	var lesson7 = Lesson{}
	lesson7.name = "从0到Go语言微服务架构师"
	lesson7.target = "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。"
	lesson7.spend = 50
	fmt.Println("lesson7 ", lesson7)
}

在这里插入图片描述

指向结构体的指针

GO
package main

import "fmt"

type Lesson struct {
	name, target    string
	spend             int
}

func main() {
	lesson8 := &Lesson{"无留陀", "失落的苗圃", 50}
	fmt.Println("lesson8 name: ", (*lesson8).name)
	fmt.Println("lesson8 name: ", lesson8.name)
}

在这里插入图片描述

在上面的程序中, lesson8 是一个指向结构体 Lesson 的指针,上面用 (*lesson8).name 访问 lesson8name 字段,上面的 lesson8.name 代替 (*lesson8).name 的解引用访问。

  • 注意:学过 C 语言的同学会认为lesson8->name才是正确的访问形式,但是在 Go 语言中,没有->访问的形式,访问结构体中的字段统一都是用.操作符

匿名字段

在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field)

GO
package main

import "fmt"

type Lesson4 struct {
	string
	int
}

func main() {
	lesson9 := Lesson4{"祖拜尔剧场", 50}
	fmt.Println("lesson9 ", lesson9)
	fmt.Println("lesson9 string: ", lesson9.string)
	fmt.Println("lesson9 int: ", lesson9.int)
}

上面的程序结构体定义了两个匿名字段,虽然这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以上面的结构体 Lesoon4 有两个名为 stringint 的字段。

嵌套结构体

结构体的字段也可能是另外一个结构体,这样的结构体称为 嵌套结构体(Nested Structs)

GO
package main

import "fmt"

type Author struct {
	name string
  	wx string
}

type Lesson5 struct {
	name,target string
	spend int
	author Author
}

func main() {
	lesson10 := Lesson5{
		name: "赛诺",
		spend: 50,
	}
	lesson10.author = Author{
		name: "妮露",
		wx: "write_code_666",
	}
	fmt.Println("lesson10 name:", lesson10.name)
	fmt.Println("lesson10 spend:", lesson10.spend)
	fmt.Println("lesson10 author name:", lesson10.author.name)
	fmt.Println("lesson10 author wx:", lesson10.author.wx)
}

上面的程序 Lesson5 结构体有一个字段 author ,而且它的类型也是一个结构体 Author

提升字段

结构体中如果有匿名的结构体类型字段,则该匿名结构体里的字段就称为 提升字段(Promoted Fields) 。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。就像刚刚上面的程序,如果我们把 Lesson 结构体中的字段 author 直接用匿名字段 Author 代替, Author 结构体的字段例如 name 就不用像上面那样使用 lesson10.author.wx 访问,而是使用 lesson10.wx 就能访问 Author 结构体中的 wx 字段。现在结构体 Authornamewx 两个字段,访问字段就像在 Lesson 里直接声明的一样,因此我们称之为提升字段。

GO
package main

import "fmt"

type Author struct {
	name string
  	wx string
}

type Lesson6 struct {
	name,target string
	spend int
	Author
}

func main() {
	lesson10 := Lesson6{
		name:   "赛诺",
		target: "大风纪官",
	}
	lesson10.author = Author{
		name: "艾尔海森",
		wx:   "write_code_666",
	}
	fmt.Println("lesson10 name:", lesson10.name)
	fmt.Println("lesson10 target:", lesson10.target)
	fmt.Println("lesson10 author wx:", lesson10.wx)
}

结构体比较

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 ==!= 运算符进行比较。可以通过==运算符或 DeeplyEqual()函数比较两个结构相同的类型并包含相同的字段值。因此下面两个比较的表达式是等价的:

GO
package main

import "fmt"

type  Lesson  struct{
	name,target string
	spend int
}

func main() {
	lesson11 := Lesson{
		name:   "赛诺",
		target: "大风纪官",
	}
	lesson12 := Lesson{
		name:   "赛诺",
		target: "大风纪官",
	}
	fmt.Println(lesson11.name == lesson12.name && lesson11.target == lesson12.target) // true
	fmt.Println(lesson11 == lesson12) // true
}

在这里插入图片描述

给结构体定义方法

在 Go 中无法在结构体内部定义方法,这一点与 C 语言类似。

GO
package main

import "fmt"

// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
	name,target string
	spend int
}

// PrintPersonInfo 定义一个与 Person 的绑定的方法
func (l Lesson) ShowLessonInfo() {
	fmt.Println("name:", l.name)
	fmt.Println("target:", l.target)
}

func main() {
	lesson13 := Lesson{
		name:   "艾尔海森",
		target: "教令院书记官",
	}
	lesson13.ShowPersonInfo()
}

上面的程序中定义了一个与结构体 Lesson 绑定的方法 ShowLessonInfo() ,其中 ShowLessonInfo 是方法名, (l Lesson) 表示将此方法与 Lesson 的实例绑定,这在 Go 语言中称为接收者,而 l 表示实例本身,相当于 Python 中的 self ,在方法内可以使用 实例本身.属性名称 来访问实例属性。

方法的参数传递方式

如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者。

GO
package main

import "fmt"

// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
	name,target string
	spend int
}

// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
func (l Lesson) ShowLessonInfo() {
	fmt.Println("name:", l.name)
	fmt.Println("target:", l.target)
}

// AddTime 定义一个与 Lesson 的绑定的方法,使 spend 值加 n
func (l *Lesson) AddTime(n int) {
	l.spend = l.spend + n
}

func main() {
	lesson13 := Lesson{
		name:   "艾尔海森",
		target: "教令院书记官",
    spend:50,
	}
	fmt.Println("添加add方法前")
	lesson13.ShowLessonInfo()
	lesson13.AddTime(5)
	fmt.Println("添加add方法后")
	lesson13.ShowLessonInfo()
}

在这里插入图片描述

函数

函数 是基于功能或逻辑进行封装的可复用的代码结构。将一段功能复杂、很长的一段代码封装成多个代码片段(即函数),有助于提高代码可读性和可维护性。由于 Go 语言是编译型语言,所以函数编写的顺序是无关紧要的。

函数的声明

在 Go 语言中,函数声明语法如下:

GO
func function_name(parameter_list) (result_list) {
    //函数体
}

函数的声明使用 func 关键词,后面依次接 function_name(函数名)parameter_list(参数列表)result_list(返回值列表) 以及 函数体

  • 形式参数列表:函数的参数名以及参数类型,这些参数作为局部变量,其值由参数调用者提供,函数中的参数列表和返回值并非是必须的。
  • 返回值列表:函数返回值的变量名以及类型,如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。
  • 如果有连续若干个参数的类型一致,那么只需在最后一个参数后添加该类型。
GO
package main

import "fmt"

// 函数返回一个无名变量,返回值列表的括号省略
func sum(x int, y int) int {
	return x + y
}

// 无参数列表和返回值
func printBookName() {
	fmt.Println("《森林书》")
}

// 参数的类型一致,只在最后一个参数后添加该类型
func sub(x , y int) int {
	return x - y
}

func main() {
	fmt.Println("56 + 1 = ", sum(56, 1))
	printBookName()
	fmt.Println("58 - 2 =", sub(58, 2))
}

在这里插入图片描述

可变参数

上面的程序参数个数都是固定的,在 Go 语言 中也可以使用可变参数的函数。

多个类型一致的参数

在参数类型前面加 ... 表示一个切片,用来接收调用者传入的参数。注意,如果该函数下有其他类型的参数,这些其他参数必须放在参数列表的前面,切片必须放在最后。

GO
package main

import "fmt"

func show(args ...string) int {
	sum := 0
	for _, item := range args {
        fmt.Println(item)
		sum += 1
	}
	return sum
}

func main() {
	fmt.Println(show("兰","那","罗"))
}

在这里插入图片描述

多个类型不一致的参数

如果传多个参数的类型都不一样,可以指定类型为 ...interface{} ,然后再遍历。

GO
package main

import "fmt"

func PrintType(args ...interface{}) {
	for _, arg := range args {
		switch arg.(type) {
		case int:
			fmt.Println(arg, "type is int.")
		case string:
			fmt.Println(arg, "type is string.")
		case float64:
			fmt.Println(arg, "type is float64.")
		default:
			fmt.Println(arg, "is an unknown type.")
		}
	}
}

func main() {
	PrintType(57, 3.14, "兰那罗")
}

解序列

使用 ... 可以用来解序列,能将函数的可变参数(即切片)一个一个取出来,传递给另一个可变参数的函数,而不是传递可变参数变量本身。

GO
package main

import "fmt"

func main() {
	var s []string
	s = append(s, []string{"兰", "那", "罗"}...)
	fmt.Println(s)
}

在这里插入图片描述

函数的返回值

当函数没有返回值时,函数体可以使用 return 语句返回。在 Go 中一个函数可以返回多个值。

GO
package main

import "fmt"

func showBookInfo(bookName, authorName string) (string, error) {
	if bookName == "" {
		return "", errors.New("图书名称为空")
	}
	if authorName == "" {
		return "", errors.New("作者名称为空")
	}
	return bookName + ",作者:" + authorName, nil
}

func main() {
	bookInfo, err := showBookInfo("《千朵玫瑰带来的黎明》", "须弥")
	fmt.Printf("bookInfo = %s, err = %v", bookInfo, err)
}

当然,在 Go 中支持返回带有变量名的值。可以将上面的函数修改如下:

GO
func showBookInfo2(bookName, authorName string) (info string, err error) {
	info = ""
	if bookName == "" {
		err = errors.New("图书名称为空")
		return
	}
	if authorName == "" {
		err = errors.New("作者名称为空")
		return
	}
    // 不使用 := 因为已经在返回值那里声明了
	info = bookName + ",作者:" + authorName
  	// 直接返回即可
	return
}

匿名函数

没有名字的函数就叫 匿名函数 ,它只有函数逻辑体,而没有函数名。匿名函数只拥有短暂的生命,一般都是定义后立即使用。

GO
func (parameter_list) (result_list) {
	body
}

内部方法与外部方法

在 Go 语言中,函数名通过首字母大小写实现控制对方法的访问权限。

  • 当方法的首字母为 大写 时,这个方法对于 所有包 都是 Public ,其他包可以随意调用。
  • 当方法的首字母为 小写 时,这个方法是 Private ,其他包是无法访问的。

包(package) 用于组织 Go 源代码,提供了更好的可重用性与可读性。Go 语言有超过 100 个的标准包,可以用 go list std | wc -l 命令查看标准包的具体数目,标准库为大多数的程序提供了必要的基础组件。

main 包

首先,我们先来看看 main 包,该包中包含一个 main() 函数,该函数是程序运行的入口。

package packagename 代码指定了某一源文件属于某一个包。它应该放在每一个源文件的第一行。例如我们 Go 的第一个程序。

GO
// hello go
package main

import "fmt"

func main() {
    fmt.Println("《千朵玫瑰带来的黎明》")
}

package main 这一行指定该文件属于 main 包。import "fmt" 语句用于导入一个已存在的名为 fmt 的包。

创建包

下面我们创建自定义的 book 包,其中,属于某一个包的源文件都应该放置于一个单独命名的文件夹里,按照 Go 的惯例,应该用包名命名该文件夹。所以应当先创建一个 book 文件夹,位于该目录下创建一个 book.go 源文件,里面实现我们自定义的数学加法函数。请注意函数名的首字母要大写。

GO
// book.go
package book

func ShowBookInfo(bookName, authorName string) (string, error) {
	if bookName == "" {
		return "", errors.New("图书名称为空")
	}
	if authorName == "" {
		return "", errors.New("作者名称为空")
	}
	return bookName + ",作者:" + authorName, nil
}

Tips:

  • 导出名字(Exported Names)
    • 我们将 book 包中的函数 ShowBookInfo 首字母大写。在 Go 中这具有特殊意义。在 Go 中,任何以大写字母开头的变量或者函数都是被导出的名字。其它包只能访问被导出的函数和变量。在这里,我们需要在 main 包中访问 ShowBookInfo 函数,因此会将它们的首字母大写。
    • 如果在 book.go 中将函数名从 ShowBookInfo 变为 showBookInfo ,并且在 main.go 中调用 book.showBookInfo 函数,则该程序编译不通过。因为如果想在包外访问一个函数,它应该首字母大写。

导入包

使用包之前我们需要导入包,在 GoLand 中会帮你自动导入所需要的包。导入包的语法为 import path ,其中 path 可以是相对于工作区文件夹的相对路径,也可以是绝对路径。

GO
package main

import (
	"fmt"
	"book"
)

func main() {
	bookName := "《千朵玫瑰带来的黎明》"
	author := "须弥"
	bookInfo, _ := book.ShowBookInfo(bookName, author)
	fmt.Println("bookInfo = ", bookInfo)
}

导入包可以单行导入也可以多行导入,像上面的程序代码就是多行导入的例子,一般我们也建议使用多行导入,当然你也可以使用单行导入:

GO
import "fmt"
import "book"

使用别名

如果我们导入了两个具有同一包名的包时会产生冲突,这时我们可以为其中一个包定义别名:

GO
import (
    "crypto/rand"
    mrand "math/rand" // 将名称替换为 mrand 避免冲突
)

当然,我们也可以使用别名代替名字很长的包名。

使用点操作

导入一个包后,如果要使用该包中的函数,都要使用 包名.方法名 语法进行调用,对于一些使用高频的包,例如 fmt 包,每次调用打印函数时都要使用 fmt.Println() 进行调用,很不方便。我们可以在导入包的时,使用 import . package_path 语法。从此,我们打印再也不用加 fmt 了。

GO
import . "fmt"

func main() {
    Println("hello, world")
}

但这种用法,会有一定的隐患,就是导入的包里可能有函数,会和我们自己的函数发生冲突。

包的初始化

每个包都允许有一个或多个 init 函数, init 函数不应该有任何返回值类型和参数,在代码中也不能显式调用它,当这个包被导入时,就会执行这个包的 init 函数,做初始化任务, init 函数优先于 main 函数执行。该函数形式如下:

GO
func init() {
}

包的初始化顺序:首先初始化 包级别(Package Level) 的变量,紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。如果一个包导入了另一个包,会先初始化被导入的包。尽管一个包可能会被导入多次,但是它只会被初始化一次。

包的匿名导入

之前说过,导入一个没有使用的包编译会报错。但有时候我们只是想执行包里的 init 函数来执行一些初始化任务的话应该怎么办呢?

我们可以使用匿名导入的方法,使用 空白标识符(Blank Identifier)

GO
import _ "fmt"

由于导入时会执行该包里的 init 函数,所以编译仍会将此包编译到可执行文件中。

流程控制

所谓流程控制就是指“程序怎么执行”或者说“程序执行的顺序”。程序整体上确实是从上往下执行,但又不单纯是从上往下。
流程控制可分为三类:

  1. 顺序执行。这个非常简单,就是先执行第一行再执行第二行……这样依次从上往下执行。
  2. 选择执行。有些代码可以跳过不执行,有选择地执行某些代码。
  3. 循环执行。有些代码会反复执行。

条件语句

在 Go 中 条件语句模型 如下:

GO
if 条件1 {
  逻辑代码1
} else if  条件2 {
  逻辑代码2
} else if 条件 ... {
  逻辑代码 ...
} else {
  逻辑代码 else
}

如果分支的 condition 为真,则执行该分支 {} 之间的代码。在 Go 中,对于 {} 的位置有严格的要求,它要求 else if (或 else ) 和两边的花括号,必须在同一行。特别注意,即使在 {} 之间只有一条语句,这两个花括号也是不能省略的。

  • 单分支判断

    只有一个 if 为单分支判断:

    GO
    score := 88
    if score >= 60 {
        fmt.Println("成绩及格")
    }
    
  • 双分支判断

    if 和一个 else 为两分支判断:

    GO
    score := 88
    if score >= 60 {
        fmt.Println("成绩及格")
    } else {
        fmt.Println("成绩不及格")
    }
    
  • 多分支判断

    ifelse if 以及 else 为多分支判断:

    GO
    score := 88
    if score >= 90 {
        fmt.Println("成绩等级为A")
    } else if score >= 80 {
        fmt.Println("成绩等级为B")
    } else if score >= 70 {
        fmt.Println("成绩等级为C")
    } else if score >= 60 {
        fmt.Println("成绩等级为D")
    } else {
        fmt.Println("成绩等级为E 成绩不及格")
    }
    
  • 条件语句高级写法

    if 还有另外一种写法,它包含一个 statement 可选语句部分,该可选语句在条件判断之前运行。它的语法是:

    GO
    if statement; condition {
    }
    

    上面单分支判断的那个例子可以重写如下:

    GO
    if score := 88; score >= 60 {
        fmt.Println("成绩及格")
    }
    

选择语句

在 Go 选择语句模型 如下:

GO
switch 表达式 {
    case 表达式值1:
        业务逻辑代码1
    case 表达式值2:
        业务逻辑代码2
    case 表达式值3:
        业务逻辑代码3
    case 表达式值 ...:
        业务逻辑代码 ...
    default:
        业务逻辑代码
}

switch 语句是一个选择语句,用于将 switch 后的表达式的值与可能匹配的选项 case 后的表达式进行比较,并根据匹配情况执行相应的代码块,执行完匹配的代码块后,直接退出 switch-case 。如果没有任何一个匹配,就会执行 default 的代码块。它可以被认为是替代多个 if-else 子句的常用方式。注意:case 不允许出现重复项。例如,下面的例子会输出 Your score is between 80 and 90.

GO
grade := "B"
switch grade {
case "A":
    fmt.Println("Your score is between 90 and 100.")
case "B":
    fmt.Println("Your score is between 80 and 90.")
case "C":
    fmt.Println("Your score is between 70 and 80.")
case "D":
    fmt.Println("Your score is between 60 and 70.")
default:
    fmt.Println("Your score is below 60.")
}
  • 一个 case 多个条件

    在 Go 中, case 后可以接多个条件,多个条件之间是 的关系,用逗号 , 相隔。

    GO
    month := 5
    switch month {
    case 1, 3, 5, 7, 8, 10, 12:
        fmt.Println("该月份有 31 天")
    case 4, 6, 9, 11:
        fmt.Println("该月份有 30 天")
    case 2:
        fmt.Println("该月份闰年为 29 天,非闰年为 28 天")
    default:
        fmt.Println("输入有误!")
    }
    
  • 选择语句高级写法

    switch 还有另外一种写法,它包含一个 statement 可选语句部分,该可选语句在表达式之前运行。它的语法是:

    GO
    switch statement; expression {
    }
    

    可以将上面的例子改写为:

    GO
    switch month := 5; month {
    case 1, 3, 5, 7, 8, 10, 12:
        fmt.Println("该月份有 31 天")
    case 4, 6, 9, 11:
        fmt.Println("该月份有 30 天")
    case 2:
        fmt.Println("该月份闰年为 29 天,非闰年为 28 天")
    default:
        fmt.Println("输入有误!")
    }
    

这里 month 变量的作用域就仅限于这个 switch 内。

  • switch 后可接函数

    switch 后面可以接一个函数,只要保证 case 后的值类型与函数的返回值一致即可。

    GO
    package main
    
    import "fmt"
    
    func getResult(args ...int) bool {
     for _, v := range args {
      if v < 60 {
       return false
      }
     }
     return true
    }
    
    func main() {
     chinese := 88
     math := 90
     english := 95
    
     switch getResult(chinese, math, english) {
     case true:
      fmt.Println("考试通过")
     case false:
      fmt.Println("考试未通过")
     }
    }
    
  • 无表达式的 switch

    switch 后面的表达式是可选的。如果省略该表达式,则表示这个 switch 语句等同于 switch true ,并且每个 case 表达式都被认定为有效,相应的代码块也会被执行。

    GO
    score := 88
    switch {
    case score >= 90 && score <= 100:
        fmt.Println("grade A")
    case score >= 80 && score < 90:
        fmt.Println("grade B")
    case score >= 70 && score < 80:
        fmt.Println("grade C")
    case score >= 60 && score < 70:
        fmt.Println("grade D")
    case score < 60:
        fmt.Println("grade E")
    }
    

    switch-case 语句相当于 if-elseif-else 语句。

  • fallthrough 语句

    正常情况下 switch-case 语句在执行时只要有一个 case 满足条件,就会直接退出 switch-case ,如果一个都没有满足,才会执行 default 的代码块。不同于其他语言需要在每个 case 中添加 break 语句才能退出。使用 fallthrough 语句可以在已经执行完成的 case 之后,把控制权转移到下一个 case 的执行代码中。fallthrough 只能穿透一层,不管你有没有匹配上,都要退出了。fallthrough 语句是 case 子句的最后一个语句。如果它出现在了 case 语句的中间,编译会不通过。

    GO
    s := "森林书"
    switch {
    case s == "森林书":
        fmt.Println("森林书")
        fallthrough
    case s == "正法炬书":
        fmt.Println("正法炬书")
    case s != "水天供书":
        fmt.Println("水天供书")
    }
    

在这里插入图片描述

循环语句

与C及python中用法相同

// for 接三个表达式
for initialisation; condition; post {
   code
}

// for 接一个条件表达式
for condition {
   code
}

// for 接一个 range 表达式
for range_expression {
   code
}

// for 不接表达式
for {
   code
}
num := 0
for num < 4 {
    fmt.Println(num)
    num++
}

for num := 0; num < 4; num++ {
    fmt.Println(num)
}

str := "千朵玫瑰带来的黎明"
for index, value := range str{
    fmt.Printf("index %d, value %c\n", index, value)
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值