上一篇我们根据大地老师的B站视频学习了一部分Go语言语法的基础部分,接下来让我继续开始学习,在这篇中将继续总结函数、接口、time包、 指针与结构体等重要的语法基础。
Go 函数
函数定义
函数是组织好的、可重复使用的、用于执行指定任务的代码块。本文介绍了Go语言中函数的相关内容。 Go语言支持的函数类型包括: 函数、匿名函数、闭包 Go语言中定义函数使用func关键字,具体格式如下
go
func 函数名(参数)(返回值){
函数体
}
函数参数
从上面的定义可以看出参数的定义是使用形参名1 类型, 形参名2 类型
,如果形参类型一样的话可以简写中间用逗号分隔
如果要定义的函数包含可变的参数则需要将函数的形参定义为可变形参,函数的可变参数是指函数的参数数量不固定,go语言中的可变参数通过在参数名后面(参数类型前面)加上...
来表示可变参数(区别于针对于数组的拆解) 当可变参数与固定参数同时出现,可变参数放在最后一个形参的位置。
go
// num 表示输入的参数个数, elements为参数元素
func sum(num int, elements ...int) {
fmt.Printf("%T\n", elements) // 传进来的参数类型为对应类型的切片
fmt.Println(num)
sum := 0
for _, v := range elements {
sum += v
}
fmt.Println(sum)
}
返回值
go
func sum1(nums ...int) (int, int) {
sum := 0
count := 0
for _, v := range nums {
sum += v
count += 1
}
return sum, count // return 关键字一次可以返回多个值
}
func main() {
sum, count := sum1(1, 2, 3, 4, 5)
fmt.Println(sum, count)
}
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回返回的时候直接写return就OK了
使用命名返回值和未命名返回值结合是不允许的
函数变量作用域
和其他语言一样go语言中也包含了全局变量与局部变量,同时也有全局作用域和局部作用域
- 全局变量\作用域 全局变量是定义在函数外部的变量,在程序整个运行周期都是有效的
- 局部变量\作用域 局部变量是函数内部定义的变量,函数内定义的变量无法在该函数外使用
函数类型与变量
和其他语言一样在Go语言中函数也是有类型与变量的概念的(函数是一等公民) 我们可以定义函数类型,通过type
关键在来定义一个函数类型, 具体的格式如下 type calculation func(int, int) int
上面语句定义了一个calculation类型,他是一个函数类型,这种函数接受两个int类型的参数并且返回一个int类型的返回值,只要符合两个int参数,一个int返回值的函数都是calculation类型的函数
整体来说就是形如
type 函数名 func(int, int) int
type关键字不光可以定义函数类型,也可以定义我们自己的数据类型,比如定义一个自己的int类型type myInt int
方法作为返回值和参数
方法作为参数
go
type calcType func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func calc(x, y int, fun calcType) int {
return fun(x, y)
}
func main() {
fmt.Println(calc(1,2, add))
}
函数作为返回值
go
type calcType func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func calc(x, y int, fun calcType) int {
return fun(x, y)
}
func operation(op string) calcType {
switch op {
case "+":
return add
case "-":
return sub
case "*":
return func(i1, i2 int) int { // 匿名函数
return i1 * i2
}
default:
return nil
}
}
func main() {
f := operation("*")
fmt.Println(f(2,2))
}
匿名函数
go语言中不支持函数的嵌套,但是可以定义匿名函数,匿名函数就是没有函数名的函数,匿名函数因为没有函数名,所以没法像普通函数那样调用,所以匿名函数必须要保存到某个变量中,或者设置为自执行在匿名函数后直接加()
匿名函数的定义格式如下
go
func(参数)(返回值) {
函数体
}
自执行函数格式如下
go
func main() {
// 匿名函数 匿名自执行函数
func(形参) {
函数体
}(实参)
}
go
type calcType func(int, int) int
func operation(op string) calcType {
switch op {
case "+":
return add
case "-":
return sub
case "*":
return func(i1, i2 int) int { // 匿名函数
return i1 * i2
}
default:
return nil
}
}
func main() {
f := calc(3, 4, func(i1, i2 int) int {
return i1 * i2
})
fmt.Println(f)
var fn = func(x, y int) int {
return x * y
}
res := calc(3,4, fn)
fmt.Println(res)
// 匿名自执行函数 形参 实参 函数体
fmt.Println(func (x, y int) int {
return x * y
}(3, 4))
}
函数递归调用
函数调用函数本身为递归,由于函数调用函数没有结束条件,所以要注意设置递归的出口,否则将会一直递归的调用下去。
最经典的例子就是实现阶乘
go
func fn(n int) int {
if n > 1 { // 递归调用出口
return n * fn(n - 1)
} else {
return 1
}
}
闭包
闭包可以理解为定义在一个函数内部的函数,在本质上闭包是将函数内部和函数外部连接起来的桥梁,或者说是函数和其引用环境的组合体。
- 闭包是指有权访问另一个函数作用域中的变量的函数
- 创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数中的局部变量
由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存,过渡的使用闭包会导致性能的下降,建议在非常有必要的时候才使用闭包 ps: 这里通过闭包创建的局部变量的回收时机 -- Go的GC处理
go
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
这里补充下前面提到的全局变量和局部变量的特点 全局变量
- 常驻内存
- 污染全局
局部变量
- 不常驻内存
- 不污染全局
Go语言中的闭包包含如下特点
- 可以让一个常量常驻内存
- 可以让一个变量不污染全局
在返回的闭包中创建的局部变量是否被立即释放要看看闭包中是否对于局部变量有操作(引用),如果有操作不会立即释放否则会释放 --- 要了解下Go中对于闭包的GC
闭包可以解决将变量常驻内存且不污染全局
defer 语句
Go语言中的defer语句会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按照defer定义的逆序顺序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句最先被执行。
命名返回值与匿名返回值在defer的表现有不同,具体可以参照上面的demo,对于这种表现是由于defer执行时机导致的。
下面再看看这个例子
由于defer在注册时要确定所有的参数,所以会先执行作为子函数的
calc("A", x, y), calc("B", x, y)
panic/recover
Go语言中目前是没有异常机制,但是使用``panic/recover`模式来处理错误,panic可以在任何地方引发,但是recover只有在defer调用的函数中有效。类似于其他函数中的try/throw
fn1 error: 抛出异常 结束
error: runtime error: integer divide by zero 结束 5
defer panic recover 结合使用
给管理员发送邮件
Go 结构体
Go中没有类的概念,Go中结构体和其他语言中的类有些相似,和其他面向对象的语言中的类相比,Go中的结构体具有更高的扩展性和灵活性
Go中基础数据类型可以表示一些事物的基本属性,但是当我们想飙到一个事物的全面或者部分属性时,这个时候再用单一的基本数据类型就无法满足要求了,Go提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名叫struct也就是我们可以通过struct来定义自己的类型
结构体的首字母可以大写也可以小写,大写表示这个结构体是公有的,在其他的包里面也可以使用,小写表示私有的只能在本包中使用
struct 定义
Go 中通过type关键字定义一个结构体,在讲解结构体之前,首先介绍下通过type关键字自定义类型以及定义类型别名。
自定义类型
在Go语言中有一些基本的数据类型,如string、 int、float、bool等,Go中可以使用type关键字来定义自定义类型 type myInt int
上面代码表示将myInt定义为int类型,通过type关键字的定义,myInt就是一种新的类型,他具有int的特性
类型别名
TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型,就像一个孩子有大名小名英文名但这些名字都是指他本人 type TypeAlias = Type
在go语言中有两个类型别名,rune和byte他们的底层定义如下
type byte = uint8
// 表示占一个字节的字符 字母type rune = int32
// 表示占4个字节的字符 中文 特殊符号
自定义类型打印出来的类型就是自定义的类型,类型别名定义的打印出来的类型就是原本的类型
结构体定义与初始化
定义
使用type和struct关键字来定义结构体,具体代码如下
go
type 类型名 struct {
字段名 字段类型
字段名 字段类型
}
- 类型名 表示自定义结构体的名称,在同一个包中不可以重复
- 字段名 表示结构体字段名,结构体中的字段名必须唯一
- 字段类型 表示结构体字段的具体类型 任意类型
实例化
只有当结构体实例化后才会真正的分配内存,也就是必须实例化后才能使用结构体的字段
结构体的字段可以是: 基本数据类型、也可以是切片、Map以及结构体,如果结构体的字段类型是指针,slice和map的零值都是nil,即没有分配空间,如果需要使用这样的字段需要先make后才能使用
- 方式一 结构体本身也是一种类型,可以像声明内置类型一样使用var关键字声明结构体类型
var 结构体实例 结构体类型
- 方式二 可以通过
new
关键字对结构体进行实例化,得到结构体的地址
go
func main() {
var p2 = new(person)
p2.name = "张三"
p2.sex = "男"
p2.age = 20
fmt.Pirntf("值:%v 类型:%T\n", p2, p2) // 值:{张三 20 男} 类型main.Person
}
通过new返回的是指针类型的实例
在go中支持对结构体指针直接使用,来访问结构图id成员,在底层
p2.name = "张三"
会转化为(*p2).name = "张三"
- 方式三 使用&对结构体进行取地址操作相当于对该结构体类型进行一次new实例化操作
核心在于p3 := &Person{}
等同于 new一次
和方式二基本一样,可以理解为对于new的简写 (得到的也是对应类型的实例的指针)
- 方式四 类似于对于map直接赋值,直接对于结构体中的字段进行赋值即可
得到的是值类型
- 方式五
得到的是指针类型 其实相当于对于方法4的取地址
- 方式六
可以部分赋值部分不赋值,不赋值的是类型的默认值
- 方式七 加不加取地址都可以加了是指针类型,不加是值类型
顺序要和定义的对齐
结构体方法和接收者
在go语言中,没有类的概念但是可以给行(结构体 自定义类型)定义方法,所谓方法就是定义了接受者的函数,接受者的概念就类似于其他语言中的this或者self。 方法的定义格式如下
go
func(接收者变量 接收者类型) 方法名(参数列表)(返回参数) {
函数体
}
- 接受者变量 接收者中的参数变量名在命名时,官方建议使用接受者类型名的第一个小写字母,而不是self或者this之类的命名,例如 Person类型的接受者变量应该命名为p,Connector类型的接受者变量应该命名为c等。
- 接收者类型和参数类型相似,可以是指针类型也可以是非指针类型
- 方法名、参数列表、返回参数具体格式于参数定义相同
go
type Person struct {
name string
age int
sex string
}
func (p Person) printInfo() {
fmt.Printf("姓名: %v 年龄: %v", p.name, p.age)
}
func main() {
p := &Person{
name: "Sharker",
age: 10,
sex: "男",
}
p.printInfo()
}
任意类型添加方法
在go中,接受者的类型可以是任意类型,不静静是结构体,任意类型都可以拥有方法举个例子在基于int类型使用type关键字可以定义新的自定义类型,然后为新定义自定义添加方法
go
type myInt int
func (m myInt) sayHello(){
fmt.Println("Hello 我是一个int")
}
func main() {
var m1 myInt
m1.sayHello()
}
非本地类型不能定义犯法,也就是说我们不能给别的包的类型定义方法 通过给新类型增加方法其实就和iOS开发中的扩展有点类似,增加方法不增加成员变量
结构体的嵌套与继承
结构体的匿名字段
结构体允许其他成员字段在声明时没有字段名而只有类型,这种没有名字的字段就成为匿名字段
go
type Person struct {
string
int
}
func main() {
p := Person{
"小王子",
18
}
fmt.Println(p.string)
}
匿名字段默认采用类型名作为字段名,结构体要求字段名必须是唯一的因此一个结构体中同种类型的匿名字段只能有一个, 结构体中命名字段和匿名字段不可以混合定义
结构体的嵌套
go
type User struct {
Username string
Password string
Address Address // 表示User结构体嵌套Address结构体
}
type Address struct {
Name string
Phone string
City string
}
还可以使用匿名嵌套的方式
go
type User struct {
Username string
Password string
Address
}
type Address struct {
Name string
Phone string
City string
}
go
type UserInfo struct {
Username string
Password string
Address
}
type Address struct {
Name string
Phone string
City string
}
func main() {
u := UserInfo {
Username: "Sharker",
Password: "xxxx",
// 嵌套结构体 可以定义匿名字段和命名字段混用
Address: Address{
Name: "北京",
Phone: "13263235040",
City: "北京",
},
}
fmt.Println(u)
}
赋值和调用和命名的一样
当访问结构体成员时会去查找结构体中查找该字段,如果找不到再去匿名结构体中查找 简单点说就是内部嵌套的结构体字段也可以在外层直接访问到
结构体命名访问冲突
优先访问的是嵌套其他结构体中的字段(外层的字段),访问被嵌套的需要加上结构体前缀
二义性了 需要加上结构提的前缀
结构体的继承
结构体的继承是通过结构体的嵌套来实现的
因为dog没有Name属性但是在调用Name的时候发现本结构体中没有的话就会去子结构体中寻找从而实现了dog继承了Animal相关的属性与方法 结构体中可以嵌套另一个结构体或者结构体指针
结构体与json相互转化
当时用Go语言写一些RESTFul接口的时候就需要涉及到结构体和Json之间的相互转化,Go Json序列化是指把结构体数据转化为Json格式的字符串,Go json反序列化是指把json数据转化为Golang中的结构体对象 Go 中的序列化与反序列化主要通过"encoding/json"包中的json.Marshal()和json.Unmarshal()方法
结构体标签
Tag是结构体的元信息,可以在运行时的时候通过反射机制读取出来,Tag在结构体字段的后方定义,由一对反引号 包裹起来,具体的格式如下 key1:"value1" key2:value2
可以定义多个tag标签 结构体tag由一个或者多个键值对组成,键与值使用冒号分割,值使用双引号括起来,同一个结构体字段可以设置多个键值对tag,不同的键值对tag之间使用空格分隔。
为结构体添加Tag时,必须严格遵守键值对的规则,结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值,例如不要在key和value之间添加空格
对于嵌套结构体序列化与反序列化与正常的非嵌套的结构体的处理是一样的按照正常的Marshal搞就可以了
Go 接口
Go中的接口是一种抽象数据类型,Go中接口定义了对象的行为规范,只定义规范不进行实现,接口中定义的规范需要由具体的对象来实现。 可以通俗的理解接口就是一个标准,他是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范。
Go 接口定义
在go中定义接口(interface)是一种类型,一种抽象的类型,接口是一组函数method的集合,go中的接口不能包含任何变量。 在go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现,接口体现了程序设计的多态和高内聚低耦合的思想。 Go中的接口也是一种数据类型,不需要显示实现,只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。
go
type 接口名 interface {
方法名1(参数列表) 返回值列表
方法名2(参数列表) 返回值列表
}
- 接口名 使用type将接口定义为自定义的类型名,GO语言的接口在命名时,一般在单词名后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等,接口名最好要能突出该接口的类型含义。
- 方法名 当方法名首字母是大写且这个接口名首字母也是大写时,这个方法可以被接口所在的包之外的代码访问。
- 参数列表、返回值列表 参数列表和返回值列表中的参数变量名可以省略
接口定义的方法没有函数体,不需要给出具体的实现 如果接口里面有方法的话,必须要通过结构体或者自定义类型实现这个接口,要实现这个接口的话必须要实现接口中的所有方法(实现这 结构体或者自定义类型) go 中接口就是一种数据类型 (interface)
外部调用是 computer.work(camera) 等价于 var usb Usber = camera
接口是规范 实现接口需要实现接口中的所有方法
空接口
GO中的接口可以不定义任何方法,没有任何方法的接口就是空接口,空接口表示没有任何约束,因此任何类型变量都可以实现空接口,空接口在实际使用的非常多,用空接口可以代表任意数据类型
空接口表示任意类型Any
go中空接口是可以直接当做类型来使用的, 可以表示任意类型
作为函数参数
使用空接口实现可以接受任意类型的函数参数
go
// 空接口作为函数参数
func show(a interface{}) {
函数体
}
map的值实现空接口
使用空接口实现可以保存任意值的字典
go
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "张三"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
切片实现空接口
go
var slice = []interface{}{"张三", 20, true, 32.9}
fmt.Println(slice)
类型断言
一个接口的值是由一个具体类型和具体类型的值两个部分组成的,这两部分分别被称为接口的动态类型和动态值。 如果我们想要判断空接口中的值的类型,那么这个时候就可以使用类型断言 x.(T)
- x 表示类型为interface{}的变量
- T 表示断言x可能的类型
定义一个方法可以传入任意数据类型,然后根据不同的类型实现不同的功能
只能在switch使用 x.(type)
结构体值接收者和指针接收者实现接口的区别
值接收者
如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针都可以赋值给接口类型变量
指针接收者
如果结构体中的方法是指针接收者,那么实例化后结构体指针类型可以赋值给接口变量 -- 只能是指针类型变量 值类型的变量不可以
一个结构体实现多个接口
两个接口Animal1, Animal2
接口嵌套
接口与接口之间可以通过嵌套创建出新的接口
空接口与类型断言的细节
go
func main() {
var userinfo = make(map[string]interface{})
userinfo["username"] = "张三"
userinfo["age"] = 20
userinfo["hobby"] = []string{"睡觉", "吃饭"}
fmt.Println(userinfo["age"])
fmt.Println(userinfo["hobby"])
fmt.Println(userinfo["hobby"][1])
}
interface{} dose not support indexing 空接口不支持idnex
获取struct里面的属性获取不到 因为是interface{}
对于第一个切片的问题 使用类型断言来给空接口一个具体的类型就可以实现访问了
对于struct是一样的
Go 指针
通过前面的教程我们知道变量是用来存储数据的,变量的本质是给存储数据的内存地址起了一个好记的名字,比如我们定义了一个变量a := 10
, 这个时候可以直接通过a
这个变量来读取内存中保存的10这个值,在计算机底层a
这个变量其实对应了一个内存地址。
指针也是一个变量,但是他是一种特殊的变量,他存储的数据不是一个普通的值,而是另一个变量的内存地址
通过地址取值
go
func main() {
a := 10
p := &a // p是指针变量 类型为*int
// *p 表示取出p这个变量对应的内存地址的值
}
将值类型变成了引用类型数据 取地址
new和make
指针需要创建内存后使用
go
var userinfo = make(map[string]string)
userinfo["username"] = "张三"
fmt.Println(userinfo)
map是引用类型数据 必须分配内存空间后才可以初始化
go
var a *int // 指针也是引用类型数据
*a = 100
fmt.Println(*a)
执行上面的代码会引发panic
,这是因为在Go语言中对于引用类型的变量,在使用的时候不仅声明它,还要为他分配内存空间,否则值就没法鵆,对于值类型的声明不需要分配内存空间,是因为他在声明的时候已经默认分配好了存储空间,要分配内存就引出了new和make,Go语言中的new和make是内建的两个函数,主要用于分配内存。
new
new是一个内置的函数,它的签名如下 func new(Type) *Type
- Type 表示类型,new函数只接受一个参数,这个参数是一个类型
- *Type表示类型指针,new函数返回一个指向该类型内存地址的指针 实际开发中new并不常用,使用new函数得到的是一个类型指针,并且该指针对应的值为该类型的零值
make
var a = make(type)
创建并分配内存空间, a的默认值为对应type
的零值
new 与 make 区别
- 两者都是用来做内存分配的
- make只能用于slice、map以及channel的初始化,返回的还是这三个引用类型本身
- 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针
Go time包以及日期函数
可以使用Day Hour等来访问时间的具体字段
Format格式化
时间类型有一个自带的方法Format进行格式化,需要注意的是Go语言中格式化时间模版不是常见的Y-m-d H:M:S而是使用了Go的诞生圣剑2006年1月2号15点04分(2006 1 2 3 4)
- 2006 年
- 01 月
- 02 日
- 03 时
- 04 分
- 05 秒
12进制和24进制的区别在于 15表示24小时制 03表示12小时制
时间戳
时间戳转日期
日期转化时间戳
上面的两种转化
- 时间戳转日期 先将时间戳转化为时间对象,再将时间对象通过
Format
转化为string - 日期转化时间戳 先将日期通过
ParseInLocation
转化为时间对象,在通过Unix
转化为时间戳
时间间隔
单位是纳秒
时间操作函数
定时器
Go mod 与 Go包详解
包的定义
包是多个Go源码的集合,是一种高级的代码复用方案,Go语言为我们提供了很多内置包,如fmt、strconv、strings、sort、error等 Go中的包可以分为三种
- 系统内置包 Go语言给我们提供了内置包,引用后可以直接使用,如fmt、strconv等
- 自定义包 开发者自己写的包
- 第三方包 属于自定义包的一种,需要下载安装到本地后才可以使用,如decimal包解决float精度丢失问题
包管理工具 go mod
在Go1.11版本之前如果我们要自定义包的话必须把项目放在GOPATH目录下,GO1.11版本之后无需要手动配置环境变量,使用go mod管理项目,也不需要非得把项目放到GOPATH指定目录下,可以在磁盘的任意位置新建项目,GO1.13以后彻底不要GOPATH了。
init
在实际项目开发中我们首先需要在项目中用go mod命令生成一个go.mod文件管理我们的项目依赖 go mod init 包名
自定义包
package是多个go源码的集合,一个包可以简单理解为一个存放多个.go文件的文件夹,该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明文件归属的包 package 包名
- 一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能再多个文件夹下面
- 包名可以不和文件夹的名字一样,包名不能包含
-
符号 - 包名为main的包为程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main的包的源码文件则不会得到可执行文件
自定义包名
在导入包名的时候,可以为导入的包设置别名,通常用于导入的包名太长或者导入的包名冲突的情况 import 别名 "包的路径"
go
import (
别名 "包的路径"
)
Go init函数
在Go程序执行时导入包语句会自动触发包内部的init函数,需要注意的是init()函数没有参数也没有返回值,init函数在程序运行时被自动调用执行,不能在代码中主动调用。 包初始化的顺序如下图所示 程序初始化流程
- 全局声明
- init()
- main()
init函数执行顺序
Go语言包会从main包开始检查其导入的所有包,每个包中又可能导入其他的包,Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码,在运行时被左后导入的包会在最初的时候调用其init函数
栈结构 先入后出
使用第三方包
可以在包管理查找常见的go第三方包 比如去上面的网址找到float精度丢失的包decimal 然后查看其安装命令
使用命令
安装第三方包
方式一
go get github.com/xxxx
全局
方式二
go mod download
全局
方式三
依赖包会自动下载到GOPath/pkg/mod中多个项目可以共享缓存的mod注意使用go mod download的时候首先需要再你的项目中引入第三方包
补充 Go mod 常见命令
Go 文件 目录操作
文件操作
读取文件
方式一 file.Read()
- 只读方式打开文件
file, err := os.Open()
- 读取文件
file.Read()
- 关闭文件流
defer file.Close()
一次读取128个字节,但是可能最后一个切片读取不满128个字节打印就会出现问题
file.Read返回的第一个参数为读取到的字节数,在将每个切片合入的时候通过tempSlice[:n] 在截取读取到的切片
方式二 bufio读取文件
- 只读方式打开文件
file, err := os.Open()
- 创建reader对象
reader := bufio.NewReader(file)
- ReadString读取文件
line, err := reader.ReadString('\n')
- 关闭文件流
defer file.Close()
bufio读取方式 reader.ReadString 返回的第一个参数是读取到的字符串
方式三 读取文件 os.ReadFile 打开关闭文件的方法都封装好了只需要一句话就可以读取 os.ReadFile
go
func main() {
str, err := os.ReadFile("./main.go")
if err == nil {
fmt.Println(string(str))
} else {
fmt.Println(err)
}
}
写入文件
打开文件的权限设置
方式一 file.Write
- 打开文件
file, err := os.OpenFile("文件地址", os.O_CREATE|os.O_RDWR, 0666)
- 写入文件
go
file.Write([]byte(str)) // 写入字节切片数据
file.WriteString("直接写入的字符串数据") // 直接写入字符串数据
- 关闭文件流
defer file.Close()
方式二 bufio 创建写入对象
- 打开文件
file, err := os.OPenFile("文件地址", os.O_CREATE|os.O_RDWR, 0666)
- 创建write对象
write := bufio.NewWriter(file)
- 将数据先写入缓存
write.WriteString("Hello\r\n")
- 将缓存中的内容写入文件
write.Flush()
- 关闭文件流
defer file.Close()
记得要Flush写入数据
方式三 os.WriteFile() 直接封装好的可以直接调用 os.WriteFile()
go
func main() {
str := "xxx"
os.WriteFile("./main.go", []byte(str), 0666)
}
文件重命名
复制文件
使用os.ReadFile
与os.WriteFile
完成文件的转移复制
使用file对象
注意要记得 关闭文件流!
删除文件
remove 不但可以删除文件也可以删除目录 删除多级目录使用removeAll 删除文件remove 递归删除removeAll