Featured image of post Go 语言学习(基础)

Go 语言学习(基础)

我的Golang学习笔记之浅入浅出

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 文件的名字

1
go build -o hello.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
age := 19
匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量,匿名变量用一个下划线_表示,列如:

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”
}

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。

常量

不能更改的变量,强制性指定内容,无法更改。 常量初始化

1
const pi = 3.141596543

也可以批量初始化常量

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)

数组

数组定义

1
var TorF [2]bool

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
var arr [3][2]int

初始化:

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 循环可以通过 breakreturnpanic 语句强制退出

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很少用,一般用来给基本数据类型申请内存,stringint返回的是对应类型的指针(*String、*int)。
  • make是用来给 slicemapchan 申请内存的,make函数返回的是对应的这三个类型本身。

map

map是一种无序的基于 key-value 的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

map定义

Go语言中 map 的定义语法如下:

1
map[keyType]ValueType
  • 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
func sum(参数) (返回值) {}

函数的使用

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() (intstring) {
	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
interface {}

空接口没有必要起名字 所有的类型都实现了空接口,也就是任意类型的变量都能保存到空接口中。

 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>

类型断言

空接口可以储存任意类型的值,那我们如何获取其储存的具体数据呢?

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

1
x.(T)

其中:

  • 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语言为我们提供了很多内置包,如:fmtosio

定义包

我们还可以根据自己的需要创建自己的包,一个包可以简单理解为一个存放.go文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包

1
package 包名

注意事项:

  • 一个文件夹下面只能有一个包,同样一个包的文件不能再多个文件夹下。
  • 包名可以不和文件夹的名字一样,包名不包含 -符号。
  • 包名为main的包为应用程序的入口包,编译时不包含main包的源代码时不会得到可执行文件。

使用包

要注意的事!包中的标识符(变量名/函数名/结构体/接口等)如果首字母时小写的,表示私有(只能在当前这个包中使用),如果想要在其他包中使用,需要将首字母改成大写。

包的路径可以写相对路径,在使用 import 导入包时,不需要填写绝对位置,就比如我要导入 calc 文件夹中的 hello.go 时不需要写成如下格式:

1
2
3
4
5
//错误方式
import "../calc/hello.go"

//正确方式
import "../calc"

你只需要指定它的目录即可,此处的../表示返回上一级目录

Go语言中禁止循环导入包!!!

文件操作

首先导入os包,对于文件操作多数时候都需要使用到os包,如下:

1
import "os"

打开文件

os.Open() 函数能够打开一个文件,返回一个 *File 和一个 err

关闭文件

对得到的文件实例调用Close()方法能够关闭文件。

读取文件 file.Read()

1
func (f *File) Read(b []byte) (n int, err error)

它接受一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时返回0io.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

文件操作:

需求分析

  1. 支持往不同的地方输出日志
  2. 日志分级别
    1. Debug
    2. Trace
    3. Info
    4. Warning
    5. Error
    6. Fatal
  3. 日志要支持开关控制,开发阶段都可以使用,上线后只有INFO级别以下才能使用。
  4. 完整的日志记录要包含有时间、行号、文件名、日志级别、日志信息
  5. 日志文件要切割
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus