Go基础语法 | 青训营笔记

56 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天。今天学习了Golang的基础语法和实际案例,今天将笔记整理发出。

1. 本章学习内容

  1. Go语言代码的基本结构

  2. Go语言的基本语法

2. Go语言基本语法内容

  1. 基本语法结构
package main
import (
	"fmt"
)
func main() {
	fmt.Println("hello world")
}

Go语言的代码结构如上所示,主要由三部分构成,package-包,import-引入库文件,fun main()-主函数,也是程序运行的出口。

  1. Go的变量类型

Go语言是强类型语言,每种变量都有对应的类型,常见的类型包括:字符串、整数、浮点型、布尔型。其中字符串可以直接通过“+”进行拼接,也可以使用“=”比较两个字符串。

  1. 变量的声明

Go语言变量的声明有以下几种方式:

  • 可以省略类型,Go会自动推导变量的类型,如第2行代码。
  • 显示声明变量的类型,但是类型需要在变量名后面。
  • 可以使用 := 来声明变量
func main() {
	var str = "Hello World"

    var i1,i2 int = 12, 23

    var flag = true

    var f1 float65

    f2 := float32(f1)
}
  1. 常量的类型

Go语言中,常量使用const 来声明,常量没有具体的类型,是根据使用时的上下文确定常量的类型。

func main() {
	const s string = "constant"
    const i = 20000
}
  1. if-else 分支控制语句

Go中的控制语句如下所示,if 判断语句后面不需要():

