Go基础-变量、分支与循环

摘要

Go是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

最初Go语言的Logo是一只可爱的土拨鼠,土拨鼠昼伏夜出的习性让它显得很有Geek范。土拨鼠的行动其实并不敏捷,不过它繁殖能力很强,生长发育的很快。

到了 2018年,Go 语言重新制定了 Logo,消灭了土拨鼠,取而代之的是纯文字。这好像是在告诉用户 Go 语言不再是一个玩具语言,而是一个严肃的高效的正式语言。

Go 语言特色

  • 简洁、快速、安全
  • 并行、有趣、开源
  • 内存管理、数组安全、编译迅速

Go 语言最主要的特性:

  • 自动垃圾回收
  • 更丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 反射
  • 语言交互性

Go 语言用途

Go语言被设计成一门应用于搭载Web服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

对于高性能分布式系统领域而言,Go语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

Go 基础

Go 语言结构

Go 语言的基础组成有以下几个部分:

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
1
2
3
4
5
6
7
8
9
10
// 定义了包名
package main

// 导入 fmt 包(的函数,或其他元素)
import "fmt"

func main() {
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!")
}
  • package main:定义了包名
    • 必须在源文件中非注释的第一行指明这个文件属于哪个包
    • 表示一个可独立执行的程序,每个Go应用程序都包含一个名为main的包
  • func main()
    • main函数是每一个可执行程序所必须包含的
    • 一般来说都是在启动后第一个执行的函数
    • 如果有init函数则会先执行该函数

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,

如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);

标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

运行

这个文件的名字是 main.go,使用下面的命令运行这个文件

1
2
3
4
$ go run main.go

# 输出
hello world!

编译运行

go run指令只是用来开发调试用的,在生产环境中程序可不是这样跑的。在开发完成后,需要将程序编译成没有任何依赖的二进制可执行文件,扔到服务器上运行起来。

1
2
3
4
$ go build main.go

$ ./main
hello world!

设置GOPATH

1
export GOPATH=~/go

关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

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

除了以上介绍的这些关键字,Go 语言还有 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 print println real
recover string true uint uint8 uintptr

程序一般由关键字、常量、变量、运算符、类型和函数组成。

程序中可能会使用到这些分隔符:括号(),中括号[]和大括号{}

程序中可能会使用到这些标点符号:.、,、;、: 和 …

数据类型

布尔型

布尔型的值只可以是常量true或者false。一个简单的例子:var b bool = true

数字类型

整型int和浮点型float32、float64Go语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。

字符串类型

字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。

