
golang 基础学习
GO 语言基础学习
主要也是自己读书的一点笔记吧
0x01 前言
当时有一天在地铁上看微信读书的时候想起来:噢对我是时候写一点关于 Go 的笔记了,所以就有了这篇 Go 语言基础学习的文章。
本文笔记基于 《Go 语言入门经典》
0x02 环境搭建
用 Goland 搭建项目,每次创建项目之后,我们都需要对 Goland 进行一些配置,在 Goland 的右上方找到“Add Configuration”并单击。

选择 Directory,接着写 helloWorld 文件

package main
import "fmt"
func main() {
fmt.Println("Hello World")
}

0x03 GO 语言基础
1. 类型
字符串类型
package main
import "fmt"
func sayHello(s string) string {
return "Hello " + s
}
func main() {
fmt.Println(sayHello("Drunkbaby"))
}

int 类型
package main
import "fmt"
func addition(x int, y int) int {
return x+y
}
func main() {
fmt.Println(addition(2,4))
}
bool 类型
package main
import "fmt"
func main() {
var b bool
fmt.Println(b)
b = true
fmt.Println(b)
}
数值类型
int:平平无奇
float:氛围 float32 与 float64,一般用 float64 居多
定义如下
var f float64 = 0.111
类型转换
- 此处提及两个包,一个是 reflect,一个是 srtconv
package main
import (
"fmt"
"reflect" "strconv")
func main() {
var b bool = true
fmt.Println(reflect.TypeOf(b))
var s string = strconv.FormatBool(true)
fmt.Println(reflect.TypeOf(s))
}
2. 变量
字符串变量
package main
import "fmt"
func main() {
var s string = "Hello World"
fmt.Println(s)
}
快捷声明变量
package main
import "fmt"
func main() {
var (
s string = "Drunkbaby"
i int = 4
)
fmt.Println(s)
fmt.Println(i)
}
声明变量后,就不能再次声明它,不然会报错。
变量零值
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

编写简短变量
:=
符号是简短变量声明
package main
import "fmt"
func main() {
s := "Hello World"
fmt.Println(s)
}
简短变量声明是最常用的变量声明方式,还有一些比较简洁的变量声明
package main
import "fmt"
var s = "Hello World"
func main() {
i := 42
fmt.Println(s)
fmt.Println(i)
}
有关作用域
和其他语言是一样的,大括号作为作用域的分割,看代码。
package main
import "fmt"
var s2 = "Hello World"
func main() {
fmt.Printf("Print 's2' variable from outer block %v\n", s2)
b := true
if b {
fmt.Printf("Printing 'b' variable from outer block %v\n", b)
i := 42
if b != false {
fmt.Printf("Printing 'i' variable from outer block %v\n", i)
}
}
}
指针
package main
import "fmt"
func main() {
s := "Hello World"
fmt.Println(&s)
}
输出一个十六进制的内存地址

将变量作为值传递时,地址不变
package main
import "fmt"
func showMemoryAddress(x *int) {
fmt.Println(x)
return
}
func main() {
i := 1
fmt.Println(&i)
showMemoryAddress(&i)
}

如果要使用指针指向变量的值,而非内存地址,应当在指针变量前加上星号。
func showMemoryAddress(x *int) {
fmt.Println(*x)
return
}
声明常量
package main
import "fmt"
const greeting string = "Hello World"
func main() {
greeting = "Goodbye, fuck security"
fmt.Println(greeting)
}
const 常量无法被修改,所以会报错

