初识Go语言 | 青训营笔记

100 阅读8分钟

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

说明

go语言给我的感觉是简洁版的c/c++语言。而我本人主语言就是c/c++所以我会通过对比的方式来学习Go语言。

一、基本语法

1.包的概念

可以在一个 Go语言源文件包声明语句之后,其它非导入声明语句之前,包含零到多个导入包声明语句。每个导入声明可以单独指定一个导入路径,也可以通过圆括号同时导入多个导入路径。要引用其他包的标识符,可以使用 import 关键字,导入的包名使用双引号包围,包名是从 GOPATH 开始计算的路径,使用/进行路径分隔。 类比于c/c++中的include不过其更具灵活性,能从开源社区中直接导入包,直接用,总而言之go语言的包管理十分成熟和友善,不像c/c++那般复杂。

2.变量和常量

变量的声明类型后置,也可以按类型推导。 三种方法如下:

//方法一:声明一个变量
var (
   b, c int = 1, 2
)
fmt.Println(b, c)
fmt.Printf("type c is %T ", c)

//方法二:自动推到类型
var e = 10
var f = "hello go"
fmt.Println(f)
fmt.Println(e)
fmt.Printf("type e is %T", e)

//方法三:只能用于声明函数内的局部变量 自动推导类型
d := 2
fmt.Println(d)
fmt.Print("hello world")

方法二和方法三即使是自动类型推导,其和py好像有点类似,但是go是强类型语言! 多变量声明:

//多变量声明
var i, g = 1, 2
fmt.Println(i, g)
//多行声明多变量
var (
   m = 1
   n = 1
)
fmt.Println(m, n)

多变量声明这点和c++有类似但是又不完全相同,多行声明是go的特点。 go语言中的常量关键字是const其一点作用就是可用作枚举:

const l = 10
fmt.Println(l)
const (
   //iota会逐渐递增
   BEIJING = iota * 3
   SHANGHAI
   TIANJING
)

3.函数

go语言中的函数,一开始用着我是十分的不习惯,因为是类型后置,但是类型后置确实是有一点逻辑在里面的,go语言的函数是可以返回多个值的。

package main
import "fmt"
func swap(x, y string) (string, string) {
   return y, x
}
func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}

函数的参数也跟c++一样有值传递,指针传递(引用传递),指针的概念和c/c++如出一辙没什么太大区别。不过这里需要注意一点,当指针作为函数参数的时候,其*是置于前面的:

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}

4.defer关键字

defer关键字用于预定对一个函数的调用,被其调用的函数可以称为延迟函数,一般用于释放占用的资源,捕捉处理异常,输出日志。如果一个函数中有多个defer他会类似于入栈的形式,后进先出。还有一个重点:defer的调用晚于函数中的return语句执行。

package main

import "fmt"

// return 先于 defer执行
// defer的调用方式类似与压栈 先进后出
func deferfunc() int {
   fmt.Println("this is defer")
   return 0
}
func returnfunc() int {
   fmt.Println("this is return")
   return 0
}
func test() int {
   defer deferfunc()
   return returnfunc()
}
// defer的执行顺序是一个压栈的顺序
func test2() {
   defer fmt.Println("1")
   defer fmt.Println("2")
   defer fmt.Println("3")
}

func main() {
   test()
   test2()
}

5.静态数组和动态数组slice

静态数组就是和c数组差不多,就是类型后置,slice是动态数组,可以动态的变换长度,可以类比于std::vector,下面是slice的基本使用(学过cpp应该都懂 切片作为函数传递的时候是引用传递 这句话是不准确的 他只是引用了底层的数组,长度len和容量cap都是拷贝):

```
package main

import "fmt"

func main() {
   //slice的声明方式
   //注意make返回的是引用类型本身
   a := []int{1, 2, 3, 4, 5}
   fmt.Printf("%v", a)
   var b []int
   fmt.Printf("%v", b)
   var c []int
   //分配三片空间
   c = make([]int, 3)
   fmt.Printf("%v", c)
   var d []int = make([]int, 3)
   fmt.Printf("%v", d)

   e := make([]int, 3)
   fmt.Printf("%v", e)

   //切片的追加
   //切片有容量和长度之分 长度是有效值的长度 容量是分配内存的大小 通过重复分配一片内存截取的话 使用copy函数
   f := make([]int, 3, 5)
   fmt.Printf("%d %d\n", len(f), cap(f))
   f = append(f, 2)
   f = append(f, 2)
   f = append(f, 2)
   f = append(f, 2)
   //此时元素的总量已经超过了长度 所以需要追加申请内存 申请内存的大小取决于设置的容量
   fmt.Printf("%d %d\n", len(f), cap(f))
   //这样子的话 长度和容量是相等的
   g := make([]int, 3)
   fmt.Printf("%d %d\n", len(g), cap(g))

   //切片的截取 切片的截取是获得其内部的指针 两块指向同一块内存空间 类似于c++中的string_view
   h := g[0:2] //左闭右开
   for key, value := range h {
      print(key, value, "\n")
   }

   println(cap(h), len(g))

}
```

6.结构体

go语言中的结构体,成员变量和初始化都和c++中的class差不多,其独特的点就是成员方法的声明和定义,还有继承和多态的差别:

package main

import (
   "fmt"
)

// go语言利用命名的大小写来指定类、类方法、类成员变量、普通函数的共有私有属性 其私有公有是针对其他的包的
type hero struct {
   id   int
   name string
   age  int
}

// 值传递
func changehero(h hero) {
   h.age = 18
}

// 引用传递
func changehero1(h *hero) {
   h.age = 22
}

type man struct {
   name string
   age  int
}