派生类型:

  • 指针类型(Pointer
  • 数组类型
  • 结构化类型(struct)
  • Channel类型
  • 函数类型
  • 切片类型
  • 接口类型(interface
  • Map类型

变量与常量

变量声明的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//指定变量类型,声明后若不赋值,使用默认值。
var v_name v_type
v_name = value

// 根据值自行判定变量类型。
var v_name = value


// 省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。
v_name := value

// 实例
var a int = 10
var b = 10
c := 10

这三种变量定义方式都是可行的,各有其优缺点。可读性最强的是第一种,写起来最方便的是第三种,第二种是介于两者之间的形式。

通常情况下:

  • var的意思就是「我很重要,你要注意」
  • :=的意思是「我很随意,别把我当回事」
  • var再带上显式的类型信息是为了方便读者快速识别变量的身份。

默认值

在第一种声明变量的时候不赋初值,编译器就会自动赋予相应类型的「零值」,不同类型的零值不尽相同,比如

  • 字符串的零值不是nil,而是空串
  • 整形的零值就是0
  • 布尔类型的零值是false

多变量声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//类型相同多个变量, 非全局变量
var x, y int
x, y = 1, 2

// 这种因式分解关键字的写法一般用于声明全局变量
var (
a int
b bool
)

// 不需要显示声明类型,自动推断
var c, d int = 1, 2
var e, f = 123, "hello"

//这种不带声明格式的只能在函数体中出现
//出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误
g, h := 123, "hello"

全局变量和局部变量

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

var globali int = 24

func main() {
var locali int = 42
fmt.Println(globali, locali)
}
  • 如果全局变量的首字母大写,那么它就是公开的全局变量。
  • 如果全局变量的首字母小写,那么它就是内部的全局变量。
  • 内部的全局变量只有当前包内的代码可以访问,外面包的代码是不能看见的。

指针类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
packge main

import "fmt"

var a int = 10

func main() {
var value int = 42
var pointer *int = &value
fmt.Println(pointer, *pointer)
fmt.Println(a)
// 获取变量内存地址
fmt.Println(&a)

var value int = 42
var p1 *int = &value
var p2 **int = &p1
var p3 ***int = &p2
fmt.Println(p1, p2, p3)
fmt.Println(*p1, **p2, ***p3)
}

指针变量本质上就是一个整型变量,里面存储的值是另一个变量内存的地址。 和 & 符号都只是它的语法堂,是用来在形式上方便使用和理解指针的。 操作符存在两次内存读写,第一次获取指针变量的值,也就是内存地址,然后再去拿这个内存地址所在的变量内容。

常量

Go 语言还提供了常量关键字 const,用于定义常量。常量可以是全局常量也可以是局部常量。你不可以修改常量,否则编译器会抱怨。常量必须初始化,因为它无法二次赋值。全局常量的大小写规则和变量是一致的。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

1
const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

1
2
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

1
const c_name1, c_name2 = value1, value2

注意事项

  • 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如a := 20就是不被允许的,编译器会提示错误no new variables on left side of :=
  • 但是a = 20是可以的,因为这是给相同的变量赋予一个新的值。
  • 如果你在定义变量a之前使用它,则会得到编译错误undefined: a
  • 如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误a declared and not used
  • 但是全局变量是允许声明但不使用。 同一类型的多个变量可以声明在同一行
  • 多变量可以在同一行进行赋值
  • 如果你想要交换两个变量的值,则可以简单地使用a, b = b, a,两个变量的类型必须是相同
  • 空白标识符_也被用于抛弃值,如值5在:_, b = 5, 7中被抛弃
  • _实际上是一个只写变量,你不能得到它的值。这样做是因为Go语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值
  • 并行赋值也被用于当一个函数返回多个返回值时,val, err = Func1(var1)

基础类型大全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import "fmt"

func main() {
// 有符号整数,可以表示正负
var a int8 = 1 // 1 字节
var b int16 = 2 // 2 字节
var c int32 = 3 // 4 字节
var d int64 = 4 // 8 字节
fmt.Println(a, b, c, d)

// 无符号整数,只能表示非负数
var ua uint8 = 1
var ub uint16 = 2
var uc uint32 = 3
var ud uint64 = 4
fmt.Println(ua, ub, uc, ud)

// int 类型,在32位机器上占4个字节,在64位机器上占8个字节
var e int = 5
var ue uint = 5
fmt.Println(e, ue)

// bool 类型
var f bool = true
fmt.Println(f)

// 字节类型
var j byte = 'a'
fmt.Println(j)

// 字符串类型
var g string = "abcdefg"
fmt.Println(g)

// 浮点数
var h float32 = 3.14
var i float64 = 3.141592653
fmt.Println(h, i)
}

-------------
1 2 3 4
1 2 3 4
5 5
true
abcdefg
3.14 3.141592653
97

字符串和数字转换

1
2
3
4
//字符串变成数字
b,error := strconv.Atoi(a)
//数字变成字符串
d := strconv.Itoa(c)

分支与循环

Go语言的分支循环语句选择性较少,循环语句它只有for循环,平时我们在其它语言用的while语句、do while语句、loop语句它是没有的。分支语句只有ifswitch,也没有三元操作符。

少并不是坏事,够用就行。语法糖丰富了表面上选择性多了功能强大了,但是也会增加代码的理解成本,用户需要掌握更多的知识才可以理解代码,这会提高语言的学习门槛。

if else

Go 语言没有三元操作符,这里只能使用 if 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

func main() {
fmt.Println(sign(max(min(24, 42), max(24, 42))))
}

func max(a int, b int) int {
if a > b {
return a
}
return b
}

func min(a int, b int) int {
if a < b {
return a
}
return b
}

func sign(a int) int {
if a > 0 {
return 1
} else if a < 0 {
return -1
} else {
return 0
}
}

switch 语句

switch 有两种匹配模式

  • 一种是变量值匹配
  • 一种是表达式匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

func main() {
fmt.Println(prize1(60))
fmt.Println(prize2(60))
}

// 值匹配
func prize1(score int) string {
switch score / 10 {
case 0, 1, 2, 3, 4, 5:
return "差"
case 6, 7:
return "及格"
case 8:
return "良"
default:
return "优"
}
}

// 表达式匹配
func prize2(score int) string {
// 注意 switch 后面什么也没有
switch {
case score < 60:
return "差"
case score < 80:
return "及格"
case score < 90:
return "良"
default:
return "优"
}
}

for 循环

Go 语言虽然没有提供 while 和 do while 语句,不过这两个语句都可以使用 for 循环的形式来模拟。

  • for 什么条件也不带的,相当于 loop {} 语句
  • for 带一个条件的相当于 while 语句
  • for 带三个语句的就是普通的 for 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
// 死循环loop
for {
fmt.Println("hello world!")
}
// 死循环 while true
for true {
fmt.Println("hello world!")
}
// for循环
for i := 0; i < 10; i++ {
fmt.Println("hello world!")
}
}

循环控制

Go 语言支持 continue 和 break 语句来控制循环,这两个语句和其它语言没什么特殊的区别。除此之外 Go 语言还支持 goto 语句。

计算机科学家迪杰斯特拉很讨厌 goto 语句,他的那片广为流传的论文《Goto 有害》已经家喻户晓般地为人所知,可是他已经去世这么多年了,很遗憾还是没能阻挡 goto 语句死灰复燃般地继续存在。