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.函数外的每个语句都必须以关键字开始。(varconstfunc 等)
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

遇到另一个 constiota 的值重新从 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
}

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 = 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 类型也可以书写成 runerune 类型本质就是 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 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 = 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 数值转换为字符串后再打印。
注: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] = 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.BufferWriteString() 方法去拼接字符串,时间复杂度为 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.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)) // 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 中,布尔类型的值只能是预定义标识符:truefalse

3.6.1 注意事项

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

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

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

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 非十进制可选前缀

一个整数数值字面量无需带前缀,除非它是负数,或者自己想要添加一个加号。
非十进制可选前缀设置表示法:
二进制:0b0B
八进制:0, 0o0O
十六进制:0x0X; 在十六进制中,[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 类型的值 truetrue <= 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 可以用在 forswitchselect

5.5.2 continue

continue 只能用在 for 循环中!

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

breakcontinue 同时出现在同一级语句块中,就会自相矛盾,导致另一个语句无法到达!

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相关推荐

  1. 20小时快速入门go语言视频 - Day5

    笔记声明 本人智商较低,好记性不如烂笔头.笔记记录了网上一套Golang免费视频教程的知识点,以供未来自己翻阅查看.不写源视频的名称和网站地址了,免得被说是广告! 20小时快速入门go语言视频 - D ...

  2. 跟恶魔奶爸学英语 20小时快速掌握英语核心秘诀笔记总结

    来源:跟恶魔奶爸学英语 20小时快速掌握英语核心秘诀 多年没学语法,语法的知识忘了不少,总感觉是阅读和写作时不太给劲,碰到恶魔奶爸的视频还可以,认认真真地学习并总结了里面所有的语法知识点,学完之后阅读 ...

  3. 敲代码时如何快速移动光标_数控加工中心编程入门知识,半小时快速入门!

    数控加工中心编程入门知识汇总,教你半小时快速入门!不管做哪一行,想要成为个中高手,必然要经得住时间的历练,自身要不断提高工作能力,要想成为一个数控高手,从大学毕业进工厂起,最起码需要6年以上的时间.既 ...

  4. 3小时快速入门数学建模竞赛-建模技巧2:追根溯源,站在巨人的肩膀上

    我们要检索哪些信息?和怎样获得快速.准确的信息?这两个问题. 首先回答第一个问题,我们需要检索哪些信息?我们从我们需要求解的内容入手,请看思维导图和系统框图,我们需要求解35年寿命期内的发电总量.经济 ...

  5. 敲代码时如何快速移动光标_数控加工中心编程入门知识,半小时快速入门!超简洁明了!...

    数控加工中心编程入门知识汇总,教你半小时快速入门!不管做哪一行,想要成为个中高手,必然要经得住时间的历练,自身要不断提高工作能力,要想成为一个数控高手,从大学毕业进工厂起,最起码需要6年以上的时间.既 ...

  6. 视频教程-快速入门Android开发 视频 教程 android studio-Android

    快速入门Android开发 视频 教程 android studio 任苹蜻,爱学啊创始人 & CEO,曾就职于某二车手公司担任Android工程师后离职创办爱学啊,我们的宗旨是:人生苦短,我 ...

  7. 2019 MySQL8 24小时快速入门(2)

    第一天我们把环境搭好了. 接下来就可以进入实战了.工欲善其事必先利其器,首先我们要做的事情,就是找个好用的界面工具.我想你大概不想一直与这样黑漆漆的界面为伴吧: 如果没有绚丽的界面给我们,MySQL如 ...

  8. 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启动界面 ...

  9. 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 添加几个页面 ...

最新文章

  1. mysql的调用有哪三种方式_MySQL数据库之mysql命令行中执行sql的几种方式总结
  2. Linux 下的终端选择,以及剪切板配置
  3. Django 3.2.5博客开发教程:URL与视图函数
  4. 【计算机系统设计】实践笔记(5)插叙:内外有别之CPU和Memory
  5. 学习笔记5-C语言-数组
  6. Android功耗(21)--- App耗电发热分析
  7. 利用 Conda 尝鲜 Python 3.10 不一样的特性 快来试试
  8. java如何多表断网,java Web如何离线使用并进行数据同步
  9. linux dd copy all partitions,Linux 系统下使用dd命令备份还原MBR主引导记录
  10. linux下备份msyql数据库
  11. 解数独(Python)
  12. HBase编程api介绍(转)
  13. mysql排序之if(isnull(字段名),0,1),fild 或者 if(isnull(字段名),1,0),fild
  14. 11 Mortal Fibonacci Rabbits
  15. mysql1062duplicate
  16. Sencha Cmd 优化 Sencha Ext JS/7.5.12
  17. OpenGL-入门-BMP像素图glDrawPixels
  18. spark进行数据清洗时,如何读取xlsx表格类型文件
  19. 金蝶物料辅助属性改造
  20. 华为云服务器部署tomcat+jdk

热门文章

  1. Qt使用QMediaPlayer类获取多媒体信息
  2. adb连接夜游神模拟器的方法
  3. java充值卡号生成_Java工具集-通用卡号转换
  4. Html左上角和右下角
  5. 你刚被人工智能洗脑,最聪明的钱已转向这16项技术
  6. MinGW-w64的下载与安装
  7. linux 逻辑卷 pe size 4.00 mib大小怎么改,linux 逻辑卷管理 调整分区大小
  8. win10 cpu虚拟化打不开
  9. 程序员英语二三事(4) - 应聘外企常用英语(1) - 从投简历开始
  10. 有源雷达与无源雷达的区别