func main() {
    const i = 7
    if i % 2 == 0 {
        fmt.Println(i,"is oushu")
    else {
        fmt.Println(i,"is jishu")
    }


    if num := 9; num < 0 {
        fmt.Println("case 01")
    else if num < 10 {
        fmt.Println("case 02")
    } else {
    	fmt.Println("case 03")
    }
}
  1. 循环

Go中只有for循环,没有while循环和do-while循环,使用for循环可以充当无限循环(死循环)和有限循环。

func main() {
	i := 1
    for {
        fmt.Println("loop")
    }
    for j:=7;j<9;j++ {
        fmt.Println("j=",j)
    }
}
  1. switch-case分支控制语句

Golang中的switch分支控制结构和Java类似,不同的地方在于其省略了(),并且不需要写break语句,这就意味着,Golang只会符合switch中的一个case,执行完该case中的语句后,则会跳出switch语句。另外可以使用switch语句取代if-else 语句

func main() {

	a := 2
	switch a {
	case 1:
		fmt.Println("one")	//执行完该条语句会直接退出switch语句
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	case 4, 5:
		fmt.Println("four or five")
	default:
		fmt.Println("other")
	}


    t:=time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    defaule:
        fmt.Println("It's after noon")
    }
}
  1. 数组

数组是具有编号且长度固定的序列。在Golang中,数组的声明方式如下:

func main() {
	var a [5]int
    a[4] = 100
    fmt.Println(a[4],len(a))

    b:=[5]int{1,2,3,4,5}

    var twoD [2][3]int
}
  1. 切片

切片可以视为一个可变长度的数组,其声明方式如下所示,通过make关键字进行声明,并且切片可以通过append()方法追加内容,但是需要注意的是必须将追加后的切片赋值给原来的那个切片。

func main(){
    s := make([]string,3)
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
	//可以使用append()方法追加内容,但是必须赋回原值
    s = append(s, "d")
	s = append(s, "e", "f")
	fmt.Println(s) // [a b c d e f]
	//copy()方法可以将一个切片赋值给另一个切片
	c := make([]string, len(s))
	copy(c, s)
	fmt.Println(c) // [a b c d e f]

	fmt.Println(s[2:5]) // [c d e]
	fmt.Println(s[:5])  // [a b c d e]
	fmt.Println(s[2:])  // [c d e f]

	good := []string{"g", "o", "o", "d"}
	fmt.Println(good) // [g o o d]
}
  1. 集合map

Golang中的map集合是通过make关键字来创建的。其赋值方式为:map["one"] = 1,其中 [] 中的内容为 key , = 右边的内容为value。Golang中的map是完全无序的。不会按照插入顺序也不会按照字母顺序进行输出。

func main() {
	m := make(map[string]int)
	m["one"] = 1
	m["two"] = 2
	fmt.Println(m)           // map[one:1 two:2]
	fmt.Println(len(m))      // 2
	fmt.Println(m["one"])    // 1
	fmt.Println(m["unknow"]) // 0

	r, ok := m["unknow"]
	fmt.Println(r, ok) // 0 false

	delete(m, "one")

	m2 := map[string]int{"one": 1, "two": 2}
	var m3 = map[string]int{"one": 1, "two": 2}
	fmt.Println(m2, m3)
}
  1. 使用range遍历

对于数组或者map集合,可以使用range来进行快速遍历。使用range遍历数组会返回索引和对应索引的值。遍历map,则第一个值是key,第二个值是value。当然在map中也可以只遍历key。

func main() {
	nums := []int{2, 3, 4}
	sum := 0
	for i, num := range nums {
		sum += num
		if num == 2 {
			fmt.Println("index:", i, "num:", num) // index: 0 num: 2
		}
	}
	fmt.Println(sum) // 9

	m := map[string]string{"a": "A", "b": "B"}
	for k, v := range m {
		fmt.Println(k, v) // b 8; a A
	}
	for k := range m {
		fmt.Println("key", k) // key a; key b
	}
}
  1. 函数

Golang中的函数结构如下所示,可以看到使用func 开头,然后紧接函数名和参数列表,最后才是函数的返回值,和Java有着较大区别。

func add(a int, b int) int {
	return a + b
}
  1. 指针

Golang支持指针,其主要目的是可以针对传入的值进行修改,传入指针类型的参数变量需要使用*+类型,例如*int,*string,而在实际使用时需要使用&+变量。如下面第13行代码。

func add2(n int) {
	n += 2
}

func add2ptr(n *int) {
	*n += 2
}

func main() {
	n := 5
	add2(n)
	fmt.Println(n) // 5
	add2ptr(&n)
	fmt.Println(n) // 7
}
  1. 结构体

结构体是带类型的字段的集合,其声明方式如下所示,可以使用结构体名称来初始化一个结构体变量。

type user struct {
    name string
    password string
}

func main() {
    a := user{name: "wang", password: "1024"}
	b := user{"wang", "1024"}
	c := user{name: "wang"}
	c.password = "1024"
	var d user
	d.name = "wang"
	d.password = "1024"
}

//普通方法
func checkPassword(u user, password string) bool {
	return u.password == password
}
  1. 结构体方法

Golang中的结构体方法类似于Java中的成员方法,其结构如下所示,结构体方法和普通方法相比,在func 后面增加了结构体类型。即代码中的(u user) 和(u *user),同时结构体类型也有指针和非指针类型。

type user struct {
	name     string
	password string
}
//结构体方法
func (u user) checkPassword(password string) bool {
	return u.password == password
}

func (u *user) resetPassword(password string) {
	u.password = password
}

func main() {
	a := user{name: "wang", password: "1024"}
	a.resetPassword("2048")
	fmt.Println(a.checkPassword("2048")) // true
}
  1. 错误处理

Golang中错误处理的习惯是使用单独的返回值来传递错误信息,这样就可以使用if-else语句来传递错误信息。可以在函数的返回值中加入一个error类型的错误,当出现错误时,创建一个新的错误信息即可。代码如下所示:

func findUser(users []user, name string) (v *user, err error) {
	//不需要使用索引时,使用_代替
    for _, u := range users {
		if u.name == name {
			return &u, nil
		}
	}
	return nil, errors.New("not found")
}
  1. 字符串操作

Golang中的对字符串的操作在标准库 “strings"中,常见的操作如下:

  • Contains()-是否包含某个字符串,返回布尔值
  • Count()-统计字符串中某个字符出现的次数
  • HasPrefix()-字符串是否以某个字符串为前缀
  • HashSuffix()-字符串是否以某个字符串为后缀
  • Index()-某字符串在目标字符串中出现的索引
  • Join()-连接多个字符串
  • Repeat()-重复多个字符串
  • Split()-分隔多个字符串
  • ToLower()-将字符串转换为小写
  • ToUpper()-将字符串转换为大写
  • len()-输出字符串的长度(内置函数)
func main() {
	a := "hello"
	fmt.Println(strings.Contains(a, "ll"))                // true
	fmt.Println(strings.Count(a, "l"))                    // 2
	fmt.Println(strings.HasPrefix(a, "he"))               // true
	fmt.Println(strings.HasSuffix(a, "llo"))              // true
	fmt.Println(strings.Index(a, "ll"))                   // 2
	fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
	fmt.Println(strings.Repeat(a, 2))                     // hellohello
	fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
	fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
	fmt.Println(strings.ToLower(a))                       // hello
	fmt.Println(strings.ToUpper(a))                       // HELLO
	fmt.Println(len(a))                                   // 5
	b := "你好"
	fmt.Println(len(b)) // 6
}
  1. 字符串的格式化

在fmt标准库中,有很多字符串的格式化输出的方法,使用%v可以打印任意类型的变量。

func main() {
	s := "hello"
	n := 123
	p := point{1, 2}
	fmt.Println(s, n) // hello 123
	fmt.Println(p)    // {1 2}

	fmt.Printf("s=%v\n", s)  // s=hello
	fmt.Printf("n=%v\n", n)  // n=123
	fmt.Printf("p=%v\n", p)  // p={1 2}
	fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
	fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

	f := 3.141592653
	fmt.Println(f)          // 3.141592653
	fmt.Printf("%.2f\n", f) // 3.14
}
  1. 结构体转化为Json

在Golang中结构体转化为Json特别简单,只需要保证结构体中的属性名首字母为大写时,使用json.Marshal()就可以进行序列化,然后打印时需要强制转换为字符串类型,否则会打印十六进制的数字。另外,如果要输出为首字母为小写的json时,则需要在结构体的字段中显式声明 json :age。详情见如下的代码:

type userInfo struct {
	Name  string
	Age   int `json:"age"`
	Hobby []string
}

func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, err := json.Marshal(a)
	if err != nil {
		panic(err)
	}
	fmt.Println(buf)         // [123 34 78 97...]
	fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}

	buf, err = json.MarshalIndent(a, "", "\t")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf))

	var b userInfo
	err = json.Unmarshal(buf, &b)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
  1. 时间处理

