Go语言上手-基础语言 | 青训营笔记

130 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。

基础语法

该部分多看代码,这里只是简要介绍。

1.变量

声明方式: var a int var a = "string"

另一种特殊声明方式,使用变量名 冒号 等于号 值 :a := "string"

常量:把var换成const。在go中,常量没有确定的类型,会根据上下文自动确定类型。

2.if-else

与C++不同的地方,必须有花括号,没有小括号。

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

临时变量可以在if后面赋值。

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

3.循环

go中的循环只有for循环。

for {
   fmt.Println("loop")
   break
}
for j := 7; j < 9; j++ {
   fmt.Println(j)
}

4.switch

与C++类似。每个分支后面不用再写break。

switch a {
case 1:
   fmt.Println("one")
case 2:
   fmt.Println("two")
case 3:
   fmt.Println("three")
case 4, 5:
   fmt.Println("four or five")
default:
   fmt.Println("other")
}

与C++不同的地方, go中的switch用法更广泛。可以取代任何if-else结构,如下:

t := time.Now()
switch {
case t.Hour() < 12:
   fmt.Println("It's before noon")
default:
   fmt.Println("It's after noon")
}

5.数组

与C++类似。初始化方式如下:

var a [5]int
b := [5]int{1, 2, 3, 4, 5}

6.切片

切片不同于数组,可以任意改变长度,也有更多丰富的操作。如下代码:

其中,make用来创建切片,var slice []type = make([]type, len)或者slice := make([]type, len)

append用来追加元素,其用法与C++有所不同,可以一次追加多个,然后必须把append的结果赋给原数组。

和python一样,支持切片操作,不过不支持负索引。

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

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

c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]

fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5])  // [a b c d e]
fmt.Println(s[2:])  // [c d e f]

good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]

7.map

map是实际使用过程中使用最频繁的数据结构。go中的map与其他语言类似。

空map的创建:m := make(map[string]int)然后根据键值对插入数据。

带初始化的创建:m2 := map[string]int{"one": 1, "two": 2}

通过delete删除:delete(m, "one")

go的map是完全无序的,遍历时不按字典序也不按插入顺序,而是随机顺序。

8.range

range可以用来快速遍历。有两个返回值,一个是索引一个是对应的值。

nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
   sum += num
   if num == 2 {
      fmt.Println("index:", i, "num:", num) // index: 0 num: 2
   }
}

对于map来说,range返回值为,第一个是key,第二个是对应的value。

m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
   fmt.Println(k, v) // b 8; a A
}
for k := range m {
   fmt.Println("key", k) // key a; key b
}

9.函数

函数和C++很不同,首先前面通过func声明,然后函数名,括号中为参数,最后是返回结果类型。

其中,变量类型后置,可以有多个返回值。多个返回值时括起来。

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
}

10.指针

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)
   fmt.Println(n) // 7
}

11.结构体

构造形式如下:

type user struct {
   name     string
   password string
}

使用和C++一样。

特殊的构造题的方法和C++差别很大,go语言的结构体方法不在结构体内。

type user struct {
   name     string
   password string
}

func (u user) checkPassword(password string) bool {
   return u.password == password
}

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

构造体方法创建如上有两种方式。都是在函数func后面接上括号,括号里面是结构体名称。加不加星号的区别是,只有加了星号才能对结构体的属性进行修改

12.错误处理

go语言符合语言习惯的处理错误方式是使用一个单独的返回值来传递错误信息。

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

调用时

u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
   fmt.Println(err)
   return
}

13.字符串操作

字符串常见操作如下: Contains判断是否包含另一个字符串,

Count返回包含字符串的数量,等等。

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 中文一个字符长度不止为1

字符串格式化:

s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p)    // {1 2}

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

在go语言中可以用%v打印任何类型的变量,%+v可以打印详细的信息,%#v则更加详细。

14.JSON处理

