20小时快速入门go语言视频 - Day1

20小时快速入门go语言视频 - Day1



一、第一个 Go 程序

// 1.go语言以包作为管理单位(简单理解:一个文件夹就是一个 go 包)
// 2.每个go文件都必须在非注释的第一行代码中声明包名
// 3.一个文件夹(目录)下,只能有一个main包
// 4.main包中有且只有一个main()函数
package main // main 表示包的标识

import "fmt"

// 程序的入口,是从这里开始调用的
func main() {
	fmt.Println("你好 go")
}

注意:关键字 import 后面的最后一个元素是目录名,而不是包名。Golang 编译器在这个路径下寻找包。

1.1 入口

一个 go 工程有且只有一个入口函数 main()
注意:main() 函数不能有任何参数和返回值。
在这里插入图片描述

1.2 Golang 保留的关键字

在这里插入图片描述

1.3 Golang预定义标识符

在这里插入图片描述



二、数据类型

类型表示同一类的数据,计算机用来计算,计算前需要把数据存储起来。
Go 有四类数据类型:

  • 基本类型:数字、字符串、布尔。
  • 聚合类型:数组、结构。
  • 引用类型:指针、切片、映射、函数、通道。
  • 接口类型:接口。

2.1 数据类型的作用

告诉编译器,这个数据应该以多大的内存进行存储,方便内存分配空间。
例如:写了一个数字 10,以 byte 类型存储,在内存中就占 1 个字节的大小;以 int 类型存储,在内存中就占4个字节。

2.2 数据类型的命名规则

1.由字母、下划线、数字构成
2.不能以数字开头
3.不能使用关键字
4.严格区分大小写

2.3 变量

2.3.1 何为变量

程序运行期间,可以改变的量。简而言之:一开始给它一个值,之后可以改变它的值。

2.3.2 声明变量

变量在使用前(赋值、打印等操作),必须先声明
语法:var 变量名 数据类型
1.声明一个变量 var a int
2.同时声明多个变量 var b, c int

2.3.3 变量的声明及赋值

例1:

var a int
a = 10
2.3.4 变量的初始化

所谓变量的初始化就是:声明变量的时候同时赋值。
例1:

var b int = 10 // 声明变量时,同时赋值(一步到位)
// int是可以省略不写的

例2:

var b int
b = 20
// 此例中分了两步走:
// 1.先声明
// 2.后赋值

例3:
使用具体值初始化变量时,编译器会自动推断其类型。

var (
    firstName = "John"
    lastName  = "Doe"
    age       = 32
)
2.3.5 自动推导类型

注意:自动推导类型必须给初始化值,它是通过这个值来确定具体的数据类型
例1:

package main

import "fmt"

func main() {
	c := 30 // 编译器会根据 30 这个值来推导对应的数据类型
	// %T 打印变量所属的类型
	fmt.Printf("c type is : %T\n", c)
	// 运行结果:
	// c type is : int
}

自动推导类型的大致流程:先声明 c 的类型,再给 c 赋值为整数值 30。
注意:海象运算符 := 前面,必须要有新的变量,不然就会报错。因为海象运算符的流程是先声明变量的类型,再给变量赋值(几乎同时发生),如果没有新的变量,那么就会变成重复声明了。另外,在函数外无法使用该特性,函数外声明变量必须使用 var 关键词。

2.3.6 多重赋值
2.3.6.1 var() 包裹起来的多重声明
package main

import "fmt"

func main() {
	var (
		a int
		b float64
	) // 关键字var+(),把多个变量包裹起来
	a = 10
	b = 3.14

	fmt.Println(a, b) // 10 3.14
}
2.3.6.2 自动推导类型(官方推荐写法)

使用海象运算符 :=

func main() {
	a, b := 10, 3.14
	fmt.Println(a, b) // 10 3.14
}
2.3.7 匿名变量

使用单个下划线 _ 表示匿名变量,丢弃数据且不处理,也不会占用内存空间。

package main

import "fmt"

func main() {
	i, j := 10, 20
	tmp, _ := i, j // 这里,j就被丢弃掉了,不会处理j这个变量了
	fmt.Println(tmp, j) // 打印j的值依然是用之前的20
}
2.3.8 变量的输入

使用内建 fmt 包下的 Scan() 函数:

func main() {
	var a int
	fmt.Printf("输入变量a的值:")

	fmt.Scanf("%d", &a) // 取a的地址,此处会阻塞等待用户的输入
	// 或者下面更加简便的等价的写法:
	// fmt.Scan(&a) // 别忘了取地址
	fmt.Printf("刚才输入的值,a=%d\n", a)
}

/*
运行结果:
输入变量a的值:11
刚才输入的值,a=11
*/
2.3.9 变量使用注意事项

1.函数外的每个语句都必须以关键字开始。(varconstfunc 等)
2.海象运算符 := 不能在函数外使用。
3.下划线 _ 多用于占位,不会占用内存空间,表示丢弃该值。

2.4 常量

2.4.1 何为常量

程序运行期间,不能改变的量。一开始给了值,就不允许再改变了!

2.4.2 初始化常量

常量必须给值,所以就是直接初始化常量了,声明和赋值一步完成。

2.4.2.1 语法

const 常量名 [数据类型] = 值

2.4.2.2 例1,单个初始化
package main

import "fmt"

const a = 10
const b = 10

func main() {
	fmt.Println(a, b)
}
2.4.2.3 例2,包裹起来一起初始化

把例1的常量声明,用括号括起来,写在一起:

const (
	a = 10
	b = 10
)
2.4.2.4 例3,多重初始化
const a, b = 10, 3.14

func main() {
	fmt.Println(a, b) //10 3.14
}
2.4.3 iota枚举
2.4.3.1 iota的特性1

iota 是常量自动生成器,每一行,自动累加1。

2.4.3.2 iota的特性2

iota 只能在常量表达式中使用(只能在 const 中使用)。
特性1 + 特性2的示例:

package main

import "fmt"

const (
	a = iota
	b = iota
	c = iota
)

func main() {
	fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c) //a = 0, b = 1, c = 2
}
2.4.3.3 iota的特性3

遇到另一个 constiota 的值重新从 0 开始计算。

package main

import "fmt"

const (
	a = iota
	b
	c
)

const (
	d = iota
	e
)

func main() {
	const (
		f = iota
		g
		h
	)

	fmt.Println("a=", a)
	fmt.Println("b=", b)
	fmt.Println("c=", c)
	fmt.Println("d=", d)
	fmt.Println("e=", e)
	fmt.Println("f=", f)
	fmt.Println("g=", g)
	fmt.Println("h=", h)
}

/*
运行结果:
a= 0
b= 1
c= 2
d= 0
e= 1
f= 0
g= 1
h= 2
*/

在这里插入图片描述

2.4.3.4 iota的特性4

同一块 const 内,可以只写一个 iota 预定义标识符。

package main

import "fmt"

func main() {
	const (
		a1 = iota
		b1
		c1
		d1
	)

	fmt.Printf("a1 = %d, b1 = %d, c1 = %d, d1 = %d\n", a1, b1, c1, d1) //a1 = 0, b1 = 1, c1 = 2, d1 = 3
}
2.4.3.5 iota的特性5

如果是同一行,iota 值都一样。

package main

import "fmt"

func main() {
	const (
		i          = iota
		j1, j2, j3 = iota, iota, iota
		k          = iota
	)

	fmt.Printf("i=%d, j1=%d, j2=%d, j3=%d, k=%d\n", i, j1, j2, j3, k) //i=0, j1=1, j2=1, j3=1, k=2
}

原因:iota 是每一行自动累加 1,在同一行内的 iota 值都是相同的。

