Go 语言学习
Go标准库
GolangStudy
Go语言项目结构
能一张图片解决我就不多说了

编写第一个程序
1
2
3
4
5
6
7
|
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
|
编译
在目录编译:
使用CMD打开存放源代码的目录,用命令
1
2
3
|
D:\Go\src\testOne\helloworld> go build
D:\Go\src\testOne\helloworld>helloworld.exe
Hello World
|
进行编译后会生成一个 .exe
文件,在CMD运行就可以输出 Hello World
。
在其他地方编译:
1
|
go build 后面写上src以后的文件路径
|
也就是从 GOPATH/src
后面开始写起
指定编译之后的 .exe
文件的名字
go run
像执行脚本文件一样执行Go代码
go install
go install
分为两部:
1.先编译得到一个可执行文件
2.将可执行文件拷贝到 GOPATH/bin
跨平台编译(交叉编译)
可以得到能在 Linux
MacOs
Windows
上运行的可执行文件
1
2
3
|
SET CGO_ENABLED=0 //禁用CGO
SET GOOS=LINUX //目标平台是Linux
SET GOARCH=amd64 //目标处理器架构是amd64位
|
MacOS 下编译Linux和Windows平台64位可执行程序:
1
2
|
CGO_ENABLED=0 GOOS=Linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
|
Linux下编译Mac和Windows平台64位可执行程序:
1
2
|
CGO_ENABLED=0 GOOS=drawin GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build
|
Windows 下编译Mac平台64位可执行程序:
1
2
3
4
|
SET CGO_ENABLED=0
SET GOOS=drawin
SET GOARCH=amd64
go build
|
语法规则
整体语法与C++类似
package main
特点:
函数外面只能写 变量、常量、函数、类型 的声明,不能写语句,如打印某字段之类的
没有缩进,保存后自动缩进。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//预声明包 main
package main
//导入语句 类似C++的头文件,导入某段框架
import "fmt"
//下面这个字段是非法的,会报错
fmt.Println("No write this")
//入口函数,与C++的 int main相似
func main(){
fmt.Println("Hello World")
}
|
变量与常量
变量
变量声明格式
Go语言的变量声明格式为:
1
2
3
|
var 变量名 变量类型
//如:
var num int
|
变量使用特色
Go语言中 非全局变量 声明后必须使用,不使用就编译不过去,这是因为编译时编译器会尽可能的缩小您的代码所占用的空间
可以使用打印暂时让程序正常编译;%s:占位符 使用num变量替换占位符
1
|
fmt.Printf("num:%s",num)
|
批量声明
1
2
3
4
5
6
|
var (
a string
b int
c bool
d float32
)
|
变量赋值
变量初始化
也就是声明的同时赋值
1
|
var nickName string = "罗斯特er"
|
短变量声明
也就是局部变量的意思,只能在函数体内使用.
匿名变量
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量,匿名变量用一个下划线_
表示,列如:
1
2
3
4
5
6
7
8
9
|
func foo() (int, string) {
return 10,"Q1mi"
}
func main() {
x,_ := foo()
_,y := foo()
fmt.Println("X=", x) //输出 10
fmt.Println("y=", y) //输出“Q1mi”
}
|
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
常量
不能更改的变量,强制性指定内容,无法更改。
常量初始化
也可以批量初始化常量
iota
常量计数器
在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota
计数一次
如:
1
2
3
4
5
6
|
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
|
定义数量级
<<
位运算符,表示在二进制的基础上左移
1
2
3
4
5
6
7
8
9
|
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
|
数据类型
Go语言中有丰富的数据类型,除了基本的整形、浮点型、布尔型、字符串外,还有数组、切片、结构体、函数、map、通道(Chanenl)等。Go语言的基本类型和其他语言大同小异。
基本数据类型
整型
有符号 int (含负数)
int8
8位整型(-128~127)
int16
16位整型(-32768~32767)
int32
32位整型(-2147483646~2147483647)
int64
64位整形 (超级大)
无符号 int
(不含负数)
uint8
8位整型
uint16
16位整型
uint32
32位整型
uint64
64位整形
特殊整型
uint
32位操作系统上就是uint32,64位系统上则是uint64
int
32位操作系统上就是int32,64位系统上则是int64
uintptr
无符号整型,用于存放一个指针
八进制和十六进制
1
2
3
4
5
6
7
8
|
func main() {
//十进制数
var num = 101
fmt.Printf("%d\n",num)
fmt.Printf("%b\n",num) //十进制转换二进制
fmt.Printf("%o\n",num) //十进制转换八进制
fmt.Printf("%x\n",num) //十进制转换十六进制
}
|
字符串
Go语言中的字符串
只能用 双引号("内容")
包裹
Go语言中 字符
可以用 单引号('h')
包裹
字符串起来就是字符串,字符串分开就是字符。每个字符串可以理解为一个数组
1
2
3
4
5
|
text := "Hello World"
word := 'W'
textNum := len(text) // len() 求字符串长度
fmt.Println(text)
|
数组
数组定义
Go语言中数组长度是数组类型的一部分,不能拿两个不同长度的数组比较
初始化
方式一
1
2
|
TorF = [2]bool{true,false}
// [true false]
|
方式二
根据初始值自动推断数组的长度是多少 [...]
<= 用这个语法
1
2
|
num := [...]int{1,2,3,4,5}
// [1 2 3 4 5]
|
方式三
根据索引来初始化
1
2
|
num := [5]int{0:1, 4:5}
// [1 0 0 0 5]
|
多维数组
[ [1, 1] [2, 2] [3, 3] ]
可以当成是一个平面,[x1,y1] ,[x2, y2] 这样的
定义:
初始化:
1
2
3
4
5
|
arr = [3][2]int{
[2]int{1,1},
[2]int{2,2},
[2]int{3,3}
}
|
if else
分支
1
2
3
4
5
6
7
8
|
age := 11
if age > 30 {
fmt.Println("中年人")
} else if age < 30 && age > 18 {
fmt.Printls("青年")
} else {
fmt.Println("未成年")
}
|
还可以用 if
判断内声明变量法,作用域知识,age
在外部为 undifind
1
2
3
4
5
6
7
|
if age:= 30; age > 30 {
fmt.Println("中年人")
} else if age < 30 && age > 18 {
fmt.Printls("青年")
} else {
fmt.Println("未成年")
}
|
for
循环
在 Go
语言中只有一个循环,就是 for
循环,但是他有很多变种。
基本格式
1
2
3
|
for i := 0; i < 10; i++ {
fmt.Println(i)
}
|
外部声明变量法
1
2
3
4
5
6
7
8
9
|
var i = 5
for ; i < 10; i++ {
fmt.Println(i)
}
//变种
for i < 10 {
fmt.Println(i)
i++
}
|
五限循环法
for
循环可以通过 break
、return
、panic
语句强制退出
1
2
3
|
for {
fmt.Println("输出")
}
|
for range
键值循环
Go语言中可以使用 for range
便利数组、切片、字符串、map及通道(channel)。通过 for range
遍历的返回值有以下规律:
1.数组、切片、字符串返回索引和值。
2.map返回键和值。
3.通道(channel)只返回通道内的值。
1
2
3
4
|
s := "Hello World"
for i, v := range s {
fmt.Printf("%d %c\n", i, v)
}
|
switch
1
2
3
4
5
6
7
8
9
10
|
n := 3
switch n {
case 1:
fmt.Println("1")
case n > 2:
fmt.Println("n大于2")
default:
fmt.Println("无效数字")
}
// 输出“n大于2”
|
另外
fallthrough
语法可以执行满足条件的case的下一个case,是为了兼容C语言中的case设计的
1
2
3
4
5
6
7
8
9
10
11
|
n := 1
switch n {
case 1:
fmt.Println("1")
fallthrough
case n > 2:
fmt.Println("n大于2")
default:
fmt.Println("无效数字")
}
// 输出 “1” 和 “n大于2”
|
goto
跳到某一行
1
2
3
4
5
|
goto next
fmt.Println("第一个输出")
next:
fmt.Println("第二个输出")
// 输出 “第二个输出” 第一个不输出
|
切片(slice)
切片是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包括 地址
、长度
、容量
。切片一般用于快速地操作一块数据集合。
1
2
3
4
5
6
7
|
func main() {
s := [...]int{0,1,2,3,4,5,6,7,8,9}
TheSlice1 := s[0:4] //这是一个切片 到数组第4位就停 不输出第四位 第一位输出
TheSlice2 := s[2:6]
fmt.Println(TheSlice1,TheSlice2)
//输出 [0 1 2 3] [2 3 4 5] 这里就没有4和6
}
|
二次切片
切片的容量是与引用数组的容量的相同
切片也可以引用切片,修改底层引用数组的长度和内容时后面的也会跟着变
make函数创造切片
1
|
arr := make([]类型,长度,容量)
|
为切片增加元素
1
2
3
4
5
6
7
|
s := []int{0,1,2,3,4,5,6,7,8,9}
TheSlice1 := s[0:4]
TheSlice1[10] = 10 //注意,不能这么写
// 调用append函数必须用原来的切片变量接收返回值
theSlicel = append(theSlicel,10)
fmt.Println(TheSlice1) //输出 [0 1 2 3 10]
|
copy函数
Go语言内建的 copy()
函数可以迅速的将一个切片的数据复制到另外一个切片空间中
使用方式如下:
1
|
copy(destSlice, srcSlice []T)
|
- srcSlice : 数据来源切片
- destSlice : 目标切片
- T数据类型
指针 pointer
Go中支持指针,但不支持指针运算,也就是说只有取值,和取地址
1
2
3
4
5
|
p := 1010
ThePoint := &p
fmt.Println(ThePoint)//输出地址 0xc00000a0a8
pValue := *ThePoint
fmt.Println(pValue)//根据地址取值 输出1010
|
make和new的区别
- make和new都是用来申请内存的
- new很少用,一般用来给基本数据类型申请内存,
string
、int
返回的是对应类型的指针(*String、*int)。
- make是用来给
slice
、map
、chan
申请内存的,make函数返回的是对应的这三个类型本身。
map
map是一种无序的基于 key-value
的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
map定义
Go语言中 map
的定义语法如下:
- keyType表示键的类型。
- ValueType表示键对应的值的类型
1
2
3
4
5
6
7
8
9
10
11
|
func main() {
var m1 map[string]int
fmt.Println(m1 == nil)
m1 = make(map[string]int,10)
m1["穷"] = 18
m1["丑"] = 35
fmt.Println(m1)
fmt.Println(m1["穷"])
}
|
类似对象的操作,输入口令提取对应的东西
map的遍历
1
2
3
4
5
6
7
8
9
10
11
12
|
fmt.Println("方式一")
for k,v := range m1 {
fmt.Println(k,v)
}
fmt.Println("方式二")
for k := range m1 {
fmt.Println(k)
}
fmt.Println("方式三")
for _, v := range m1 {
fmt.Println(v)
}
|
删除
1
2
3
|
fmt.Println("删除")
delete(m1,"丑")
fmt.Println(m1)
|
函数
函数的定义
函数的使用
1
2
3
4
5
6
7
|
func sum(x int, y int) (ret int) {
return x + y
}
func main() {
r := sum(2,3)
fmt.Println(r)
}
|
多个返回值
1
2
3
4
5
6
7
|
func sum() (int,string) {
return 100, "元"
}
func main() {
_, r := sum(2,3)
fmt.Println(r)
}
|
可变长度的参数
1
2
3
4
5
6
7
|
func fun(x int, y ...int) (ret int) {
fmt.Println(x)
fmt.Println(y) // y的类型是切片 []int
}
func main() {
fun(1,2,3,4,5,6,7,8,9)
}
|
无名称函数
函数内部没有办法声明有名字的函数,所以我们创建一个没有名字的函数,用变量去接收它
1
2
3
4
5
6
7
8
|
func main() {
fun1 := func(x, y int){
fmt.Println(x + y)
return
}
fun1(2,3)
}
|
Go语言中没有默认参数
闭包
闭包也是一个函数,但是这个函数包含了他外部作用域的一个或多个变量。
底层原理:
- 函数可以作为返回值
- 函数内部查找变量的顺序,现在自己内部找,找不到再去外层找。
1
2
3
4
5
6
7
8
9
10
11
12
|
func main() {
ret := adder(100)
ret2 := ret(200)
fmt.Println(ret2)
}
func adder(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
//输出 300
|
defer
最先 被定义defer的变量或者函数 最后执行
内置函数介绍
内置函数|介绍
- | —
close|主要用来关闭channel
len|用来求长度,比如string、array、slice、map、channel。
new|用来分配内存,主要用来分配值类型,比如int、string。返回的是指针
make|用来分配内存,主要引用类型,比如chan、map、slice
append|用来追加元素到数组、slice中
panic和recover|用来做错误处理
panic / recover
使用 panic/recover
模式来处理错误。panic
可以在任何地方引发,但是 recover
只有在 defer
调用的函数中有效。
自定义类型和类型别名
自定义类型
type
关键字用来定义自定义类型变量
1
2
3
4
|
type myInt int //自定义int类型变量
var m = myInt
m = 100
fmt.Println(m)
|
类型别名
某个类型的其他称呼,类似你int32类型变量 可以称为rune类型变量,rune的类型实际上就是int32类型,
1
2
3
4
|
type yourInt = int
var m yourInt
m = 100
fmt.Println(m)
|
自定义类型和类型别名的区别
可以理解为一个是新建,而另一个是在原有的基础上复制一个类型。
结构体struct
结构体是由不同类型变量组成的一个集合体
比如我用结构体造了一个标准人类的类型变量,里面包含这个人的年龄、姓名、性别这类属性。
1
2
3
4
5
6
7
8
9
10
|
type person struct {
name string
age int
gender string
}
var locter person
locter.name = "locter"
locter.age = 20
locter.gender = "男"
|
匿名结构体
多用于临时场景
1
2
3
4
5
6
7
|
var s struct {
x string
y int
}
s.x = "hallo"
s.y = 100
|
结构体指针
如果想在某个函数中修改结构体内的数据,就需要用到结构体指针。因为按常理在Go语言中函数的参数传参永远是拷贝粘贴的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type person struct {
name string
age int
gender string
}
func f(x person) {
x.gender = "男"
}
func f2(x *person) { //接收指针类型的person
(*x).gender = "男" //这里修改的是地址中的属性,即 a 的属性
}
func main() {
var a paerson
a.name = "王莉娜"
a.gender = "女"
f(a) // 这一行是无效的,并不能真正的去改变a的属性,因为函数传参是拷贝粘贴,改变的是a的副本
fmt.Println(a.gender) //女
f2(&a) //这里传过去的是a的地址
}
|
创建指针类型结构体
可以用 new
关键字来为结构体开辟一个内存地址
1
2
3
4
5
6
|
func main() {
var p2 = new(person)
fmt.Printf("%T\n", p2) //输出 *main.person
fmt.Printf(p2) //输出 &{ }
fmt.Printf("%p\n", p2) //输出p2变量的内存地址
}
|
匿名字段结构体
字段比较少也比较简单的场景,不常使用的方法
列如下面这类,没有对结构体内的数值进行命名
1
2
3
4
|
type person struct {
string
int
}
|
如何使用未命名字段?
1
2
3
4
5
6
7
8
|
func main() {
p1 := person {
"张三",
9000,
}
fmt.Println(p1)
fmt.Println(p1.string)//可以使用类型来选择字段
}
|
嵌套结构体
在一个结构体中嵌套另一个结构体,这样做的好处是不用反复的写同样的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
//结构体嵌套
type address struct {
province string
city string
}
type person struct {
name string
age int
addr address
}
type company struct {
name string
addr address
}
func main() {
p1 := person{
name : "张三",
age : 19,
addr: address{
province: "广西",
city: "长沙",
},
}
fmt.Println(p1)
fmt.Println(p1.addr.city)
}
|
匿名嵌套结构体
优点是可以不用对嵌套的结构体命名,还可以直接访问嵌套的结构体的数据,不需要进行二次进入内部然后访问,大多数时候会用到这个方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//匿名嵌套结构体
type address struct {
province string
city string
}
type person struct {
name string
age int
address
}
func main() {
p1 := person{
name : "张三",
age : 19,
address: address{
province: "广西",
city: "长沙",
},
}
fmt.Println(p1)
fmt.Println(p1.city)//不需要 p1.address.city
}
|
可以使用嵌套结构体来模拟实现继承,Go语言中本没有继承
结构体与JSON
在这里需要用到json包!!!
1.序列化: 把Go语言中的结构体变量 => Json格式的字符串
2.反序列化: Json格式的字符串 => Go语言中能够识别的结构体变量
序列化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package main
import (
"encoding/json"
"fmt"
)
// 结构体与JSON
type person struct {
Name string
Age int
}
func main() {
p1 := person{
Name: "张三",
Age: 22,
}
b, err := json.Marshal(p1)
if err != nil {
fmt.Printf("Marshal failed err:%v", err)
return
}
fmt.Println(string(b))
}
//最终输出:{"Name":"张三","Age":22}
|
反序列化:
1
2
3
4
5
6
7
8
9
10
|
str := `{"Name":"张三","Age":22}`
var p2 person
_err := json.Unmarshal([]byte(str), &p2)
if _err != nil {
fmt.Printf("Marshal failed err:%v", _err)
return
}
fmt.Printf("%#v\n", p2)
//最终输出:main.person{Name:"张三", Age:22}
|
方法和接收者
Go语言中的 方法(Method)
是一种作用于特定类型变量的函数。这种特定类型变量叫做 接收者(Receiver)
。接受者的概念就类似于其它语言中的 this
或者 self
。
方法的定义格式如下:
1
2
3
|
func (接受者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
|
列如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
type dog struct {
name string
}
//构造函数
func newDog(name string) dog {
return dog {
name: name,
}
}
//方法和接收者
func (d dog) wang() {
fmt.Printf("%s : 汪汪汪~", d.name)
}
func main() {
d1 := newDog("汪柴")
d1.wang() //输出 “汪柴 : 汪汪汪~”
}
|
接口(interface)
接口是一种类型,是一种特殊的类型,它规定了变量有哪些方法。
(你把东西传进去,不管什么类型他都能运行,类似print
方法)
在编程中会遇到以下场景:
- 我不关心一个变量是什么类型,我只关心能调用它的什么方法
接口也可以嵌套
接口的定义与实现
1
2
3
4
|
type 接口名 interface {
方法名1(参数1, 参数2...)(返回值1, 返回值2...)
方法名2(参数1, 参数2...)(返回值1, 返回值2...)
}
|
用来给变量\参数\返回值等设置类型。
接口的实现
一个变量如果实现了接口中规定的所有的方法,那么这个变量就实现了这个接口,可以称为这个接口类型的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
type cat struct {}
type dog struct {}
type person struct {}
//定义一个能叫的类型,使用关键字interface
type speaker interface {
speak() //只要实现了speak方法的变量都是speaker类型
}
func (c cat) speak() {
fmt.Println("喵喵喵~")
}
func (d dog) speak() {
fmt.Println("旺旺旺~")
}
func (p person) speak() {
fmt.Println("啊啊啊~")
}
//在原本的方法中,传递参数只能有一种类型,而我们要传的是三种不同类型
//分别是 cat dog person 这在一般情况下是不允许的
//但是我们可以定义一个可以传进去的类型,speaker 他是interface(接口)类型
//在其中调用speak函数
func da(x speaker) {
x.speak()
}
func main() {
var c1 cat
var d1 dog
var p1 person
da(c1)
da(d1)
da(p1)
}
|
值接收者和指针接收者的区别
使用值接收者实现接口,结构体类型和结构体指针类型的变量都能存。
指针接收者实现接口只能接收指针类型的变量,无法接收值类型变量。
接口和类型的关系
多个类型可以实现同一个接口。
一个类型可以实现多个接口。
空接口
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
空接口没有必要起名字
所有的类型都实现了空接口,也就是任意类型的变量都能保存到空接口中。
1
2
3
4
5
6
7
8
9
10
|
func main() {
var m1 map[string]interface{}
m1 = make(map[string]interface{}, 16)
m1["name"] = "张三"
m1["age"] = 18
m1["married"] = true
m1["hobby"] = [...]string{"唱","跳","rap"}
fmt.Println(m1)
}
// 输出: map[age:18 hobby:[唱 跳 rap] married:true name:张三]
|
空接口参数使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
func main() {
var m1 map[string]interface{}
m1 = make(map[string]interface{}, 16)
m1["name"] = "张三"
m1["age"] = 18
m1["married"] = true
m1["hobby"] = [...]string{"唱","跳","rap"}
show(m1)
show(false)
show(nil)
}
//输出:
//type:map[string]interface {} value:map[age:18 hobby:[唱 跳 rap] married:true name:张三]
//type:bool value:false
//type:<nil> value:<nil>
|
类型断言
空接口可以储存任意类型的值,那我们如何获取其储存的具体数据呢?
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
其中:
- x: 表示类型为interface{}的变量;
- T: 表示断言 x可能是的类型;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
switch t := a.(type) {
case int:
fmt.Println("is int: ",t)
case string:
fmt.Println("is string: ",t)
case bool:
fmt.Println("is bool: ",t)
}
}
func main() {
show("hello")
}
// 输出:
// type:string value:hello
// is string: hello
|
包(package)
包介绍
包(package)
十多个Go源码的集合,是一种高级的代码复用方案。Go语言为我们提供了很多内置包,如:fmt
、os
、io
等
定义包
我们还可以根据自己的需要创建自己的包,一个包可以简单理解为一个存放.go
文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包
注意事项:
- 一个文件夹下面只能有一个包,同样一个包的文件不能再多个文件夹下。
- 包名可以不和文件夹的名字一样,包名不包含
-
符号。
- 包名为
main
的包为应用程序的入口包,编译时不包含main
包的源代码时不会得到可执行文件。
使用包
要注意的事!包中的标识符(变量名/函数名/结构体/接口等)如果首字母时小写的,表示私有(只能在当前这个包中使用),如果想要在其他包中使用,需要将首字母改成大写。
包的路径可以写相对路径,在使用 import
导入包时,不需要填写绝对位置,就比如我要导入 calc
文件夹中的 hello.go
时不需要写成如下格式:
1
2
3
4
5
|
//错误方式
import "../calc/hello.go"
//正确方式
import "../calc"
|
你只需要指定它的目录即可,此处的../
表示返回上一级目录
Go语言中禁止循环导入包!!!
文件操作
首先导入os
包,对于文件操作多数时候都需要使用到os
包,如下:
打开文件
os.Open()
函数能够打开一个文件,返回一个 *File
和一个 err
关闭文件
对得到的文件实例调用Close()
方法能够关闭文件。
读取文件 file.Read()
1
|
func (f *File) Read(b []byte) (n int, err error)
|
它接受一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时返回0
和io.EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
D:\Go\src\FileOperation>go run File.go
读了128个字节
package main
import (
"fmt"
"os"
)
func main() {
fileObj, err := os.Open("./File.go")
if err != nil {
fmt.Println("Unabl
读了128个字节
e to open this file: ", err)
return
}
// 关闭文件
defer fileObj.Close()
// 读文件
// var tmp = make([]byte, 128) /
读了128个字节
/指定读的长度
var tmp [128]byte
for {
n, err := fileObj.Read(tmp[:])
if err != nil {
fmt.Printf("read from file f
读了128个字节
ailed,err:%v", err)
return
}
fmt.Printf("读了%d个字节\n", n)
fmt.Println(string(tmp[:n]))
if n < 128 {
return
读了10个字节
}
}
}
|
文件写入操作
os.OpenFile()
函数能够以指定模式打开文件,从而实现文件写入相关功能。
1
2
3
4
|
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
}
|
其中,name
:要打开文件的文件名,flag
:打开文件的模式,有以下几种:
模式 |
含义 |
os.O_WRONLY |
只写 |
os.O_CREATE |
创建文件 |
os.O_RDONLY |
只读 |
os.O_RDWR |
读写 |
os.O_TRUNC |
清空 |
os.O_APPEND |
追加 |
perm
: 文件权限,一个八进制数。r(读)o4、w(写)o2、x (执行)o1。
练习:制作一个日志记录程序
接口:用处?日志可以输出到终端,也可以输出到文件,输出到kafka
文件操作:
需求分析
- 支持往不同的地方输出日志
- 日志分级别
- Debug
- Trace
- Info
- Warning
- Error
- Fatal
- 日志要支持开关控制,开发阶段都可以使用,上线后只有INFO级别以下才能使用。
- 完整的日志记录要包含有时间、行号、文件名、日志级别、日志信息
- 日志文件要切割