go语言里面的JSON操作非常简单,对于已有的一个结构体,我们可以什么都不做,只要保证每个字段的第一个字母都是大写,也就是公开字段。就可以用json.Marshal()来序列化,变成一个JSON字符串。然后也可以通过json.Unmarshal()来反序列化。默认的序列化后JSON里面是大写字母开头,可以通过在结构体变量后面加上json:"name"来更改序列化后在JSON里面的名称。

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

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

15.时间处理

主要有,通过time.Now()来获取当前时间,也可以通过time.Date()来构造带时区的时间。通过.Unix()来获取时间戳。

.Year()获取年,其他月、日、时、分、秒同理。

.Format()来格式化输出时间的形式。

.Sub()用来对两个时间进行减法,得到一个时间段。时间段又可以通过对于函数去得到他有多少分钟多少秒,如.Minutes()获取有多少分钟(如此得到的会包含小数,比如这里面65分钟用.Hours()得到的不是1,而是1.083333333333)。

time.Parse()用来将字符串转化成go的time类型。第一个参数为格式,第二个参数为字符串。

now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
diff := t2.Sub(t)
fmt.Println(diff)                           // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
   panic(err)
}
fmt.Println(t3 == t)    // true
fmt.Println(now.Unix()) // 1648738080

16.数字解析

字符串和数字之间的转换。

f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111

n, _ = strconv.ParseInt("0x1000", 0, 64)
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

17.进程信息

通过os.Args来获取命令行参数,os.Getenv来获取环境变量。

// go run example/20-env/main.go a b c d
fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))

buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
   panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1       localhost

实战

猜谜游戏

很简单的一个小程序,通过一些简单if-elsefor循环来实现。

该实战部分主要是熟悉go语言,其中包括输入输出的编写、随机数的生成以及if-elsefor结构的简单运用。

func main() {
   maxNum := 100
   rand.Seed(time.Now().UnixNano())
   num := rand.Intn(maxNum)
   fmt.Println("The num is" , num )

   for  {
      fmt.Println("Please input your guess")

      var guess int
      _, err := fmt.Scanf("%d", &guess)

      if err != nil {
         log.Fatal(err)
      }

      if guess < num{
         fmt.Println("Your guess is smaller than the num")
      }else if guess > num{
         fmt.Println("Your guess is bigger than the num")
      }else{
         fmt.Println("You are right")
         break
      }
   }
}

在线字典

通过调用接口来实现一个在线字典。

这里用彩云翻译,课后可以用火山翻译也试试。(课后作业就是用另一个翻译网站完成,并且通过并行使用两个网站的方式提高效率)并行的方式在下一节内容

1.png

然后对该dict鼠标右键->Copy->Copy as cURL(bash)。将复制到的内容粘贴到git中,如下图:

git.png

再将得到的内容复制下来,粘贴到该网址。可以将curl转换成相应的语言。这里我们当然选go。

curlconverter.png

再将得到的内容复制粘贴到我们的goland里面去。生成的代码主要是构造了一个HTTP请求。这是一个Post请求,会用到http.NewRequest()函数,第一个参数是方法,第二个参数是URL,第三个参数是data。因为data可能很大,所以用了一个流来读,用strings.NewReader()将字符串转换成一个流。然后发送请求,接收响应。通过ioutil.ReadAll来读取整个流,得到所有响应。

daima.png

此时得到最基础的代码v1,此时直接运行代码就可以得到输出,能得到所有的响应。如图:

v1.png

但是此时不能自定义输入,我们可以用JSON序列化来自定义输入。代码如下,先构造一个与JSON结构相对应类型的结构体。先初始化内容,然后再通过json.Marshal()来得到序列化之后的字符串,最后由bytes.NewReader()将字符串转换成流。剩下的代码不用变。如此一来就优化到了第二个版本v2,能够自定义输入。

type DictRequest struct {
   TransType string `json:"trans_type"`
   Source    string `json:"source"`
   UserID    string `json:"user_id"`
}