2.4.3.6 iota的特性6

iota 的值,只取决于它所在第几行(下标从 0 开始),可以理解成为:行索引。

package main

import "fmt"

func main() {
	const (
		i          = 10
		j1, j2, j3 = iota, iota, iota
		k          = 20
		l, m       = iota, iota
		n          = "abc"
		o          = iota
	)

	fmt.Printf("i=%d, j1=%d, j2=%d, j3=%d, k=%d, l=%d, m=%d, n=%s, o=%d\n", i, j1, j2, j3, k, l, m, n, o)
}

/*
运行结果:
i=10, j1=1, j2=1, j3=1, k=20, l=3, m=3, n=abc, o=5
*/

在这里插入图片描述

2.4.4 常量使用的注意事项
2.4.4.1 初始化常量不允许使用海象运算符:=

声明常量不允许使用海象运算符

2.4.4.2 常量不允许被修改

下例中,对常量重新赋值就会报错:
常量不允许修改

2.4.4.3 常量必须给值

常量表示不可变的值,不给初始值,怎么让编译器去常量化呢?
常量必须给值



三、基础数据类型

指定数据类型是告诉编译器,这个值需要分配多大的内存空间。

3.1 总述

3.1.1 Go 语言中的数据类型

以下是 Go 语言中的数据类型:
在这里插入图片描述

3.1.2 Go 语言的数据类型分类

1.基础类型(Basic Types)。包括了:数值类型(支持整型、浮点型、复数)、字符串类型、布尔类型。
2.符合类型(Aggregate Types)。包括了:数组、结构体。
3.引用类型(Reference Types)。包括了:指针、切片、map、channel、接口、函数。

3.1.3 注意数据类型的溢出

每个数据类型都有一个固定的取值范围,数据类型的值,不能超过这个范围。
数据类型的溢出有 2 种:编译时、运行时。

3.1.3.1 数据类型编译时的溢出

以下示例会导致编译时就报错:

func main() {
	var i uint8
	for i = 0; i < 270; i++ {
	}
}

/*
运行结果:
# command-line-arguments
.\main.go:5:15: constant 270 overflows uint8

Compilation finished with exit code 2
*/

这个示例就相当于语法错误了,数值已经溢出了该数据类型,编译都编不过。
使用 IDE,就会提示错误:
在这里插入图片描述

3.1.3.2 数据类型运行时的溢出

以下示例会导致运行时的溢出:

func main() {
	var myint uint8
	myint = 250

	for i := 0; i < 15; i++ {
		fmt.Println(myint)
		myint++
	}
}

/*
运行结果:
250
251
252
253
254
255
0
1
2
3
4
5
6
7
8

Process finished with exit code 0
*/

这个示例属于逻辑错误,不会报错,但是会影响到最终的结果。

3.2 浮点型

浮点型无法适用于任何一个整型类型!
下例中,f2 会被自动推导成为 float64 类型:

func main() {
	f2 := 3.14
	fmt.Printf("f2 type is : %T\n", f2) //f2 type is : float64
}

float64float32 更加准确。使用自动推导类型的时候,浮点数会被推导成为 float64 类型。

3.3 字符类型

字符只是数值的特殊用例,Golang 使用数值来表示一个字符。
Golang 中,使用 byteint32rune 类型,来代表一个字符,一个字符由一对单引号 '' 包裹起来。
byte 类型(字节类型),它的本质是 uint8 类型,存储的时候会以一个 uint8 数值进行存储。
int32 类型,代表一个 UTF-8 字符。它还有另外一个书写方式:rune,两者是完全等价的,只是书写方式的不同而已(就像本名和小名,int32 是本名,rune 是小名,用于表示 Unicode 字符)。

3.3.1 用 byte 表示一个单字符

byte 类型的本质是用 uint8 类型进行存储,uint8 的范围是 [0~255]。使用 byte 类型存储一个字符的时候,注意不要超出 uint8 的范围。

3.3.1.1 byte 类型的基本使用

可以把 [0~255] 范围内的任意整数赋值给一个 byte 类型。

func main() {
	var ch byte //声明字符类型
	ch = 97
	fmt.Println("ch =", ch)        //打印出数字97,因为Golang是使用一个整型数值来表示一个字符
	fmt.Printf("%c, %d\n", ch, ch) //%c,指定以字符方式打印

	fmt.Println("--------------------------")

	ch = 'A'
	fmt.Println("ch =", ch)
	//%v是万能匹配格式符,表示该变量本身的值
	//因为Golang是使用一个整型数值来表示一个字符,因此ch本身的值就是一个整型数值
	fmt.Printf("%%v=%v, %%c=%c\n", ch, ch)
}

/*
运行结果:
ch = 97
a, 97
--------------------------
ch = 65
%v=65, %c=A
*/

输出时,如果想要看到一个完整的字符,需要指定以字符 %c 格式显示,否则字符类型是以它本质的 uint8 数值来显示的。

3.3.1.2 使用自动推导时,一个字符会被推导成 int32 类型

使用自动推导类型的时候,一个字符会被推导成 int32 类型。

func main() {
	ch := 'a' // 'a'是一个字符
	fmt.Println("ch = ", ch)
	fmt.Printf("ch type is : %T\n", ch) // 自动推导成 int32 类型
	fmt.Printf("%%v = %v\n", ch)        // %v 万能匹配格式符,表示该变量本身的值
	fmt.Printf("%%c = %c\n", ch)        // %c 指定以字符输出

	fmt.Println("----------------------")

	ch = '中'
	fmt.Println("ch = ", ch)
	fmt.Printf("ch type is : %T\n", ch)
	fmt.Printf("%%v = %v\n", ch)
	fmt.Printf("%%c = %c\n", ch)

	fmt.Println("----------------------")

	ch = ',' // 英文状态下的逗号
	fmt.Println("ch = ", ch)
	fmt.Printf("ch type is : %T\n", ch)
	fmt.Printf("%%v = %v\n", ch)
	fmt.Printf("%%c = %c\n", ch)

	ch = ',' // 中文状态下的逗号
	fmt.Println("ch = ", ch)
	fmt.Printf("ch type is : %T\n", ch)
	fmt.Printf("%%v = %v\n", ch)
	fmt.Printf("%%c = %c\n", ch)

	fmt.Println("----------------------")

	ch = '\n' // 换行符
	fmt.Println("ch = ", ch)
	fmt.Printf("ch type is : %T\n", ch)
	fmt.Printf("%%v = %v\n", ch)
	fmt.Printf("%%c = %c", ch) // 换行符是控制字符,不会直接显示,但会产生换行效果
}

/*
运行结果:
ch =  97
ch type is : int32
%v = 97
%c = a
----------------------
ch =  20013
ch type is : int32
%v = 20013
%c = 中
----------------------
ch =  44
ch type is : int32
%v = 44
%c = ,
ch =  65292
ch type is : int32
%v = 65292
%c = ,
----------------------
ch =  10
ch type is : int32
%v = 10
%c = 
// '\n'会产生换行效果,即使fmt.Printf()格式化字符串中不在末尾写'\n',也会发生换行
*/

可以看到,无论是单字符还是复合字符,就算本身是个 ASCII 码字符。只要用了自动推导类型,那么这个字符就会被推导成 int32 类型。
我个人猜想,Golang 并不能很明确地确定(或者判定时会影响一点性能)这个字符到底是单字符还是复合字符。所以就用了 int32 类型来保证能够存储任何类型字符,int32 类型也可以书写成 rune 类型,rune 类型的本质是 int32 类型。

3.3.2 案例
3.3.2.1 案例1:英文字母大小写转换

