Go chapter1


前言

因为最近这段时间被拉去打一个程序开发的比赛,所以花几天学了一下go语言。

接下来打算把学习go的笔记更一下,后面再写一些关于go的安全编程的内容。

基本结构

包和函数

1
2
3
4
5
6
7
package main()
import (
"fmt"
)
func main(){
fmt.Println("hello world")
}

package关键字声明了代码所属的包。

在package关键字之后,代码使用了import关键字来导入自己将要用到的包。一个包可以包含任意数量的函数。

fmt包提供了用于格式化输入和输出的函数。

func关键字用于声明函数,在本例中这个函数的名字就是main。每个函数的体都需要使用大括号包围,这样go才能知道每个函数从何处开始,又在何处结束。

当我们运行一个程序的时候,它总是从 main 包的 main 函数开始运行。如果 main 不存在,编译器将报错。

每次用到被导入包的某个函数时,我们都需要在函数的名字前面加上包的名字以及一个点号作为前缀。

唯一允许的大括号放置风格

go对于大括号({})的摆放非常挑剔。左大括号 { 与func关键字位于同一行,而右大括号 } 则独占一行。这是go语言唯一允许的大括号放置风格,除此之外的其他大括号放置风格都是不被允许的。

如果用户尝试将左大括号和 func 关键字放在不同的行里面,那么go编译器将报告一个语法错误。

被美化的计算器

执行计算

注释

go语言的注释和C语言一样

单行注释

1
//这是单行注释

多行注释

1
2
3
/*
这是多行注释
*/

算术运算符

编程语言中一般通用的常规运算符。

运算符 功能
+
-
*
/
%

格式化输出

Println输出函数
这个函数输出的内容会在后面加一个换行,也就是\n

1
fmt.Println("hello world")

Printf格式化输出函数

1
fmt.Printf("number: %v",10)

Printf 接收的第一个参数总是文本,第二个参数则是表达式,而文本中包含的格式化变量%v则会在之后被替换成表达式的值。

这个和C语言中的printf很相似。

%v是通用类型的意思

虽然Println会自动将输出的内容推进至下一行,但是Printf和Print却不会那么做。对于后面这两个函数,用户可以通过在文本里面放置换行符\n来将输出内容推进至下一行。

如果用户指定多个格式化变量,那么Printf函数将按顺序把它们替换成相应的值。

1
fmt.Printf("string: %[0]v \n number: %[1]v \n","Earth",10)

Printf除可以在句子的任意位置将格式化变量替换成指定的值之外,还能够调整文本的对齐位置。

用户可以通过指定带有宽度的格式化变量%4v,将文本的输出宽度填充至4个字符。

当宽度为正数时,空格将被填充至文本左边,而当宽度为负数时,空格将被填充至文本右边。

1
2
3
4
5
6
7
8
fmt.Printf("%10v\n",10)
//输出结果
//左边被以空格填充
10
fmt.Printf("%-10v1\n",10)
//输出结果
//右边被以空格填充
10 1

常量和变量

常量

1
2
//基本声明
const host=24

变量

1
2
//基本声明
var dis=9633

\

走捷径

一次声明多个变量

每一行单独声明一个变量

1
var dis =560

一次声明一组变量

1
2
3
4
var(
dis=560
speed=1008
)

同一行声明多个变量

1
var dis,speed=560,1008

需要注意的是,为了保证代码的可读性,我们在一次声明一组变量或者在同一行声明多个变量之前,应该先考虑这些变量是否相关。

增量并赋值操作符

有几种快捷方式可以让我们在赋值的同时执行以下操作。

1
2
3
var weight=129.0
weigth*=0.37
//等效于:weight=weight*0.37

常见的有,这些概念一般其他编程语言中也有

符号 功能
+= 加上符号右边的值后再赋值
-= 减上符号右边的值后再赋值
*= 乘以符号右边的值后再赋值
/= 除以符号右边的值后再赋值
%= 模运算符号右边的值后再赋值

自增运算符

用户可以使用i++执行加1操作

但是go并不支持++i这种C语言中的操作。

1
2
3
var i=0
i++
i--

数字游戏

使用rand包来生成伪随机数

