Go语言入门指南:语法基础学习|青训营

103 阅读9分钟

00009.jpg

ps:图片为笔者Ai绘画所作

什么是GO语言

1,高性能,高并发
2,语法简单
3,丰富的标准库
4,完善的工具链
5,静态连接
6,快速编译
7,跨平台
8,垃圾回收(标记-清除算法)

Go变量

在 Go 编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。具体分类如下:

类型详解
布尔型布尔型的值只可以是常量 true 或者 false
数字类型整型 int 和浮点型 float。Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码
字符串类型字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
派生类型(a) 指针类型(Pointer)(b) 数组类型© 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型

定义变量

Go中使用var来声明一个变量,数据类型常放在变量名之后

package main
import "fmt"
func main() {
	var a int = 27
	fmt.Println(a);
} 

符号:=

Go中还可以使用:=符号来对变量进行初始化

a := 27

匿名变量

匿名变量的特点是一个下画线_,这本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。

    func GetData() (int, int) {
        return 10, 20
    }
    func main(){
        a, _ := GetData()
        _, b := GetData()
        fmt.Println(a, b)
    }

变量作用域

作用域指的是已声明的标识符所表示的常量、类型、函数或者包在源代码中的作用范围,在此我们主要看一下go中变量的作用域,根据变量定义位置的不同,可以分为以下三个类型: *

  • 函数内定义的变量为局部变量,这种局部变量的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。这种变量在存在于函数被调用时,销毁于函数调用结束后。
  • 函数外定义的变量为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,甚至可以使用import引入外部包来使用。全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。
  • 函数定义中的变量称为形式参数,定义函数时函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。形式参数会作为函数的局部变量来使用。*

指针

Go中指针的用法与C基本一致,不再赘述


Slice切片

简单地说,切片就是一种简化版的动态数组。因为动态数组的长度不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,而切片则使用得相当广泛。

切片高效操作的要点是要降低内存分配的次数,尽量保证append操作(在后续的插入和删除操作中都涉及到这个函数)不会超出cap的容量,降低触发内存分配的次数和每次分配内存大小。
切片的定义

type SliceHeader struct {
    Data uintptr   // 指向底层的的数组指针
    Len  int	   // 切片长度
    Cap  int	   // 切片最大长度
}


添加元素

append() :内置的泛型函数,可以向切片中增加元素。

在切片尾部追加N个元素

var a []int
a = append(a, 1)               // 追加1个元素
a = append(a, 1, 2, 3)         // 追加多个元素
a = append(a, []int{1,2,3}...) // 追加一个切片

注意:尾部添加在容量不足的条件下需要重新分配内存,可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用append函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。

在切片开头位置添加元素

var a = []int{1,2,3}
a = append([]int{0}, a...)        // 在开头位置添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

注意:在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制1次。因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多。

append链式操作

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...)     // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

每个添加操作中的第二个append调用都会创建一个临时切片,并将a[i:]的内容复制到新创建的切片中,然后将临时创建的切片再追加到a[:i]。

append和copy组合
a = append(a, 0)     // 切片扩展1个空间
copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
a[i] = x             // 设置新添加的元素

删除元素

根据要删除元素的位置有三种情况:

  • 从开头位置删除; 直接移动数据指针,代码如下:
a = []int{1, 2, 3, ...}
a = a[1:]                       // 删除开头1个元素
a = a[N:]                       // 删除开头N个元素

将后面的数据向开头移动,使用append原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)

a = []int{1, 2, 3}
a = append(a[:0], a[1:]) // 删除开头1个元素
a = append(a[:0], a[2:]) // 删除开头2个元素

使用copy将后续数据向前移动,代码如下:

a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
  • 从中间位置删除; 对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用append或copy原地完成: append删除操作如下:
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1], ...)
a = append(a[:i], a[i+N:], ...)

copy删除操作如下:

a = []int{1, 2, 3}
a = a[:copy(a[:i], a[i+1:])] // 删除中间1个元素
a = a[:copy(a[:i], a[i+N:])] // 删除中间N个元素
  • 从尾部删除。 代码如下所示:
a = []int{1, 2, 3, ...}

a = a[:len(a)-1]   // 删除尾部1个元素
a = a[:len(a)-N]   // 删除尾部N个元素

删除切片尾部的元素是最快的


Map

实际开发中使用较多的数据结构,由KV键值对构成,具有无序性 定义方法:

m:=make(map[string]int)//string是key的类型,int是value的类型

func main(){
   m:=make(map[string]int)//string是key的类型,int是value的类型
   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["two"])//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}//也可以用var语句,两者功能一致
   fmt.Println(m2,m3)//map[one:1 two:2] map[one:1 two:2]
}


Range语句

用来遍历切片或map

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


函数的使用

Go语言的函数数据类型是后置的,函数原生支持返回多个值。一般情况下会返回两个结果:真正的结果 + 错误信息 。


结构体

结构体中可以定义多个不同的数据类型。可以通过结构体.成员的方式来访问结构体成员

type user struct {
    name     string
    password string
}

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

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

func (u *user) resetPassword(password string){//类似于C++中的类成员函数,加上指针可以修改结构体的值,否则就相当于调用拷贝构造函数
	u.password = password
}

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"

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

	a.resetPassword("2048")//此处调用了结构体方法
}

Json转化

对于一个已有的结构体,如果其变量名首字母均为大写,即可使用json.Marshal进行序列化操作,序列化后会变成一个buf数组。若要输出字符,需要使用string(buf)进行强制类型转换。也可以使用json.Unmarshal实现反序列化到一个空变量中,如果json中想使用小写的数据名称,则可以在结构体后声明,如下所示。Go语言这种特性使得他更易与前端联调,进行数据传递

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{"golan", "TypeScript"}}  
buf, err := json.Marshal(a)  
if err != nil {  
panic(err)  
}  
fmt.Println(buf)  
fmt.Println(string(buf))  
  
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)  
a1 := []int{1, 2, 3}  
a1 = a1[1:] // 删除开头1个元素  
a1 = a1[2:] // 删除开头N个元素  
fmt.Println(a1[:1])  
}

异常处理

Go语言相比Java的异常处理,能够清晰的知道是哪个函数发出了错误信息。

通过在函数的返回值中添加一个error变量,从而获取到实际的错误信息。

时间处理

最常用的是time.Now()获取当前时间,也可以使用time.Date(YYYY, MM, DD, HH, MM, SS, +x, time.timeReigon)来构造一个具有时区的时间。

获取时间段:time2.Sub(time1)语句。输出一个时间段,得到其时分秒值。

利用time.Format("2006-01-02 15:04:05")来格式化一个时间字符串;同样地,可以用time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:29")语句来解析一个时间字符串。

可以利用now.Unix()来获取一个时间戳。