go学习笔记(一)

Go语言笔记

Go 使用编译器来编译代码。编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。要创建并运行 Go 程序,程序员必须执行如下步骤。

  1. 使用文本编辑器创建 Go 程序;
  2. 保存文件;
  3. 编译程序;
  4. 运行编译得到的可执行文件。

go语言基本语法

go语言数据类型

go语言按类别有以下几种数据类型:

  1. 布尔型
    布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
  2. 数字类型
    整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
  3. 字符串类型:
    字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
  4. 派生类型:
    包括:
    • (a) 指针类型(Pointer)
    • (b) 数组类型
    • (c) 结构化类型(struct)
    • (d) Channel 类型
    • (e) 函数类型
    • (f) 切片类型
    • (g) 接口类型(interface)
    • (h) Map 类型

go语言指针

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

​ 受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

认识指针地址和指针类型:

实例:

package main

import "fmt"

func main() {
	var cat int = 1
	var str string = "banana"
	fmt.Printf("内存地址为:%p,%p", &cat, &str)
}
//输出:内存地址为:0xc00001a0a8,0xc000050250

提示:变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。

从指针获取指针指向的值:

当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值,代码如下。

实例:

package main

import "fmt"

func main() {
	var cat int = 1
	var str string = "banana"
	fmt.Printf("内存地址为:%p,%p", &cat, &str)

	ptr := &str
	fmt.Printf("内存地址为:%x", ptr)
	sum := *ptr
	fmt.Printf("sum的值为::%T,%s", sum, sum)
}

使用指针修改值:

实例:

package main
import "fmt"
// 交换函数
func swap(a, b *int) {
    // 取a指针的值, 赋给临时变量t
    t := *a
    // 取b指针的值, 赋给a指针指向的变量
    *a = *b
    // 将a指针的值赋给b指针指向的变量
    *b = t
}
func main() {
// 准备两个变量, 赋值1和2
    x, y := 1, 2
    // 交换变量值
    swap(&x, &y)
    // 输出变量值
    fmt.Println(x, y)
}

实例:使用指针变量获取命令行的输入信息

package main
// 导入系统包
import (
    "flag"
    "fmt"
)
// 定义命令行参数
var mode = flag.String("mode", "", "process mode")
func main() {
    // 解析命令行参数
    flag.Parse()
    // 输出命令行参数
    fmt.Println(*mode)
}

将这段代码命名为 main.go,然后使用如下命令行运行:

go run main.go --mode=fast

命令行输出结果如下:

fast

创建指针的另一种方法-new()函数

str := new(string)
*str = "Go语言教程"
fmt.Println(*str)

go语言变量声明

Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。

声明变量的一般形式是使用 var 关键字:

var name type

其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型。

实例:

package main
import "fmt"
func main() {
    var a string = "Runoob"
    fmt.Println(a)

    var b, c int = 1, 2
    fmt.Println(b, c)
}

指定变量类型,如果没有初始化,则变量默认为零值

  • 数值类型(包括complex64/128)为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)
  • 以下几种类型为 nil
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

实例

package main

var x, y int
var (  // 这种因式分解关键字的写法一般用于声明全局变量
    a int
    b bool
)

var c, d int = 1, 2
var e, f = 123, "hello"

//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"

func main(){
    g, h := 123, "hello"
    println(x, y, a, b, c, d, e, f, g, h)
}

注意事项

package main

import "fmt"

func main() {
   var a string = "abc"
   fmt.Println("hello, world")
}		

尝试编译这段代码将得到错误 a declared but not used

此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用

fmt.Println("hello, world", a)

但是全局变量是允许声明但不使用的。 多变量可以在同一行进行赋值,如:

var a, b int
var c string
a, b, c = 5, 7, "abc"

上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:

a, b, c := 5, 7, "abc"

这被称为 并行 或 同时 赋值。

交换变量:如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

匿名变量:空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

go语言变量逃逸分析

栈的概念:

  • 栈是一种特殊规则的线性表的数据结构
  • 特点是先进后出
  • 往栈中放入元素的过程叫做入栈。入栈会增加栈的元素数量,最后放入的元素总是位于栈的顶部,最先放入的元素总是位于栈的底部
  • 从栈中取出元素时,只能从栈顶部取出。取出元素后,栈的元素数量会变少。最先放入的元素总是最后被取出,最后放入的元素总是最先被取出。不允许从栈底获取数据,也不允许对栈成员(除了栈顶部的成员)进行任何查看和修改操作

堆的概念:

	堆在内存分配中类似于往一个房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够装下家具的空间再摆放家具。经过反复摆放和腾空家具后,房间里的空间会变得乱七八糟,此时再往这个空间里摆放家具会发现虽然有足够的空间,但各个空间分布在不同的区域,没有一段连续的空间来摆放家具。此时,内存分配器就需要对这些空间进行调整优化。
	堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。

逃逸分析:

例1:

package main
import "fmt"
// 本函数测试入口参数和返回值情况
func dummy(b int) int {
    // 声明一个变量c并赋值
    var c int
    c = b
    return c
}
// 空函数, 什么也不做
func void() {
}
func main() {
    // 声明a变量并打印
    var a int
    // 调用void()函数
    void()
    // 打印a变量的值和dummy()函数返回
    fmt.Println(a, dummy(0))
}
go run -gcflags "-m -l" main.go
//使用 go run 运行程序时,-gcflags 参数是编译参数。其中 -m 表示进行内存分配分析,-l 表示避免程序内联,也就是避免进行程序优化。
//运行结果如下:
# command-line-arguments
./main.go:22:13: ... argument does not escape
./main.go:22:13: a escapes to heap
./main.go:22:22: dummy(0) escapes to heap
0 0

​ 上面例子中变量 c 是整型,其值通过 dummy() 的返回值“逃出”了 dummy() 函数。变量 c 的值被复制并作为 dummy() 函数的返回值返回,即使变量 c 在 dummy() 函数中分配的内存被释放,也不会影响 main() 中使用 dummy() 返回的值。变量 c 使用栈分配不会影响结果。

取地址发生逃逸:

package main
import "fmt"
// 声明空结构体测试结构体逃逸情况
type Data struct {
}
func dummy() *Data {
    // 实例化c为Data类型
    var c Data
    //返回函数局部变量地址
    return &c
}
func main() {
    fmt.Println(dummy())
}
//执行命令
go run -gcflags "-m -l" main.go
//结果分析
# command-line-arguments
./main.go:11:6: moved to heap: c
./main.go:16:13: ... argument does not escape
&{}

​ 将 c 移到堆中,这句话表示,Go 编译器已经确认如果将变量 c 分配在栈上是无法保证程序最终结果的,如果这样做,dummy() 函数的返回值将是一个不可预知的内存地址

​ 这种情况一般是 C/C++ 语言中容易犯错的地方,引用了一个函数局部变量的地址,Go语言最终选择将 c 的 Data 结构分配在堆上。然后由垃圾回收器去回收 c 的内存

逃逸分析场景:

指针逃逸

package main

func test2() *int {
	c1 := 20
	return &c1
}

func main() {
	c2 := test2()
	println(*c2)
}

栈空间不足逃逸

package main

func main() {
	t := make([]int, 1000)
	m := make([]int, 10000)

	t[0] = 1
	m[0] = 1
}

/*
make([]int, 1000) does not escape
make([]int, 10000) escapes to heap
*/

闭包引用逃逸

package main

func Fibonacci() func() int {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}

func main() {
	f := Fibonacci()
	for i := 0; i < 5; i++ {
		println(f())
	}
}

动态类型逃逸

当对象不确定大小或者被作为不确定大小的参数时发生逃逸。t的大小是个变量所以会逃逸到堆上。size作为interface{}参数逃逸到堆上

package main

func main() {
	var size int = 10
	t := make([]int, size)
	m := make([]int, 10)
	for i := 0; i < size; i++ {
		t[i] = i
		m[i] = i
	}
}

/*
make([]int, size) escapes to heap
make([]int, 10) does not escape
*/

切片或者map赋值

在给切片或者map赋值对象指针(与对象共享内存地址时),对象会逃逸到堆上。但赋值对象值或者返回对象值切片是不会发生逃逸的。

package main

func test3() {
	var i = 10
	var j = 10
	s2 := make([]*int, 2)
	m2 := make(map[int]*int)

	s2[0] = &i
	m2[0] = &j
}

func main() {
	test3()
}

/*
./test.go:442:6: moved to heap: i
./test.go:443:6: moved to heap: j
./test.go:445:12: make([]*int, 2) does not escape
./test.go:447:12: make(map[int]*int) does not escape
*/

go语言常量

常量的定义格式:

const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

常量可以用做枚举:

const (
    Unknown = 0
    Female = 1
    Male = 2
)

iota常量生成器

例:首先定义一个 Weekday 命名类型,然后为一周的每天定义了一个常量,从周日 0 开始。在其它编程语言中,这种类型一般被称为枚举类型。

type Weekday int
const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

go语言type关键字(类型别名)

区分类型别名与类型定义

定义类型别名的写法为:

type TypeAlias = Type

类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名.

例:

package main
import (
    "fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
    // 将a声明为NewInt类型
    var a NewInt
    // 查看a的类型名
    fmt.Printf("a type: %T\n", a)
    // 将a2声明为IntAlias类型
    var a2 IntAlias
    // 查看a2的类型名
    fmt.Printf("a2 type: %T\n", a2)
}

结果显示 a 的类型是 main.NewInt,表示 main 包下定义的 NewInt 类型,a2 类型是 int,IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。

非本地类型不能定义方法

能够随意地为各种类型起名字,是否意味着可以在自己包里为这些类型任意添加方法呢?

package main
import (
    "time"
)
// 定义time.Duration的别名为MyDuration
type MyDuration = time.Duration
// 为MyDuration添加一个函数
func (m MyDuration) EasySet(a string) {
}
func main() {
}

编译报错cannot define new methods on non-local type time.Duration

不能在一个非本地的类型 time.Duration 上定义新方法,非本地类型指的就是 time.Duration 不是在 main 包中定义的,而是在 time 包中定义的,与 main 包不在同一个包中,因此不能为不在一个包中的类型定义方法。

解决这个问题有下面两种方法:

  • 将第 8 行修改为 type MyDuration time.Duration,也就是将 MyDuration 从别名改为类型;
  • 将 MyDuration 的别名定义放在 time 包中。

go语言关键字与标识符

Go语言的词法元素包括 5 种,分别是标识符(identifier)、关键字(keyword)、操作符(operator)、分隔符(delimiter)、字面量(literal),它们是组成Go语言代码和程序的最基本单位。

关键字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

go语言容器

go语言数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。

和数组对应的类型是 Slice(切片),Slice 是可以增长和收缩的动态序列,功能也更灵活。

go语言数组声明

数组的声明语法如下:

var 数组变量名 [元素数量]Type

语法说明如下所示:

  • 数组变量名:数组声明及使用时的变量名。
  • 元素数量:数组的元素数量,可以是一个表达式,但最终通过编译期计算的结果必须是整型数值,元素数量不能含有到运行时才能确认大小的数值。
  • Type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。

数组的每个元素都可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置,内置函数 len() 可以返回数组中元素的个数。

例:

var a [3]int             // 定义三个整数的数组
fmt.Println(a[0])        // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素
// 打印索引和元素
for i, v := range a {
    fmt.Printf("%d %d\n", i, v)
}
// 仅打印元素
for _, v := range a {
    fmt.Printf("%d\n", v)
}

初始化数值:

var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"

提示:可以根据...省略号来定义数组

q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

数组的长度是数组类型的一个组成部分,因此 [3]int 和 [4]int 是两种不同的数组类型,数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。

q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // 编译错误:无法将 [4]int 赋给 [3]int

比较两个数组是否相等

a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // 编译错误:无法比较 [2]int == [3]int

遍历数组

var team [3]string
team[0] = "hammer"
team[1] = "soldier"
team[2] = "mum"
for k, v := range team {
    fmt.Println(k, v)
}

go语言多维数组

例:

// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1 和 3 的元素
array = [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化数组中指定的元素
array = [4][2]int{1: {0: 20}, 3: {1: 41}}

go语言切片

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。

从数组或切片生成新的切片

从连续内存区域生成切片是常见的操作,格式如下:

slice [开始位置 : 结束位置]

语法说明如下:

  • slice:表示目标切片对象;
  • 开始位置:对应目标切片对象的索引;
  • 结束位置:对应目标切片的结束索引。

从数组生成切片

var a  = [3]int{1, 2, 3}
fmt.Println(a, a[1:2])
//输出:[1 2 3]  [2]

其中 [2] 就是 a[1:2] 切片操作的结果。

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置 - 开始位置;
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
  • 当缺省开始位置时,表示从连续区域开头到结束位置;
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
  • 两者同时缺省时,与切片本身等效;
  • 两者同时为 0 时,等效于空切片,一般用于切片复位。

声明新的切片

例:

// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty)
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
/*
[] [] []
0 0 0
true
true
false
*/

使用make()函数构造切片

如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:

make( []Type, size, cap )

其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。

a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
//输出:
[0 0] [0 0]
2 2

备注:使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

go语言append()为切片添加元素

例:

var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……,代码如下:

var numbers []int
for i := 0; i < 10; i++ {
    numbers = append(numbers, i)
    fmt.Printf("len: %d  cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
}

除了在切片的尾部追加,我们还可以在切片的开头添加元素:

var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

因为 append 函数返回新切片的特性,所以切片也支持链式操作,我们可以将多个 append 操作组合起来,实现在切片中间插入元素:

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

每个添加操作中的第二个 append 调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 中。

go语言copy():切片复制

Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

go语言从切片中删除元素

从开头位置删除

删除开头的元素可以直接移动数据指针:

a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素

也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):

a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素

还可以用 copy() 函数来删除开头的元素:

纯文本复制
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素

从中间位置删除

对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成:

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

从尾部删除

a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素

删除切片指定位置的元素

package main
import "fmt"
func main() {
    seq := []string{"a", "b", "c", "d", "e"}
    // 指定删除位置
    index := 2
    // 查看删除位置之前的元素和之后的元素
    fmt.Println(seq[:index], seq[index+1:])
    // 将删除点前后的元素连接起来
    seq = append(seq[:index], seq[index+1:]...)
    fmt.Println(seq)
}

go语言关键字:循环迭代切片

例1:

// 创建一个整型切片,并赋值
slice := []int{10, 20, 30, 40}
// 迭代每一个元素,并显示其值
for index, value := range slice {
    fmt.Printf("Index: %d Value: %d\n", index, value)
}

例2:

// 创建一个整型切片,并赋值
slice := []int{10, 20, 30, 40}
// 迭代每个元素,并显示其值
for _, value := range slice {
    fmt.Printf("Value: %d\n", value)
}

例3:

// 创建一个整型切片,并赋值
slice := []int{10, 20, 30, 40}
// 从第三个元素开始迭代每个元素
for index := 2; index < len(slice); index++ {
    fmt.Printf("Index: %d Value: %d\n", index, slice[index])
}

go语言的多维切片

例:

//声明一个二维切片
var slice [][]int
//为二维切片赋值
slice = [][]int{{10}, {100, 200}}

可以简写成:

// 声明一个二维整型切片并赋值
slice := [][]int{{10}, {100, 200}}

例2:组合切片的切片

// 声明一个二维整型切片并赋值
slice := [][]int{{10}, {100, 200}}
// 为第一个切片追加值为 20 的元素
slice[0] = append(slice[0], 20)
//输出:
[[10 20] [100 200]]

go语言map

map 是引用类型,可以使用如下方式声明:

var mapname map[keytype]valuetype

其中:

  • mapname 为 map 的变量名。
  • keytype 为键类型。
  • valuetype 是键对应的值类型。

提示:[keytype] 和 valuetype 之间允许有空格。

在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 pair 的数目。

实例:

package main
import "fmt"
func main() {
    var mapLit map[string]int
    //var mapCreated map[string]float32
    var mapAssigned map[string]int
    mapLit = map[string]int{"one": 1, "two": 2}
    mapCreated := make(map[string]float32)
    mapAssigned = mapLit
    mapCreated["key1"] = 4.5
    mapCreated["key2"] = 3.14159
    mapAssigned["two"] = 3
    fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
    fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
    fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
    fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
}
//输出结果:
Map literal at "one" is: 1
Map created at "key2" is: 3.14159
Map assigned at "two" is: 3
Map literal at "ten" is: 0

示例中 mapLit 演示了使用{key1: value1, key2: value2}的格式来初始化 map ,就像数组和结构体一样。

上面代码中的 mapCreated 的创建方式mapCreated := make(map[string]float)等价于mapCreated := map[string]float{}

mapAssigned 是 mapList 的引用,对 mapAssigned 的修改也会影响到 mapLit 的值。

注意:可以使用 make(),但不能使用 new() 来构造 map,如果错误的使用 new() 分配了一个引用对象,会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:

mapCreated := new(map[string]float)

接下来当我们调用mapCreated["key1"] = 4.5的时候,编译器会报错:

invalid operation: mapCreated["key1"] (index of type *map[string]float).

go语言遍历map

例:对map进行排序,map是无序的,需要解除数组来排序

scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
// 声明一个切片保存map数据
var sceneList []string
// 将map数据遍历复制到切片中
for k := range scene {
    sceneList = append(sceneList, k)
}
// 对切片进行排序
sort.Strings(sceneList)
// 输出
fmt.Println(sceneList)

go语言元素的删除

Go语言提供了一个内置函数 delete(),用于删除容器内的元素

例:

scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
delete(scene, "brazil")
for k, v := range scene {
    fmt.Println(k, v)
}

go语言列表list

​ 列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。

​ 在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。

初始化列表

list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。

\1) 通过 container/list 包的 New() 函数初始化 list

变量名 := list.New()

\2) 通过 var 关键字声明初始化 list

var 变量名 list.List

​ 列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机。

在列表中插入元素

双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack。

提示

这两个方法都会返回一个 *list.Element 结构,如果在以后的使用中需要删除插入的元素,则只能通过 *list.Element 配合 Remove() 方法进行删除,这种方法可以让删除更加效率化,同时也是双链表特性之一。

在列表中插入元素

l := list.New()
l.PushBack("fist")
l.PushFront(67)

列表插入元素的方法如下表所示。

方 法 功 能
InsertAfter(v interface {}, mark * Element) * Element 在 mark 点之后插入元素,mark 点由其他插入函数提供
InsertBefore(v interface {}, mark * Element) *Element 在 mark 点之前插入元素,mark 点由其他插入函数提供
PushBackList(other *List) 添加 other 列表元素到尾部
PushFrontList(other *List) 添加 other 列表元素到头部

列表操作元素:

package main
import "container/list"
func main() {
    l := list.New()
    // 尾部添加
    l.PushBack("canon")
    // 头部添加
    l.PushFront(67)
    // 尾部添加后保存元素句柄
    element := l.PushBack("fist")
    // 在fist之后添加high
    l.InsertAfter("high", element)
    // 在fist之前添加noon
    l.InsertBefore("noon", element)
    // 使用
    l.Remove(element)
}

代码说明如下:
第 6 行,创建列表实例。
第 9 行,将字符串 canon 插入到列表的尾部。
第 12 行,将数值 67 添加到列表的头部。
第 15 行,将字符串 fist 插入到列表的尾部,并将这个元素的内部结构保存到 element 变量中。
第 18 行,使用 element 变量,在 element 的位置后面插入 high 字符串。
第 21 行,使用 element 变量,在 element 的位置前面插入 noon 字符串。
第 24 行,移除 element 变量对应的元素。

遍历列表-访问列表的每一个元素

​ 遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数,代码如下所示。

l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
    fmt.Println(i.Value)
}

go语言make和new关键字

关键点

1、分配内存的内置函数是new 和 make
2、new 方式 分配内存,并不常用,一般使用短式和结构体字面量的方式
3、new 不能用于map, channel, 切片的声明
4、new 分配时,接收的是类型,返回的是该类型的指针,并且默认初始化了
5、make 分配后,没有初始化,需要显示初始化
6、make 分配内存后,返还的是该类型
7、make的使用场景? 下面三个进行声明和初始化时用
channel 切片(slice) map
8、sync.Map, 以及 sync.Mutex 这两个可以直接 通过var来声明变量,不需要初始化
var map2 sync.Map
var mu sync.Mutex
可以直接使用map2, mu变量,操作
9、普通变量,声明后,就有默认的初始化值 ,可以直接使用
10、引用类型,需要声明、分配内存 两步;如果只是声明,操作时会抛异常

go语言流程控制

go语言if else(分支机构)

if语句实例:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
 
   /* 判断条件 */
   if a == 100 {
       /* if 条件语句为 true 执行 */
       if b == 200 {
          /* if 条件语句为 true 执行 */
          fmt.Printf("a 的值为 100 , b 的值为 200\n" );
       }
   }
   fmt.Printf("a 值为 : %d\n", a );
   fmt.Printf("b 值为 : %d\n", b );
}

特殊写法:

if err := Connect(); err != nil {
    fmt.Println(err)
    return
}

go语言for循环

例:

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

无限循环:

sum := 0
for {
    sum++
    if sum > 100 {
        break
    }
}

for循环的三种模式:

for init; condition; post { }

for condition { }

for { }
  • init: 一般为赋值表达式,给控制变量赋初值;
  • condition: 关系表达式或逻辑表达式,循环控制条件;
  • post: 一般为赋值表达式,给控制变量增量或减量。

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

for key, value := range oldMap {
    newMap[key] = value
}

以上代码中的 key 和 value 是可以省略。

如果只想读取 key,格式如下:

for key := range oldMap

或者这样:

for key, _ := range oldMap

如果只想读取 value,格式如下:

for _, value := range oldMap

实例1:计算 1 到 10 的数字之和:

package main

import "fmt"

func main() {
   sum := 0
      for i := 0; i <= 10; i++ {
         sum += i
      }
   fmt.Println(sum)
}

实例2:

package main

import "fmt"

func main() {
   sum := 1
   for ; sum <= 10; {
      sum += sum
   }
   fmt.Println(sum)

   // 这样写也可以,更像 While 语句形式
   for sum <= 10{
      sum += sum
   }
   fmt.Println(sum)
}

实例3:

package main

import "fmt"

func main() {
   sum := 0
   for {
      sum++ // 无限循环下去
   }
   fmt.Println(sum) // 无法输出
}

For-each range 循环

这种格式的循环可以对字符串、数组、切片等进行迭代输出元素。

实例:

package main
import "fmt"

func main() {
   strings := []string{"google", "runoob"}
   for i, s := range strings {
      fmt.Println(i, s)
   }


   numbers := [6]int{1, 2, 3, 5}
   for i,x:= range numbers {
      fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
   }  
}

break跳出循环:

  • 用于循环语句中跳出循环,并开始执行循环之后的语句。
  • break 在 switch(开关语句)中在执行一条 case 后跳出语句的作用。
  • 在多重循环中,可以用标号 label 标出想 break 的循环。

实例:在变量 a 大于 15 的时候跳出循环:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* for 循环 */
   for a < 20 {
      fmt.Printf("a 的值为 : %d\n", a);
      a++;
      if a > 15 {
         /* 使用 break 语句跳出循环 */
         break;
      }
   }
}

实例2:多重循环的标记用法

package main

import "fmt"

func main() {

   // 不使用标记
   fmt.Println("---- break ----")
   for i := 1; i <= 3; i++ {
      fmt.Printf("i: %d\n", i)
      for i2 := 11; i2 <= 13; i2++ {
         fmt.Printf("i2: %d\n", i2)
         break
      }
   }

   // 使用标记
   fmt.Println("---- break label ----")
   re:
      for i := 1; i <= 3; i++ {
         fmt.Printf("i: %d\n", i)
         for i2 := 11; i2 <= 13; i2++ {
         fmt.Printf("i2: %d\n", i2)
         break re
      }
   }
}

执行结果如下:

---- break ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11
---- break label ----
i: 1
i2: 11	

continue跳出循环:

实例:在变量 a 等于 15 的时候跳过本次循环执行下一次循环:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* for 循环 */
   for a < 20 {
      if a == 15 {
         /* 跳过此次循环 */
         a = a + 1;
         continue;
      }
      fmt.Printf("a 的值为 : %d\n", a);
      a++;    
   }  
}

以下实例有多重循环,演示了使用标记和不使用标记的区别:

package main

import "fmt"

func main() {

    // 不使用标记
    fmt.Println("---- continue ---- ")
    for i := 1; i <= 3; i++ {
        fmt.Printf("i: %d\n", i)
            for i2 := 11; i2 <= 13; i2++ {
                fmt.Printf("i2: %d\n", i2)
                continue
            }
    }

    // 使用标记
    fmt.Println("---- continue label ----")
    re:
        for i := 1; i <= 3; i++ {
            fmt.Printf("i: %d\n", i)
                for i2 := 11; i2 <= 13; i2++ {
                    fmt.Printf("i2: %d\n", i2)
                    continue re
                }
        }
}

goto语句:

Go 语言的 goto 语句可以无条件地转移到过程中指定的行。

goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。

但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。

实例:在变量 a 等于 15 的时候跳过本次循环并回到循环的开始语句 LOOP 处:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* 循环 */
   LOOP: for a < 20 {
      if a == 15 {
         /* 跳过迭代 */
         a = a + 1
         goto LOOP
      }
      fmt.Printf("a的值为 : %d\n", a)
      a++    
   }  
}	

go语言switch语句

switch语句实例:

Go 编程语言中 switch 语句的语法如下:

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

实例:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )    
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}
  1. 一分支多值
var a = "mum"
switch a {
case "mum", "daddy":
    fmt.Println("family")
}
  1. 分支表达式

case 后不仅仅只是常量,还可以和 if 一样添加表达式,代码如下:

var r int = 11
switch {
case r > 10 && r < 20:
    fmt.Println(r)
}

Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

Type Switch 语法格式如下:

switch x.(type){
    case type:
       statement(s);      
    case type:
       statement(s); 
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s);
}

实例

package main

import "fmt"

func main() {
   var x interface{}
     
   switch i := x.(type) {
      case nil:  
         fmt.Printf(" x 的类型 :%T",i)                
      case int:  
         fmt.Printf("x 是 int 型")                      
      case float64:
         fmt.Printf("x 是 float64 型")          
      case func(int) float64:
         fmt.Printf("x 是 func(int) 型")                      
      case bool, string:
         fmt.Printf("x 是 bool 或 string 型" )      
      default:
         fmt.Printf("未知型")    
   }  
}

fallthrough实例:

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

package main

import "fmt"

func main() {

    switch {
    case false:
            fmt.Println("1、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("2、case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3、case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("4、case 条件语句为 true")
    case false:
            fmt.Println("5、case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6、默认 case")
    }
}
/*以上代码执行结果为:
2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true
*/

go语言函数

go语言函数声明

Go语言里面拥三种类型的函数:

  • 普通的带有名字的函数
  • 匿名函数或者 lambda 函数
  • 方法

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

可变函数:

  • 如果一个函数的参数是可变参数,同时还有其他的参数,可变参数要放在列表的最后。
  • 一个函数的参数列表中最多只能有一个可变参数。

实例:

package main

import "fmt"

func main() {
	getsums(1, 2, 3, 4, 5, 7)
}
//... 定义一个可变参数
func getsums(nums ...int) {
	sum := 0
	for i := 0; i < len(nums); i++ {
		sum += nums[i]
	}
	fmt.Println("和为:", sum)
}

参数传递:

值传递:

​ 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

实例:

package main

import "fmt"

func main() {
	//值传递
	/*
		arr2的数据是从arr1复制来的,所以是不同空间
		修改arr2并不会影响arr1
		值传递,传递的是数据的副本,修改数据,对于原始数据没有影响
		值类型的数据,默认都是值传递:基础类型,array,struct
	*/
    //定义一个数组 [值的个数]数据类型{值}
	arr := [4]int{1, 2, 3, 4}
	fmt.Println(arr)
	update(arr)
	fmt.Println("打印调用函数后的数据:", arr)
}

func update(arr2 [4]int) {
	fmt.Println("打印获取的数据", arr2)
	arr2[0] = 200
	fmt.Println("打印修改完的数据:", arr2)
}

引用传递:

​ 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。包括slice(切片)、map、chan等

实例:

package main

import "fmt"

func main() {
    //定义一个切片 与数组不同为不定义值的个数
	s1 := []int{1, 2, 3, 4}
	fmt.Println(s1)
	update2(s1)
	fmt.Println("打印调用函数后的值", s1)
}
func update2(s2 []int) {
	fmt.Println("打印传递进来的值:", s2)
	s2[0] = 300
	fmt.Println("修改后的值:", s2)
}

go语言将秒转化为时间

package main

import "fmt"

const (
	// 定义每分钟的秒数
	SecondsPerMinute = 60
	//  定义每小时的秒数
	SecondsPerHour = SecondsPerMinute * 60
	//  定义每天的秒数
	SecondsPerDay = SecondsPerHour * 24
)

// 将传入的“秒”解析为3种时间单位
func resolveTime(seconds int) (day int, hour int, minute int) {
	day = seconds / SecondsPerDay
	hour = seconds / SecondsPerHour
	minute = seconds / SecondsPerMinute
	return
}

func main() {
	//  将返回值作为打印参数
	fmt.Println(resolveTime(1000))
	//  只获取消息和分钟
	_, hour, minute := resolveTime(18000)
	fmt.Println(hour, minute)
	//  只获取天
	day, _, _ := resolveTime(90000)
	fmt.Println(day)
}

go语言匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成

匿名函数可以被赋值:

// 将匿名函数体保存到f()中
f := func(data int) {
    fmt.Println("hello", data)
}
// 使用f()调用
f(100)

匿名函数作为回调函数:

例1:

package main

import "fmt"

func main() {
	r1 := add(1, 2)
	fmt.Println(r1)

	r2 := oper(1, 3, add)
	fmt.Println(r2)

	r3 := oper(10, 2, sub)
	fmt.Println(r3)
}
//定义一个函数,可以接受一个函数作为参数
func oper(a, b int, fun func(int, int) int) int {
	return fun(a, b)
}

func add(a, b int) int {
	return a + b
}

func sub(a, b int) int {
	return a / b
}

例2:

package main
import (
    "fmt"
)
// 遍历切片的每个元素, 通过给定函数进行元素访问
func visit(list []int, f func(int)) {
    for _, v := range list {
        f(v)
    }
}
func main() {
    // 使用匿名函数打印切片内容
    visit([]int{1, 2, 3, 4}, func(v int) {
        fmt.Println(v)
    })
}

go语言闭包

Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:

函数 + 引用环境 = 闭包

在闭包内部修改引用的变量

闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改

// 准备一个字符串
str := "hello world"
// 创建一个匿名函数
foo := func() {
   
    // 匿名函数中访问str
    str = "hello dude"
}
// 调用匿名函数
foo()

例:

package main

import "fmt"

func add1() func(int) int {
    var x int     //x最开始的值为0,f(10)把值10传给返回函数的参数y
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	f := add1()
	fmt.Println(f(10))
	fmt.Println(f(20))
	fmt.Println(f(30))
}

示例:闭包的记忆效应

​ 被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。

package main

import "fmt"

func Accumulate(value int) func() int {
	return func() int {
		value++
		return value
	}
}
func main() {
	accumulator := Accumulate(1)
	fmt.Println(accumulator())
	fmt.Println(accumulator())
	fmt.Println(accumulator())
	fmt.Printf("%p\n", &accumulator)

	accumulator2 := Accumulate(20)
	fmt.Println(accumulator2())
	fmt.Println(accumulator2())
	fmt.Println(accumulator2())
	fmt.Println(accumulator())
	fmt.Printf("%p\n", &accumulator2)
}
//输出:
2
3           
4           
0xc0000ca018
21          
22          
23          
5           
0xc0000ca028

示例:闭包实现生成器

闭包的记忆效应被用于实现类似于设计模式中工厂模式的生成器,下面的例子展示了创建一个玩家生成器的过程。

package main
import (
    "fmt"
)
// 创建一个玩家生成器, 输入名称, 输出生成器
func playerGen(name string) func() (string, int) {
    // 血量一直为150
    hp := 150
    // 返回创建的闭包
    return func() (string, int) {
        // 将变量引用到闭包中
        return name, hp
    }
}
func main() {
    // 创建一个玩家生成器
    generator := playerGen("high noon")
    // 返回玩家的名字和血量
    name, hp := generator()
    // 打印值
    fmt.Println(name, hp)
}

闭包还具有一定的封装性,第 11 行的变量是 playerGen 的局部变量,playerGen 的外部无法直接访问及修改这个变量,这种特性也与面向对象中强调的封装性类似。

go语言可变参数

示例1:任意类型的可变参数

用 interface{} 传递任意类型数据是Go语言的惯例用法,使用 interface{} 仍然是类型安全的

package main
import "fmt"
func MyPrintf(args ...interface{}) {
    for _, arg := range args {
        switch arg.(type) {
            case int:
                fmt.Println(arg, "is an int value.")
            case string:
                fmt.Println(arg, "is a string value.")
            case int64:
                fmt.Println(arg, "is an int64 value.")
            default:
                fmt.Println(arg, "is an unknown type.")
        }
    }
}
func main() {
    var v1 int = 1
    var v2 int64 = 234
    var v3 string = "hello"
    var v4 float32 = 1.234
    MyPrintf(v1, v2, v3, v4)
}
//输出
1 is an int value.
234 is an int64 value.
hello is a string value.
1.234 is an unknown type.

示例2:遍历可变参数列表

可变参数列表的数量不固定,传入的参数是一个切片,如果需要获得每一个参数的具体值时,可以对可变参数变量进行遍历,

package main
import (
    "bytes"
    "fmt"
)
// 定义一个函数, 参数数量为0~n, 类型约束为字符串
func joinStrings(slist ...string) string {
    // 定义一个字节缓冲, 快速地连接字符串
    var b bytes.Buffer
    // 遍历可变参数列表slist, 类型为[]string
    for _, s := range slist {
        // 将遍历出的字符串连续写入字节数组
        b.WriteString(s)
    }
    // 将连接好的字节数组转换为字符串并输出
    return b.String()
}
func main() {
    // 输入3个字符串, 将它们连成一个字符串
    fmt.Println(joinStrings("pig ", "and", " rat"))
    fmt.Println(joinStrings("hammer", " mom", " and", " hawk"))
}

类型3:获得可变参数类型

当可变参数为 interface{} 类型时,可以传入任何类型的值,此时,如果需要获得变量的类型,可以通过 switch 获得变量的类型

package main
import (
    "bytes"
    "fmt"
)
func printTypeValue(slist ...interface{}) string {
    // 字节缓冲作为快速字符串连接
    var b bytes.Buffer
    // 遍历参数
    for _, s := range slist {
        // 将interface{}类型格式化为字符串
        str := fmt.Sprintf("%v", s)
        // 类型的字符串描述
        var typeString string
        // 对s进行类型断言
        switch s.(type) {
        case bool:    // 当s为布尔类型时
            typeString = "bool"
        case string:    // 当s为字符串类型时
            typeString = "string"
        case int:    // 当s为整型类型时
            typeString = "int"
        }
        // 写字符串前缀
        b.WriteString("value: ")
        // 写入值
        b.WriteString(str)
        // 写类型前缀
        b.WriteString(" type: ")
        // 写类型字符串
        b.WriteString(typeString)
        // 写入换行符
        b.WriteString("\n")
    }
    return b.String()
}
func main() {
    // 将不同类型的变量通过printTypeValue()打印出来
    fmt.Println(printTypeValue(100, "str", true))
}
//输出
value: 100 type: int
value: str type: string
value: true type: bool

示例4:在多个可变参数函数中传递参数

​ 可变参数变量是一个包含所有参数的切片,如果要将这个含有可变参数的变量传递给下一个可变参数函数,可以在传递时给可变参数变量后面添加...,这样就可以将切片中的元素进行传递,而不是传递可变参数变量本身。

package main
import "fmt"
// 实际打印的函数
func rawPrint(rawList ...interface{}) {
    // 遍历可变参数切片
    for _, a := range rawList {
        // 打印参数
        fmt.Println(a)
    }
}
// 打印函数封装
func print(slist ...interface{}) {
    // 将slist可变参数切片完整传递给下一个函数
    rawPrint(slist...)
}
func main() {
    print(1, 2, 3)
}
//输出
1
2
3

如果尝试将第 20 行修改为:

rawPrint("fmt", slist)

再次执行代码,将输出:

[1 2 3]

此时,slist(类型为 []interface{})将被作为一个整体传入 rawPrint(),rawPrint() 函数中遍历的变量也就是 slist 的切片值。

可变参数使用...进行传递与切片间使用 append 连接是同一个特性。

go语言defer

​ Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

多个延迟执行语句的处理顺序

当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出),下面的代码是将一系列的数值打印语句按顺序延迟处理,如下所示:

package main
import (
    "fmt"
)
func main() {
    fmt.Println("defer begin")
    // 将defer放入延迟调用栈
    defer fmt.Println(1)
    defer fmt.Println(2)
    // 最后一个放入, 位于栈顶, 最先调用
    defer fmt.Println(3)
    fmt.Println("defer end")
}

go语言递归函数

构成递归需要具备以下条件:

  • 一个问题可以被拆分成多个子问题;
  • 拆分前的原问题与拆分后的子问题除了数据规模不同,但处理问题的思路是一样的;
  • 不能无限制的调用本身,子问题需要有退出递归状态的条件。

注意:编写递归函数时,一定要有终止条件,否则就会无限调用下去,直到内存溢出。

例:斐波那契数列

package main
import "fmt"
func main() {
    result := 0
    for i := 1; i <= 10; i++ {
        result = fibonacci(i)
        fmt.Printf("fibonacci(%d) is: %d\n", i, result)
    }
}
func fibonacci(n int) (res int) {
    if n <= 2 {
        res = 1
    } else {
        res = fibonacci(n-1) + fibonacci(n-2)
    }
    return
}
//输出结果
fibonacci(1) is: 1
fibonacci(2) is: 1
fibonacci(3) is: 2
fibonacci(4) is: 3
fibonacci(5) is: 5
fibonacci(6) is: 8
fibonacci(7) is: 13
fibonacci(8) is: 21
fibonacci(9) is: 34
fibonacci(10) is: 55

例2:数字阶乘