下列代码中,会显示一个110之间的数字。这个程序会先向Intn函数传入数字10以返回一个09的随机数,然后把这个数字加一并将其结果赋值给变量num。

传入给Intn函数的10会让rand生成从零开始的十个数字之间的随机数,即一个09的数字,所以如果我们要求是110则需加上一个1。

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

import(
"fmt"
"math/rand"
)
func main(){
var num = rand.Intn(10)+1
fmt.Println(num)
}

虽然 rand 包的导入路径为math/rand,但是我们在调用 Intn 函数的时候只需要使用包名 rand 作为前缀即可,不需要使用整个导入路径。

循环和分支

真或假

布尔变量

注意,go中0和1不能作为布尔值使用。

1
2
3
4
//真
var z=ture
//假
var j=false

go语言标准库里面有好多函数都会返回布尔值。

例如如下代码

代码中使用了strings包的Contais函数来检查command变量是否包含单词”outsize”,如果包含则返回为true,否则返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"strings"
)

func main(){
var command="hello world"
var exit=strings.Contains(command,"hello")
fmt.Println("结果",exit)
}
//运行结果
结果 true

比较

比较运算符

符号 含义
== 相等
!= 不相等
< 小于
<= 小于等于
> 大于
>= 大于等于
表中的运算符既可以比较文本,又可以比较数值。

比较结果返回的是布尔值。

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

import (
"fmt"
)

func main(){
var num=33
var age=num<10
fmt.Printf("%v是否小于10:%v(true/false)\n",num,age)
}
//运行结果
33是否小于10falsetrue/false

使用if实现分支判断

如下所示,计算机可以使用布尔值或者比较条件在if语句中选择不同的执行路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main(){
var command="go"
if command=="no"{
fmt.Println("1")
}else if command=="go"{
fmt.Println("2")
}else{
fmt.Println("3")
}
}
//运行结果
2

这里要注意,else if 和 else 都要紧跟在前一个大括号后面,否则会报错。

else if语句和else语句都是可选的。当有多个分支路径可选时,可以重复使用else if直到满足需要为止。

逻辑运算符

基本都是编程语言通用的。

符号 含义
|| 逻辑或
&& 逻辑与
逻辑非
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

func main(){
var num=1000
var bin=num>500 || (num/2==500 && num%100==10)
if bin{
fmt.Println("真")
}else{
fmt.Println("假")
}
}
//运行结果

跟大多数编程语言一样,go也采用了短路逻辑:如果位于 || 运算符之前的第一个条件为真,那么位于 || 运算符之后的条件就可以被忽略,没有必要再对其进行求值。

&& 运算符的行为与 || 运算符正好相反:只有在两个条件都为真的情况下,运算结果才为真。

逻辑非运算符 ! 可以将一个布尔值从 false 变为 true,或者将 true 变为 false。

使用switch实现分支判断

go提供了 switch 语句,它可以将单个值和多个值进行比较。

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

import (
"fmt"
)
func main(){
var command="b"
//将命令和给定的多个分支进行比较
switch command {
case "a":
fmt.Println("a")
//使用逗号分隔多个可选值
case "b","c":
fmt.Println("b/c")
//没有匹配的分支则执行
default:
fmt.Println("d")
}
}

switch 语句的另一种用法是像 if…else 那样,在每个分支中单独设置比较条件。

switch 还拥有独特的 fallthrough 关键字,它可以用于执行下一个分支的代码。

与 c 和 java 等编程语言不同,go的 switch 语句执行一个条件分支后默认是不执行下一行的

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

import (
"fmt"
)

func main(){
var command="b"
//比较表达式放置到单独的分支里
switch command {
case "a":
fmt.Println("a")
case "b":
fmt.Println("b")
//下降至下一分支
fallthrough
case "c":
fmt.Println("c")
default:
fmt.Println("d")
}
}

使用循环实现重复执行

当需要重复执行同一段代码的时候,与一遍又一遍键入相同的代码相比,更好的办法是使用 for 关键字。

go语言是没有 while 循环关键字的,不过如下所示我们可以通过 for 关键字实现 while 循环。

后面会讲解 for 关键字如何实现类似于C语言中的那种形式。

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
package main

import (
"fmt"
)
func main(){
var count=10
for count>0{
fmt.Println(count)
count--
}
fmt.Println("结束")
}
//运行结果
10
9
8
7
6
5
4
3
2
1
结束