func main() {
   client := &http.Client{}
   request := DictRequest{TransType: "en2zh", Source: "good"}
   buf, err := json.Marshal(request)
   if err != nil {
      log.Fatal(err)
   }
   var data = bytes.NewReader(buf)
   req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
   if err != nil {
      log.Fatal(err)
   }

现在我们只能输出所有的响应体,不符合我们的期望。我们想要的是将响应体里面有用的信息提取出来,比如音标,中文翻译之类的。而我们得到的是JSON,那么就可以和request一样进行处理,构造出对应的结构体之后通过反序列化将JSON转换成结构体,再将结构体中我们需要的部分输出。 但是,现在有一个很大的问题。那就是JSON里面的内容太复杂了,自己写不太现实 实在是不想再秃头了 。还好有工具可以帮忙解决,oktools.net/json2go 。将对应的JSON复制进去,就可以转换了。

oktools.png

然后将生成的结构体放到我们程序中去。

//结构体
type DictResponse struct {
   Rc   int `json:"rc"`
   Wiki struct {
      KnownInLaguages int `json:"known_in_laguages"`
      Description     struct {
         Source string      `json:"source"`
         Target interface{} `json:"target"`
      } `json:"description"`
      ID   string `json:"id"`
      Item struct {
         Source string `json:"source"`
         Target string `json:"target"`
      } `json:"item"`
      ImageURL  string `json:"image_url"`
      IsSubject string `json:"is_subject"`
      Sitelink  string `json:"sitelink"`
   } `json:"wiki"`
   Dictionary struct {
      Prons struct {
         EnUs string `json:"en-us"`
         En   string `json:"en"`
      } `json:"prons"`
      Explanations []string      `json:"explanations"`
      Synonym      []string      `json:"synonym"`
      Antonym      []string      `json:"antonym"`
      WqxExample   [][]string    `json:"wqx_example"`
      Entry        string        `json:"entry"`
      Type         string        `json:"type"`
      Related      []interface{} `json:"related"`
      Source       string        `json:"source"`
   } `json:"dictionary"`
}

再将v2的代码进行一些改动,得到响应体之后,用json.Unmarshal(bodyText, &dictResponse)将其反序列化。

defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
   log.Fatal(err)
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
   log.Fatal(err)
}
fmt.Printf("%#v\n", dictResponse)

然后再观察一下,将我们想要的内容对应结构体中的位置找出来进行输出。

fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
   fmt.Println(item)
}

最后再进行一些小优化,此时我们的v3版本代码已经基本完成了我们的需求。但是虽然输出是自定义的,我们还要改成从命令行输入才行,然后我们这里处理的主体部分是main函数。将其改成query函数,把要翻译的单词作为参数传进来。然后main函数从命令行读取数据,调用query函数。如下:

func query(word string) {
   client := &http.Client{}
   //var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)

   request := DictRequest{TransType: "en2zh" , Source: word}
   buf , err := json.Marshal(request)
   if err != nil {
      log.Fatal(err)
   }
   data := bytes.NewReader(buf)
   req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
   if err != nil {
      log.Fatal(err)
   }
//省略构造请求的内容
   fmt.Println(word,"UK:",dictResponse.Dictionary.Prons.En,"US:", dictResponse.Dictionary.Prons.EnUs)
   for _,item := range dictResponse.Dictionary.Explanations{
      fmt.Println(item)
   }
}

func main(){
   if len(os.Args) != 2{
      fmt.Fprint(os.Stderr,`usage:simpleDict WORD example: simpleDict hello`)
      os.Exit(1)
   }
   //fmt.Println(os.Args[0])
   word := os.Args[1]
   query(word)
}

最终效果如图:

image.png

SOCKS5代理

懒!!!!!!!!!!!!!!

略,写的慢,没时间写了。有时间单独发吧。

end !