大家好,关于go网站源码分享复制工具官网很多朋友都还不太明白,今天小编就来为大家分享关于github网站源码的知识,希望对各位有所帮助!
▌说在前面:
现在拿到offer超级难,甚至连面试电话,一个都搞不到。
尼恩的技术社区中(50+),很多小伙伴凭借“左手云原生+右手大数据”的绝活,拿到了offer,并且是非常优质的offer,据说年终奖都足足18个月。
从Java高薪岗位和就业岗位来看,云原生、K8S、GO现在对于高级工程师/架构师来说,越来越重要。尼恩从架构师视角出发,基于自己的尼恩3高架构师知识体系和知识宇宙,写一本《GO学习圣经》
▌最终的学习目标:
咱们的目标,不仅仅在于GO应用编程自由,更在于GO架构自由。
前段时间,一个2年小伙伴希望涨薪到18K,尼恩把GO语言的项目架构,给他写入了简历,导致他的简历金光闪闪,脱胎换股,完全可以去拿头条、腾讯等30K的offer,年薪可以直接多20W。
足以说明,GO架构的含金量。
另外,前面尼恩的云原生是没有涉及GO的,但是,没有GO的云原生是不完整的。
所以,GO语言、GO架构学习完了之后,咱们在去打个回马枪,完成云原生的第二部分:《Istio+K8SCRD的架构与开发实操》,帮助大家彻底穿透云原生。
▌本书目录:
-说在前面:\n-最终的学习目标\n-本书目录\n-学习GO的相关资料:\n-Go/C/C++/Java四大语言的对比\n-面向过程C语言\n-面向对象C++语言\n-Java语言\n-Golang:\n-Golang与Java等其他语言的对比\n-Java\n-c
执行速度快(4.28),学习难度适中,开发速度适中。但是由于c开发的,但是现在都迁移到了java上。
▌C/C++
现存编程语言中的老祖,其他语言皆由此而生。执行速度最快无人能及。但是写起来最为复杂,开发难度大。
▌Javascript
编程语言中特立独行的傲娇美女。前端处理能力是其它语言无法比拟。发展中的js后端处理能力也是卓越不凡。前后端通吃,舍我其谁?
▌Python
脚本语言,速度最慢(258s),代码简洁、学习进度短,开发速度快。豆瓣就是拿python写的。Python著名的服务器框架有django,flask。但是python在大型项目上不太稳定,因此有些用python的企业后来迁移到了java上。
▌scala
编译语言,比python快十倍,和java差不多,但是学习进度慢,而且在实际编程中,如果对语言不够精通,很容易造成性能严重下降。,后来比如Yammer就从scala迁移到了java上。微服务框架有lagom等。
▌Go
编程界的小鲜肉。高并发能力无人能及。即具有像Python一样的简洁代码、开发速度,又具有C语言一样的执行效率,优势突出。
▌Go语言的官网
设计Go语言是为了解决当时Google开发遇到的问题:
大量的C++代码,同时又引入了Java和Python成千上万的工程师数以万计行的代码分布式的编译系统数百万的服务器
Google开发中的痛点:
编译慢失控的依赖每个工程师只是用了一个语言里面的一部分程序难以维护(可读性差、文档不清晰等)更新的花费越来越长交叉编译困难
如何解决google的问题和痛点?
Go希望成为互联网时代的C语言。多数系统级语言(包括Java和C等高级语言引入了GC机制,即程序员不需要再考虑内存的回收等,而是由语言特性提供垃圾回收器来回收内存。但是随之而来的可能是程序运行效率的降低。
GC过程是:先stoptheworld,扫描所有对象判活,把可回收对象在一段bitmap区中标记下来,接着立即starttheworld,恢复服务,同时起一个专门gorountine回收内存到空闲list中以备复用,不物理释放。物理释放由专门线程定期来执行。
GC瓶颈在于每次都要扫描所有对象来判活,待收集的对象数目越多,速度越慢。
一个经验值是扫描10w个对象需要花费1ms,所以尽量使用对象少的方案,比如我们同时考虑链表、map、slice、数组来进行存储,链表和map每个元素都是一个对象,而slice或数组是一个对象,因此slice或数组有利于GC。
GC性能可能随着版本不断更新会不断优化,这块没仔细调研,团队中有HotSpot开发者,应该会借鉴jvmgc的设计思想,比如分代回收、safepoint等。
内存自动回收,再也不需要开发人员管理内存开发人员专注业务实现,降低了心智负担只需要new分配内存,不需要释放
▌3内存分配
初始化阶段直接分配一块大内存区域,大内存被切分成各个大小等级的块,放入不同的空闲list中,对象分配空间时从空闲list中取出大小合适的内存块。
内存回收时,会把不用的内存重放回空闲list。
空闲内存会按照一定策略合并,以减少碎片。
▌4快速编译:
Go语言的编译速度相对较快,在大型项目中也不会出现明显的编译延迟。同时,Go语言生成的可执行文件体积小,运行速度快。
▌5简单易学:
Go语言语法简单、结构清晰,易于学习和使用。它的标准库设计也非常简洁,便于开发者上手使用。
▌6跨平台支持:
Go语言可以在多个操作系统和硬件平台上编译执行,具有很好的跨平台支持性能。
▌7丰富的工具链:
Go语言提供了一系列的工具,例如gofmt、govet、gotest等,帮助开发者更加方便地进行代码格式化、检查、测试等工作。
▌8天然支持网络编程:
Go语言标准库中包含了丰富的网络编程接口,可以轻松实现Web应用程序和分布式系统等。
总之,Go语言具有高并发、内存安全、快速编译、简单易学、跨平台支持、丰富的工具链等核心特性,使得它成为一种很受欢迎的编程语言,在互联网、大数据、网络编程等领域得到了广泛的应用。
▌Go语言行业案例
Go语言在多个行业中都有广泛的应用,以下是一些代表性的行业案例:
互联网:谷歌、知乎等众多互联网公司都在采用Go语言进行后台开发。例如,谷歌的网络爬虫系统GoogleCrawler就是用Go语言编写的。金融:很多金融领域的公司也开始采用Go语言进行开发。例如,美国在线支付公司Stripe使用Go语言编写了一个高性能的数据分析系统。游戏:Go语言在游戏行业中也有着广泛应用。例如,腾讯的游戏服务器框架TarsGO就是用Go语言编写的。大数据:Go语言具有高并发和高性能的特点,因此在大数据领域中也受到欢迎。例如,Uber公司的Cherami消息传递系统就是用Go语言编写的。区块链:Go语言也成为了区块链领域的首选编程语言之一。例如,以太坊的客户端Geth就是使用Go语言编写的。
总之,Go语言在多个行业中都得到了广泛的应用,并且随着其不断完善和优化,其使用范围还将进一步扩大。
除了大名鼎鼎的Docker,完全用GO实现。业界最为火爆的容器编排管理系统kubernetes完全用GO实现。之后的DockerSwarm,完全用GO实现。
除此之外,还有各种有名的项目,如etcd/consul/flannel,七牛云存储等等均使用GO实现。
有人说,GO语言之所以出名,是赶上了云时代。
但为什么不能换种说法?也是GO语言促使了云的发展。
除了云项目外,还有像今日头条、UBER这样的公司,他们也使用GO语言对自己的业务进行了彻底的重构。
▌GO语言产生的宽松工作环境
go语言的产生,得益于谷歌宽松的技术研究环境。
谷歌的“20%时间”工作方式,允许工程师拿出20%的时间来研究自己喜欢的项目。
语音服务GoogleNow、谷歌新闻GoogleNews、谷歌地图GoogleMap上的交通信息等,全都是20%时间的产物。
Go语言最开始也是20%时间的产物。
这点,特别值得国内大厂学习。国内大厂流行996,极致压榨大家的工作时间,严格管控大家的技术产出,大家没有任何的创新空间、创造空间。
国内大厂的管理方式,极度不利于技术创新。享受不到技术创新、专利创新带来的赢家通吃红利。
▌Go语言开发环境搭建:
▌Go语言SDK工具包括以下几种:
Go编译器:Go编译器是将Go源代码编译成可执行文件的工具。Go编译器可以通过命令行或者集成开发环境(IDE)中进行使用。Go开发环境(IDE):Go语言没有官方推荐的IDE,但是有很多第三方的IDE可以供开发者使用。例如,GoLand、VisualStudioCode、SublimeText等。Gofmt:Gofmt是一个格式化代码的工具,可以让Go代码更加规范和易读。Govet:Govet是一个静态代码分析工具,可以检查代码中的潜在问题,并提供相应的建议。Gotest:Gotest是一个单元测试工具,可以帮助开发者编写测试用例并进行自动化测试。
总之,Go语言的开发工具丰富多样,可以根据个人需求选择最适合自己的工具进行开发。
▌Go编译器SDK的安装
Golang是一种编程语言,其安装和使用步骤如下:
安装Golang:
从官方网站下载对应操作系统的安装文件。
Allreleases-TheGoProgrammingLanguage(google.cn)(https://golang.google.cn/dl/)
尼恩的工具包里边,给大家都准备好了
Golang的官方网站提供了多个版本的二进制安装程序,可以根据不同的操作系统和硬件架构选择相应的版本。
对于Windows系统,可以下载.msi文件的安装程序,对于Linux或MacOSX系统,可以下载源代码或二进制文件的压缩包。
Windows系统,使用.msi文件的安装程序,一路按照向导,安装就ok了。
Golang的安装路径
配置环境变量:
安装完成后,我们需要将Golang的安装路径添加到系统的环境变量中。
对于Windows系统,可在“计算机”或“我的电脑”右键选择“属性”,在“高级系统设置”中点击“环境变量”,在“系统变量”中添加环境变量“GO_HOME”和“GOPATH”,分别指向Golang的安装路径和工作目录。
GO_HOME指向Golang的安装路径\nGOPATH指向工作目录
GO_HOME环境变量如下:
在Windows系统上,可以打开“控制面板”->“系统与安全”->“系统”,点击“高级系统设置”,然后点击“环境变量”按钮,在“系统变量”中找到“Path”变量,添加Golang的bin目录路径,例如“C:\\Go\\bin”。
C:\\ProgramFiles\\Go\\bin已经自动进入到了path环境变量里边,不需要手动添加了:
对于Linux或MacOSX系统,可将Golang的安装路径添加到PATH环境变量中,例如在~/.bashrc文件中添加:
exportPATH=$PATH:/usr/local/go/bin
以上就是Golang的安装和使用步骤。Golang具有高效、简单、安全等优点,适合构建各种类型的应用程序,例如服务器、网络服务、命令行工具等。
▌GoLangSDK安装和JavaSDK的安装比较
简直是一模一样,就是名字不同而已。
▌Golang集成开发工具IDE
以下是一些常用的Golang开发工具:
GoLand:由JetBrains开发,拥有丰富的功能和插件支持的集成开发环境(IDE)。VisualStudioCode:轻量级的代码编辑器,使用Go扩展可以获得与GoLand类似的功能。SublimeText:另一个流行的代码编辑器,也有很多插件可用于支持Golang开发。Vim:强大的文本编辑器,可以通过添加插件和配置来支持Golang开发。LiteIDE:专门为Golang打造的开发环境,支持自动补全等基本功能,适合初学者。Eclipse:一个广泛使用的IDE,可以通过安装相应的插件来支持Golang开发。
以上是一些常用的Golang开发工具,您可以根据个人偏好选择最适合您的开发工具。
▌GoLand安装和使用
大部分小伙伴,非常熟悉JetBrains公司的idea,这里推荐的是这个公司提供的GoLand。
GoLand是一款由JetBrains公司开发的集成开发环境(IDE),专门用于Go编程语言。
下面是GoLand的安装和使用步骤:
下载安装包:
从官方网站https://www.jetbrains.com/go/download/下载对应操作系统的安装文件。尼恩的工具包里边,给大家都准备好了
安装GoLand:
运行安装包并按照提示进行安装。
打开GoLand:
打开GoLand后,会看到一个欢迎界面。在这里可以新建项目、打开已有项目等。
▌创建项目:
在欢迎界面选择&34;,选择项目类型和存储位置,然后点击&34;按钮。
创建一个项目
▌配置GoSDK:
打开File->Settings菜单,在左侧导航栏中选择&34;,然后在右侧&34;选项中添加你的GoSDK路径,例如“/usr/local/go”。
完成之后,空项目如下:
▌编写代码:
在编辑器中编写代码,new一个新的文件,名字为hello,类型为simpleapplication,如下:
新的go文件创建好了,如下:
编写一个helloworld案例:
一共是两句:
一句是import&34;一句是fmt.Println(&34;)
第一句什么意思呢?import&34;是Go语言中用于导入标准库fmt的语句。
fmt提供了格式化输入输出的函数,例如:Println()和Printf()等,可以在控制台打印输出内容。
这个包也包括了一些其他的函数,如字符串格式化和解析、文件读写等。
使用import&34;可以让你在Go程序中使用fmt包中的函数,从而方便地进行输入输出操作。
例如上面的代码:
import&34;\n\nfuncmain(){\nfmt.Println(&34;)\n}
这段代码中,我们使用import&34;导入了fmt包。
第二句fmt.Println(&34;)是什么意思呢?
fmt.Println(&34;)是Go语言中用于向控制台输出&34;的语句。
Println是位于fmt包中的Println()函数。
在这个例子中,我们使用fmt.Println()函数按顺序打印给定参数,并在最后一个参数后添加换行符。
因此,将&34;作为参数传递给fmt.Println()函数将在控制台上显示Hello,World!并自动换行。
main()函数中调用了Println()函数来输出Hello,World!到控制台上。
▌运行程序:
点击编辑器顶部的运行按钮(绿色三角形)或者使用快捷键Shift+F10来运行程序。
运行结果如下:
以上就是GoLand的安装和使用步骤。
GoLand具有良好的用户界面、丰富的功能和插件支持,可以提高开发效率,让Go语言的开发变得更加简单和高效。
▌Go的执行原理以及Go的命令
▌Go的源码文件分类:
在Go中,源码文件分为4种类型:
命令源码文件:以.go为扩展名的文件。命令源码文件属于main包,包含main方法,是程序的运行入口,是每个可独立运行的程序必须拥有的。库源码文件:以.go为扩展名的文件。不包含main入口方法。测试文件:以_test.go为后缀的源码文件测试文件用于编写单元测试和性能测试等测试代码。C语言源码文件:
以.c、.h或.s等后缀名的文件,用于包含一些C语言实现的辅助代码或工具。
无源码文件:不包含代码的文件,例如README、LICENSE等。这些文件虽然对于程序的运行没有影响,但对于开发者来说具有重要意义。
其中,.go源码文件和_test.go测试文件是最常见的文件类型。
总体而言:
在一个Go工程中,通常将所有的源码文件组织在一个或多个package中,并对外提供相应的接口。测试文件则用于测试这些接口的正确性和性能。除此之外,Go还支持嵌套的package和vendor目录,使得工程的组织结构更加灵活和清晰。同时,在Go编译时,会自动生成相关的二进制文件、静态库或动态链接库等文件。
▌1、命令源码文件:
命令源码文件的两个特征:
声明自己属于main代码包、包含main函数,这是一个无参数声明和结果声明、名字叫做main的函数。
命令源码文件的作用:
命令源码文件是Go程序的入口。命令源码文件是可以单独运行的。
可以使用gorun命令直接运行,也可以通过gobuild或goinstall命令得到相应的可执行文件。所以命令源码文件是可以在机器的任何目录下运行的。
step1:通过gorun命令直接运行命令源码文件:
同一个代码包中最好也不要放多个命令源码文件。多个命令源码文件虽然可以分开单独gorun运行起来,但是无法通过gobuild和goinstall。
命令源码文件被goinstall安装以后,变成了exe可执行文件,一般去了哪儿呢?
如果GOPATH只有一个工作区,那么相应的可执行文件会被存放当前工作区的bin文件夹下;如果有多个工作区,就会安装到GOBIN指向的目录下。
复制hello.go为hello2.go文件,并修改里面的内容:
hello目录下有两个go文件了,两个命令源码文件:
一个是hello.go一个是hello2.go。
先说明一下,在上述文件夹中放了两个命令源码文件,同时都声明自己属于main代码包:packagemain。
打开控制台终端,进入工目录,执行gorun命令执行两个命令源码文件,可以看到两个go文件都可以被执行:
上面执行gorun没有问题,但是执行gobuild和goinstall问题就来了。
看看会发生什么:
PSC:\\Users\\nien\\go\\src\\awesomeProject>gobuild\nawesomeProject\n.\\hello2.go:5:6:mainredeclaredinthisblock\n.\\hello.go:5:6:otherdeclarationofmain
运行效果图:
这也就证明了一个问题:多个命令源码文件虽然可以分开单独gorun运行起来,但是无法通过gobuild和goinstall。
▌2、库源码文件
库源码文件就是不具备命令源码文件上述两个特征的源码文件。存在于某个代码包中的普通的源码文件。
在Go语言中,库源码文件也需要遵循一定的命名规则和编写规范。
以下是一些通用的概念和步骤:
创建一个新的package,package名称应该与文件夹名称相同。在package中编写代码,可以包含多个源码文件。对外暴露需要让其他package访问的函数、类型或变量,需要在其名称前面添加大写字母。
funcAdd(a,bint)int{\nreturna+b\n}
编写文档注释,可以使用godoc命令来查看文档。
//Add将两个整数相加并返回结果\nfuncAdd(a,bint)int{\nreturna+b\n}
使用gobuild或goinstall命令来构建和安装库,安装后可以在其他程序中import并使用。
$goinstallgithub.com/xxx/yyy
以上是库源码文件的基本流程和示例,更多细节和高级用法可以参考官方文档:
HowtoWriteGoCode-TheGoProgrammingLanguage(google.cn)(https://golang.google.cn/doc/code34;testing&34;addfunctionfailed&34;fmt&34;Hello,World!&34;你的输入是:%s&34;fmt&34;Hello,World!&34;你的输入是:%s&39;EOF&internal\npackagefilecommand-line-arguments=C:\\Users\\nien\\AppData\\Local\\go-build\\69\\692a2eab765b2347d83f7dbba3333eeb19fd3151614ee8c7a70f2d5270f9fa95-d\npackagefilefmt=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\fmt.a\npackagefileruntime=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\runtime.a\npackagefileerrors=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\errors.a\npackagefileinternal/fmtsort=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\fmtsort.a\npackagefileio=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\io.a\npackagefilemath=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\math.a\npackagefileos=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\os.a\npackagefilereflect=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\reflect.a\npackagefilestrconv=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\strconv.a\npackagefilesync=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\sync.a\npackagefileunicode/utf8=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\unicode\\utf8.a\npackagefileinternal/abi=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\abi.a\npackagefileinternal/bytealg=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\bytealg.a\npackagefileinternal/cpu=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\cpu.a\npackagefileinternal/goarch=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\goarch.a\npackagefileinternal/goexperiment=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\goexperiment.a\npackagefileinternal/goos=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\goos.a\npackagefileruntime/internal/atomic=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\runtime\\internal\\atomic.a\npackagefileruntime/internal/math=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\runtime\\internal\\math.a\npackagefileruntime/internal/sys=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\runtime\\internal\\sys.a\npackagefileinternal/reflectlite=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\reflectlite.a\npackagefilesort=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\sort.a\npackagefilemath/bits=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\math\\bits.a\npackagefileinternal/itoa=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\itoa.a\npackagefileunicode/utf16=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\unicode\\utf16.a\npackagefileunicode=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\unicode.a\npackagefileinternal/race=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\race.a\npackagefileinternal/syscall/windows/sysdll=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\syscall\\windows\\sysdll.a\npackagefilepath=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\path.a\npackagefileinternal/syscall/windows/registry=C:\\ProgramFiles\\Go\\pkg\\windows_amd64\\internal\\syscall\\windows\\registry.a\nmodinfo&34;\nEOF\nmkdir-p$WORK\\b001\\exe\\\ncd.\n&34;-o&34;-importcfg&34;-s-w-buildmode=pie-buildid=0bpO_A9QPFYvfKrLftok/uW5e8pk9X87KweqPqgKm/3kCXaL5ldAaRrmyF6wVL/0bpO_A9QPFYvfKrLftok-extld=gcc&34;\n$WORK\\b001\\exe\\hello.exe
这里可以看到创建了两个临时文件夹b001和exe,先执行了compile命令,然后link,生成了归档文件.a和最终可执行文件,最终的可执行文件放在exe文件夹里面。
命令的最后一步就是执行了可执行文件。
◆gorun的参数详解
命令格式:gorun[可选参数]。命令作用:编译完成并马上运行Go程序。特殊说明:gorun只支持属于main包的一个或多个文件作为参数,不然是不能进行编译的。如下示例:
1.有一个属于包hello的文件hello.go。\n2.执行编译gorunhello/hello.go\n\n然后提示错误:gorun:cannotrunnon-mainpackage
常用参数:
参数名
格式
含义
-o
-ofile
指定编译后二进制文件名
-importcfg
-importcfgfile
从文件中读取倒入配置
-s
-s
省略符号表并调试信息
-w
-w
省略DWARF符号表
-buildmode
-buildmodemode
设置构建模式-默认为exe
-buildid
-buildidid
将ID记录为Go工具链的构建ID
-extld
-extldlinker
设置外部链接器-默认为clang或者gcc
-work
-work
设置该参数后不会在程序结束后删掉编译的临时文件,可用于参看编译生成的文件
-n
-n
加上该参数可以查看编译的过程,但不会继续执行编译后的二进制文件
-x
-x
加上该参数可以查看编译的过程,会继续执行编译后的二进制文件
这里需要对后面两个参数进行额外说明。
在使用gorun编译时候会将二进制文件放到一个临时目录(位置和操作系统或GOTMPDIR有关。
所以想进行查看可以使用-s或-n命令。这里简单执行如下:
执行:gorun-n-workhello.go\n\n输出:(不同的环境和版本可能有一些区别)
上面的输出主要干了这些事:
创建编译依赖需要的临时目录。在编译过程中设置一个临时环境变量WORK,用于编译的工作区并执行编译后的二进制文件。可以通过GOTMPDIR设置。编译和生产编译所需的依赖。编译如标准库、外部依赖、自身代码,然后生成、链接对应的归档和编译配置文件。创建exe目录。创建并进入编译二进制需要的零食目录。生成可执行文件。执行可执行文件。如上列中:$WORK/b001/exe/hello.exe。
可以看到,最终gorun命令是生成了2个文件,一个是归档文件,一个是可执行文件。
归档文件在哪儿呢?
gorun命令在第二次执行的时候,如果发现导入的代码包没有发生变化,那么gorun不会再次编译这个导入的代码包。直接静态链接进来。
◆importcfg.link文件
importcfg.link是Go1.17中引入的一个新特性,用于指定链接器的行为。
importcfg.link文件是一个文本文件,其中包含一组指令,可以控制程序的链接方式和依赖项。
在默认情况下,Go工具链会自动处理程序的依赖关系并链接生成可执行文件。但是在某些情况下,例如需要使用特定的链接器、链接静态库或者禁止使用某些库等,可能需要手动配置链接器的行为。
下面是一个importcfg.link文件的示例:
packagefilemypkg.a=/path/to/mypkg.a\n\nimport(\n&34;\n_&34;\n)\n\nunresolvedx/x_test.go:import&34;isnotaknownimportpath.
在上面的示例中,我们使用packagefile指令将mypkg.a静态库的路径设置为/path/to/mypkg.a,然后使用import命令导入crypto/tls和github.com/mattn/go-sqlite3包,并显示一个unresolved的错误消息来指示未知的导入路径。
需要注意的是,只有在支持-importcfg标志的Go工具链版本中才能使用importcfg.link文件,例如Go1.17及更高版本。对于旧版本的Go工具链,可以使用-extldflags等标志来手动指定链接器的行为。
▌2、gobuild
gobuild命令主要是用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。
如果是普通包,当你执行gobuild命令后,不会产生任何文件。如果是main包,当只执行gobuild命令后,会在当前目录下生成一个可执行文件。如果需要在$GOPATH/bin目录下生成相应的exe文件,需要执行goinstall或者使用gobuild-o路径/可执行文件。gobuild命令默认会编译当前目录下的所有go文件。如果某个文件夹下有多个文件,而你只想编译其中某一个文件,可以在gobuild之后加上文件名,例如gobuilda.go;你也可以指定编译输出的文件名。比如,我们可以指定gobuild-o可执行文件名,默认情况是你的package名(非main包),或者是第一个源文件的文件名(main包)。gobuild会忽略目录下以”_”或者”.”开头的go文件。如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。
gobuild用于编译我们指定的源码文件或代码包以及它们的依赖包。但是注意如果用来编译非命令源码文件,即库源码文件,gobuild执行完是不会产生任何结果的。这种情况下,gobuild命令只是检查库源码文件的有效性,只会做检查性的编译,而不会输出任何结果文件。
gobuild编译命令源码文件,则会在该命令的执行目录中生成一个可执行文件,来个例子也印证了这个过程。
编译后,得到exe文件。
目录在哪儿呢?gobuild后面不追加目录路径的话,它就把当前目录作为代码包并进行编译。
gobuild-obin/hello.exehello.go
◆详解:gobuild究竟干了些什么呢?
gobuild命令究竟做了些什么呢?
gobuild执行过程和gorun大体相同,唯一不同的就是在最后一步,gorun是执行了可执行文件,但是gobuild命令,只是把库源码文件编译了一遍,然后把可执行文件移动到了当前目录的文件夹中。
总结一下如下图:
◆gobuild命令参数
命令格式:gobuild[可选参数]。命令作用:编译指定的源文件、软件包和其他依赖,但是不会在编译后执行二进制文件。特殊说明:gobuild和gorun在编译过程中其实是差不多的,不同之处是gobuild会生成编译好二进制文件并删掉编译过程产生的临时目录。若没有-o指定文件名,则和当前目录名一致。
执行:gobuild-xmain.go\n\n输出:\n…\n…\nmv$WORK/b001/exe/a.outmain\n//多了这步\nrm-r$WORK/b001/
常用参数:
参数名
格式
含义
-o
-ofile
指定编译后二进制文件名
-a
-a
强制重新编译涉及的依赖
-s
-s
省略符号表并调试信息
-w
-w
省略DWARF符号表
-p
-p
指定编译过程中的并发数,默认为CPU数
-work
-work
设置该参数后不会在程序结束后删掉编译的临时文件,可用于参看编译生成的文件
-n
-n
加上该参数可以查看编译的过程,但不会继续执行编译后的二进制文件
-x
-x
加上该参数可以查看编译的过程,会继续执行编译后的二进制文件
▌3、goinstall命令
goinstall命令是用来编译并安装代码包或者源码文件的。
goinstall命令在内部实际上分成了两步操作:
第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到$GOPATH/pkg或者?$GOPATH/bin。
实际上,goinstall命令只比gobuild命令多做了一件事,即:安装编译后的结果文件到指定目录。
什么时候生产可执行文件?什么时候生产.a应用包?
可执行文件:一般是goinstall带main函数的go文件产生的,有函数入口,所有可以直接运行。.a应用包:一般是goinstall不包含main函数的go文件产生的,没有函数入口,只能被调用。
goinstall用于编译并安装指定的代码包及它们的依赖包。当指定的代码包的依赖包还没有被编译和安装时,该命令会先去处理依赖包。与gobuild命令一样,传给goinstall命令的代码包参数应该以导入路径的形式提供。并且,gobuild命令的绝大多数标记也都可以用于
安装代码包会在当前工作区的pkg的平台相关目录下生成归档文件(即.a文件)。安装命令源码文件会在当前工作区的bin目录(如果GOPATH下有多个工作区,就会放在GOBIN目录下)生成可执行文件。
同样,goinstall命令如果后面不追加任何参数,它会把当前目录作为代码包并安装。这和gobuild命令是完全一样的。
goinstall命令后面如果跟了代码包导入路径作为参数,那么该代码包及其依赖都会被安装。
goinstall命令后面如果跟了命令源码文件以及相关库源码文件作为参数的话,只有这些文件会被编译并安装。
当代码包中有且仅有一个命令源码文件的时候,在文件夹所在目录中执行gobuild命令,会在该目录下生成一个与目录同名的可执行文件。
执行goinstall之前,一般情况下,需要提前设置GOBIN环境变量:
在GOBIN下生成对应的可执行文件。
◆什么是GOBIN?
GOBIN是一个环境变量,用于指定goinstall命令安装可执行文件的位置。
除了GOBIN之外,可以使用goenv看下其他的环境变量
当我们使用goinstall命令编译Go程序时,生成的可执行文件会被安装到$GOBIN目录下(如果没有设置GOBIN,则默认为$GOPATH/bin目录下)。
在Linux或macOS中,可以通过以下命令设置GOBIN:
$exportGOBIN=/path/to/go/bin
在Windows中,可以使用以下命令设置GOBIN:
setxGOBIN&34;\neg:\nsetxGOBIN&34;
还有,如果要直接运行已安装的程序,需要注意的是:GOBIN需要在系统的PATH环境变量中才能起作用。所以,可以将$GOBIN或者$GOPATH/bin添加到PATH环境变量中,以便在任何目录下都可以直接运行已安装的程序。
通常情况下,我们会将$GOPATH/bin添加到PATH中,这样安装的程序就可以直接在终端中使用了。
例如:
$exportPATH=$PATH:$GOPATH/bin
或者在Windows中:
>setxPATH&34;
这样,在任何目录下都可以直接运行已安装的程序。
◆详解:goinstall究竟干了些什么呢?
goinstall命令究竟做了些什么呢?
goinstall前面几步依旧和gorun、gobuild完全一致,只是最后一步的差别.
最后一步,goinstall会把命令源码文件安装到当前工作区的bin目录(如果GOPATH下有多个工作区,就会放在GOBIN目录下)。如果是库源码文件,就会被安装到当前工作区的pkg的平台相关目录下。
总结一下如下图:
◆goinstall命令参数
命令格式:goinstall[可选参数]。命令作用:编译并安装源文件、软件包,即把编译后的文件(可执行二进制文件、归档文件等)安装到指定的目录中。特殊说明:将编译后的文件(可执行二进制文件、归档文件等)安装到指定的目录中。若设置了环境变量GOBIN,则把二进制可执行文件移到该目录。若禁用Gomodules则放到GOOS_$GOARCH下。
执行:goinstall-xmain.go\n\n输出:\n…\nmkdir-p/Users/ucwords/go/bin/\n…\nmv$WORK/b001/exe/a.out/Users/ucwords/go/bin/目标目录(gomodules的目录名)\nrm-r$WORK/b001/
常用参数:
参数名
格式
含义
-o
-ofile
指定编译后二进制文件名
-a
-a
强制重新编译涉及的依赖
-s
-s
省略符号表并调试信息
-w
-w
省略DWARF符号表
-p
-p
指定编译过程中的并发数,默认为CPU数
-work
-work
设置该参数后不会在程序结束后删掉编译的临时文件,可用于参看编译生成的文件
-n
-n
加上该参数可以查看编译的过程,但不会继续执行编译后的二进制文件
-x
-x
加上该参数可以查看编译的过程,会继续执行编译后的二进制文件
▌4、goget
goget命令用于从远程代码仓库(比如Github)上下载并安装代码包。
注意,goget命令会把当前的代码包下载到$GOPATH中的第一个工作区的src目录中,并安装。
使用goget下载第三方包的时候,依旧会下载到$GOPATH的第一个工作空间,而非vendor目录。当前工作链中并没有真正意义上的包依赖管理,不过好在有不少第三方工具可选。
如果在goget下载过程中加入-d标记,那么下载操作只会执行下载动作,而不执行安装动作。比如有些非常特殊的代码包在安装过程中需要有特殊的处理,所以我们需要先下载下来,所以就会用到-d标记。
还有一个很有用的标记是-u标记,加上它可以利用网络来更新已有的代码包及其依赖包。如果已经下载过一个代码包,但是这个代码包又有更新了,那么这时候可以直接用-u标记来更新本地的对应的代码包。如果不加这个-u标记,执行goget一个已有的代码包,会发现命令什么都不执行。只有加了-u标记,命令会去执行gitpull命令拉取最新的代码包的最新版本,下载并安装。
命令goget还有一个很值得称道的功能——智能下载。在使用它检出或更新代码包之后,它会寻找与本地已安装Go语言的版本号相对应的标签(tag)或分支(branch)。比如,本机安装Go语言的版本是1.x,那么goget命令会在该代码包的远程仓库中寻找名为“go1”的标签或者分支。如果找到指定的标签或者分支,则将本地代码包的版本切换到此标签或者分支。如果没有找到指定的标签或者分支,则将本地代码包的版本切换到主干的最新版本。
goget常用的一些标记如下:
标记名称
标记描述
-d
让命令程序只执行下载动作,而不执行安装动作。
-f
仅在使用-u标记时才有效。该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。如果下载并安装的代码包所属的项目是你从别人那里Fork过来的,那么这样做就尤为重要了。
-fix
让命令程序在下载代码包后先执行修正动作,而后再进行编译和安装。
-insecure
允许命令程序使用非安全的scheme(如HTTP)去下载指定的代码包。如果你用的代码仓库(如公司内部的Gitlab)没有HTTPS支持,可以添加此标记。请在确定安全的情况下使用它。
-t
让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
-u
让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
总结一下如下图:
▌5、其他命令
◆goclean
goclean命令是用来移除当前源码包里面编译生成的文件,这些文件包括
_obj/旧的object目录,由Makefiles遗留_test/旧的test目录,由Makefiles遗留_testmain.go旧的gotest文件,由Makefiles遗留test.out旧的test记录,由Makefiles遗留build.out旧的test记录,由Makefiles遗留*.[568ao]object文件,由Makefiles遗留DIR(.exe)由gobuild产生DIR.test(.exe)由gotest-c产生MAINFILE(.exe)由gobuildMAINFILE.go产生
◆gofmt
gofmt命令主要是用来帮你格式化所写好的代码文件。
比如我们写了一个格式很糟糕的test.go文件,我们只需要使用fmtgotest.go命令,就可以让go帮我们格式化我们的代码文件。
但是我们一般很少使用这个命令,因为我们的开发工具一般都带有保存时自动格式化功能,这个功能底层其实就是调用了gofmt命令而已。
使用gofmt命令,更多时候是用gofmt,而且需要参数-w,否则格式化结果不会写入文件。gofmt-wsrc,可以格式化整个项目。
◆gotest命令
gotest命令是Go语言自带的一个测试工具,用于执行程序中的单元测试和性能测试。
执行gotest命令时,它会自动查找当前目录及其子目录下的所有以_test.go结尾的文件,并执行其中的测试函数。
gotest命令支持多种参数和选项,可以通过gohelptest查看完整的帮助文档。其中一些常用的参数和选项包括:
-v:输出详细的测试日志信息,包括测试用例的名称、运行时间、每个测试函数的输出结果等。-run:指定需要运行的测试函数的名称或正则表达式。-cover:生成代码覆盖率报告,报告中会显示哪些代码被测试覆盖到了,哪些没有被覆盖到。-bench:执行性能测试,并输出测试结果。可以指定-benchmem选项来输出内存分配的情况。例如,执行gotest-v./…命令将会递归地运行当前目录及其子目录下所有的测试函数,并输出详细的测试日志信息。
godoc命令
作用:打印出程序实体说明文档。后可不跟参数或一个参数或两个参数格式:godoc标记参数标记和参数可以不填,
godoc在main包下,执行godoc默认是不打印的,除非加上-cmd标记,后面会讲在非main包下,执行godoc打印当前代码包文档及其程序实体列表(程序实体:变量、常量、函数、结构体以及接口)godoc标记标记有如下:
标记
含义
-c
区分后跟参数的大小写,比如:godoc-cpackageOne(默认不写是不区分大小写)
-cmd
加入此标记会使godoc命令同时打印出main包中的可导出的程序实体(其名称的首字母大写)的文档。默认下,这些文档是不会被打印的。
-u
加入此标记后会使godoc命令同时打印出不可导出的程序实体(其名称的首字母小写)的文档。默认下,这部分文档是不会被打印出来的。
godoc参数godoc参数,比如:godochttp.Request会输出http包下Request文档说明,也可以跟两个参数,见下godoc参数1参数2,比如:godocnet/httpRequest,需要说明的是第一个参数要写完整的导入路径,我个人理解就是在参数1的范围下,打印出参数2的文档说明详情,其实此时doc和参数之间还可以加入标记,相当于打印文档时又加入了条件判断。
◆gofix
用来修复以前老版本的代码到新版本,例如go1之前老版本的代码转化到go1
◆goversion
查看go当前的版本
◆goenv
查看当前go的环境变量
◆golist
列出当前全部安装的package
▌golang代码规范:
命名在所有的编程语言中都是有规则可寻的,也是需要遵守的,只有我们有了好的命名习惯才可以写出好的代码,例如我们在生活中对建筑的命名也是希望可以表达这个建筑的含义和作用。
在Go语言中也是一样的,Go语言的函数名,变量名,常量名,类型名和包的命名也是都遵循这一规则的:一个一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。
大写字母和小写字母是不同的:Car和car是两个不同的名字。
Go语言中也有类似java的关键字,且关键字不能用于自定义名字,只能在特定语法结构中使用.
breakdefaultfuncinterfaceselect\ncasedefergomapstruct\nchanelsegotopackageswitch\nconstfallthroughifrangetype\ncontinueforimportreturnvar
除此之外Go语言中还有30多个预定义的名字,比如int和ture等
内建常量:truefalseiotanil\n内建类型:intint8int16int32int64uintuint8uint16uint32uint64uintptr\nfloat32float64complex128complex64boolbyterunestringerror\n内建函数:makelencapnewappendcopyclosedeletecomplexrealimagpanicrecover
通常我们在Go语言编程中推荐的命名方式是驼峰命名例如:ReadAll,不推荐下划线命名。
▌好的规范的价值
对于团队而言,好的规范虽一定程度降低开发自由度,但带来的好处是不可忽视的
可以减少bug的产生降低review和接手成本,通过统一规范,每个人代码风格统一,理想情况看谁的代码就像自己写的一样利于写一些片段脚本提高代码可读性
下面总结了平时项目中必须遵守的规范,后续会不断更新,也可以来尼恩的高并发社区《技术自由圈(原名:疯狂创客圈)》中交流。
▌Import规范
原则上遵循goimports规范,goimports会自动把依赖按首字母排序,并对包进行分组管理,通过空行隔开,默认分为本地包(标准库、内部包)、第三方包。标准包永远位于最上面的第一组使用完整路径,不要使用相对路径包名和git路径名不一致时,或者多个相同包名冲突时,使用别名代替
▌错误处理
error作为函数的值返回,必须对error进行处理,或将返回值赋值给明确忽略。error作为函数的值返回且有多个返回值的时候,error必须是最后一个参数。
//不建议\nfuncdo()(error,int){\n}\n//建议\nfuncdo()(int,error){\n}
优先处理错误,能return尽早return。理想情况代码逻辑是平铺的顺着读就能看懂,过多的嵌套会降低可读性
//不建议\niferr!=nil{\n//errorhandling\n}else{\n//normalcode\n}\n\n//建议\niferr!=nil{\n//errorhandling\nreturn//orcontinue,etc.\n}\n//normalcode
错误返回优先独立判断,不与其他变量组合判断
//不建议\nx,y,err:=f()\niferr!=nil||y==nil{\nreturnerr//当y与err都为空时,函数的调用者会出现错误的调用逻辑\n}\n\n//建议\nx,y,err:=f()\niferr!=nil{\nreturnerr\n}\nify==nil{\nreturnfmt.Errorf(&34;)\n}
▌panic
在业务逻辑处理中禁止使用panic。在main包中只有当完全不可运行的情况可使用panic,例如:文件无法打开,数据库无法连接导致程序无法正常运行。对于其它的包,可导出的接口不能有panic,只能在包内使用。建议在main包中使用log.Fatal来记录错误,这样就可以由log来结束程序,或者将panic抛出的异常记录到日志文件中,方便排查问题。panic捕获只能到goroutine最顶层,每个自行启动的goroutine,必须在入口处捕获panic,并打印详细堆栈信息或进行其它处理。
▌recover
recover用于捕获runtime的异常,禁止滥用recover。必须在defer中使用,一般用来捕获程序运行期间发生异常抛出的panic或程序主动抛出的panic。
▌单元测试
单元测试文件名命名规范为example_test.go。测试用例的函数名称必须以Test开头,例如TestExample。如果存在funcFoo,单测函数可以带下划线,为funcTest_Foo。如果存在func(b*Bar)Foo,单测函数可以为funcTestBar_Foo。下划线不能出现在前面描述情况以外的位置。每个重要的可导出函数都要首先编写测试用例,测试用例和正规代码一起提交方便进行回归测试。
▌类型断言失败处理
typeassertion的单个返回值形式针对不正确的类型将产生panic。
因此,请始终使用“commaok”的惯用法。
//不建议\nt:=i.(string)\n\n//建议\nt,ok:=i.(string)\nif!ok{\n//优雅地处理错误\n}
▌注释
在编码阶段同步写好变量、函数、包注释,注释可以通过godoc导出生成文档。程序中每一个被导出的(大写的)名字,都应该有一个文档注释。所有注释掉的代码在提交codereview前都应该被删除,除非添加注释讲解为什么不删除,并且标明后续处理建议(比如删除计划)。
▌命名规范
文件名应该采用小写,并且使用下划线分割各个单词,文件名尽量采用有意义简短的文件结构体,接口,变量,常量,函数均采用驼峰命名
命名规范设计变量、常量、全局函数、结构、方法等等的命名。
Go语言从语法层面进行了以下限定:任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则以小写字母开头。
当一个命名以一个大写字母开头,如GetUserName,那么使用这种形式的标识符的对象就可以被外部包的代码使用(客户端程序需要先导入这个包),这被称为导出(如面向对象语言中的public);命名如果以小写字母开头,则对包外是不可兼得,但是他们在整个包的内部是可见的并且可用的(如面向对象语言中的private)
▌包命名
保持package的名字和目录保持一致,尽量采用有意义的包名,简短、有意义且尽量和标准库不要冲突。
包命应该为小写单词,不要使用下划线或者混合大小写。
packagepsych\npackageservice
▌文件命名
尽量采用有意义且简短的文件名,应为小写单词,使用下划线分隔各个单词
customer_dao.go
▌结构体命名
采用驼峰命名法,首字母根据访问控制大小写
struct声明和初始化格式采用多行,例:
typeCustomerOrderstruct{\nNamestring\nAddressstring\n}\norder:=CustomerOder{&34;,&34;}
▌接口命名
命名规则基本上与上面结构体类似
单个函数的结构名以“er”作为后缀,例如Reader,Writer
typeReaderinterface{\nRead(p[]byte)(nint,errerror)\n}
▌首字母访问控制规则
在Golang中,如果一个标识符(如变量、函数名等)的首字母大写,表示它是可导出的(exported),即可以被外部包访问和使用;如果首字母小写,则表示它是不可导出的(unexported),只能在当前包内部使用。
这是因为Golang的访问控制是基于标识符的命名规则来实现的,首字母大小写的不同决定了该标识符的可见性。具体来说,对于一个标识符,如果它的首字母大写,那么它就是公开(public)的,可以被其他包导入后直接使用;如果首字母小写,那么它就是私有(private)的,只能在当前包内部使用,对于其他包来说是不可见的。
举个例子,假设我们有一个包叫做&34;,其中定义了一个变量&34;:
packageexample\n\nvarNamestring=&34;
由于变量&34;的首字母大写,所以它是可导出的,可以被其他包导入后直接使用:
packagemain\n\nimport(\n&34;\n&34;\n)\n\nfuncmain(){\nfmt.Println(example.Name)//输出:hello\n}
但如果把&34;的首字母改为小写,那么它就是不可导出的,外部包无法访问和使用它:
packageexample\n\nvarnamestring=&34;\n\npackagemain\n\nimport(\n&34;\n&34;\n)\n\nfuncmain(){\nfmt.Println(example.name)//编译错误:nameundefined(cannotrefertounexportednameexample.name)\n}
因此,在Golang中,通过标识符的命名规则来实现访问控制,可以有效地保障代码的封装性和安全性。
▌方法命名
Golang中的方法命名遵循一般的命名规则,遵守首字母大小访问控制规则,同时,也建议采用驼峰式命名法(camelcase)。
在Golang中,方法通常是与某个类型(结构体、接口等)关联的函数。方法名应该简洁明了,描述清楚该方法的作用和功能,通常使用动词加上一定的描述或说明来命名。
以下是一些常见的方法命名规范:
GetXxx:表示获取某个属性或值,例如GetName表示获取名称。SetXxx:表示设置某个属性或值,例如SetAge表示设置年龄。AddXxx:表示添加某个元素或对象,例如AddItem表示添加一个元素。RemoveXxx:表示移除某个元素或对象,例如RemoveItem表示移除一个元素。DoXxx:表示执行某个操作,例如DoSomething表示执行某个操作。XxxWithYyy:表示使用Yyy作为参数执行Xxx操作,例如WriteWithTimeout表示使用超时参数执行写操作。
需要注意的是,方法名应该尽量避免使用缩写或缩略语,除非是广泛使用的常见缩写,否则容易引起歧义和误解。同时,方法名也应该尽量避免冗长,以保持代码的简洁性和可读性。
举个例子,假设我们有一个结构体叫做&34;,它有一个方法&34;:
typePersonstruct{\nNamestring\nAgeint\n}\n\nfunc(p*Person)SayHello(){\nfmt.Printf(&34;,p.Name,p.Age)\n}
在上面的例子中,我们使用了驼峰式命名法来命名方法&34;,同时结合动词和名词来描述该方法的作用。这样做可以使代码更易读、易懂。
▌变量命名
和结构体类似,一般遵循驼峰命名法,首字母根据访问控制大小写,但遇到特有名词时,需要遵循以下规则:
如果变量为私有,且特有名词为首个单词,则使用小写,如appService;
若变量为bool类型,则名称应以has、is、can或allow开头
varisExistbool\nvarhasConflictbool\nvarcanManagebool\nvarallowGitHookbool
▌常量命名
常量需使用全部大写字母组成,并使用下划线分词
constAPP_URL=&34;
如果是枚举类型的常量,需要先创建对应类型
typeSchemestring\n\nconst{\nHTTPScheme=&34;\nHTTPSScheme=&34;\n}
▌单元测试命名
在Golang中,单元测试采用的是内置的testing包,我们需要遵循一定的命名规范来编写测试函数的名称。Golang的测试函数命名规则是:
测试函数必须以Test开头,例如TestAdd。测试函数参数列表中必须有一个类型为*testing.T的参数,例如funcTestAdd(t*testing.T)。测试函数的参数列表中不应该包含任何其他参数。测试函数的名称应该具有描述性,可以使用驼峰式命名法,例如TestAddTwoNumbers。
另外,对于某个包下的所有测试函数,我们可以将它们组织成一个表格测试(Table-DrivenTests)的形式,这样可以使测试代码更加简洁、清晰和易于维护。表格测试的命名规则如下:
表格测试函数必须以Test开头,例如TestAddTable。表格测试函数参数列表中必须有一个类型为*testing.T的参数,例如funcTestAddTable(t*testing.T)。表格测试函数的参数列表中不应该包含任何其他参数。表格测试函数应该包含一个结构体切片或映射作为输入参数,同时也需要定义一个期望输出结果的切片或映射,例如:
funcTestAddTable(t*testing.T){\ntests:=[]struct{\na,bint\nwantint\n}{\n{1,2,3},\n{0,0,0},\n{-1,-1,-2},\n}\nfor_,tt:=rangetests{\ngot:=Add(tt.a,tt.b)\nifgot!=tt.want{\nt.Errorf(&34;,tt.a,tt.b,got,tt.want)\n}\n}\n}
在这个例子中,我们使用了表格测试的形式来测试Add函数的功能和正确性。其中,tests切片包含了多组输入参数和期望输出结果,每次循环都会进行一次测试,并将实际输出结果与期望输出结果进行比较,如果不一致则使用t.Errorf输出错误信息和相关参数。
需要注意的是,单元测试应该覆盖代码中的所有核心功能和可能出现的异常情况,以保证代码的质量和可靠性。同时,测试代码也需要遵循相应的编程规范和最佳实践,提高测试代码的可读性和可维护性。
▌控制结构
if语句
//不建议,变量优先在左\nifnil!=err{\n}\n//建议这种\niferr!=nil{\n}\n\n//不建议,bool类型变量直接进行\nifhas==true{\n}\n//建议\nifhas{\n}
switch语句,必须有default哪怕什么都不做业务代码禁止使用goto,其他框架或底层源码推荐尽量不用。
▌函数
函数返回相同类型的两个或三个参数,或者如果从上下文中不清楚结果的含义,使用命名返回,其它情况不建议使用命名返回。
func(n*Node)Parent1()*Node\n\nfunc(n*Node)Parent2()(*Node,error)\n\nfunc(f*Foo)Location()(lat,longfloat64,errerror)
▌Defer
当存在资源管理时,应紧跟defer函数进行资源的释放。判断是否有错误发生之后,再defer释放资源。
resp,err:=http.Get(url)\niferr!=nil{\nreturnerr\n}\n//defer放到错误处理之后,不然可能导致panic\ndeferresp.Body.Close()
禁止在循环中的延迟函数中使用defer,因为defer的执行需要外层函数的结束才会释放,未来会有很多坑
//不要这样使用\nfuncfilterSomething(values[]string){\nfor_,v:=rangevalues{\nfields,err:=db.Query(xxx)\niferr!=nil{\n}\ndeferfields.Close()\n//xxx\n}\n}\n\n//但是可以使用这种方式\nfuncfilterSomething(values[]string){\nfor_,v:=rangevalues{\nfunc(){\nfields,err:=db.Query(xxx)\niferr!=nil{\n//xxx\n}\ndeferfields.Close()\n//xxxx\n}()\n}\n}
▌魔法数字
如果魔法数字出现超过2次,则禁止使用。
funcgetArea(rfloat64)float64{\nreturn3.14*r*r\n}\n\nfuncgetLength(rfloat64)float64{\nreturn3.14*2*r\n}\n\n//建议定义一个常量代替魔法数字\n//PIxxx\nconstPI=3.14
▌代码规范性常用工具
上面提到了很过规范,go语言本身在代码规范性这方面也做了很多努力,很多限制都是强制语法要求,例如左大括号不换行,引用的包或者定义的变量不使用会报错,此外go还是提供了很多好用的工具帮助我们进行代码的规范,
gofmt大部分的格式问题可以通过gofmt解决,gofmt自动格式化代码,保证所有的go代码与官方推荐的格式保持一致,于是所有格式有关问题,都以gofmt的结果为准。
goimport我们强烈建议使用goimport,该工具在gofmt的基础上增加了自动删除和引入包.
gogetgolang.org/x/tools/cmd/goimports
govetvet工具可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前return的逻辑,struct的tag是否符合标准等。
gogetgolang.org/x/tools/cmd/vet
使用如下:
govet.
▌包和文件:
每个Go程序都是由包构成的。程序从main包开始运行。
本程序通过导入路径&34;和&34;来使用这两个包。
import(\n&34;\n&34;\n)\n\nfuncmain(){\nfmt.Println(&34;)\n\nvarinputstring\nfmt.Scanln(&input)\n\nfmt.Println(&34;,input)\nfmt.Println(&34;,rand.Intn(10))\n\n}
按照约定,包名与导入路径的最后一个元素一致。
例如,&34;包中的源码均以packagerand语句开始。
注意:此程序的运行环境是固定的,因此rand.Intn总是会返回相同的数字。
要得到不同的数字,需为生成器提供不同的种子数,参见rand.Seed(https://go-zh.org/pkg/math/rand/34;github.com/tidwall/gjson&34;github.com/tidwall/gjson&34;github.com/tidwall/gjson&34;github.com/tidwall/gjson&34;fmt&34;math&34;fmt&34;math/rand&34;Hello,World!&34;你的输入是:%s&34;Myfavoritenumberis&34;fmt&34;Thesumof%dand%dis:%d\\n&34;hello&34;world&34;Thesumof%dand%dis:%d\\n&34;processing(%d)is:%d\\n&34;1&34;2&34;1&34;2&34;defer&defer_statements)中是明确说明了的。
要使用defer时不踩坑,最重要的一点就是要明白,returnA这一条语句并不是一条原子指令!
返回值=A\n调用defer函数\n空的return
接着我们看下例1,它可以改写成这样:
funcf()(resultint){\nresult=0//return语句不是一条原子调用,returnxxx其实是赋值+ret指令\nfunc(){//defer被插入到return之前执行,也就是赋返回值和ret指令之间\nresult++\n}()\nreturn\n}
所以例子1的这个返回值是1。
再看例2,它可以改写成这样:
funcf()(rint){\nt:=5\nr=t//赋值指令\nfunc(){//defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过\nt=t+5\n}\nreturn//空的return指令\n}
所以这个的结果是5。
最后看例3,它改写后变成:
funcf()(rint){\nr=1//给返回值赋值\nfunc(rint){//这里改的r是传值传进去的r,不会改变要返回的那个r值\nr=r+5\n}(r)\nreturn//空的return\n}
所以这个例子3的结果是1
defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是returnA语句并不是一条原子指令,defer被插入到了赋值与ret之间,因此可能有机会改变最终的返回值。
▌变量:
var语句用于声明一个变量列表。var`语句可以出现在包或函数级别。
变量的声明的语法一般是:
var变量名字类型=表达式
通常情况下“类型”或“=表达式”两个部分可以省略其中的一个。
例子:
//声明并初始化一个整数类型变量x\nvarxint=10
跟函数的参数列表一样,类型在最后
在Go中,变量是程序中存储数据的基本单元。每个变量都有一个类型和一个值,并且可以被赋值、传递和修改。
以下是一些基本的变量定义和使用方式:
packagemain\n\nimport&34;\n\nfuncmain(){\n//声明并初始化一个整数类型变量x\nvarxint=10\n\n//声明并初始化一个字符串类型变量s\nvarsstring=&34;\n\n//声明一个布尔类型变量b,不需要显式初始化,默认为false\nvarbbool\n\n//打印变量的值\nfmt.Println(x)\nfmt.Println(s)\nfmt.Println(b)\n\n//修改变量的值\nx=20\ns=&34;\nb=true\n\n//再次打印变量的值\nfmt.Println(x)\nfmt.Println(s)\nfmt.Println(b)\n\n//短变量声明语法\ny:=30\nz,w:=&34;,true\n\n//打印新的变量\nfmt.Println(y)\nfmt.Println(z)\nfmt.Println(w)\n\n//类型推导\nm:=40\nn:=&34;\n\n//打印新的变量\nfmt.Println(m)\nfmt.Println(n)\n}
在该示例中,我们首先使用var关键字声明和初始化了三个变量:x、s和b。
其中,x是一个整数类型变量,初始化为10;s是一个字符串类型变量,初始化为&34;;b是一个布尔类型变量,没有显式初始化,因此默认值为false。
我们通过fmt.Println函数打印了这三个变量的值,并修改了它们的值。
示例接下来,演示了Go中的短变量声明语法和类型推导方式。
使用短变量声明语法可以更简洁地定义和初始化变量,而类型推导则可以让编译器自动推断变量类型,避免冗长的类型声明。
需要注意的是,在使用变量时,我们需要特别注意变量的作用域和生命周期,并尽可能地遵循最佳实践,以确保代码的正确性和可读性。
▌变量的初始化
变量声明可以包含初始值,每个变量对应一个。
如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
vari,jint=1,2\nfuncFunc(){\nvarc,python,java=true,false,&34;\nfmt.Println(i,j,c,python,java)\n}
▌短变量声明
在函数中,简洁赋值语句:=可在类型明确的地方代替var声明。
函数外的每个语句都必须以关键字开始(var,func等等),因此:=结构不能在函数外使用。
import&34;\nfuncFunc(){\nvari,jint=1,2\nk:=3\nc,python,java:=true,false,&34;\nfmt.Println(i,j,k,c,python,java)\n}
▌零值
没有明确初始值的变量声明会被赋予它们的零值。
零值是:
数值类型为0,布尔类型为false,字符串为&34;(空字符串)。
funcFunc(){\nvariint\nvarffloat64\nvarbbool\nvarsstring\nfmt.Printf(&34;,i,f,b,s)\n}
▌常量:
在Go中,常量是一种固定不变的值,其值在编译时就已经确定,不能被修改。常量通常用于存储程序中不可变的值,例如数学常数、密码等。
以下是一些基本的常量定义和使用方式:
packagemain\n\nimport&34;\n\nfuncmain(){\n//声明一个整数类型常量\nconstxint=10\n\n//声明一个字符串类型常量\nconstsstring=&34;\n\n//打印常量的值\nfmt.Println(x)\nfmt.Println(s)\n\n//尝试修改常量的值,会导致编译错误\n//x=20\n//s=&34;\n}
在该示例中,我们使用const关键字定义了两个常量:x和s。其中,x是一个整数类型常量,初始化为10;s是一个字符串类型常量,初始化为&34;。我们通过fmt.Println函数打印了这两个常量的值,并尝试修改它们的值,结果会导致编译错误。
需要注意的是,在使用常量时,我们需要特别注意常量的作用域和生命周期,并尽可能地遵循最佳实践,以确保代码的正确性和可读性。
▌数值常量
数值常量是高精度的值。一个未指定类型的常量由上下文来决定其类型。
在Go中,数值常量是一种固定不变的数值,其值在编译时就已经确定,不能被修改。数值常量可以使用各种进制和精度表示。
以下是一些基本的数值常量定义和使用方式:
packagemain\n\nimport&34;\n\nfuncmain(){\n//十进制表示整数常量\nconstxint=10\n\n//八进制表示整数常量\nconstyint=012\n\n//十六进制表示整数常量\nconstzint=0x1a\n\n//浮点数常量\nconstafloat64=3.14\n\n//复数常量\nconstbcomplex128=1+2i\n\n//打印常量的值\nfmt.Println(x)\nfmt.Println(y)\nfmt.Println(z)\nfmt.Println(a)\nfmt.Println(b)\n\n//尝试修改常量的值,会导致编译错误\n//x=20\n//y=0123\n//z=0x1b\n//a=3.15\n//b=2+1i\n}
在该示例中,我们使用不同的进制和精度表示了各种类型的数值常量,包括整数常量、浮点数常量和复数常量。我们通过fmt.Println函数打印了这些常量的值,并尝试修改它们的值,结果会导致编译错误。
需要注意的是,在使用数值常量时,我们需要特别注意常量的精度和范围,并尽可能地遵循最佳实践,以确保代码的正确性和可读性。
▌GO数据基本类型:
Go的数据类型分四大类:
基本类型:数字number,字符串string和布尔型boolean。聚合类型:数组array和构造体struct。援用类型:指针pointer,切片slice,字典map,函数func和通道channel。接口类型:接口interface。
其中,基本类型又分为:
整型:int8、uint8、byte、int16、uint16、int32、uint32、int64、uint64、int、uint、uintptr。浮点型:float32,float64。复数类型:complex64、complex128。布尔型:bool。字符串:string。字符型:rune。
常用的Go的基本类型有
bool\n\nstring\n\nintint8int16int32int64\nuintuint8uint16uint32uint64uintptr\n\nbyte//uint8的别名\n\nrune//int32的别名\n//表示一个Unicode码点\n\nfloat32float64\n\ncomplex64complex128
本例展示了几种类型的变量。
同导入语句一样,变量声明也可以“分组”成一个语法块。
int,uint和uintptr在32位系统上通常为32位宽,在64位系统上则为64位宽。当你需要一个整数值时应使用int类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
basic-types.go
packagebasic\n\nimport(\n&34;\n&34;\n)\n\nvar(\nToBebool=false\nMaxIntuint64=1<<64-1\nzcomplex128=cmplx.Sqrt(-5+12i)\n)\n\nfuncBasicTypeDemo(){\nfmt.Printf(&34;,ToBe,ToBe)\nfmt.Printf(&34;,MaxInt,MaxInt)\nfmt.Printf(&34;,z,z)\n}
▌类型转换
表达式T(v)将值v转换为类型T。
一些关于数值的转换:
variint=42\nvarffloat64=float64(i)\nvaruuint=uint(f)
或者,更加简单的形式:
i:=42\nf:=float64(i)\nu:=uint(f)
与C不同的是,Go在不同类型的项之间赋值时需要显式转换。试着移除例子中float64或uint的转换看看会发生什么。
▌类型推导
在声明一个变量而不指定其类型时(即使用不带类型的:=语法或var=表达式语法),变量的类型由右值推导得出。
当右值声明了类型时,新变量的类型与其相同:
variint\nj:=i//j也是一个int
不过当右边包含未指明类型的数值常量时,新变量的类型就可能是int,float64或complex128了,这取决于常量的精度:
i:=42//int\nf:=3.142//float64\ng:=0.867+0.5i//complex128
尝试修改示例代码中v的初始值,并观察它是如何影响类型的。
▌整型
Go语言同时提供了有符号和无符号类型的整数运算。
有符号整形数类型:
int8,长度:1字节,取值范围:(-128~127)\nint16,长度:2字节,取值范围:(-32768~32767)\nint32,长度:4字节,取值范围:(-2,147,483,648~2,147,483,647)\nint64.长度:8字节,取值范围:(-9,223,372,036,854,775,808~9,223,372,036,854,775,807)
无符号整形数类型:
uint8,长度:1字节,取值范围:(0~255)\nuint16,长度:2字节,取值范围:(0~65535)\nuint32,长度:4字节,取值范围:(0~4,294,967,295)\nuint64.长度:8字节,取值范围:(0~18,446,744,073,709,551,615)
byte是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。
uintptr是一种无符号的整数类型,没有指定具体的bit大小但是足以容纳指针。
uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
此外在这里还需要了解下进制的转换方便以后学习和使用:
十进制整数:使用0-9的数字表示且不以0开头。//100123455\n八进制整数:以0开头,0-7的数字表示。//01000600\n十六进制整数:以0X或者是0x开头,0-9|A-F|a-f组成//0xff0xFF12
▌浮点型
浮点型。float32精确到小数点后7位,float64精确到小数点后15位。
由于精确度的缘故,你在使用==或者!=来比较浮点数时应当非常小心。
浮点型(IEEE-754标准):\nfloat32:(+-1e-45->+-3.4*1e38)32位浮点类型\nfloat64:(+-51e-324->1071e308)64位浮点类型
浮点型中指数部分由”E”或”e”以及带正负号的10进制整数表示。例:3.9E-2表示浮点数0.039。3.9E+1表示浮点数39。有时候浮点数类型值也可以被简化。比如39.0可以被简化为39。0.039可以被简化为.039。在Golang中浮点数的相关部分只能由10进制表示法表示。
▌复数
复数类型:\ncomplex64:由两个float32类型的值分别表示复数的实数部分和虚数部分\ncomplex128:由两个float64类型的值表示复数的实数部分和虚数部分
复数类型的值一般由浮点数表示的实数部分、加号”+”、浮点数表示的虚数部分以及小写字母”i”组成,
例如:
varxcomplex128=complex(1,2)//1+2i
对于一个复数c=complex(x,y),可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(c)获得该复数的虚部,也就是y。
▌布尔型
在Go语言中,布尔值的类型为bool,值是true或false,布尔可以做3种逻辑运算,&&(逻辑且),||(逻辑或),!(逻辑非),布尔类型的值不支持其他类型的转换.
布尔值可以和&&(AND)和||(OR)操作符结合,并且可能会有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不在被求值,因此下面的表达式总是安全的:
s!=&34;&&s[0]==&39;
其中s[0]操作如果应用于空字符串将会导致panic异常。
▌rune类型
字符是UTF-8编码的Unicode字符,Unicode为每一个字符而非字形定义唯一的码值(即一个整数),
例如字符a在unicode字符表是第97个字符,所以其对应的数值就是97,
也就是说对于Go语言处理字符时,97和a都是指的是字符a,而Go语言将使用数值指代字符时,将这样的数值称呼为rune类型。
rune类型是Unicode字符类型,和int32类型等价,通常用于表示一个Unicode码点。
rune和int32可以互换使用。一个Unicode代码点通常由”U+”和一个以十六进制表示法表示的整数表示,例如英文字母’A’的Unicode代码点为”U+0041”。
在Go中,rune类型是用于表示Unicode码点的类型。Unicode是一种标准,用于为世界上各种语言和符号分配唯一的数字编码,以便它们可以在计算机中存储和处理。
在Go中,rune类型实际上是一个int32类型的别名。它可以用于表示任何Unicode码点,并提供了一些有用的函数和方法,用于处理字符串和Unicode编码。
以下是一些基本的rune类型使用方式:
funcRuneDemo(){\n//使用单引号表示一个rune类型值\nvarrrune=&39;\n\n//打印这个rune类型值\nfmt.Println(r)\n\n//将rune类型转换为字符串类型\ns:=string(r)\nfmt.Println(s)\n\n//遍历一个字符串并打印每个字符的Unicode码点\nfori,c:=range&34;{\nfmt.Printf(&34;,i,c)\n}\n}
在该示例中:
我们首先使用单引号表示了一个rune类型值,即Unicode码点&34;。我们通过fmt.Println函数打印了这个rune类型值,并将其转换为字符串类型。接下来,我们遍历了一个字符串,并通过%U格式化符号打印了每个字符的Unicode码点。
输出的结果如下:
20320\n你\n字符0:U+0068\n字符1:U+0065\n字符2:U+006C\n字符3:U+006C\n字符4:U+006F\n字符5:U+0020\n字符6:U+4E16\n字符9:U+754C
需要注意的是,在处理字符串和Unicode编码时,我们需要特别注意编码和解码的方式,以避免出现错误或者不正确的结果。同时,我们也要了解各种字符集和编码标准之间的差异,以便在处理多语言和多文化环境下的应用程序时保持最佳实践。
此外rune类型的值需要由单引号”‘“包裹,不过我们还可以用另外几种方式表示:
rune类型值的表示中支持几种特殊的字符序列,即:转义符。
▌字符串
在Go语言中,组成字符串的最小单位是字符,存储的最小单位是字节,字符串本身不支持修改。
字节是数据存储的最小单元,每个字节的数据都可以用整数表示,例如一个字节储存的字符a,实际存储的是97而非字符的字形,将这个实际存储的内容用数字表示的类型,称之为byte。
字符串是不可变的字节序列,它可以包含任意数据,包括0值字节,但是主要还是为了人可读的文本。内置的len()函数返回字符串的字节数。
字符串的表示法有两种,即:原生表示法和解释型表示法。原生表示法,需用用反引号”`”把字符序列包起来,如果用解释型表示法,则需要用双引号”””包裹字符序列。
varstr1string=&34;\nvarstr2string=`keke`
这两种表示的区别是,前者表示的是所见即所得的(除了回车符)。后者所表示的值中转义符会起作用。字符串值是不可变的,如果我们创建了一个此类型的值,就不可能再对它本身做任何修改。
varstrstring//声明一个字符串变量\nstr=&34;//字符串赋值\nch:=str[0]//取字符串的第一个字符
▌整型运算
在整型运算中,算术运算、逻辑运算和比较运算,运算符优先级从上到下递减顺序排列:
*/%<<>>&&^\n+-|^\n==!=<<=>>=\n&&\n||
在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序。
▌bit位操作运算符:
符号
操作
操作数是否区分符号
&
位运算AND
No
^
位运算XOR
No
&^
位清空(ANDNOT)
No
<<
左移
Yes
>>
右移
Yes
▌复合数据类型
复合数据类型主要有:
数组SliceMap结构体JSON
数组和结构体都是有固定内存大小的数据结构。
在复合数据类型中数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。
相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。
▌数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
在Go中,数组是一种固定长度、类型相同的数据结构。数组可以包含任何类型的元素,但是它们的长度在定义时就必须确定,并且无法动态改变。
▌一个数组的案例
以下是一个基本的数组定义和使用方式:
packagemain\n\nimport&34;\n\nfuncArrayDemo(){\n//定义一个包含五个整数的数组\nvara[5]int\n\n//打印数组的值\nfmt.Println(a)\n\n//修改数组中的元素\na[2]=3\nfmt.Println(a)\nfmt.Println(a[2])\n\n//定义并初始化一个数组\nb:=[3]string{&34;,&34;,&34;}\nfmt.Println(b)\n\n//遍历数组并打印每个元素\nfori,x:=rangeb{\nfmt.Printf(&34;,i,x)\n}\n}
执行结果如下:
[00000]\n[00300]\n3\n[helloworld!]\nb[0]:hello\nb[1]:world\nb[2]:!
在该示例中,我们首先使用var关键字定义了一个包含五个整数的数组a。由于没有显式初始化数组中的元素,因此它们都被自动初始化为零值。
我们通过fmt.Println函数打印了这个数组的值,并修改了其中的一个元素,并再次打印了这个数组的值和一个特定的元素。
接下来,我们使用短变量声明语法定义了一个包含三个字符串的数组b,并通过花括号进行了初始化。我们遍历了数组b并通过fmt.Printf函数打印了每个元素的索引和值。
需要注意的是,在使用数组时,我们需要特别注意数组的长度和元素类型,并尽可能地遵循最佳实践,以确保代码的正确性和可读性。同时,我们也要注意数组在内存中的分布和访问方式,以便在处理大型数据集和高性能应用程序时保持最佳性能。
▌数组的定义
数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。
varm[3]int=[3]int{1,2,3}\nvarn[3]int=[3]int{1,2}\nfmt.Println(n[2])//&34;
在数组字面值中,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算。
m:=[…]int{1,2,3}\nfmt.Printf(&34;,m)//&34;
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。
数组可以直接进行比较,当数组内的元素都一样的时候表示两个数组相等。
arr1:=[3]int{1,2,3}\narr2:=[3]int{1,2,3}\narr3:=[3]int{1,2,4}\nfmt.Println(arr1==arr2,arr1==arr3)//true,false
▌元素的访问
数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。
▌数组作为参数
数组可以作为函数的参数传入,但由于数组在作为参数的时候,其实是进行了拷贝,这样在函数内部改变数组的值,是不影响到外面的数组的值。
funcArrIsArgs(arr[4]int){\narr[0]=120\n}\nm:=[…]int{1,2,3,4}\nArrIsArgs(m)
如果想要改变外部数组的值,就只能使用指针,
使用指针,在函数内部改变的数组的值,也会改变外面的数组的值:
funcArrIsArgs(arr*[4]int){\narr[0]=20\n}\nm:=[…]int{1,2,3,4}\nArrIsArgs(&m)
这里的*和&的区别:
&是取地址符号,即取得某个变量的地址,如;&a*是指针类型变量定义,可以表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元,也就是这个地址所存储的值.
通常这样的情况下都是用切片来解决,而不是用数组。
由于数组的长度是固定的,因而在使用的时候我们用的最多的是slice(切片),它是可以增长和收缩动态序列,slice功能也更灵活。
▌Slice:
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
数组和slice关系非常密切,一个slice可以访问数组的部分或者全部数据,而且slice的底层本身就是对数组的引用。
▌从数组或切片生成新的切片
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。从连续内存区域生成切片是常见的操作,格式如下:
//slice:表示目标切片对象\n//开始位置:对应目标切片对象的索引\n//结束位置:对应目标切片的结束索引\nslice[开始位置:结束位置]
一个Slice由三部分组成:指针,长度和容量。
内置的len和cap函数可以分别返回slice的长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
▌一个Slice的案例
在Go中,切片是一种动态长度的、可变长数组数据结构。它基于数组实现,但是可以动态增加或减少其长度,并且支持各种强大的操作和函数。
以下是一个基本的切片定义和使用方式:
funcSliceDemo(){\n//定义一个包含五个整数的数组\na:=[5]int{1,2,3,4,5}\n\n//声明一个从数组a中获取的切片\ns:=a[1:4]\n\n//打印切片的值和长度\nfmt.Println(s)\nfmt.Println(len(s))\n\n//修改切片中的元素\ns[1]=10\nfmt.Println(s)\nfmt.Println(a)\n\n//使用make函数创建一个新的切片\nb:=make([]int,3)\nfmt.Println(b)\n\n//向切片中添加新的元素\nb=append(b,4,5,6)\nfmt.Println(b)\n\n//遍历切片并打印每个元素\nfori,x:=rangeb{\nfmt.Printf(&34;,i,x)\n}\n}
执行结果
[234]\n3\n[2104]\n[121045]\n[000]\n[000456]\nb[0]:0\nb[1]:0\nb[2]:0\nb[3]:4\nb[4]:5\nb[5]:6
在该示例中,我们首先定义了一个包含五个整数的数组a,并通过[1:4]的方式声明了一个从数组a中获取的切片s。我们通过fmt.Println函数打印了这个切片的值和长度,并修改了其中的一个元素,查看了其对原数组的影响。
接下来,我们使用make函数创建了一个新的切片b,并通过append函数向其中添加了三个新元素。我们遍历了切片b并通过fmt.Printf函数打印了每个元素的索引和值。
需要注意的是,在使用切片时,我们需要特别注意其底层数组和长度,并尽可能地遵循最佳实践,以确保代码的正确性和可读性。
同时,我们也要掌握切片的内存分配和释放方式,以便在处理大型数据集和高性能应用程序时保持最佳性能。
▌从指定范围中生成切片
切片有点像C语言里的指针,指针可以做运算,但代价是内存操作越界,
切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全、强大。
切片和数组密不可分,如果将数组理解为一栋办公楼,那么切片就是把不同的连续楼层出租给使用者,出租的过程需要选择开始楼层和结束楼层,这个过程就会生成切片,表示原有的切片
生成切片的格式中,当开始和结束位置都被忽略时,生成的切片将表示和原切片一致的切片,并且生成的切片与原切片在数据内容上也是一致的,代码如下:
a:=[]int{1,2,3}\nfmt.Println(a[:])
a是一个拥有3个元素的切片,将a切片使用a[:]进行操作后,得到的切片与a切片一致,代码输出如下:
[123]
从数组生成切片,代码如下:
vara=[3]int{1,2,3}\nfmt.Println(a,a[1:2])
其中a是一个拥有3个整型元素的数组,被初始化为数值1到3,使用a[1:2]可以生成一个新的切片,代码运行结果如下:
[123][2]
其中[2]就是a[1:2]切片操作的结果。
从数组或切片生成新的切片拥有如下特性:
取出的元素数量为:结束位置-开始位置;取出元素不包含结束位置对应的索引,切片最后一个元素使用slice[len(slice)]获取;当缺少开始位置时,表示从连续区域开头到结束位置;当缺少结束位置时,表示从开始位置到整个连续区域末尾;两者同时缺少时,与切片本身等效;两者同时为0时,等效于空切片,一般用于切片复位。
根据索引位置取切片slice元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误,生成切片时,结束位置可以填写len(slice)但不会报错。下面通过实例来熟悉切片的特性:
▌重置切片,清空拥有的元素
把切片的开始和结束位置都设为0时,生成的切片将变空,代码如下:
a:=[]int{1,2,3}\nfmt.Println(a[0:0])
代码输出如下:
[]
▌直接声明新的切片
除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:
//其中name表示切片的变量名\n//Type表示切片对应的元素类型\nvarname[]Type
下面代码展示了切片声明的使用过程:
//声明字符串切片\n//声明一个字符串切片,切片中拥有多个字符串\nvarstrList[]string\n\n//声明整型切片\n//声明一个整型切片,切片中拥有多个整型数值\nvarnumList[]int\n\n//声明一个空切片\n//将numListEmpty声明为一个整型切片\n//本来会在{}中填充切片的初始化元素,这里没有填充,所以切片是空的,但是此时的numListEmpty已经被分配了内存,只是还没有元素\nvarnumListEmpty=[]int{}\n\n//输出3个切片\n//切片均没有任何元素,3个切片输出元素内容均为空\nfmt.Println(strList,numList,numListEmpty)\n\n//输出3个切片大小\n//没有对切片进行任何操作,strList和numList没有指向任何数组或者其他切片\nfmt.Println(len(strList),len(numList),len(numListEmpty))\n\n//切片判定空的结果\n//声明但未使用的切片的默认值是nil,strList和numList也是nil,所以和nil比较的结果是true\n//numListEmpty已经被分配到了内存,但没有元素,因此和nil比较时是false\nfmt.Println(strList==nil)\nfmt.Println(numList==nil)\nfmt.Println(numListEmpty==nil)
代码输出结果:
[][][]\n000\ntrue\ntrue\nfalse
▌还有5W字待发布
本文,仅仅是《Golang圣经》的第一部分。
《Golang圣经》后面的内容更加精彩,涉及到高并发、分布式微服务架构、WEB开发架构,具体请关注进展,请关注《技术自由圈》公众号。
如果需要获取《Golang圣经》,请到《技术自由圈》公众号,发送暗号“领电子书”。
最后,如果学习过程中遇到问题,可以来尼恩的万人高并发社区中交流。
▌技术自由的实现路径PDF获取:
▌实现你的架构自由:
《吃透8图1模板,人人可以做架构》PDF《10Wqps评论中台,如何架构?B站是这么做的!!!》PDF《阿里二面:千万级、亿级数据,如何性能优化?教科书级答案来了》PDF《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》PDF《100亿级订单怎么调度,来一个大厂的极品方案》PDF《2个大厂100亿级超大流量红包架构方案》PDF
…更多架构文章,正在添加中
▌实现你的响应式自由:
《响应式圣经:10W字,实现Spring响应式编程自由》PDF这是老版本《Flux、Mono、Reactor实战(史上最全)》PDF
▌实现你的springcloud自由:
《SpringcloudAlibaba学习圣经》PDF《分库分表Sharding-JDBC底层原理、核心实战(史上最全)》PDF《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》PDF
▌实现你的linux自由:
《Linux命令大全:2W多字,一次实现Linux自由》PDF
▌实现你的网络自由:
《TCP协议详解(史上最全)》PDF《网络三张表:ARP表,MAC表,路由表,实现你的网络自由!!》PDF
▌实现你的分布式锁自由:
《Redis分布式锁(图解-秒懂-史上最全)》PDF《Zookeeper分布式锁-图解-秒懂》PDF
▌实现你的王者组件自由:
《队列之王:Disruptor原理、架构、源码一文穿透》PDF《缓存之王:Caffeine源码、架构、原理(史上最全,10W字超级长文)》PDF《缓存之王:Caffeine的使用(史上最全)》PDF《JavaAgent探针、字节码增强ByteBuddy(史上最全)》PDF
▌实现你的面试题自由:
4000页《尼恩Java面试宝典》PDF40个专题
….
注:以上尼恩架构笔记、面试题的PDF文件,请到《技术自由圈》公众号获取
还需要啥自由,可以告诉尼恩。尼恩帮你实现…….
好了,文章到此结束,希望可以帮助到大家。
