初识Go语言 | 青训营笔记

101 阅读8分钟

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

本堂课重点内容总结

今天这堂课主要分为了2大部分:快速入门go语言基础语法和go语言3个实战案例

go语言是一门比较年轻的编程语言,它具有高性能、高并发、语法简单、学习曲线平缓(相对于Java,确实简洁很多)、丰富的标准库、完善的工具链、静态链接、快速编译、跨平台和垃圾回收等特点。

今天的课程主要有如下主要内容:

  • 选择IDE 
  • 学习基础语法 Hello World 
  • 变量 
  • 流程控制 
  • 数组,切片和映射 
  • 函数,指针,结构体与结构体方法 
  • Go 错误处理 
  • Go标准库 
  • Go 语言实战

配置开发环境

我选择了GoLand做为后续的开发工具,它是由 Jetbrains 公司开发的,由于我的主语言是Java,Jetbrains开发的IntelliJ IDEA的强大功能令我印象深刻,所以我选择了这款对于新手友好的开发工具。

值得一提的是,GoLand 是一款付费软件,在购买前,可以进行 30 天的免费使用;同时,作为一名在校大学生,可以向 Jetbrains 申请一份免费的教育许可证,其允许大学生在学业期间免费使用 Jetbrains 的全套工具链。

Go的基础语法

Hello World

导入fmt包在Go语言中主要用于格式化输入输出。fmt包中提供了很多函数,如Printf、Sprintf、Fprintf等,可以用来格式化输出字符串。

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

变量

变量可以通过显示或者隐式两种方式进行,后者采用频率更高一些。

package main
​
import (
    "fmt"
    "math"
)
​
func main() {
    //声明方式1
    var a = "initial"var b,c int = 1,2var d = truevar e float64//声明方式2
    f := float32(e)
​
    g := a + "foo"
    //Go中不允许变量不被使用,如果你定义了不使用会报错
    fmt.Println(a,b,c,d,e,f)//initial 1 2 true 0 0
    fmt.Printf(g)//initialfoo//Go中的常量没有特定的类型,而是根据使用的上下文来自动确定类型
    const s string = "2002XiaoYu" 
    const h = 500000000//5亿,8个0
    const i = 3e20/h
    fmt.Println(s,h,i,math.Sin(h),math.Sin(i))
  }

流程控制,选择语句,循环语句

这里各种语法使用差别不大,就不细说了。

数组,切片和映射

数组是定长的,因此在实际业务中使用的并不是很多,因此,更多情况下我们会使用切片代替数组。

就像它的名字一样,切片某个数组或集合的一部分,切片是可变容量的,其工作原理类似于 Java 的ArrayList,当切片容量不足时,便会自动扩容然后返回一个新的切片给我们。

需要注意的是,切片的长度容量是两个完全不同的东西,前者才是切片实际的长度,后者则是一个阈值,当切片长度达到该阈值时才会对切片进行扩容。

