跳到主要内容

Golang

1. 介绍

1.1 什么是Go语言?

  • Google开源的语言
  • 编译型语言
  • 21世纪的C语言

1.2 Go语言特点

  • 语法简介
  • 开发效率高
  • 执行性能好

2. Go开发环境安装

官方下载地址:

安装后,使用下面的命令查看 golang 版本。显示版本号,说明安装成功。

go version

3. 认识Go程序

3.1 main函数

和其它后端语言一样,main函数是程序执行的入口。

package main

// 导入fmt包
import "fmt"

// main函数,程序入口
func main() {
fmt.Println("Hello World")
}

3.2 编译

go build

在终端使用go build命令将.go文件编译成当前平台的可执行文件。

例如在 MacOS 中,终端进入.go文件所在目录,执行go build命令,编译器会将 .go 源文件编译成 MacOS 上的可执行文件。./filename执行可执行文件,控制台打印Hello World

go build -o 文件名:自定义编译的文件名:

跨平台编译(交叉编译)

默认go build编译的是当前操作系统的可执行文件,如果要编译其他平台的可执行文件需要指定目标操作系统的平台和处理器架构:

Windows下编译Linux平台的64位可执行程序:

SET CGO_ENABLED=0 //禁用CGO
SET GOOS=linux // 目标平台是linux
SET GOARCH=amd64 // 目标处理器架构是amd64
go build

然后再执行go build命令,得到的就是Linux平台的可执行文件。

Mac下编译Linux和Windows平台的64位可执行程序:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

Linux下编译Mac和Windows平台64位可执行程序:

CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

Windows下编译Mac平台64位可执行程序:

SET CGO_ENABLED=0 
SET GOOS=darwin
SET GOARCH=amd64
go build

4. 标识符和关键字

4.1 标识符

在编程语言中,标识符就是程序员可以定义的具有特殊意义的词,如变量名,常量名,函数名等等。

Go语言中的标识符由字母、数字、下划线(_)组成,并且只能以字母或下划线开头。如:abc_abc_123等。

4.2 关键字

关键字是指编程语言中预先定义好的具有特殊含义的词,不能把关键字和保留字用作标识符。

Go语言中共有25个关键字,按作用划分3类:包管理、程序实体声明与定义、程序流程控制。

包管理(2个):
import package

程序实体声明与定义(8个):
chan const func interface map struct type var

程序流程控制(15个):
break case continue default defer else fallthrough
for go goto if range return select switch

Go语言中还有37个保留字。(保留字是在语言中还未使用的,但是不能作为标识符使用)

Constants:
true false iota nil

Types:
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error

Functions:
make len cap new append copy close delete
complex real imag panic recover

5. 变量

5.1 变量的来历

程序在运行时,数据都是存储在内存中的。我们想要在代码中操作某个数据时需要到内存中找到这个数据,但是如果我们直接在代码中通过内存地址去操作数据是非常糟糕的,这使得代码的可读性变得非常差并且容易出错。所以我们就利用变量将数据的内存地址保存起来,以后我们直接通过这个变量就能找到内存上对应的数据了。

5.2 变量类型

变量 (Variable) 的功能是存储数据。不同变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的类型有:整型、浮点型、布尔型等。

Go语言中每一个变量都有自己的类型,并且变量必须经过声明才能使用。

5.3 变量声明

Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。如果是局部变量,声明后必须使用,否则会编译时报错。

5.4 标准声明

Go语言的变量声明格式为:

var 变量名 变量类型

示例代码

package main

import "fmt"

// 声明变量
var name string
var age int
var isOk bool

// 批量声明
var (
mobile string
address string
remark string
)

func main() {
name = "张三"
age = 25
isOk = true

fmt.Printf("name is %s", name)
}

5.5 变量的初始化

Go语言在声明变量的时候,会自动在指定的内存区域进行初始化操作。每个变量会根据其数据类型初始化默认值,如整型和浮点型的默认值(初始值)是0,string的默认值是空字符串,bool的默认值是false,切片、函数、指针变量的默认值是nil

我们也可以在声明变量的同时为其初始化值,变量声明并初始化的标准格式如下:

var 变量名 变量类型 = 表达式

举个例子:

var name string = "张三"
var age int = 25

或者一次声明并初始化多个变量:

var name, age = "张三", 25

5.6 类型推导