3. 函数
返回多个值
package main
import "fmt"
func getPrize() (int, string) {
i := 2
s := "Drunkbaby"
return i, s
}
func main() {
quantity, prize := getPrize()
fmt.Printf("You won %v %v\n", quantity, prize)
}
不定参数函数
很有意思,是用三个点的,变量 numbers 是一个包含所有参数的切片。
代码如下
package main
import "fmt"
func sumNumbers(numbers... int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
result := sumNumbers(1, 2, 3, 4)
fmt.Printf("The result is %v\n", result)
}
具名返回值
按照声明顺序返回具体名称变量值。
package main
import "fmt"
func sayHi() (x, y string) {
x = "hello"
y = "world"
return
}
func main() {
fmt.Println(sayHi())
}
递归函数
package main
import "fmt"
func feedMe(portion int, eaten int) int {
eaten = portion + eaten
if eaten >= 5 {
fmt.Printf("I'm full! I've eaten %d\n", eaten)
return eaten
}
fmt.Printf("I'm still hungry! I've eaten %d\n", eaten)
return feedMe(portion, eaten)
}
func main() {
feedMe(1, 0)
}
将函数作为值传递
package main
import "fmt"
func anotherFunction(f func() string) string {
return f()
}
func main() {
fn := func() string {
return "function called"
}
fmt.Println(anotherFunction(fn))
}
4. 控制流程
if
package main
import "fmt"
func main() {
b := true
if b {
fmt.Println("b is true!")
}
}
else
package main
import "fmt"
func main() {
b := false
if b {
fmt.Println("b is true")
} else {
fmt.Println("b is false")
}
}
elseif
package main
import "fmt"
func main() {
i := 3
if i == 3 {
fmt.Println("i is 3")
} else if i == 2 {
fmt.Println("i is 2")
}
}
运算符
- 算术运算符

- 逻辑运算符

switch,可以说是 if 的高级用法了
package main
import "fmt"
func main() {
i := 2
switch i {
case 2:
fmt.Println("Two")
case 3:
fmt.Println("Three")
case 4:
fmt.Println("Four")
}
}
default in switch 语句
package main
import "fmt"
func main() {
s := "c"
switch s {
case "a":
fmt.Println("The letter is a!")
case "b":
fmt.Println("The letter is b!")
default:
fmt.Println("I don't recognize that letter")
}
}
for 循环
package main
import "fmt"
func main() {
i := 0
for i < 10 {
i++
fmt.Println("i is", i)
}
}
简化写法,也是最常规的写法
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Println("i is", i)
}
}
包含 range 子句的 for 语句
package main
import "fmt"
func main() {
numbers := []int{1,2,3,4}
for i, n := range numbers {
fmt.Println("The index of the loop is", i)
fmt.Println("The value from the array is", n)
}
}
defer 语句
defer 是一个很有用的 Go 语言功能,它能够让程序在函数返回前执行另一个函数。函数在遇到 return 语句或到达函数末尾的时候返回。defer 语句通常用于执行清理操作或确保操作。
package main
import "fmt"
func main() {
defer fmt.Println("I am run after the function completes")
fmt.Println("Hello World")
}
- 在执行完 main 函数后执行 defer 函数

5. 数组、切片和映射
数组的相关基础操作
package main
import "fmt"
func main() {
var cheeses [2]string
cheeses[0] = "Silly"
cheeses[1] = "Drunkbaby"
fmt.Println(cheeses[0])
fmt.Println(cheeses[1])
fmt.Println(cheeses)
}

声明数组的变量之后,就不能给它再添加元素了,比如此处我想 cheeses[2]="boy"
就会报错
切片
一些书面语比较难以理解,我们可以讲的简易一些,切片≈数组
切片是底层数组中的一个连续片段,,通过它可以访问该数组中一系列带编号的元素,因此,切片可以让我们访问数组的特定部分,比较像索引吧。
在切片中添加元素
其实我并不喜欢这种说法,更像是给数组增加元素。
用的方法是直接 append
,,很有意思,不像 Java 里面是 xxx.append(),可以同时添加多个元素
package main
import "fmt"
func main() {
var cheeses = make([]string, 2)
cheeses[0] = "Silly"
cheeses[1] = "Drunkbaby"
cheeses = append(cheeses, "Boy", "too", "silly")
fmt.Println(cheeses)
}
从切片中删除元素
也是用 append
package main
import "fmt"
func main() {
var cheeses = make([]string, 3)
cheeses[0] = "Silly"
cheeses[1] = "Drunkbaby"
cheeses[2] = "Baby"
fmt.Println(len(cheeses))
fmt.Println(cheeses)
cheeses = append(cheeses[:2], cheeses[2+1:]...)
fmt.Println(len(cheeses))
fmt.Println(cheeses)
}

