Go 语法概述

Go 的数据类型:

基本类型: int, float32, float64, boolean, string, rune

聚合类型: array, struct

引用类型: pointer, slice, map, function, channel

接口类型

1 分号

不同于C等其他语言,写代码时语句不必以分号 ; 作为终止符,目的是:

  1. 减少输入,增加可读性
  2. 语句没有加分号时,不再标识为错误
  3. 避免开发者使用分号写出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 文档中被称为最佳实践。

测试接口比测试像 doggorilla 这样的具体类型要容易得多。

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值