n!=1×2×3×…×n`,阶乘亦可以递归方式定义:`0!=1,n!=(n-1)!×n
package main
import "fmt"
func Factorial(n uint64) (result uint64) {
    if n > 0 {
        result = n * Factorial(n-1)
        return result
    }
    return 1
}
func main() {
    var i int = 10
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

go语言结构体

go语言结构体定义

Go语言可以通过自定义的方式形成新的类型,结构体就是这些类型中的一种复合类型,结构体是由零个或多个任意类型的值聚合成的实体,每个值都可以称为结构体的成员。

结构体成员也可以称为“字段”,这些字段有以下特性:

  • 字段拥有自己的类型和值;
  • 字段名必须唯一;
  • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

结构体定义格式如下:

type 类型名 struct {
    字段1 字段1类型
    字段2 字段2类型
    …
}

对各个部分的说明:

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • struct{}:表示结构体类型,type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。
  • 字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
  • 字段1类型、字段2类型……:表示结构体各个字段的类型。

​ 使用结构体可以表示一个包含 X 和 Y 整型分量的点结构:

type Point struct {
    X int
    Y int
}

​ 同类型的变量也可以写在一行:

type Color struct {
    R, G, B byte
}

结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存.

go语言实例化结构体

实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的内存是完全独立的。

基本的实例化形式:

var ins T

其中,T 为结构体类型,ins 为结构体的实例。

用结构体表示的点结构(Point)的实例化过程

type Point struct {
    X int
    Y int
}
var p Point
p.X = 10
p.Y = 20	

在例子中,使用.来访问结构体的成员变量,如p.Xp.Y等,结构体成员变量的赋值方法与普通变量一致。

创建指针类型的结构体:

ins := new(T)

其中:

  • T 为类型,可以是结构体、整型、字符串等。
  • ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。

下面的例子定义了一个玩家(Player)的结构,玩家拥有名字、生命值和魔法值,实例化玩家(Player)结构体后,可对成员进行赋值,代码如下:

type Player struct{
    Name string
    HealthPoint int
    MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300

取结构体的地址实例化

在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式如下:

ins := &T{}

其中:

  • T 表示结构体类型。
  • ins 为结构体的实例,类型为 *T,是指针类型。

下面使用结构体定义一个命令行指令(Command),指令中包含名称、变量关联和注释等,对 Command 进行指针地址的实例化,并完成赋值过程

type Command struct {
    Name    string    // 指令名称
    Var     *int      // 指令绑定的变量
    Comment string    // 指令的注释
}
var version int = 1
cmd := &Command{}
cmd.Name = "version"
cmd.Var = &version
cmd.Comment = "show version"

取地址实例化是最广泛的一种结构体实例化方式,可以使用函数封装上面的初始化过程

func newCommand(name string, varref *int, comment string) *Command {
    return &Command{
        Name:    name,
        Var:     varref,
        Comment: comment,
    }
}
cmd = newCommand(
    "version",
    &version,
    "show version",
)

指向结构的指针

//指向结构的指针
package main 
  
import "fmt"
  
//定义一个结构
type Employee struct { 
    firstName, lastName string 
    age, salary         int
} 
  
func main() { 
  
    //获取指向结构体的指针
    emp8 := &Employee{"Sam", "Anderson", 55, 6000} 
  
        // emp8.firstName用于访问
        //字段firstName
    fmt.Println("First Name: ", emp8.firstName) 
    fmt.Println("Age: ", emp8.age) 
}

go语言嵌套结构体

示例

//嵌套结构体 
package main 
  
import "fmt"
  
//创建结构体
type Author struct { 
    name   string 
    branch string 
    year   int
} 
  
//创建嵌套结构体
type HR struct { 
  
    //字段结构
    details Author 
} 
  
func main() { 
  
    // 初始化结构体字段 
    result := HR{       
        details: Author{"Sona", "ECE", 2013}, 
    } 
  
    //打印输出值
    fmt.Println("\n作者的详细信息") 
    fmt.Println(result) 
}

示例

package main 
  
import "fmt"
  
//创建结构 
type Student struct { 
    name   string 
    branch string 
    year   int
} 
  
//创建嵌套结构
type Teacher struct { 
    name    string 
    subject string 
    exp     int
    details Student 
} 
  
func main() { 
  
    //初始化结构字段
    result := Teacher{ 
        name:    "Suman", 
        subject: "Java", 
        exp:     5, 
        details: Student{"Bongo", "CSE", 2}, 
    } 
   
    fmt.Println("老师详细情况") 
    fmt.Println("老师的名字: ", result.name) 
    fmt.Println("学科: ", result.subject) 
    fmt.Println("经历: ", result.exp) 
  
    fmt.Println("\n学生详细资料") 
    fmt.Println("学生的名字: ", result.details.name) 
    fmt.Println("学生的部门名称: ", result.details.branch) 
    fmt.Println("年龄: ", result.details.year) 
}

go匿名结构和匿名字段

例1

//匿名结构的概念
package main 
  
import "fmt"
  
func main() { 
  
    // 创建和初始化匿名结构
    Element := struct { 
        name      string 
        branch    string 
        language  string 
        Particles int
    }{ 
        name:      "詹三", 
        branch:    "开发部", 
        language:  "C++", 
        Particles: 498, 
    } 
  
    //显示匿名结构
    fmt.Println(Element) 
}

例2

package main 
  
import "fmt"
  
//创建一个结构匿名字段 
type student struct { 
    int
    string 
    float64 
} 
  
// Main function 
func main() { 
  
    // 将值分配给匿名,学生结构的字段
    value := student{123, "Bud", 8900.23} 
  
    fmt.Println("入学人数 : ", value.int) 
    fmt.Println("学生姓名 : ", value.string) 
    fmt.Println("套餐价格 : ", value.float64) 
}

go函数用做结构体字段

Golang中的结构或struct是用户定义的类型,它允许我们在一个单元中创建一组不同类型的元素。

示例

//作为Go结构中的字段
package main 
  
import "fmt"
  
// Finalsalary函数类型
type Finalsalary func(int, int) int
  
//创建结构
type Author struct { 
    name      string 
    language  string 
    Marticles int
    Pay       int
  
    //函数作为字段
    salary Finalsalary 
} 
  
func main() { 
  
    // 初始化字段结构
    result := Author{ 
        name:      "Sonia", 
        language:  "Java", 
        Marticles: 120, 
        Pay:       500, 
        salary: func(Ma int, pay int) int { 
            return Ma * pay 
        }, 
    } 
  
    fmt.Println("作者姓名: ", result.name) 
    fmt.Println("语言: ", result.language) 
    fmt.Println("五月份发表的文章总数: ", result.Marticles) 
    fmt.Println("每篇报酬: ", result.Pay) 
    fmt.Println("总工资: ", result.salary(result.Marticles, result.Pay)) 
}
//输出
作者姓名:  Sonia
语言:  Java
五月份发表的文章总数:  120
每篇报酬:  500
总工资:  60000

go语言方法

Go方法与Go函数相似,但有一点不同,就是方法中包含一个接收者参数。在接收者参数的帮助下,该方法可以访问接收者的属性。在这里,接收方可以是结构类型或非结构类型。在代码中创建方法时,接收者和接收者类型必须出现在同一个包中。而且不允许创建一个方法,其中的接收者类型已经在另一个包中定义,包括像int、string等内建类型。如果您尝试这样做,那么编译器将抛出错误。

结构类型接收器的方法

在Go语言中,允许您定义其接收者为结构类型的方法。可以在方法内部访问此接收器,如以下示例所示:

package main 
  
import "fmt"
  
//Author 结构体
type author struct { 
    name      string 
    branch    string 
    particles int
    salary    int
} 
  
//接收者的方法 
func (a author) show() { 
  
    fmt.Println("Author's Name: ", a.name) 
    fmt.Println("Branch Name: ", a.branch) 
    fmt.Println("Published articles: ", a.particles) 
    fmt.Println("Salary: ", a.salary) 
} 
  
func main() { 
  
    //初始化值
    //Author结构体
    res := author{ 
        name:      "Sona", 
        branch:    "CSE", 
        particles: 203, 
        salary:    34000, 
    } 
  
    //调用方法
    res.show() 
}
//输出
Author's Name:  Sona
Branch Name:  CSE
Published articles:  203
Salary:  34000

非结构类型接收器的方法

在Go语言中,只要类型和方法定义存在于同一包中,就可以使用非结构类型接收器创建方法。如果它们存在于int,string等不同的包中,则编译器将抛出错误,因为它们是在不同的包中定义的。

package main 
  
import "fmt"
  
//类型定义
type data int

//定义一个方法
//非结构类型的接收器 
func (d1 data) multiply(d2 data) data { 
    return d1 * d2 
} 
  
/* 
//如果您尝试运行此代码,

//然后编译器将抛出错误 
func(d1 int)multiply(d2 int)int{ 
return d1 * d2 
} 
*/
  
func main() { 
    value1 := data(23) 
    value2 := data(20) 
    res := value1.multiply(value2) 
    fmt.Println("最终结果: ", res) 
}
//最终结果:  460

带指针接收器的go方法

在Go语言中,允许您使用指针接收器创建方法。在指针接收器的帮助下,如果方法中所做的更改将反映在调用方中,这对于值接收器是不可能的。

package main 
  
import "fmt"
  
// Author 结构体
type author struct { 
    name      string 
    branch    string 
    particles int
} 
  
//方法,使用author类型的接收者
func (a *author) show(abranch string) { 
    (*a).branch = abranch 
} 
  
// Main function 
func main() { 
  
    //初始化author结构体
    res := author{ 
        name:   "Sona", 
        branch: "CSE", 
    } 
  
    fmt.Println("Author's name: ", res.name) 
    fmt.Println("Branch Name(Before): ", res.branch) 
  
    //创建一个指针
    p := &res 
  
    //调用show方法
    p.show("ECE") 
    fmt.Println("Author's name: ", res.name) 
    fmt.Println("Branch Name(After): ", res.branch) 
}
//输出
Author's name:  Sona
Branch Name(Before):  CSE
Author's name:  Sona
Branch Name(After):  ECE

方法可以接收指针和值

众所周知,在Go中,当一个函数具有值参数时,它将仅接受参数的值,如果您尝试将指针传递给值函数,则它将不接受,反之亦然。但是Go方法可以接受值和指针,无论它是使用指针还是值接收器定义的。如下例所示:

package main 
  
import "fmt"
  
// Author 结构体
type author struct { 
    name   string 
    branch string 
} 
  
//带有指针的方法
//author类型的接收者
func (a *author) show_1(abranch string) { 
    (*a).branch = abranch 
} 
  
//带有值的方法
//作者类型的接收者 
func (a author) show_2() { 
    a.name = "Gourav"
    fmt.Println("Author's name(Before) : ", a.name) 
} 
  

func main() { 
  
     //初始化值
     //作者结构体
    res := author{ 
        name:   "Sona", 
        branch: "CSE", 
    } 
  
    fmt.Println("Branch Name(Before): ", res.branch) 
  
     //调用show_1方法
     //(指针方法)带有值
    res.show_1("ECE") 
    fmt.Println("Branch Name(After): ", res.branch) 
  
     //调用show_2方法
     //带有指针的(值方法)
    (&res).show_2() 
    fmt.Println("Author's name(After): ", res.name) 
}
//输出
Branch Name(Before):  CSE
Branch Name(After):  ECE
Author's name(Before) :  Gourav
Author's name(After):  Sona

方法和函数之前的差异

方法 函数
它包含接收器。 它不包含接收器。
它可以接受指针和值。 它不能同时接受指针和值。
可以在程序中定义相同名称但不同类型的方法。 程序中不允许定义相同名称但不同类型的函数。

go语言接口

go语言接口声明

如何实现接口

在Go语言中,为了实现接口,必须实现接口中声明的所有方法。go语言接口是隐式实现的。与其他语言一样,它不包含实现接口的任何特定关键字

// Golang程序说明如何
//实现接口
package main

import "fmt"

//创建一个接口
type tank interface {

    // 方法
    Tarea() float64
    Volume() float64
}

type myvalue struct {
    radius float64
    height float64
}

//实现方法
//桶的(Tank)接口
func (m myvalue) Tarea() float64 {

    return 2*m.radius*m.height + 2*3.14*m.radius*m.radius
}

func (m myvalue) Volume() float64 {

    return 3.14 * m.radius * m.radius * m.height
}

func main() {

    // 访问使用桶的接口
    var t tank
    t = myvalue{10, 14}
    fmt.Println("桶的面积 :", t.Tarea())
    fmt.Println("桶的容量:", t.Volume())
}
//输出
桶的面积 : 908
桶的容量: 4396

类型断言

在Go语言中,类型断言是应用于接口值的操作。换句话说,类型断言是提取接口值的过程。

//类型断言 
package main 
  
import "fmt"
  
func myfun(a interface{}) { 
  
    //提取a的值
    val := a.(string) 
    fmt.Println("值为: ", val) 
} 
func main() { 
  
    var val interface { 
    } = "nhooo"
      
    myfun(val) 
}

在上面的示例中,如果将val:= a。(string)语句更改为val:= a。(int),则程序会抛出panic异常。因此,为了避免此问题,我们使用以下语法:

value, ok := a.(T)

在这里,如果a的类型等于T,则该值包含a的动态值,并且ok将设置为true。并且如果a的类型不等于T,则ok设置为false并且value包含零值,并且程序不会抛出panic异常。如下面的程序所示:

package main

import "fmt"

func myfun(a interface{}) {
    value, ok := a.(float64)
    fmt.Println(value, ok)
}
func main() {

    var a1 interface {
    } = 98.09

    myfun(a1)

    var a2 interface {
    } = "nhooo"

    myfun(a2)
}
//输出
98.09 true
0 false

类型判断

在Go接口中,类型判断用于将接口的具体类型与case语句中提供的多种类型进行比较。它与类型声明类似,只是有一个区别,即大小写指定类型,而不是值。您还可以将类型与接口类型进行比较。

package main

import "fmt"

func myfun(a interface{}) {

    //使用类型判断
    switch a.(type) {

    case int:
        fmt.Println("类型: int,值:", a.(int))
    case string:
        fmt.Println("\n类型: string,值: ", a.(string))
    case float64:
        fmt.Println("\n类型: float64,值: ", a.(float64))
    default:
        fmt.Println("\n类型未找到")
    }
}

go语言多个接口

在Go语言中,不允许在两个或多个接口中创建相同的名称方法。如果尝试这样做,则您的程序将崩溃。

//多个接口的概念
package main

import "fmt"

// 接口 1
type AuthorDetails interface {
    details()
}

// 接口 2
type AuthorArticles interface {
    articles()
}

// 结构体
type author struct {
    a_name    string
    branch    string
    college   string
    year      int
    salary    int
    particles int
    tarticles int
}

//实现接口方法1
func (a author) details() {

    fmt.Printf("作者: %s", a.a_name)
    fmt.Printf("\n部分: %s 通过日期: %d", a.branch, a.year)
    fmt.Printf("\n学校名称: %s", a.college)
    fmt.Printf("\n薪水: %d", a.salary)
    fmt.Printf("\n出版文章数: %d", a.particles)

}

// 实现接口方法 2
func (a author) articles() {

    pendingarticles := a.tarticles - a.particles
    fmt.Printf("\n待定文章: %d", pendingarticles)
}

// Main value
func main() {

    //结构体赋值
    values := author{
        a_name:    "Mickey",
        branch:    "Computer science",
        college:   "XYZ",
        year:      2012,
        salary:    50000,
        particles: 209,
        tarticles: 309,
    }

    // 访问使用接口1的方法
    var i1 AuthorDetails = values
    i1.details()

    //访问使用接口2的方法
    var i2 AuthorArticles = values
    i2.articles()

}

go语言接口嵌套

接口嵌套并同时拥有自己的方法的接口示例

package main

import "fmt"

// 接口 1
type AuthorDetails interface {
    details()
}

// 接口 2
type AuthorArticles interface {
    articles()
    picked()
}

// 接口 3
//接口3嵌套了接口1和接口2,同时加入了自己的方法
type FinalDetails interface {
    details()
    AuthorArticles
    cdeatils()
}

// author 结构体
type author struct {
    a_name    string
    branch    string
    college   string
    year      int
    salary    int
    particles int
    tarticles int
    cid       int
    post      string
    pick      int
}

// 实现接口1的方法
func (a author) details() {

    fmt.Printf("作者: %s", a.a_name)
    fmt.Printf("\n部门: %s 通过日期: %d", a.branch, a.year)
    fmt.Printf("\n大学名称: %s", a.college)
    fmt.Printf("\n薪水: %d", a.salary)
    fmt.Printf("\n发表文章数: %d", a.particles)
}

// 实现接口2的方法
func (a author) articles() {

    pendingarticles := a.tarticles - a.particles
    fmt.Printf("\n待定文章数: %d", pendingarticles)
}

func (a author) picked() {

    fmt.Printf("\n所选文章的总数: %d", a.pick)
}

// 实现嵌入了接口的方法
func (a author) cdeatils() {

    fmt.Printf("\n作者Id: %d", a.cid)
    fmt.Printf("\n提交: %s", a.post)
}

func main() {

    //结构体赋值
    values := author{

        a_name:    "Mickey",
        branch:    "Computer science",
        college:   "XYZ",
        year:      2012,
        salary:    50000,
        particles: 209,
        tarticles: 309,
        cid:       3087,
        post:      "Technical content writer",
        pick:      58,
    }

    // 使用 FinalDetails 接口访问接口1,2的方法
    var f FinalDetails = values
    f.details()
    f.articles()
    f.picked()
    f.cdeatils()
}

go语言接口实现ocp原则

ocp可扩展原则*

package main

import "fmt"

type Pet interface {
	eat()
	sleep()
}
type Dog struct {
	name string
}
type Cat struct {
	name string
}

func (dog Dog) eat() {
	fmt.Printf("dog is eat %v\n", dog.name)
}
func (dog Dog) sleep() {
	fmt.Printf("dog is sleep %v\n", dog.name)
}
func (cat Cat) eat() {
	fmt.Printf("cat is eat %v\n", cat.name)
}
func (cat Cat) sleep() {
	fmt.Printf("cat is sleep %v\n", cat.name)
}

type Person struct {
}
//pet既可以传递dog也可以传递cat
func (person Person) care(pet Pet) {
	pet.eat()
	pet.sleep()
}
func main() {
	dog := Dog{}
	cat := Cat{}
	person := Person{}
	person.care(cat)
	person.care(dog)
}

go语言模拟oop的属性和方法

go没有面向对象的概念,也没有封装的概念,但可以通过结构体和函数绑定来实现oop的属性和方法等特性

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func (person Person) eat() {
	fmt.Println("eat...")
}

func (person Person) sleep() {
	fmt.Println("sleep")
}
func (person Person) work() {
	fmt.Println("work")
}

func main() {
	person := Person{
		name: "wjm",
		age:  26,
	}

	person.eat()
	person.sleep()
	person.work()
}

go语言继承

go语言本质上没有oop的概念,也没有继承的概念,但是可以通过结构体嵌套实现

package main

import "fmt"

type Animal struct {
	name string
	age  int
}

func (animal Animal) eat() {
	fmt.Println("eat")
}
func (animal Animal) sleep() {
	fmt.Println("sleep")
}

type Dog struct {
	Animal
	color string
}
type Cat struct {
	Animal
	color string
}

func main() {
	dog := Dog{
		Animal: Animal{"didi", 2},
		color:  "white",
	}
	fmt.Println(dog.color)
	dog.eat()
	dog.sleep()
}

go并发

go并发(goroutines)

Go语言提供了称为Goroutines的特殊功能。Goroutine是一种函数或方法,可与程序中存在的任何其他Goroutine一起独立且同时执行。换句话说,每个Go语言中同时执行的活动称为Goroutines,您可以将Goroutine视为轻量级线程。与线程相比,创建Goroutines的成本非常小。每个程序至少包含一个Goroutine,并且该Goroutine被称为主Goroutine。如果主Goroutine终止,则所有Goroutine在主Goroutine之下运行,那么程序中存在的所有goroutine也将终止;Goroutine始终在后台运行。

如何创建Goroutines

package main 
  
import "fmt"
  
func display(str string) { 
    for w := 0; w < 6; w++ { 
        fmt.Println(str) 
    } 
} 
  
func main() { 
  
    // 调用Goroutine 
    go display("Welcome") 
  
    //正常调用函数
    display("nhooo.com") 
}
//输出
nhooo.com
nhooo.com
nhooo.com
nhooo.com
nhooo.com
nhooo.com

但是您可能发现问题了,它只显示调用普通函数的结果,而不显示Goroutine的结果,因为执行新的Goroutine时,Goroutine调用会立即返回。它不像普通函数那样等待Goroutine完成执行,它们总是在Goroutine调用后一直前进到下一行,并忽略Goroutine返回的值。

修改后的Goroutines:

package main 
  
import ( 
    "fmt"
    "time"
) 
  
func display(str string) { 
    for w := 0; w < 6; w++ { 
        time.Sleep(1 * time.Second) 
        fmt.Println(str) 
    } 
} 
  
func main() { 
  
    // 调用Goroutine 
    go display("Welcome") 
  
    //调用普通函数
    display("nhooo") 
}

Goroutines的优点:

  • Goroutine比线程开销小。
  • Goroutine存储在堆栈中,并且堆栈的大小可以根据程序的要求而增大和缩小。但是在线程中,堆栈的大小是固定的。
  • Goroutine可以使用通道进行通信,并且这些通道经过特殊设计,可以防止在使用Goroutines访问共享内存时出现争用情况。
  • 假设一个程序有一个线程,并且该线程有许多与之关联的Goroutine。如果由于资源需求,任何Goroutine阻塞了线程,则所有其余Goroutine将分配给新创建的OS线程。所有这些细节对程序员都是隐藏的。

匿名Goroutines:

在Go语言中,您还可以为匿名函数启动Goroutine,换句话说,您可以简单地通过使用go关键字作为该函数的前缀来创建匿名Goroutine

示例:

package main 
  
import ( 
    "fmt"
    "time"
) 
  
func main() { 
  
    fmt.Println("Welcome!! to Main function") 
  
    //创建匿名Goroutine
    go func() { 
  
        fmt.Println("Welcome!! to nhooo.com") 
    }() 
  
    time.Sleep(1 * time.Second) 
    fmt.Println("GoodBye!! to Main function") 
}

go语言基础之runtime包

runtime.Gosched()让出CPU时间片,重新等待安排任务

示例:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go func(s string) {
        for i := 0; i < 2; i++ {
            fmt.Println(s)
        }
    }("world")
    
    // 主协程
    for i := 0; i < 2; i++ {
        // 切一下,再次分配任务 先去干线程的活,后干自己的活
        runtime.Gosched()
        fmt.Println("hello")
    }
}
//输出
world
world
hello
hello

runtime.Goexit():退出当前协程

package main

import (
    "fmt"
    "runtime"
)

func main() {
    go func() {
        defer fmt.Println("A.defer")
        func() {
            defer fmt.Println("B.defer")
            // 结束协程 后面不在执行,直接退出
            runtime.Goexit()
            defer fmt.Println("C.defer")
            fmt.Println("B")
        }()
        fmt.Println("A")
    }()
    for {
    }
}
//输出
B.defer
A.defe

Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。

func a() {
    for i := 1; i < 10; i++ {
        fmt.Println("A:", i)
    }
}

func b() {
    for i := 1; i < 10; i++ {
        fmt.Println("B:", i)
    }
}

func main() {
    runtime.GOMAXPROCS(1)
    go a()
    go b()
    time.Sleep(time.Second)
}
//输出:先输出B,再输出A,两个任务只有一个逻辑核心,此时是做完一个任务再做另一个任务。

go语言多个goroutines

Goroutine是一种函数或方法,可与程序中存在的任何其他Goroutine一起独立且同时执行。换句话说,每个Go语言中同时执行的活动称为Goroutines。在Go语言中,允许您在一个程序中创建多个goroutine。您可以简单地通过使用go关键字作为函数或方法调用的前缀来创建goroutine

示例:

package main

import (
	"fmt"
	"time"
)

func Aname() {
	arr1 := [4]string{"wo", "ai", "wo", "jia"}
	for i := 0; i < 4; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Printf("%s\n", arr1[i])
	}
}
func Aid() {
	arr2 := [4]int{1, 2, 3, 4}
	for i := 0; i < 4; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Printf("%v\n", arr2[i])
	}
}
func main() {
	fmt.Println("begin")
	go Aname()
	go Aid()
	time.Sleep(500 * time.Millisecond)
	fmt.Println("end")
}

