20小时快速入门go语言视频 - Day1
20小时快速入门go语言视频 - Day1
- 一、第一个 Go 程序
- 1.1 入口
- 1.2 Golang 保留的关键字
- 1.3 Golang预定义标识符
- 二、数据类型
- 2.1 数据类型的作用
- 2.2 数据类型的命名规则
- 2.3 变量
- 2.3.1 何为变量
- 2.3.2 声明变量
- 2.3.3 变量的声明及赋值
- 2.3.4 变量的初始化
- 2.3.5 自动推导类型
- 2.3.6 多重赋值
- 2.3.6.1 `var()` 包裹起来的多重声明
- 2.3.6.2 自动推导类型(官方推荐写法)
- 2.3.7 匿名变量
- 2.3.8 变量的输入
- 2.3.9 变量使用注意事项
- 2.4 常量
- 2.4.1 何为常量
- 2.4.2 初始化常量
- 2.4.2.1 语法
- 2.4.2.2 例1,单个初始化
- 2.4.2.3 例2,包裹起来一起初始化
- 2.4.2.4 例3,多重初始化
- 2.4.3 iota枚举
- 2.4.3.1 iota的特性1
- 2.4.3.2 iota的特性2
- 2.4.3.3 iota的特性3
- 2.4.3.4 iota的特性4
- 2.4.3.5 iota的特性5
- 2.4.3.6 iota的特性6
- 2.4.4 常量使用的注意事项
- 2.4.4.1 初始化常量不允许使用海象运算符`:=`
- 2.4.4.2 常量不允许被修改
- 2.4.4.3 常量必须给值
- 三、基础数据类型
- 3.1 总述
- 3.1.1 Go 语言中的数据类型
- 3.1.2 Go 语言的数据类型分类
- 3.1.3 注意数据类型的溢出
- 3.1.3.1 数据类型编译时的溢出
- 3.1.3.2 数据类型运行时的溢出
- 3.2 浮点型
- 3.3 字符类型
- 3.3.1 用 `byte` 表示一个单字符
- 3.3.1.1 `byte` 类型的基本使用
- 3.3.1.2 使用自动推导时,一个字符会被推导成 `int32` 类型
- 3.3.2 案例
- 3.3.2.1 案例1:英文字母大小写转换
- 3.3.2.2 案例2:计算英文单词每个字母加起来的值
- 3.3.2.2.1 题目描述
- 3.3.2.2.2 实现方式
- 3.3.2 用 `int32` 类型存储一个复合字符
- 3.3.2.1 存储一个中文字符的方式
- 3.3.4 `int32` 和 `rune` 的关系以及区别
- 3.3.4.1 `int32` 和 `rune` 之间的关系
- 3.3.4.2 见名知意的区别
- 3.3.4.3 查看官方文档对他们的解释
- 3.3.5 转义字符
- 3.3.6 注意事项
- 3.3.6.1 字符只能使用一对单引号包裹起来
- 3.3.6.2 一对单引号 `''` 中,只能放一个字符
- 3.3.6.3 byte类型的范围
- 3.3.7 ASCII码参考表
- 3.4 字符串类型
- 3.4.1 `rune` 类型是数值类型,与字符串类型不兼容
- 3.4.2 字符串的截取是以字节为单位
- 3.4.3 对字符串的索引,只会一个 `byte` 值
- 3.4.4 遍历中英文混合字符串的示例
- 3.4.4.1 `[]int32()` 写法
- 3.4.4.2 与 `[]int32()` 等价的 `[]rune()` 写法
- 3.4.4.3 `range` 遍历字符串
- 3.4.4.3.1 获取下标的时候,得到的是 `uint8` 类型
- 3.4.4.3.2 获取值的时候,得到的是 `int32` 类型
- 3.4.4.4 参考文献
- 3.4.5 如何修改字符串中的某个元素
- 3.4.5.1 错误警示
- 3.4.5.2 正确的操作
- 3.4.5.2.1 单字符构成的字符串
- 3.4.5.2.2 字符串中有复合字符
- 3.4.6 其他类型与字符串的转换
- 3.4.6.1 十六进制转换为字符串
- 3.4.7 高性能字符串拼接的几个方式
- 3.4.7.1 使用 `strings.Builder`
- 3.4.7.2 使用 `bytes.Buffer`
- 3.4.7.3 使用内建函数 `copy`
- 3.4.7.4 使用内建函数 `append`
- 3.4.7.5 加号拼接
- 3.4.7.6 使用 `strings.Repeat`
- 3.4.7.7 编写 `Benchmark` 测试代码
- 3.4.7.8 测试结果
- 3.4.7.9 总结
- 3.4.7.10 参考文献
- 3.4.8 字符串不总是 `UTF-8` 文本
- 3.4.9 Go 字符串使用 `byte` 表示的原因
- 3.5 字符和字符串的区别
- 3.6 bool类型
- 3.6.1 注意事项
- 3.6.2 用数值来表示真假的错误示例
- 3.7 复数类型
- 3.8 类型转换
- 3.9 类型别名
- 3.10 格式化输出
- 3.11 非十进制可选前缀
- 3.12 `_` 增强数值的可读性
- 四、运算符
- 4.1 Golang内建的运算符
- 4.2 算术运算符
- 4.3 关系运算符
- 4.4 逻辑运算符
- 4.5 位运算符
- 4.6 赋值运算符
- 4.7 运算符优先级
- 4.8 Golang中,不能使用复合表达式
- 4.9 运算符参考网站
- 五、流程控制
- 5.1 Golang最基本的三种程序运行控制
- 5.1.1 顺序控制
- 5.1.2 选择控制
- 5.1.3 循环控制
- 5.2 if条件语句
- 5.3 switch
- 5.3.1 语法
- 5.3.2 break
- 5.3.3 fallthrough
- 5.3.4 省略条件
- 5.3.5 测试多个符合条件的值
- 5.4 for循环
- 5.4.1 基本语法
- 5.4.2 基本流程
- 5.4.3 range迭代
- 5.4.4 几个for循环小案例
- 5.4.4.1 九九乘法表
- 5.4.4.2 冒泡排序
- 5.4.4.3 水仙花数
- 5.5 跳转语句
- 5.5.1 break
- 5.5.2 continue
- 5.5.3 break和continue不能同时出现在同一级语句块中
- 5.6 goto
- 5.6.1 goto不能跨函数使用
- 5.6.2 goto无条件跳转
- 5.6.3 goto 的使用场景示例
- 六、值类型 && 引用类型
- 6.1 值类型
- 6.2 引用类型
一、第一个 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 mainimport "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 mainimport "fmt"func main() {var (a intb float64) // 关键字var+(),把多个变量包裹起来a = 10b = 3.14fmt.Println(a, b) // 10 3.14
}
2.3.6.2 自动推导类型(官方推荐写法)
使用海象运算符 :=
:
func main() {a, b := 10, 3.14fmt.Println(a, b) // 10 3.14
}
2.3.7 匿名变量
使用单个下划线 _
表示匿名变量,丢弃数据且不处理,也不会占用内存空间。
package mainimport "fmt"func main() {i, j := 10, 20tmp, _ := i, j // 这里,j就被丢弃掉了,不会处理j这个变量了fmt.Println(tmp, j) // 打印j的值依然是用之前的20
}
2.3.8 变量的输入
使用内建 fmt
包下的 Scan()
函数:
func main() {var a intfmt.Printf("输入变量a的值:")fmt.Scanf("%d", &a) // 取a的地址,此处会阻塞等待用户的输入// 或者下面更加简便的等价的写法:// fmt.Scan(&a) // 别忘了取地址fmt.Printf("刚才输入的值,a=%d\n", a)
}/*
运行结果:
输入变量a的值:11
刚才输入的值,a=11
*/
2.3.9 变量使用注意事项
1.函数外的每个语句都必须以关键字开始。(var
、const
、func
等)
2.海象运算符 :=
不能在函数外使用。
3.下划线 _
多用于占位,不会占用内存空间,表示丢弃该值。
2.4 常量
2.4.1 何为常量
程序运行期间,不能改变的量。一开始给了值,就不允许再改变了!
2.4.2 初始化常量
常量必须给值,所以就是直接初始化常量了,声明和赋值一步完成。
2.4.2.1 语法
const 常量名 [数据类型] = 值
2.4.2.2 例1,单个初始化
package mainimport "fmt"const a = 10
const b = 10func main() {fmt.Println(a, b)
}
2.4.2.3 例2,包裹起来一起初始化
把例1的常量声明,用括号括起来,写在一起:
const (a = 10b = 10
)
2.4.2.4 例3,多重初始化
const a, b = 10, 3.14func 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 mainimport "fmt"const (a = iotab = iotac = 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
遇到另一个 const
,iota
的值重新从 0 开始计算。
package mainimport "fmt"const (a = iotabc
)const (d = iotae
)func main() {const (f = iotagh)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 mainimport "fmt"func main() {const (a1 = iotab1c1d1)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 mainimport "fmt"func main() {const (i = iotaj1, j2, j3 = iota, iota, iotak = 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 mainimport "fmt"func main() {const (i = 10j1, j2, j3 = iota, iota, iotak = 20l, m = iota, iotan = "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 uint8for i = 0; i < 270; i++ {}
}/*
运行结果:
# command-line-arguments
.\main.go:5:15: constant 270 overflows uint8Compilation finished with exit code 2
*/
这个示例就相当于语法错误了,数值已经溢出了该数据类型,编译都编不过。
使用 IDE,就会提示错误:
3.1.3.2 数据类型运行时的溢出
以下示例会导致运行时的溢出:
func main() {var myint uint8myint = 250for i := 0; i < 15; i++ {fmt.Println(myint)myint++}
}/*
运行结果:
250
251
252
253
254
255
0
1
2
3
4
5
6
7
8Process finished with exit code 0
*/
这个示例属于逻辑错误,不会报错,但是会影响到最终的结果。
3.2 浮点型
浮点型无法适用于任何一个整型类型!
下例中,f2 会被自动推导成为 float64
类型:
func main() {f2 := 3.14fmt.Printf("f2 type is : %T\n", f2) //f2 type is : float64
}
float64
比 float32
更加准确。使用自动推导类型的时候,浮点数会被推导成为 float64
类型。
3.3 字符类型
字符只是数值的特殊用例,Golang 使用数值来表示一个字符。
Golang 中,使用 byte
、int32
、rune
类型,来代表一个字符,一个字符由一对单引号 ''
包裹起来。
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 = 97fmt.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:97fmt.Printf("大写A转小写a: %c\n", 'A'+32) //大写A转小写a: afmt.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 mainimport "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,说明这个英文字母是大写的,大写应该减 64sum += 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
类型也可以书写成 rune
,rune
类型本质就是 int32
类型。
3.3.2.1 存储一个中文字符的方式
先看一个错误的示例:
func main() {var ch bytech = '中' //这行会报错: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) //int32fmt.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 int32
和 rune
的关系以及区别
3.3.4.1 int32
和 rune
之间的关系
等价的关系。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
就知道,最终显示数值。个人认为,int32
和 rune
的区别就是见名知意的作用:看它们的数据类型就知道了该数据的最终意图和呈现方式。
3.3.4.3 查看官方文档对他们的解释
在 Goland 中,按住 Ctrl
键,鼠标移动到 rune
这个类型上,就能看到 rune
的定义:
点击后,便能看到 byte
类型和 rune
类型的文档:
byte
类型的大意:byte
是 uint8
的别名,在所有方面都等同于 uint8
。按照惯例,它用于区分字节值和8 位无符号整数值。
rune
类型的大意:rune
是 int32
的别名,在所有方面都等同于 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 = 256fmt.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 stringstr1 = 'abc' // 报错:Cannot use ''abc'' (type rune) as type string in assignmentfmt.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
数值转换为字符串后再打印。
注:Unicode
和 ASCII
一样,都是一种字符集,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]。
rune
是 int32
的别名,因此打印类型时,就显示了 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] = 24456str = 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.Builderfor i := 0; i < Loop; i++ {str.WriteString("a")}
}
注:不要拷贝 strings.Builder
的值。
3.4.7.2 使用 bytes.Buffer
bytes
包的 Buffer
实现了 io.Writer
的接口,使用 bytes.Buffer
的 WriteString()
方法去拼接字符串,时间复杂度为 O(n)
。
示例:
func BytesBuffer() {var buffer bytes.Bufferfor i := 0; i < Loop; i++ {buffer.WriteString("a")}
}
3.4.7.3 使用内建函数 copy
示例:
func GoCopy() {bs := make([]byte, 0, Loop)bl := 0for 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 stringfor 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.Builder
的 WriteString()
方法。
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)) // trues2 := "\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 bytech = 10fmt.Printf("aaa%cbbb", ch)
}/*
运行结果:
aaa
bbb
*/
3.字符串的结尾都隐藏了一个结束符 \0
。
在ASCII码中它的10进制值是0
,它是一个空字符,是看不到的。
str1 := "a"
实则上是由'a'
和'\0'
组成了一个字符串。
3.6 bool类型
Golang 中,布尔类型的值只能是预定义标识符:true
或 false
。
3.6.1 注意事项
1.布尔类型变量的零值为 false
。
2.Golang 中不允许将整型强制转换为布尔型。
3.布尔类型无法参与数值运算,也无法与其他类型进行转换。
3.6.2 用数值来表示真假的错误示例
在 Golang 中,布尔类型的值只能是 true
或 false
。
一些脚本编程语言或是一些弱类型编程语言中,数值 0 或 1 、空数组、空集合等也可以用来表真或假。但在 Golang 中,这是不允许的!Golang 中,bool
值只能是 true
或 false
!
错误示例:
func main() {a := 0if a { //这行会报错:非布尔'a'(类型为int)用作条件fmt.Println("yes")}
}
3.7 复数类型
由实部和虚部构成。
func main() {var t1 complex128 //声明t1 = 3 + 5i //赋值fmt.Println("t1 = ", t1)t2 := 7 + 9.9ifmt.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 inti = 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这个数据类型起个别名叫biginttype bigint int64var a bigint //a变量声明为bigint类型fmt.Printf("a type is : %T\n", a) //指向了自定义的bigint类型//多个别名一起声明type (char bytelong int64)//声明变量为自定义类型var ch charvar ll longch = 'a'll = 123456fmt.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 非十进制可选前缀
一个整数数值字面量无需带前缀,除非它是负数,或者自己想要添加一个加号。
非十进制可选前缀设置表示法:
二进制:0b
或 0B
八进制:0
, 0o
或 0O
十六进制:0x
或 0X
; 在十六进制中,[a ~ f] 或 [A ~ F] 代表十进制值 10 ~ 15
只有单个 0
被认为是十进制的 0
。
3.12 _
增强数值的可读性
如果有一个特别大的数值,一眼看过去,一时半会儿看不清楚它是百亿还是千亿。那么在代码中,可以在这个数值中添加下划线 _
,来增强数值的可读性。
例如:
func main() {n := 123_456_789_000fmt.Printf("n type: %[1]T, value=%[1]v\n", n)n += 55_55555_55555fmt.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 << 4func 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 运算符优先级
优先级:从上往下由高到低
使用小括号()
把一个表达式包裹起来可以提升优先级。
4.8 Golang中,不能使用复合表达式
其他编程语言中,比较一个数即要大于某个数同时也要小于某个数(比如:当 a=7 时,判断:a 是否大于等于 0 并且 a 是否小于等于 10),那么就会用到复合布尔表达式(例如:0 <= a <= 10)。
但在 Golang 中就会报错:
func main() {a := 7fmt.Println("0 <= a <= 10 的结果:", 0 <= a <= 10)/*cannot convert 10 (type untyped number) to type boolinvalid operation: 0 <= a <= 10 (mismatched types bool and int)不匹配的类型bool和int*/
}
golang的int类型与bool类型不兼容!0 <= a
得出的结果是一个 bool 类型的值 true
,true <= 10
。10是一个 int 类型,true 是 bool 类型,两者的类型在 golang 中不兼容,因此报错!
需要使用与
(&&
)运算符:
func main() {a := 7fmt.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 := 80switch { // 这里不写变量本身了case grade > 90: // case 分支中判断是否满足条件,符合条件进入这个分支fmt.Println("优秀")case grade >= 80: // case 中判断是否满足条件fmt.Println("良好")default:fmt.Println("其他")}
}/*
运行结果:
良好
*/
注意:此种方式下,每一个 case
分支中表达式的最终值都必须是布尔值:
func main() {score := 80name := "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 := 80name := "aaa"level := 1switch {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
可以用在 for
、switch
、select
。
5.5.2 continue
continue
只能用在 for
循环中!
5.5.3 break和continue不能同时出现在同一级语句块中
break
和 continue
同时出现在同一级语句块中,就会自相矛盾,导致另一个语句无法到达!
5.6 goto
goto
可以在任何地方使用,但不能跨函数使用。
不建议使用,因为会破坏程序的结构!
5.6.1 goto不能跨函数使用
package mainimport "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 ENDfmt.Println("3333")
}
5.6.3 goto 的使用场景示例
有这么一个场景:打印 1 ~ 10,不能用 for
循环。
用 goto
就能很容易实现需求:
func main() {n := 1LOOP: // 定义 goto 的标签,以及实现代码逻辑fmt.Printf("%d ", n)n++if n <= 10 {goto LOOP // 跳转到该标签}
}/*
运行结果:
1 2 3 4 5 6 7 8 9 10
*/
以上代码,也是运用了循环的思维模式,只是换了跳转的方式而已。
还有一种方式就是递归:
var n = 1func 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 //函数
20小时快速入门go语言视频 - Day1相关推荐
- 20小时快速入门go语言视频 - Day5
笔记声明 本人智商较低,好记性不如烂笔头.笔记记录了网上一套Golang免费视频教程的知识点,以供未来自己翻阅查看.不写源视频的名称和网站地址了,免得被说是广告! 20小时快速入门go语言视频 - D ...
- 跟恶魔奶爸学英语 20小时快速掌握英语核心秘诀笔记总结
来源:跟恶魔奶爸学英语 20小时快速掌握英语核心秘诀 多年没学语法,语法的知识忘了不少,总感觉是阅读和写作时不太给劲,碰到恶魔奶爸的视频还可以,认认真真地学习并总结了里面所有的语法知识点,学完之后阅读 ...
- 敲代码时如何快速移动光标_数控加工中心编程入门知识,半小时快速入门!
数控加工中心编程入门知识汇总,教你半小时快速入门!不管做哪一行,想要成为个中高手,必然要经得住时间的历练,自身要不断提高工作能力,要想成为一个数控高手,从大学毕业进工厂起,最起码需要6年以上的时间.既 ...
- 3小时快速入门数学建模竞赛-建模技巧2:追根溯源,站在巨人的肩膀上
我们要检索哪些信息?和怎样获得快速.准确的信息?这两个问题. 首先回答第一个问题,我们需要检索哪些信息?我们从我们需要求解的内容入手,请看思维导图和系统框图,我们需要求解35年寿命期内的发电总量.经济 ...
- 敲代码时如何快速移动光标_数控加工中心编程入门知识,半小时快速入门!超简洁明了!...
数控加工中心编程入门知识汇总,教你半小时快速入门!不管做哪一行,想要成为个中高手,必然要经得住时间的历练,自身要不断提高工作能力,要想成为一个数控高手,从大学毕业进工厂起,最起码需要6年以上的时间.既 ...
- 视频教程-快速入门Android开发 视频 教程 android studio-Android
快速入门Android开发 视频 教程 android studio 任苹蜻,爱学啊创始人 & CEO,曾就职于某二车手公司担任Android工程师后离职创办爱学啊,我们的宗旨是:人生苦短,我 ...
- 2019 MySQL8 24小时快速入门(2)
第一天我们把环境搭好了. 接下来就可以进入实战了.工欲善其事必先利其器,首先我们要做的事情,就是找个好用的界面工具.我想你大概不想一直与这样黑漆漆的界面为伴吧: 如果没有绚丽的界面给我们,MySQL如 ...
- uni-app 5小时快速入门 6 项目配置
uni-app 5小时快速入门 文章目录 uni-app 5小时快速入门 6 项目配置 6.1 manifest.json 6.1.1 基础配置 6.1.2 App图标配置 6.1.3 App启动界面 ...
- uni-app 5小时快速入门 13 uni-app路由
uni-app 5小时快速入门 13 uni-app路由 文章目录 uni-app 5小时快速入门 13 uni-app路由 13.1 具体操作 13.1.1 创建新项目 13.1.2 添加几个页面 ...
最新文章
- mysql的调用有哪三种方式_MySQL数据库之mysql命令行中执行sql的几种方式总结
- Linux 下的终端选择,以及剪切板配置
- Django 3.2.5博客开发教程:URL与视图函数
- 【计算机系统设计】实践笔记(5)插叙:内外有别之CPU和Memory
- 学习笔记5-C语言-数组
- Android功耗(21)--- App耗电发热分析
- 利用 Conda 尝鲜 Python 3.10 不一样的特性 快来试试
- java如何多表断网,java Web如何离线使用并进行数据同步
- linux dd copy all partitions,Linux 系统下使用dd命令备份还原MBR主引导记录
- linux下备份msyql数据库
- 解数独(Python)
- HBase编程api介绍(转)
- mysql排序之if(isnull(字段名),0,1),fild 或者 if(isnull(字段名),1,0),fild
- 11 Mortal Fibonacci Rabbits
- mysql1062duplicate
- Sencha Cmd 优化 Sencha Ext JS/7.5.12
- OpenGL-入门-BMP像素图glDrawPixels
- spark进行数据清洗时,如何读取xlsx表格类型文件
- 金蝶物料辅助属性改造
- 华为云服务器部署tomcat+jdk