切片元素复制
用的是 copy
package main
import "fmt"
func main() {
var cheeses = make([]string, 2)
cheeses[0] = "Silly"
cheeses[1] = "Drunkbaby"
var smeelyCheeses = make([]string, 2)
copy(smeelyCheeses, cheeses)
fmt.Println(smeelyCheeses)
}
映射
映射读起来比较别扭,比较像 Java 里面的 Map,也就是键值对
- 创建空映射
var players= make(map[string]int)
对应的放 key value 并不是用 put 方法,而是如此
players["cook"] = 32
players["bairstow"] = 27
players["stokes"] = 26
从映射中删除元素
package main
import "fmt"
func main() {
var players = make(map[string]int)
players["cook"] = 32
players["bairstow"] = 27
players["stokes"] = 26
delete(players, "cook")
fmt.Println(players)
}
6. 结构体与指针
一个基础的结构体
结构体与类差不多,概念方面不再赘述
package main
import "fmt"
type Movie struct {
Name string
Rating float32
}
func main() {
m := Movie{
Name: "Drunkbaby",
Rating: 10,
}
fmt.Println(m.Name, m.Rating)
}

“实例化一个 struct 实例”
- 和 new 一个 class 一样,比较简单
package main
import "fmt"
type Person struct {
Name string
Rating float32
}
func main() {
var m Person
fmt.Printf("%+v\n", m)
m.Name = "Drunkbaby"
m.Rating = 10
fmt.Printf("%+v\n", m)
}

“new 一个 struct 实例”
package main
import "fmt"
type Student struct {
Name string
Age float32
}
func main() {
m := new(Student)
m.Name = "Drunkbaby"
m.Age = 10
fmt.Printf("%+v\n", m)
}
嵌套结构体
结构体内包含结构体
package main
import "fmt"
type Superhero struct {
Name string
Age int
Address Address
}
type Address struct {
Number int
Street string
City string
}
func main() {
e := Superhero{
Name: "Batman",
Age: 32,
Address: Address{
Number: 1007,
Street: "Mountain Drive",
City: "Gotham",
},
}
fmt.Printf("%+v\n", e)
}

结构体的构造函数
- 简单。。。
package main
import "fmt"
type Alarm struct {
Time string
Sound bool
}
func NewAlarm(time string) Alarm {
a := Alarm{
Time: time,
Sound: true,
}
return a
}
func main() {
fmt.Printf("%+v\n", NewAlarm("07:00"))
}
结构体进行比较
这个挺有意思哈哈
package main
import "fmt"
type Drink struct {
Name string
Ice bool
}
func main() {
a := Drink{
Name: "Lemonade",
Ice: true,
}
b := Drink{
Name: "Lemonade",
Ice: true,
}
if a == b {
fmt.Println("a and b are the same")
}
fmt.Printf("%+v\n", a)
fmt.Printf("%+v\n", b)
}
检查结构体类型
在 go 里面用的是 reflect,挺有意思的,在 Java 里面这也被称作反射,不愧是反射,能够看到结构体是什么
fmt.Println(reflect.TypeOf(a))
fmt.Println(reflect.TypeOf(b))
区分指针引用和值引用
- 这就是老师特别喜欢讲的,变了一个,另外一个会不会变
先说结论
值引用,两个无关联
指针引用,一个变另外一个跟着变
值引用
package main
import "fmt"
type work struct {
Name string
isWork bool
}
func main() {
a := work{
Name: "Fire",
isWork: true,
}
b := a
b.isWork = false
fmt.Printf("%+v\n", b)
fmt.Printf("%+v\n", a)
fmt.Printf("%p\n", &a)
fmt.Printf("%p\n", &b)
}

指针引用
两个一起变
package main
import "fmt"
type Teacher struct {
Name string
isWorking bool
}
func main() {
a := Teacher{
Name: "Drunkbaby",
isWorking: true,
}
b := &a
b.isWorking = false
fmt.Printf("%+v\n", *b)
fmt.Printf("%+v\n", a)
fmt.Printf("%p\n", b)
fmt.Printf("%p\n", &a)
}

将指向的指针(而不是本身)赋给 ,这是使用和号字符表示的。由于 b 是指针,因此必须使用星号字符对其进行引用
7. 创建方法与接口
方法,也就是类的函数,和 Java 当中的 “方法”是一样的,那么在 Go 中需要如何定义呢?
比如我有一个类(结构体),代码如下
package main
type Movie struct {
Name string
Rating float64
}
func (m *Movie) summary() string {
return "1"
}
需要我们在方法名前,加入 m *struct
才可以
声明与调用方法
package main
import (
"fmt"
"strconv")
type Movie struct {
Name string
Rating float64
}
func (m *Movie) summary() string {
r := strconv.FormatFloat(m.Rating, 'f', 1, 64)
return m.Name + ", " + r
}
func main() {
m := Movie{
Name: "SpiderMan",
Rating: 3.2,
}
fmt.Println(m.summary())
}

