golang初学
前言
一门语言不够那就学两门,对于完全没学过go但是学过其它语言且有一定的开发经验的我来说,感觉两天学完基本语法没问题。
安装报错
当我去官网下载了go的最新版本的安装程序后,运行后发生这么一个错误:
这是典型的MSI安装包权限或系统服务异常问题。
然后我这么做:
- 缺少Windows Installer文件夹
打开“此电脑” → C盘 → Windows文件夹 → 检查是否存在名为Installer的隐藏文件夹。若不存在,手动新建一个名为Installer的文件夹(注意:不要加引号,不要带空格)。
后就成功了,那么这个文件夹是什么?
C:\Windows\Installer 文件夹是 Windows Installer 服务用于存储软件安装、更新、修复和卸载所需缓存文件的核心目录,其作用是确保系统能正确管理软件生命周期。
基础语法
当我要运行main文件时,我可以在命令行运行go run main.go;或者运行go build main.go,这样会生成main.exe,然后.\main.exe运行这个可执行文件,这相当于把编译执行拆开。
我每次修改完代码后,都需要ctrl+s保存,不然终端看的还是旧代码。
短变量声明法:
1 | func main() { |
这样可正常打印:1 2 3(中间有一个空格,这是规范,就算println中不夹空格这里也会加),然后短变量声明不能用来声明全局变量
匿名变量:
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿
名变量用一个下划线_表示,例如:
1 | func getUserinfo (string, int){ |
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
注意事项:
1、函数外的每个语句都必须以关键字开始(var、const、func 等)
2、:=不能使用在函数外。
3、_多用于占位,表示忽略值。
基本数据类型及其范围:
先记一下整形的数据范围

然后我们声明变量的时候就可以显示定义某个数是哪个范围的,就像这样:
1 | func main() { |
会打印出:
值:123 类型int8
此时要注意,由于int8类型的范围是-128到127,所以如果赋值为128会报错。
如果是直接var num int = 128,那么此时的数据类型根据操作系统架构而定,如果是64位系统,int就默认是int64。
那么为防止数据超出范围,为什么不都直接用int呢?因为int比int8等更小位数的数据占用的字节更多,更不省内存。
只要数据大小不溢出,各数据类型可相互转换:
再记一下各转义字符输出数据:
字符属于int型:它的值为ascii值

go中的字符分为两种类型。
一种是byte,也叫uint8类型类型,就是输出“我”时输出的是三个我现在还不认识的符号,因为它在utf-8编码下占据三个字节
另一种是rune类型, 按%v(%c)格式输出时就是正常的汉字和它对应的ascii值,例如20320(你)
二者区别在于输出汉字时,能否直接输出汉字原型。
值类型:
基本数据类型和数组都属于值类型,值类型有一个特点就是:
当数组a1被声明了,然后数组a2 := a1,那么此时a2(副本)是额外开辟了内存空间的,此后a1的修改于a2无关,类似于浅拷贝
引用类型:
就是与上面相对的,副本会改变的,一般切片就是引用类型,存在内存空间共享
函数作为类型
我知道函数可以作为参数,其实函数也能作为数据类型。

如图,第一行通过type关键字声明了一个calc类型的函数,那么我在主函数中就可以声明一个calc类型的函数c,然后c就可以复制为函数sub,因为sub是完全符合calc的参数及返回要求的,这里很像python中的多态。
闭包:可以让变量常驻内存又不污染全局
闭包就是函数里面再写个函数,然后外层函数返回值是内层函数。在主函数中,当外层函数被调用并被传入了一个参数,此时它返回一个内层函数,然后外层函数的局部变量相对于内层函数就是全局变量,当内层函数对该变量进行修改时,该变量会被作为全局变量而改变,所以如果下一行又调用了这个内层函数,那么此时的内层函数是在上一行改变了的变量的条件下进行作用的。(这一段很抽象,只有我编辑的时候能看懂,所以上图!)


异常处理
go中如果没有特异的异常处理代码,遇到错误时会直接结束执行,不会继续往后。
下面给出defer、panic、recover抛出异常的示例:
1 | func readFile(fileName string) error { |
这段代码只会打印:
抛出异常给管理员发送邮件
因为抛出异常时,函数会准备退出,但是退出之前要先执行defer语句,然后recover() 成功捕获了 panic 传递过来的错误信息。
指针巩固理解
变量a的值为10,程序运行时,a存在于内存中,且有一个内存中的地址,然后可以令p := &a,此时指针p的值就是a的地址,同时呢,p自身也有一个地址,这个地址是和a的地址没有关系的,也能用取地址符号&获得p的地址。
结构体
结构体方法:
结构体函数有下面这两种定义方式
第二种的接收者类型为指针,这样的方式使得在主函数中调用这个方法时,属性可以全局改变,但是第一种就不行。
使用第三方包
假如我的项目目录最后是Test(项目名)
那么我应该先在命令行用go mode init 项目名 来初始化项目,这一步会生成go.mod文件用于项目包管理。
当我要自己写一个包时,我应该先新建一个calc文件夹在根目录下,然后在calc文件夹下写函数,就像这样:
然后引用时就这样:
这是用自己的包的流程,接下来说用官方包:
要先去网站Go Packages - Go Packages中找到自己要的那个包
然后import左上角这个包的地址
,然后要么用go mod download 命令下载,要么就用go mod tidy来自动补全缺失的包(这个好用一点),然后就能用了
接口
Go中,把接口理解为一种约定,里面会声明一些函数,然后这些函数依赖结构体来实现(结构体需要实现约定中的所有函数)。然后在主函数中,声明一个接口实例和一个结构体实例,把结构体实例赋值给接口实例,此时接口实例就能调用接口中约定的方法了。
空接口
给它赋值的变量是什么类型,那么这个接口就是什么类型。
类型断言
当我要判断一个接口实例是什么类型时。
第一种用法:用语法flag := a.(string),相当于判断实例a是否时string类型,这是一个布尔变量。
第二种用法:a.(type),然后用switch case来分类讨论。
接口到底有啥用
这么说接口其实可以不用写,它可以理解为只是一个标注作用,当项目中的某个结构体实现了接口定义的所有方法,它就实现了这个接口,实际上此时那个结构体还和接口没有关系。但是当有一个函数要传入一个接口变量是,我就可以传入那个实现了接口的结构体了对吗?对,但是接口还是有重要意义的,就像这样:
1 | // ============ 没有接口的世界 ============ |
协程
协程是用户级的一个操作,比如程序中的一个函数,在主协程中(main.go)只需要一个go关键字就能开辟一个协程。当主协程执行完毕时,无论其它协程有没有执行完,都会停止。
管道
用于各个协程之间通信(各协程可以共享管道)
互斥锁与读写互斥锁
这俩东西不一样,读写互斥锁中,读操作是可以并发的,但是写操作要独占一个锁
反射
初步理解为给函数传入一个参数,定义函数时用空接口表示要接收的参数,这样的话实际使用时可以接收任意数据类型的接口,然后可以通过反射包中的方法获取传入参数的类型以及值。获取到后就可以分情况修改或者操作这个参数了。
文件
读文件时,每次可以选择读一行或者多少字节,然后for循环读取,读完后会有个err参数接收到信息,用if判断这个参数,然后跳出循环。
写文件时,有个参数配置:
泛型
1 | func getData[T any](value T) T { |
这样的话,传入的 value可以是任何类型,不需要做类型断言,也不用担心类型安全

