Go语言入门指南 | 青训营

104 阅读7分钟

Go语言入门指南 | 青训营

一、代码结构

// 标识属于哪个包
package main

// 引入库文件
import (
	"fmt"
)

// 代码主体
func main() {
    ...
}

二、基础语法

1、变量申明

变量声明有两种方式:

func main(){
    var a = "hello"
    var b, c int = 1, 2
    f := float32(e)
}

如上述代码片段所示,go变量可以通过 var 关键词进行申明,我们可以在变量后声明该变量的类型,也可以选择让编译器自己识别。

我们还可以省略 var 关键字,直接通过 := 符号,来声明变量。

go和其他语言一样,也拥有常量类型,通过const字段声明:

const c = 123

// 还可以通过()将一组常量包含起来
const (
	PI	float64	= 3.1415926
    MAX	int	    = 1000
    ...
)

2、条件判断

go的条件判断语句和C/C++类似,唯一的区别是,if后面的条件语句不需要使用“()”。如:

if 7%2 == 0 {
    fmt.Println("7 is even")
} else {
    fmt.Println("7 is odd")
}

3、循环

go中尤其仅有for循环,示例如下:

// 没有任何条件判断的情况下,即为死循环
for {
    fmt.Println("hello world!")
}

// 类似于C/C++中的for使用,只是没有括号
for i := 1; i < 10; i++ {
    fmt.Println(i)
}

// 此处类似while的用法
for i <= 10 {
    fmt.Println(i)
    i = i + 1
}

在go的循环中,也保留了breakcontinue关键字的使用,用法和C/C++一样。

4、分支结构(switch)

switch a {
    case 1:
    	fmt.Println("one")
    case 2:
    	fmt.Println("two")
    default:
    fmt.Println("other")
}

go的switch拥有更加丰富的功能,甚至可以不写判断的变量,用来代替if-else。如:

switch {
    case a < b:
    	fmt.Println("a < b")
    ...
    default:
    	fmt.Println("...")
}

5、数组&切片

数组的定义方式:

var a [5]int

这样就定义了一个长度为5的int类型的数组a,其余的使用和C/C++类似。

但是由于数组长度固定,我们一般不太会使用它,我们会使用更加灵活的切片。

切片的使用比较复杂,这边只做一些基础的介绍:

// 使用make创建切片
s := make([]string, 3)
s[0] = "a" // 通过下标可以直接访问
s[1] = "b"
s[2] = "c"

// 通过append命令,追加元素
// 不过由于其底层实现方式的原因,append之后会获得一个新切片,通过如下方式,可以实现原切片元素的追加
s = append(s, "d")

c := make([]string, len(s))
copy(c, s)  // 通过copy函数,将s的数据拷贝到c中

这里稍微拓展一下切片的底层知识,方便大家去理解。

切片其实是一种数据结构,它存储了三个元素:指针,长度,容量。

  • 指针指向一个底层数组
  • 长度是当前切片存储的数据的长度
  • 容量是切片可以存储的最大长度

切片的另一种用法:

// 假设现在存在一个切片:s = [a, b, c, d, e, f]
fmt.Println(s[2:5])   // output: [c, d, e].  不包括第五个
fmt.Println(s[:5])    // output: [a, b, c, d, e]  从开头开始
fmt.Println(s[2:])    // output: [c, d, e, f]  一直到结尾

6、map

其他语言中,可能叫做hash、字典。

在日常开发中使用很广泛。

// 方括号内的为key的类型,方括号后的int为value类型
m := make(map[string]int)

// map存储键值对
m["one"] = 1

// 获取map的值
value, ok := m["one"]  // ok的值可以用来确定该key是否存在

// 删除map中的某个键值对
delete(m, "one")

map在索引的时候,是一种偏向随机索引的过程。

7、range

可以使用range快速遍历切片和map。代码更加简洁。

nums := []int{2,3,4}
// 对数组使用range时,会产生两个数据,第一个为索引,第二个为值
for i, num := range nums {
    ...
}

m := map[string]string{"a": "A", "b": "B"}
// 对map使用range时,返回两个值,一个是key,一个是value
for k, v := range m {
    ....
}

8、函数

go中函数的结构如下所示:

func 函数名(变量1 变量1类型, 变量2 变量2类型) 返回类型 {
    函数主体
} 

// 示例
func add(a int, b int) int {
    return a + b
}
// go的函数可以返回多个值
func exists(m map[string]string, k string) (v string, ok bool) {
    v, ok = m[k]
    return v, ok
}

9、指针

相比C/C++的指针,go的指针功能有限,主要用于对函数的参数进行修改。

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

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

