Go语言语法基础 | 青训营笔记

81 阅读9分钟

这是我参与【第五届青训营】伴学笔记创作活动的第1天

Go语言语法基础

定义变量

Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。

变量声明:

  • var name type

  • 名字 := 表达式

    注意:当定义一个变量后又使用该符号初始化变量,就会产生编译错误

    //val := 1相等于
    var val int 
    val =1 
    

如果没有初始化,则变量默认为零值。

如果没有指定变量类型,可以通过变量的初始值来判断变量类型。

var d = true

多变量声明:

可以同时声明多个类型相同的变量(非全局变量)

var x, y int
var c, d int = 1, 2
g, h := 123, "hello"

关于全局变量的声明如下:var ( vname1 v_type1 vname2 v_type2 )

var ( 
    a int
    b bool
)

匿名变量:

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

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

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

指针

变量其实是一种使用方便的占位符,用于引用计算机内存地址。Go 语言中的的取地址符是&,放到一个变量前使用就会返回相应变量的内存地址。

指针变量其实就是用于存放某一个对象的内存地址。

指针声明和初始化

声明格式如下:var var_name var-type,其中的var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。

代码举例如下:

var n1 *int        /* 指向整型*/
var n2 *float32    /* 指向浮点型 */

指针的初始化就是取出相对应的变量地址对指针进行赋值,具体如下:

 var a int= 20   /* 声明实际变量 */
 var p *int     /* 声明指针变量 */
 p = &a         /* 指针变量的存储地址 */

空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil,也称为空指针。它概念上和其它语言的null、NULL一样,都指代零值或空值。

package main
​
import "fmt"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
}

数组

声明数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var variable_name [SIZE] variable_type
var nums [10] float32

初始化数组

  • 直接进行初始化:var nums= [5]float32{2.0, 3.4, 7.0, 50.0}

  • 通过字面量在声明数组的同时快速初始化数组:nums:= [5]float32{2.0, 3.4, 7.0, 50.0}

  • 数组长度不确定,编译器通过元素个数自行推断数组长度,在[ ]中填入...,

    举例如下:var nums= [...]float32{ 2.0, 3.4, 7.0, 50.0}和nums:= [...]float32{2.0, 3.4, 7.0, 50.0}

在c语言中我们知道数组名在本质上是数组中第一个元素的地址,而在go语言中,数组名仅仅表示整个数组,是一个完整的值,一个数组变量即是表示整个数组。在go中一个数组变量被赋值或者被传递的时候实际上就会复制整个数组。如果数组比较大的话,这种复制往往会占有很大的开销。所以为了避免这种开销,往往需要传递一个指向数组的指针,这个数组指针并不是数组。

数组指针

通过数组和指针的知识我们就可以定义一个数组指针

var a = [...]int{1, 2, 3} // a 是一个数组
var b = &a                // b 是指向数组的指针

数组指针除了可以防止数组作为参数传递的时候浪费空间,还可以利用其和for range来遍历数组

for i, v := range b {     // 通过数组指针迭代数组的元素
    fmt.Println(i, v)
}

结构体

声明结构体:

type struct_variable_type struct {
   member definition
   ...
   member definition
}

使用该结构体:

variable_name := structure_variable_type {value1, value2...valuen}
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

结构体指针:

定义一个结构体变量的语法:var struct_pointer *user。

使用结构体指针访问结构体成员仍然使用.操作符。格式如下:struct_pointer.name

实例代码:

package main

import "fmt"

type user struct {
	name     string
	password string
}

func main() {
    //声明方式1
	a := user{name: "wang", password: "1024"}
    //2
	b := user{"wang", "1024"}
    //3
	c := user{name: "wang"}
	c.password = "1024"
    //4
	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")
    fmt.Println(a)     //{wang 2048}
	fmt.Println(a.checkPassword("2048")) // true
}

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) checkPassword(password string) bool {
	return u.password == password
}

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

字符串

一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组

相关函数:

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
}

slice切片

类似python中的切片

定义

可以声明一个未指定大小的数组来定义切片。切片不需要说明长度。

var identifier []type

或使用 make() 函数来创建切片。

var slice []type = make([]type, len)
或
slice := make([]type, len)

添加元素

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

1.在切片尾部追加N个元素

var a []int
a = append(a, 1)               // 追加1个元素
a = append(a, 1, 2, 3)         // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

2.在切片开头位置添加元素

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

3.append和copy组合

a = append(a, 0)     // 切片扩展1个空间
copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
a[i] = x             // 设置新添加的元素
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2])   // c
fmt.Println("len:", len(s)) // 3

c := make([]string, len(s))
copy(c, s)  //复制一份s到c中

删除元素

1.从开头位置删除;

直接移动数据指针

a = []int{1, 2, 3, ...}
a = a[1:]                       // 删除开头1个元素(相当于截取第1个元素到最后一个元素的部分)
a = a[N:]                       // 删除开头N个元素

2.从中间位置删除; 对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用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个元素

3.从尾部删除;