当然一个类可以有多个方法
值传递与指针传递
- 方法传参,就是很平常。
package main
import "fmt"
type Triangle struct {
base float64
height float64
}
func (t Triangle) changeBase(f float64) {
t.base = f
return
}
func (t *Triangle) changeBasePointer(f float64) {
t.base = f
return
}
func main() {
t := Triangle{
base: 3,
height: 1,
}
t.changeBase(4)
fmt.Println(t.base)
t.changeBasePointer(4)
fmt.Println(t.base)
}
- 如果需要修改原始结构体,就选指针。如果不需要修改原始结构体,就使用值。
接口
固定格式
type name interface {
}
// 在里面可以定义方法
我的理解是,接口里面定义方法,然后让具体的类去实现,但是这个类的实现就很微妙。。。。。。。。。。可以说基本没什么关联。无语
package main
import (
"errors"
"fmt")
type Robot interface {
PowerOn() error
}
type T850 struct {
Name string
}
func (a *T850) PowerOn() error {
return nil
}
type R2D2 struct {
Broken bool
}
func (r R2D2) PowerOn() error {
if r.Broken {
return errors.New("R2D2 is broken")
} else {
return nil
}
}
func Boot(r Robot) error {
return r.PowerOn()
}
func main() {
t := T850{
Name: "The Terminator",
}
r := R2D2{
Broken: true,
}
err := Boot(&r)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Robot is powered on")
}
err = Boot(&t)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Robot is powered on")
}
}
8. 字符串
一些基础的操作就不讲了,直接看一些我认为有意思的地方
使用缓冲区(stringBuffer)拼接字符串
package main
import (
"bytes"
"fmt")
func main() {
var buffer bytes.Buffer
for i := 0; i < 500; i++ {
buffer.WriteString("z")
}
fmt.Println(buffer.String())
}
原理同 Java
进制表示

小写
使用 ToLower()
接口
查找
使用 strings.Index()
,如果找到,则输出第一个字串的索引开始;若找不到,则输出 -1
删除字符串中的空格
- 删除前后空格
package main
import (
"fmt"
"strings")
func main() {
fmt.Println(strings.TrimSpace(" I don't need all the space "))
}
9. Map 相关操作
Map 是 key-value 的键值对数据结构,类似于集合的意思
创建 map (但必须分配空间)
原本逻辑上声明 map,从 Java 的角度来说没什么问题
package main
import "fmt"
func main() {
var a map[int]string
fmt.Println(a)
}
但是这个程序,我们是不能进行赋值的,原因是并没有给此 map,也就是变量 a 分配内存空间,如果我们尝试赋值,如下语句,是会报错的
a[1]="Drunkbaby"

那么要如何才能分配空间并赋值呢?这里我们创建一个空的 map 需要用 make
,完整有效代码如下
package main
import "fmt"
func main() {
// 创建空 map,却没有分配空间
var a map[int]string
// 在使用 map 前,需要先 make,make 的作用就是给 map 分配数据空间
a = make(map[int]string, 10)
a[1] = "Drunkbaby;"
fmt.Println(a)
}

同样可以自己赋值 a[2] = "silly"
;
但是再写 a[1] = "quan9i"
,那么之前的 a[1] = "Drunkbaby"
会被覆盖
代码如下
package main
import "fmt"
func main() {
// 创建空 map,却没有分配空间
var a map[int]string
// 在使用 map 前,需要先 make,make 的作用就是给 map 分配数据空间
a = make(map[int]string, 10)
a[1] = "Drunkbaby;"
a[2] = "cute"
a[1] = "quan9i"
fmt.Println(a)
}
结果如图,覆盖了 a[1]
,且是有序的

