0.环境安装与项目结构

学习课程:https://www.bilibili.com/video/BV1Xv411k7Xn/?spm_id_from=333.999.0.0

1.环境配置

1.Windows下环境配置

go编译器 http://golang.google.cn/ ,走流程安装后配置环境变量即可。

国内代理

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

2.linux下Golang的安装

tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz

export PATH=$PATH:/usr/local/go/bin
export PATH=$PATH:/usr/local/go/bin
source /etc/profile

2.项目管理

1.创建项目文件夹

Goprojects

[]bin //存放产生的二进制文件

[]pkg //存放产生的中间缓存的文件

[]src //存放源码
[][]crm //包
[][][] app.go //项目文件

创建环境变量

GOPATH = Goprojects(项目路径)
GOROOT = [Go 安装目录]

Path中增加

%GOROOT%\bin
%GOPATH%

配置完成后运行go env 确保配置正确
如果GOPATH修改失败,则管理员在terminal输入

setx GOPATH 'GOPATH的路径'

2.运行实例
在src内crm文件夹下创建项目文件
app.go

package main
import "fmt"

func main(){
fmt.Println("haha")
}

windows下

1. go run app.go //直接运行
2. go build //直接编译生成crm.exe
3. go install //编译将crm.exe生成到GOPATH/bin下

3.Golang设置
Goland 设置 -> 打开滚轮调节字符大小
Goland 设置 ->Editor Inlay Hints 关闭注释
对应项目时指定GOPATH

4.包的设定:
同一个文件夹(包)下的文件(.go)一定要写包名称且要一致
main包: main包一定要写个main函数->项目入口
非main包写在不同的文件夹中

包的导入:
同包内调用功能不用import
非同包调用功能要用import

文件中函数首字母是小写代表之能被本包调用
首字母是大写表示可以被任何包调用

5.在1.11版本后可采用gomodules进行包管理
关于gomodules:https://blog.csdn.net/qq_26857259/article/details/136042199
关于go.mod与gopath:https://blog.csdn.net/tlqwanttolearnit/article/details/131457222

3.包调用示例

采用go.mod
项目结构

Goprojects

[]bin //存放产生的二进制文件
[]pkg //存放产生的中间缓存的文件
[]go.mod //包管理
[]src //存放源码
[][]crm //包
[][][]app.go
[][]test
[][][]Aaa.go

app.go

package main  

import (
"fmt"
a "rev/src/test"
)

func main() {
fmt.Println("haha")
a.Testa()
}

Aaa.go

package res  

import "fmt"

func Testa() {
fmt.Printf("i am test")
}

go.mod

module rev  

go 1.19

1.第一个Go程序

GO是一门编译语言,编译运行成可执行文件过程中编译器能够捕获一些错误
GO编译器从main包main函数进入

package main
import (
"fmt"
)
//左花括号一定要和func在一行
//右花括号单独成行
func main(){
fmt.Println("Hello world")
}

Go注释

//单行注释
/*多行注释
*/

2.运算符与打印

1.运算符

1.加减乘除:+-*/
2.取余:%
3.赋值运算符:=
4.次方运算:math.Pow
5.自增运算符:没有++count 只有count++\count += 1,count– 同理

6.逻辑运算符
|| 或
&& 与存在短路逻辑
! 取反逻辑运算符

package main
import (
"fmt"
)
func main() {
var haveTorch = true
var litTorch = false

if !haveTorch || !litTorch {
fmt.Println("notion to see here")
}
}

2.打印

1.fmt.Print没有换行
fmt.Println有一个换行
两者都可以传入多个参数,用逗号或者加号隔开,逗号有空格,加号没有空格

package main

import (
"fmt"
)
func main() {
fmt.Print("My weight on the surface of Mars is ")
fmt.Print(149.0 * 0.3783)
fmt.Print(" lbs, and I would be ")
fmt.Print(18 * 365 / 687)
fmt.Print(" years old.")
//,输出一个空格
fmt.Println("My weight on the surface of Mars is", 149.0*0.3783, "lbs, and I would be", 18*365.2425/687, "years old.")
//+不含空格,只能拼接字符串
a := 123
fmt.Printf("Type %T for %[1]v\n",a)
}

2.格式化输出
可以使用Printf来控制打印的输出结果

package main

import (
"fmt"
)

func main() {
fmt.Printf("My weight on the surface of Mars is %v lbs,", 149.0*0.3783)
fmt.Printf(" and I would be %v years old.\n", 18*365/687)
fmt.Printf("My weight on the surface of %v is %v lbs.\n", "Earth", 149.0)
}

可以使用Printf对齐文本
%4v 相当于想做填充足够4个空格
正数向左填充,负数向右填充

package main

import (
"fmt"
)

func main() {
fmt.Printf("%-15v $%4v\n", "SpaceX", 94)
fmt.Printf("%-15v $%4v\n", "Virgin Galactic", 100)
}

常见格式

%v  - 打印值的默认格式
%T - 打印值的类型
%% - 打印%

%b - 二进制
%c - 该值对应的unicode
%d - 十进制
%o - 八进制
%x - 十六进制
%U - Unicod格式
%e - 科学计数法

%s - 字符串或者[]byte

%q - 该值对应的双引号括起来的go字符串字面值,必要时会安全转义

3.输入

fmt.Scan(&num)

package main
import "fmt"
func main() {
var num int
fmt.Scan(&num)
c := num % 10
b := (num / 10) % 10
a := num / 100
sum := a + b + c
fmt.Println("sum=", sum)
}

fmt.Scanf

package main

import "fmt"

func main() {
var ch rune
fmt.Scanf("%c", &ch)
switch {
case (ch >= 'a' && ch <= 'z'):
fmt.Printf("%c\n", ch-32)
case (ch >= 'A') && (ch <= 'Z'):
fmt.Printf("%c\n", ch+32)
case ch == ' ':
fmt.Println("SPACE")
case (ch >= '1') && (ch <= '9'):
fmt.Printf("%c\n", ch)
default:
fmt.Println("other")
}
}

3.常量与变量

1.变量常量

const 用来声明常量,常量的值不可以改变
var 用来声明变量,想要使用变量需要进行声明

package main

import (
"fmt"
)

func main() {
const lightSpeed = 299792 //km/s
var distance = 56000000 //km

fmt.Println(distance/lightSpeed, "seconds")

distance = 401000000
fmt.Println(distance/lightSpeed, "seconds")

}

