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中增加
配置完成后运行go env 确保配置正确 如果GOPATH修改失败,则管理员在terminal输入
2.运行实例 在src内crm文件夹下创建项目文件 app.go
package mainimport "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
1.第一个Go程序 GO是一门编译语言,编译运行成可执行文件过程中编译器能够捕获一些错误 GO编译器从main包main函数进入
package mainimport ( "fmt" ) func main () { fmt.Println("Hello world" ) }
Go注释
2.运算符与打印 1.运算符 1.加减乘除:+-*/ 2.取余:% 3.赋值运算符:= 4.次方运算:math.Pow 5.自增运算符:没有++count 只有count++\count += 1,count– 同理
6.逻辑运算符 || 或 && 与存在短路逻辑 ! 取反逻辑运算符
package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport "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 mainimport "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 mainimport ( "fmt" ) func main () { const lightSpeed = 299792 var distance = 56000000 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 mainimport ( "fmt" "math/rand" ) func main () { var num = rand.Intn(10 ) + 1 fmt.Println(num) num = rand.Intn(10 ) + 1 fmt.Println(num) }
4.变量及作用域 1.变量被声明以后就进入了作用域(变量就变得可见了) 只要变量在作用域内,你就可以访问它,否则就报错
2.作用域的好处:可以在不同的作用域内使用相同的变量名 Go作用域:{}
package mainimport ( "fmt" "math/rand" ) func main () { var count = 0 for count < 10 { 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 mainimport "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 mainimport "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) fmt.Printf("%05.2f" ,third) }
浮点类型的精度 浮点类型不适合用于金融类计算 为了尽量最小化舍入错误,建议先做乘法,再做除法 通过误差来判断相等
package mainimport ( "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
最常用无符号整数类型 uint
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) var number int8 = 127 number++ fmt.Println(number)
4.打印每个bit—%b
var green uint8 = 3 fmt.Printf("%08b\n" ,green) green++ fmt.Printf("%08b\n" ,green)
5.整数类型的最大最小值 与架构无关的整数类型 math.MaxInt16–16位最大值常量 math.MinInt64 –64位最小值常量 而int和uint可能是32位和64位的
如何避免整数环绕 应使用int64
package mainimport ( "fmt" "time" ) func main () { future := time.Unix(126888321900 , 0 ) fmt.Println(future) }
3.big(很大的数) 1.表示方法: 1.使用浮点类型 2.int64 3.uint64 4.使用big包 没有为指数形式的数值指定类型的话,那么Go会将它视作float64类型
package mainimport ( "fmt" ) func main () { var distance = 24e18 fmt.Println(distance) }
2.big包 对于较大的整数(超过10**18):big.Int 对于任意精度的浮点类型:big.Float 对于分数 big.Rat
NewInt()函数可以把int64转化为big.Int类型
package mainimport ( "fmt" "math/big" ) func main () { lightSpeed := big.NewInt(299792 ) secondsPerDay := big.NewInt(86400 ) fmt.Println(lightSpeed, secondsPerDay) fmt.Printf("%T %T" , lightSpeed, secondsPerDay) }
适用于更大的数字
package mainimport ( "fmt" "math/big" ) func main () { distance := new (big.Int) distance.SetString("240000000000000000000000000000000000" , 10 ) fmt.Println(distance) }
一旦使用big.Int 等式里其他的部分也必须用big.Int
new的用法
package mainimport ( "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.字符串的零值
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)
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)
任何整数类型都可以用%c 打印,但是用rune表示该数值表示一个字符
6.字符 -‘A’ 单引号括起来,没指定字符类型,GO会推断其为rune类型 字符字面值也可以用byte类型
var grade rune grade := 'A' var grade1 = 'A' var grade2 rune = 'A' var star byte = '*'
Caesar cipher 凯撒加密法 把每个字母向后移固定的位置
package mainimport "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 mainimport ( "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 mainimport ( "fmt" ) func main () { question := ">Como estas?" for i, c := range question { fmt.Printf("%v %c\n" , i, 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{ }
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)
最常用:
countdown := 9 str := fmt.Sprintf("Launch in T minus %v seconds." ,countdown) fmt.Println(str)
6.字符串转换为数字 可能会发生错误函数的处理 atoi-要转换的数字字符串太大,atoi函数可能会发生错误
package mainimport ( "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 ) Timefuture := 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 mainimport "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 ) }
每个方法可以有多个参数,但是只能有一个接收者。 方法体中,接收者的行为和其他参数一样
func (接收器变量 接收器类型) MethodName(参数列表) (返回值列表){}
3.方法调用 变量.方法()
package mainimport "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()) }
另一种声明形式
var sensor func () kelvinsensor = kelvinTocelsius
带参数
package mainimport ( "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 mainimport "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 mainimport ( "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 mainimport "fmt" var f = func () { fmt.Println("dress up for the masquerade" ) } func main () { f() }
传参的匿名函数
package mainimport "fmt" func main () { f := func (message string ) { fmt.Println(message) } f("hahahaa" ) }
常用的另一种-立即执行的函数表达式
package mainimport "fmt" func main () { func () { fmt.Println("Function is here" ) }() }
3.函数闭包 匿名函数在Go里被称作函数字面值 其需要保留外部作用域的变量引用,函数字面值都闭包的 闭包:匿名函数封闭并包围作用域中的变量而得名的 闭包允许一个函数访问其外部作用域中的变量,即使在函数调用完成后,这些变量仍然保持其值。
package mainimport "fmt" type kelvin float64 func main () { var k kelvin = 294.0 sensor := func () kelvin { return k } fmt.Println(sensor()) k++ fmt.Println(sensor()) }
sensor捕获了外面的k,保持k不被释放,形成闭包
7.数组、切片、map 1.数组 1.1数组初始化、访问、遍历 1.数组长度固定且有序的元素集合
var planets [8 ]string dwarfs := [5 ]string {"A" ,"b" ,"c" ,"d" ,"e" } planets := [...]string { "mercury" , "venus" , "earth" , }
2.访问数组元素从[0]开始
3.数组的长度-len
fmt.Println(len (planets))
4.遍历数组
package mainimport "fmt" func main () { dwarf := [...]string { "A" , "b" , "C" , "d" , } for i := 0 ; i < len (dwarf); i++ { fmt.Println(i, dwarf[i]) } } for i, k := range dwarf { fmt.Println(i, k) }
1.2.数组的拷贝 1.数组的拷贝 最后两个数组不同
planetsMark := planets planets[2 ] = "whoops" fmt.Println(planets) fmt.Println(planetsMark)
数组作为参数传给函数是通过拷贝,原数组不改变
package mainimport "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 mainimport ( "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 mainimport ( "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" , } terrestrial := planets[0 :4 :5 ] worlds := append (terrestrial,"Ceres" ) dump("planets" , planets) dump("terrestrial" ,terrestrial) dump("worlds" , worlds) }
使用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 () { dwarfs := make ([]string ,0 ,10 ) dump("dwarfs" ,dwarfs) dwarfs = append (dwarfs,"Ceres" ,"Pluto" ,"Haumea" ,"Makemake" ,"Eris" ) dump("dwarfs" ,dwarfs) }
声明可变参数的函数 声明Printf,append这样的可变参数函数,需要在函数的最后一个参数前面加上…符号
package mainimport "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 : keyint : value
示例
package mainimport "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 mainimport "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函数可以接受一个或者两个参数
temperature := make (map [float64 ]int ,8 )
使用map作为计数器
package mainimport "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]++ for t,num := range frequency{ fmt.Println("%+.2f occurs %d times\n" ,t,num) } }
使用map和slice实现数据分组
package mainimport "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) } for g,temperatures := range groups{ fmt.Printf("%v %v\n" ,g,temperatures) } }
将map用作set 元素不重复
package mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport ( "encoding/json" "fmt" "os" ) func main () { type location struct { Lat, Long float64 } curiosity := location{-4.5895 , 137.4417 } 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 mainimport ( "encoding/json" "fmt" "os" ) func main () { type location struct { Lat float64 `json:"latitude"xml:"latitude"` Long float64 `json:"longitude"` } curiosity := location{-4.5895 , 137.4417 } 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 mainimport "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 mainimport "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 } 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 mainimport ( "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 mainimport "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "time" ) type stardater interface { YearDay() int Hour() int } 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 mainimport "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 mainimport ( "fmt" ) func main () { answer := 42 fmt.Println(&answer) }
&无法获得字符串/数值/布尔字面值的地址
*用来解引用
package mainimport ( "fmt" ) func main () { answer := 42 fmt.Println(&answer) address := &answer fmt.Println(*address) }
Go语言没有address++
2.指针类型 *int/*string
package mainimport "fmt" func main () { canada := "Canada" var home *string fmt.Printf("home is a %T\n" , home) home = &canada fmt.Println(*home) }
3.指针用来指向
package mainimport "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 mainimport "fmt" func main () { superpowers := &[3 ]string {"flight" , "invisibility" , "super strength" } fmt.Println(superpowers[0 ]) fmt.Println(superpowers[1 :2 ]) }
2.指针2 1.函数与指针 函数传参是参数副本,但是用指针变量指向同一个内存地址 函数方法借助内存地址修改值
package mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport ( "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 mainimport ( "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 mainimport ( "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.birthday() }
即使接收者为nil,也会继续调用方法
2.nil函数值 当变量被声明为函数类型,其默认值是nil
package mainimport "fmt" func main () { var fn func (a, b int ) int fmt.Println(fn == nil ) }
检查函数值是否为nil,并在有需要的时候提供默认行为(最佳实现)
package mainimport ( "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 mainimport "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 mainimport "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 mainimport "fmt" func main () { var v interface {} fmt.Printf("%T %v %v \n" , v, v, v == nil ) }
当接口类型被赋值以后,接口就会在内部指向该变量的类型和值
package mainimport "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 检验接口的内部表示
6.nil之外的另一个选择
package mainimport "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 mainimport ( "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 mainimport ( "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 mainimport ( "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