在每次迭代开始之前,表达式 count>0 都会被求值并产生一个布尔值。当该值为 false也就是 count 变量等于 0 的时候,循环就会终止。反之,如果该值为真,那么程序将继续执行循环体,也就是被 { 和 } 包围的那部分代码。

此外我们还可以通过不为 for 语句设置任何条件来产生无限循环,然后在有需要的时候通过在循环体内使用 break 语句来跳出循环。

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
package main

import (
"fmt"
)
func main(){
var count=10
for count>0{
fmt.Println(count)
count++
if count>20{
break
}
}
fmt.Println("结束")
}
//运行结果
10
11
12
13
14
15
16
17
18
19
20
结束

变量作用域

审视作用域

变量从声明之时开始就处于作用域当中,换句话说,变量就是从那时开始变为可见的。

只要变量仍然存在于作用域当中,程序就可以访问它。然而在作用域之外访问变量就会报错。

变量作用域的一个好处是我们可以为不同的变量复用相同的名字。因为除极少数小型程序之外,程序的变量几乎不可能不出现重名。

go 的作用域通常会随着大括号 {} 的出现而开启和结束。

在下面的代码清单中,main函数开启了一个作用域,而for循环则开启了一个嵌套作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import (
"fmt"
"math/rand"
)

func pmain(){
var count = 0
//开启新的作用域
for count <10 {
var num = rand.Intn(10)+1
fmt.Println(num)
count++
}
//作用域结束
}

因为对 count 变量的声明用于 main 函数的函数作用域之内,所以它在 main 函数结束之前将一致可见

在循环结束之后访问num变量将会引发编译器报错。

简短声明

简短声明为 var 关键字提供了另一种备选语法。

以下两行代码是完全等效的:

1
2
var count = 10 
count := 10

简短声明不仅仅是简化了声明语句,而且可以在无法使用 var 关键字的地方使用。

如下例代码

未使用简短声明

1
2
3
4
5
var count=0
for count=10;count>0;count--{
fmt.Println(count)
}
fmt.Println(count)

在不使用简短声明的情况下,count 变量的声明必须被放置在循环之外,这意味着在循环解释之后 count 变量将继续存在于作用域。

在for循环中使用简短声明

1
2
3
4
for count := 10; count > 0; count--{
fmt.Println(count)
}
//随着循环结束,count变量将不再处于作用域之内

简短声明还可以在 if 语句中声明新的变量

1
2
3
4
5
6
7
if num := rand.Intn(3);num==0{
fmt.Println("Space Adventures")
}else if num ==1{
fmt.Println("SpaceX")
}else{
fmt.Println("Virgin Galactic")
}//随着if语句结束,变量将不再处于作用域之内

在switch语句中使用

1
2
3
4
5
6
7
8
9
10
switch num := rand.Intn(10);num{
case 0:
fmt.Println("Space Adventures")
case 1:
fmt.Println("SpaceX")
case 2:
fmt.Println("Virgin Galactic")
default:
fmt.Println("Random spaceline #",num)
}

作用域的范围

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
package main
import {
"fmt"
"math/rand"
}
//era变量在整个包都是可用的,相当于全局变量
var era="AD"
func main(){
//era变量和year变量都处于作用域之内
year := 2018

//变量era、year和month都处于作用域之内
switch month := rand.Intn(12)+1;month{
case 2:
//变量era、year、month和day都处于作用域之内
day := rand.Intn(28)+1
fmt.Println(era,year,month,day)

//上面那个day和下面的day变量是全新声明的变量,跟上面生成的同名变量并不相同
case 4,6,9,11:
day := rand.Intn(30)+1
fmt.Println(era,year,month,day)
default:
day := rand.Intn(31)+1
fmt.Println(era,year,month,day)
}//month变量和day变量不再处于作用域之内
}//year变量不再处于作用域之内

因为对 era 变量的声明位于 main 函数之外的包作用域中,所以它对于 main 包中的所有函数都是可见的。

注意:因为包作用域在声明变量时不允许使用简短声明,所以我们无法在这个作用域中使用

后言

参考书籍:Go语言趣学指南
参考课程:Go语言编程快速入门(Golang)