Go 语言入门--Go语言基础语法 Part 2

99 阅读10分钟

一、前言

接着上一个博客(Go 语言入门--Go语言基础语法 Part 1)写,我们继续来学习Go语言的基础语法

二、基础语法

11.结构体

结构体是带类型的字段的集合,简单来说,就是一些类型加起来的集合。

在go语言中,结构体的定义和使用和C语言类似,但是也有形式上的不同,但是使用方式还是一样的

示例:

//定义一个名为user的结构体
type user srtuct {
    name string
    password string
}
​
func main(){
    //在主函数中定义结构体变量
    //第一种方式,用字段名进行匹配
    a := user(name :"xioaming" , password:"1024")
    //第二种方式,自动匹配字段
    b := user("xiaoming","1024")
    //第三种方式,先定义一种字段,只对一部分字段进行初始化
    c := user(name:"xiaoming")
    c.password = "1024"
    //第四种方式,先定义,后初始化
    var d user
    d.name = "xiaoming"
    d.password = "1024"
}

在go语言中,结构体经常会作为参数传入自定义函数中,而且经常作为指针传入,这样能够实现对结构体的修改,也可以在避免一些大结构体的拷贝开销。

注:拷贝开销:指的是一般情况下,传入函数的参数只是对于参数值的拷贝,而不是原值。 示例(续上):

//二者的效果是一样的
func checkPassword(u user , password string) bool{
    return u.password == password
}
func checkPassword(u *user , password string) bool{
    return u.password == password
}

12.结构体方法

在go语言中,我们可以为结构体定义方法,就像java中给类写方法一样,这样我们就可以在实例中调用这个方法,简单说,就是我们可以在定义结构体变量之后,通过调用其中的方法完成一些操作

type user struct{
    name     string
    password string
}
//接下来给这个结构体定义方法
func (u user) checkUser(name string , password string) bool{
    return name == u.name && password == u.password
}
​
func (u *user) setPassword(password string){
    u.password = password
}

其定义方法很简单,就是把结构体变量放在函数名前面,并且需要加上括号,这样的话,我们就可通过 a.checkPassword("xxxx") 对方法进行调用。

并且从我上面的代码可以看出,结构体方法可以有两种写法,一种是带指针的写法,一种是不带指针的写法,区别在于带指针的写法可以修改结构体中的字段。

a := user(name:"xiaoming", password:"123456")
a.setPassword("1234567");
fmt.println(a.password) //1234567

13.错误处理

和Java中需要使用异常来处理错误信息不同,在go语言中,我们通常会用一个单独的返回值来传递错误信息 注意:在使用这个时,需要提前引入errors 处理过程如下:

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

我们可以通过在参数列表中加上一个error,就代表这个函数可能会出错

然后我们可以通过简单的if-else语句来处理错误,并且可以很清晰的看出是哪个函数出了错。

如果出了错,那么就返回 nil 和 error

如果没有出错,那么就返回 原本的结果 和 nil

注:在 Golang 中,nil 是一个预定义的标识符,在不同的上下文环境中有不同的含义,但通常表示“无”、“空”或“零值”。nil 可以赋值给指针、切片、map、通道、函数以及接口类型的变量。

下面出如何通过返回值来判断和处理错误,判断错误的写法还是比较固定的

//续上
func main(){
    u,err := findUser([]users {{"wang" , "123456" },{"guan","1234567"}} ,"wang")
    if err != nil {
        fmt.println(err)
        return
    }
    fmt.println(u.name)    //wang
}

14.字符串操作

前提:引入 strings

在go语言中,我们可以通过string工具库来使用一些工具函数,比如:Contains用于判断一个字符串中是否包含另一个字符串,Count用于字符串计数,Index用于字符串的查找位置,Join用于连接多个字符串,Repeat用于重复多个字符串,Replace用于替换字符串

下面是使用示例:

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
    //func Replace(s, old, new string, n int) string
    //用法:返回将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。
    fmt.println(strings.Replace(a,"e","E",-1));
    fmt.println(strings.Split("a-b-c","-"))     //[a b c]
    fmt.println(strings.ToLower(a))             //A
    fmt.println(len(a))                         //5
    //中文的对应字节不同
    fmt.println(len("你好"))                     //6
    
}

15.字符串格式化

在fmt标准库中有很多和字符串格式相关的方法,printf和c中的printf就很像,但是有一点不同,在go语言中,我们可以用%v来打印任何类型的参数,也可以用%+v来打印更详细的参数,还有%#v则更加详细

type point struct{
    x,y int
}
​
func main(){
    p := point(1,2)
    fmt.printf("%v",p)                   //{1 2}
    fmt.printf("%+v",p)                  //{x:1 y:2}
    fmt.printf("%#v",p)                  //main.point{x:1 y:2}
}

16.JSON处理

前提:需要引入 encoding/json

这里只涉及到标准库中的JSON处理

其实go语言中的JSON处理是非常简单的,我们只需要把结构体中的字段名的第一个字母大写即可,这样这个字段就成了公开字段 ,那么这个结构体就可以用 json.Marshal 序列化,变成一个JSON格式的字符串

序列化之后字符串也可以通过 json.Unmarshal 反序列化 到一个空变量中,这样默认序列化出来的字符串,输出会是以大写字母,也就是定义的公开字段的方式输出,我们可以用tag的写法,修改JSON结果里面的字段名