同时声明多个变量

//几种声明形式
var distance = 56000000
var speed = 100800

var (
distance = 56000000
speed = 100800
)

var(
distance int
speed int
)
var distance,speed = 56000000, 100800
const hoursPerDay, minutesPerHour = 24,60

2.rand随机数

import “math/rand”

package main

import (
"fmt"
"math/rand"
)

func main() {
//rand.Intn(10):0-9
var num = rand.Intn(10) + 1
fmt.Println(num)

num = rand.Intn(10) + 1
fmt.Println(num)
}

4.变量及作用域

1.变量被声明以后就进入了作用域(变量就变得可见了)
只要变量在作用域内,你就可以访问它,否则就报错

2.作用域的好处:可以在不同的作用域内使用相同的变量名
Go作用域:{}

package main

import (
"fmt"
"math/rand"
)

func main() {
//count的作用域范围是main
var count = 0

for count < 10 {
//num作用域范围是每一次的for循环体
var num = rand.Intn(10) + 1
fmt.Println(num)
count++
}
}

3.短声明
短声明语句更短,而且可以在无法使用var的地方使用

var count = 0
for count = 10;count > 0;count--{
fmt.Println(count)
}

//使用短声明
for count:=10;count > 0;count-- {
fmt.Println(count)
}

4.package作用域:
era是在main函数外声明的,其拥有package作用域
如果main package有多个函数
那么era对它们都可见
短声明不可用来声明package作用域的变量

package main

import "fmt"

var era = "AD"

func main() {
year := 2018
fmt.Println(year)
fmt.Println(era)
}

5.类型只在其作用域中起作用

5.数据类型

1.浮点型

1.小数默认为float64
默认是float64占用8字节内存,叫做双精度
float32占用4字节内存,叫做单精度

days := 365.2425
var days = 365.2425
var days float64 = 365.2425

var answer float64 = 42
//指定单精度
var pi32 float32 = math.Pi

2.零值
声明变量不赋初始值,它的值就是零值

3.显示浮点类型
Printf结合%f格式化动词来指定显示小数的位数
​ 1.%f 宽度:会显示出的最少字符个数(包含小数点和小数)
​ 2.%5.2f 保留小数点后两位,整体长度为5,不够左边填充,
​ 小数点占一位,当宽度大于数字个数,左边填充空格
​ 3.%.3f保留小数点后三位
​ 精度:小数点后显示的位数

package main

import "fmt"

func main() {
third := 1.0 / 3
fmt.Println(third)
fmt.Printf("%v\n", third)
fmt.Printf("%f\n", third)
fmt.Printf("%.3f\n", third)
fmt.Printf("%5.2f\n", third)
//左边用0填充
fmt.Printf("%05.2f",third)
}

浮点类型的精度
浮点类型不适合用于金融类计算
为了尽量最小化舍入错误,建议先做乘法,再做除法
通过误差来判断相等

package main

import (
"fmt"
"math"
)

func main() {
piggyBank := 0.1
piggyBank += 0.2
fmt.Println(piggyBank == 0.3)
fmt.Println(math.Abs(piggyBank-0.3) < 0.0001)
}

2.整数

1.各种整形占用大小

int8 --1个字节
int16 --2个字节
int32 -- 4个字节
int64 ---8个字节
int
uint ---计算机决定字节数

最常用整数类型 int

var year int = 2018

最常用无符号整数类型 uint

var month uint = 2

int和uint 是针对目标设备优化的类型
超过20亿数据可以考虑用int64和 uint 64

2.uint8 可以用来表示8位的颜色

var red,green,blue uint8 = 0,141,213

有时候用uint8可以节省很多内存

十六进制

var red,green,blue = 0x00,0x8d,0xd5

打印十六进制%x

fmt.Printf("%x %x %x",red,green,blue)
//也可以指定最小宽度和填充
var red,green,blue uint8 = 0,141,213
fmt.Printf("color: #%02x%02x%02x;",red,green,blue)

3.整数环绕
所有整数都有一个取值范围,超出这个范围,就会产生环绕

var red uint8 = 255
red++
fmt.Println(red) //0

var number int8 = 127
number++
fmt.Println(number)//-128

4.打印每个bit—%b

var green uint8 = 3
fmt.Printf("%08b\n",green) //00000011
green++
fmt.Printf("%08b\n",green) //00000100

5.整数类型的最大最小值
与架构无关的整数类型
math.MaxInt16–16位最大值常量
math.MinInt64 –64位最小值常量
而int和uint可能是32位和64位的

如何避免整数环绕
应使用int64

package main

import (
"fmt"
"time"
)

func main() {
//第一个位置填写的是距离unix时间的秒数
future := time.Unix(126888321900, 0)
fmt.Println(future)
}

3.big(很大的数)

1.表示方法:
​ 1.使用浮点类型
​ 2.int64
​ 3.uint64
​ 4.使用big包
没有为指数形式的数值指定类型的话,那么Go会将它视作float64类型

package main

import (
"fmt"
)

func main() {
var distance = 24e18
fmt.Println(distance)
}

2.big包
对于较大的整数(超过10**18):big.Int
对于任意精度的浮点类型:big.Float
对于分数 big.Rat

NewInt()函数可以把int64转化为big.Int类型

package main

import (
"fmt"
"math/big"
)

func main() {
//使用NewInt必须要先将数据转换为int64
lightSpeed := big.NewInt(299792)
secondsPerDay := big.NewInt(86400)
fmt.Println(lightSpeed, secondsPerDay)
fmt.Printf("%T %T", lightSpeed, secondsPerDay)
}

适用于更大的数字

package main

import (
"fmt"
"math/big"
)

func main() {
distance := new(big.Int)
//第二个参数-十进制
distance.SetString("240000000000000000000000000000000000", 10)
fmt.Println(distance)
}

一旦使用big.Int 等式里其他的部分也必须用big.Int

new的用法

package main

import (
"fmt"
)

func main() {
distance := new(float64)
*distance = 1.22
fmt.Println(distance)
}

3.较大数的常量:
go里常量可以无类型,不会报错

const distance = 240000000000000

比较大的数可以作为常量直接使用

fmt.Println("Andromeda Galaxy is",2400000000000000/299792/86400,"light days away.")

无类型的字面值由big包支持
如果容纳的下,常量可以赋值给变量

4.多语言文本