byte 本质上是 uint8 类型,所以两者可以直接相互转换、运算。格式化时,用 %c 来表示一个字符,用 %d 来表示整型数值。
英文字母大小写转换时,使用字符类型进行操作会非常好用。大小写之间的规律:大小写相差 32,小写的数值大。(大写 A 是 65,小写 a 是 97)
例:

func main() {
	fmt.Printf("大写A:%d,小写a: %d\n", 'A', 'a') //大写A:65,小写a:97
	fmt.Printf("大写A转小写a: %c\n", 'A'+32)     //大写A转小写a: a
	fmt.Printf("小写a转大写A:%c\n", 'a'-32)      //小写a转大写A:A
}

/*
运行结果:
大写A:65,小写a: 97
大写A转小写a: a
小写a转大写A:A
*/
3.3.2.2 案例2:计算英文单词每个字母加起来的值
3.3.2.2.1 题目描述

英语 26 个字母分别代表 1 到 26 的数字,编写一段代码,计算出单词的每个字母加起来等于多少。
例如:
hardwork(勤奋)8+1+18+4+23+15+18+11=98
knowledge(知识)11+14+15+23+12+5+4+7+5=96
love(爱)12+15+22+5=54
luck(运气)12+21+3+11=47
attitude(态度)1+20+20+9+20+21+4+5=100

3.3.2.2.2 实现方式

实现方式有很多,这里采用:对字符本身的 Unicode 码值的运算来实现。
实现思路:
Golang 中,字符串的存储方式是:采用 UTF-8 编码格式下的 Unicode 码值,进行存储。遍历字符串中的每个字符,都将得到这个字符的 Unicode 码值。
假设,输入的都是正确的英文单词。那么单词中的每个字母,它就是一个字符,可以得到它本身的 Unicode 码值。
通过减值的方式来确定字母的大小写。大写字母的范围是 [65 ~ 90], 小写字母的范围是 [97 ~ 122],任何一个大写字母减去 96,都会小于 1,因此遇到大写字母,需要减去 64,刚好能够对应上 26 个字母,由 1 到 26 的数值排列。小写字母直接减去 96,就能对应上了。

package main

import "fmt"

// 计算一个英文单词中的每个字母之和
func computeletters(s string) (sum int64) {
	for i, char := range s {
		// 非英文字母的情况
		// 空格的 ASCII 码值为 32,需要把空格的情况添加进去
		if char != 32 && !(char >= 65 && char <= 90) && !(char >= 97 && char <= 122) {
			fmt.Printf("char:%c, index:%d. does not an english letter.\n", char, i)
			return -1
		}

		if char == 32 {
			// 遇到空格,什么都不做
		} else if char-96 < 1 {
			// Unicode 值减去 96,小于了 1,说明这个英文字母是大写的,大写应该减 64
			sum += int64(char - 64)
		} else {
			sum += int64(char - 96)
		}
	}

	return
}

func main() {
	arr := []string{"hardwork", "knowledge", "love", "luck", "attitude"}

	for i := range arr {
		v := computeletters(arr[i])
		fmt.Printf("word: %s, total value is:%d\n", arr[i], v)
	}
}


/*
运行结果:
word: hardwork, total value is:98
word: knowledge, total value is:96
word: love, total value is:54
word: luck, total value is:47
word: attitude, total value is:100
*/

byte 类型的本质是 uint8 类型,uint8 类型是一个整型数值,范围 [0~255] 的整数,所以它能与另一个整型数值进行运算。

3.3.2 用 int32 类型存储一个复合字符

当处理中文、日文或者其他复合字符时,则需要用 int32 类型来处理 Unicode 文本。
int32 类型也可以书写成 runerune 类型本质就是 int32 类型。

3.3.2.1 存储一个中文字符的方式

先看一个错误的示例:

func main() {
	var ch byte
	ch = '中' //这行会报错:constant 20013 overflows byte,超出了ASCII码的范围
	fmt.Printf("ch type is : %T\n", ch)
}

注意:byte 的范围是 [0~255],20013 明显超出了范围!
接下来,使用自动推导的技巧来实现存储一个中文字符:

func main() {
	ch := '中'
	fmt.Printf("ch type is : %T\n", ch) //int32
	fmt.Printf("using %%v, ch = %v\n", ch)
	fmt.Printf("using %%c, ch = %c\n", ch)
}

/*
运行结果:
ch type is : int32
using %v, ch = 20013
using %c, ch = 中
*/

与 3.3.1.2 中的示例演示一样:使用自动推导的时候,一个中文字符会被推导成为 int32 类型。

3.3.4 int32rune 的关系以及区别
3.3.4.1 int32rune 之间的关系

等价的关系。rune 只是 int32 的一个别名,在功能上完全等价。

3.3.4.2 见名知意的区别

用于更好地让程序猿区分,这个变量是字节值还是无符号整数值。用 rune 来表示一个字符值,用 int32 来表示一个整数值。
让人一看这个数据类型就能知道,这个变量的最终用途是什么。
例如:

func main() {
	chars := []rune{443, 27017, 6379, 3306}
	fmt.Println(string(chars)) //最终作为字符来呈现

	commonPorts := []int32{443, 27017, 6379, 3306}
	fmt.Println("there are some common ports: ", commonPorts) //最终作为数值来呈现
}

/*
运行结果:
ƻ榉ᣫ೪
there are some common ports:  [443 27017 6379 3306]
*/

看到 rune 就知道,最终呈现的是字符;看到 int32 就知道,最终显示数值。个人认为,int32rune 的区别就是见名知意的作用:看它们的数据类型就知道了该数据的最终意图和呈现方式。

3.3.4.3 查看官方文档对他们的解释

在 Goland 中,按住 Ctrl 键,鼠标移动到 rune 这个类型上,就能看到 rune 的定义:
在这里插入图片描述
点击后,便能看到 byte 类型和 rune 类型的文档:
在这里插入图片描述
byte 类型的大意:byteuint8 的别名,在所有方面都等同于 uint8。按照惯例,它用于区分字节值和8 位无符号整数值。
rune 类型的大意:runeint32 的别名,在所有方面都等同于 int32。按照惯例,它用于区分字符值和整数值。

3.3.5 转义字符

有两种字符:一种是控制字符,另一种是可显示字符。
可显示字符就是可以输出,能看得到内容的字符。
控制字符就是转义字符,有特殊的含义,是不可见的。以反斜杠 \ 开头的是转义字符。
例1:先看这个例子:

func main() {
	fmt.Printf("hello go")
	fmt.Printf("abcdefg")
}

/*
运行结果:
hello goabcdefg
//没有换行符,内容会紧跟在上一行的内容后面
/*

例2:
\n 不会输出到屏幕上,是看不见的。但它会进行换行操作,属于功能性的字符:

func main() {
	fmt.Printf("hello go%c", '\n')
	fmt.Printf("abcdefg")
}

/*
运行结果:
hello go
abcdefg
*/
3.3.6 注意事项
3.3.6.1 字符只能使用一对单引号包裹起来

一个字符只能使用一对单引号''包裹起来!
下例中,ch = “a” 这行代码会报错:

func main() {
	var ch byte //声明字符类型
	ch = "a"    //报错:cannot use "a" (type string) as type byte in assignment(不能使用字符串作为byte字符的值)
	fmt.Printf("%c, %d\n", ch, ch)
}

下例是能够正确运行的:

func main() {
	var ch byte //声明字符类型
	ch = 'a'
	fmt.Printf("%c, %d\n", ch, ch) //a, 97
}
3.3.6.2 一对单引号 '' 中,只能放一个字符

在这里插入图片描述

3.3.6.3 byte类型的范围

byte 类型实质上是一个 uint8 类型,uint8 的范围是 [0 ~ 255](0 和 255 都能被取到),不在这个范围就是值溢出,会报错!
数值不在 uint8 范围内的错误示例:

