Golang 语法
Go 的数据类型:
基本类型: int, float32, float64, boolean, string, rune
聚合类型: array, struct
引用类型: pointer, slice, map, function, channel
接口类型
1 分号
不同于C等其他语言,写代码时语句不必以分号 ;
作为终止符,目的是:
- 减少输入,增加可读性
- 语句没有加分号时,不再标识为错误
- 避免开发者使用分号写出C那样的语言允许的长行代码
仅在必要时才写分号,但是 Go 的语法仍然要求有分号,只是 Go 的词法分析器会自动完成插入分号。
2 注释
一般使用 //
注释 Go 代码
/* ... */
习惯性用于调试
The second is to use opening tags (/*
) and closing tags (*/
). For documenting code, it is considered idiomatic to always use //
syntax. You would only use the /* ... */
syntax for debugging
package main
import "fmt"
func main() {
/*
多行注释
This is a multi line comment.
You can type within this section
*/
fmt.Println("Go!")
// 单行注释
// single line comment
fmt.Println("Lang!")
}
3 变量与函数
使用关键字 var
声明变量,变量名称后是变量的类型。
var myVar string
声明变量同时初始化:
var s = "seven" // go 会自动推断 s 的类型
Go 编译器禁止变量只声明不使用。
但是函数之外的,处于 package level 的变量则无此要求,可以只声明不使用。
package main
import "fmt"
func main() {
var whatToSay string
whatToSay = "Hi world"
fmt.Println(whatToSay)
var i int;
i = 7
fmt.Println("i is set to", i)
whatWasSaid := saySomething()
// shorthand, function return type
// decides the type of whatWasSaid
fmt.Println(whatWasSaid);
}
// 自定义函数
func saySomething() string {
return "something"
}
Go 函数可以返回多个值。
func saySomething() (string, string) {
return "something", "else"
}
调用:
whatWasSaid, anotherWhatWasSaid := saySomething();
Go 没有面向对象的概念, 下面的函数,whatever
函数,首字母小写,意思是说,此函数只在当前的包 package main
内可见,首字母大小写决定scope。如果函数名是Whatever
, 那么此函数将在包外可见。从fmt, log 等package可以发现,可以直接使用的函数都是大写开头的,例如: fmt.Println
。
func whatever() {
}
变量或者函数,首字母大写相当于公有(public),小写相当于为当前package所私有(private)。
4 指针
与C类似,Go 可以使用指针:
package main
import "log"
func main() {
var myString string
myString = "Green"
log.Println("myString is set to", myString)
changeUsingPointer(&myString)
log.Println("After func call myString is set to", myString)
}
func changeUsingPointer(s *string) {
log.Println("s is set to", s)
newValue := "Red"
*s = newValue
}
输出:
2021/10/30 13:53:53 myString is set to Green
2021/10/30 13:53:53 s is set to 0xc000040230
2021/10/30 13:53:53 After func call myString is set to Red
5 结构体 struct
用户可以自定义类型
package main
import (
"log"
"time"
)
type User struct {
FirstName string
LastName string
PhoneNumber string
Age int
BirthDate time.Time
}
func main() {
user := User {
FirstName: "Trevor",
LastName: "Sawler",
}
log.Println(user.FirstName, user.LastName, user.BirthDate)
}
6 Receivers: Structs with functions 结构体的函数
结构体可以具有函数,使用 reciever,将函数绑定到结构体:
package main
import (
"log"
)
type myStruct struct {
FirstName string
}
// receiver: 绑定 printFirstName() 函数到 myStruct,
// 因此函数 printFirstName 是 myStruct 的一部分
func (m *myStruct) printFirstName() string {
return m.FirstName
}
func main() {
var myVar myStruct
myVar.FirstName = "John"
myVar2 := myStruct{
FirstName: "Mary",
}
log.Println("myVar is set to", myVar.printFirstName())
log.Println("myVar2 is set to", myVar2.printFirstName())
}
输出:
2021/10/30 15:18:45 myVar is set to John
2021/10/30 15:18:45 myVar2 is set to Mary
7 映射
map 不可改变、快速、无序。
package main
import "log"
type User struct {
FirstName string
LastName string
}
func main() {
// ! don't declare a map like this
// valid, but useless
//var myOtherMap map[string]int
// create a map this way
//myTestMap := make(map[string]int)
myMap := make(map[string]User)
me := User{
FirstName: "grass",
LastName: "land",
}
myMap["me"] = me
log.Println(myMap["me"])
}
key 不必是 string
类型,也可以是int
,只要不与其他key值重复就可以。
8 切片
在 Go 中,几乎从不使用数组,而是使用 slice 即切片。
package main
import "log"
func main() {
var mySlice []string // a slice of strings
mySlice = append(mySlice, "John")
mySlice = append(mySlice, "Jim")
mySlice = append(mySlice, "Tom")
log.Println(mySlice)
}
slice 极其有用,可以添加、删除、排序等等。
package main
import (
"log"
"sort"
)
func main() {
var mySlice []int
mySlice = append(mySlice, 2)
mySlice = append(mySlice, 1)
mySlice = append(mySlice, 3)
log.Println(mySlice)
sort.Ints(mySlice)
log.Println(mySlice)
}
其他声明切片的方法:
package main
import (
"log"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// [1 2 3]
log.Println(numbers[0:3]) // 下标从0开始
}
9 决策结构 (Decision Structures)
if
语句:
package main
import "log"
func main() {
cat := "cat"
if cat == "cat" {
log.Println("cat is cat")
} else {
log.Println("cat is not cat")
}
}
Go 对 if ... else
的格式,即大括号位置以及换行都有要求:
package main
import "log"
func main() {
myNum := 100
isTrue := false
if myNum > 100 && !isTrue {
log.Println("my if")
} else if myNum < 100 || isTrue == true {
log.Println("my else if")
} else {
log.Println("my else")
}
}
switch .... case
举例, 不必加 break
:
package main
import "log"
func main() {
myVar := "sdfsd"
switch myVar {
case "cat":
log.Println("cat is set to cat")
case "dog":
log.Println("cat is set to dog")
case "fish":
log.Println("cat is set to fish")
default:
log.Println("cat is set to something else")
}
}
10 循环
for
循环中使用了分号,分号除此之外很少使用:
package main
import "log"
func main() {
for i := 0; i < 10; i++ {
log.Println(i)
}
}
下面的 for
循环,使用了空白标识符:
package main
import "log"
func main() {
animals := []string{"dog", "fish", "horse", "cow", "cat"}
// 本来这里应该有一个参数,例如 i,但是有时候不需要用到,
// 为了避免编译器抱怨,使用 blank identifier _
for _, animal := range animals {
log.Println(animal)
}
}
迭代 map:
package main
import "log"
func main() {
animals := make(map[string]string)
animals["dog"] = "Fido"
animals["cat"] = "Fluffy"
animals["horse"] = "Charlie"
for animalType, animal := range animals {
log.Println(animalType, animal)
}
}
输出:
2021/10/30 17:56:30 horse Charlie
2021/10/30 17:56:30 dog Fido
2021/10/30 17:56:30 cat Fluffy
range over strings:
package main
import "log"
func main() {
var firstLine = "Once upon a midnight"
for i, l := range firstLine {
// 打印出的都是数字,非字母
// 例如 2021/10/30 18:01:30 0 79
log.Println(i, l)
}
}
A string is a slice of runes, rune is a byte
So a string is a slice of bytes.
package main
import "log"
func main() {
type User struct {
FirstName string
LastName string
Email string
Age int
}
var users []User
users = append(users, User{"John", "Smith", "John@Smith.com", 30})
users = append(users, User{"M", "U", "M@Smith.com", 29})
users = append(users, User{"L", "V", "L@Smith.com", 21})
users = append(users, User{"K", "W", "K@Smith.com", 19})
for _, l := range users {
log.Println(l.FirstName, l.LastName, l.Email, l.Age)
}
}
运行结果:
2021/10/30 19:09:16 John Smith John@Smith.com 30
2021/10/30 19:09:16 M U M@Smith.com 29
2021/10/30 19:09:16 L V L@Smith.com 21
2021/10/30 19:09:16 K W K@Smith.com 19
11 接口
package main
import "fmt"
type Animal interface {
Says() string
NumberOfLegs() int
}
type Dog struct {
Name string
Breed string
}
type Gorilla struct {
Name string
Color string
NumberOfTeeth int
}
func main() {
dog := Dog{
Name: "Samson",
Breed: "German Shephered",
}
PrintInfo(&dog)
}
func PrintInfo(a Animal) {
fmt.Println("This animal says", a.Says(), "and has", a.NumberOfLegs(), "legs")
}
func (d *Dog) Says() string {
return "Woof"
}
func (d *Dog) NumberOfLegs() int {
return 4
}
在 Go 中,大多数接收器都是指针类型,这是有充分理由的。 指针类型的接收器只会让事情变得更快。 它简单得多,实际上在 go 文档中被称为最佳实践。
测试接口比测试像 dog
或 gorilla
这样的具体类型要容易得多。
In Go, most receivers are pointer types and there’s a very good reason for that. A receiver of pointer type just makes things faster. It’s much simpler and it’s actually referred to in the go documentation as best practices.
It’s much easier to test an interface that it is to test a concrete type like dog or gorilla.
12 包
终端运行命令: go mod init github.com/my_github_account/mysmallgo
将生成 go.mod 文件。
然后可以可以开始创建自己的go 包。
VS code 自动 import 必要的包。
github.com/alice201601/mysmallgo 即使以后不上传自己的工程文件,这样命名也是很有用的。
helpers/helpers.go:
package helpers
type SomeType struct {
TypeName string
TypeNumber int
}
main.go:
package main
import (
"log"
"github.com/alice201601/mysmallgo/helpers"
)
func main() {
log.Println("Hello")
var myVar helpers.SomeType
myVar.TypeName = "Some type"
log.Println(myVar.TypeName)
}
包极其有用,便于组织项目代码。
So packages are incredibly useful, incredibly powerful, a nice way of getting our code organized into logical groupings
13 通道 (channel)
helpers.go
package helpers
import (
"math/rand"
"time"
)
func RandomNumber(n int) int {
rand.Seed(time.Now().UnixNano())
value := rand.Intn(n)
return value
}
main.go
package main
import (
"log"
"github.com/my_github_account/mysmallgo/helpers"
)
const numPool = 10
// get a random number
func CalculateValue(intChan chan int) {
randomNumber := helpers.RandomNumber(numPool)
intChan <- randomNumber
}
func main() {
// intChan is a channel of int
intChan := make(chan int)
// And all defer says, is whatever comes after this keyword,
// defer, execute that as soon as the current function is done.
defer close(intChan) // good practice, always close a channel
go CalculateValue(intChan)
num := <-intChan // listen
log.Println(num)
}
通道是一种将信息从程序的一个部分发送到程序的另一部分的方法。
Channels are a means of sending information from one part of your program to another part of your program.
And all that says is I’m creating a channel, a place to send information which will be received in one or more places in my program, and that channel can only hold it. That’s all that it can hold.
14 JSON 读写
14.1 读取 JSON 到结构
package main
import (
"encoding/json"
"log"
)
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
HairColor string `json:"hair_color"`
HasDog bool `json:"has_dog"`
}
func main() {
// json 里不能加 注释 //, 逗号也有要求
myJson := `[{
"first_name":"Clark",
"last_name":"Kent",
"hair_color":"black",
"has_dog":true
},
{
"first_name":"Bruce",
"last_name":"Wayne",
"hair_color":"black",
"has_dog":false
}
]
`
var unmarshalled []Person
err := json.Unmarshal([]byte(myJson), &unmarshalled)
if err != nil {
log.Println("Error unmarshalling json: ", err)
}
log.Printf("unmarshalled: %v", unmarshalled)
}
输出:
2021/10/31 17:11:41 unmarshalled: [{Clark Kent black true} {Bruce Wayne black false}]
14.2 Write JSON from a struct:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
HairColor string `json:"hair_color"`
HasDog bool `json:"has_dog"`
}
func main() {
// Write json from a struct
var mySlice []Person
var m1 Person
m1.FirstName = "Wally"
m1.LastName = "West"
m1.HairColor = "red"
m1.HasDog = false
mySlice = append(mySlice, m1)
var m2 Person
m2.FirstName = "Diana"
m2.LastName = "Prince"
m2.HairColor = "blue"
m2.HasDog = false
mySlice = append(mySlice, m2)
// now a slice of two entries created
// then convert this slice into json
newJson, err := json.MarshalIndent(mySlice, "", " ")
if err != nil {
log.Println("error marshalling: ", err)
}
fmt.Println(string(newJson))
}
15 测试
When we write code, we want to write tests to ensure that our code actually does what it’s supposed to do. Fortunately, go makes this remarkably easy.
In go the test themselves live right beside the code you are trying to test. 例如,要测试main.go 就在同一路径创建文件:main_test.go:
package main
import "testing"
var tests = []struct {
name string
dividend float32
divisor float32
expected float32
isErr bool
}{
{"valid-data", 100.0, 10.0, 10.0, false},
{"invalid-data", 100.0, 0.0, 0.0, true},
{"expect-5", 50.0, 10.0, 5.0, false},
{"expect-fraction", -1.0, -777.0, 0.0012870013, false},
}
func TestDivision(t *testing.T) {
for _, tt := range tests {
got, err := divide(tt.dividend, tt.divisor)
if tt.isErr {
if err == nil {
t.Error("expected an error but did not get one")
}
} else {
if err != nil {
t.Error("did not expect an error but got one", err.Error())
}
}
if got != tt.expected {
t.Errorf("expected %f but got %f", tt.expected, got)
}
}
}
main.go
:
package main
import (
"errors"
"log"
)
func main() {
result, err := divide(100.0, 0)
if err != nil {
log.Println(err)
return
}
log.Println("result of division is", result)
}
func divide(x, y float32) (float32, error) {
var result float32
if y == 0 {
return result, errors.New("cannot divide by 0")
}
result = x / y
return result, nil
}
两种测试命令:
go test
go test -v
显示details
go test -cover
显示测试覆盖率。
go test -coverprofile=coverage.out && go tool cover -html=coverage.out
此命令在 VS 终端无法运行, cmd 却可以, 输出报告,显示测试的覆盖率,覆盖代码
[1] https://medium.com/golangspec/automatic-semicolon-insertion-in-go-1990338f2649
[2] https://www.quora.com/Should-we-use-semicolons-in-Go
[3] https://www.digitalocean.com/community/tutorials/how-to-write-comments-in-go