1.声明字符串

peace := "peace"
var peace = "peace"
var peace string = "peace"

2.字符串的零值

var blank string

3.字符串的字面值可以包含转义字符如 \n
如果确实想要\n而不是换行,就用`来替代",这叫做做原始字符串字面值

fmt.Println("peace be upon you \n upon you be peace")
fmt.Println(`strings can multiple lines with \n escape sequence`)

4.可以给某变量赋不同的string值,但是string本身不可变

message = "shalom"
c =: message[5]
fmt.Printf("%c\n",c)
//不能改!如下情况会报错
//message[5] = 'd'

5.字符,code points,runs,byte
unicode 联盟为超过100万字符分配了相应的数值,该数值叫做code point,65代表A
为了表示unicode code point ,GO语言提供了rune 这个类型, rune是int32的一个类型别名
byte是uint8类型的别名,目的是用于二进制数据
byte可以表示由ASCii定义的英语字符

类型别名

type byte = uint8
type rune = int32

打印:
如果想打印字符而不是数值,可以使用%c

var pi rune = 960
var alpha rune = 940
var omega rune = 969
var bang byte = 33

fmt.Printf("%v %v %v %v\n",pi,alpha,omega,bang)
fmt.Printf("%c %c %c %c\n",pi,alpha,omega,bang)
//960 940 969 33
//π ά ω !

任何整数类型都可以用%c 打印,但是用rune表示该数值表示一个字符

6.字符 -‘A’
单引号括起来,没指定字符类型,GO会推断其为rune类型
字符字面值也可以用byte类型

var grade rune
grade := 'A'
var grade1 = 'A'
var grade2 rune = 'A'
//但是不是所有字符字面值都可以使用byte
var star byte = '*'

Caesar cipher 凯撒加密法
把每个字母向后移固定的位置

package main

import "fmt"

func main() {
c := 'y'
c = c + 3

if c > 'z' {
c = c - 26
}
fmt.Printf("%c", c)
}

7.len函数
len 返回byte数量 - 只适合英语

message := "usdjaklsdj alksjd lskaj das"
fmt.Println(len(message))

Go中字符串用UTF-8编码

8.若要支持其他类型语言
先解码成rune类型
使用utf-8.DecodeRuneInString函数会返回第一个字符以及字符所占的字节

package main

import (
"fmt"
"unicode/utf8"
)

func main() {
question := ">Como estas?"
fmt.Println(len(question), "bytes")
fmt.Println(utf8.RuneCountInString(question), "runes")

c, size := utf8.DecodeRuneInString(question)
fmt.Printf("First rune:%c %v bytes", c, size)
}

9.range关键字可以遍历各种集合

package main

import (
"fmt"
)

func main() {
question := ">Como estas?"
//按照字符索引,i按照字节索引,c是字符
for i, c := range question {
fmt.Printf("%v %c\n", i, c)
}
}


/* 弃位符
for _, c:= range question{
fmt.Printf("%c\n",c)
*/

5.类型转换

1.可以用+连接两个字符串

2.整形浮点型不能混用
整形转浮点型

age := 41
marsAge := float64(age)

浮点型转整形
小数点后面被截断,而非舍入

earthDays := 365.2425
fmt.Println(int(earthDays))

3.无符号有符号间也要转换

4.不同大小的整数类型之间也要转换
math提供max和min常量来判断,来判断是否超过最大值最小值

var bh float64 = 32768

if bh < math.MinInt16 || bh > math.MaxInt{
// handle out of range vlaue
}

5.字符串转换
string()

var pi rune= 960
var alpha rune = 940
var omega rune = 969
var bang byte = 33

fmt.Print(string(pi),string(alpha),string(omega),string(bang))
//πάω!

更为正确的 itoa - Integer to ascii

countdown := 111
str := "Launch in T minus" + strconv.Itoa(countdown) + "seconds."
fmt.Println(str)
//Launch in T minus111seconds.

最常用:

countdown := 9
str := fmt.Sprintf("Launch in T minus %v seconds.",countdown)
fmt.Println(str)

6.字符串转换为数字
可能会发生错误函数的处理
atoi-要转换的数字字符串太大,atoi函数可能会发生错误

package main

import (
"fmt"
"strconv"
)

func main() {
countdown, err := strconv.Atoi("1124123")
if err != nil {
//打印错误信息
fmt.Println(err.Error())
}
fmt.Println(countdown)
}

在Go 里不能把1和0当成true flase

6.函数与方法

1.函数

1.Go在标准库文档中列出了标准库每个包中声明的东西

func Intn (n int) int
num := rand.Intn(10)

Go中,大写字母开头的函数变量或其他标识符和其他标识符都会被导出,对其他包可用,小写字母不行

多个参数

func Unix(sec int64,nsec int64) Time
//调用
future := time.Unix(1267213123,0)
//如果多个形参类型相同可以只写一次
func Unix(sec,nsec int64) Time

2.返回多个值

func Atio(s string) (i int,err error)

3.声明可变参数

func Println(a ...interface{})(n int,err error)

…表示函数的参数的数量是可变的
a参数类型为interface{} 是一个空接口

4.函数示例

package main

import "fmt"

func kelvinback(k float64) float64 {
k -= 273.15
return k
}

func main() {
kelvin := 400.0
celsius := kelvinback(kelvin)
k := fmt.Sprintf("%v", kelvin)
c := fmt.Sprintf("%v", celsius)
fmt.Println(k + "`K is " + c + "`C")
}

2.方法

1.类型

函数和某个类型关联-方法

1.声明新类型

type celsius float64
var temperature celsius = 20

celsius 是一种全新的类型,由于它和float64具有相同的行为和表示,所以赋值操作能顺利执行

func main() {
type celsius float64
const degrees = 66
var temprature celsius = degrees
temprature += 10
fmt.Println(temprature)
}

不同类型无法混用:
错误示例!

var temperatue celsius = 20.0
var warmup float64 = 10
//两者无法相加,会报错
temperature += warmup

2.方法

1.通过方法添加行为
可以将方法与同包中声明的任何类型相关联,但不可以是int,float64等预声明的类型进行关联

type celsius float64
type kelvin float64
//函数
func kelvinToCelsius(k kelvin) celsius{
return celsius(k - 273.15)
}
//方法
func (k kelvin) celsius() celsius{
return celsius(k - 273.15)
}
//kelvin 与 celsius 关联

每个方法可以有多个参数,但是只能有一个接收者。
方法体中,接收者的行为和其他参数一样

func (接收器变量 接收器类型) MethodName(参数列表) (返回值列表){
}

3.方法调用
变量.方法()

package main

import "fmt"

func main() {
var k kelvin = 294.0
var c celsius

c = kelvinToCelsius(k)
c = k.celsius()
fmt.Println(c)
}

type kelvin float64
type celsius float64
//函数
func kelvinToCelsius(k kelvin) celsius {
return celsius(k - 273.14)
}

// 方法的关联
func (k kelvin) celsius() celsius {
return celsius(k - 273.15)
}

3.一等函数

1.函数赋值

头等函数,高阶函数
1.可以将函数赋给变量

type kelvin float64

func fakeSensor() kelvin {
return kelvin(rand.Intn(151)+150)
}

func realSensor() kelvin {
return 0
}

func main() {
sensor := fakeSensor
fmt.Println(sensor())

sensor = realSensor
fmt.Println(sensor())
//sensor类型为: func() kelvin
}

另一种声明形式

var sensor func() kelvin
sensor = kelvinTocelsius

带参数

package main

import (
"fmt"
)

type kelvin float64

func fakeSensor(n int64) kelvin {
return kelvin(n)
}

func realSensor(n int64) kelvin {
return kelvin(n)
}

func main() {
sensor := fakeSensor
fmt.Println(sensor(1))

sensor = realSensor
fmt.Println(sensor(2))
}

另一种写法

package main
import "fmt"

type kelvin float64
func fakeSensor(n int64) kelvin {
return kelvin(n)
}
func realSensor(n int64) kelvin {
return kelvin(n)
}
func main() {
var sensor func(n int64) kelvin
sensor = fakeSensor
fmt.Println(sensor(4))
}

2.将函数作为参数传递给函数

package main

import (
"fmt"
"math/rand"
"time"
)

type kelvin float64

func measureTemperature(samples int, sensor func() kelvin) {
for i := 0; i < samples; i++ {
k := sensor()
fmt.Println("%v`k\n", k)
time.Sleep(time.Second)
}
}

