Go语言快速入门-基本特性1

  |  

摘要: Go 语言基本特性,part1,涉及环境配置、语法基础、运算符、包管理、变量与常量

【对数据分析、人工智能、金融科技、风控服务感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:潮汐朝夕
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


环境配置

Go 语言编译器可以从下面网址下载,解压缩后即可使用:

找到下载链接执行下载即可:

1
wget https://golang.google.cn/dl/go1.19.4.linux-amd64.tar.gz

然后解压缩到 /usr/local 下:

1
sudo tar -zxf go1.19.4.linux-amd64.tar.gz -C /usr/local

这样 Go 语言编译器就安装完成了。当然也可以用 Linux 下的 yum 或者 apt 直接安装。

Go 一般需要以下三个环境变量:

  • GOROOT: Go语言编译器、文档、相关工具所在路径,也就是与前面安装路径对应的 /usr/local/go
  • GOPATH: Go语言编译器的工作目录。此目录下要求包含三个子目录 srcbinpkg。编译代码时,Go 编译器会在 src 目录查找代码文件,编译后生成的可执行程序输出到 bin 目录,程序包会输出到 pkg 目录。
  • path: 把编译器的可执行文件所在目录路径追加到 path 变量。

打开 ~/.profile,加入以下内容然后 source 一下即可。

1
2
3
export GOROOT=/usr/local/go
export GOPATH=${HOME}/go
export PATH=${PATH}:${GOROOT}/bin:${GOPATH}:/bin

编译器安装好和环境变量设置好,就可以测试一下了:

1
go version

结果如下:

1
go version go1.19.4 linux/amd64

语法基础

代码结构

打开 test.go 写一段最简单的代码:

1
2
3
4
5
6
7
package main // 声明此代码文件所属包名,所有 Go 文件都要有

import "fmt" // 告诉编译器该文件要用到哪个包里的 API

func main() { // func 关键字定义函数
fmt.Print("Hello World") // 调用 fmt 包中的 Print 函数
}

命名为 main 的包在编译时会生成可执行文件,可以直接运行。main 包中必须存在一个名为 main 的函数,作为程序的入口。

Go 可以将一个包的代码分散在同一级目录下的多个代码文件中,因此要注意可能出现 main 函数重复定义的问题。

Go 语句

分号表示语句结束,但是分号也可以省略。如果一行要放多个语句,则不能省略分号。

代码块

