Go基本语法|青训营笔记

43 阅读8分钟

这是我参与「第五届青训营 」笔记创作活动的第1天

前段时间忙着写论文,青训营的课程没来得及总结,从这篇开始从GO基础开始总结。 我原本是使用Java进行开发的,趁青训营了解一些GO。

GO简介

Go是由Google出品的,具有以下特点

  • 高性能、高并发。Java的并发是基于多线程的,线程属于内核态,开销大。go是基于协程的,协程属于用户态,轻量级线程。

  • 语法简单、学习曲线平缓。接触过C或者C++的应该很容易上手

  • 丰富的标准库,不需要额外导入一些第三方库,标准库有很高的稳定性保证。

  • 完善的工具链,代码优化,内置单元测试等。

  • 静态链接(java中有动态链接),只需要拷贝一个文件就可以直接运行,Java需要部署一个GRE环境才能运行。

  • 快速编译,一秒钟左右快速编译。

  • 跨平台(交叉编译,路由器,树莓派等等)

  • 自动垃圾回收机制,(C++中没有垃圾回收机制)

  • 多返回值函数(java的返回值只有一个)

  • 错误处理等。(没有Java中try catch的繁琐,直接将error在方法中将异常作为返回值)

开发环境

首先需要下载安装Golang,类似Java中的JDK。安装及配置环境过程比较简单。下载地址go.dev/doc/install

编译工具有VSCode和GoLand。对于Java开发者而言,使用过IDEA,GoLand更容易上手。JetBrain全家桶破解版YYDS。

基本语法

1、Hello World

程序员接触的第一行代码“hello world”。

Go的执行入口是main包下的main函数。

package main
//导入工具包
import (
   "fmt"
)
//主函数入口
func main() {
   fmt.Println("hello world")
}

//错误示例:“ { ”必须和方法名在同一行
func main() 
{
   fmt.Println("hello world")
}

2、变量声明

变量声明由两种方式:

  • var 变量名 类型 (和Java相反,把类型放到了后面。)
  • 变量名 := 值 (声明并赋值,系统会自动推断值的类型)
func main() {

   var a = "string"
   var b, c int = 1, 2
   var d = true
   var e float64
   f := float32(e)
   g := a + "foo"
   fmt.Println(a, b, c, d, e, f) 
   //输出 
   fmt.Println(g)                
   const s string = "constant"
   const h = 500000000
   const i = 3e20 / h
   fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

3、常量声明

常量是再程序运行过程中不可修改的值。对照Java中以final关键字修饰的常量。

package main

import (
   "fmt"
   "math"
)

func main() {
   const a string = "constant"
   const b = 500000000
   const c = 3e20 / h
   fmt.Println(a, b, c, math.Sin(b), math.Sin(c))
}

4、条件语句

Go不支持三目运算符?:

  • if else
if 10%2 == 0 {
   fmt.Println("偶数")
} else {
   fmt.Println("奇数")
}
  • switch

switch后可省略变量名

func main() {
   a := 2
   switch a {
   case 1:
      fmt.Println("one")
   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")
   default:
      fmt.Println("It's after noon")
   }
}

select只能用于通道操,channel在多线程中应用juejin.cn/post/719665…

5、循环语句

Go只支持for循环,不支持do while循环。for循环格式如下:

for 条件 {
    循环体
}
package main

import "fmt"

func main() {
   //1、省略条件
   for {
      fmt.Println("循环输出")
      break //不加break是死循环
   }
   //2、条件循环
   for j := 1; j < 10; j++ {
      fmt.Println(j)
   }

   for n := 0; n < 5; n++ {
      if n%2 == 0 {
         continue
      }
      fmt.Println(n)
   }
   i := 1
   for i <= 3 {
      fmt.Println(i)
      i = i + 1
   }
}

6、数组

数组是定长的,不可改变,所以实际开发中不常用。数组内的元素都是同一种类型。数组的创建方式如下:

  • 先声明,在赋值
var a [5]int
a[1] = 100
a[2] = 200
复制代码
  • 声明时直接赋值
b := [5]int{1, 2, 3, 4, 5}
复制代码
  • 二维数组
var c [2][3]int
for i := 0; i < 2; i++{
    for j := 0; j < 3; j++{
        c[i][j] = i+j
    }
}

7、切片

长度可变,可追加元素,实现动态数组,实现范围读取。

创建切片的函数是

make([]type, len, capacity)

参数分别对应:切片类型,初始化切片长度,指定容量(可选参数)

切片最大容量和切片长度

cap(切片)
len(切片)
  • 声明切片并赋值
var slice1 []int
slice1 = make([]int, 5) 

slice slice2 =[]int{123}
slice3 := []int{123}
复制代码

或者

slice4 := make([]int, 4)
slice4[0] = 0
slice4[1] = 1
slice4 = slice4.append(2)
slice4 = slice4.append(3)

//数组拷贝
slice5 := make(int[], len(slice4))
copy(slice5,slice4)

//切片截取赋值
slice6 := slice5[0:5]
复制代码

切片读取范围是前闭后开

slice[:] //整个数组引用
slice[startIndex:endIndex]
slice[startIndex:]
slice[:endIndex]
复制代码

8、函数

函数的格式如下,返回值可以是多个。

func 函数名(参数名 参数类型, 参数名 参数类型) 返回类型{
    return 值
}

示例:

func add(a int, b int) int {
   return a + b
}

func add2(a, b int) int {
   return a + b
}

func exists(m map[string]string, k string) (v string, ok bool) {
   v, ok = m[k]
   return v, ok
}

9、指针

指针也是一种变量,指向的是变量的地址。

函数参数是传递变量时,传的是值,而不是地址,修改不会影响变量。

函数参数是传递指针时,传递的是变量的地址,修改指针变量会影响值。要通过&进行传递。

juejin.cn/post/719593…

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
}