Go语言中,变量可以根据其在声明初始化时所赋值的数据类型推断出变量具体的数据类型。

举个例子:

var name = "张三" // 初始化变量时将字符串赋值给name,则变量name的数据类型为string
提示

这种方式只适用于在变量声明的同时并初始化时,并且Go是强类型语言。

5.7 短变量声明

在函数内部,可以使用更简略的:=方式声明并初始化变量。

name := "张三" // 只能在函数内部使用(也就是局部变量)

5.8 匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。匿名变量用一个下划线_表示,例如:

func foo() (int, string) {
return 10, "张三"
}

func main() {
age, _ := foo()
_, name := foo()

fmt.Println("age:", age)
fmt.Println("name:", name)
}

匿名变量不占用命名空间,不会被分配内存,所以匿名变量之间不存在重复声明。(在Lua等编程语言里,匿名变量也被叫做哑元变量)

注意事项:

  1. 函数外的每个语句都必须以关键字开始(varconstfunc等);
  2. :=不能再函数外部使用;
  3. _多用于占位,表示忽略值。

6. 常量

6.1 声明常量

相对于变量,常量是恒定不变的量(值),多用于定义程序运行期间不会改变的值。常量的声明和变量的声明非常类似,只是把关键字var换成了const,常量的定义的时候必须赋值。

const pi = 3.1415
const e = 2.7182

声明了pie这两个常量之后,在整个程序运行期间他们的值都不能再发生改变了。

多个常量也可以一起声明:

const (
OK = 200
NOTFOUND = 404
)

// 批量声明常量时,如果某一行声明没有赋值,默认同上一行
const (
n1 = 100
n2
n3
)

6.2 iota

iota是Go语言的常量计数器,只能在常量的表达式中使用。

iotaconst关键字出现时将被重置为0,const中每新增一行常量声明,iota计数+1。iota可以理解为const语句块中的行索引。使用iota能简化定义,在定义枚举时很有用。

// iota
const (
a1 = iota // 0
a2 // 1
a3 // 2
a4 // 3
)

7. 基本数据类型

7.1 整型

整型分为两大类:按长度分为int8int16int16int64,对应的无符号整型:uint8uint16uint32uint64。其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。

类型描述
uint8无符号8位整型(0到255)
uint16无符号16位整型(0到65535)
uint32无符号32位整型(0到4294967295)
uint64无符号64位整型(0到18446744073709551615)
int8有符号8位整型(-128到127)
int16有符号16位整型(-32768到32767)
int32有符号32位整型(-2147483648到2147483647)
int64有符号64位整型(-9223372036854775808到9223372036854775807)

7.2 特殊整型

类型描述
uint32位操作系统上就是uint32,64位操作系统上就是uint64
int32位操作系统上就是int32,64位操作系统上就是int64
uintptr无符号整形,用于存放指针

注意:在使用intuint类型时,不能假定它是32位或是64位整型,而是考虑intuint可能在不同平台上的差异。

注意事项:获取对象的长度的内建len()函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或map的元素数量等都可以用int来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用intuint

7.3 八进制&十六进制

Go语言中无法直接定义二进制数,关于八进制和十六进制数的示例如下:

package main

import "fmt"

func main() {
var i1 = 0145
fmt.Printf("%d\n", i1) // %d 输出十进制
fmt.Printf("%b\n", i1) // %b 输出二进制
fmt.Printf("%o\n", i1) // %o 输出八进制
fmt.Printf("%x\n", i1) // %x 输出十六进制数
fmt.Printf("%T\n", i1) // 查看变量类型

// 八进制
var i2 = 077
fmt.Printf("%d\n", i2)

// 十六进制
var i3 = 0xff
fmt.Printf("%d\n", i3)

// 声明int8类型变量
var i4 int8 = 9
fmt.Printf("%T\n", i4)

i5 := int8(9)
fmt.Printf("%T\n", i5)

}

7.4 printf 格式化输出

