💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
- 推荐:「stormsha的主页」👈,持续学习,不断总结,共同进步,为了踏实,做好当下事儿~
- 专栏导航
- Python面试合集系列:Python面试题合集,剑指大厂
- GO基础学习笔记系列:记录博主学习GO语言的笔记,该笔记专栏尽量写的试用所有入门GO语言的初学者
- 数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等
- 运维系列:总结好用的命令,高效开发
- 算法与数据结构系列:总结数据结构和算法,不同类型针对性训练,提升编程思维
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨
💖The Start💖点点关注,收藏不迷路💖 |
📒文章目录
- Go 程序的基本组成要素
- 1、包(Packages)
- 2、导入(Imports)
- 3、可见性规则
- 4、函数
- 5、注释
- 6、类型
- 7、Go 程序的基本结构
- 8、类型转换
- 9、Go 命名规范
Go 程序的基本组成要素
package mainimport "fmt"func main() {fmt.Println("Like collection follow share One click triple connection Inevitably becoming wealthy")
}
1、包(Packages)
包是Go语言中组织和结构化代码的核心机制之一。在Go语言中,每个程序都由一系列包(通常简称为pkg)构成,开发者可以在自己的包中定义内容,同时也可以方便地从其他包中导入所需的功能。
类似于其他编程语言中的库或命名空间概念,每一个Go源文件都明确归属于且仅归属于一个包。一个包可以由多个扩展名为 .go
的源文件组成,因此,源文件名与它们所属的包名通常是不相同的。
在编写Go源文件时,必须在非注释部分的第一行声明该文件属于哪个包,例如:package main
。这里,package main
表示这是一个可以独立执行的程序包,每个Go应用程序的核心都会包含一个名为 main
的包,其中包含 main
函数作为程序执行的入口点。
实际上,即使你的应用程序仅使用 main
包,也无需将所有代码都堆积在一个庞大的文件中。你可以将代码拆分成若干个规模适宜的文件,并在每个文件非注释部分的第一行都声明 package main
,以表明这些文件均属于 main
包的一部分。
此外,除了主程序的 main
包,一个Go应用程序还可以包含多个不同的包。如果编译一个包名并非 main
的源文件集合,比如一个名为 pack1
的包,编译后生成的对象文件将会是 pack1.a
,这通常是一个归档文件,而不是可以直接执行的程序。此类非 main
包通常作为库供其他包或主程序引用和使用。
还有一个编码规范上的要点需注意,即在Go语言中,所有的包名应当遵循小写字母命名规则,以保持一致性并符合Go社区的约定。这样做的好处是可以避免因大小写问题造成的导入错误,并有利于整个Go生态系统的包易于识别和使用。
在不同操作系统下,标准库存放的位置有所不同。例如,在 Windows 操作系统上,若安装的是 32 位 Go,标准库路径通常位于 Go的根目录下,子目录为 pkg\windows_386
;若是 64 位版本,则对应路径为 pkg\windows_amd64
。而在 Linux 平台上,64 位系统的标准库路径是Go 根目录/pkg/linux_amd64
,32 位系统则是 Go 根目录/pkg/linux_386
。一般情况下,不论何种平台,标准包的确切位置都可以表示为 $GOROOT/pkg/$GOOS_$GOARCH/
。
Go 的标准库提供了大量的预定义包,如 fmt
(格式化输入输出)、os
(操作系统交互)等,这些都是开发过程中频繁使用的基石。同时,开发者完全可以创建和使用自定义的包。
在构建 Go 程序的过程中,包及其内部文件的编译顺序是至关重要的,而这取决于包间的依赖关系。具体来说,编译过程会依据依赖链从底层向上层逐步编译。
同一包内的所有源文件必须作为一个整体进行编译,这意味着一个目录通常只包含一个包的源码。一旦某个包发生了变更或需要重新编译,所有引用了这个包的客户端程序也都需要随之重新编译以确保代码的一致性和正确性。
Go 的包管理模式设计巧妙,采用显式依赖关系来实现高效编译。编译器在编译过程中会利用后缀为 .o
的对象文件记录和传递类型信息。这意味着在编译过程中,编译器并不直接处理源文件的所有间接依赖,而是仅关注直接依赖的 .o 文件。
假设存在以下依赖关系:A.go
依赖 B.go
,B.go
又依赖 C.go
。在编译过程中,Go 编译器会按如下顺序进行:
- 首先编译
C.go
,生成C.o
; - 然后编译
B.go
,同时利用已经编译好的C.o
,生成B.o
; - 最后编译
A.go
,此时只需读取B.o
而不是C.o
,因为B.o
已经包含了从C.go
继承的类型信息。
这种逐级编译、传递依赖的方式极大地提升了大型项目编译时的效率,尤其在工程规模逐渐增大,依赖层次变深的情况下,能够明显减少编译时间。
2、导入(Imports)
一个 Go 程序是通过 import
关键字引入并链接一组所需的包。例如,import "fmt"
表示告诉 Go 编译器,当前程序需要用到 fmt
包中的函数或者其他元素。fmt
包提供了格式化输入/输出功能的函数,其包名被置于半角双引号内。当导入已编译好的包时,无需包含其源代码,只要导入即可访问其公开声明的方法和变量。
如果有多个包需要导入,可以分多行独立导入:
import "fmt"
import "os"
或者在同一行中用分号隔开,但这并不是推荐的做法:
import "fmt"; import "os"
更为常见且更优美的方式是采用因式分解导入语法,这同样适用于 const
、var
和 type
的声明或定义:
import ("fmt""os"
)
实际上,即使一行写完也会被 gofmt
工具自动格式化为多行形式,保持可读性:
import ("fmt""os"
)
在导入多个包时,遵循字母顺序排列包名是一种良好的编码习惯,有助于提高代码的整洁度和一致性。
关于包查找路径,当包名不以.
或 /
开头时,如 "fmt"
或 "container/list"
, Go 会在 GOPATH(或 Go modules 环境下的模块缓存)的全局文件夹结构中查找。若包名以 ./
开头,Go 则会在当前包的相对目录中查找;如果包名以 /
开头(包括在 Windows 系统中,此处代表绝对路径),则会在系统的绝对路径下查找。需要注意的是,自 Go 1.11 引入 Go modules 后,强烈建议使用模块机制管理依赖,而非传统的 GOPATH 方式。
Go 确实不允许在工作区内的非本地包中使用相对导入路径,如 ./XXX
。这是因为 Go 强调项目的独立性和可移植性,本地相对导入仅限于同一个模块内的相互引用。 导入一个包意味着编译器会把该包中的所有导出(公开)的代码对象合并到当前程序中。尽管如此,每个包内的所有代码对象(除下划线 _
外)的标识符必须在其包内保持唯一,以防止命名冲突。然而,相同标识符可以在不同包中重复使用,因为可以通过包名前缀来明确区分它们。
Go 包中的代码对象对外可见性的控制基于一种严格的规则:只有首字母大写的标识符(如函数、类型、常量和变量)会被视为导出(公开)的对象,可以被其他包所引用;反之,首字母小写的标识符被视为私有,只能在本包内部使用。这一规则由 Go 编译器强制执行。
在Go语言中,如果导入了一个包但并未在程序中实际使用其导出的内容,编译器会在构建程序时发出警告(而非错误),提示“imported and not used: os”。虽然这不会阻止程序编译成功,但确实体现了Go语言提倡简洁、无冗余的设计哲学——“没有不必要的代码!”(Don’t pay for what you don’t use)。这样的机制鼓励开发者只导入真正需要的包,进而减少潜在的依赖关系和提高程序性能。不过,可以通过在导入包时添加下划线 “_” 来忽略这种未使用的包警告,如 import _ "os"
,这通常用于引入包的副作用,而不使用其导出的标识符。
3、可见性规则
在 Go 语言中,标识符的可见性规则非常直观且严格。当标识符(包括但不限于常量、变量、类型、函数名、结构体字段等)以一个大写字母(不限于ASCII码范围,可以是任何Unicode编码的大写字母,如希腊字母)开头,如 Group1
,则这个标识符所代表的对象可以被外部包的代码访问和使用,这相当于面向对象语言中的 public 成员。相反,如果标识符以一个小写字母开头,则表示它是包级私有的,对包外不可见,但在包内是全局可见和可使用的,类似于面向对象语言中的 private 成员。
举例来说,如果在包 pack1
中定义了一个以大写字母开头的变量或函数,比如 Etab
,由于它是导出的,所以在当前包中通过导入 pack1
后,就能够以点标记法来调用这个变量或函数,就像在面向对象语言中一样,书写形式为 pack1.Etab
。这里的 pack1
是不能省略的,因为它明确了变量或函数所在的命名空间。
通过这样的设计,包在 Go 中起到了命名空间的作用,有效地避免了不同包间可能出现的命名冲突问题。例如,即使有两个不同的包 pack1
和 pack2
中都有一个名为 Etab
的变量或函数,但由于它们处于不同的包内,因此可通过 pack1.Etab
和 pack2.Etab
明确地区分开来。
针对包名可能存在冲突或者为了代码阅读简洁性,Go 提供了包别名的机制。例如,我们可以为 fmt
包定义一个别名 fm
,通过 import fm "fmt"
导入后,原本的 fmt.Println
就可以写作 fm.Println
来使用,从而避免了与其他包或局部变量名称的混淆,同时也可根据个人喜好对包名进行个性化设置。 如下代码展示了如何使用包的别名:
package mainimport stormsha "fmt" // 别名func main() {stormsha.Println("hello, world")
}
在Go语言中,当你在使用import
导入所需包之后,确实可以在同一个源文件(如stormsha.go
)中定义或声明任意数量的全局常量(const)、变量(var)和自定义类型(type)。这些声明的对象拥有包级别的作用域,也就是说它们在整个包内都是可见的,可供该包内任何函数访问和调用。
例如,在stormsha.go
文件中,你可以定义一个常量c
,一个变量v
以及一个自定义类型MyType
:
package mypackageimport "fmt"const c = "This is a constant"
var v = 42 // This is a variabletype MyType struct {Name stringValue int
}func ExampleFunc() {fmt.Println(c)fmt.Println(v)t := MyType{Name: "Example", Value: 100}fmt.Println(t)// ...
}
你可以在同一个包内声明并实现多个函数(func),这些函数可以相互调用,并且都能够访问上述定义的常量、变量和类型。通过这种方式组织代码,可以使程序更具模块化和可读性。
4、函数
在Go语言中,定义一个函数的基本格式如下:
func functionName(parameters) (returnValues) {// 函数体
}
其中,functionName
是函数名,parameters
是参数列表,每个参数由参数名和参数类型组成,多个参数之间用逗号 ,
分隔。returnValues
是返回值列表,如果有返回值,则也需要指定其类型,多个返回值之间同样用逗号 ,
分隔。如果函数不需要参数或返回值,可以省略参数列表和返回值列表。
main
函数在Go语言中扮演着程序入口的角色,它是每一个可执行程序所必需的,且必须不带参数和返回值。违反这一规则会导致编译错误。
Go语言的函数体由一对大括号 {}
包围,左大括号 {
必须紧跟在函数声明的括号 )
后面,不能换行,否则会引发编译错误。右大括号 }
则通常放置在函数体代码的下一行,但对于非常简单的函数,也可以采用单行形式定义。
在Go语言中,函数名遵循特定的命名规则:如果函数是为了被外部包调用,则函数名应以大写字母开头,采用Pascal命名法;如果函数仅在当前包内使用,则函数名应以小写字母开头,采用驼峰命名法。
至于输出,fmt
包提供了诸如 Print
和 Println
等函数,它们能将字符串和变量输出到控制台,其中 Println
会在末尾自动添加换行符 \n
。在生产环境中,应始终使用 fmt
包提供的函数进行输出,而不是内建的 print
和 println
函数,因为后者主要用于临时调试。
当函数执行完毕或遇到 return
语句时,函数会返回并继续执行调用函数之后的代码。程序正常退出时的退出码(exit status)为 0,表示程序执行成功;非零值则通常表示程序因异常情况终止。在自动化测试或脚本中,经常通过检查程序的退出码来判断程序是否成功执行。
5、注释
package mainimport "fmt" // 实现格式化I/O的包func main() {fmt.Printf("العالم. Le monde. Καλημέρα κόσμε. こんにちは. świat. 世界.\n")
}
以上代码示例展示了如何在 Go 语言中使用国际化字符,并演示了如何编写和使用注释。这个程序通过 fmt
包的 Printf
函数打印了希腊语、日语、法语等语言,表明 Go 支持多种语言字符集。
Go 语言的注释分为单行注释和多行注释两种类型:
-
单行注释以
//
开头,直到行尾都是注释内容,这类注释广泛应用于代码的临时解释说明,例如:// 这是一个单行注释
-
多行注释(块注释)以
/*
开始,并以*/
结束,其间的内容均为注释,例如:/* * 这是一个多行注释,* 这是一个多行注释。*/
在 Go 语言中,每个包应当在其文档的顶部包含一个多行注释,这个注释紧挨在 package
语句之前,用以对该包的整体功能和目的进行概述。这个注释会被 godoc
工具解析,用于生成包的官方文档。在该注释下方,可以根据需要添加更多详细的注释来进一步解释包内的各个组成部分。
// stormsha 包实现了若干个博主属性.
//
// 当你引入此包可以深入地了解博主的所有信息:
// 颜值等级,身高体重,是否单身等等.
package stormshaimport "fmt"// 博主不仅人长得刷还习得一身武艺
// 抽烟、喝酒、烫头社会最恨我龙哥.
func getSkillTree() {fmt.Println("抽烟")fmt.Println("喝酒")fmt.Println("烫头")
}
在示例中,stormsha
包的注释就是一个很好的文档注释范例,它清楚地介绍了包的功能——实现了博主所有信息读取的方法。同时,每个导出的函数(如 getSkillTree
函数)也应当有相应的文档注释,这些注释以函数名开头,随后紧跟对其功能和行为的描述,以便其他开发者理解并正确使用该函数。godoc
工具会收集这些注释并生成一份结构化的文档,便于查阅和参考。
6、类型
在 Go 语言中,声明变量通常使用 var 关键字,若不指定初始值,Go 会自动赋予变量其类型的默认零值。例如,整数型(int)变量的零值是 0,布尔型(bool)变量的零值是 false,字符串(string)变量的零值是空字符串 “” 等。
Go 语言中的基本类型包括但不限于:
- 整数类型:
int
、int8
、int16
、int32
、int64
,无符号整数uint
、uint8
、uint16
、uint32
、uint64
- 浮点数类型:
float32
、float64
- 布尔类型:
bool
- 字符串类型:
string
- 复合/结构化类型:
- 结构体(
struct
) - 数组(
array
) - 切片(
slice
),它是动态数组的概念 - 映射(
map
),关联键值对的数据结构 - 通道(
channel
),用于goroutine
间通信
- 结构体(
- 接口(
interface{}
),它可以表示任意类型的值,只要该类型实现了接口中声明的方法。
对于结构化类型,如果没有具体实例赋值,它们确实没有具体的值,而是表现为 nil,即一个预定义的标识符,表示该类型的空值引用。
此外,Go 语言还支持将函数作为类型使用,这被称为函数类型。声明一个函数类型的变量后,该变量就可以指向实现了相同签名的任何函数。函数类型声明的一般格式如下:
funcNameType := func(argTypes...) returnType {
// 函数体
}
你可以在函数体中的某处返回使用类型为 returnType 的变量 var:
return var
一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来,如:
// 这种多返回值一般用于判断某个函数是否执行成功 (true/false) 或与其它返回值一同返回错误消息(详见之后的并行赋值)。
func funcNameType (a typea, b typeb) (t1 type1, t2 type2)
返回的形式:
return var1, var2
这样定义的 funcNameType 就是一个函数类型,你可以创建一个变量并赋给它一个同类型的函数值。这一特性体现了 Go 语言强大的函数式编程能力。而且,Go 不支持传统的类继承,但通过组合(composition)和接口实现(interfaces)提供了灵活的设计模式。
使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体,也可以定义一个已经存在的类型的别名,如:
type IT int
里并不是真正意义上的别名,因为使用这种方法定义之后的类型可以拥有更多的特性,且在类型转换时必须显式转换。
然后我们可以使用下面的方式声明变量:
var a IT = 5
这里我们可以看到 int 是变量 a 的底层类型,这也使得它们之间存在相互转换的可能。
如果你有多个类型需要定义,可以使用因式分解关键字的方式,例如:
type (IT intFT float64STR string
)
每个值都必须在经过编译后属于某个类型(编译器必须能够推断出所有值的类型),因为 Go 语言是一种静态类型语言。
7、Go 程序的基本结构
虽然Go语言编译器并不强制要求这种结构顺序,但遵循这样的结构可以帮助提高代码的可读性和一致性,让开发者从上至下阅读代码时有更好的体验。
在Go程序中,一般遵循以下组织结构:
-
包导入(Package Import): 首先,程序以
package <package-name>
语句声明当前包的名称,紧接着是通过import
语句导入所需的外部包,可以是标准库包或其他自定义包。 -
常量、变量和类型的定义或声明(Constants, Variables, Types Declaration): 在完成包导入之后,通常会开始定义或声明在当前包中全局使用的常量、变量和类型。这些声明有助于定义程序的基本数据结构和初始化状态。
-
init()
函数(The init() Function): 如果有必要,可以定义init()
函数。这是一个特殊的函数,无需接收任何参数,也不返回任何值。当包含init()
函数的包被导入时,该函数会被自动调用,且按照它们在源文件中的出现顺序执行。 -
main()
函数(The main() Function): 如果当前包是程序的主入口点,也就是package main
,那么接下来会定义main()
函数。main()
函数也是无参数且无返回值的,它是程序执行的起点。 -
其他函数定义(Other Function Definitions): 在
main()
函数定义之后,依次定义程序中其他的辅助函数。对于类型的方法,建议首先定义与类型相关的所有方法,这样有助于读者理解类型的功能。在非类型相关的函数中,可以按照它们在main()
函数中调用的顺序进行定义,或者根据功能相近的原则进行分组和排序,如果函数数量较多,按照字母顺序也是一种常见的排序方式。
下面的程序虽然不具备实际功能,但它很好地展示了典型Go程序的推荐结构布局。
package mainimport ("fmt"
)const c = "C"var v int = 5type T struct{}func init() { // 包初始化函数
}func main() {var a intFunc1()// ...fmt.Println(a)
}func (t T) Method1() {//...
}func Func1() { // 辅助函数//...
}
Go 程序的执行(程序启动)顺序如下:
- 按顺序导入所有被
main
包引用的其它包,然后在每个包中执行如下流程: - 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
- 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有
init()
函数的话,则调用该函数。 - 在完成这一切之后,main 也执行同样的过程,最后调用
main()
函数开始执行程序。
8、类型转换
在Go语言中,类型转换是非常明确且受控的过程。由于Go不支持隐式类型转换,因此在需要将一种类型的值转换为另一种类型时,必须显式地进行类型转换操作,就如同调用一个函数一样:
valueOfTypeB = typeB(valueOfTypeA)
这里的typeB(valueOfTypeA)
就是进行类型转换的表达式,即将valueOfTypeA
类型的值转换为typeB
类型。
例如:
a := 5.0 // a 是 float64 类型
b := int(a) // b 被显式转换为 int 类型
类型转换在一定条件下是允许的,如从取值范围较小的类型向取值范围较大的类型转换(如将 int16
转换为 int32
),这种转换不会导致数据丢失。然而,若试图将一个取值范围较大的类型转换为范围较小的类型(如将 int32
转换为 int16
或将 float32
转换为 int
),可能会导致精度丢失(数值被截断)。在这种情况下,编译器会在检测到非法类型转换时抛出编译错误,否则在运行时可能会触发运行时错误。
另外,对于具有相同底层类型(底层类型是指无类型别名时的实际类型)的变量,可以互相转换。例如:
type IT int // IT 是 int 类型的别名var a IT = 5 // a 是 IT 类型,但其底层类型仍然是 int
c := int(a) // c 被转换回 int 类型
d := IT(c) // d 再次转换回 IT 类型,由于 IT 和 int 有相同的底层类型,所以这个转换是合法且安全的
9、Go 命名规范
Go 语言的命名规范着重强调代码的简洁性、一致性和可读性。以下是 Go 语言中关于命名的一些关键规范:
-
包名(Package Names):
- 包名应简短、有意义且全部使用小写字母,不使用下划线。例如:
fmt
、net/http
、math/rand
。
- 包名应简短、有意义且全部使用小写字母,不使用下划线。例如:
-
标识符(Identifiers):
- 变量名、函数名、常量名、类型名等遵循驼峰命名法(MixedCaps 或 mixedCaps),不使用下划线进行连接。
- 导出(public)的标识符(对外可见的)以大写字母开头,如
ToUpper
,非导出标识符以小写字母开头,如toLowerCase
。 - 变量名应反映其用途,例如,布尔类型的变量可以用
isRunning
或isEnabled
这样的名称。
-
函数和方法(Functions and Methods):
- 函数和方法名称通常是动词或名词短语,如果函数用于获取值,则不必像Java那样使用
get
前缀,例如,直接命名为Name()
而不是GetName()
。 - 修改对象状态的方法可以使用
SetName()
这样的命名方式。
- 函数和方法名称通常是动词或名词短语,如果函数用于获取值,则不必像Java那样使用
-
常量(Constants):
- 常量通常使用全大写字母和下划线进行命名,如
CONSTANT_VALUE
,尤其是那些具有全局意义的常量。
- 常量通常使用全大写字母和下划线进行命名,如
-
接口(Interfaces):
- 接口名称通常为名词或名词短语,也可以是形容词+名词的形式,例如
Reader
、Writer
或Formatter
。
- 接口名称通常为名词或名词短语,也可以是形容词+名词的形式,例如
-
结构体(Structs):
- 结构体名使用驼峰命名,如
Employee
,对应的指针类型在代码中通常使用结构体名的首字母小写形式表示,如employeePtr
。
- 结构体名使用驼峰命名,如
通过遵循这些命名规范,Go 程序员能够编写出清晰、易于理解和维护的代码。此外,Go 语言自带的 gofmt
工具会自动调整代码格式,其中包括对命名规范的部分校验和标准化,有助于团队协作时保持代码风格的一致性。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄
💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏、分享下吧,非常感谢!👍 👍 👍
🔥🔥🔥道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙
💖The End💖点点关注,收藏不迷路💖 |