10、结构体

结构体类似Java中的类,是对一系列变量的封装,是一系列带类型的字段集合

type 结构体名 struct{
    字段名 字段类型
    字段名 字段类型
    ...
}
type user struct {
   name     string
   password string
}

func main() {
   a := user{name: "张三", password: "1024"}
   b := user{"李四", "1024"}
   c := user{name: "王五"}
   c.password = "1024"
   var d user
   d.name = "赵六"
   d.password = "1024"

   fmt.Println(a, b, c, d)                
   fmt.Println(checkPassword(a, "haha"))   // false
   fmt.Println(checkPassword2(&a, "haha")) // false
}

func checkPassword(u user, password string) bool {
   return u.password == password
}

func checkPassword2(u *user, password string) bool {
   return u.password == password
}

11、结构体方法

方法和函数的区别在于func后有无接收者。类似Java中的成员方法。

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
}

11、Map集合

Map是无序的键值对集合。也是通过make函数进行创建

m := make(map[string]int)
m["a"]=1
m["b"]=2
r,error = m["unknow"]
delete(m,"a")

12、接口

接口和struct一样,也是一种类型,用关键字interface定义。 接口中只有方法声明,没有实现也没有字段(JAVA中可以有static final的字段)。

type Animal interface {
	Eat(string) string
}

接口是抽象出一些类的公共特点。

type Dog struct{}
 
func (dog Dog) Eat(food string) (emoij string)  {
	if food == "骨头" {
		emoij = "汪汪"
	} else {
		emoij = "呜呜"
	}
	return
}
var animal Animal = Dog{}
food := "骨头"
fmt.Println(animal.Eat(food))

13、异常处理

go通过内置的error接口提供了简单的错误处理方式

type error interface {
    Error() string
}

在方法返回时,返回多个参数,其中一个是error

type user struct {
   name     string
   password string
}

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")
}

func main() {
   u, err := findUser([]user{{"wang", "1024"}}, "wang")
   if err != nil {
      fmt.Println(err)
      return
   }
   fmt.Println(u.name) // wang

   if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
      fmt.Println(err) // not found
      return
   } else {
      fmt.Println(u.name)
   }
}

14、并发

go关键字可以开启一个goroutine新的轻量级线程。

go 参数名(参数列表)
package main

import (
   "fmt"
   "time"
)

func say(s string) {
   for i := 0; i < 5; i++ {
      time.Sleep(100 * time.Millisecond)
      fmt.Println(s)
   }
}

func main() {
   go say("world")
   say("hello")
}

image.png

15、字符串操作

Go提供了strings工具包对字符串进行操作

package main

import (
   "fmt"
   "strings"
)

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
}

json串

涉及到网络传输或者存储时,会用到json串和struct的转换。

package main

import (
   "encoding/json"
   "fmt"
)

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"}}
}

字符串转换

Go提供了strconv工具进行字符串与数字类型之间的转换

package main

import (
   "fmt"
   "strconv"
)

func main() {
   f, _ := strconv.ParseFloat("1.234", 64)
   fmt.Println(f) // 1.234

   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
}

时间处理

go提供了time类处理时间

package main

import (
   "fmt"
   "time"
)

func main() {
   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
}

总结较为粗略,后续完善一下延申的知识。