文章目录
- Date: 2020.05
- Author: 浅若清风cyf
前言
参考文档:
《The Way to Go》
《Go 入门指南》
一、Go语言特点
①安全机制: Go 语言是一门类型安全和内存安全的编程语言。虽然 Go 语言中仍有指针的存在,但并不允许进行指针运算。
②平台支持: Go 语言对于网络通信、并发和并行编程有极佳支持,从而更好地利用大量的分布式和多核的计算机
③构建速度: 它的构建速度快(编译和链接到机器代码的速度),极大地提升了开发者的生产力,同时也使得软件开发过程中的代码测试环节更加紧凑
④依赖管理: C 语言中“头文件”, Go 语言采用包模型,通过严格的依赖关系检查机制来加快程序构建的速度,提供了非常好的可量测性。
⑤执行速度: Go 语言在执行速度方面也可以与 C/C++ 相提并论。
⑥实现多态: Go 语言没有类和继承的概念,通过接口(interface)的概念来实现多态性。
⑦静态类型: 不允许隐式类型转换
⑧动态语言特性和UTF-8编码
⑨垃圾回收和自动内存分配: 因为垃圾回收和自动内存分配的原因,Go 语言不适合用来开发对实时性要求很高的软件。
⑩特性缺失: 面向对象语言中使用的特性 Go 语言都没有支持
小结:
- 简化问题,易于学习
- 内存管理,简洁语法,易于使用
- 快速编译,高效开发
- 高效执行
- 并发支持,轻松驾驭
- 静态类型
- 标准类库,规范统一
- 易于部署
- 文档全面
- 免费开源
二、平台与架构
1、两个编译器:gc和gccgo
2、平台支持:支持类 Unix (linux和Mac OS)系统和Windows系统
3、处理器架构支持:Intel和ARM(支持安卓系统)
4、程序构建:go build
和go install
go build
编译自身包和依赖包go install
编译并安装自身包和依赖包
5、安装与运行环境https://learnku.com/docs/the-way-to-go/install-go-on-linux/3566
6、样例代码:
package main
func main() {
println("Hello", "world")
}
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("%s", runtime.Version())
}
7、Go的源代码分支:
- Go release:最新稳定版,实际开发最佳选择
- Go weekly:包含最近更新的版本,一般每周更新一次
- Go tip:永远保持最新的版本,相当于内测版
三、编辑器、集成开发环境与其它工具
1、编辑器:Emacs、Vim、Xcode 6、KD Kate、TextWrangler、BBEdit、McEdit、TextMate、TextPad、JEdit、SciTE、Nano、Notepad++、Geany、SlickEdit、Visual Studio Code、IntelliJ IDEA 和 Sublime Text 2
2、调试器:gdb
3、构建并运行 Go 程序
go build
编译自身包和依赖包go install
编译并安装自身包和依赖包
4、格式化代码go fmt(gofmt)
- Go 对于代码的缩进层级方面使用 tab (4个/8个空格)还是空格并没有强制规定
- 格式化方法:
gofmt -w *.go
或gofmt 文件目录
gofmt -w *.go
会格式化并重写所有 Go 源文件;
gofmt map1
会格式化并重写 map1 目录及其子目录下的所有 Go 源文件
- 其他格式化方法:
gofmt -r <原内容> -> <替换内容> -w *.go
gofmt -r '(a) -> a' –w *.go
上面的代码会将源文件中没有意义的括号去掉。
gofmt -r 'a[n:len(a)] -> a[n:]' –w *.go
上面的代码会将源文件中多余的
len(a)
去掉。
gofmt –r 'A.Func1(a,b) -> A.Func2(b,a)' –w *.go
上面的代码会将源文件中符合条件的函数的参数调换位置。
5、生成代码文档go doc
go doc package
获取包的文档注释go doc package/subpackage
获取子包的文档注释go doc package function
获取某个函数在某个包中的文档注释- 本地文档浏览服务器:
命令行godoc -http=:6060
;浏览器http://localhost:6060
6、其他工具
go install
是安装 Go 包的工具。主要用于安装非标准库的包文件,将源代码编译成对象文件。go fix
用于将你的 Go 代码从旧的发行版迁移到最新的发行版,工具会给出文件名和代码行数的提示以便让开发人员快速定位并升级代码go test
是一个轻量级的单元测试框架
7、性能
- Go 比 C++ 要慢 20%,比任何非静态和编译型语言快 2 到 10 倍,并且能够更加高效地使用内存。
- Go 的编译速度要比绝大多数语言都要快,比 Java 和 C++ 快 5 至 6 倍
- 在最理想的情况下,Go 能够和 C++ 一样快,比 Scala 快 2 至 3 倍,比 Java 快 5 至 10 倍。
- Go 在内存管理方面也可以和 C++ 相媲美,几乎只需要 Scala 所使用的一半,是Java的五分之一左右。
8、与其它语言进行交互cgo工具
- (详细使用方法见文档3.9)
- 与C交互:
cgo
- 与C++交互:
SWIG
四、基本结构和基本数据类型
1.文件名、关键字与标识符
- 文件名:小写字母+下划线(’_’)
- 变量名:区分大小写、字符开头(字母或_)
- 关键字(保留字):
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
- 36 个预定义标识符
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
- 分隔符:括号
()
,中括号[]
和大括号{}
- 标点符号:
.
、,
、;
、:
和…
。 - 结尾:不需要加分号
;
2.Go 程序的基本结构和要素
- 示例:
package main //包
import "fmt" //导入
func main() { //函数
fmt.Println("hello, world")
}
(1)包的概念、导入与可见性
- 包:在源文件中非注释的第一行指明这个文件属于哪个包(同一个包的代码不一定都写在一个文件里,只要指明属于同一个包即可,类似JAVA)
- 标准库:Go 的标准库包含了大量的包(如:fmt 和 os),但是你也可以创建自己的包【包的依赖关系决定了其构建顺序。】
- import:
import "fmt"
告诉 Go 编译器这个程序需要使用fmt
包(的函数,或其他元素)【类似python的包或C语言的头文件】 - 导入多个包的方法:
//法1:
import "fmt"
import "os"
//法2:
import "fmt"; import "os"
//法3:
import (
"fmt"
"os"
)
//法4:
import ("fmt"; "os")
-
指定导入包的位置:以
.
或/
开头, -
- 如果包名以
./
开头,则 Go 会在相对目录中查找;
- 如果包名以
-
- 如果包名以
/
开头,则会在系统的绝对路径中查找。
- 如果包名以
-
- 否则 Go 会在全局文件进行查找
-
可见性规则 :类似
public
和private
-
- 包外可见 : 标识符以大写字母开头【相当于public】
-
- 包外不可见:标识符以小写字母开头【相当于private】
(2)函数
- 函数定义:
func functionName()
- init()函数:最先执行
- main 函数:没有参数、没有返回类型
- 括号要求:左大括号
{
必须与方法的声明放在同一行,右大括号}
需要被放在紧接着函数体的下一行。
如果函数非常简短,也可以将它们放在同一行:
func Sum(a, b int) int { return a + b } //没有返回的变量名?
- 符合规范的函数一般形式:
func functionName(parameter_list) (return_value_list) {
…
}
//示例:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2){
}
其中:(变量名 类型)
- parameter_list 的形式为
(param1 type1, param2 type2, …)
- return_value_list 的形式为
(ret1 type1, ret2 type2, …)
fmt.Println("hello, world")
和fmt.Print("hello, world\n")
- 打印函数:单纯地打印一个字符串或变量甚至可以使用预定义的方法来实现,如:
print
、println:print("ABC")
、println("ABC")
、println(i)
(带一个变量 i)。【这些函数只可以用于调试阶段,在部署程序的时候务必将它们替换成fmt
中的相关函数。】
(3)注释
- 注释不会被编译,但可以通过 godoc 来使用。godoc 工具会收集这些注释并产生一个技术文档。
- 单行注释:
//
- 多行注释:
/*块注释*/
(4)类型
- var类型:使用 var 声明的变量的值会自动初始化为该类型的零值
- 基本类型:
int、float、bool、string
- 结构化(复合)类型:
struct、array、slice、map、channel
- 描述类型:
interface
- 空值:
nil
- 多返回值:函数可以有多个返回值
- 自定义类型(取别名):
type IZ int
- 变量名在前,类型名在后
(5)Go 程序的一般结构
package main
import (
"fmt"
)
const c = "C"
var v int = 5
type T struct{}
func init() { // initialization of package
}
func main() {
var a int
Func1()
// ...
fmt.Println(a)
}
func (t T) Method1() { //参数不应该是在函数名后面吗?func Method1 (t T)()
//...
}
func Func1() { // exported function Func1
//...
}
(6)类型转换
- 所有的转换都必须显式说明:
valueOfTypeB = typeB(valueOfTypeA)
即:类型 B 的值 = 类型 B(类型 A 的值)
- 具有相同底层类型的变量之间可以相互转换
- 取值范围小转换到取值范围大,反之,会报错
(7)Go 命名规范
- Go 语言中对象的命名也应该是简洁且有意义的。
- 通过 gofmt 来强制实现统一的代码风格。
3.常量
- 三种类型:布尔型、数字型(整数型、浮点型和复数)和字符串型
- 定义格式:
const identifier [type] = value
(类型可省了) - 数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出
const Ln2 = 0.693147180559945309417232121458\
176568075500134360255254120680009
const Log2E = 1/Ln2 // this is a precise reciprocal
const Billion = 1e9 // float constant
const hardEight = (1 << 100) >> 97
- 反斜杠
\
: 可以在常量表达式中作为多行的连接符使用。 - 当常量赋值给一个精度过小的数字型变量时,可能会因为无法正确表达常量所代表的数值而导致溢出
- 常量也允许使用并行赋值的形式:
const beef, two, c = "eat", 2, "veg"
- 常量还可以用作枚举:
const ( //这些枚举值可以用于测试某个变量或常量的实际值,比如使用 switch/case 结构
Unknown = 0
Female = 1
Male = 2
)
iota
可以被用作枚举值:(iota
的详细用法请查看《Go编程基础》 第四课:常量与运算符
const (
a = iota //a=iota=0
b //b=1
c //c=2
)
4.变量
(1)简介
- 变量声明:变量的类型放在变量的名称之后
- C 语言中,
int* a, b
的声明表示a 是指针而 b 不是【学了这么久的C语言才知道原来*是跟着变量名的!!!】- Go 中可以很轻松地将它们都声明为指针类型:
var a, b *int
- 变量声明的书写方法:
-
- ①分开写(一句一行)
-
- ②因式分解关键字法(一般用于声明全局变量)
- 当一个变量被声明之后,系统自动赋予它该类型的零值。所有的内存在 Go 中都是经过初始化的。
- 变量的命名方法:骆驼命名法(首个单词小写,新单词的首字母大写)【如果可被外包使用,则首字母大写】
- 作用域==>全局变量、局部变量
- 声明的变量未指定类型,但有赋值时,如:
var i = 5
,Go 编译器可以根据变量的值来自动推断其类型 - 变量的类型也可以在运行时实现自动推断,这种写法主要用于声明包级别的全局变量,
var (
HOME = os.Getenv("HOME")
USER = os.Getenv("USER")
GOROOT = os.Getenv("GOROOT")
)
- 简短声明语法
:=
,用于在函数体内声明局部变量,如:
a := 1
(2)值类型和引用类型
- 值类型:
int、float、bool 和 string、数组、结构 等
- 值类型的变量的值存储在栈中
- 引用类型:引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
- 获取变量i内存地址(指针):
&i
- 在 Go 语言中,指针属于引用类型,被引用的变量会存储在堆中,以便进行垃圾回收
- 其他引用类型:
slices、maps、channel
(3)打印
- 函数使用方法(与C语言类似):
func Printf(format string, list of variables to be printed)
- 作用:格式化字符串
- 输出到控制台:
fmt.Print("Hello:", 23)
- 返回给调用者:
fmt.Sprint("Hello:", 23)
(4)简短形式,使用 := 赋值操作符
- 使用操作符
:=
可以高效地创建一个新的变量,称之为初始化声明 - 只能被用在函数体内,而不可以用于全局变量的声明与赋值
- 举例:
a := 50
或b := false
//类型由编译器自动推断 - 在同一个代码块中。初始化声明后,不可再使用同名变量进行初始化声明。如:
a := 20
不允许 ,而a = 20
是可以的 - 局部变量声明后不使用是会报错的
- 全局变量是允许声明但不使用。
- 支持多变量并行 或 同时 赋值,可用于多个返回值的情况
- 交换变量的值:
a, b = b, a
- 空白标识符
_
被用于抛弃值(与python相同)
(5)init 函数
- 不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高
- 每个源文件都只能包含一个 init 函数(初始化以单线程执行)
5.基本类型和运算符
- 强调!!!:Go 是强类型语言,因此不会进行隐式转换,任何不同类型之间的转换都必须显式说明
- 表达式解析顺序:Go 不存在像 C 那样的运算符重载,表达式的解析顺序是从左至右
- 括号提升优先级
- 一元运算与二元运算
(1) 布尔类型 bool
- 取值: true 或者 false
- 比较:相等
==
或者不等!=
- 逻辑运算:非
!
、和&&
、或||
【同C语言】 - 用处:条件表达式【if、for 和 switch 结构】
- 格式化输出:
%t
- 布尔值的命名(非强制):以
is
或者Is
开头
(2)数字类型
- 类型:整型、浮点型、复数
- 位运算:位的运算采用补码
- 基于架构的类型:长度由操作系统决定
-
- 例如:
int
、uint
和uintptr
。
- 例如:
- 与操作系统架构无关的类型:长度固定
-
- 整型:
有符号:int8、int16、int32、int64;无符号:uint8、uint16、uint32、uint64
- 整型:
-
- 浮点型:
float32、float64
;
- 浮点型:
-
- 没有double类型!!!
-
- 精度:float32 精确到小数点后 7 位,float64 精确到小数点后 15 位;尽可能地使用 float64
-
- Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用
- 格式化说明符:`
-
- %d
用于格式化整数(
%x和
%X用于格式化 16 进制表示的数字),
%g用于格式化浮点型(
%f输出浮点数,
%e输出科学计数表示法),
%0nd` 用于规定输出长度为n的整数,其中开头的数字 0 是必须的。
- %d
-
%n.mg
用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串%5.2e
来输出 3.4 的结果为3.40e+00
。
- 显示类型转换:注意是否超出取值范围和精度丢失
- 复数:
-
- 类型:complex64 (32 位实数和虚数)
和complex128 (64 位实数和虚数)
- 类型:complex64 (32 位实数和虚数)
-
- 表示:
re+imI
,如:var c1 complex64 = 5 + 10i
- 表示:
-
- 格式化输出:输出全部
%v
、输出一部分:%f
- 格式化输出:输出全部
-
- 获得:
c = complex(re, im)
- 获得:
-
- 比较:等号
==
或者不等号!=
- 比较:等号
- 位运算:
-
- 二元运算符:按位与
&
、按位或|
、按位异或^
、位清除&^
- 二元运算符:按位与
-
- 一元运算符:按位补足
^
、位左移<<
、位右移>>
- 一元运算符:按位补足
位清除
&^
:将指定位置上的值设置为 0。
按位补足^
:该运算符与异或运算符一同使用,即m^x
,对于无符号 x 使用“全部位设置为 1”,对于有符号 x 时使用m=-1
。例如:^10 = -01 ^ 10 = -11
- 逻辑运算符:
==
、!=
、<
、<=
、>
、>=
- 算术运算符:
+
、-
、*
、/
、%
- 语句简写:
-=
、*=
、/=
、%=
,如:语句b = b + a
简写为b+=a
- 带有
++
和--
的只能作为语句,而不能用于表达式【与C语言不同!!!】 - 随机数:
-
- 函数
rand.Float32
和rand.Float64
返回介于 [0.0, 1.0) 之间的伪随机数,其中包括 0.0 但不包括 1.0。函数rand.Intn
返回介于 [0, n) 之间的伪随机数。
- 函数
-
- 使用
Seed(value)
函数来提供伪随机数的生成种子,一般情况下都会使用当前时间的纳秒级数字
- 使用
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
a := rand.Int() //没哟限制输出范围
b := rand.Intn(8) //相当于%8,输出范围为0~7
timens := int64(time.Now().Nanosecond())
rand.Seed(timens)
fmt.Printf("%2.2f / ", 100*rand.Float32()) //输出[0,100)的随机浮点数,整数部分2位,小数部分2位
}
- 运算符与优先级
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
- 类型别名:
type TZ int
- 字符类型:
-
- 字符只是整数的特殊用例
-
byte
类型是uint8
的别名
-
rune
类型是int32
的别名
-
- 写法:
//ASCII码:byte类型
var ch byte = 65 //法1:10进制法
var ch byte = '\x41' //法2:16进制法(\x后2位16进制数)
var ch byte = '\101' //法3:8进制法(\后3位8进制数)
//Unicode码:int16或int类型,至少占用2个字节
var ch int = '\u0041' //法4:\u后4位16进制
var ch3 int = '\U00101234' //法5:\U后8位16进制
-
- 格式化输出:字符
%c
、字符的整数%v
或%d
、U+hhhh 的字符串%U
- 格式化输出:字符
6.字符串
- 编码格式:UTF-8
- 长度:不定长(1 至 4 个字节)【与C++、Java 、Python 不同】,节省空间,无需编码和解码
- 解释字符串:
\n
、\r
、\t
、\u
或\U
、\\
(与双引号一起使用) - 非解释字符串:字符串使用反引号括起来
`This is a raw string \n` 中的 `\n\` 会被原样输出。
-
- 和 C/C++不一样,Go 中的字符串是根据长度限定,而非特殊字符
\0
。
- 和 C/C++不一样,Go 中的字符串是根据长度限定,而非特殊字符
-
string
类型的零值为长度为零的字符串,即空字符串""
。
- 获取字符串的内容:标准索引法
str[i]
,str为字符串名 - 字符串拼接符
+
,简写形式+=
,其他拼接方法:函数strings.Join()
或字节缓冲bytes.Buffer
7.strings 和 strconv 包
(1)前后缀判断(HasPrefix/HasSuffix)
HasPrefix
判断字符串 s
是否以 prefix
开头:
strings.HasPrefix(s, prefix string) bool
HasSuffix
判断字符串 s
是否以 suffix
结尾:
strings.HasSuffix(s, suffix string) bool
(2)字符串包含关系判断(Contains)
Contains
判断字符串 s
是否包含 substr
:
strings.Contains(s, substr string) bool
(3)字符串匹配返回索引(Index/LastIndex/IndexRune)
//ASCII码
strings.Index(s, str string) int //返回第一个索引
strings.LastIndex(s, str string) int //返回最后一个索引
//非ASCII码
strings.IndexRune(s string, r rune) int
//第二个参数也可以是int类型
- 找不到返回-1
(4)字符串替换(Replace)
strings.Replace(str, old, new, n) string
- 将字符串
str
中的前n
个字符串old
替换为字符串new
- 若n = -1,则替换全部
old
为new
(5)统计字符串出现次数(Count)
Count
用于计算字符串 str
在字符串 s
中出现的非重叠次数:
strings.Count(s, str string) int
(6)字符串复制拼接(Repeat)
strings.Repeat(s, count int) string
//例:
newS = strings.Repeat("a", 3) //newS="aaa"
(7)修改字符串大小写(ToLower/ToUpper)
- 对象:Unicode码
strings.ToLower(s) string //变小写
strings.ToUpper(s) string //变大写
(8)修剪字符串(Trim)
- 剔除开头和结尾的字符
-
- 剔除空白符号:
strings.TrimSpace(s)
- 剔除空白符号:
-
- 剔除指定字符"cut":
strings.Trim(s, "cut")
- 剔除指定字符"cut":
- 剔除开头:
TrimLeft
- 剔除结尾:
TrimRight
(9)分割字符串(Fields/Split)
- 空白符号分割符分割:
strings.Fields(s)
- 自定义分割符
sep
分割:strings.Split(s, sep)
- 返回slice(切片)【一般用for-range处理】
(10)拼接 slice 到字符串(Join)
- 使用分割符号
sep
来拼接:strings.Join(sl []string, sep string) string
(11)从字符串中读取内容(Read)
- 生成一个Reader,返回Reader指针:
strings.NewReader(str)
- 从 []byte 中读取内容:
Read()
- 从字符串中读取下一个 byte 或者 rune:
ReadByte()
和ReadRune()
(12)字符串与其它类型的转换( strconv包)
- 任何类型 T 转换为字符串总是成功的。
- 数字类型==>字符串:①
strconv.Itoa(i int) string
、②strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string
- 字符串==>数字类型:①
strconv.Atoi(s string) (i int, err error)
②strconv.ParseFloat(s string, bitSize int) (f float64, err error)
- 具体使用方法见参考书
8.时间和日期
time
包- 数据类型
time.Time
- 当前时间:
time.Now()
、t.Day()
、t.Minute()
、t.Month()
、t.Year()
- 时区:
t = time.Now().UTC()
- 预定义格式化字符串:
t.Format(time.RFC822)
和t.Format(time.ANSIC)
- 自定义时间格式化字符串:
fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year())
- 标准时间的格式化:
fmt.Println(t.Format("02 Jan 2006 15:04"))
,输出:21 Jul 2011 10:31
- 进程控制:
time.After
、time.Ticker
、time.Sleep
9.指针
- Go 语言为程序员提供了控制数据结构的指针的能力,但不能进行指针运算
- Go 语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式
- 使用方法与C/C++相似
- 取地址符:
&
(返回相应变量的内存地址) - 使用指针:
var i1 = 5
var intP *int
intP = &i1
- 获取指针指向的内容:
*intP
(初始值为nil
) - 不能获取字面量或常量的地址
- Go语言中,像C/C++中的指针移动指向字符串/数组的某个位置是不被允许的(如:
c = *p++
是不合法的) - 一个空指针的反向引用是不合法的(如:
var p *int = nil
是不合法的)
五、控制结构
1.if-else结构
- 格式:(与C语言相似)
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
- 注意0:条件表达式不需要加括号(类似Python)
- 注意1:只有一条语句时,大括号也不可被省略
- 注意2:if 和 else 之后的左大括号
{
必须和关键字在同一行 - 注意3:前段代码块的右大括号
}
必须和 else-if 关键字在同一行 - 注意4:Go 1.1版本之前,不要同时在 if-else 结构的两个分支里都使用 return 语句
- 注意5 if 可以包含一个初始化语句(如:给一个变量赋值),初始化语句后方必须加上分号
if val := 10; val > max { //简短方式 `:=` 声明的变量的作用域只存在于 if 结构中
// do something
}
- -常用的条件表达式:
-
- 操作系统类型:
if runtime.GOOS == "windows"
- 操作系统类型:
2.测试多返回值函数的错误
- Go 语言的函数经常使用两个返回值来表示执行是否成功:返回某个值以及 true 表示成功
- 返回零值(或 nil)和 false 表示失败
- 第二个返回值:可以用一个error类型替代;成功执行的话,error 的值为 nil,否则就会包含相应的错误信息
- comma,ok 模式
- 在错误发生的同时终止程序的运行:
os.Exit(1)
3.switch结构
- Go 语言中的 switch 结构,可以接受任意形式的表达式,但必须是相同类型
- 可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:
case val1, val2, val3
。 - 一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个 switch 代码块,不需要特别使用
break
语句来表示结束。【与C语言不同】 - 如果在执行完每个分支的代码后,还希望继续执行后续分支的代码,可以使用
fallthrough
关键字来达到目的。 - 可以使用
return
语句来提前结束代码块的执行 default
分支(可选项):一般放在最后(相当于else)- 格式:
switch var1 { //花括号 `{` 必须和 switch 关键字在同一行
case val1:fallthrough
...
case val2:
...
default:
...
}
- 其他高级示例见参考资料
switch a, b := x[i], y[j] {
case a < b: t = -1
case a == b: t = 0
case a > b: t = 1
}
4.for结构
- 循环结构:Go 语言中只有 for 结构可以使用(没有while结构)
(1)基于计数器的迭代
- 和C语言的
for(;;)
相似,可以在循环中同时使用多个计数器
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
//使用多个计数器
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
- for循环嵌套
for i:=0; i<5; i++ {
for j:=0; j<10; j++ {
println(j)
}
}
(2)基于条件判断的迭代
- 类似C语言的while语言
- 基本形式为:
for 条件语句 {}
- 示例:
var i int = 5
for i >= 0 {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
(3)无限循环
- for 循环的头部没有条件语句
- 相当C语言中的
while(TRUE)
for { }
:相当于for true { }
- 需要在循环体内有相关的条件判断确保能退出循环,如:
return
或break
(4)for-range结构
- 可以迭代任何一个集合(包括数组和 map)
- 类似python中的
for i in array:
- 一般形式为:
for ix, val := range coll { }
- 迭代字符串:
for pos, char := range str {
fmt.Printf("Character on position %d is: %c \n", pos, char)
}
5.Break与continue
(1)break
- 一个 break 的作用范围为该语句出现后的最内部的结构,它可以被用于任何形式的 for 循环(计数器、条件判断等)
- 在 switch 或 select 语句中,break 语句的作用结果是跳过整个代码块,执行后续的代码。
(2)continue
- 忽略剩余的循环体而直接进入下一次循环的过程【执行之前依旧需要满足循环的判断条件】
- 关键字 continue 只能被用于 for 循环中
6.标签与goto
- 标签名称:区分大小写,一般用全部大写字母
- 可以使用 goto 语句和标签配合使用来模拟循环【但不推荐使用】
- 使用 goto,应当只使用正序的标签(标签位于 goto 语句之后)
- 标签使用示例:
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
- 标签和goto模拟循环示例:
func main() {
i:=0
HERE:
print(i)
i++
if i==5 {
return
}
goto HERE
}
六、函数
1.介绍
- 返回值:
return
语句可以带有零个或多个参数 - 三种类型的函数:
-
- 普通的带有名字的函数
-
- 匿名函数或者lambda函数
-
- 方法(Methods)
- main()、init()函数:无参数、无返回值
- 函数签名: 函数参数、返回值以及它们的类型的统称。
- 书写格式:
func g() {
}
- 函数调用的基本格式:
pack1.Function(arg1, arg2, …, argn)
- 可以使用其他函数调用作为调用函数的参数
- Go语言不支持函数重载
- 不需要写函数体的情况:
-
- 申明一个在外部定义的函数:
func flushICache(begin, end uintptr)
- 申明一个在外部定义的函数:
-
- 申明一个函数类型:
type binOp func(int, int) int
- 申明一个函数类型:
- 函数不能嵌套,但可以使用匿名函数
- Go 没有泛型(generic)的概念,也就是说它不支持那种支持多种类型的函数
- 使用接口
2.函数参数与返回值
- 形参和返回值可以只定义类型,没有名称
(1)参数传递
- 按值传递:传递参数的副本
- 按引用传递:传递参数的地址(指针)
&variable
- 像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递
(2)返回值
- 返回值在函数调用时就已经被赋予了一个初始零值
- 命名返回值:需要加括号,return可不以带参数【return 可不带参数】
- 非命名返回值:返回多个非命名返回值时,需要使用
()
把它们括起来,比如(int, int)
【return 需要指明返回的变量或可计算的值】 - 示例:
//非命名返回值
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
//命名返回值
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
// return x2, x3
return //return无需参数
//return 或 return var 都是可以的。
}
- 即使函数使用了命名返回值,你依旧可以无视它而返回明确的值
(3)空白符"_"
- 空白符用来匹配一些不需要的值,然后丢弃掉(与python相似)
(4)改变外部变量
- 作用:节省内存
- 在函数内修改指针的内容
- 示例:reply是一个指针,通过*reply修改指针内容,无需返回值
// this function changes reply:
func Multiply(a, b int, reply *int) {
*reply = a * b
}
3.传递变长参数
- 变参函数:最后一个参数是采用
...type
(type为参数的类型) 的形式,参数长度>=0 - 示例:
func myFunc(a, b, arg ...int) {}
- 可以通过
slice...
的形式来传递参数,调用变参函数 - 示例:
func main() {
x := min(1, 3, 2, 0)
fmt.Printf("The minimum is: %d\n", x)
slice := []int{7,9,3,5,1}
x = min(slice...) //采用slice...传递参数
fmt.Printf("The minimum in the slice is: %d", x)
}
func min(s ...int) int {
if len(s)==0 {
return 0
}
min := s[0]
for _, v := range s { //_是索引信息
if v < min {
min = v
}
}
return min
}
- 传递不同类型的变长参数:
-
- 使用结构struct【定义结构:
type name struct
】
- 使用结构struct【定义结构:
-
- 使用空接口interface{}【可以接受任何类型的参数】
//相当于把interface{}作为类型名
func typecheck(..,..,values … interface{}) {
for _, value := range values { //_是索引信息,不需要使用,故使用空白符
switch v := value.(type) {
case int: …
case float: …
case string: …
case bool: …
default: …
}
}
}
4.defer 和追踪
- defer:推迟到函数返回之前一刻才执行某个语句或函数,一般用于释放某些已分配的资源(相当于Java的finally语句块)
- 多个defer:逆序执行(后进先出,类似栈)
func f() {
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
}
//输出:4 3 2 1 0
- defer进行收尾工作:
-
- 关闭文件流:
defer file.Close()
- 关闭文件流:
-
- 解锁一个加锁资源:
defer mu.Unlock()
- 解锁一个加锁资源:
-
- 打印最终报告:
defer printFooter()
- 打印最终报告:
-
- 关闭数据库链接:
// open a database connection
- 关闭数据库链接:
- 使用 defer 语句实现代码追踪(见参考书)
- 使用 defer 语句来记录函数的参数与返回值(见参考书)
5.内置函数
- 不需要导入即可使用
- close:管道通信
- len、cap:长度或数量;容量
- new、make:分配内存
- copy、append:复制和连接切片
- panic、recover:错误处理机制
- print、println:底层打印函数
- complex、real、imag:创建和操作复数
6.递归函数
- 使用方法与其他语言的方法相同
- 解决栈溢出的问题:懒惰求值(使用管道(channel)和 goroutine来实现)【详见参考书第14.8节】
- Go 语言中也可以使用相互调用的递归函数:多个函数之间相互调用形成闭环(函数的声明顺序可以是任意的)
- 示例:见递归函数
7.将函数作为参数
- 回调:函数作为其它函数的参数进行传递,然后在其它函数内调用执行
- 示例:见回调函数
8.闭包
- 匿名函数:没有函数名的函数,不能独立存在,但可以用于变量赋值,通过变量名对函数进行调用
- 示例1:匿名函数的定义和调用
func() { //没有函数名,定义函数体
sum := 0
for i := 1; i <= 1e6; i++ {
sum += i
}
}() //此括号表示调用该匿名函数
- 示例2:变量名调用匿名函数
fplus := func(x, y int) int { return x + y } //定义匿名函数,并赋值给变量fplus
fplus(3,4) //通过变量名fplus调用匿名函数
- defer 语句和匿名函数:可以用于改变函数的命名返回值
- 闭包:即匿名函数,一个闭包继承了函数所声明时的作用域,作用域内的变量都被共享到闭包的环境中,这些变量可以在闭包中被操作,直到被销毁。
- 闭包的应用:包装函数、错误检查
9.应用闭包:将函数作为返回值
10.使用闭包调试
runtime
中的函数Caller()
log
包中的特殊函数
使用闭包调试
11.计算函数执行时间
- 结束时间-起始时间:
start := time.Now() //起始时间
longCalculation()
end := time.Now() //结束时间
delta := end.Sub(start) //作差
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
12.通过内存缓存来提升性能
- 内存缓存:在内存中缓存和重复利用相同计算的结果(避免重复计算)
- 方法:使用索引
通过内存缓存来提升性能