//类的成员方法的定义
//这种定义方式是一个副本
//func (this man) Set(name string, age int) man {
// this.name = name
// this.age = age
// return this
//}
//
//func (this man) Show() {
// fmt.Println("年龄是", this.age, "姓名是", this.name)
//}

// 这是指针传递的类方法
func (this *man) Set(name string, age int) *man {
   this.name = name
   this.age = age
   return this
}

func (this *man) Show() {
   fmt.Println("年龄是", this.age, "姓名是", this.name)
}

type Amnimal struct {
   len  int
   name string
}

// 面向对象的继承
func (this *Amnimal) test() {
   fmt.Print("hello world \n")
}

type Cat struct {
   //表明继承自animal
   Amnimal
}

// 可以重写父类的方法
func (this *Cat) test() {
   fmt.Print("hello world yes ! \n")
}

// 面向对象的多态
// 定义一个接口 其他类重写接口既可实现多态
type color interface {
   showColor()
}
type blue struct {
   color string
}

func (this *blue) showColor() {
   println(this.color)
}

type red struct {
   color string
}

func (this *red) showColor() {
   println(this.color)
}

func show(c color) {
   c.showColor()
}
func main() {

   //结构体的初始化
   //h: = hero{id: 1,name:"hero",age: 1}
   var h hero = hero{id: 0, name: "hero", age: 0}
   fmt.Printf("%v\n", h)
   changehero(h)
   fmt.Printf("%v\n", h)
   changehero1(&h)
   fmt.Printf("%v\n", h)

   m := man{}
   m.age = 10
   m1 := m.Set("王仁鑫", 18)
   m1.Show()
   m.age = 10
   m.Show()

   var cat Cat
   cat.len = 1
   cat.name = "hello"
   cat.test()

   b := blue{"蓝色"}
   r := red{color: "红色"}

   show(&b)
   show(&r)
}

二、常用模块

Json

json的数据格式十分常见,go语言中十分简洁的定义了其序列化和反序列化的方法:

package main

import (
   "encoding/json"
   "fmt"
)

// 字段名必须设为公有 不然其他包方法运行调用结构体是访问不到的
type userInfo struct {
   Name  string
   Id    int `json:"id"` //可以通过这种方式命名json转化后别名
   Hobby []string
}

func main() {
   a := userInfo{Name: "王仁鑫", Id: 1, Hobby: []string{"篮球", "唱", "跳"}}
   fmt.Println(a)
   //转成json格式
   buf, err := json.Marshal(a)
   //panic 和 recover理解为异常的抛出和捕捉
   if err != nil {
      panic(err)
   }
   //byte是对应的Ascll编码值
   fmt.Println(buf)
   //用字符串形式打包
   fmt.Println(string(buf))
   //每个元素都以新的缩进开始
   buf, err = json.MarshalIndent(a, "", "\t")
   if err != nil {
      panic(err)
   }
   fmt.Println(string(buf))

   //反序列化 将json数据反序列化为一个对象
   var b userInfo
   //传进去序列化的buf和对象的地址
   err = json.Unmarshal(buf, &b)
   fmt.Printf("%#v\n", b)
}

Time

一些日常调用时间的方法,获取时间段,时间点,时间戳等。

package main

import (
   "fmt"
   "time"
)
func main() {
   //获取当前的时间
   now := time.Now()
   fmt.Println(now)
   //构造一个时间点
   //年月日小时分钟秒钟 加时区
   t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local)
   t2 := time.Date(2022, 1, 2, 0, 0, 0, 0, time.Local)
   //可以依次获取时间的各个信息
   fmt.Println(t1)
   fmt.Println(t1.Date())
   fmt.Println(t1.Hour())
   fmt.Println(t1.Local())
   fmt.Println(t1.Second())
   //自定义格式化输出
   fmt.Println(t1.Format("2002-01-01 15:04:05"))
   //输出两个时间点的时间间隔
   diff := t2.Sub(t1)
   fmt.Println(diff)
   //解析字符串 前一个是模板 后一个是实际需要解析的内容
   //t3, err := time.Parse("2002-01-01 15:04:05", "2022-01-01 15:04:05")
   //if err != nil {
   // // panic(err)
   // //}
   // //fmt.Println(t3 == t1)
   //获取时间戳(格林威治时间到当前时间的总秒数)
   fmt.Println(now.Unix())
}

Number

对字符串和数字之间的转化。

package main

import (
   "fmt"
   "strconv"
)

func main() {
   //64代表64位精度的
   f, _ := strconv.ParseFloat("1.123", 64)
   fmt.Println(f)
   i, _ := strconv.ParseInt("1", 10, 64)
   fmt.Println(i)

   //直接转化为十进制的数字
   n2, _ := strconv.Atoi("13213")
   fmt.Println(n2)
   //如果字符串不合法
   n3, err := strconv.Atoi("aaa")
   if err != nil {
      panic("出大问题")
   }
   fmt.Println(n3)
}

三、项目

项目一猜谜游戏

重点:熟悉go语言的基本语法和一些模块的使用

项目二在线词典

重点:了解了go语言常见的几个工具以及go原生自带的http框架

  1. Json生成结构体oktools.net/json2go
  2. 利用curl生成http请求头部等curlconverter.com/go/

项目三socket5代理服务器

重点:深度了解了代理服务器协议socket5! socks协议的设计初衷是在保证网络隔离的情况下,提高部分人员的网络访问权限,但是对应网络安全来讲,更多的是利用socks5这个协议来访问到内部的网络,访问一些访问不到的资源,这也是对于网络攻防层面来讲,但是socks5的用途也是很多的。