type userInfo struct{
    Name string
    Age     int 'json:"age"'
    Hobby   []string
}
func main(){
    a:= userInfo{"wang",18,{"basketball","football"}}
    buf,err := json.Marshal(a)   //buf ---- [123 34 78 ……]
    if err != nil{
        panic(err)
    }
    fmt.println(string(buf))   //{"Name":"wang","age":18,"Hobby":["basketball","football"] }
    var user
    err = json.Unmarshal(buf,&user)  //把buf中的json反序列化到b中,返回一个可能的错误
    if err != nil{
        panic(err)
    }
    fmt.println(user) //{"Name":"wang","age":18,"Hobby":["basketball","football"] }
    
}

注:在Go语言中,panic是一个内置的函数,用于在程序遇到严重的错误时触发一个不可恢复的异常。当panic被调用时,它会暂停正常的程序执行流程,打印出错误信息,并可能调用所有的 goroutine 中的 defer 函数,然后结束程序。这是处理错误的一种非常强烈和直接的方式。

defer语句是Go语言中的一个精华特性,用于在函数结束前自动执行代码。当函数结束时,defer语句中的代码会以逆序执行,即使函数是通过返回语句或恐慌结束的也会如此。这通常用于执行清理工作,比如关闭文件或网络连接。

具体的相关和用法会在后面再进行普及

17.时间处理

前提:需要引入time

在go语言中时间操作是很简单的,我们最常用的就是利用time.Now()获取现在的时间,也可以用time.Date()去构造一个带时区的时间。

func main(){
    now := time.Now()
    fmt.println(now)    //2024-11-22 09:59:14.2642387 +0800 CST m=+0.010202701
    t := time.Date(2024,2,12,1,25,36,0,time.UTC)
    fmt.println(t) //2024-2-12 01:25:36 +0000 UTC
}

注:可能有很多会有这样的疑问,为什么使用Now的时候,秒数后有七位,m又是什么

前提知识:时间戳:时间戳是一种用于唯一标识时间的方法,通常表示为自某一特定时刻或者起点(如Unix纪元1970年1月1日午夜)以来的秒数或者毫秒数。在计算机科学中,时间戳常用于跟踪日志、同步处理和存储事件数据。

1.秒数有七位:对于时间戳来说,一般使用秒级或者毫秒级,采用浮点数或定点数来表示小数部分,为了在大部分时候满足精度,我们会用七位小数来表示毫秒级的小数部分。

2.m:m 就是 Monotonic Clocks,意思是单调时间的,所谓单调,就是只会不停的往前增长,不受校时操作的影响,这个时间是自进程启动以来的秒数。

我们也可以对时间进行格式化,可以用YYYY-MM-dd的形式,因为time库中定义了年、月、日、时、分、秒、周、时区的多种表现形式,如下:

  • 年: 06/2006
  • 月: 1/01/Jan/January
  • 日: 2/02/_2
  • 时: 3/03/15/PM/pm/AM/am
  • 分: 4/04
  • 秒: 5/05
  • 周: Mon/Monday
  • 时区:-07/-0700/Z0700/Z07:00/-07:00/MST

所以我们可以自由通过这些进行组合,达到自己想要的输出形式

func main(){  //续上
    fmt.println(t.Format("2006-01-02 15:04:05")) //2024-02-12 01:25:36
}

上面有很多方法来获取这个时间点的年月日小时分钟秒,然后也能用点 sub 去对两个时间进行减法,得到一个时间段。时间段又可以去得到它有多少小时,多少分钟、多少秒。 上面有很多方法来获取这个时间点的年月日小时分钟秒,然后也能用点 sub 去对两个时间进行减法,得到一个时间段。时间段又可以去得到它有多少小时,多少分钟、多少秒

    func main(){  //续上
        fmt.println(t.Format("2006-01-02 15:04:05")) //2024-02-12 01:25:36
        fmt.println(t.Year(),t.Month(),t.Day(),t.Hour(),t.Minute()) //2024 2 12 1 25
        diff := now.Sub(t)
    }

我们还可以通过一些方法来转换时间的格式 比如time.Parse();

func main(){  //续上
    t2 := time.Parse("2006-01-02 15:04:05","2024-2-12 01:25:36")
    fmt.println(t2 == t)   //true
}

在一些系统交互或者方法中需要用到时间戳,我们也可以通过方法来拿到时间戳

func main{ 
    now := time.Now()
    // 取时间戳
    fmt.Println(now.Unix())       // 当前时间戳
    fmt.Println(now.UnixMilli())  // 毫秒级时间戳
    fmt.Println(now.UnixMicro())  // 微秒级时间戳
    fmt.Println(now.UnixNano())   // 纳秒级时间戳
    fmt.Println(now.Nanosecond()) // 时间戳小数部分 单位:纳秒
}

18.数字解析

前提:引入 strconv

在go中,我们是说的数字解析,就是把字符串形式的数字转换为数字形式,而数字形式就是说可以把这些数字转换为整数或浮点数

最基本的方法是当我们有一个以字符串形式存在的10进制数字时,我们想把它转换成一个整数。为了做到这一点,我们可以利用最广泛使用的 Atoi() 函数。

func main(){
    i:="111"
    res,err = strconv.Atoi(i)
    if err != nil{
        fmt.println("error")
        return
    }
    fmt.Println("The number now is:", res)  //111
    fmt.Printf("The type of the number is: %T", res) //int
}

还有两种可以把字符串转化为数字形式的函数

ParseInt()的语法

number, error := strconv.ParseInt(string, base, bitSize)

ParseFloat()的语法

number, error := strconv.ParseFloat(string, bitSize)
注意:我们可能遇到"0X1234"这种十六进制的字符串,这时我们只能有ParseInt()来转化他

三、结语:

今天的学习到此为止,现在我们基本学习了go语言基础语法,之后的就是尝试着敲出自己的代码了