func main() {
	var ch byte //声明字符类型
	ch = 256
	fmt.Printf("%c, %d\n", ch, ch)
	/* 报错内容如下:
	# command-line-arguments
	.\main.go:7:7: constant 256 overflows byte
	*/
}
3.3.7 ASCII码参考表

https://zh.wikipedia.org/wiki/ASCII

3.4 字符串类型

由多个字符所组成的一串内容,被称为字符串。一个字符串需要用双引号 "" 包裹起来!
Golang 中的字符串以 UTF-8 编码方式存储,处理字符串也是采用 UTF-8 的编码方式,每个字符串都是 Unicode 字符集。

3.4.1 rune 类型是数值类型,与字符串类型不兼容
func main() {
	var str1 string
	str1 = 'abc' // 报错:Cannot use ''abc'' (type rune) as type string in assignment
	fmt.Println(str1)
}

str1 = ‘abc’ 这行会报错!字符只能使用单引号 '' 包裹起来,使用双引号 "" 包裹的是字符串!

3.4.2 字符串的截取是以字节为单位

使用 len() 函数获取字符串长度时,获取到的是该 UTF-8 编码字符串的字节长度。

func main() {
	s := "我是" // 字符串中是两个中文汉字

	fmt.Println("len(c)=", len(s))

	fmt.Printf("s[1], value=%v, char=%c\n", s[1], s[1])
	fmt.Println("s[:3]=", s[:2])
	fmt.Println("s[:3]=", s[:3])
}

/*
运行结果:
len(c)= 6
s[1], value=136, char=ˆ
s[:3]= �
s[:3]= 我
*/

在中文字符串中肆意乱截取,很大概率会输出乱码。
由 len(s) 可以得知,此例中字符串 s 的长度为 6,而字符串中只有 2 个汉字。因此,在 Golang 中,一个汉字占 3 个字节。一个汉字由 3 个字节编码而成,使用下标取值时,只是取到了某一个字节的值而已。

3.4.3 对字符串的索引,只会一个 byte

Golang 使用 UTF-8 编码方式存储、处理字符串,最终是以 Unicode 字符集的形式来表现字符串。Unicode 字符集最终也是用整数数值的方式来呈现的。
因此,对字符串的索引操作,会返回一个 byte 值,而不是一个具体显示内容的字符。
示例:

func main() {
	s := "我是gopher"

	for i := 0; i < len(s); i++ {
		fmt.Printf("s[%d], %%d=%d, %%c=%c\n", i, s[i], s[i])
	}
}

/*
运行结果:
s[0], %d=230, %c=æ
s[1], %d=136, %c=ˆ
s[2], %d=145, %c=‘
s[3], %d=230, %c=æ
s[4], %d=152, %c=˜
s[5], %d=175, %c=¯
s[6], %d=103, %c=g
s[7], %d=111, %c=o
s[8], %d=112, %c=p
s[9], %d=104, %c=h
s[10], %d=101, %c=e
s[11], %d=114, %c=r
*/

如果字符串中含有中文字符(一个中文字符在 UTF-8 编码方式下,占 3 个字节)。因为是由 3 个字节编码而成,如果按照索引来取含有 UTF-8 编码的字符,就会出现乱码。

3.4.4 遍历中英文混合字符串的示例

UTF-8 是一种变长的编码方式,字符长度从 1 个字节到 4 个字节不等。而 byte 类型只占 1 个字节。就算你想要使用多个 byte 进行表示,但也无从知晓要处理的 UTF-8 字符究竟占了几个字节。
利用 []int32()[]rune() 将字符串转为 Unicode 字符集(Unicode 的数值),再进行截取。这样就无需考虑字符串中是否含有 UTF-8 字符的情况了。

3.4.4.1 []int32() 写法
func main() {
	s := "我是gopher"

	fmt.Println("s=", s) //fmt.Print()系列函数是输出内容的本身

	//Golang使用UTF-8编码方式将字符串存储成Unicode字符集
	//UTF-8编码方式下,一个中文占3个字节
	//[:2]只是取到了前两个字节值而已,所以显示了乱码
	fmt.Println("s[:2]=", s[:2])
	fmt.Println("s[1]=", s[1])

	//遍历取到的是字符串中的每个字符。如果是中文字符,那么依然是占3个字节,遍历的时候也是取了3个字节
	for i, v := range s {
		//格式化打印是直接显示原本的字符内容
		fmt.Printf("s[%d] : unicode=%v, %%c=%c\n", i, v, v)
	}

	fmt.Println("----------------------------------------")

	//利用[]int32()将字符串存储为Unicode字符集
	unicodeString := []int32(s)
	fmt.Println("unicodeString value : ", unicodeString) //显示的是Unicode字符的数值
	fmt.Println("unicodeString[:3] : ", unicodeString[:3])

	//遍历的是Unicode字符集切片
	for i, v := range unicodeString {
		fmt.Printf("s[%d] = %v\n", i, v)
	}

	//截取到的是Unicode切片中的前2个值
	//将这2个值转换为字符串之后再输出
	fmt.Println(string(unicodeString[:2]))
	fmt.Println(unicodeString[:2]) //不转换成字符串只会输出原本的内容。fmt.Print()系列函数是输出内容的本身
}

/*
运行结果:
s= 我是gopher
s[:2]= �
s[1]= 136
s[0] : unicode=25105, %c=我
s[3] : unicode=26159, %c=是
s[6] : unicode=103, %c=g
s[7] : unicode=111, %c=o
s[8] : unicode=112, %c=p
s[9] : unicode=104, %c=h
s[10] : unicode=101, %c=e
s[11] : unicode=114, %c=r
----------------------------------------
unicodeString value :  [25105 26159 103 111 112 104 101 114]
unicodeString[:3] :  [25105 26159 103]
s[0] = 25105
s[1] = 26159
s[2] = 103
s[3] = 111
s[4] = 112
s[5] = 104
s[6] = 101
s[7] = 114
我是
[25105 26159]
*/

在这里插入图片描述

3.4.4.2 与 []int32() 等价的 []rune() 写法

[]rune() 将字符串转换为 Unicode 码点。

func main() {
	s := "我爱 Golang!" //感叹号是中文的

	unicodeRune := []rune(s)

	fmt.Println("unicode value : ", unicodeRune)
	fmt.Println("unicodeRune[:2], unicode value : ", unicodeRune[:2])
	fmt.Println("string(unicodeRune[:2]) = ", string(unicodeRune[:2]))
	fmt.Println("string(unicodeRune) : ", string(unicodeRune))
}

/*
运行结果:
unicode value :  [25105 29233 32 71 111 108 97 110 103 65281]
unicodeRune[:2], unicode value :  [25105 29233]
string(unicodeRune[:2]) =  我爱
string(unicodeRune) :  我爱 Golang!
*/

与 3.4.4.1 示例中的原理一致:[]int32()[]rune() 都是将字符串转换成 Unicode 字符数值。无论是截取还是直接打印,得到的都将是 Unicode 数值。若想要看到正常的文字内容,需要将 Unicode 数值转换为字符串后再打印。
注:UnicodeASCII 一样,都是一种字符集,UTF-8 是一种编码格式。

3.4.4.3 range 遍历字符串

range 遍历字符串,得到的是 rune 类型的字符。

3.4.4.3.1 获取下标的时候,得到的是 uint8 类型

使用 range 遍历字符串,通过下标去取字符串中的值,得到的是单个字符,字符的本质类型则是 uint8
示例:

func main() {
	s := "我是gopher"

	for i := range s {
		fmt.Printf("current i=%d, %%d=%[2]d, %%c=%[2]c, %%T=%[2]T\n", i, s[i])
	}

	fmt.Println("-------- 以下示例为了区别 range 遍历 --------")

	for i := 0; i < len(s); i++ {
		fmt.Printf("current i=%d, %%d=%[2]d, %%c=%[2]c, %%T=%[2]T\n", i, s[i])
	}
}

/*
运行结果:
current i=0, %d=230, %c=æ, %T=uint8
current i=3, %d=230, %c=æ, %T=uint8
current i=6, %d=103, %c=g, %T=uint8
current i=7, %d=111, %c=o, %T=uint8
current i=8, %d=112, %c=p, %T=uint8
current i=9, %d=104, %c=h, %T=uint8
current i=10, %d=101, %c=e, %T=uint8
current i=11, %d=114, %c=r, %T=uint8
-------- 以下示例为了区别 range 遍历 --------
current i=0, %d=230, %c=æ, %T=uint8
current i=1, %d=136, %c=ˆ, %T=uint8
current i=2, %d=145, %c=‘, %T=uint8
current i=3, %d=230, %c=æ, %T=uint8
current i=4, %d=152, %c=˜, %T=uint8
current i=5, %d=175, %c=¯, %T=uint8
current i=6, %d=103, %c=g, %T=uint8
current i=7, %d=111, %c=o, %T=uint8
current i=8, %d=112, %c=p, %T=uint8
current i=9, %d=104, %c=h, %T=uint8
current i=10, %d=101, %c=e, %T=uint8
current i=11, %d=114, %c=r, %T=uint8
*/

两段代码有一点区别:for i := range s 只出现了 2 个乱码,而 for i := 0; i < len(s); i++ 出现了 6 个乱码。
原因:
Golang 中,字符串是以 UTF-8 编码格式存放的 Unicode 字符码点,一个中文占 3 个编码字节。
for i := range s 的时候,虽然是以 []rune 类型在遍历。但遇到了通过下标去获取字符串中的内容(相当于指定去取字符串中,某个下标值中的内容),只能取到对应下标中的字节码值。第一次,i 是初始值 0;第二次,i 的值则变成了 3。所以,只是对应地去取了这 2 个下标中的内容。
for i := 0; i < len(s); i++ 的时候,就不再是遍历 []rune 类型了,是逐个逐个地遍历每一个字节,把字符串中的每个字节都遍历到了。

3.4.4.3.2 获取值的时候,得到的是 int32 类型

使用 range 遍历字符串的时候,值的部分是 int32 类型。
示例:

func main() {
	s := "我是gopher"

	for i, c := range s {
		fmt.Printf("current i=%d, %%d=%[2]d, %%c=%[2]c, %%T=%[2]T\n", i, c)
	}
}

/*
运行结果:
current i=0, %d=25105, %c=我, %T=int32
current i=3, %d=26159, %c=是, %T=int32
current i=6, %d=103, %c=g, %T=int32
current i=7, %d=111, %c=o, %T=int32
current i=8, %d=112, %c=p, %T=int32
current i=9, %d=104, %c=h, %T=int32
current i=10, %d=101, %c=e, %T=int32
current i=11, %d=114, %c=r, %T=int32
*/

使用 range 遍历字符串,分别得到下标和具体的值。因为一个中文字符在 Golang 的 UTF-8 编码下占 3 个字节,所以此例中,占用的下标就是 [0 ~ 2]、[3 ~ 5]。
runeint32 的别名,因此打印类型时,就显示了 rune 它本身的数据类型。

3.4.4.4 参考文献

https://juejin.im/post/6844903998634328078

3.4.5 如何修改字符串中的某个元素

我想要修改字符串中的某个元素,如何操作?

3.4.5.1 错误警示

直接对下标中的元素进行修改:

func main() {
	str := "hello"
	str[0] = 'x' //报错:cannot assign to str[0]
	fmt.Println(str)
}

编译就通不过,直接报错。原因:在 Golang 中,字符串是不可变的

3.4.5.2 正确的操作

使用 []byte()[]rune(),先将一个字符串转换成字节切片类型,然后对某个下标中的元素进行修改,最后使用 string() 转换回字符串。

3.4.5.2.1 单字符构成的字符串

如果这个字符串都是有单字符构成,那么先将这个字符串转换为 []byte 类型后,再修改某个下标中的元素,最后将这个 []byte 类型使用 string() 转换回来。

func main() {
	str := "hello"
	b := []byte(str) //先转换成 []byte 类型
	fmt.Println(b)

	b[0] = 'x'
	str = string(b) //再将 []byte 类型转换回字符串类型
	fmt.Println(str)
}

/*
运行结果:
[104 101 108 108 111]
xello
*/
3.4.5.2.2 字符串中有复合字符

如果一个字符串有复合字符,那么就需要使用 []rune[]int32 来转换。转换成字节后才能修改,最后依然用 string() 转换回字符串。

func main() {
	str := "go 你好"
	b := []rune(str) // []int32() 也可以
	fmt.Println(str, ",", b)

	fmt.Println(rune('很')) //单个字符转换,也可以直接写成:fmt.Println('很')

	b[3] = 24456
	str = string(b)
	fmt.Println(str)
}

/*
运行结果:
go 你好 , [103 111 32 20320 22909]
24456
go 很好
*/
3.4.6 其他类型与字符串的转换
3.4.6.1 十六进制转换为字符串

有一个字符串切片,里面的每个元素都是以字符串形式保存的十六进制值。现在要将这些十六进制值,转换为其原本的明文字符内容。

import (
	"fmt"
	"strconv"
)

func main() {
	original := []string{"30d7", "30ed", "30b0", "30e9", "30e0"}

	for _, v := range original {
		if s, err := strconv.ParseInt(v, 16, 32); err == nil {
			fmt.Printf("%T\t%d\t%c\n", v, s, s)
		} else {
			panic(err)
		}
	}
}

/*
运行结果:
string	12503	プ
string	12525	ロ
string	12464	グ
string	12521	ラ
string	12512	ム
*/
3.4.7 高性能字符串拼接的几个方式

将以下几个字符串拼接方式,都放入单独的函数中,最后使用 Go 语言自带的 Benchmark 进行简单的性能测试。
设置 1000 次拼接,因此:const Loop = 1000

3.4.7.1 使用 strings.Builder

它使用零值、不拷贝零值、使用内存最小。
示例:

func StrBuilder() {
	var str strings.Builder

	for i := 0; i < Loop; i++ {
		str.WriteString("a")
	}
}

注:不要拷贝 strings.Builder 的值

3.4.7.2 使用 bytes.Buffer

bytes 包的 Buffer 实现了 io.Writer 的接口,使用 bytes.BufferWriteString() 方法去拼接字符串,时间复杂度为 O(n)
示例:

func BytesBuffer() {
	var buffer bytes.Buffer

	for i := 0; i < Loop; i++ {
		buffer.WriteString("a")
	}
}
3.4.7.3 使用内建函数 copy

示例:

func GoCopy() {
	bs := make([]byte, 0, Loop)
	bl := 0

	for i := 0; i < Loop; i++ {
		bl += copy(bs[bl:], "a")
	}
}
3.4.7.4 使用内建函数 append

示例:

func GoAppend() {
	bs := make([]byte, 0, Loop)

	for i := 0; i < Loop; i++ {
		bs = append(bs, 'a')
	}
}
3.4.7.5 加号拼接

使用加号 + 进行拼接。
示例:

func StrPlus() {
	var result string

	for i := 0; i < Loop; i++ {
		result += "a"
	}
}
3.4.7.6 使用 strings.Repeat

将 N 个字符串 s,连接成一个新的字符串。
示例:

