1.按照下文配置环境时ctrl+shift+p出不来、安装失败
答:多试几次多多重启vscode,记得打开vscode的管理员模式、检查go的环境变量。
2.解决方案自go故障排查集锦 - chalon - 博客园 摘抄如下:
问题4:无法编译go程序,提示:
Build Error: go build -o e:\go\src\1go\1day\1_hello\__debug_bin.exe -gcflags all=-N -l .
go: go.mod file not found in current directory or any parent directory; see 'go help modules' (exit status 1)
原因分析:未发现go.mod;
解决方法:切换至项目目录下,执行go mod init命令,初始化项目即可产生go.mod;
最后看到这些还都蛮感人的
3.在程序输入时报错”noDebug mode: unable to process 'evaluate' request“
第一个方法是按这个做:
noDebug mode: unable to process 'evaluate' request · Issue #2015 · golang/vscode-go · GitHub
第二个方法是放弃在这里
进行输入,而是用终端
来运行程序输入输出。
4.语法上的特殊性质
具体见菜鸟教程Go 语言基础语法 | 菜鸟教程
go的百分号格式化 见文章GO语言百分号参数_XUAN528XUAN的博客-CSDN博客_go 百分号
error是接口
变量声明
格式为var 变量名 变量类型。如var num float32 = 1.5
也可以不指定类型,这时候go会帮你判断是什么类型。var 变量名 = value
也可以使用:= : 变量名 := value
对于值类型,可以通过&i来获取变量i的内存地址。
对于引用类型:
局部变量不允许声明了然后不用,但是全局变量允许
go的并行赋值
函数有多个返回值时可以并行赋值
package main
import "fmt"
func main() {
_, a := count(10, 3)
fmt.Println(a)
}
func count(a, b int) (int, int) {
return a + b, a - b
}
go的常量
定义格式 const 常量名 [数据类型] = value
当然可以省略[数据类型]
常量定义也存在并行赋值的方法
/*输出 1 1 1 1*/
package main
import "fmt"
const (
a = 1
b
c
d
)
func main() {
fmt.Println(a, b, c, d)
}
go还有一种特殊常量,iota,可以认为是一个可以被编译器修改的常量,可以理解为const语句块内的行索引
/*输出为 0 1 2 0 1*/
package main
import "fmt"
const (
UNKNOWN = iota
FEMALE
MALE
)
const (
HUMAN = iota
HAHA
)
func main() {
fmt.Println(UNKNOWN, FEMALE, MALE, HUMAN, HAHA)
}
这点很神奇,看来名字不能乱取
这个也是,看来行不能乱换。
运算符
条件控制
if-else
package main
import "fmt"
var i int = 0
func main() {
if i < 0 {//如果写成带括号形式会自动帮你去掉
fmt.Println(i)
} else {//这里不能换行
fmt.Print("big")
}
}
switch-case
package main
import "fmt"
var i int = 1
func main() {
switch i {
case 0:
fallthrough
case 1:
fmt.Println("对了")
}
}
select语句
这个还看不大懂,之后再说吧
循环语句
格式 for init; condition; post { } ——相当于c的for循环
for condition { } ——相当于c的while循环
for { } ——相当于c的死循环
func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
sum := 1
for sum <= 10 {
sum += sum
}
fmt.Println(sum)
}
func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
for i := range arr {
fmt.Println(arr[i])
}
fmt.Println("------")
for i, ele := range arr {
fmt.Println(i, ele)
}
}
/*
1
2
3
4
5
6
7
8
9
10
11
12
------
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
10 11
11 12
*/
go的break新增了一个标记,这点不错,可以用来退出多重循环
continue也有标记
会从continue语句那里跳到标签层的下一轮循环继续。相当于在continue那句变成break,然后最外层循环内的内层循环下面啥都没有
函数
定义格式:
func 函数名 (参数列表) (返回类型列表【可以返回多个值】){ }
跟JavaScript一样灵活,go也可以用一个变量储存函数的地址
package main
import "fmt"
func main() {
myFunction := func(a string) {
fmt.Printf("Please call me %s", a)
}
myFunction("大佬")
}
函数闭包
create返回的函数变量用到了函数体的外部变量c,但在create执行结束后,仍能得到这个c的值,这就满足函数闭包,并称变量c为捕获变量
这个讲得很清楚,通常称闭包函数为有状态的函数存了捕获列表和函数地址
每次都打印2
方法
/*这是一般的函数*/
/* 定义结构体 */
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", getArea(c1))
}
//该 method 属于 Circle 类型对象中的方法
func getArea(c Circle) float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
/* 这是方法 */
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
如果想要改变结构体中radius的值,需要传指针
package main
import (
"fmt"
)
/* 定义结构体 */
type Circle struct {
radius float64
}
func main() {
var c Circle
fmt.Println(c.radius)
c.radius = 10.00
fmt.Println(c.getArea())
c.changeRadius(20)
fmt.Println(c.radius)
change(&c, 30)
fmt.Println(c.radius)
}
func (c Circle) getArea() float64 {
return c.radius * c.radius
}
// 注意如果想要更改成功c的值,这里需要传指针
func (c *Circle) changeRadius(radius float64) {
c.radius = radius
}
// 以下操作将不生效
//func (c Circle) changeRadius(radius float64) {
// c.radius = radius
//}
// 引用类型要想改变值需要传指针
func change(c *Circle, radius float64) {
c.radius = radius
}
变量的作用域
数组
定义格式 var variable_name [SIZE] variable_type eg. var arr [10]int
初始化方法
var arr = [ ]type { } eg.var arr = [9]int {1,2,3,4,5,6,7,8,9}
arr := []type{} eg.arr := [...]int{1,2,3,4,5,6,7,8,9}
func main() {
arr := [][]int{}
row1 := []int{1, 2, 3, 4}
row2 := []int{5, 6, 7, 8}
//使用 append() 函数向空的二维数组添加两行一维数组
arr = append(arr, row1)
arr = append(arr, row2)
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Print(arr[i][j])
}
}
/*
打印数组也可以这样
fmt.Println(arr[0], arr[1])
*/
}
向函数传递数组
方法一,形参设定数组大小
eg.func myFunction(param [10]int){ }
方法二,形参未设定数组大小
eg.func myFunction(param []int){ }
如下段代码给出求和函数。
func main() {
arr := []int{1, 2, 3, 4, 5, 6}
fmt.Println(getSum(arr))
}
func getSum(arr []int) int {
sum := 0
for i := 0; i < len(arr); i++ {
sum += arr[i]
}
return sum
}
指针
指针的定义格式 var 变量 *type,或者比如说变量 := &a
Go可以定义指针数组,即数组元素都是指针。
func main() {
num := []int{1, 2, 3}
var arr [3]*int
for i := range num {
arr[i] = &num[i]
}
for i := range arr {
fmt.Println(arr[i], *arr[i])
}
}
指针作为函数参数
package main
import (
"fmt"
)
func main() {
a := 10
b := 20
fmt.Printf("Swap之前 %d %d\n", a, b)
swap(&a, &b)
fmt.Printf("Swap之后 %d %d\n", a, b)
a, b = swap2(a, b)
fmt.Printf("Swap2之后 %d %d\n", a, b)
}
//C语言传统swap写法
func swap(a, b *int) {
var temp = *a
*a = *b
*b = temp
}
//Go
func swap2(a, b int) (int, int) {
return b, a
}
结构体
定义结构
type struct_variable_type struct { member definition member definition ... member definition }type Book struct {
name string
id int16
author string
subject string
}
结构体的定义方式
type Book struct {
name string
id int
author string
subject string
}
func main() {
//两种定义
a := Book{"Hello", 123455, "me", "English"}
var b = Book{"Hello", 123455, "me", "English"}
fmt.Println(a == b)
}
//输出:True
还可以像其他oop语言一样用new
作为函数参数
func printBook(b Book) {
fmt.Print(b.name, b.author, b.subject, b.id)
}
这点跟C语言不大一样
注意,
从上可注意到if里边也是可以定义变量。
切片Slice
切片Slice与数组Array的区别:
定义格式
var slice []int
切片不需要说明长度,但也可以用make来定义切片的初始长度
切片的初始化
也可以通过另一个数组初始化切片
还可以通过切片初始化切片。注意这个可以用来截取切片,左闭右开
最后可以通过内置函数初始化切片。其中capacity是可选参数
package main
import "fmt"
func main() {
a := make([]int, 0, 10)
for i := 0; i < 19; i++ {
a = append(a, i)
}
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
fmt.Println(len(a), cap(a))
}
/*
输出:
0123456789101112131415161718
19 20
*/
Range
package main
import "fmt"
func main() {
var arr1 = []int{1, 2, 3}
sum := 0
for _, i := range arr1 {
sum += i
}
fmt.Println(sum)
}
此处可以用格式化输出printf,就可以输出字符了
Go的集合Map
Map 是一种无序的键值对的集合,key-value。
定义格式
1.声明变量,默认为nil var myMap[key的类型]value的类型
2.使用make函数 myMap := make(map[key的类型]value的类型)
注意不能往nil的map里面塞东西。所以你就算用了第一个定义格式,也得再make。
package main
import "fmt"
func main() {
// var myMap map[string]int
// myMap = make(map[string]int)
myMap := make(map[string]int)
myMap["张三"] = 33
myMap["李四"] = 99
myMap["王五"] = 88
myMap["老刘"] = 90
for key, value := range myMap {
fmt.Println(key, value)
}
}
对一个map取key值,如myMap["张三"],其实得到的返回值不止一个,第一个返回值就是key存在时对应的value,第二个返回值则是bool类型,key存在返回true,不存在返回false
类型转换
格式:
不支持隐形类型转换
Go的接口
package main
import "fmt"
func main() {
casio := new(Casio)
fmt.Println(casio.add(1, 2))
fmt.Println(casio.sub(1, 2))
}
type Calculator interface {
add(int, int) int
sub(int, int) int
call()
}
type Casio struct {
name string
}
func (c Casio) add(a, b int) int {
return a + b
}
func (c Casio) sub(a, b int) int {
return a - b
}
可以发现一个结构体可以选择只实现接口的部分类而非全部类
如果想要在实现方法里修改结构体的属性,只需在方法那边传入结构体指针即可。
同样的也有多态
Go的类型转换
Go没有自动类型转换,只能强制类型转换。
注意类型转换括号括在变量
Go的错误处理
有时候错误需要反应的东西不只限于一个string,我们可以用一个对象来表示这个错误类型,即在go中的具体实现是一个表示错误的结构体。 具体的错误类的实现,错误信息方法的实现,错误在函数的用法,以及有错误的函数被调用时的用法,详见下列代码,模拟除0错误。
package main
import (
"fmt"
)
// 自定义错误信息结构
type DIV_ERR struct {
etype int // 错误类型
v1 int // 记录下出错时的除数、被除数
v2 int
}
// 实现接口方法 error.Error()
func (div_err DIV_ERR) Error() string {
if 0==div_err.etype {
return "除零错误"
}else{
return "其他未知错误"
}
}
// 除法
func div(a int, b int) (int,*DIV_ERR) {
if b == 0 {
// 返回错误信息
return 0,&DIV_ERR{0,a,b}
} else {
// 返回正确的商
return a / b, nil
}
}
func main() {
// 正确调用
v,r :=div(100,2)
if nil!=r{
fmt.Println("(1)fail:",r)
}else{
fmt.Println("(1)succeed:",v)
}
// 错误调用
v,r =div(100,0)
if nil!=r{
fmt.Println("(2)fail:",r)
}else{
fmt.Println("(2)succeed:",v)
}
}
此处有错误处理技巧,参见视频Go语言技巧 - 2.【错误处理】谈谈Go Error的前世今生_哔哩哔哩_bilibili
其中第一点违背了面向对象的继承法则。
正如java中可以把错误throw掉,以及try-catch机制,Go中也有相应的机制,即panic 与 recover,一个用于主动抛出错误,一个用于捕获panic抛出的错误。
在说上面那两个是啥之前,我们可以先了解一下啥是函数中的的defer。
【尚硅谷】Golang入门到实战教程丨一套精通GO语言_哔哩哔哩_bilibili
package main
import "fmt"
func main() {
fmt.Println(sum(10, 20))
}
func sum(n1, n2 int) int {
defer fmt.Println("ok1,n1 = ", n1)
defer fmt.Println("ok2,n2 = ", n2)
res := n1 + n2
fmt.Println("ok3,res = ", res)
return res
}
/*
输出结果:
ok3,res = 30
ok2,n2 = 20
ok1,n1 = 10
30
*/
注意:
func main() {
fmt.Println(sum(10, 20))
}
func sum(n1, n2 int) int {
res := n1 + n2
return 0
fmt.Println("ok3,res = ", res)
defer fmt.Println("ok1,n1 = ", n1)
defer fmt.Println("ok2,n2 = ", n2)
return res
}
/*
输出结果:
0
*/
因此我们的一般用法是
刚打开资源就加一句defer(如果实际不考虑释放顺序的话)
此处用的是关闭句柄的功能。
此时要记得defer后面是一个可执行语句,所以不能只定义一个函数,还得去执行。而执行匿名函数很简单,只需在定义的{}后面加个()就可以调用执行了
defer处理完异常后,main下面的代码就可以继续执行了。否则会panic,中断程序
这里也可以这么写。
自定义错误的话就是这个写法。
有效捕获:
func main() {
test()
fmt.Println("hello")
}
func except() {
recover()
}
func test() {
defer except()
panic("runtime error")
}
func test2() {
defer func() { recover() }()
}
无效:
输出three
Go的并发
go关键字
语法格式:go 函数名(参数列表)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
go的Channel
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
/*
result:-5 17 12
*/
可以看出channel是先进后出的栈结构。
channel可以设置缓冲区,通过make的第二个参数来指定缓冲区大小
记录试出来的死锁情况
package main
import "fmt"
func test(ch chan int) {
for i := 0; i < 8; i++ {
fmt.Println(<-ch)
}
fmt.Println("Finished")
}
func main() {
ch := make(chan int, 10)
//缓冲区已满,在等待接收区,死锁了
for i := 0; i < 13; i++ {
ch <- i
}
go test(ch)
}
Go可以使用range关键字遍历得到的channel,类似于数组与切片。
如果通道接收不到数据后
ok就变为false。
可以用close()函数来关闭通道
package main
import "fmt"
func test(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
fmt.Println("Finished")
close(ch)
}
func main() {
ch := make(chan int, 10)
go test(ch)
for i := range ch {
fmt.Println(i)
}
}
如果删去此处的close(),会发生死锁
// range 函数遍历从通道接收到的数据,因为在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,而会一直等待接收第 11 个数据,发生阻塞,死锁。
我们单独写一个 say2 函数来跑 goroutine,并且 Sleep 时间设置长一点,150 毫秒,看看会发生什么:
package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s, (i+1)*100) } } func say2(s string) { for i := 0; i < 5; i++ { time.Sleep(150 * time.Millisecond) fmt.Println(s, (i+1)*150) } } func main() { go say2("world") say("hello") }输出结果:
hello 100 world 150 hello 200 hello 300 world 300 hello 400 world 450 hello 500 [Done] exited with code=0 in 2.066 seconds问题来了,say2 只执行了 3 次,而不是设想的 5 次,为什么呢?
原来,在 goroutine 还没来得及跑完 5 次的时候,主函数已经退出了。
我们要想办法阻止主函数的结束,要等待 goroutine 执行完成之后,再退出主函数。那么我们就可以用个channel,让主函数在那边等接数据
package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s, (i+1)*100) } } func say2(s string, ch chan int) { for i := 0; i < 5; i++ { time.Sleep(150 * time.Millisecond) fmt.Println(s, (i+1)*150) } ch <- 0 close(ch) } func main() { ch := make(chan int) go say2("world", ch) say("hello") fmt.Println(<-ch) }我们引入一个信道,默认的,信道的存消息和取消息都是阻塞的,在 goroutine 中执行完成后给信道一个值 0,则主函数会一直等待信道中的值,一旦信道有值,主函数才会结束。
package main
import (
"fmt"
)
func test(c chan int) {
for i := 0; i < 100; i++ {
c <- i
}
fmt.Println("Finished test")
}
func main() {
c := make(chan int, 100)
go test(c)
for true {
val := <-c
if val > 5 {
close(c)
break
}
fmt.Println(val)
}
fmt.Println("------------------")
for val := range c {
fmt.Println(val)
}
}
channel关闭后不能访问往里面塞东西,但是数值仍存着,还是可以全部拿出来。但是close只能close一遍
package main
import (
"fmt"
"time"
)
var flag bool = false
func test(c chan int) {
for i := 0; i < 300; i++ {
if flag {
break
}
c <- i
}
fmt.Println("Finished test")
}
func main() {
c := make(chan int, 100)
go test(c)
i := 0
for true {
i++
val := <-c
if val == 5 {
close(c)
flag = true
break
}
if i >= 450 {
break
}
fmt.Println(val)
time.Sleep(1000)
}
fmt.Println("------------------")
for val := range c {
fmt.Println(val)
}
}
输出结果:
0
1
2
3
4
------------------
6
7
......
30
Finished test
31
32
......
105