a = []int{1, 2, 3, ...}
a = a[:len(a)-1]   // 删除尾部1个元素 (相当于截取第1个元素到第len(a)-1一个元素的部分)
a = a[:len(a)-N]   // 删除尾部N个元素

map

package main

import "fmt"

func main() {
    //定义1
	m := make(map[string]int)
    //定义2 
	m2 := map[string]int{"one": 1, "two": 2}
    //定义3
	var m3 = map[string]int{"one": 1, "two": 2}
    //插入数据
	m["one"] = 1
	m["two"] = 2
	
    //获取数据 ok用来判断该map中是否存在该key
	r, ok := m["unknow"]
	fmt.Println(r, ok) // 0 false

    //删除数据
	delete(m, "one")
    
	fmt.Println(m)           // map[one:1 two:2]
	fmt.Println(len(m))      // 2
	fmt.Println(m["one"])    // 1
	fmt.Println(m["unknow"]) // 0
	fmt.Println(m2, m3)    //map[one:1 two:2] map[one:1 two:2]
}

函数

具名函数:就和c语言中的普通函数意义相同,具有函数名、返回值以及函数参数的函数。

func fuction_name([parameter list])[return types]{
	函数体
}
func Add(a, b int) int {
    return a+b
}

匿名函数:指不需要定义函数名的一种函数实现方式,它由一个不带函数名的函数声明和函数体组成。

var Add = func(a, b int) int {
    return a+b
}
package main
​
import "fmt"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
}
​
func main() {
    res := add(1, 2)
    fmt.Println(res) // 3
​
    v, ok := exists(map[string]string{"a": "A"}, "a")
    fmt.Println(v, ok) // A True
}

json

json.Marshal(userInfo):将数据结构转换为json字符串

json.MarshalIndent(userInfo, "", "\t"):将数据结构转换为json字符串(打印出来比较好看)

json.Unmarshal(str, &stu):将json字符串转换为数据结构

package main
​
import (
    "encoding/json"
    "fmt"
)
​
type userInfo struct {
    Name  string
    Age   int `json:"age"`   //即把Age改为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))
    //{
    //  "Name": "wang",
    //  "age": 18,
    //  "Hobby": [
    //      "Golang",
    //      "TypeScript"
    //  ]
    //}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"}}
}

strconv

数字解析包

1.string转int

可以使用func Atoi(s string) (i int, err error),Atoi代表Ascii to Integer。

可以使用func ParseInt(s string, base int, bitSize int) (i int64, err error)

  • base为进制,值为2~36,如果为0,则会根据字符串自动判断,前置为"0x"的是16进制,前置为"0"的是8进制,其余的为10进制,
  • bitSize是返回的整数类型,0、8、16、32分别代表int、int8、int16、int32。

2.int转string

使用func Itoa(i int) string

3.string转bool

func ParseBool("TRUE")

4.string转float

func ParseFloat(s string, bitSize int) (f float64, err error)

5.float转string

func FormatFloat(f float64, fmt byte, prec, bitSize int) string

  • fmt表示格式,’f’(-ddd.dddd)、’b’(-ddddp±ddd,指数为二进制)、’e’(-d.dddde±dd,十进制指数)、’E’(-d.ddddE±dd,十进制指数)、’g’(指数很大时用’e’格式,否则’f’格式)、’G’(指数很大时用’E’格式,否则’f’格式);
  • prec表示控制的精度(排除指数部分):对’f’、’e’、’E’,它表示小数点后的数字个数;对’g’、’G’,它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
package main
​
import (
    "fmt"
    "strconv"
)
​
func main() {
    f, _ := strconv.ParseFloat("1.234", 64)  //string转float
    fmt.Println(f) // 1.234
​
    n, _ := strconv.ParseInt("111", 10, 64)  //string转int
    fmt.Println(n) // 111
​
    n, _ = strconv.ParseInt("0x1000", 0, 64)
    fmt.Println(n) // 4096
​
    n2, _ := strconv.Atoi("123")   //string转int
    fmt.Println(n2) // 123
​
    n2, err := strconv.Atoi("AAA")  //string转int
    fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
    
    s:=strconv.Itoa(1234)   //int转string
    fmt.Println(s)  //1234
    
    str:=strconv.FormatFloat(45.3210,'f',-1,64)  //float转string
    fmt.Println(str) //45.321
    
    str2:=strconv.FormatFloat(45.3210,'f',5,64) //float转string
    fmt.Println(str2)  //45.32100
    
    fmt.Println(strconv.ParseBool("t"))  //true <nil>
    fmt.Println(strconv.ParseBool("true")) //true <nil>
    fmt.Println(strconv.ParseBool("True")) //true <nil>
    fmt.Println(strconv.ParseBool("0")) //false <nil>
    fmt.Println(strconv.ParseBool("f")) //false <nil>
}

总结:

总体来说,Go语言对于有其他语言基础的同学来说上手不算困难,Go语言很像c语言+python的结合体,对基础的语法进行了解学习后,即可上手进行简单的开发。