这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
作为基础比较差的学员,可以和大家一起学一门新的语言好像是非常幸运的事情呢。 事实上,半个小时的课程我边做笔记边看,看了好几个小时......
以下为go语言入门课程笔记
Golang入门
知识点内容
1.GO语言背景
Go语言优点(优点不少
- 高性能、高并发
- 语法简单,学习曲线平缓(与c相似,更加简化)
- 丰富的标准库,有良好的稳定性和安全性,基本不需要装载第三方库,并且可持续享受语言迭代的性能优化
- 完善的工具链
- 静态链接 运行只需要拷贝唯一可执行文件,简便
- 快速编译 编译速度快
- 跨平台 具有很高的适用性,可交叉编译
- 自带垃圾回收
使用Go的公司
字节、腾讯、美团、谷歌、facebook等
2.入门
开发环境安装
go.dev/ 需调整环境变量
配置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语言的实战。
- 经典的猜数字游戏,给一个随机数让用户去猜,告知用户所猜的大了或者小了,然后进行下一次猜数字。
- 通过Go语言发送http请求,解析json,实现一个在线词典。
- SOCKS5代理,实现了SOCKS5的各个握手流程。
总结
从目前的学习来看,Go总体的语法与c类似,学过c语言的来学Go会相对容易一些,不同的是Go语言比c简洁很多,感受比较深的是Go丰富的标准库,在使用时提供了很大的方便。在实现SOCKS5的过程,由于基础较差,看得很艰难......
一遍看下来,感觉会忘记的有很多......明天再看看,不过到时候打过几遍之后自然也就会用了,大家一起加油叭!!!