func StrRepeat() {
	for i := 0; i < Loop; i++ {
		strings.Repeat("a", Loop)
	}
}

3.4.7.7 编写 Benchmark 测试代码
import "testing"

func BenchmarkStrBuilder(b *testing.B) {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		StrBuilder()
	}
}

func BenchmarkBytesBuffer(b *testing.B) {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		BytesBuffer()
	}
}

func BenchmarkGoCopy(b *testing.B) {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		GoCopy()
	}
}

func BenchmarkGoAppend(b *testing.B) {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		GoAppend()
	}
}

func BenchmarkStrPlus(b *testing.B)  {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		StrPlus()
	}
}

func BenchmarkStrRepeat(b *testing.B) {
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		StrRepeat()
	}
}
3.4.7.8 测试结果

测试环境:某度的云服务器,最低级的那种:1 核 CPU、1 G 内存。OS:ubuntu server 18.04 64bit。
测试 3 次后,得到结果如下:
在这里插入图片描述

3.4.7.9 总结

在确切知道有多少内容的情况下,可以提前将 append()copy() 这两个内建函数的容量值(也就是 cap ),给申请下来,避免了 cap 的重复检查、扩容。
因此,在事先知道字符串的长度时:append() 的性能最高。copy() 的开销最少。
如果无法确定字符串内容的多少,最佳的方案就是 strings.BuilderWriteString() 方法。

3.4.7.10 参考文献

https://www.toutiao.com/a6736789153746256396

3.4.8 字符串不总是 UTF-8 文本

字符串的值可以包含任何字节,只有当字符串字面量(string literal)使用时,才会是 UTF-8
验证一个字符串是否为 UTF-8 文本,可以使用 unicode/utf8 包下的 utf8.ValidString() 方法。
示例:

func main() {
	s1 := "ABC"
	fmt.Println(utf8.ValidString(s1)) // true

	s2 := "\xAF"
	fmt.Println(utf8.ValidString(s2)) // false
}

备注:来源于:https://levy.at/blog/10(字符串不总是UTF8文本)

3.4.9 Go 字符串使用 byte 表示的原因

Go 字符串使用 byte 序列来表示,根本原因是因为各种语言的字符长度“飘忽不定”。
先看下面这段代码:

func main() {
	s1 := "é" // 这个是葡萄牙语,中译:它的
	fmt.Printf("len(s1)=%d, []rune=%#v, []byte=%#v\n", len(s1), []rune(s1), []byte(s1))

	// 既然在 byte 下占 3 个字节的长度,那么就可以对这个 byte 逐位修改
	b_s1 := []byte(s1)
	b_s1[0] = 'a'
	b_s1[1] = 'b'
	b_s1[2] = 'c'
	fmt.Println(string(b_s1))
}

/*
运行结果:
len(s1)=3, []rune=[]int32{101, 769}, []byte=[]byte{0x65, 0xcc, 0x81}
abc
*/

葡萄牙语的这个单词,在 []rune 中,占 2 个字节长度;而在 []byte 中,占了 3 个长度。
各国语言的字符,在不同的数据类型下([]rune []byte),长度不一等都一样。
下面看一个中文示例:

func main() {
	s1 := "中"
	fmt.Printf("len(s1)=%d, []rune=%#v, []byte=%#v\n", len(s1), []rune(s1), []byte(s1))
	fmt.Println("[]rune(s1) =", len([]rune(s1)))
}

/*
运行结果:
len(s1)=3, []rune=[]int32{20013}, []byte=[]byte{0xe4, 0xb8, 0xad}
[]rune(s1) = 1
*/

字符串中只有一个元素"中",[]rune 下,占 1 个字节长度;而在 []byte 中,依然占了 3 个长度。
因为各个语言中的字符长度“飘忽不定”,因此只能采用一种统一的策略来进行管理,以免发生混乱。

3.5 字符和字符串的区别

1.字符只能用单引号包裹起来,字符串需要双引号包裹起来。
2.字符只能是单个字符,字符串是能多个字符所构成。
有的转义字符只是看起来是由两个字符构成。比如 \n:肉眼看上去是 \n 两个字符拼接在一起的,其实它是ASCII码 10 的另外一个书写形式。
示例:

func main() {
	var ch byte
	ch = 10
	fmt.Printf("aaa%cbbb", ch)
}

/*
运行结果:
aaa
bbb
*/

3.字符串的结尾都隐藏了一个结束符 \0
在ASCII码中它的10进制值是0,它是一个空字符,是看不到的。
str1 := "a"实则上是由'a''\0'组成了一个字符串。

3.6 bool类型

Golang 中,布尔类型的值只能是预定义标识符:truefalse

3.6.1 注意事项

1.布尔类型变量的零值为 false
2.Golang 中不允许将整型强制转换为布尔型。
3.布尔类型无法参与数值运算,也无法与其他类型进行转换。

3.6.2 用数值来表示真假的错误示例

在 Golang 中,布尔类型的值只能是 truefalse
一些脚本编程语言或是一些弱类型编程语言中,数值 0 或 1 、空数组、空集合等也可以用来表真或假。但在 Golang 中,这是不允许的!Golang 中,bool 值只能是 truefalse
错误示例:

func main() {
	a := 0

	if a { //这行会报错:非布尔'a'(类型为int)用作条件
		fmt.Println("yes")
	}
}

在这里插入图片描述

3.7 复数类型

由实部和虚部构成。

func main() {
	var t1 complex128 //声明
	t1 = 3 + 5i       //赋值
	fmt.Println("t1 = ", t1)

	t2 := 7 + 9.9i
	fmt.Printf("t2 type is : %T\n", t2)

	//通过内建函数,取实部和虚部
	fmt.Printf("real(t2)=%v, imag(t2)=%v\n", real(t2), imag(t2))
}

/*
运行结果:
t1 =  (3+5i)
t2 type is : complex128
real(t2)=7, imag(t2)=9.9
*/

3.8 类型转换

类型转换只能转换相互兼容的类型,比如 byte 类型转换成 int 类型。
示例:

func main() {
	ch := 'a'

	var i int
	i = int(ch) //byte类型本质上就是一个uint8类型,和int兼容类型,所以可以相互转换

	fmt.Printf("%%d:i=%d, %%c:i=%c, i type is : %T\n", i, i, i)
	//%d:i=97, %c:i=a, i type is : int
}

3.9 类型别名

给一个数据类型起一个别名(小名),最常用的场景就是结构体 struct
语法:type 别名 数据类型
使用:var 变量名 别名
示例:

func main() {
	//给int64这个数据类型起个别名叫bigint
	type bigint int64
	var a bigint                      //a变量声明为bigint类型
	fmt.Printf("a type is : %T\n", a) //指向了自定义的bigint类型

	//多个别名一起声明
	type (
		char byte
		long int64
	)
	//声明变量为自定义类型
	var ch char
	var ll long
	ch = 'a'
	ll = 123456
	fmt.Printf("ch=%v, ll=%v\n", ch, ll)
	fmt.Printf("ch type is : %T, ll type is : %T\n", ch, ll)
}

/*
运行结果:
a type is : main.bigint
ch=97, ll=123456
ch type is : main.char, ll type is : main.long
*/

3.10 格式化输出

参考网站:

https://godoc.org/fmt

%v 属于万能格式,自动匹配格式输出。

3.11 非十进制可选前缀

一个整数数值字面量无需带前缀,除非它是负数,或者自己想要添加一个加号。
非十进制可选前缀设置表示法:
二进制:0b0B
八进制:0, 0o0O
十六进制:0x0X; 在十六进制中,[a ~ f] 或 [A ~ F] 代表十进制值 10 ~ 15
只有单个 0 被认为是十进制的 0