代码块常出现在函数、结构体、接口、分支、循环等复杂语句中。注意左大括号 { 不能另起一行输入。例如:

1
2
3
4
5
if a > 0 {
...
} else {
...
}

编译代码

go 命令 build 参数编译代码,后面没有附加参数则编译当前目录下所有文件,注意这种方式需要有 go.mod 文件:

1
go build

也可以只编译特定文件:

1
go build a.go

可以指定输出文件:

1
go build -o myapp a.go

运算符

操作数

操作数与运算符一起形成表达式。

算术运算符

基础运算:

1
2
3
4
5
+
-
*
/ 地板除
% 余数可能为负数,注意相应处理

指数运算没有运算符,标准库 math 里有:

1
2
func Pow(x, y float64) float64
func Pow10(n float64) float64

自增与自减:

1
2
n++
n--

比较运算符

比较运算符返回结果是布尔值 true 或 false。

1
2
3
4
5
6
==
!=
>
<
>=
<=
  • 比较整数,浮点数
1
2
3
4
5
6
7
8
var a = 5
var b = 2
var res1 = a > b
fmt.Printf("%d 大于 %d 吗? %t \n", a, b, res1)
var c = 5.0
var d = 2.0
var res2 = c > d
fmt.Printf("%.3f 大于 %.3f 吗? %t \n", c, d, res2)
  • 比较字符串对象
1
2
3
4
var str1 = "yes"
var str2 = "Yes"
var res3 = str1 == str2
fmt.Printf("%s 等于 %s 吗? %t \n", str1, str2, res3)
  • 比较指针

如果两个指针引用了同一个对象,则它们相等。不同类型的指针变量不可比较。

1
2
3
4
5
var num = 30000
var p1 * int = &num
var p2 * int = &num
var res4 = p1 == p2
fmt.Print(res4)
  • 比较自定义结构体

如果自定义结构体的各个字段的值都想等,则认为两个结构体实例相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义新结构体
type MyStruct struct {
fd01 int8
fd02 float64
fd03 int16
}

// 声明变量并赋值
var obj1, obj2 MyStruct
obj1 = MyStruct {
fd01: 2,
fd02: 0.00075,
fd03: 809,
}
obj2 = MyStruct {
fd01: 2,
fd02: 0.00075,
fd03: 809,
}
var res5 = obj1 == obj2
fmt.Printf("obj1 与 obj2 是否相等? %t\n", res5)
  • 比较空接口类型

对于两个空接口类型变量,如果它们包含的值相同,则认为两者相等:

1
2
3
4
5
var k1, k2 interface{}
k1 = 50
k2 = 50
var res6 = k1 == k2
fmt.Printf("k1 与 k2 是否相等? %t \n", res6)
  • 比较接口类型

两个接口类型 T 的变量,它们的值的类型实现了 T 接口,则这两个变量也可以比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// pet 接口定义了 GetName 方法
type pet interface {
GetName() string
}

// 结构体 cat 和 dog 都定义了 GetName 方法
// 因此结构体 cat 和 dog 都认为实现了 get 接口
type cat struct {
name string
}

func (c cat) GetName() string {
return c.name
}

type dog struct {
name string
}

func (d dog) GetName() string {
return d.name
}

pet1 的值为 cat 结构体的实例,pet2 的值为 dog 结构体的实例。

pet1 == pet2 不成立,因为它们的值不是同一个对象;pet2 == pet3 成立,因为它们的值相同(同一个 dog 对象)。

1
2
3
4
5
6
7
8
9
10
11
// 用 pet 接口声明两个变量,赋值时分别用 cat 和 dog 结构体实例
var (
pet1 pet = cat{name: "Jack"}
pet2 pet = dog{name: "Tim"}
pet3 pet = pet2
)

var res7 = pet1 == pet2
fmt.Printf("pet1与pet2是否相等? %t\n", res7)
var res8 = pet2 == pet3
fmt.Printf("pet2与pet3是否相等? %t\n", res8)

逻辑运算符

要求参与运算的操作数均为 bool 类型。

1
2
3
&&
||
!

位运算符

仅作用于整数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
&
|
^ 按位异或
<<
>>
&^ 清除标志位,将特定位变为 0
^ 按位取反
```

## 成员运算符

```plain
.

成员运算符可以访问各种类型对象的成员,有几个注意点:

(1) 如果对象是指针或接口类型,且其值是空引用 nil,则访问成员会报错;

(2) 如果该对象不存在指定成员,或对外部隐藏,则访问成员也会报错。

取地址运算符

1
&

取地址运算符,可以获取某个对象实例的内存地址。

假定变量 a 的类型是 T,则取 a 地址赋值为指针 p 的代码如下:

1
var p * T = &a

取地址运算符常用于函数的按引用参数传递。

复合运算符

1
2
3
4
5
6
7
8
9
10
11
+=
-=
*=
/=
%=
&=
|=
^=
<<=
>>=
&^=

运算符优先级

下表中数值越大,优先级越高。

级别 运算符
5 * , /, %/ <<, >>, &, &^
4 +, -, ^, |
3 ==, !=, <, <=, >, >=
2 &&
1 ||

包管理

package 语句

有一些注意点:

  • package 语句必须是有效代码的第一行
  • 由英文字母,数字,下划线和其它有效 Unicode 字符组成
  • 不能数字开头
  • 可以中文,因为 Go 默认用 UTF-8 字符编码
  • 不能出现 .
  • 不能出现 /, \, #, %, &, ! 等特殊字符

程序包的目录结构

Go 以目录为单位界定程序包。因此同一级目录只允许用一个包名。

可以让包名与目录名相同,使得代码结构更清楚。

导入语句

import 语句可以分着写也可以合着写:

1
2
3
4
5
6
7
import "demo/test1"
import "demo/test2"

import (
"demo/test1"
"demo/test2"
)

初始化函数

程序包的代码中可以定义一个 init 函数,当此包被其他代码导入时,init 函数会被调用。

同一个包下的代码文件都可以定义 init 函数,调用顺序取决于代码文件的执行顺序。

模块与 go.mod

如果将项目代码放在 GOPATH 目录之外,源代码位于 src/ 子目录,在使用 import 语句时,可以用相对路径。例如:

1
2
3
import ../test
import ../../test
import .test

但是不便于管理,因为一旦项目结构变了,所有代码的 import 都要手动修改。

因此引入模块概念,模块信息通过 go.mod 文件描述,源代码不需要额外修改。

go.mod 文件基本结构

go.mod 由一组简单指令构成,下面是示例:

1
2
3
4
5
6
7
8
9
10
11
12
module example.com/app

go 1.1.13

require (
abc.com v0.0.0
kitty.net/myprj v0.0.0
)

replace abc.com => ./myabc

replace kitty.net/myprj => ./project

module 指令设置当前模块的名称或路径。模块名要求至少有一个 .,也就是模块名称要包含一个有效的域名,方便 Go 程序在编译时可以通过代码仓库实时下载模块。例如模块名为 github.com/mymodule

go 指令设定所使用的 go 语言版本。

require 指令用于列出当前应用程序中需要引入的模块。格式为 <模块名称> <版本>

当 Go 编译器无法从网络卸载 require 指令所指定的模块时,可以用 replace 指令来设置一个本地路径来替换在线路径。格式为 <原模块> => <新的本地路径>

go mod 命令

1
2
3
4
5
6
7
8
go mod init <模块名> # 创建 go.mod
go mod edit -module=demo.cn/db # 将模块名改为 demo.cn/db
go mod edit -go 1.13 # 使用 v1.13 版本的 Go
go mod edit -require=<模块名>@<版本>
go mod edit -require=<模块名1>@<版本1> -require=<模块名2>@<版本2>
go mod edit -droprequire=<模块名>@<版本>
go mod edit -replace=<原模块名>@版本=<替代模块>@<版本>
go mod edit -dropreplace=<原模块名>@版本=<替代模块>@<版本>

本地开发

开发测试阶段,要引用的模块代码一般放在本地,此时 go.mod 文件应该用 replace 将所需模块替换为本地路径。

首先新建 demo_mod 目录,在下面建立文件 test.go,代码如下:

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

// 导入标准库模块
import "fmt"

func SayHello(who string) {
fmt.Printf("你好, %s\n", who)
}

func SayMoning(who string) {
fmt.Printf("早上好, %s\n", who)
}

hellolib 包定义了 SayHello 和 SayMorning 两个函数。

然后执行以下命令,生成 go.mod 文件:

1
go mod init example.com/demo

生成的 go.mod 文件内容如下:

1
2
3
module example.com/demo

go 1.19

创建目录 my_app,在该目录下新建文件 main.go,代码如下:

1
2
3
4
5
6
7
8
9
package main

// 导入 hellolib 包所在路径
import "example.com/demo"

func main() {
hellolib.SayHello("小明")
hellolib.SayMorning("小雷")
}

由于 hellolib 包的代码文件与 go.mod 在同一级目录下,所以 import 语句只需要指定模块的根路径。

在 my_app 中执行以下命令,创建 go.mod 文件:

1
go mod init myproject.cn

生成的 go.mod 文件如下:

1
2
3
module myproject.cn

go 1.19

添加 require 命令,引用 example.com/demo 模块:

1
go mod edit -require=example.com/demo@v0.0.0

go.mod 文件中生成的 require 指令如下:

1
require example.com/demo v0.0.0

由于源代码都在本地,因此以上 require 指令在编译时不能找到 example.com/demo 模块,需要增加 replace:

1
go mod edit -replace=example.com/demo=../demo_mod

生成的 replace 指令如下:

1
replace example.com/demo => ../demo_mod

运行 main 模块:

1
go run main.go

成员的可访问性

Go 通过成员名称首字母决定可访问性,只有当成员名称首字母大写时,其他包中的代码才能访问该成员。


变量与常量

变量的初始化

初始化分为声明阶段和赋值阶段:

  1. 声明阶段:指定变量名与所属类型。
  2. 赋值阶段:为变量分配一个有效的值。

赋值使用 var 关键字,格式如下:

1
var <变量名> <变量类型>

变量声明后,会自动分配一个 0 值,对字符串类型,默认值为 nil

可以声明同时赋值:

1
var b int16 = 980

可以省略变量类型,这样可以从赋值内容自动推断变量类型,也可以手动加上类型转换:

1
2
var c = 3.1415926
var c = (float32)3.1415926

可以用 := 进行更简便的写法:

1
c := 1.5e7

组合赋值

1
2
var a, b, c = 10, 20, 30
a, b, c := 10, 20, 30

组合赋值常用于调用函数后接收多个返回值:

1
2
3
4
5
func test() (string, string, int) {
return "abc", "xyz", 10000
}

r1, r2, r3 := test()

匿名变量

1
a, b, _ = "a", "b", "c"

常用于调用返回多个值的函数,但是调用方只使用一部分返回值。

常量

常量声明使用 const 关键字。

1
2
const MOD int = 1e9+7
const MOD = 1e9+7

批量声明

1
2
3
4
5
6
7
8
9
10
var (
a = 0.01
b = 0.02
c int16 = 120
)

const (
MOD = 1e9 + 7
UP = 0.01
)

变量的作用域

代码访问变量时回从距离当前代码最近的作用域开始,逐渐扩大作用域,直至找到,如果找不到则报错。

变量的默认值

如果声明变量后未进行赋值,则会分配一个默认值。

看一下代码的输出,了解各个类型的默认值。

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

import "fmt"

func main() {
var v1 int8
fmt.Printf("%-15[1]T%[1]v\n", v1)

var v2 int16
fmt.Printf("%-15[1]T%[1]v\n", v2)

var v3 int32
fmt.Printf("%-15[1]T%[1]v\n", v3)

var v4 int64
fmt.Printf("%-15[1]T%[1]v\n", v4)

var v5 float32
fmt.Printf("%-15[1]T%[1]v\n", v5)

var v6 float64
fmt.Printf("%-15[1]T%[1]v\n", v6)

var v7 string
fmt.Printf("%-15[1]T%[1]v\n", v7)

var v8 rune
fmt.Printf("%-15[1]T%[1]v\n", v8)

type MyStruct struct {
m float32
n int16
}
var v9 MyStruct
fmt.Printf("%-12s%+v\n", "结构体", v9)

type MyInterface interface {
MyMethod()
}
var v10 MyInterface
fmt.Printf("%-13s%v\n", "接口", v10)

var v11 * int
fmt.Printf("%-13s%v\n", "指针", v11)
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
int8           0
int16 0
int32 0
int64 0
float32 0
float64 0
string
int32 0
结构体 {m:0 n:0}
接口 <nil>
指针 <nil>

Share