一般声明方式比较多的是这样
var cities = make(map[string]string)
// 或者是
cities := make(map[string]string)
简单练习案例
package main
import "fmt"
// 练习案例
/*
课堂练习:演示一个 key-value 的 value 是 map 的案例
比如:我们要存放3个学生的信息,每个学生有 name 和 sex 信息
思路:map[string]map[string]string
*/
func main() {
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 2)
studentMap["stu01"]["name"] = "Drunkbaby"
studentMap["stu01"]["sex"] = "男"
studentMap["stu02"] = make(map[string]string, 2)
studentMap["stu02"]["name"] = "D1ng"
studentMap["stu02"]["sex"] = "女"
fmt.Println(studentMap["stu01"])
fmt.Println(studentMap["stu02"])
}
}

同样还可以这样取值
fmt.Println(studentMap["stu01"]["name"])
Map 的增删改查
增加和更新
根据 key 是否存在,如果 key 存在就是更新,不存在就是增加
package main
import "fmt"
func main() {
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "上海"
cities["no3"] = "广州"
fmt.Println(cities)
cities["no3"] = "上海 ~"
fmt.Println(cities)
}

删除
package main
import "fmt"
// 增加与更新
func main() {
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "上海"
cities["no3"] = "广州"
fmt.Println(cities)
// 演示删除
delete(cities, "no1")
fmt.Println(cities)
// 当 delete 指定的 key 不存在时,删除不会操作,也不会报错
delete(cities, "no4")
fmt.Println(cities)
}

- 删除细节说明
如果要删除 map 当中所有的 key,有如下两种方法
1、遍历所有的 key,逐一删除
2、直接 make 一个新的空间,让老空间被 GC
Map 查询
package main
import "fmt"
// 增加与更新
func main() {
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "上海"
cities["no3"] = "广州"
fmt.Println(cities)
key, flag := cities["no1"]
if flag {
fmt.Println("no1 的 key 为", key)
}
}

Map 的遍历
Map 的遍历不能简单的用 for 循环
package main
import "fmt"
// 增加与更新
func main() {
cities := make(map[string]string)
cities["no1"] = "北京"
cities["no2"] = "上海"
cities["no3"] = "广州"
for k, v := range cities {
fmt.Printf("k=%v v=%v\n", k, v)
}
}
和 python 基本上差不多
- 那如果要遍历复杂的 Map 呢?就像我们之前讲的那一个案例,Map 套 Map
package main
import "fmt"
// 遍历循环一个较为复杂的 Map
func main() {
studentMap := make(map[string]map[string]string)
studentMap["stu01"] = make(map[string]string, 2)
studentMap["stu01"]["name"] = "Drunkbaby"
studentMap["stu01"]["sex"] = "男"
studentMap["stu02"] = make(map[string]string, 2)
studentMap["stu02"]["name"] = "D1ng"
studentMap["stu02"]["sex"] = "女"
for k1, v1 := range studentMap {
fmt.Println(k1)
for k2, v2 := range v1 {
fmt.Printf("\t k2=%v v2=%v\n", k2, v2)
}
}
}

Map 的切片
还是比较重要的,但是确实很繁琐,最重要的是理解分配内存和 append 追加的思想
比如我们现在 make 一个长度为 2 的 Map,那么如果它满了之后,我们还需要追加数据,应该怎么操作呢?
答案是用 append()
函数,具体实现可以看代码,很好理解
package main
import "fmt"
// Map 切片
/*
要求:使用一个 Map 来记录 monster 的信息 name 和 age 也就是说一个 monster 对应一个 map 并且妖怪的个数可以动态增加 => Map 切片
*/
func main() {
// 1、声明一个 map 切片
monsters := make([]map[string]string, 2)
// 2、增加一个妖怪的信息
monsters[0] = make(map[string]string)
monsters[0]["name"] = "牛魔王"
monsters[0]["age"] = "500"
monsters[1] = make(map[string]string)
monsters[1]["name"] = "玉兔精"
monsters[1]["age"] = "400"
fmt.Println(monsters)
newMonsters := map[string]string{
"name": "append 追加的妖怪",
"age": "200",
}
monsters = append(monsters, newMonsters)
fmt.Println(monsters)
}

Map 排序
新版本会自动根据 key 排序
Map 使用小结
1、使用前需要 make
2、map 是引用类型,在一个函数接收 map 后,如果修改,则会修改源数据中的 map
3、map 的 value 一般是 struct,更适合管理复杂的数据
第三点可能比较难理解,不过简单想一想就明白了
- 本文标题:GO 语言基础学习
- 创建时间:2022-09-07 20:33:37
- 本文链接:2022/09/07/GO-语言基础学习/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!