func fakeSensor() kelvin {
return kelvin(rand.Intn(151) + 150)
}

func main() {
measureTemperature(3, fakeSensor)
}

3.声明函数类型

type sensor func() kelvin

原来

funcmeasureTemperature(samples int,s func() kelvin)

可以精简为

func measureTemprature(samples int,s sensor)

2.匿名函数

匿名函数:没有名字的函数

package main

import "fmt"

var f = func() {
fmt.Println("dress up for the masquerade")
}

func main() {
f()
}

传参的匿名函数

package main

import "fmt"

func main() {
f := func(message string) {
fmt.Println(message)
}
f("hahahaa")
}

常用的另一种-立即执行的函数表达式

package main

import "fmt"

func main() {
func() {
fmt.Println("Function is here")
}()
}

3.函数闭包

匿名函数在Go里被称作函数字面值
其需要保留外部作用域的变量引用,函数字面值都闭包的
闭包:匿名函数封闭并包围作用域中的变量而得名的
闭包允许一个函数访问其外部作用域中的变量,即使在函数调用完成后,这些变量仍然保持其值。

package main
import "fmt"
type kelvin float64

func main() {
var k kelvin = 294.0
sensor := func() kelvin {
return k
}
//294
fmt.Println(sensor())
k++
//295
fmt.Println(sensor())
}

sensor捕获了外面的k,保持k不被释放,形成闭包

7.数组、切片、map

1.数组

1.1数组初始化、访问、遍历

1.数组长度固定且有序的元素集合

var planets [8]string

//使用复合字面值初始化数组
dwarfs := [5]string{"A","b","c","d","e"}

//go编译器会自动算出数组的元素数量
planets := [...]string{
"mercury",
"venus",
"earth",
}

2.访问数组元素从[0]开始

earth := planets[2]

3.数组的长度-len

fmt.Println(len(planets))

4.遍历数组

package main

import "fmt"

func main() {
dwarf := [...]string{
"A",
"b",
"C",
"d",
}
for i := 0; i < len(dwarf); i++ {
fmt.Println(i, dwarf[i])
}
}
//使用range遍历数组
for i, k := range dwarf {
fmt.Println(i, k)
}

1.2.数组的拷贝

1.数组的拷贝
最后两个数组不同

planetsMark := planets
planets[2] = "whoops"
fmt.Println(planets)
fmt.Println(planetsMark)

数组作为参数传给函数是通过拷贝,原数组不改变

package main
import "fmt"
func terraform(planets [8]string) {
for i := range planets {
planets[i] = "New" + planets[i]
}
fmt.Println(planets)
}
func main() {
planets := [8]string{
"mercury",
"mars",
"earth",
"uranus",
}
terraform(planets)
fmt.Println(planets)
}

作为参数传递时,数组长度要和形参数组长度一致
函数一般不适用数组作为参数,使用slice

1.3.二维数组

二维数组

var board [8][8]string

board[0][0] = "r"
board[0][7] = "r"

for column := range board[1] {
board[1][column] = "p"
}
fmt.Print(board)

2.切片

2.1.切片入门

slice - 指向数组的窗口
planets是一个数组, planets[0:4] 是一个切片,它切分出数组里前4个元素
半开区间

[0:4] 0,1,2,3
[6:8] 6,7
[:4] 0,1,2,3
[4:] 4,5-最后
[:] 所有元素

slice 索引不为负!
切片也适用于切字符串,索引的数字是字节数

slice声明

[]string

dwarf = []string{"Ceres","Ploto","Humea"}

示例:

package main

import (
"fmt"
"strings"
)

func hyperspace(worlds []string) {
for i := range worlds {
worlds[i] = strings.TrimSpace(worlds[i])
}
}
func main() {
planets := []string{" Veno ", " Earth ", " Mars"}
hyperspace(planets)
fmt.Println(strings.Join(planets, " "))
}

带有方法的slice 将slice或数组作为底层类型,然后绑定其他方法

package main

import (
"fmt"
"sort"
)


func main() {
planets := []string{
"Mecury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune",
}
sort.StringSlice(planets).Sort()
fmt.Println(planets)
}

2.2.更大的切片

1.append函数 - 可以将元素添加到slice中

func main() {
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dwarfs = append(dwarfs, "orcus")
fmt.Println(dwarfs)
}