通用占位符描述
%+v变量值,如 张三
%#v带有类型格式的变量值,如 "张三"
%T变量的数据类型
布尔值描述
%t布尔值
整数值描述
%b二进制表示
%c相应Unicode码点所表示的字符
%d十进制表示
%o八进制表示
%q单引号围绕的字符字面值,由Go语法安全地转义
%x十六进制表示,字母形式为小写 a-f
%X十六进制表示,字母形式为大写 A-F
%UUnicode格式:U+1234,等同于 "U+%04X"
浮点数及复数描述
%b无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat中的 'b' 转换格式一致。例如 -123456p-78
%e科学计数法,例如 -1234.456e+78
%E科学计数法,例如 -1234.456E+78
%f有小数点而无指数,例如 123.456
%g根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出
%G根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出
字符串和bytes的slice表示描述
%s字符串或切片的无解译字节
%q双引号围绕的字符串,由Go语法安全地转义
%x十六进制,小写字母,每字节两个字符
%X十六进制,大写字母,每字节两个字符
指针描述
%p十六进制表示,前缀 0x

这里没有 'u' 标记。若整数为无符号类型,他们就会被打印成无符号的。类似地,这里也不需要指定操作数的大小(int8,int64)。

提示

对于%v来说默认的格式是:

  • bool: %t
  • int, int8 etc.: %d
  • uint, uint8 etc.: %d, %x if printed with %#v
  • float32, complex64, etc: %g
  • string: %s
  • chan: %p
  • pointer: %p

由此可以看出,默认的输出格式可以使用%v进行指定,除非输出其他与默认不同的格式,否则都可以使用%v进行替代(但是不推荐使用)

之前整理的:

占位符描述
%a读入一个浮点值(仅C99有效)
%c用来输出单个字符
%d用来输出有符号的十进制整数(包括int/char类型)
%i读入十进制,八进制,十六进制整数
%o用来输出无符号的八进制整数
%x用来输出无符号的十六进制整数
%s用来输出一个字符串
%p读入指针
%u用来输出无符号的十进制整数(包括int/char类型)
%n至此已读入值的等价字符数
%[]扫描字符集合
%%读%符号
%f用来输出小数形式的十进制浮点数(输入时小数形式和指数形式都可以识别)
%e用来输出指数形式的十进制浮点数(输入时小数形式和指数形式都可以识别)
%g用来输出指数形式和小数形式两者中较短的十进制浮点数(输入时小数形式和指数形式都可以识别)

7.5 浮点型

Go语言支持两种浮点数:float32float64,这两种浮点型数据格式遵循IEEE 754标准。float32的浮点数最大范围约3.4e38,可以使用math.MaxFloat32查看。float64的浮点数的最大范围约为1.8e308,可以使用math.MaxFloat64查看。

打印浮点数时,可以使用fmt包配合动词%f,代码如下:

package main

import (
"fmt"
"math"
)

func main() {

// 最大的float32数
fmt.Printf("%f\n", math.MaxFloat32)

// 最大的float64数
fmt.Printf("%f\n", math.MaxFloat64)

fmt.Printf("%f\n", math.Pi)
fmt.Printf("%.2f\n", math.Pi) // %.2f 小数点保留后两位
}

7.6 复数

complex64complex128

package main

import "fmt"

func main() {
var c1 complex64
c1 = 1 + 2i

var c2 complex128
c2 = 2 + 3i

fmt.Println(c1)
fmt.Println(c2)
}

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

7.7 布尔值

Go语言中以bool类型进行声明布尔类型数据,布尔类型数据只有truefalse两个值。

package main

import "fmt"

func main() {
b1 := true
var b2 bool

fmt.Printf("%T b1=%v\n", b1, b1) // %v 输出变量的值
fmt.Printf("%T b2=%v\n", b2, b2)

var n = 100
var s = "Hello Go"

// 常用的占位符
fmt.Printf("%T \n", n) // 输出变量的数据类型
fmt.Printf("%v %v \n", n, s) // 输出变量的值(任意类型数据)
fmt.Printf("%b \n", n) // 输出变量的二进制数
fmt.Printf("%d \n", n) // 输出变量的十进制数
fmt.Printf("%o \n", n) // 输出变量的八进制数
fmt.Printf("%x \n", n) // 输出变量的十六进制数
fmt.Printf("%s \n", s) // 输出字符串变量
}

注意:

  1. 布尔类型变量的默认值是false
  2. Go语言中不允许将整型强制转换成布尔类型数据;
  3. 布尔类型数据无法参与数值运算,也无法与其它类型进行转换。

7.8 字符串

Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他的原生数据类型(int、bool、float32、float64等)一样。Go语言中的字符串的内部实现使用UTF-8编码。字符串的值为双引号""中的内容,可以在Go语言的源码中直接添加非ASCII码字符,例如:

s1 := "hello"
s2 := "你好"
提示

注意:

  • Go语言中字符串使用双引号包裹;
  • Go语言中单引号包裹的是字符。

7.9 字符串转义符

Go语言的字符串常见转义符包括回车、换行、单双引号、制表符等。如下表所示:

转义符含义
\r回车符(返回行首)
\n换行符(直接跳到下一行的同列位置)
\t制表符
\'单引号
\"双引号
\反斜杠

反引号``中的内容会原样输出:

package main

import "fmt"

func main() {
s1 := `人生苦短
远离Python
`
fmt.Println(s1)
}

7.10 字符串的常用操作

方法介绍
len()字符串长度
+fmt.Sprintf拼接字符串
strings.Split分割字符串
strings.contains判断是否包含
strings.HasPrefixstrings.HasSuffix前缀/后缀判断
strings.Index()strings.LastIndex()子串出现的位置
strings.Join(a[]string,sep string)join操作

7.11 字符串拼接

package main

import "fmt"

func main() {

pre := "Hello"
suf := "Go"

fmt.Println(len(pre)) // 5
fmt.Println(len(suf)) // 2
// + 拼接字符串
s1 := pre +" "+ suf
// fmt.Sprintf 最终返回一个字符串
s2 := fmt.Sprintf("%s %s", pre, suf)

fmt.Println(s1) // Hello Go
fmt.Println(s2) // Hello Go
}

7.12 byte和rune

提示

Go语言定义了两种字符类型:

  • btye类型,代表了一个ASCII码字符;
  • rune类型,代表了一个UTF-8字符。

rune类型,官方的定义是:rune is an alias for int32 and is equivalent to int32 in all ways。具体看如下示例:

打印输出str的长度:

str := "你好 Go~"
fmt.Println(len(str)) // 10

字符串str的预期长度应该是6,为什么输出的是10?

这是因为Go语言中string类型底层是通过byte数组实现的。中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而Go默认编码正好是utf-8。

Go语言中为了处理非ASCII码类型的字符(中文或其他语系字符)定义了rune类型。还有一个byte数据类型与rune相似,它是用来处理ASCII码类型字符的:于:

  • byte 等同于int8,常用来处理ASCII字符
  • rune 等同于int32,常用来处理unicodeutf-8字符
str := "你好 Go~"

fmt.Println(len([]rune(str))) // 6
fmt.Println(utf8.RuneCountInString(str)) // 6

7.13 字符串修改

package main

import "fmt"

func main() {

str := "你好啊"

// 字符串修改
s1 := []rune(str) // 把字符串强制转换成一个rune切片
fmt.Println(s1) // [20320 22909 21834]
fmt.Printf("%c\n", s1) // [你 好 啊]

s1[2] = '吗' // 把s1的第三个字符改成'吗'
fmt.Println(string(s1)) // 你 好 吗

}

7.14 类型转换

Go语言中只有强制类型转换(显式转换),没有隐式类型转换。该语法只能在两个类型支持互相转换的时候使用。bool类型不能和其他任何类型进行转换。

强制类型转换的基本语法如下:

T(表达式)

其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等。

8. 流程控制

Go语言中最常用的流程控制有iffor,而switchgoto主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。

8.1 if else(条件分支)

Go语言中if条件判断的格式如下:

if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else {
分支3
}

代码示例

package main

func main() {
/**
老婆给当程序员的老公打电话:“下班顺路买一斤包子带回来,如果看到卖西瓜的,买一个。
”当晚,程序员老公手捧一个包子进了家门……老婆怒道:“你怎么就买了一个包子?”老公答曰:“因为看到了卖西瓜的。”
*/

hasWatermelon := true

if hasWatermelon {
println("买一个包子")
} else {
println("买一斤包子")
}
}

if条件判断还要一个特殊的写法,可以在if表达式之前添加一个执行语句,再根据俄变量值进行判断:

代码示例

if hasWatermelon := true; hasWatermelon {
println("买一个包子")
} else {
println("买一斤包子")
}

8.2 switch case(条件分支)

使用switch语句可以方便地对大量的值进行条件判断。

package main

import "fmt"