3.12 _ 增强数值的可读性

如果有一个特别大的数值,一眼看过去,一时半会儿看不清楚它是百亿还是千亿。那么在代码中,可以在这个数值中添加下划线 _,来增强数值的可读性。
例如:

func main() {
	n := 123_456_789_000
	fmt.Printf("n type: %[1]T, value=%[1]v\n", n)

	n += 55_55555_55555
	fmt.Printf("n type: %[1]T, value=%[1]v\n", n)
}

/*
运行结果:
n type: int, value=123456789000
n type: int, value=679012344555
*/

其实是个语法糖,_ 被当做分隔符来使用了。自己看着怎么易读,就怎么分割。同样适用于十六进制的值以及小数中!


四、运算符

4.1 Golang内建的运算符

Golang 内建的运算符有:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符。

4.2 算术运算符

在这里插入图片描述
Golang 中自增自减的注意
在 Golang 中,++(自增)和 --(自减)是单独的语句,并不是运算符。
而且自增自减必须是单独一行(单独的语句),下例中的自增是非法的:

i := 1
j = i++ //此处的 i++ 是非法的,i++ 必须是单独的语句

4.3 关系运算符

在这里插入图片描述

4.4 逻辑运算符

在这里插入图片描述

4.5 位运算符

位运算符是对整数在内存中的二进制位进行操作。
在这里插入图片描述
示例1:

const size = 2 << 4

func main() {
	fmt.Println("2 << 10 = ", 2<<10)     //表示:2*2**10,2乘以2的10次方
	fmt.Println("1024 >> 2 = ", 1024>>2) //表示:1024/2**2,1024除以2的2次方
	fmt.Println("const size = ", size)
}

/*
运算结果:
2 << 10 =  2048
1024 >> 2 =  256
const size =  32
*/

4.6 赋值运算符

在这里插入图片描述

4.7 运算符优先级

优先级:从上往下由高到低
Go运算符优先级
使用小括号()把一个表达式包裹起来可以提升优先级。

4.8 Golang中,不能使用复合表达式

其他编程语言中,比较一个数即要大于某个数同时也要小于某个数(比如:当 a=7 时,判断:a 是否大于等于 0 并且 a 是否小于等于 10),那么就会用到复合布尔表达式(例如:0 <= a <= 10)。
但在 Golang 中就会报错:

func main() {
	a := 7
	fmt.Println("0 <= a <= 10 的结果:", 0 <= a <= 10)
	/*
		cannot convert 10 (type untyped number) to type bool
		invalid operation: 0 <= a <= 10 (mismatched types bool and int)
		不匹配的类型bool和int
	*/
}

golang的int类型与bool类型不兼容0 <= a 得出的结果是一个 bool 类型的值 truetrue <= 10。10是一个 int 类型,true 是 bool 类型,两者的类型在 golang 中不兼容,因此报错!
需要使用&&)运算符:

func main() {
	a := 7
	fmt.Println("0 <= a <= 10 的结果:", 0 <= a && a <= 10)
	//0 <= a <= 10 的结果: true
}

4.9 运算符参考网站

https://www.runoob.com/go/go-operators.html



五、流程控制

对程序做出逻辑性控制。

5.1 Golang最基本的三种程序运行控制

5.1.1 顺序控制

程序按顺序运行,流水账从上往下运行,不发生跳转。

5.1.2 选择控制

依据是否满足条件,有选择地执行相应功能。

5.1.3 循环控制

依据条件是否满足,循环多次执行某段代码。

5.2 if条件语句

if 语句支持1个初始化语句,初始化语句与判断语句写在同一行中。
示例:

func main() {
	if a := 10; a == 10 {
		fmt.Println("yes") //yes
	}
}

a := 10 是初始化语句,a == 10 是判断语句。
这样可以非常好地控制变量的作用域:

func main() {
	if a := 10; a == 10 {
		fmt.Println("yes") // yes
	}

	fmt.Println(a) // undefined: a
}

在这里插入图片描述

5.3 switch

if...else if...else 另一种简洁灵活的写法。

5.3.1 语法

最基本的语法:

switch 变量本身 {
case 变量的值1:
	语句
case 变量的值2:
	语句
case 变量的值n:
	语句
default: //default可以省略
	语句
}

变量本身和下面 case 分支中的变量值进行比较(从上往下比较),匹配到了就进入对应的 case 分支中。
switch 也支持1个初始化语句。
注意:一个 case 分支中的值,不能与其他 case 分支中的值重复!并且,在 case 表达式中的值,必须与 swith 语句变量的类型一致,否则无法通过编译。

5.3.2 break

默认情况下,case 语句中自带了 break。执行完一个 case 分支后,自动跳出当前整个 switch,不会自动向下执行其他 case

5.3.3 fallthrough

不判断下一个case 变量的值,无条件强制执行下去。
fallthrough下面必须要接语句,无论是接 case 还是 default

func main() {
	switch num := 1; num { //支持1个初始化语句,初始化语句和变量本身使用分号分隔
	case 1:
		fmt.Println("print 1")
		fallthrough //不跳出switch语句块,下一个case语句不做判断,无条件强制执行下去
	case 2:
		fmt.Println("print 2")
		fallthrough //不跳出switch语句块,下一个case语句不做判断,无条件强制执行下去
	default:
		fmt.Println("print else")
	}
}

/*
运行结果:
print 1
print 2
print else
*/
5.3.4 省略条件

在 Go 中,switch 后面可以省略条件,通过匹配各个 case 中的布尔值选择分支。
语法:

switch { // 不接变量本身,根据各个case条件进行判断、选择
case 条件1: // case 后面放条件
	语句
case 条件2: // case 后面放条件
	语句
case 条件n: // case 后面放条件
	语句
default: // default 可以省略
	语句
}

如果根据 case 条件的判断结果去自行选择分支,那么 switch 后面就不能接变量本身了:

func main() {
	grade := 80
	switch { // 这里不写变量本身了
	case grade > 90: // case 分支中判断是否满足条件,符合条件进入这个分支
		fmt.Println("优秀")
	case grade >= 80: // case 中判断是否满足条件
		fmt.Println("良好")
	default:
		fmt.Println("其他")
	}
}

/*
运行结果:
良好
*/

注意:此种方式下,每一个 case 分支中表达式的最终值都必须是布尔值:

func main() {
	score := 80
	name := "aaa"
	
	switch {
	case score > 90:
		fmt.Println("good")
	//case score > 70:
	//	fmt.Println("fine")
	case name > "aaaa":
		fmt.Println("name is aaa")
	case 1.0 > 0.9:
		fmt.Println("bigger")
	}
}

/*
运行结果:
bigger
*/

可以将上例中的 case 语句逐个注释、取消注释,来看运行结果。
但是,如果 case 分支的表达式中有非布尔类型的值,将无法通过编译。
反面案例:

func main() {
	score := 80
	name := "aaa"
	level := 1

	switch {
	case score > 90:
		fmt.Println("good")
	//case score > 70:
	//	fmt.Println("fine")
	case name > "aaaa":
		fmt.Println("name is aaa")
	case 1.0 > 0.9:
		fmt.Println("bigger")
	case level: // Invalid case 'level' in switch on 'true' (mismatched types 'int' and 'bool')
		fmt.Println("level one.")
	}
}

最后一个分支中的表达式的结果是一个 int 类型,无法与布尔类型匹配,无法通过编译!

5.3.5 测试多个符合条件的值

case 语句中,可以同时写上多个可能符合条件的值,使用逗号分割它们。例如:case 值1, 值2, 值3
示例:

func main() {
	switch grade := 70; grade {
	case 90:
		fmt.Println("优秀")
	case 80:
		fmt.Println("良好")
	case 60, 70:
		fmt.Println("及格")
	default:
		fmt.Println("不及格")
	}
}

/*
运行结果:
及格
*/

5.4 for循环

Golang 中的循环只有 for 循环。

5.4.1 基本语法
for 初始化条件;判断条件;条件变化 {
	语句
}
5.4.2 基本流程

1.初始化条件。
2.判断条件是否为真,如果为真进入循环体内,如果为假则跳出循环。
3.执行条件变化的语句。
4.重复 2.3.4 步骤。

5.4.3 range迭代

一种自动实现的迭代器,常用于:数组、切片、通道。需要在 for 循环中使用。
range 有两个返回值:第一个返回值是元素的下标;第二个返回值是元素自身的值。
示例:

func main() {
	str1 := "abc"
	for i := range str1 { //第2个返回值会被丢弃,只取用第1个返回值(元素的下标)
		fmt.Printf("str[%d]=%c\n", i, str1[i])
	}
}

/*
运行结果:
str[0]=a
str[1]=b
str[2]=c
*/
5.4.4 几个for循环小案例
5.4.4.1 九九乘法表

打印九九乘法表:

func main() {
	//外层控制共循环几次
	for i := 1; i < 10; i++ {

		//里层控制每次循环需要计算几次
		for j := 1; j <= i; j++ {
			fmt.Printf("%d x %d = %d\t", j, i, i*j) //1*9的格式来显示
		}
		fmt.Println()
	}
}

/*
运行结果:
1 x 1 = 1
1 x 2 = 2       2 x 2 = 4
1 x 3 = 3       2 x 3 = 6       3 x 3 = 9
1 x 4 = 4       2 x 4 = 8       3 x 4 = 12      4 x 4 = 16
1 x 5 = 5       2 x 5 = 10      3 x 5 = 15      4 x 5 = 20      5 x 5 = 25
1 x 6 = 6       2 x 6 = 12      3 x 6 = 18      4 x 6 = 24      5 x 6 = 30      6 x 6 = 36
1 x 7 = 7       2 x 7 = 14      3 x 7 = 21      4 x 7 = 28      5 x 7 = 35      6 x 7 = 42      7 x 7 = 49
1 x 8 = 8       2 x 8 = 16      3 x 8 = 24      4 x 8 = 32      5 x 8 = 40      6 x 8 = 48      7 x 8 = 56      8 x 8 = 64
1 x 9 = 9       2 x 9 = 18      3 x 9 = 27      4 x 9 = 36      5 x 9 = 45      6 x 9 = 54      7 x 9 = 63      8 x 9 = 72      9 x 9 = 81
*/
5.4.4.2 冒泡排序
func Bubbling(sli []int) {
	length := len(sli)

	//外层控制共循环几次
	for i := 0; i < length-1; i++ {

		//里层控制每个元素都参与比较
		for j := i + 1; j < length-1; j++ {
			if sli[i] < sli[j] { //策略:大的数字放在前面
				sli[i], sli[j] = sli[j], sli[i]
			}
		}
	}
}

func main() {
	sli := []int{5, 9, 10, 3, 6, 1, 0, 7, 8, 4, 2, 0}
	fmt.Println("before bubbling:", sli)
	Bubbling(sli)
	fmt.Println("after bubbling:", sli)
}

/*
运行结果:
before bubbling: [5 9 10 3 6 1 0 7 8 4 2 0]
after bubbling: [10 9 8 7 6 5 4 3 2 1 0 0]
*/
5.4.4.3 水仙花数

打印出十进制 100-999 以内的“水仙花数”。
所谓“水仙花数”是指一个三位数,其各位数字立方之和等于该数本身。例如:153 是一个“水仙花数”,因为:153 = 1的三次方 + 5的三次方 + 3的三次方。
如下图所示:
在这里插入图片描述

//严格意义来说水仙花数指三位数

import (
	"fmt"
	"math"
)

//把这个三位数进行拆分,得到百位、十位、个位
func GetEveryNum(n int) []int {
	sli := make([]int, 3)

	sli[0] = n / 100     //得到百位的数字
	sli[1] = n / 10 % 10 //得到十位的数字
	sli[2] = n % 10      //得到个位的数字

	return sli
}

//得到这个三位数,每个数的立方和
func GetMul(sli []int) int {
	return GetCubeResult(sli[0], 3) + GetCubeResult(sli[1], 3) + GetCubeResult(sli[2], 3)
}

//计算立方的函数
func GetCubeResult(x, y int) int {
	return int(math.Pow(float64(x), float64(y)))
}

func main() {
	narcissisticNumbers := make([]int, 0) //不要指定len,不然默认会用

	for i := 100; i < 1000; i++ {
		sli := GetEveryNum(i)

		mulResult := GetMul(sli)
		if mulResult == i { //各位数字立方之和等于i,即表示是一个水仙花数
			narcissisticNumbers = append(narcissisticNumbers, mulResult)
		}
	}

	fmt.Println(narcissisticNumbers)
}

/*
运行结果:
[153 370 371 407]
*/

5.5 跳转语句

5.5.1 break

break 可以用在 forswitchselect

5.5.2 continue

continue 只能用在 for 循环中!

5.5.3 break和continue不能同时出现在同一级语句块中

breakcontinue 同时出现在同一级语句块中,就会自相矛盾,导致另一个语句无法到达!
在这里插入图片描述

5.6 goto

goto 可以在任何地方使用,但不能跨函数使用。
不建议使用,因为会破坏程序的结构

5.6.1 goto不能跨函数使用
package main

import "fmt"

func testFunc() {
END:
	fmt.Println("this is testFunc.")
}

func main() {
	goto END
}

/*
运行结果:
.\main.go:6:1: label END defined and not used
.\main.go:11:7: label END not defined
*/
5.6.2 goto无条件跳转

程序遇到 goto 语句,将会无条件强制跳转。

func main() {
	fmt.Println("111")

	goto END //自定义标签名,无条件强制跳转去该标签的所在代码块

	fmt.Println("3333") //此行代码永远无法到达

END:
	fmt.Println("END target")
}

/*
运行结果:
111
END target
*/

尝试将上面代码中的 END 标签和其代码块放到 main() 函数的第一行,会发生什么情况?
程序进入死循环,没完没了地一直在跳转。

func main() {
END:
	fmt.Println("END target")

	fmt.Println("111")

	goto END

	fmt.Println("3333")
}
5.6.3 goto 的使用场景示例

有这么一个场景:打印 1 ~ 10,不能用 for 循环。
goto 就能很容易实现需求:

func main() {
	n := 1

LOOP: // 定义 goto 的标签,以及实现代码逻辑
	fmt.Printf("%d ", n)
	n++

	if n <= 10 {
		goto LOOP // 跳转到该标签
	}
}

/*
运行结果:
1 2 3 4 5 6 7 8 9 10 
*/

以上代码,也是运用了循环的思维模式,只是换了跳转的方式而已。
还有一种方式就是递归:

var n = 1

func main() {
	if n <= 10 {
		fmt.Printf("%d ", n)
		n++
		main()
	}
}

/*
运行结果:
1 2 3 4 5 6 7 8 9 10
*/

递归是循环的另一种表现形式。



六、值类型 && 引用类型

6.1 值类型

bool
int(32 or 64), int8, int16, int32, int64
uint(32 or 64), uint8(byte), uint16, uint32, uint64
float32, float64
string
complex64, complex128
array // 固定长度的数组

6.2 引用类型

Golang 中,只有这几个引用类型:

slice      // 切片
map 	   // HashMap
pointer    //指针类型
channel    //管道(通道)
interface  //接口
function   //函数
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值