2.长度和容量length&capacity
Slice 中元素的个数决定了slice的长度
容量 - 对应的底层数组的长度

len(slice) - 获取长度
cap(slice) - 获取底层数组长度

3.三个索引的切分操作

func dump(label string,slice []string){
fmt.Printf("%v:length%v,capacity %v %v\n",label,len(slice),cap(slice),slice)
}

func main(){
planets := []string{
"Mercury","Venus","Earth","Mars",
"Jupiter","Saturn","Uranus","Neptune",
}
//第一个是开始索引,第二个是结束索引,第三个是底层数组结束索引cap
terrestrial := planets[0:4:5]
worlds := append(terrestrial,"Ceres")
dump("planets", planets)
dump("terrestrial",terrestrial)
dump("worlds", worlds)
}
//planets:length8,capacity 8 [Mercury Venus Earth Mars Ceres Saturn Uranus Neptune]
//terrestrial:length4,capacity 5 [Mercury Venus Earth Mars]
//worlds:length5,capacity 5 [Mercury Venus Earth Mars Ceres]

使用make函数对slice进行预分配
当slice容量不足以进行append,Go必须创建新数组并且复制旧数组中的内容
一般情况新数组是原数组长度的两倍
但是使用make函数,可以对slice进行预分配策略
目的:尽量避免额外的内存分配和数组复制操作

func dump(label string,slice []string){
fmt.Printf("%v:length%v,capacity %v %v\n",label,len(slice),cap(slice),slice)
}

func main(){
//创建长度为0,容量为10的切片,刚开始10可以省略
dwarfs := make([]string,0,10)
dump("dwarfs",dwarfs)
dwarfs = append(dwarfs,"Ceres","Pluto","Haumea","Makemake","Eris")
dump("dwarfs",dwarfs)
}

声明可变参数的函数
声明Printf,append这样的可变参数函数,需要在函数的最后一个参数前面加上…符号

package main

import "fmt"

func terraform(prefix string, worlds ...string) []string {
newWorlds := make([]string, len(worlds))

for i := range worlds {
newWorlds[i] = prefix + " " + worlds[i]
}
return newWorlds
}

func main() {
twoWorlds := terraform("New", "Venus", "Mars")
fmt.Println(twoWorlds)

planets := []string{"Venus", "Mars", "Jupiter"}
newPlanets := terraform("New", planets...)
fmt.Println(newPlanets)
}

3.map

map-k/v 匹配
声明map

map [string] int
string: key
int: value

示例

package main

import "fmt"

func main() {
temperature := map[string]int{
"Earth": 15,
"Mars": -65,
}
temp := temperature["Earth"]
fmt.Printf("On average the Earth is %v C.\n", temp)
//增,改
temperature["Earth"] = 16
temperature["Venus"] = 464
//删
delete(temperature,"Venus")
fmt.Println(temperature)

moon := temperature["Moon"]
fmt.Println(moon)
}

逗号与ok写法

if moon, ok := temperature["Moon"]; ok {
fmt.Printf("On average the moon is %v C\n", moon)
} else {
fmt.Println("Where is the moon?")
}

map不会复制

package main
import "fmt"
func main(){
planets := map[string]string{
"Earth":"Sector ZZ9",
"Mars":"Sector ZZ9",
}
planetsMarkII := planets
planets["Earth"] = "whoops"

fmt.Println(planets)
fmt.Println(planetsMarkII)

delete(planets,"Earth")
fmt.Println(planetsMarkII)
}

使用make函数对map进行预分配
创建map时,make函数可以接受一个或者两个参数

//第二个参数为指定数量的key预先分配空间
temperature := make(map[float64]int,8)

使用map作为计数器

package main
import "fmt"

