这是我参与「第三届青训营 -后端场」笔记创作活动的的第 1 篇笔记
- Go 程序组织结构
- 安装
- 开发环境
- 基础语法
Go 程序组织结构
- 包声明
golang 以包package为单位来组织代码,每个以.go结尾的 go 语言源代码开头一般都为包声明,包声明指明当前源代码位于哪个包中
包声明是必须的
package main
- 注释
在package main上可以写注释来指明该包的作用,go 中有两种注释形式:单行注释//和多行注释/*[注释的内容]*/
这两种注释都是可以的,不过在 go 中一般推荐使用单行注释,即//
// main 包是 main 函数的所在,可用于生成可执行程序
package main
# src 存放源代码
src/
# 这里的 main 只是文件夹的名称,习惯上包名和文件夹名一致 (当然可以不同)
main/
# 位于包中的文件,需要加上 package [package_name] 来指定包名,
# 这里 main.go 和 ui.go 如果都位于 main 包中,则应该在其中指明
# package main,假如 ui.go 位于别的包中,如 ui 包,那其应该声明
# package ui
# 虽然 go 依靠包名组织源代码,但一个良好项目结构是必不可少的,所以还是
# 保持尽可能包名和文件名相同吧
- main.go
- ui.go
util/
- util.go
- main 函数
golang 中,函数通过 func 来声明。与主流编程语言不同,go 的类型声明是后置的,而且形参的类型声明如果相同,则只用声明一个即可,形参间用逗号隔开
以下是一个完整的 go 源代码:main.go
// main 包可生成可执行文件
package main
// import 将导入 main 包外的包,当导入多个包的时候,可以用括号包裹
// 注意!go 不允许导入包但是不使用!
import (
"fmt"
)
// main 是程序开始的地方
// go 中多用单行注释,多行注释并不常用
// 在各种包、函数、类型之上的注释一般以其对应的名称开头,注释内容和注释符号之间以一个空格分隔
// 这种写法不是强制性的,只是方便使用 godoc 工具生成文档
func main() { // 这个左括号必须写在这里,不能另起一行,这有失灵活,
// 但却在一定程度增强了代码的可读性
// go 中不使用 `;` 作为语句的终结,原因是 go 会自动在特定语句后自动添加分号
var a, b int // 自动初始化为 0
a = 1
b = 2
// go 的 `简短声明` ,可自动执行类型推断,这里 sum 和 add 的返回值类型相同,为 int
sum := add(a, b)
// Println 是包 fmt 中的函数,调用前需要先写包名,所以在命名包的时候尽量做到言简意赅,
// 简写包名要注意
// 在这一点上千万别学 Java !
fmt.Println(sum)
// main 不需要 return 0
}
// add 返回两个数相加的结果,由于 a, b 类型相同,所以前面的 a 可以省略类型声明
// 在返回值这里还有一部分知识点,但对于快速上手有点无关紧要所以我不写了 [摆]
// 像 c 和 python,你需要在函数调用前声明,但 go 无所谓,只要能找到就行
func add(a, b int) int {
return a + b
}
安装
go.dev/
studygolang.com/dl
goproxy.cn/
开发环境
- VScode 免费,丰富的插件
- Goland 30 days 试用
- SpaceVim 免费,上手难度较高
各有各的优点√ (Goland完胜x)
基础语法
- 变量
变量分为值类型和引用类型 --- go 是按值传递
注意!go 不允许声明了变量而不用
var a = "Hello, world!" // go 中字符串是不可变的
var b, c int = 1, 2
var f = true
var double float64 // go 中没有 double,转而是 float32 和 float64
number := 0xFF // 简短声明
- 常量
const NumOfPeople uint = 2000 // 常量一般使用驼峰命名法,uint 是无符号 int
// int 的位数与平台有关,同时,go 中也提供
// int32, uint32, int64, uint64
const str = "Hello, world!"
- 条件
if a > b { // if 的条件只能为 `true` 或 `false`
// ...
}
if a <= b && a >= c {
// ...
} else if c == 0 || c == 1 {
// ...
} else {
// ...
}
if a >= b {
// ...
} else {
// ...
}
// go 中的 switch,相比 C,case 后默认 break
switch flag {
case true:
// ...
case false:
// ...
fallthrough // `fallthrough` 相当于使 break 失效,
// 执行下一个 case/default
default:
// ...
}
- 循环
// 在 Go 语言中只有一个循环关键字 `for`
for i := 0; i < len(str); i++ { // len() 是 go 的内置函数,这里是返回
// 字符串 str 的长度
cnt++ // go 只允许这一种自加方式,同时这种方式为一条语句,即不能这样写
// array[idx++] 这是不允许的; ++idx 也是不存在的
// ...
}
// while 类型
for canRun(x) {
// ...
}
// for...range 返回一个可迭代对象的索引和值
for idx, value := range BigArray {
// ...
}
// 因为 go 不允许声明了变量不用,所以可以用下划线 `_` 来将不需要的值丢弃,例如
for _, value := range array {
// ...
}
- 函数
// 函数名大写表示包外可见
func SayHi() string {
return "Hi~"
}
// 包外无法调用
func add(a int, b int) int {
return a + b
}
// 简写
func add(a, b int) int {
return a + b
}
func OpenFile(path string) ([]byte, err) { // go 中可以有多个返回值
// []byte是切片,引用类型
// ...
}
function0 := func() {
// ...
}
- 数组 / 切片
/* 数组 */
var arr [4]int // 一个容量为 4 的 int 型数组,声明即自动初始化为 0
// bool 类型为 false,引用类型为 nil
var arr0 [4]int{1, 2, 3, 4}
var arr1 [...]int{1, 2, 3, 4, 5, 6, 7} // 不指定容量,编译器自动推断
/* 切片 --- 引用类型 */
var slice0 []int // slice0 默认为 nil
var slice1 = make([]int, 5, 10) // 内置 make 函数,
// slice1 容量(cap) = 10, 大小(len) = 5
// 前 5 位被初始化为 0
slice0 = append(slice0, 10) // 向slice0 添加元素,cap 不足则自动扩容
fruit := []string{"apple", "banana", "peach"} // string 类型的切片
// 其 len == cap 为 3
- 指针
go 提供指针,但是不允许指针的加减
var ptr *int
var a, b = 1, 2
swap(&a, &b) // 通过 & 取地址
func swap(a, b *int) {
*a, *b = *b, *a // 通过 * 解引用,没错!这就是简单的变量交换!
}
- map
m := make(map[string]int) // 可以指定容量,不写为 0,这里的容量
// 指的是通过调用 len 得到的,而不是 cap
// cap(m) 会报错
m["one"] = 1
m["two"] = 2
var m0 = map[string]uint{"one": 1, "two": 2, "three": 3}
// 删除键值对
delete(m["one"])
// 判断键是否存在
// 如果直接访问一个 map 不存在的键不会报错!
if _, ok := m0["four"]; !ok { // 如果不存在
log.Panic("invaild key!") // 调用 log 包中的函数
}
- 结构体
// 结构体名称大写,包外可见,字段同理,与 C++/Java 相比省去了访问修饰符
type User struct {
Name string
Age uint
Addr string
accountPasswd string // 包外不可访问
qqAccount uint64
// ...
}
var user = User{} // 字段将被初始化
var user0 = &User{} // 这里是获取指向空结构的指针
var user1 = User{
Name: "张三",
Age: 32,
Addr: "地球", // 最后这个逗号不能省,除非最后一个字段后是右大括号
}
user2 := &User{Name: "李四", Age: 45}
// 结构体方法,相当于对象的方法,虽然 go 没有显式地提供面向对象,但它也提供了
// 不错的抽象,你可以自己来模拟面向对象编程的模式,同样大小写限定了访问的权限
func (u *User) QQAccount() uint64 { // 这里相当于 getter 不过按照习惯
// go 不使用类似 "GetXXX" 的方式命名
return u.qqAccount // 这里的 `u`,相当于 this 指针,但在 go 中
// 它有另一个名字,叫 receiver,这里是指针接收者
}
// 同样,还有值接收者
func (u User) QQAccount0() uint64 {
return u.qqAccount
}
// 这两者有一定的差异,它们之间的异同要彻底说明白不容易,我们可以按照官方推荐的
// 做法 "如果你搞不明白,用指针接收者"