Golang中的格式化用的是一个特定的时间,即“2006-01-02 15:04:05”。

  • time.Now()-获取当前时间
  • Format()-格式化时间
  • Sub()-某个时间减去另外一个时间
func main() {
    //time是一个时间库
	now := time.Now()
	fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
	t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
	t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
	fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
	fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
	fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
	diff := t2.Sub(t)
	fmt.Println(diff)                           // 1h5m0s
	fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
	t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
	if err != nil {
		panic(err)
	}
	fmt.Println(t3 == t)    // true
	fmt.Println(now.Unix()) // 1648738080
}
  1. 类型转换

字符串和数值之间的转换都在“strconv”包中。常见的方法有:

  • ParseFloat()-解析字符串为浮点型
  • ParseInt()-解析字符串为整型。
  • Atoi()-快速将字符串转换为数字
  • ItoA()-快速将数字转换为字符串
func main() {
    //64表示精度
	f, _ := strconv.ParseFloat("1.234", 64)
	fmt.Println(f) // 1.234
	//10表示进制,不传是自动推断
	n, _ := strconv.ParseInt("111", 10, 64)
	fmt.Println(n) // 111
	
	n, _ = strconv.ParseInt("0x1000", 0, 64)
	fmt.Println(n) // 4096

	n2, _ := strconv.Atoi("123")
	fmt.Println(n2) // 123

	n2, err := strconv.Atoi("AAA")
	fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
  1. 获取进程相关的信息

Golang中可以通过os标准库来获取Go程序运行时进程相关的信息。

  • Args()-获取参数李彪
  • Getenv()、Setenv()-获取/写入环境变量
import (
    "fmt"
    "os"
    "os/exec"
)


func main() {
    // go run example/20-env/main.go a b c d
    fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
    fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
    fmt.Println(os.Setenv("AA", "BB"))


    buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
    if err != nil {
        panic(err)
    }
    fmt.Println(string(buf)) // 127.0.0.1       localhost
}

可以看到Golang相比Java的一些不同地方:

  • 不需要写“
  • if-else,switch语句不需要写“ ()
  • switch语句中不需要写 break 语句
  • 可以使用switch语句代替if-else语句
  • Golang中的库函数以 大写字母 开头,内置函数和自定义函数使用小写字母开头
  • fmt.Println 输出语句中使用 “,”分隔并连接(语法分隔,输出连接)
  • 变量的声明和java不同,其中更是分为变量和常量。
  • 函数的结构和Java不同,Golang的返回值在后,Java的返回值在前。
  • Golang支持指针,Java中称为引用类型。
  • Java中一切皆为对象,而Golang中则为结构体,并且结构体方法对应成员方法。但是两者书写规则相差较远。
  • Golang中是使用自定义错误信息的处理的方式,而Java中则是使用异常的处理方式。
  • 使用:=符号可以在变量不声明的情况下直接被赋值使用。