func main(){
temperatures := []float64{
-28.0,32.0,-31.0,-29.0,-23.0,-29.0,-28.0,-33.0,
}
frequency := make(map[float64]int)
for _,t := range temperatures{
frequency[t]++

//使用range遍历时,顺序需无法保证
for t,num := range frequency{
fmt.Println("%+.2f occurs %d times\n",t,num)
}
}

使用map和slice实现数据分组

package main
import "fmt"

func main(){
temperatures := []float64{
-28.0,32.0,-31.0,-29.0,-23.0,-29.0,-28.0,-33.0,
}
groups := make(map[float64][]float64)
for _,t := range temperatures{
//浮点型截断到整数
g := math.Trunc(t/10) * 10
groups[g] = append(groups[g],t)
}
//使用range遍历时,顺序需无法保证
for g,temperatures := range groups{
fmt.Printf("%v %v\n",g,temperatures)
}
}

将map用作set
元素不重复

package main

import "fmt"

func main() {
temperatures := []float64{
-28.0, 32.0, -31.0, -29.0, -23.0, -29.0, -28.0, -33.0,
}
set := make(map[float64]bool)
for _, t := range temperatures {
set[t] = true
}
//判断值是否出现
if set[-28.0] {
fmt.Println("set member")
}
fmt.Println(set)
}

map-排序

unique := make([]float64,0,len(set))
for t := range set {
unique = append(unique,t)
}
sort.Float64s(unique)
fmt.Println(unique)

8.结构体、接口

1.struct

struct可以将不同的类型的东西组合在一起

package main

import "fmt"

func main() {
var curiosity struct {
lat float64
long float64
}
curiosity.lat = -4.5895
curiosity.long = 127.4417

fmt.Println(curiosity.lat, curiosity.long)
fmt.Println(curiosity)
}

通过类型复用结构体

package main
import "fmt"


func distance(loc1,loc2 location) dis{
return dis{0.0,0.0}
}

type location struct {
lat float64
long float64
}

type dis struct {
lat float64
long float64
}

func main() {

var spirit location
spirit.lat = -4.5895
spirit.long = 127.4417

var opportunity location
opportunity.lat = -1.9462
opportunity.long = 354.4734

fmt.Println(spirit, opportunity)
}

通过复化字面值初始化struct- 常用

package main
import "fmt"
func main(){
type location struct{
lat,long float64
}
opportunity := location{lat:-1.9462,long:354.4734}
fmt.Println(opportunity)
insight := location(lat:4.5,long:135.9)
fmt.Println(insight)
//按字段顺序进行初始化
spirit := location{-14.5684,175.472636}
}

struct的打印

import "fmt"

func main() {
type location struct {
lat, long float64
}
curiosity := location{-4.589, 123.4417}
//直接打印值
fmt.Printf("%v\n", curiosity)
//打印时带上字段
fmt.Printf("%+v", curiosity)
}

struct 的复制

赋值完全复制,互相独立

package main

import "fmt"
func main() {
type location struct {
lat, long float64
}
curiosity := location{-4.589, 123.4417}

binary := curiosity
cc := binary

cc.lat = 114514
fmt.Println(cc, curiosity)
}

多字段用struct类型组成slice

package main
import "fmt"
func main() {
type location struct {
name string
lat float64
long float64
}

locations := []location{
{name: "Bradury", lat: -4.5898, long: 137.4417},
{name: "Columbia Memorial Station", lat: -14.5684, long: 175.4726},
{name: "Challenger", lat: -1.9462, long: 354.4734},
}
fmt.Println(locations)
}

将struct编码为JSON

JSON - JavaScript 对象表示法

常用于web api

json 包中的Marshal 函数可以将struct 中的数据转化为JSON格式

package main

import (
"encoding/json"
"fmt"
"os"
)

func main() {
type location struct {
//!!字段必须是大写字母才会被json导出
Lat, Long float64
}
curiosity := location{-4.5895, 137.4417}
//成功err空,失败err不为空
bytes, err := json.Marshal(curiosity)
exitOnError(err)

fmt.Println(string(bytes))
}
func exitOnError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

使用struct 标签来自定义JSON

Go语言中的json包要求struct中的字段必须以大写字母开头,类似驼峰命名法

snake_case 蛇形命名规范

可以为字段注明标签,使得json包在进行编码时能够按照标签样式修改字段名

package main
import (
"encoding/json"
"fmt"
"os"
)

func main() {
type location struct {
//!!字段必须是大写字母才会被json导出,如果是xml则也在这加标签
Lat float64 `json:"latitude"xml:"latitude"`
Long float64 `json:"longitude"`
}
curiosity := location{-4.5895, 137.4417}
//成功err空,失败err不为空
bytes, err := json.Marshal(curiosity)
exitOnError(err)

fmt.Println(string(bytes))
}
func exitOnError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

2.struct+方法

将方法关联到声明的结构体类型上

package main
import "fmt"

type coordinate struct {
d, m, s float64
h rune
}

// 算十进制°
func (c coordinate) decimal() float64 {
sign := 1.0
switch c.h {
case 'S', 'W', 's', 'w':
sign = -1
}
return sign * (c.d + c.m/60 + c.s/3600)
}

func main() {
lat := coordinate{4, 35, 22.2, 'S'}
long := coordinate{127, 26, 30.12, 'E'}
fmt.Println(lat.decimal(), long.decimal())
}

构造函数
可以使用struct复合字面值来初始化需要的数据
如果struct初始化的时候还要做很多事情,那就考虑写个构造用的函数
通常new+类型名->构造函数

package main

import "fmt"

type coordinate struct {
d, m, s float64
h rune
}

// 算十进制°
func (c coordinate) decimal() float64 {
sign := 1.0
switch c.h {
case 'S', 'W', 's', 'w':
sign = -1
}
return sign * (c.d + c.m/60 + c.s/3600)
}

type location struct {
lat, long float64
}

// 构造用的函数
//通常new+类型名->构造函数
func newLocation(lat, long coordinate) location {
return location{lat.decimal(), long.decimal()}
}

func main() {
lat := coordinate{4, 35, 22.2, 'S'}
long := coordinate{127, 26, 30.12, 'E'}
fmt.Println(lat.decimal(), long.decimal())
fmt.Println(newLocation(lat, long))
}

New函数
有些构造函数的名字就叫New()
Go调用函数形式为包名.函数名,例如errors.New()

class替代方案
使用struct 并配备几个方法也可以达到同样的效果
计算火星两点距离

package main

import (
"fmt"
"math"
)

type location struct {
lat, long float64
}

type world struct {
radius float64
}

func (w world) distance(p1, p2 location) float64 {
s1, c1 := math.Sincos(rad(p1.lat))
s2, c2 := math.Sincos(rad(p2.lat))
clong := math.Cos(rad(p1.long - p2.long))
return w.radius * math.Acos(s1*s2+c1*c2*clong)
}

func rad(deg float64) float64 {
return deg * math.Pi / 180
}

func main() {
var mars = world{radius: 3389.5}
spirit := location{-14.5684, 175.4726}
opportunity := location{-1.9462, 354.4734}

dist := mars.distance(spirit, opportunity)
fmt.Printf("%.2f km\n", dist)
}

3.组合与转发

Go中通过结构体实现组合
继承能实现的组合都能实现,更为灵活

package main

import "fmt"

type report struct {
sol int
temperature temperature
location location
}

type temperature struct {
high, low celsius
}

type location struct {
lat, long float64
}

type celsius float64

func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func (r report) average() celsius {
return r.temperature.average()
}
func main() {
bradbury := location{-4.5895, 137.4417}
t := temperature{high: -1.0, low: -78.0}
fmt.Println(t.average())

report := report{
sol: 15,
temperature: t,
location: bradbury,
}
fmt.Println(report.temperature.average())
fmt.Printf("%+v\n", report)
fmt.Printf("a balmy %v° C\n", report.temperature.high)
}

struct嵌入实现方法的转发
temperature 嵌入到struct中,temperature 的方法可以给report用
只给定字段类型,不给定字段名称

type sol int
type report struct{
sol
temperature
location
}
func (s sol) days(s2 sol) int{
days := int(s2-s)
if days<0{
days = -days
}
return days
}

func main(){
report := report{sol:15}
fmt.Println(report.sol.days(1446))
fmt.Println(report.days(1446))
}

struct 可以转发任意类型

命名冲突

func (s sol) days(s2 sol) int{
days := int(s2-s)
if days<0{
days = -days
}
return days
}

func (l location) days(12 location) int {
return 5
}
func main(){
report := report{sol:5}
//会报错
fmt.Println(report.days(1446))
//不会报错
fmt.Println(report.sol.days(1446))
}

如果有

func (r report) days(s2 sol) int {
return r.sol.days(s2)
}

fmt.PrintlN(report.days(1446))

会自动调用上述函数,不会报错
自身方法优先级要更高

4.接口

接口关注于类型可以做什么
接口通过列举类型必须满足的一组方法来进行声明
在Go语言中,不需要显式声明接口

package main

import (
"fmt"
"strings"
)

var t interface {
talk() string
}

type martian struct{}

func (m martian) talk() string {
return "nack nack"
}

type laser int

func (l laser) talk() string {
return strings.Repeat("pew", int(l))
}

func main() {
t = martian{}
fmt.Println(t.talk())

t = laser(3)
fmt.Println(t.talk())
}

为了复用,通常把接口声明为类型
按约定,接口名称通常以er结尾

type talker interface {
talk() string
}

func shout(t talker){
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}

凡是满足talker接口的类型都可以使用shout,
martian和laser都可以使用

func main(){
shout(martian{})
shout(laser(2))
}

方法的转发

package main

import (
"fmt"
"strings"
)

var t interface {
talk() string
}

type laser int

func (l laser) talk() string {
return strings.Repeat("pew", int(l))
}

func shout(t talker){
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}

type starship struct {
laser
}

func main() {
s := starship{laser(3)}
fmt.Println(s.talk())
shout(s)
}

Go语言的接口都是隐式实现的

package main

import (
"fmt"
"time"
)

type stardater interface {
YearDay() int
Hour() int
}

// time.Time 有两个方法,恰好实现了接口
// 将time.Time改成stardater,不仅time.Time可以满足,其他满组接口的内容都可以满足
func stardate(t stardater) float64 {
doy := float64(t.YearDay())
h := float64(t.YearDay())
return 1000 + doy + h
}

func main() {
day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
fmt.Printf("%.1f Curiosity has landed \n", stardate(day))
}

fmt 包中声明Stringer接口

package main
import "fmt"

type location struct {
lat, long float64
}

func (l location) String() string {
return fmt.Sprintf("%v,%v", l.lat, l.long)
}

func main() {
curiosity := location{-4.5895, 137.4417}
fmt.Println(curiosity)
}

9.指针

1.指针1

指正是指向另一个变量地址的变量
Go中不会出现迷途指帧

1.&与*
变量会将他们存储在RAM中,存储位置时该变量的内存地址

package main

import (
"fmt"
)

func main() {
answer := 42
//&地址操作符
fmt.Println(&answer)
}

&无法获得字符串/数值/布尔字面值的地址

*用来解引用

package main

import (
"fmt"
)

func main() {
answer := 42
fmt.Println(&answer)
address := &answer
fmt.Println(*address)
}

Go语言没有address++

2.指针类型
*int/*string

package main

import "fmt"

func main() {
canada := "Canada"
var home *string
fmt.Printf("home is a %T\n", home)
home = &canada
fmt.Println(*home)
}

3.指针用来指向

package main

import "fmt"

func main() {
var administrator *string
scolese := "Christopher J. Scolese"
administrator = &scolese
fmt.Println(*administrator)

bolden := "Charles F. Bolden"
administrator = &bolden
fmt.Println(*administrator)

bolden = "Charles Frank Bolden Jr."
fmt.Println(*administrator)
}

4.指向结构的指针
结构体的复合字面值可以前面放置&符号
访问字段时进行解引用不是必须的

 package main

import "fmt"

func main() {
type person struct {
name, superpower string
age int
}
timmy := &person{
name: "Timothy",
age: 10,
}

timmy.superpower = "flying"
(*timmy).name = "xiaoming"
fmt.Printf("%+v\n", timmy)
}

5.指向数组的指针
&可以放在数组复合字面值前面来创建指向数组的指针
数组在执行索引或切片操作时会自动解引用
Go里面数组和指针是两种完全独立的类型
Slice 和 map 的符合字面值前面也可以放置 & 操作符,但是Go 并没有为他们提供自动解引用的功能

package main

import "fmt"
func main() {
superpowers := &[3]string{"flight", "invisibility", "super strength"}
fmt.Println(superpowers[0])
fmt.Println(superpowers[1:2])
}

2.指针2

1.函数与指针
函数传参是参数副本,但是用指针变量指向同一个内存地址
函数方法借助内存地址修改值

package main

import "fmt"

type person struct {
name, superpower string
age int
}

func birthday(p *person) {
p.age++
}

func main() {
rebecca := person{
name: "Rebecca",
superpower: "imagination",
age: 14,
}

birthday(&rebecca)
fmt.Printf("%+v", rebecca)
}

2.方法与指针
方法的接收者与方法的参数在处理指针方面很相似

package main

import "fmt"

type person struct {
name, superpower string
age int
}

func (p *person) birtyday() {
p.age++
}

func main() {
terry := person{
name: "terry",
age: 15,
}
terry.birtyday()
fmt.Printf("%+v", terry)
}

方法用.标记符调用时会自动使用&符号取内存地址

import "fmt"

type person struct {
name, superpower string
age int
}

func (p *person) birtyday() {
p.age++
}

func main() {
nathan := person{
name: "nathon",
age: 14,
}
nathan.birtyday()
fmt.Printf("%+v", nathan)
}

一种类型的某些方法需要用到指针作为接收者,这种类型的所有方法都应用指针作为接收者

3.内部指针
它用于确定结构体中指定字段的内存地址
&不仅可以获得结构体的内存地址,还可以获取结构体中某些字段的内存地址

package main

import "fmt"

type stats struct {
level int
endurance, health int
}

func levelUp(s *stats) {
s.level++
s.endurance = 42 + (14 * s.level)
s.health = 5 * s.endurance
}

type character struct {
name string
stats stats
}

func main() {
player := character{name: "Matthias"}
levelUp(&player.stats)
fmt.Printf("%+v", player)
}

4.修改数组
函数通过指针对数组的元素进行修改

package main

import "fmt"

func reset(board *[8][8]rune) {
board[0][0] = 'r'
}

func main() {
var board [8][8]rune
reset(&board)

fmt.Printf("%c", board[0][0])
}

5.隐式指针
map在赋值或者作为参数传递时不会被复制
map是一种隐式的指针,map的键和值都可以是指针类型

6.slice指向数组
slice是指向数组的窗口,slice指向数组元素的时候也用了指针
每个slice内部都会被表示为一个包含3个元素的结构,他们分别指向1.数组的指针2.slice的容量3.slice的长度
当slice被直接传递至函数或方法时,slice的内部指针就可以对底层数据进行修改
指向slice的显示指针的唯一作用就是修改slice本身,slice的长度,容量以及起始偏移量

package main

import "fmt"

func reclassify(planets *[]string) {
//对切片本身进行修改
*planets = (*planets)[0:8]
}

func main() {
planets := []string{
"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn", "Uranus",
"Neptune", "Pluto",
}
reclassify(&planets)
fmt.Println(planets)
}

7.指针和接口

package main

import (
"fmt"
"strings"
)

type talker interface {
talk() string
}

func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}

type martian struct{}

func (m martian) talk() string {
return "nack nack"
}

func main() {
shout(martian{})
shout(&martian{})
}

如果方法使用的是指针接收者

package main

import (
"fmt"
"strings"
)

type talker interface {
talk() string
}

func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}

type laser int

func (l *laser) talk() string {
return strings.Repeat("pew", int(*l))
}

func main() {
pew := laser(3)
shout(&pew)
}

10.nil与报错处理

1.nil

nil 表示无或者零
在Go 里,nil是一个零值
如果一个指针没有明确的指向,它的值就是nil
其他的slice,map和接口的零值也是nil
nil会导致panic,程序崩溃

1.保护方法

package main

import (
"fmt"
)

type person struct {
age int
}

func (p *person) birthday() {
//保护方法
if p = nil{
return
}
p.age++
}

func main() {
var nobody *person
fmt.Println(nobody)
//nobody 是nil,在传入方法后p.age++报错
nobody.birthday()
}

即使接收者为nil,也会继续调用方法

2.nil函数值
当变量被声明为函数类型,其默认值是nil

package main

import "fmt"

func main() {
var fn func(a, b int) int
fmt.Println(fn == nil)
}

检查函数值是否为nil,并在有需要的时候提供默认行为(最佳实现)

package main

import (
"fmt"
"sort"
)

func sortStrings(s []string, less func(i, j int) bool) {
if less == nil {
less = func(i, j int) bool { return s[i] < s[j] }
}
sort.Slice(s, less)
}

func main() {
food := []string{"onion", "carrot", "celery"}
sortStrings(food, nil)
fmt.Println(food)
}

3.nil slice
如果slice在声明之后没有使用符合字面值或内置的make函数进行初始化,其值为nil
range/len/append等内置函数都可以正常处理值为nil的slice

package main

import "fmt"

func main() {
var soup []string
fmt.Println(soup == nil)

for _, ingredient := range soup {
fmt.Println(ingredient)
}

fmt.Println(len(soup))
soup = append(soup, "union", "hi")
fmt.Println(soup)
}

空slice和值为nil的slice并不相等,但它们通常可以替换使用

4.nil map
如果map在声明之后没有使用符合字面值或内置的make函数进行初始化,其值为nil

package main

import "fmt"

func main() {
var soup map[string]int
fmt.Println(soup == nil)

measurement, ok := soup["onion"]
if ok {
fmt.Println(measurement)
}
for ingredient, measurement := range soup {
fmt.Println(ingredient, measurement)
}
}

5.nil接口
声明接口类型未被赋值时,其值为nil
对于一个未被赋值的接口变量来说,它的接口类型和值都是nil,并且变量本身也等于nil

package main

import "fmt"

func main() {
var v interface{}
fmt.Printf("%T %v %v \n", v, v, v == nil)
}

当接口类型被赋值以后,接口就会在内部指向该变量的类型和值

package main

import "fmt"

func main() {
var v interface{}
fmt.Printf("%T %v %v \n", v, v, v == nil)
var p *int
v = p
fmt.Printf("%T %v %v \n", v, v, v == nil)
}

Go中接口类型的变量只有类型和值都为nil时才等于nil
检验接口的内部表示

fmt.Printf("%#v\n",v)

6.nil之外的另一个选择

package main

import "fmt"

type number struct {
value int
valid bool
}

func newNumber(v int) number {
return number{value: v, valid: true}
}

func (n number) String() string {
if !n.valid {
return "not set"
}
return fmt.Sprintf("%d", n.value)
}

func main() {
n := newNumber(42)
fmt.Println(n)

e := number{}
fmt.Println(e)
}

2.错误处理

函数在返回错误时,函数最后边的返回值用来表示错误
调用函数后立即检查是否发生错误
没有发生错误,返回的错误值为nil

package main

import (
"fmt"
"io/ioutil"
"os"
)

func main() {
files, err := ioutil.ReadDir("sad")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
for _, file := range files {
fmt.Println(file.Name())
}
}

2.优化错误处理
1.将程序中不会出错的部分和包含潜在错误的部分隔离开来
2.对于不得不返回错误的代码,应尽力简化相应的错误处理代码

3.文件写入

package main

import (
"fmt"
"os"
)

func proverbs(name string) error {
f, err := os.Create(name)
if err != nil {
return err
}
_, err = fmt.Fprintln(f, "Errors are values.")
if err != nil {
f.Close()
return err
}

_, err = fmt.Fprintln(f, "Don't just check errors, handle them gracefully.")
f.Close()
return err

}

func main() {
err := proverbs("proverbs.txt")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

4.defer关键字
使用defer关键字,Go可以确保所有deferred的动作可以在函数返回前执行

func proverbs(name string) error {
f, err := os.Create(name)
if err != nil {
return err
}
//写一次即可
defer f.Close()

_, err = fmt.Fprintln(f, "Errors are values.")
if err != nil {
return err
}

_, err = fmt.Fprintln(f, "Don't just check errors, handle them gracefully.")
return err
}

可以defer任意的函数和方法
defer消除必须时刻惦记执行资源释放的负担

5.有创意的错误处理

package main

import (
"fmt"
"io"
"os"
)

type safeWriter struct {
w io.Writer
err error
}

func (sw *safeWriter) writeln(s string) {
if sw.err != nil {
return
}

_, sw.err = fmt.Fprintln(sw.w, s)
}

func proverbs(name string) error {
f, err := os.Create(name)
if err != nil {
return err
}
defer f.Close()

sw := safeWriter{w: f}
sw.writeln("asdasd")

}

6.New error
errors 包里有一个构造用New函数,它接收string 作为参数用来表示错误信息,该函数返回error类型

参考文章:

学习课程:https://www.bilibili.com/video/BV1Xv411k7Xn/?spm_id_from=333.999.0.0
https://blog.csdn.net/qq_31387691/article/details/109169252
https://blog.csdn.net/weixin_37717557/article/details/102873556
https://blog.csdn.net/hitpter/article/details/134450368