go语言通道(channl)

在Go语言中,通道是goroutine与另一个goroutine通信的媒介,并且这种通信是无锁的。换句话说,通道是一种技术,它允许一个goroutine将数据发送到另一个goroutine。默认情况下,通道是双向的,这意味着goroutine可以通过同一通道发送或接收数据,如下图所示:

创建通道的两种方式:

package main

import "fmt"

func main() {

    //使用var关键字创建通道
    var mychannel chan int
    fmt.Println("channel的值: ", mychannel)
    fmt.Printf("channel的类型: %T ", mychannel)

    // 使用 make() 函数创建通道
    mychannel1 := make(chan int)
    fmt.Println("\nchannel1的值:", mychannel1)
    fmt.Printf("channel1的类型: %T ", mychannel1)
}

在Go语言中,通道工作有两个主要的操作,一个是发送,另一个是接收,这两个操作统称为通信。<-运算符的方向表示是接收数据还是发送数据。在通道中,默认情况下,发送和接收操作块直到另一端没有数据为止。它允许goroutine在没有显式锁或条件变量的情况下彼此同步。

1.发送操作

Mychannel <- element

上面的语句表明数据(element)在<-运算符的帮助下发送到通道(Mychannel)。

2.接收操作

element := <-Mychannel

上面的语句表明该元素从channel(Mychannel)接收数据。如果接收到的语句的结果不可用(不需要使用)

<-Mychannel

示例:

package main 
  
import "fmt"
  
func myfunc(ch chan int) { 
  
    fmt.Println(234 + <-ch) 
} 
func main() { 
    fmt.Println("主方法开始") 
    //创建通道l 
    ch := make(chan int) 
  
    go myfunc(ch) 
    ch <- 23 
    fmt.Println("主方法结束") 
}
//输出
主方法开始
257
主方法结束

关闭通道

您也可以在close()函数的帮助下关闭通道。这是一个内置函数,并设置一个标识,表示不再有任何值将发送到该通道。

//Go程序说明如何
//关闭使用的通道
//range循环和关闭函数
package main

import "fmt"

func myfun(mychnl chan string) {

    for v := 0; v < 4; v++ {
        mychnl <- "nhooo"
    }
    close(mychnl)
}

func main() {

    //创建通道
    c := make(chan string)

    // 使用 Goroutine
    go myfun(c)

    //当ok的值为为true时,表示通道已打开,可以发送或接收数据
    //当ok的值设置为false时,表示通道已关闭
    for {
        res, ok := <-c
        if ok == false {
            fmt.Println("通道关闭 ", ok)
            break
        }
        fmt.Println("通道打开 ", res, ok)
    }
}
//输出
通道打开  nhooo true
通道打开  nhooo true
通道打开  nhooo true
通道打开  nhooo true
通道关闭  false

通道中的for循环

package main 
import "fmt"
  
func main() { 
  
    // 使用 make() 函数创建通道
    mychnl := make(chan string) 
  
    // 匿名 goroutine 
    go func() { 
        mychnl <- "GFG"
        mychnl <- "gfg"
        mychnl <- "Geeks"
        mychnl <- "nhooo"
        close(mychnl) 
    }() 
  
    //使用for循环
    for res := range mychnl { 
        fmt.Println(res) 
    } 
}

通道的容量

package main

import "fmt"

func main() {

    // 使用 make() 函数创建通道
    mychnl := make(chan string, 4)
    mychnl <- "GFG"
    mychnl <- "gfg"
    mychnl <- "Geeks"
    mychnl <- "nhooo"

    // 使用  cap() 函数查找通道的容量
    //len()表示长度
    fmt.Println("channel容量为: ", cap(mychnl))
}

go语言的单向通道

