代码风格使用的平时工作时的风格,可能和标准的风格不同
第一个GO程序
首先安装Go语言,配置环境变量等
编辑器:推荐使用VSCODE(免费),避免使用破解版软件
目录结构
- bin:编译后的二进制文件
- pkg:编译后的库文件
- src:源码文件
–网站域名
---------作者/机构
-------------------项目1
--------------------------------模块
.转自b站李文周老师视频go语言系列视频
如图新建目录,main.go作为程序入口
内容如下:
package main
import "fmt"
func main() { //疑问:go build没指定文件名。是通过main函数来生成可执行文件的吗?
fmt.Println("hello world")
}
然后在day01下打开控制台输入go build指令,即可变异main.go文件,生成day01可执行文件,输入day01.exe执行即可
PS D:\study\go\src\github.com\Pxz\day01> go build
PS D:\study\go\src\github.com\Pxz\day01> .\day01.exe
hello world
切片分析
- 切片是引用类型,引用的是切片第一个元素在底层数组的位置
- 切片类似Python的列表,可以自动扩容。
- 切片的容量是切片第一个元素在底层数组的位置到底层数组的最后一位:例如底层数组A长度是10,切片B := A[3:] 那么B的容量就是7
会不会类似Python也会自动减容呢?值得探索
直接贴代码:一定要仔细看完!
package main
import "fmt"
func main() {
fmt.Println("=================0====================")
lstA := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} //:=自动根据类型声明变量,[...]根据数组的大小自动计算容量,就是[10]int{0,1,2....9}的缩写
slipA := lstA[:]
fmt.Printf("底层数组:%T,%v\n", lstA, lstA)
fmt.Printf("切片:%T,%v\n", slipA, slipA)
fmt.Println("=================1====================")
slipA1 := lstA[:3]
fmt.Printf("slipA1%v Len:%v,Cap:%v\n", slipA1, len(slipA1), cap(slipA1))
slipA2 := lstA[3:]
fmt.Printf("slipA2%v Len:%v,Cap:%v\n", slipA2, len(slipA2), cap(slipA2))
//append
//没超过底层数组长度
fmt.Println("=================2====================")
slipA3 := append(slipA1, 555) //必须要接收返回值,可以使用 _ 接收
fmt.Printf("slipA3%v Len:%v,Cap:%v\n", slipA3, len(slipA3), cap(slipA3))
fmt.Printf("slipA2%v Len:%v,Cap:%v\n", slipA2, len(slipA2), cap(slipA2)) //底层数组改变了所以其他的切片的值也改变了
fmt.Printf("底层数组:%T,%v\n", lstA, lstA) //会改变底层数组的值
//超过底层数组长度
fmt.Println("=================3====================")
slipA4 := append(slipA, 555)
fmt.Printf("%v Len:%v,Cap:%v\n", slipA4, len(slipA4), cap(slipA4)) //超过长度能够正常append,切片扩容了一倍
fmt.Printf("底层数组:%T,%v\n", lstA, lstA) //但是底层数组还是没变
//通过append删除 现在底层数组是这样的:[0 1 2 555 4 5 6 7 8 9],删除555
fmt.Printf("改变前的底层数组:%T,%v\n", lstA, lstA)
slipA5 := lstA[4:]
_ = append(slipA1, slipA5...) // slipA1是从0-3的切片,...的作用是把切片中的元素拆开,类似Python中的*args
fmt.Printf("改变后的底层数组:%T,%v\n", lstA, lstA)
}
程序输出:
=================0====================
底层数组:[10]int,[0 1 2 3 4 5 6 7 8 9]
切片:[]int,[0 1 2 3 4 5 6 7 8 9]
=================1====================
slipA1[0 1 2] Len:3,Cap:10
slipA2[3 4 5 6 7 8 9] Len:7,Cap:7
=================2====================
slipA3[0 1 2 555] Len:4,Cap:10
slipA2[555 4 5 6 7 8 9] Len:7,Cap:7
底层数组:[10]int,[0 1 2 555 4 5 6 7 8 9]
=================3====================
[0 1 2 555 4 5 6 7 8 9 555] Len:11,Cap:20
底层数组:[10]int,[0 1 2 555 4 5 6 7 8 9]
改变前的底层数组:[10]int,[0 1 2 555 4 5 6 7 8 9]
改变后的底层数组:[10]int,[0 1 2 4 5 6 7 8 9 9]
map使用
包含知识点:函数、for循环遍历、map
注意事项:
- for循环遍历字符串,需要用两个值来接收,第一个值为下标,第二个值为字符对应int32类型。(这里感觉我使用map[int]int来存储可能会更好一点,但是现在这样结果更直观些)
- map是引用类型,在函数中改变它的值也会影响函数外的值
- map如果不存在键,默认返回零值
代码
package main
import (
"fmt"
"strconv"
)
// 使用函数和map结合统计字符串中每个字符出现的次数
func CountString(sStr string) map[string]int {
mResult := make(map[string]int, 10)
fmt.Printf("%v\n", sStr)
for _, sVal := range sStr { // for循环遍历string,sVal的值为int32, _为下标的值
sKey := strconv.Itoa(int(sVal)) // int32不能直接用itoa,要强转为int才行
AddMap(mResult, sKey, 1) // map是引用类型,所以不要返回值接收也能改变它的值
}
return mResult
}
func AddMap(mMap map[string]int, sKey string, iNum int) { //这一步其实有点多余了,直接 += 就可以了
_, ok := mMap[sKey] //判断键值是否存在,ok是bool值,_ 是键对应的值,如果键不存在则默认零值
if ok {
mMap[sKey] += iNum
} else {
mMap[sKey] = iNum
}
return
}
func main() {
var sString string
sString = "abcabc"
mResult := CountString(sString)
fmt.Printf("=============================\n")
for key, val := range mResult {
fmt.Printf("%s:%v\n", key, val)
}
}
输出
abcabc
=============================
97:2
98:2
99:2
PS D:\study\go\src\github.com\Pxz\day03map>
defer
在函数return的时候才执行,可以用于释放资源
内置函数
close:主要用来关闭channel
len:求长度,例如string、array、slice、map、channel
new:分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make:分配内存,主要用来分配引用类型,比如chan、map、slice
append:用来追加元素到数组、slice中
panic和recover:用来做错误处理(成对出现的)
panic和recover
panic类似于Python中的raise
recover:放在defer定义的立即执行函数中
err := recover()
执行recover之后,会捕获panic抛出的异常,执行后程序能够继续运行(类似try catch)
注意:
- recover()必须搭配defer使用
- defer一定要定义在panic之前
fmt标准库
- fmt.print(s) :直接打印字符串,没有换行
- fmt.printlen(s):打印字符串并且在后面输出换行
- fmt.printf(“格式化字符串”, 值, 值)
- %T:类型
- %d:十进制
- %b:二进制
- %o:八进制
- %x:十六进制
- %c:字符
- %s:字符串
- %p:指针
- %v:值(原样输出) %#v看的更清楚
- %f:浮点数
- %%:将转义%, **注意,这里不能用\符号转义 **
- %t:布尔
结构体
go 语言没有类的概念
自定类型
例如:
main.go文件中
type myInt int //自定义类型 注意和类型别名区分,type yourInt = int
vat n myInt
n = 100
fmt.Printf("%T", n) ====>> main.myInt
结构体
定义:
type 类型名 struct{
字段名1 字段类型
字段名1 字段类型
...
}
例如
type Person struct{
age int
name string
}
gorutine
首先提几个问题:
- gorutine之间如何通信?
- 管道如果没有缓冲区,往里面写数据会发生什么?
- 关闭管道之后,还能写入吗,还能读取吗?
- 不关闭管道,往有缓冲区的空管道里面读取会发生什么?
- 读取已关闭的,有缓冲区的空管道会发生什么?
代码测试如下:
package main
import "fmt"
func main() {
// test()
MakerAndConsumer()
}
// 生产者与消费者
func MakerAndConsumer() {
CangKu := make(chan int, 100)
Goods := make(chan int, 100)
Maker(CangKu)
// Consumer(CangKu)
// close(Goods) // 通道被关闭后无法写入,只读
GetFromCangKu(CangKu, Goods)
ShowGoods(Goods)
fmt.Println("hello world")
}
func Maker(CangKu chan int) {
for i := 1; i < 100; i++ {
CangKu <- i
}
close(CangKu)
}
func Consumer(CangKu chan int) {
for {
val, res := <-CangKu
if !res {
fmt.Printf("err %v %v\n", val, res)
break
}
fmt.Printf("value = %v\n", val)
}
}
func GetFromCangKu(CangKu chan int, Goods chan int) {
for {
val, res := <-CangKu
if !res {
fmt.Printf("err %v %v\n", val, res)
break
}
Goods <- val * val
}
close(Goods) // 查看下面的func test可知,写完之后如果没有其他线程写入的话,需要关闭
}
func ShowGoods(Goods chan int) {
for {
val, res := <-Goods
if !res {
fmt.Printf("no goods, break, %v %v\n", val, res)
break
}
fmt.Printf("goods = %v", val)
}
}
func test() {
// 没有指定容量,也没有其他线程接收,阻塞,报错
// c := make(chan int)
// c <- 1
// 指定容量,可以写入,空的channel并且没有close,如果还读的话,也没有其他线程写入,阻塞,报错
// c := make(chan int, 1)
// c <- 1
// <-c
// <-c
// 写完关闭,空的channel不会阻塞,而是返回0值和False
c := make(chan int, 1)
c <- 1
close(c)
for i := 0; i < 2; i++ {
val, res := <-c
fmt.Println(val, res)
}
}