五.包
1.包的基本概念
go的每一个文件都属于一个包,go以包的形式来管理文件和项目目录结构
2.包的作用
(1)区分相同名字的函数、变量等标识符 (2)当程序文件很多时,可以很好的管理项目 (3)控制函数、变量等访问范围,即作用域
3.包的使用细节
(1)在访问其他包函数时,语法是包名.函数名 (2)Go支持给包名取别名,但取别名后,原来的包名无法使用
(3)同一包下不能有同名的函数和同名的全局变量
(4)如果想要编译一个可执行文件,需要将包名声明main: package main
4.关于包的分析与思考
- 代码组织: 包是一种将相关代码组织在一起的方式,帮助项目保持结构清晰,减少代码之间的耦合。通过将功能模块放在不同的包中,可以更好地划分代码功能,使其易于管理和维护。
- 可复用性: 包提供了代码的封装和抽象,可以被其他代码引用和复用。通过将通用功能封装为包,可以减少重复编写类似的代码,提高代码的可维护性和可重用性。
- 访问控制: 在Go语言中,标识符(函数、变量、类型等)的首字母大小写决定了其在包外部的可见性。首字母大写的标识符可以在包外部访问,首字母小写的标识符则只能在包内部访问。这种方式可以有效地控制包的公共接口和私有实现。
- 导入包: 通过
import语句,可以将其他包的功能引入当前包中。这使得在代码中使用其他包的功能变得非常简单。例如,import "fmt"引入了标准库的格式化包。 - 循环依赖: Go语言不允许循环依赖,即包 A 依赖于包 B,同时包 B 也依赖于包 A。这种限制促使开发者设计更清晰、更合理的包结构,避免循环依赖带来的问题。
- 包的初始化: 每个包可以包含一个特殊的
init()函数,用于包的初始化操作。当程序运行时,包的init()函数会按照导入顺序自动执行,可以用来初始化包内的变量、执行必要的设置等。 - 包的命名: 包的名称应当具有描述性,并且遵循短小的命名原则。官方建议包名使用单数形式,并使用简洁的、易于理解的名称。
- 第三方包: Go语言社区拥有丰富的第三方包,可以通过工具如 Go Modules 管理项目的依赖关系。这些第三方包可以加速开发过程,避免重复造轮子。
总之,包是Go语言中非常重要的概念,有助于将代码组织得更加结构化、模块化和可维护。在编写代码时,合理地划分包,定义清晰的接口,遵循命名规范,都是提高代码质量和开发效率的关键因素。
六.函数
1.init函数
介绍
每一个源文件都可以包含一个init函数,该函数会在main函数前被调用
作用
完成一些初始化工作
init函数使用细节
执行流程:引入的包的全局变量定义->引入的包的init函数-> 全局变量定义->init函数->main函数
2.匿名函数
介绍
如果某个函数只使用一次,可以考虑使用匿名函数,同时匿名函数也支持多次调用
匿名函数使用方式
(1)方式一
定义名函数时就直接调用,该方式只能调用一次匿名函数
(2)方式二
将匿名函数赋给一个函数变量,再通过该变量来调用匿名函数
(3)方式三
将匿名函数赋给一个全局变量,那么该匿名函数成为全局匿名函数
3.defer使用细节
(1)当go执行到一个defer时,不会立即执行defer后的语句,而是将其保存起来等到函数结束后按照先入后出的方式执行
(2)defer可以在函数执行完毕后,及时的释放函数创建的资源
(3)defer保存语句时也会将相关的值拷贝入栈
4.函数使用细节
(1)Go中函数不支持重载
(2)在Go中,函数也是一种数据类型,可以赋值给变量,通过这个变量可以对函数调用。所以函数也可以作为形参被调用
(3)Go支持自定义数据类型 语法: type 自定义数据类型名 数据类型 这相当于一个别名 例子: type newint int 此时newint等价于int
(4)支持对函数返回值命名
(5)可以使用_标识符忽略返回值,若一个函数cal有两个返回值a和b,可用result,_=cal(xx,xx)只接收一个返回值
(6)Go支持可变参数
例如0到多个参数:func sum(a...int) sum int{ }
1到多个参数:func sub(n int,a...int) sub int{ }
其中a是变量名,int为数据类型,中间的...不可省略
七.切片
1.介绍
(1)切片是数组的引用,所以切片是引用类型
(2)切片的使用和数组类似
(3)切片的长度是可以变化的,因此切片是一个动态变化数组
(4)切片基础语法: var 变量名 [] 类型
2.切片的使用方式
(1)方式一:定义一个切片,然后让切片去引用一个已经创建好的数组。这种方式是直接引用数组,该数组程序员可见
(2)方式二:通过make来创建切片,这种方式可以指定切片的大小和容量。这种方式make也会创建一个数组,由切片在底层进行维护,程序员不可见 基本语法: var 切片名 [] 数据类型 =make ([] , 长度 ,[容量])
(3)方式三:定义一个切片,直接就指定具体数组,使用原理类似make的方式
3.append内置函数
用append内置函数可以对切片进行动态追加
基础语法:slice = append(slice,xx,xx)
原理分析: (1)切片append操作的本质是对数组扩容 (2)go底层会创建一下新的数组newArr (3)将slice原来包含的元素拷贝到新的数组newArr (4)slice重新引用newArr (5)数组newArr在底层维护,程序员不可见
4.string和slice
(1)string底层是一个byte数组,因此string也可以进行切片
(2)string不可变,但是可以先将string->[]byte/[]rune ->修改->重写转成string
5.切片注意事项和细节说明
(1)切片初始化时 var slice=arr[start:end]
说明:从arr数组下标为start取到下标为end的元素,但是不包含arr[end]
(2)切片初始化时范围在[0-len(arr)]之间,不可以越界,但是可以动态增长
(3) var slice = arr[0:end] 可以简写为 var slice = arr[:end]
同理
var slice = arr[start:len(arr)] 可以简写为 var slice = arr[start:]
var slice =arr[0:end] 可以简写为 var slice = arr[:]
(4)cap是一个内置函数,用于统计切片的容量,即最大可以存放多少元素
(5)切片定义完后还需要让其引用到一个数组或make一个空间才能使用
(6)切片可以继续切片
(7)切片用内置函数copy完成拷贝,且参与拷贝的两个切片的数据空间相互独立
6.关于切片的分析与思考
- 动态大小: 切片的长度可以在运行时动态变化,这使得切片适用于那些需要灵活调整大小的情况,而无需手动管理内存。
- 引用底层数组: 切片并不存储实际的数据,而是对底层数组的引用。这意味着修改切片中的元素会影响到底层数组,同时也影响到引用该底层数组的其他切片。
- 切片表达式: 使用切片表达式可以从已有的数组或切片中创建新的切片,例如:
slice := arr[start:end]。这使得在不复制数据的情况下,可以截取、组合、重排切片的元素。 - make() 函数与切片容量: 使用
make()函数可以创建指定类型、长度和容量的切片。切片的容量可以影响切片的扩展操作,当切片的长度达到容量时,再次添加元素会导致切片重新分配底层数组并复制数据。 - append() 函数:
append()函数用于在切片末尾添加一个或多个元素,如果切片的底层数组容量不足,append()会自动分配更大的底层数组,并复制数据。这也是切片操作中的一个潜在开销点,需要注意。 - 多维切片: Go语言支持多维切片,也就是切片的切片。这使得在处理多维数据时更加方便。
- 切片和数组的对比: 切片在很多情况下更为灵活,因为它们允许动态大小和更便捷的操作。然而,数组在一些场景中可能更高效,因为它们在内存中是连续存储的,而切片可能涉及到间接引用。
- 切片的生命周期: 需要注意切片的生命周期,尤其是在多个函数之间传递切片时。由于切片引用底层数组,如果切片超出了其作用域,但底层数组仍然在使用,那么底层数组将不会被回收,可能导致内存泄漏。
总的来说,Go语言中的切片是一种强大的工具,能够有效地管理动态大小的数据集合。在使用切片时,需要注意底层数组的共享情况、内存管理以及性能方面的考虑,以便充分利用切片的优势并避免潜在的问题。