func main() {
    n := 5
    add1(n)  // main中的n不会改变
    add2(&n) // 穿的参数需要增加取地址符,该函数会修改main中的n
}

10、结构体

结构体是带类型的字段的集合

// 此处定义一个结构体
type user struct {
    name	 string
    password string
}

// 创建结构体的若干种方法
a := user{name: "wang", password: "111"}
b := user{"wang", "111"}
c := user{name: "wang"}
c.password = "111"  // 通过.运算符操作结构体内元素

给结构体申明成员函数:

package main
import "fmt"

// 此处定义一个结构体
type user struct {
    name	 string
    password string
}

// 定义一个user的成员函数
func (u user) checkPassword(password string) bool {
    return u.password == password
}

// 主函数
func main() {
    a := user{"wang", "111"}
    fmt. Println(a.checkPassword("222"))  // false
}

11、错误处理

go中想要进行错误处理,需要导入“errors”包。

不同于java的异常捕获处理方式,go往往会在函数的返回值中增加一个error类型的值实现错误处理。代码如下:

package main

import {
    "fmt"
    "errors"
}

type user struct {
    name	 string
    password string
}

func  findUsers(users [user], name string) (v *user, err error) {
    for _, u := range users {
        if u.name == name {
            return &u, nil  // nil相当于java中的null
        }
    }
    return nil, errors.New("not found")
}

func main() {
    u, err := findUser([]user{{"wang", "111"}, {"zhang", "222"}}, "wang")
    if err != nil {
        ...  // 错误处理
    }
}

12、字符串操作

字符串的常用操作

a := "hello"
strings.Contains(a, "ll")       // 判断a中是否存在“ll”,此处发牛true
string.Count(a, "l")		    // 计算a中有多少个l,此处返回2
string.HasPrefix(a, "he")	    // 判断he是否为a的前缀,此处返回true
string.HasSuffix(a, "llo")		// 判断llo是否为a的后缀,此处返回true
string.Index(a, "ll")			// 计算ll在字符串中的位置,此处为2
string,Join([]string{"he", "llo"}, "-")  // he-llo
string.Repeat(a, 2)				// hellohello
string.Replace(a, "e", "E", -1)	// hEllo
string.Split("a-b-c", "-")		// [a b c]
string.ToLower(a)				// hello
string.ToUpper(a) 				// HELLO
len(a)							// 5

字符串的格式化,类似其他语言

import "fmt"

fmt.Println(a, b, c...)    	// 打印多个变量
fmt.Printf(....)			// 格式化打印
// %v 万能格式
//   %+v 更加详细
//   %#v 更更加详细
// %d 整型数字
// %s 字符串
// ...

13、JSON操作

需要引入包“encoding/json”

go可以对结构体形式的内容进行序列化,不过要求结构体对应的属性必须首字母大写。

package main

import (
	"fmt"
    "encoding/json"
)

type userInfo struct {
    Name	string
    Age		int		'json:"age"'  // 此处的tag是修改json之后的字段名称
    Hobby	[]string
}

func main() {
    a := userInfo{"gege", 18, []string{"sing", "jump", "rap"}}
    buf, err := json.Marshal(a)  // 通过json.Marshall函数实现序列化
    fmt.Println(string(buf))  	 // {"Name":'gege', "age":18, "Hobby":["sing", "jump", "rap"]}
   
    // 反序列化
    var b userInfo
    err = json.Unmarshal(buf, &b)
}

14、时间处理

需要导入包“time”

now := time.Now

// 获取某个详细字段
now.Year()
now.Month()
now.Day()
now.Hour()
now.Minute()

// 格式化
now.Format("2006-01-02 15:04:05")  // 不同于其他语言的YYYY MM……这边使用的是一个固定的时间,改时间也写在了官方文档上的

// 时间解析
t, err := time.Parse("2006-01-02 15:04:05","2023-07-01 01-02-03")

// 获取时间戳
now.Unix()

15、字符串和数字的转换

需要导入包“strconv”

f, _ := strconv.ParseFloag("1.234", 64)		// f = 1.234
// 10表示是10进制,如果传0,就自动推测;64表示64位
n, _ := strconv.ParseInt("111", 10, 64)		// n = 111;

n2, _ := strconv.Atoi("123")		// n2 = 123
s, _ := strconv.Itoa(123)			// s = "123"

16、进程信息

需要导入包 “os”,“os/exec”

fmt.Println(os.Args)				// 获取进程的参数信息
fmt.Println(os.Getenv("PATH"))		// 获取环境变量
fmt.Println(os.Setenv("AA","BB"))	// 设置环境变量

exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()	// 通过此命令快速的启动子进程,并获取其输入输出