课程笔记一 Golang入门 | 青训营笔记

134 阅读10分钟

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

作为基础比较差的学员,可以和大家一起学一门新的语言好像是非常幸运的事情呢。 事实上,半个小时的课程我边做笔记边看,看了好几个小时......

以下为go语言入门课程笔记

Golang入门

知识点内容

1.GO语言背景

Go语言优点(优点不少

  1. 高性能、高并发
  2. 语法简单,学习曲线平缓(与c相似,更加简化)
  3. 丰富的标准库,有良好的稳定性和安全性,基本不需要装载第三方库,并且可持续享受语言迭代的性能优化
  4. 完善的工具链
  5. 静态链接 运行只需要拷贝唯一可执行文件,简便
  6. 快速编译 编译速度快
  7. 跨平台 具有很高的适用性,可交叉编译
  8. 自带垃圾回收

使用Go的公司

字节、腾讯、美团、谷歌、facebook等

2.入门

开发环境安装

go.dev/ 需调整环境变量

studygolang.com/dl

代理goproxy.cn/

配置IDE

vscode可直接官网安装,需安装插件,具体可以看教程,

新人要注意的是,在代码文件夹中要新建一个go.mod

3.基础语法

以下会有部分解释写在注释的位置,方便观看

hello world

每门语言的第一个程序了属于是,Hello world!

package main //main包 入口

import( 
    "fmt" //标准库的fmt包
)

func main(){
    fmt.Println("hello world")
}

编译结果

go run hello.go

Hello world!

变量

变量声明:var、 := ,支持+运算符

var后若不加数据类型则进行自动识别, :=也是自动识别数据类型。

常量const 无确定类型,自动确定类型。

var a = "initial"
var b, c int = 1, 2
g := a + "foo"
const h = 500000000

条件语句 if else

if else基本与c语言相同

if num := 9; num < 0 {
    fmt.Println(num, "is negative")
} else if num < 10 {
    fmt.Println(num, "has 1 digit")
} else {
    fmt.Println(num, "has multiple digits")
}

与c不同的是,if后无需加括号,并且大括号不能写入下一行

循环

Golang只有唯一一种for循环,没有while与do while,省去了选择的麻烦

for j := 7; j < 9; j++ {

代码中三个条件皆可省略不写,若都不写代表一个死循环,在循环里continue与break正常使用。

同样不需要加括号

switch分支

在Golang里switch的功能较强,可以使用字符串、数字等各种数据类型,

甚至可取代任意if else:在switch后不加变量,在每个case写条件语句。

并且不需要在每一个case后面写入break来结束switch部分代码的执行,Golang中case识别到运行完对应语句后,会自动跳转到switch后的下一部分代码。

switch a {
case 1:
    fmt.Println("one")
case 2:
    ...

default:
    fmt.Println("other")
}


switch {
case x<10:
    fmt.Println("x<10")
default:
    ...
}    

数组

关于数组的使用与c语言差别不大,定义时需要写入数组类型,计数从0开始,并且可以直接输出整个数组。

以下是两种定义与输出

var a [5]int
a[0] = 100;

b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)

切片

是一种可变数组,比数组更加常用。切片容量不够时会自动进行扩容。

使用make创建,用append追加元素

在索引时可以使用冒号表示范围,切片操作,如2:5(从2到4,左闭右开), 2:(从2到结尾),:5(从开头到4,依旧是左闭右开)

左闭右开 左闭右开 左闭右开 左闭右开 左闭右开 左闭右开 左闭右开 左闭右开

s := make([]string,3)
s[0]="a"
s[1]="b"
s[2]="c"

s=append(s,"d") //[a,b,c,d]
s=append(s,"e","f") //[a,b,c,d,e,f]

c:= make ([]string,len(s))
copy(c,s) //赋值

s[2:5]

map

在其他语言中可能称为哈希或者字典,在Golang里面叫map,Go中map的遍历是无序的,随机的

可以用map创建一个空map,需要两个数据类型,第一个类型为key,第二个为value。 通过方括号进行写入配位对与读取配位对。用delete语句来删除配位对。

在读取配位值时,可以在定义变量时在变量后加上一个“,ok”来判定该key是否存在于map中

m := make(map[string]int) //两种数据类型
m["one"] = 1 //通过方括号写入配位对
map2 := make(map[string]int{"one":1,"two":2) //可以在创建时加入大括号来定义,用var同理
fmt.Println(m["one"]) //读取
fmt.Println(m["unknown"] //0

r, ok := m["unknown"]
fmt.Println(r, ok) 

delete(m,"one") //(map,key)

range

可以快速遍历slice或者map,使代码更加简洁

在遍历slice时,range得到两个值,一个是索引,一个是对应的值。

在遍历slice时,range得到两个值,一个是key,一个是value。

// 遍历slice时
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums { //range得到两个值,一个是索引,一个是对应的值。若不需要索引可以用下划线来忽略
    sum += num
 
// 遍历map时
m := map(m[string]string){"a":"A","b":"B"}
for k, v := range m{ //得到两个值,一个是key,一个是value
    fmt. Println(k, v)
}
for k := range m{ //只遍历key
}

函数

变量类型是后置的!!!! 在业务逻辑函数里,一般都会返回多个值,第一个值为真正的输出,第二个为错误信息,别忘了错误信息的输出!!!!!

如下

func add(a int, 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
}

指针

Golang支持指针,但相比起c/c++,Golang中指针的操作非常有限,一般是用来调整参数。

正常非指针的调用类型,在Go中应该是拷贝的数据,并不能影响到原本的数据,需要使用指针来改变, 类似“只读文件”与“可修改文件的区别”的区别

如下

func add2(n int) { //数据为拷贝,并不能改变原数
	n += 2
}

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

func main() {
	n := 5
	add2(n)
	fmt.Println(n) // 5
	add2ptr(&n) //与c相同的取地址操作
	fmt.Println(n) // 7
}

结构体

定义结构体

大体格式与c相同

type user struct{
    name      string
    password  string
}    

初始化

另外,初始化时可以只定义部分,未定义部分默认为0值。如下

a := user{name: "wang", password: "1024")
b := user{"wang","1024"}
c := user{name: "wang"} 

调用或写入 使用 结构体.成员 的方式写入或读取,对学过其他语言的来说应该是很熟悉了。

var d user
d.name = "wang"

另外结构体也可以作为函数参数进行使用,分为指针与非指针两种,指针型可以对结构体进行修改,并且防止一些对于大结构体拷贝的开销。

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
}

func main() {
	a := user{name: "wang", password: "1024"}
	a.resetPassword("2048") //调用

代码明显更加的简洁优雅了

错误处理

不同于Java 的异常,Golang的错误处理在函数的输出上,使用errors包。

在函数中进行错误判定,并多加一个error类型的输出,若没有错误,return原值与nil,若错误则return nil, errors.New("not found")可以根据输出很容易的判定错误地方与进行修改。

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

字符串操作

使用strings包。

课程中给出了许多的用例,但是只讲述了一部分的作用,以下是我补充的内容

strings.Contains(a, "ll")为是否包含后面的字符串

strings.Count(a, "l")为特定字符计数

strings.HasPrefix(a, "he")用来检测字符串是否以指定的前缀开头

strings.HasSuffix(a, "llo")用来检测字符串是否以指定的后缀结尾

strings.Index(a, "ll")寻找字符串中包含指定字符串的第一个实例索引,若没有则返回-1

strings.Join([]string{"he", "llo"}, "-")将字符串切片中的元素链接为单个字符串

strings.Repeat(a, 2)重复字符串a,后面为重复次数,返回的为一个字符串

strings.Replace(a, "e", "E", -1)对字符串的内容进行替换,Replace(s, old, new string, n int),n为替换的个数,若小于0为无限制次数

strings.Split("a-b-c", "-")删除字符串的指定内容

strings.ToLower(a)小写

strings.ToUpper(a)大写

注意,len()识别字符串是一个汉字的长度不为1

字符串格式化

标准库fmt中含有很多格式化的方法 如fmt.Println(),fmt.Printf.

与c语言不同的是,输出时只需使用%v,更加方便,使用%+v,%#v会更加详细。 可以用%.2f的方式来控制浮点数小数位。

	fmt.Printf("s=%v\n", s)  // s=hello
	fmt.Printf("n=%v\n", n)  // n=123
	fmt.Printf("p=%v\n", p)  // p={1 2}
	fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
	fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

	f := 3.141592653
	fmt.Println(f)          // 3.141592653
	fmt.Printf("%.2f\n", f) // 3.14

Json处理

使用encoding/json包,若结构体a的成员名开头都为大写,则可以使用buf, err := json.Marshal(a)对其进行序列化,此时buf是一个十六进制的数组序列,

使用string(buf)对其进行转化,或者用Unmarshal定义到一个空的结构体变量b里面。 err = json.Unmarshal(buf,&b)取b的地址,输出错误信息并对b进行操作。

可以在结构体中的成员类型后加上json:"age"之类的代码,改变最终输出时的开头字母大小写。

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

时间处理

使用time

now := time.Now()获取当前时间

t = time.Date(2022,3,27,1,25,36,0,time.UTC)构造一个时间

t.Year(),t.Month(),t.Day(),t.Hour(),t.Minute()可获取具体时间部分

diff := t2.Sub(t)可获取t2-t的时间段

t.Format("2006-01-02 15:04:05")格式化一个时间到一个时间字符串,注意时间格式,可以用time.Parse("2006-01-02 15:04:05")将时间字符串解析成时间,记得输出error。

now.Unix可获取一个时间戳。

字符串与数字的转化

使用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) //10为十进制
	fmt.Println(n) // 111

	n, _ = strconv.ParseInt("0x1000", 0, 64) //0为自动推测进制数
	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
}

进程信息

使用os包与os/exec

os.Args获取进程在执行的命令行参数

os.Getenv("PATH")获取环境变量

os.Setenv("AA","BB")写入环境变量

使用exec.Command("grep","127.0.0.1","/etc/hosts").CombinedOutput()启动进程并获取输入输出

3.Go语言实战

通过三个项目来进行Go语言的实战。

  1. 经典的猜数字游戏,给一个随机数让用户去猜,告知用户所猜的大了或者小了,然后进行下一次猜数字。
  2. 通过Go语言发送http请求,解析json,实现一个在线词典。
  3. SOCKS5代理,实现了SOCKS5的各个握手流程。

总结

从目前的学习来看,Go总体的语法与c类似,学过c语言的来学Go会相对容易一些,不同的是Go语言比c简洁很多,感受比较深的是Go丰富的标准库,在使用时提供了很大的方便。在实现SOCKS5的过程,由于基础较差,看得很艰难......

一遍看下来,感觉会忘记的有很多......明天再看看,不过到时候打过几遍之后自然也就会用了,大家一起加油叭!!!