摘要: 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语言编译器的工作目录。此目录下要求包含三个子目录src
、bin
、pkg
。编译代码时,Go 编译器会在 src 目录查找代码文件,编译后生成的可执行程序输出到 bin 目录,程序包会输出到 pkg 目录。path
: 把编译器的可执行文件所在目录路径追加到 path 变量。
打开 ~/.profile
,加入以下内容然后 source 一下即可。
1 | export GOROOT=/usr/local/go |
编译器安装好和环境变量设置好,就可以测试一下了:
1 | go version |
结果如下:
1 | go version go1.19.4 linux/amd64 |
语法基础
代码结构
打开 test.go
写一段最简单的代码:
1 | package main // 声明此代码文件所属包名,所有 Go 文件都要有 |
命名为 main 的包在编译时会生成可执行文件,可以直接运行。main 包中必须存在一个名为 main 的函数,作为程序的入口。
Go 可以将一个包的代码分散在同一级目录下的多个代码文件中,因此要注意可能出现 main 函数重复定义的问题。
Go 语句
分号表示语句结束,但是分号也可以省略。如果一行要放多个语句,则不能省略分号。
代码块
代码块常出现在函数、结构体、接口、分支、循环等复杂语句中。注意左大括号 {
不能另起一行输入。例如:
1 | if a > 0 { |
编译代码
go 命令 build 参数编译代码,后面没有附加参数则编译当前目录下所有文件,注意这种方式需要有 go.mod
文件:
1 | go build |
也可以只编译特定文件:
1 | go build a.go |
可以指定输出文件:
1 | go build -o myapp a.go |
运算符
操作数
操作数与运算符一起形成表达式。
算术运算符
基础运算:
1 | + |
指数运算没有运算符,标准库 math
里有:
1 | func Pow(x, y float64) float64 |
自增与自减:
1 | n++ |
比较运算符
比较运算符返回结果是布尔值 true 或 false。
1 | == |
- 比较整数,浮点数
1 | var a = 5 |
- 比较字符串对象
1 | var str1 = "yes" |
- 比较指针
如果两个指针引用了同一个对象,则它们相等。不同类型的指针变量不可比较。
1 | var num = 30000 |
- 比较自定义结构体
如果自定义结构体的各个字段的值都想等,则认为两个结构体实例相等。
1 | // 定义新结构体 |
- 比较空接口类型
对于两个空接口类型变量,如果它们包含的值相同,则认为两者相等:
1 | var k1, k2 interface{} |
- 比较接口类型
两个接口类型 T 的变量,它们的值的类型实现了 T 接口,则这两个变量也可以比较。
1 | // pet 接口定义了 GetName 方法 |
pet1 的值为 cat 结构体的实例,pet2 的值为 dog 结构体的实例。
pet1 == pet2 不成立,因为它们的值不是同一个对象;pet2 == pet3 成立,因为它们的值相同(同一个 dog 对象)。
1 | // 用 pet 接口声明两个变量,赋值时分别用 cat 和 dog 结构体实例 |
逻辑运算符
要求参与运算的操作数均为 bool 类型。
1 | && |
位运算符
仅作用于整数值。
1 | & |
成员运算符可以访问各种类型对象的成员,有几个注意点:
(1) 如果对象是指针或接口类型,且其值是空引用 nil
,则访问成员会报错;
(2) 如果该对象不存在指定成员,或对外部隐藏,则访问成员也会报错。
取地址运算符
1 | & |
取地址运算符,可以获取某个对象实例的内存地址。
假定变量 a 的类型是 T,则取 a 地址赋值为指针 p 的代码如下:
1 | var p * T = &a |
取地址运算符常用于函数的按引用参数传递。
复合运算符
1 | += |
运算符优先级
下表中数值越大,优先级越高。
级别 | 运算符 |
---|---|
5 | * , /, %/ <<, >>, &, &^ |
4 | +, -, ^, | |
3 | ==, !=, <, <=, >, >= |
2 | && |
1 | || |
包管理
package 语句
有一些注意点:
- package 语句必须是有效代码的第一行
- 由英文字母,数字,下划线和其它有效 Unicode 字符组成
- 不能数字开头
- 可以中文,因为 Go 默认用 UTF-8 字符编码
- 不能出现
.
- 不能出现
/
,\
,#
,%
,&
,!
等特殊字符
程序包的目录结构
Go 以目录为单位界定程序包。因此同一级目录只允许用一个包名。
可以让包名与目录名相同,使得代码结构更清楚。
导入语句
import 语句可以分着写也可以合着写:
1 | import "demo/test1" |
初始化函数
程序包的代码中可以定义一个 init
函数,当此包被其他代码导入时,init
函数会被调用。
同一个包下的代码文件都可以定义 init 函数,调用顺序取决于代码文件的执行顺序。
模块与 go.mod
如果将项目代码放在 GOPATH
目录之外,源代码位于 src/
子目录,在使用 import 语句时,可以用相对路径。例如:
1 | import ../test |
但是不便于管理,因为一旦项目结构变了,所有代码的 import 都要手动修改。
因此引入模块概念,模块信息通过 go.mod
文件描述,源代码不需要额外修改。
go.mod 文件基本结构
go.mod 由一组简单指令构成,下面是示例:
1 | module example.com/app |
module 指令设置当前模块的名称或路径。模块名要求至少有一个 .
,也就是模块名称要包含一个有效的域名,方便 Go 程序在编译时可以通过代码仓库实时下载模块。例如模块名为 github.com/mymodule
。
go 指令设定所使用的 go 语言版本。
require 指令用于列出当前应用程序中需要引入的模块。格式为 <模块名称> <版本>
。
当 Go 编译器无法从网络卸载 require 指令所指定的模块时,可以用 replace 指令来设置一个本地路径来替换在线路径。格式为 <原模块> => <新的本地路径>
。
go mod 命令
1 | go mod init <模块名> # 创建 go.mod |
本地开发
开发测试阶段,要引用的模块代码一般放在本地,此时 go.mod 文件应该用 replace 将所需模块替换为本地路径。
首先新建 demo_mod 目录,在下面建立文件 test.go,代码如下:
1 | package hellolib |
hellolib 包定义了 SayHello 和 SayMorning 两个函数。
然后执行以下命令,生成 go.mod 文件:
1 | go mod init example.com/demo |
生成的 go.mod 文件内容如下:
1 | module example.com/demo |
创建目录 my_app,在该目录下新建文件 main.go,代码如下:
1 | package main |
由于 hellolib 包的代码文件与 go.mod 在同一级目录下,所以 import 语句只需要指定模块的根路径。
在 my_app 中执行以下命令,创建 go.mod 文件:
1 | go mod init myproject.cn |
生成的 go.mod 文件如下:
1 | module myproject.cn |
添加 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 通过成员名称首字母决定可访问性,只有当成员名称首字母大写时,其他包中的代码才能访问该成员。
变量与常量
变量的初始化
初始化分为声明阶段和赋值阶段:
- 声明阶段:指定变量名与所属类型。
- 赋值阶段:为变量分配一个有效的值。
赋值使用 var
关键字,格式如下:
1 | var <变量名> <变量类型> |
变量声明后,会自动分配一个 0 值,对字符串类型,默认值为 nil
。
可以声明同时赋值:
1 | var b int16 = 980 |
可以省略变量类型,这样可以从赋值内容自动推断变量类型,也可以手动加上类型转换:
1 | var c = 3.1415926 |
可以用 :=
进行更简便的写法:
1 | c := 1.5e7 |
组合赋值
1 | var a, b, c = 10, 20, 30 |
组合赋值常用于调用函数后接收多个返回值:
1 | func test() (string, string, int) { |
匿名变量
1 | a, b, _ = "a", "b", "c" |
常用于调用返回多个值的函数,但是调用方只使用一部分返回值。
常量
常量声明使用 const
关键字。
1 | const MOD int = 1e9+7 |
批量声明
1 | var ( |
变量的作用域
代码访问变量时回从距离当前代码最近的作用域开始,逐渐扩大作用域,直至找到,如果找不到则报错。
变量的默认值
如果声明变量后未进行赋值,则会分配一个默认值。
看一下代码的输出,了解各个类型的默认值。
1 | package main |
结果如下:
1 | int8 0 |