//仅接收数据
c1:= make(<- chan bool)

//仅用于发送数据
c2:= make(chan<-bool)

双向通道转换为单向通道

package main 
  
import "fmt"
  
func sending(s chan<- string) { 
    s <- "nhooo"
} 
  
func main() { 
  
    //创建双向通道
    mychanl := make(chan string) 
  
        //在这里,sending()函数将双向通道转换为仅发送通道 
    go sending(mychanl) 
  
    //在这里,通道只在goroutine内部发送,而在goroutine之外,通道是双向的,所以它打印nhooo 
    fmt.Println(<-mychanl) 
}

go语言异常(Error)

go语言Error(错误处理)

Go没有像Java中的try / catch这样的异常机制,我们不能在Go中抛出异常。

Go使用另一种机制,称为延迟恐慌和恢复机制。

Go通过返回一个错误对象来处理函数和方法的简单错误。错误对象可能是唯一或最后一个返回值。如果函数中没有错误,则错误对象为nil。

Go语言检测和报告错误情况的方法是

  • 可能导致错误的函数将返回两个变量:一个值和一个错误代码,如果成功,则为nil;如果错误条件,则为== nil。
  • 在函数调用之后检查错误。如果发生错误( if error != nil),则停止执行实际功能(或必要时整个程序)。

实例:

package main
import "errors"
import "fmt"
import "math"
func Sqrt(value float64) (float64, error) {
   if (value < 0) {
      return 0, errors.New("Math: 负数的平方根")
   }
   return math.Sqrt(value), nil
}
func main() {
   result, err := Sqrt(-64)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println(result)
   }
   result, err = Sqrt(64)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println(result)
   }
}
//输出
Math: 负数的平方根
8

go语言Recover(恢复)

恢复用于从紧急情况或错误情况中重新获得对程序的控制。它停止终止序列并恢复正常执行。从延迟函数中调用。它检索通过panic调用传递的错误值。通常,它返回nil,没有其他效果。

实例:

package main
import (
   "fmt"
)
func main() {
   fmt.Println(SaveDivide(10, 0))
   fmt.Println(SaveDivide(10, 10))
}
func SaveDivide(num1, num2 int) int {
   //如果不加此语句会直接程序错误退出
    defer func() {
      fmt.Println(recover())
   }()
   quotient := num1 / num2
   return quotient
}
//输出
runtime error: integer divide by zero
0
<nil>
1

go语言Panic

Panic是一种我们用来处理错误情况的机制。紧急情况可用于中止函数执行。当一个函数调用panic时,它的执行停止,并且控制流程到相关的延迟函数。

这个函数的调用者也会被终止,调用者的延迟函数也会被执行(如果有的话)。这个过程一直持续到程序结束。现在报告错误情况。

这种终止序列称为panic,可以由内置函数recover控制。

示例1:

package main

import "os"

func main() {
	panic("Error Situation")
	_, err := os.Open("/tmp/file")
	if err != nil {
		panic(err)
	}
}
//输出
panic: Error Situation

goroutine 1 [running]:
main.main()
/Users/pro/GoglandProjects/Panic/panic example1.go:6 +0x39

示例2:

package main

import "fmt"
func main() {
	fmt.Println("Calling x from main.")
	x()
	fmt.Println("Returned from x.")
}
func x() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in x", r)
		}
	}()
	fmt.Println("Executing x...")
	fmt.Println("Calling y.")
	y(0)
	fmt.Println("Returned normally from y.")
}

func y(i int) {
	fmt.Println("Executing y....")
	if i > 2 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v" , i))
	}
	defer fmt.Println("Defer in y", i)
	fmt.Println("Printing in y", i)
	y(i + 1)
}
//输出
Calling x from main.
Executing x...
Calling y.
Executing y....
Printing in y 0
Executing y....
Printing in y 1
Executing y....
Printing in y 2
Executing y....
Panicking!
Defer in y 2
Defer in y 1
Defer in y 0
Recovered in x 3
Returned from x.

go语言基础包用法

time标准库

一、时间类型

time.Time类型表示时间。我们可以通过time.Now()函数获取当前的时间对象,然后获取时间对象的年月日时分秒等信息。示例代码如下:

func timeDemo() {
	now := time.Now() //获取当前时间
	fmt.Printf("current time:%v\n", now)

	year := now.Year()     //年
	month := now.Month()   //月
	day := now.Day()       //日
	hour := now.Hour()     //小时
	minute := now.Minute() //分钟
	second := now.Second() //秒
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

二、时间戳

基于时间对象获取时间戳的示例代码如下:

func timestampDemo() {
	now := time.Now()            //获取当前时间
	timestamp1 := now.Unix()     //时间戳
	timestamp2 := now.UnixNano() //纳秒时间戳
	fmt.Printf("current timestamp1:%v\n", timestamp1)
	fmt.Printf("current timestamp2:%v\n", timestamp2)
}

使用time.Unix()函数可以将时间戳转为时间格式。

func timestampDemo2(timestamp int64) {
	timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
	fmt.Println(timeObj)
	year := timeObj.Year()     //年
	month := timeObj.Month()   //月
	day := timeObj.Day()       //日
	hour := timeObj.Hour()     //小时
	minute := timeObj.Minute() //分钟
	second := timeObj.Second() //秒
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

三、时间间隔

time.Durationtime包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。time.Duration表示一段时间间隔,可表示的最长时间段大约290年。

time包中定义的时间间隔类型的常量如下:

const (
    Nanosecond  Duration = 1
    Microsecond          = 1000 * Nanosecond
    Millisecond          = 1000 * Microsecond
    Second               = 1000 * Millisecond
    Minute               = 60 * Second
    Hour                 = 60 * Minute
)

例如:time.Duration表示1纳秒,time.Second表示1秒。

四、时间操作

4.1 add

我们在日常的编码过程中可能会遇到要求时间+时间间隔的需求,Go语言的时间对象有提供Add方法如下:

//求一小时后的时间
func main() {
	now := time.Now()
	later := now.Add(time.Hour) // 当前时间加1小时后的时间
	fmt.Println(later)
}

4.2 sub

求两个时间之间的差值:

例子:

func main() {	
    now := time.Now()
    later := now.Add(time.Hour) // 当前时间加1小时后的时间
    fmt.Println(later)
    subTime := later.Sub(now)
    fmt.Println(subTime)
}

4.3 equal

判断两个时间是否相同

例:

func main() {
	now := time.Now()
	later := now.Add(time.Hour) // 当前时间加1小时后的时间
	fmt.Println(now.Equal(later))
}
//false

4.4 before

表示是否在某时间点之前,如果在返回真;否则返回假。

func main() {
	now := time.Now()
	later := now.Add(time.Hour) // 当前时间加1小时后的时间
	fmt.Println(now.Before(later))
}

4.5 after

表示是否在某时间点之后,如果在返回真;否则返回假。

func main() {
	now := time.Now()
	later := now.Add(time.Hour) // 当前时间加1小时后的时间
	fmt.Println(now.After(later))
}

五、定时器

使用time.Tick(时间间隔)来设置定时器,定时器的本质上是一个通道(channel)。

func tickDemo() {
	ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
	for i := range ticker {
		fmt.Println(i)//每秒都会执行的任务
	}
}

六、时间格式化

时间类型有一个自带的方法Format进行格式化,需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S而是使用Go的诞生时间2006年1月2号15点04分(记忆口诀为2006 1 2 3 4)。也许这就是技术人员的浪漫吧。

补充:如果想格式化为12小时方式,需指定PM

func formatDemo() {
	now := time.Now()
	// 格式化的模板为Go的出生时间2006年1月2号15点04分 Mon Jan
	// 24小时制
	fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
	// 12小时制
	fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
	fmt.Println(now.Format("2006/01/02 15:04"))
	fmt.Println(now.Format("15:04 2006/01/02"))
	fmt.Println(now.Format("2006/01/02"))
}

七、解析字符串格式的时间

now := time.Now()
fmt.Println(now)
// 加载时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
	fmt.Println(err)
	return
}
// 按照指定时区和指定格式解析字符串时间
timeObj, err := time.ParseInLocation("2006/01/02 15:04:05", "2022/08/04 14:15:20", loc)
if err != nil {
	fmt.Println(err)
	return
}
fmt.Println(timeObj)
fmt.Println(timeObj.Sub(now))

math标准库

go语言math包中的常用方法有:1、取绝对值方法Abs();2、幂次方方法Pow();3、开平方方法Sqrt();4、开立方方法Cbrt();5、向上取整方法Ceil();6、向下取整方法Floor()

实例:

package main

import (
	"fmt"
	"math"
)

func main() {
	/*
	   取绝对值,函数签名如下:
	       func Abs(x float64) float64
	*/
	fmt.Printf("[-3.14]的绝对值为:[%.2f]\n", math.Abs(-3.14))

	/*
	   取x的y次方,函数签名如下:
	       func Pow(x, y float64) float64
	*/
	fmt.Printf("[2]的16次方为:[%.f]\n", math.Pow(2, 16))

	/*
	   取余数,函数签名如下:
	       func Pow10(n int) float64
	*/
	fmt.Printf("10的[3]次方为:[%.f]\n", math.Pow10(3))

	/*
	   取x的开平方,函数签名如下:
	       func Sqrt(x float64) float64
	*/
	fmt.Printf("[64]的开平方为:[%.f]\n", math.Sqrt(64))

	/*
	   取x的开立方,函数签名如下:
	       func Cbrt(x float64) float64
	*/
	fmt.Printf("[27]的开立方为:[%.f]\n", math.Cbrt(27))

	/*
	   向上取整,函数签名如下:
	       func Ceil(x float64) float64
	*/
	fmt.Printf("[3.14]向上取整为:[%.f]\n", math.Ceil(3.14))

	/*
	   向下取整,函数签名如下:
	       func Floor(x float64) float64
	*/
	fmt.Printf("[8.75]向下取整为:[%.f]\n", math.Floor(8.75))

	/*
	   取余数,函数签名如下:
	       func Floor(x float64) float64
	*/
	fmt.Printf("[10/3]的余数为:[%.f]\n", math.Mod(10, 3))

	/*
	   分别取整数和小数部分,函数签名如下:
	       func Modf(f float64) (int float64, frac float64)
	*/
	Integer, Decimal := math.Modf(3.14159265358979)
	fmt.Printf("[3.14159265358979]的整数部分为:[%.f],小数部分为:[%.14f]\n", Integer, Decimal)

}

输出结果:

[-3.14]的绝对值为:[3.14]
[2]的16次方为:[65536]
10的[3]次方为:[1000]                                            
[64]的开平方为:[8]                                              
[27]的开立方为:[3]                                              
[3.14]向上取整为:[4]                                            
[8.75]向下取整为:[8]                                            
[10/3]的余数为:[1]                                              
[3.14159265358979]的整数部分为:[3],小数部分为:[0.14159265358979]

os标准库

os包提供了操作系统的系列函数,这些接口不依赖平台。设计为Unix风格的,错误处理是go风格的;调用失败会返回错误值而非错误码。通常错误值里包含更多信息。

os包的接口在所有操作系统中都是一致的。非公用的属性可以从操作系统特定的syscall包获取。

一、操作系统基本命令

相关方法:

func Getwd() (dir string, err error) // 获取当前工作目录的根路径
func Chdir(dir string) error // 将工作目录修改为dir
func Chmod(name string, mode FileMode) error // 修改name文件或文件夹的权限(对应linux的chmod命令)
func Chown(name string, uid, gid int) error // 修改name文件或文件夹的用户和组(对应linux的chmod命令)
func Mkdir(name string, perm FileMode) error // 使用指定的权限和名称创建一个文件夹(对于linux的mkdir命令)
func MkdirAll(path string, perm FileMode) error // 使用指定的权限和名称创建一个文件夹,并自动创建父级目录(对于linux的mkdir -p目录)
func Rename(oldpath, newpath string) error // 修改一个文件或文件夹的文字(对应linux的mv命令)
func Remove(name string) error // 删除指定的文件夹或者目录  ,不能递归删除,只能删除一个空文件夹或一个文件(对应linux的 rm命令)
 
func RemoveAll(path string) error // 递归删除文件夹或者文件(对应linux的rm -rf命令)

实例:

func main() {
    // 为了减少代码的篇幅,基本所有的错误在这篇文字里面我都丢弃
    wd, _ := os.Getwd()
    println("获取当前工作目录的根路径:", wd)
 
    _ = os.Chdir(path.Join(wd, "go_os/demo1"))
    w, _ := os.Getwd()
    println("获取x修改后的当前工作目录的根路径:", w)
 
    _ = os.MkdirAll("dirs/dir1", 0777)
    _ = os.Mkdir("dirs/dir2", 0777)
    _ = os.Rename("dirs/dir1", "dirs/dir3")
    _ = os.Remove("dirs/dir2")
    _ = os.RemoveAll("dirs")
 
}

二、创建、写入、打开、读取文件

1.相关方法:

func Create(name string) (file *File, err error) // 创建一个空文件,注意当文件已经存在时,会直接覆盖掉原文件,不会报错
func Open(name string) (file *File, err error) // 打开一个文件,注意打开的文件只能读,不能写
func OpenFile(name string, flag int, perm FileMode) (file *File, err error) // 以指定的权限打开文件

2.创建和写入文件

func main() {
    wd, _ := os.Getwd()
    file, _ := os.Create(wd + "/go_os/demo1/1.txt")
    defer file.Close()
    println(file.Name())
    file_info,_ := file.Stat()
    fmt.Println(file_info)
    _,_ = file.Write([]byte("hello world!\n"))
    _,_ = file.WriteString("张亚飞")
}

3.追加文件

func main()  {
    wd, _ := os.Getwd()
    f, err := os.OpenFile(wd + "/go_os/demo1/1.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    defer f.Close()
    if err != nil {
        // 打开文件失败处理
        fmt.Println(err)
        return
 
    }
    content := "\n写入的文件内容"
    _,_ = f.Write([]byte(content))
}

4.读取文件

func main(){
    wd, _ := os.Getwd()
    file, _ := os.Open(wd + "/go_os/demo1/1.txt")
    defer file.Close() // 不要忘记关闭文件
    b := make([]byte, 4) // 文件内容不多,我们一次性读4个字节,多读几次,不一次性读完
    var str string
    for {
        n, err := file.Read(b)
        if err != nil {
            if err == io.EOF { // EOF表示文件读取完毕
                break // 退出
            }
        }
        str += string(b[:n]) // 保存文件内容
    }
    println(str) // 打印文件
}

5.查看文件信息

func main()  {
    wd, _ := os.Getwd()
 
    file, _ := os.Open(wd + "/go_os/demo1/1.txt") // 以只读的方式打开文件
    defer file.Close() // 不要忘记关闭文件
    // 获取文件的信息
    fInfo, _ := file.Stat()
    println("是否是一个目录:", fInfo.IsDir())
    println("文件的修改时间:", fInfo.ModTime().String())
    println("文件的名字:", fInfo.Name())
    println("文件的大小:", fInfo.Size())
    println("文件的权限:", fInfo.Mode().String())
    /*
    是否是一个目录: false
    文件的修改时间: 2020-06-17 09:52:05.7987495 +0800 CST
    文件的名字: 1.txt
    文件的大小: 24
    文件的权限: -rw-rw-rw-
    */
}

三、获取操作系统信息

1.相关方法

func Hostname() (name string, err error) // 获取主机名
func Getenv(key string) string // 获取某个环境变量
func Setenv(key, value string) error // 设置一个环境变量,失败返回错误,经测试当前设置的环境变量只在 当前进程有效(当前进程衍生的所以的go程都可以拿到,子go程与父go程的环境变量可以互相获取);进程退出消失
func Clearenv() // 删除当前程序已有的所有环境变量。不会影响当前电脑系统的环境变量,这些环境变量都是对当前go程序而言的
func Exit(code int) // 让当前程序以给出的状态码(code)退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
func Getuid() int // 获取调用者的用户id
func Geteuid() int // 获取调用者的有效用户id
func Getgid() int // 获取调用者的组id
func Getegid() int // 获取调用者的有效组id
func Getgroups() ([]int, error) // 获取调用者所在的所有组的组id
func Getpid() int // 获取调用者所在进程的进程id
func Getppid() int // 获取调用者所在进程的父进程的进程id

示例:

func main()  {
    hostname, _ := os.Hostname()
    println("获取主机名,", hostname)
 
    println("获取gopath环境变量:", os.Getenv("GOPATH"))
 
    _ = os.Setenv("test", "test") // 设置环境变量
    println("获取上一步设置的test环境变量:", os.Getenv("test"))
 
    os.Clearenv() // 清除当前程序的所以环境变量
    println("获取清理后的环境变量test和GOPATH:", os.Getenv("test"), os.Getenv("GOPATH"))
 
    println("获取调用者的用户id", os.Getuid())
    println("获取调用者的有效用户id", os.Geteuid())
    println("获取调用者的组id", os.Getgid())
    println("获取调用者的有效组id", os.Getegid())
 
    sli, _ := os.Getgroups()
    println("获取调用者所在的所有组的组id", sli) //
 
    println("获取调用者所在进程的进程id", os.Getpid())
    println("获取调用者所在进程的父进程的进程id", os.Getppid())
    /*
    获取主机名, home-fei
    获取gopath环境变量: E:\go\project
    获取上一步设置的test环境变量: test
    获取清理后的环境变量test和GOPATH:
    获取调用者的用户id -1
    获取调用者的有效用户id -1
    获取调用者的组id -1
    获取调用者的有效组id -1
    获取调用者所在的所有组的组id [0/0]0x0
    获取调用者所在进程的进程id 4968
    获取调用者所在进程的父进程的进程id 11588
     */
}

四、其他

1、相关方法

Exit() // 退出系统进程
func IsPathSeparator(c uint8) bool // 判断字c是否是一个路径分隔符
func IsExist(err error) bool // 判断一个错误是否表示一个文件或文件夹是否已存在,ErrExist和一些系统调用错误会使它返回真。
func IsNotExist(err error) bool // 判断一个错误是否表示一个文件或文件夹是否不存在,ErrNotExist和一些系统调用错误会使它返回真。
func IsPermission(err error) bool // 判断一个错误是否表示权限不足,ErrPermission和一些系统调用错误会使它返回真。

示例

func exit()  {
    // 模拟条件
    if 1 != 2 {
        println("程序启动失败,xxx条件不满足!")
        os.Exit(1)
    }
    println("程序启动成功!")
}
// 程序启动失败,xxx条件不满足!<br>
func os_path()  {
    print("判断 / \\ : 是否是路径分隔符: ")
    println(os.IsPathSeparator('/'), os.IsPathSeparator('\\'), os.IsPathSeparator(':'))
}<br>// 判断 / \ : 是否是路径分隔符: true true false

ioutil标准库

一、相关方法

func ReadAll(r io.Reader) ([]byte, error)
func ReadDir(dirname string) ([]os.FileInfo, error)
func ReadFile(filename string) ([]byte, error)
func WriteFile(filename string, data []byte, perm os.FileMode) error
func TempDir(dir, prefix string) (name string, err error)
func TempFile(dir, pattern string) (f *os.File, err error)

示例:

package main
 
import (
    "fmt"
    "io/ioutil"
    "strings"
)
 
func main() {
    //NopCloser返回一个读取对象的ReadCloser接口
    //用于提供Close方法
    r := strings.NewReader("hello");
    rcl := ioutil.NopCloser(r);
    defer rcl.Close();
 
    //ReadAll读取所有数据
    r2 := strings.NewReader("1234567890");
    p, _ := ioutil.ReadAll(r2);
    fmt.Println(string(p));  // 1234567890
 
    //读取目录下信息
    fileInfo, _ := ioutil.ReadDir("./");
    for _, v := range fileInfo {
        fmt.Println(v.Name());
    }
 
    //读取整个文件数据
    data, _ := ioutil.ReadFile("E:/go/project/test.log");
    fmt.Println(string(data));
 
    //向指定文件写入数据,如果文件不存在,则创建文件,写入数据之前清空文件
    ioutil.WriteFile("./xxx.txt", []byte("hello,world"), 0655);
 
    //在当前目录下,创建一个以test为前缀的临时文件夹,并返回文件夹路径
    name, _ := ioutil.TempDir("./", "test");
    fmt.Println(name);
 
    //在当前目录下,创建一个以test为前缀的文件,并以读写模式打开文件,并返回os.File指针
    file, _ := ioutil.TempFile("./", "test");
    file.WriteString("写入字符串");
    file.Close();
}

path标准库

path

实现了对斜杠分隔的路径进行操作的函数。

func IsAbs(path string) bool // 判断是否是一个绝对路径
func Split(path string) (dir, file string) // 将路径分割为路径和文件名
func Join(elem ...string) string // 将多个字符串合并为一个路径
func Ext(path string) string // 返回路径中扩展部分
func Base(path string) string // 返回路径的最后一个元素
func Dir(path string) string // 返回路径中目录部分
func Clean(path string) string // 返回同目录的最短路径
func Match(pattern, name string) (matched bool, err error) // 正则是否匹配路径(shell 文件名匹配)

示例:

func main() {
    pt := "E:/go"
 
    // 判断是否是一个绝对路径
    is_abs := path.IsAbs(pt)
    fmt.Println(is_abs) // false linux中以/开头为根路径
 
    // 将路径分割为路径和文件名
    pf := "E:/go/project/src/go_dev/go_path/main.go"
    dir, file := path.Split(pf)
    fmt.Println(dir, file) // E:/go/project/src/go_dev/go_path/ main.go
 
    // 将多个字符串合并为一个路径
    dir_join := path.Join("E:/go", "project", "src")
    fmt.Println(dir_join) // E:/go/project/src
 
    // 返回路径中扩展
    file_ext := path.Ext(pf)
    fmt.Println(file_ext) // .go
 
    // 返回路径的最后一个元素
    dir_base := path.Base(pf)
    fmt.Println(dir_base) // main.go
 
    // 返回路径中目录部分
    dir = path.Dir(pf)
    fmt.Println(dir) // E:/go/project/src/go_dev/go_path
 
    // 返回同目录的最短路径
    dir_a := "/usr/../etc/../etc/ssh"
    fmt.Println(path.Clean(dir_a)) // /etc/ssh
 
    // 正则是否匹配路径
    is_match, err := path.Match("*.xml", "a.xml")
    fmt.Println(is_match, err) // true <nil>
}

filepath

filepath 包实现了兼容各操作系统的文件路径操作函数。

filepath.Separator // 预定义变量,表示路径分隔符 /
filepath.ListSeparator // 预定义变量,表示环境变量分隔符 :
func Abs(path string) (string, error) // 返回path 相对当前路径的绝对路径
func Clean(path string) string // 返回path 的最短路径
func Rel(basepath, targpath string) (string, error) // 返回targpath 相对 basepath路径
func EvalSymlinks(path string) (string, error) // 返回软链指向的路径
func VolumeName(path string) string // 返回路径最前面的卷名
func ToSlash(path string) string // 路径分隔符替换为 /
func FromSlash(path string) string // / 替换为路径分隔符
func SplitList(path string) []string // 分隔环境变量里面的路径
func Walk(root string, walkFn WalkFunc) error // 遍历 root 目录下的文件树,并调用 walkF

示例:

func main() {
    // 预定义变量
    fmt.Println(string(filepath.Separator), string(filepath.ListSeparator)) // \ ;
 
    // 返回path 相对当前路径的绝对路径
    dir := "~/gocode/src/go_note/package/filepath"
    real_dir, err := filepath.Abs(dir)
    fmt.Println(real_dir, err)
    // E:\go\project\src\go_dev\~\gocode\src\go_note\package\filepath <nil>
 
    // 返回path 的最短路径
    dir = "/usr/../etc/../tmp"
    clear_dir := filepath.Clean(dir)
    fmt.Println(clear_dir) // \tmp
 
    // 返回targpath 相对 basepath路径
    basepath, targpath := "/usr/local", "/usr/local/go/bin"
    rel_dir, err := filepath.Rel(basepath, targpath)
    fmt.Println(rel_dir, err) // go/bin <nil>
 
    // 返回软链指向的路径
    symlink := "/usr/local"
    real_dir, err = filepath.EvalSymlinks(symlink)
    fmt.Println(real_dir, err) // /usr/local <nil>
 
    // 返回路径最前面的卷名
    root := "/usr/local/go"
    vol := filepath.VolumeName(root)
    fmt.Println(vol) // ''
 
    // 路径分隔符替换为 `/`
    slash_dir := filepath.ToSlash(root)
    fmt.Println(slash_dir) // /usr/local/go
 
    //  `/` 替换为路径分隔符
    from_slash := filepath.FromSlash(slash_dir)
    fmt.Println(from_slash) // \usr\local\go
 
    // 分隔环境变量里面的路径
    env_path := "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/go/bin"
    env_slice := filepath.SplitList(env_path)
    for k, v := range env_slice {
        fmt.Println(k, v)
    }
    // 0 /usr/local/bin
    // 1 /usr/bin
    // 2 /bin
    // 3 /usr/sbin
    // 4 /sbin
    // 5 /opt/X11/bin
    // 6 /usr/local/go/bin
 
    // 遍历 root 目录下的文件树,并调用 walkFn
    root_dir, err := os.Getwd()
    err = filepath.Walk(root_dir, pathName)
    fmt.Println(err)
}

strconv标准库

Go语言中strconv包实现了基本数据类型和其字符串表示的相互转换。strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi()Itia()、parse系列、format系列、append系列。

atoi()

将字符串类型转换为int类型:

s1 := "100"
i1, err := strconv.Atoi(s1)
if err != nil {
    fmt.Println("can't convert to int")
} else {
    fmt.Printf("type:%T value:%#v\n", i1, i1) //type:int value:100
}

itoa()

将int类型转换为字符串类型:

i2 := 200
s2 := strconv.Itoa(i2)
fmt.Printf("type:%T value:%#v\n", s2, s2) //type:string value:"200"

bufio标准库

NewReader

示例:

func test01() {
	r1 := strings.NewReader("hello world!")
	r2 := bufio.NewReader(r1)
	r3, _ := r2.ReadString('\n')//以\n结尾
	fmt.Printf("r3:%v\n", r3)
}
func test02() {
	f1, _ := os.Open("a.txt")
	defer f1.Close()
	r1 := bufio.NewReader(f1)
	r2, _ := r1.ReadString('\t') //以\t结尾
	fmt.Printf("%v\n", r2)
}

Read

读取数据写入p。本方法返回写入p的字节数。本方法一次调用最多会调用下层Reader接口一次Read方法,因此返回值n可能小于len。读取到达结尾时,返回值n将为0而err将为io.EOF。

package main
 
import (
	"bufio"
	"fmt"
	"io"
	"strings"
)
 
func main() {
	s := strings.NewReader("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
	br := bufio.NewReader(s)
	p := make([]byte, 10)
 
	for {
		n, err := br.Read(p)
		if err == io.EOF {
			break
		} else {
			fmt.Printf("string(p[0:n]): %v\n", string(p[0:n]))
		}
	}
}

WriteTo

示例:

func test04() {
	reader1 := strings.NewReader("qeqdadasdasdasdadsad")
	reader2 := bufio.NewReader(reader1)
	buffer1 := bytes.NewBuffer(make([]byte, 0))
	reader2.WriteTo(buffer1)
	fmt.Printf("%v\n", buffer1)
}

NewWriter

示例:

func test05() {
   file, _ := os.OpenFile("a.txt", os.O_RDWR, 777)
   defer file.Close()
   writer := bufio.NewWriter(file)
   writer.WriteString("hello java")
   writer.Flush()//写入后需要刷新
}

Scanner

示例:

func test06() {
	s := strings.NewReader("aaa bbb ccc")
	scanner := bufio.NewScanner(s)
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {//扫描下一个有没有,有的话输出文本
		fmt.Println(scanner.Text())
	}
}

fmt标准库

fmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分。

一、向外输出

print

Print系列函数会将内容输出到系统的标准输出,区别在于Print函数直接输出内容,Printf函数支持格式化输出字符串,Println函数会在输出内容的结尾添加一个换行符。

示例:

func main() {
    fmt.Print("在终端打印该信息。")
    name := "沙河小王子"
    fmt.Printf("我是:%s\n", name)
    fmt.Println("在终端打印单独一行显示")
}
//输出
在终端打印该信息。我是:沙河小王子
在终端打印单独一行显示

Fprint

Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中,我们通常用这个函数往文件中写入内容。

// 向标准输出写入内容
fmt.Fprintln(os.Stdout, "向标准输出写入内容")
fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
    fmt.Println("打开文件出错,err:", err)
    return
}
name := "沙河小王子"
// 向打开的文件句柄中写入内容
fmt.Fprintf(fileObj, "往文件中写如信息:%s", name)
//只要满足io.writer类型的都能写入

Sprint

Sprint系列函数会把传入的数据生成并返回一个字符串。

s1 := fmt.Sprint("沙河小王子")
name := "沙河小王子"
age := 18
s2 := fmt.Sprintf("name:%s,age:%d", name, age)
s3 := fmt.Sprintln("沙河小王子")
fmt.Println(s1, s2, s3)

Errorf

Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。

	err := fmt.Errorf("这是一个错误")

Go1.13版本为fmt.Errorf函数新加了一个%w占位符用来生成一个可以包裹Error的Wrapping Error。

e := errors.New(``"原始错误e"``)``w := fmt.Errorf(``"Wrap了一个错误%w"``, e)

获取输入

Go语言fmt包下有fmt.Scanfmt.Scanffmt.Scanln三个函数,可以在程序运行过程中从标准输入获取用户的输入。

fmt.Scan

  • Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。
func main() {
    var (
        name    string
        age     int
        married bool
    )
    fmt.Scan(&name, &age, &married)
    fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

fmt.Scanf

  • Scanf从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。
func main() {
    var (
        name    string
        age     int
        married bool
    )
    fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married)
    fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

fmt.Scanf不同于fmt.Scan简单的以空格作为输入数据的分隔符,fmt.Scanf为输入数据指定了具体的输入内容格式,只有按照格式输入数据才会被扫描并存入对应变量。

$ ./scan_demo
1:小王子 2:28 3:false
扫描结果 name:小王子 age:28 married:false

fmt.Scanln

  • Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。
  • 本函数返回成功扫描的数据个数和遇到的任何错误。
func main() {
    var (
        name    string
        age     int
        married bool
    )
    fmt.Scanln(&name, &age, &married)
    fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}

log标准库

一、使用logger

log包定义了Logger类型,该类型提供了一些格式化输出的方法。本包也提供了一个预定义的“标准”logger,可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用,比自行创建一个logger对象更容易使用。

package main

import (
	"log"
)

func main() {
	log.Println("这是一条很普通的日志。")
	v := "很普通的"
	log.Printf("这是一条%s日志。\n", v)
	log.Fatalln("这是一条会触发fatal的日志。")
	log.Panicln("这是一条会触发panic的日志。")
}

logger会打印每条日志信息的日期、时间,默认输出到系统的标准错误。Fatal系列函数会在写入日志信息后调用os.Exit(1)。Panic系列函数会在写入日志信息后panic。

二、配置logger

1.标准的logger配置

默认情况下的logger只会提供日志的时间信息,但是很多情况下我们希望得到更多信息,比如记录该日志的文件名和行号等。log标准库中为我们提供了定制这些设置的方法。

log标准库中的Flags函数会返回标准logger的输出配置,而SetFlags函数用来设置标准logger的输出配置。

2.flag选项

log标准库提供了如下的flag选项,它们是一系列定义好的常量。

const (
    // 控制输出日志信息的细节,不能控制输出的顺序和格式。
    // 输出的日志在每一项后会有一个冒号分隔:例如2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
    Ldate         = 1 << iota     // 日期:2009/01/23
    Ltime                         // 时间:01:23:23
    Lmicroseconds                 // 微秒级别的时间:01:23:23.123123(用于增强Ltime位)
    Llongfile                     // 文件全路径名+行号: /a/b/c/d.go:23
    Lshortfile                    // 文件名+行号:d.go:23(会覆盖掉Llongfile)
    LUTC                          // 使用UTC时间
    LstdFlags     = Ldate | Ltime // 标准logger的初始值
)

示例:

func main() {
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("这是一条很普通的日志。")
}
//输出
2017/06/19 14:05:17.494943 .../log_demo/main.go:11: 这是一条很普通的日志。

3.配置日志前缀

log标准库中还提供了关于日志信息前缀的两个方法:

func Prefix() string
func SetPrefix(prefix string)

其中Prefix函数用来查看标准logger的输出前缀,SetPrefix函数用来设置输出前缀。

func main() {
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("这是一条很普通的日志。")
	log.SetPrefix("[小王子]")
	log.Println("这是一条很普通的日志。")
}

上面的代码输出如下:

[小王子]2017/06/19 14:05:57.940542 .../log_demo/main.go:13: 这是一条很普通的日志。

这样我们就能够在代码中为我们的日志信息添加指定的前缀,方便之后对日志信息进行检索和处理。

4.配置日志输出地址

SetOutput函数用来设置标准logger的输出目的地,默认是标准错误输出。

例如,下面的代码会把日志输出到同目录下的xx.log文件中。

func main() {
	logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("open log file failed, err:", err)
		return
	}
	log.SetOutput(logFile)
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("这是一条很普通的日志。")
	log.SetPrefix("[小王子]")
	log.Println("这是一条很普通的日志。")
}

如果你要使用标准的logger,我们通常会把上面的配置操作写到init函数中。

三、创建logger

log标准库中还提供了一个创建新logger对象的构造函数–New,支持我们创建自己的logger示例。

New创建一个Logger对象。其中,参数out设置日志信息写入的目的地。参数prefix会添加到生成的每一条日志前面。参数flag定义日志的属性(时间、文件等等)。

举个例子:

func test5() {
	file, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0755)
   //logger := log.New(os.Stdout, "<New>", log.Lshortfile|log.Ldate|log.Ltime)
	logger := log.New(file, "<wjm>",log.Llongfile|log.Ldate|log.Lmicroseconds)
	logger.Println("自定义logger")
}

Bytes标准库

bytes包提供了对字节切片进行读写操作的一系列函数,字节切片处理的函数比较多分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等。

一、常用函数:

示例

package main

import (
	"bytes"
	"fmt"
)

func main() {
	var i int = 1
	var j byte = 2
	j = byte(i)
	fmt.Printf("j:%v.. %T\n", j, j)

	b1 := []byte("hello world!")
	s1 := []byte("h")
	s2 := []byte("l")
	s3 := []byte("o")
	//切片数组s在切片数组b的个数
	fmt.Println(bytes.Count(b1, s1))
	fmt.Println(bytes.Count(b1, s2))
	fmt.Println(bytes.Count(b1, s3))

	i2 := []byte("hello")
	//将一个字节数组重复n次
	fmt.Println(string(bytes.Repeat(i2, 1)))
	fmt.Println(string(bytes.Repeat(i2, 3)))

	i3 := []byte("hello world!")
	old := []byte("o")
	new := []byte("aaa")
	//替换字符串
	fmt.Println(string(bytes.Replace(i3, old, new, 0)))
	fmt.Println(string(bytes.Replace(i3, old, new, 2)))

	//Runes
	s := []byte("你好世界")
	r := bytes.Runes(s)
	fmt.Println("转换前字符串的长度:", len(s)) //12
	fmt.Println("转换后字符串的长度:", len(r)) //4
	fmt.Println(string(s))

	i4 := []byte("helloworld")
	i5 := []byte("HelloWorld")
	i6 := []byte("helloworld")
	//是否包含
	c1 := bytes.Contains(i4, i5)
	c2 := bytes.Contains(i4, i6)
	fmt.Println(c1, c2)

	//定义一个分隔符并连接字符串
	a1 := [][]byte{[]byte("你好"), []byte("世界")}
	i7 := []byte(",")
	fmt.Println(string(bytes.Join(a1, i7)))
}
//输出
j:1.. uint8
1                      
3                      
2                      
hello                  
hellohellohello        
hello world!           
hellaaa waaarld!       
转换前字符串的长度: 12
转换后字符串的长度: 4 
你好世界               
false true             
你好,世界

二、buffer类型

缓冲区是具有读取和写入方法的可变大小的字节缓冲区。Buffer的零值是准备使用的空缓冲区。

声明一个Buffer的四种方法:

var b bytes.Buffer // 直接定义一个Buffer变量,不用初始化,可以直接使用
b := new(bytes.Buffer) // 使用New返回Buffer变量
b := bytes.NewBuffer(s []byte) // 从一个[]byte切片,构造一个Buffer
b := bytes.NewBufferString(s string) // 从一个string变量,构造一个Buffer

往buffer中写入数据:

b.Write(d []byte) // 将切片d写入Buffer数据
b.WriteString(s string) // 将字符串s写入Buffer尾部
b.WriteByte(c byte) // 将字符c写入Buffer尾部
b.WriteRune(r rune) // 将一个rune类型的数据放到缓冲器的尾部
b.WriteTo(w io.Writer) // 将Buffer中的内容输出到实现了io.Writer接口的可写入对象中

注:将文件中的内容写入Buffer,则使用ReadFrom(i io.Reader)

从Buffer中读取数据到指定容器

c := make([]byte, 8)
b.Read(c) //一次读取8个byte到c容器中,每次读取新的8个byte覆盖c中原来的内容
b.ReadByte() //读取第一个byte,b的第一个byte被拿掉,赋值给 a => a, _ := b.ReadByte()
b.ReadRune() //读取第一个rune,b的第一个rune被拿掉,赋值给 r => r, _ := b.ReadRune()
b.ReadBytes(delimiter byte) //需要一个byte作为分隔符,读的时候从缓冲器里找第一个出现的分隔符(delim),找到后,把从缓冲器头部开始到分隔符之间的所有byte进行返回,作为byte类型的slice,返回后,缓冲器也会空掉一部分
b.ReadString(delimiter byte) //需要一个byte作为分隔符,读的时候从缓冲器里找第一个出现的分隔符(delim),找到后,把从缓冲器头部开始到分隔符之间的所有byte进行返回,作为字符串返回,返回后,缓冲器也会空掉一部分
b.ReadFrom(i io.Reader) //从一个实现io.Reader接口的r,把r里的内容读到缓冲器里,n返回读的数量
 
file, _ := os.Open(".text.txt")
buf := bytes.NewBufferString("Hello world")
buf.ReadFrom(file)
//将text.txt内容追加到缓冲器的尾部
fmt.Println(buf.String())
//清空数据
b.Reset()
//转换为字符串
b.String()

Reader类型

Reader实现了io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, io.ByteScanner, io.RuneScanner接口,Reader是只读的、可以seek。

package main
 
import (
    "bytes"
    "fmt"
)
 
func testReader() {
    data := "123456789"
    //通过[]byte创建Reader
    re := bytes.NewReader([]byte(data))
    //返回未读取部分的长度
    fmt.Println("re len : ", re.Len())
    //返回底层数据总长度
    fmt.Println("re size : ", re.Size())
    fmt.Println("---------------")
 
    buf := make([]byte, 2)
    for {
        //读取数据
        n, err := re.Read(buf)
        if err != nil {
            break
        }
        fmt.Println(string(buf[:n]))
    }
 
    fmt.Println("----------------")
 
    //设置偏移量,因为上面的操作已经修改了读取位置等信息
    re.Seek(0, 0)
    for {
        //一个字节一个字节的读
        b, err := re.ReadByte()
        if err != nil {
            break
        }
        fmt.Println(string(b))
    }
    fmt.Println("----------------")
 
    re.Seek(0, 0)
    off := int64(0)
    for {
        //指定偏移量读取
        n, err := re.ReadAt(buf, off)
        if err != nil {
            break
        }
        off += int64(n)
        fmt.Println(off, string(buf[:n]))
    }
}
 
func main() {
    testReader()
}

posted @   TeamWang  阅读(93)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
点击右上角即可分享
微信分享提示