func main() {

score := 66

switch {
case score >= 85:
fmt.Println("优秀")
case score > 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
}

8.3 for(循环结构)

Go语言中的所有循环类型均可使用for关键字来完成。

for循环的基本格式如下:

for 初始语句; 条件表达式; 结束语句 {
循环体
}

初始语句用来声明变量,条件表达式用来判断是否进入循环执行循环体,循环体执行结束后执行结束语句。

package main

import "fmt"

func main() {

// 基本格式
sum := 0
for i := 0; i <= 100; i++ {
sum += i
}

fmt.Printf("0到100的累加和:%v\n", sum) // 5050

}

for循环的初始语句和结束语句都可以省略,但是分号不能省略。

8.4 无限循环

for {
循环体
}

for循环可以通过breakgotoretuenpanic语句强制退出循环。

8.5 for range(键值循环)

Go语言中可以使用for range遍历数组、切片、字符串、map及通道(channel)。通过for range遍历的返回值有以下规律:

  1. 数组、切片、字符串返回索引和值;
  2. map返回键和值;
  3. 通道(channel)只返回通道内的值。
package main

import "fmt"

func main() {

s := "你好 Go~"

for i, v := range s {
fmt.Printf("索引:%d,元素:%c\n", i, v)
}

}

练习:九九乘法表

package main

import "fmt"

func main() {

for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d ", j, i, i * j)
}
fmt.Println()
}

}

运行结果:

1 * 1 = 1    
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81

8.6 goto(跳转到指定标签)

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。

package main

import "fmt"

func main() {

for i := 0; i < 10; i++ {

if i == 3 {
goto breakTag // 跳转到标签
}

fmt.Println(i)
}

breakTag: // 设置标签
fmt.Println("结束for循环")
}

注意:现在的代码中不推荐使用。

9. 运算符

9.1 算术运算符

运算符描述
+相加
-相减
*相乘
/相除
%取余

注意:自加++和自减--在Go语言中是单独的语句,并不是运算符。

9.2 关系运算符

运算符描述
==相等
!=不等
>大于
<小于
>=大于等于
<=小于等于

注意:相同类型的数据才能比较

9.3 逻辑运算符

运算符描述
&&
||

9.4 位运算符

位运算符对整数在内存中的二进制位进行操作。

运算符描述
&参与运算的两数各对应的二进位相与(两位均为1才为1)
|参与运算的两数各对应的二进位相或(两位有一个为1才为1)
^参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1
<<左移n位就是乘以2的n次方(“a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0)
>>右移n位就是除以2的n次方(“a>>b”是把a的各二进位全部右移b位)

9.5 赋值运算符

运算符描述

10. 函数

10.1 main函数

先来认识下main函数。

package main

import "fmt"

func main(){
fmt.Printf("Hello Go.")
}

很多程序都有main函数,它是程序执行的入口。

通过上面的示例代码,来认识下 go 语言代码。

  • package关键字,表示该 go 文件的包名,需要注意的是,main函数所在的包是main包。

  • import关键字,导包。

    • 如果同时导入多个包,可以使用这种写法。
      import (
    "fmt"
    "math"
    )
  • func关键字,用来声明函数的。这里需要注意,函数体的左花括号必须写在函数声明一行,不能换到下一行。

10.2 定义函数

函数的格式

func 函数名(参数1 参数类型, 参数2 参数类型) 返回值类型 {
// 方法体...
}

代码示例

func plus(a int32, b int32) int32 {
return a + b
}

func main() {
res := plus(3, 5)
fmt.Printf("结果:%d", res) // 8
}

10.3 函数的返回值

在 10.2 中已经介绍了函数的定义。在 Go 语言中,函数支持返回多个值,这点和其它语言略有不同。

// 多个返回值需要使用小括号括起来,返回值类型使用逗号分隔
func plus(a int32, b int32) (int32, int32) {
// return 多个值
return a + b, a * b
}

Go 语言中,函数的返回值可以是有变量名的(非匿名)

// 返回值可以是非匿名
func plus(a int32, b int32) (r1 int32, r2 int32) {
// 为返回值赋值
r1 = a + b
r2 = a * b

return
}

函数的返回值列表中多个变量是想同类型的,数据类型可以简写。

// 返回值可以是非匿名
func plus(a int32, b int32) (r1, r2 int32) {
// 为返回值赋值
r1 = a + b
r2 = a * b

return
}