Go语言中的map是一种内置数据结构,用于存储键值对。键必须是可比较的数据类型(如字符串、整数等),值可以是任何数据类型。map可用于统计字符串中单词出现次数或维护用户信息数据库等任务。可以使用make函数创建新的map

  • 在其他语言中,他可能叫做哈希或者字典

  • map遍历时完全无序的,不会按照字母或者插入顺序

    package main ​ import "fmt" ​ func main() { var a [5]int a[4] = 520 fmt.Println(a[4], len(a)) //520 5 ​ b := [5]int{1, 2, 3, 4, 5} fmt.Println(b) //[1 2 3 4 5] ​ var twoD [2][3]int for i := 0; i < 2; i++ { for j := 0; j < 3; j++ { twoD[i][j] = i + j } } fmt.Println("2d:", twoD) //2d: [[0 1 2] [1 2 3]] }

    package main

    import "fmt"

    func main() { //使用make创建切片 s := make([]string, 3) s[0] = "a" s[1] = "b" s[2] = "c" fmt.Println("获取:", s[0])//获取: a fmt.Println("长度:", len(s))//长度: 3

    //切片可以使用append追加元素
    s = append(s, "d")
    s = append(s, "e", "f") //记得双引号不能打成单引号
    fmt.Println(s)//[a b c d e f]
    
    //使用make的时候也可以直接指定长度进行创建
    c := make([]string, len(s))
    copy(c, s) //使用copy拷贝数值
    fmt.Println(c)//[a b c d e f]
    
    fmt.Println(s[2:5])//[c d e] 取出第二个到第五个元素之前(不包括第五个元素,意思其实就是2<=x<5的范围)
    fmt.Println(s[:5])//[a b c d e] 取出5之前的元素,0 1 2 3 4
    fmt.Println(s[2:])//[c d e f] 取出第二个及以后的元素,除了下标0 1的元素不取,其他全要 
    
    good := []string{"好", "好", "学", "习"}
    fmt.Println(good)//[好 好 学 习]
    

    }

    package main

    import "fmt"

    func main() { m := make(map[string]int)//这将创建一个空map,其中键是字符串,值是整数 m["one"] = 1 //one是键,1是值 m["two"] = 2 fmt.Println(m) //map[one:1 two:2] fmt.Println(len(m)) //2 fmt.Println(m["one"]) //1 fmt.Println(m["unknow"]) //0

    r, ok := m["unknow"]     //使用索引运算符检索特定键的值
    fmt.Println(r, ok) //0 false
    
    delete(m, "one")         //使用delete函数从map中删除键值对
    
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    println(m2, m3) //0xc000143da0 0xc000143d70
    

    }

函数,指针,结构体与结构体方法

Go 语言支持指针操作,但默认情况下,指针必须指向一个合法对象,而不是一个可能不存在的内存地址,也不能使用指针进行地址运算。支持指针的 Go 也侧面印证了,默认情况下,Go 的方法传参均为传值,而不是传引用,如果不传入指针而直接传入一个值的话,则方法实参会被复制一份再传入,这里与Java的差别很大。

Go 不是一门面向对象的语言,因此,Go 并没有类(Class)或是其他类似概念,取而代之的,是同类语言中均拥有的结构体(Struct)

package main

import "fmt"
//Go跟其他语言很不一样的地方在于他的类型是后置的,别人是int a,在Go中是a int
func add(a int,b int) int  {//这是一个简单的加法函数,它接受两个整数并返回它们的和
	return a+b
}

func add2(a,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//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
}

package main

import "fmt"

func add2(n int) {//函数中的参数默认是传值调用,也就是说,函数会接收一个变量的副本,而不是变量本身
	n += 2//这是无效的
}

func add2ptr(n *int) {//而 add2ptr 函数中的参数是一个指针,它指向的是 main 函数中的 n 变量
	*n += 2
}

func main() {
	n := 5
	add2(n)//在 add2 函数中,对 n 变量的更改不会影响 main 函数中的 n 变量。
	fmt.Println(n)//5
	add2ptr(&n)//因此,在 add2ptr 函数中对 *n 的更改会影响到 main 函数中的 n 变量。需要加上取地址符&来获取变量的内存地址
	fmt.Println(n)//7
}
//如果你希望在函数中更改变量的值,需要使用指针

package main

import "fmt"
//这是一个简单的结构体定义。表示一个用户的账号跟密码
type user struct {
	name     string
	password string
}

func main() {
	//方式1:创建一个结构体变量需要使用结构体类型名称和对应的值
	a := user{name: "小余", password: "2002"}
    //也可以通过结构体字段的顺序来创建,我讲用户的name跟password省略掉了
	b := user{"大余", "1997"}
    //方式2:可以不全部赋值完,后面再通过点号(.)来访问进行赋值
	c := user{name: "小满"}
	c.password = "520520"
	//方式3:声明一个结构体变量而不赋值,后面再进行赋值操作。
	var d user
	d.name = "2002XiaoYu"
	d.password = "666"

	fmt.Println(a, b, c, d)//{小余 2002} {大余 1997} {小满 520520} {2002XiaoYu 666}
	fmt.Println(checkPassword(a, "haha"))//false
	fmt.Println(checkPassword2(&a, "haha"))//false
    
    //访问结构体字段使用点号(.)
    fmt.Println(a.name)//小余
}

//进行比较密码
func checkPassword(u user, password string) bool {
	return u.password == password
}

func checkPassword2(u *user, password string) bool {
	return u.password == password
}

Go 错误处理

在 Go 语言中,错误处理是一个重要的部分。Go 提供了一种简单的方法来处理错误,即将错误作为函数的返回值。在 Go 中,错误是一个内置的 error 类型,该类型实现了 error 接口。在错误发生时,函数可以返回一个 error 值。

package main

import (
	"errors"
	"fmt"
)

type user struct {
	name     string
	password string
}

//写上(v *user, err error),证明我们需要返回两个值,其中一个是错误信息
func findUser(users []user, name string) (v *user, err error) {
	for _, u := range users {
		if u.name == name {
			return &u, nil//没有错误则返回原本的结果和一个nil
		}
	}
	return nil, errors.New("not found")//如果要return,必须同时返回两个值
}

func main() {
	u, err := findUser([]user{{"小余", "6666"}}, "小余")//接收的时候也需要写两个变量
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(u.name) //小余,只有没有nil的时候才能取值,不然会发生错误

	if u, err := findUser([]user{{"小余", "6666"}}, "li"); err != nil {
		fmt.Println(err) //not found
		return
	} else {
		fmt.Println(u.name)
	}
}