Go语言入门与基础笔记——从安装到并发编程

Go语言入门与基础学习笔记——从HelloWorld到Go并发

0 写在前面

所有学习内容来自开源社区或资源
来源:
极客兔兔 https://geektutu.com/
Go菜鸟教程 https://www.runoob.com/go/
狂神说 https://www.kuangstudy.com/

1 学前工作

1.1 下载安装Go

Go 语言中文网https://studygolang.com/dl

选择自己操作系统相关的安装包

在这里插入图片描述

1.2 配置安装目录环境变量

下载安装后打开cmd 并输入go version 出现版本信息表示安装成功,否则需要配置系统环境变量,安装到哪个文件夹就将哪个文件夹添加进系统变量

在这里插入图片描述

1.3 配置工作目录环境变量

配置安装环境变量之后,Go还需要一个安装目录,即GOROOT和GOPATH

创建文件夹GoWorks 并配置GOPATH和GOROOT系统变量

然后在文件夹GoWorks下创建三个新目录分别为 srcpkgbin

在这里插入图片描述

1.4 检查前置步骤是否生效

打开cmd输入go env 查看 GOPATHGOROOT为更改后的文件,否则需要重新修改系统的环境变量

在这里插入图片描述

1.5 下载和安装GoLand

https://www.jetbrains.com/go/download/download-thanks.html

安装过程略

1.6 Hello,World

package main

import "fmt"

func main() {

	fmt.Println("Hello,World!") // 打印输出Helloworld字符串

}

在这里插入图片描述

2 基础语法

2.1 注释

package main

import "fmt"
// 我是单行注释
/*
多行注释
多行注释
多行注释
多行注释
*/

func main() {
	fmt.Println("Hello,World!") // 打印输出Helloworld字符串
}

2.2 变量

package main

import "fmt"

func main() {
	var name string = "tylers"
	name = "zhangsan"
    
    var (
		name string
		age  int
		addr string
	)
	//string 默认值 空
	// int 默认值 0
	
	fmt.Println(name) // zhangsan
}

2.3 内存地址

func main() {
	var num int
	num = 100
	//string 默认值 空
	// int 默认值 0
	fmt.Println("num:%d, 内存地址:%p", num, &num) // 取地址符 &变量名

	num = 200
	fmt.Println("num:%d, 内存地址:%p", num, &num) // 取地址符 &变量名
}

//内存地址不变

2.4 变量的交换

package main

import "fmt"

func main() {
	var a int = 100
	var b int = 200

	b, a = a, b
	fmt.Println(a, b)
}

2.5 函数的调用和返回

package main

import "fmt"

func test() (int, int) {
	return 100, 200
}

func main() {
	a, b := test()
	fmt.Println(a, b)
}

2.6 匿名变量

package main

import "fmt"

func test() (int, int) {
	return 100, 200
}

func main() {
	a, _ := test()
	_, b := test()
	fmt.Println(a)
	fmt.Println(b)
}

2.7 变量的作用域

package main

import (
	"fmt"
)

var name string = "kuangshen"

func main() {

	// = 赋值, 就是将右边的值,赋给左边的变量
	// name 变量
	// 局部变量 
	// 就近原则使用变量

	var age int = 18
	var name string = "kuangshen333"

	fmt.Println(name, age)
}

func aaa() {
	fmt.Println(name)
}

2.8 常量

package main

import "fmt"

func main() {
	const URL string = "www.baidu.com" //  显式定义
	const URL2 = "www.baidu.com"       //  隐式定义

	const a, b, c = 3.14, "kuangshen", false // 同时定义多个常量

	fmt.Println(URL)
	fmt.Println(URL2)
	fmt.Println(a, b, c)
}

2.9 iota

在这里插入图片描述

在这里插入图片描述

package main

import "fmt"

func main() {

	const (
		a = iota // iota 0
		b = iota // iota 1
		c = iota // iota 2
		d = "hahaha" // "hahaha" iota 3
		e         // "hahaha" iota 4
		f = 100   // 100 iota 5
		g  		  // 100  iota 6
		h = iota  // iota 7
		i         // iota 8
	)

	fmt.Println(a, b, c, d, e, f, g, h, i)

}

在这里插入图片描述

package main

import "fmt"

func main() {

	const (
		a = iota // 0
		b = iota
		c = iota
		d = "hahaha"
		e
		f = 100
		g
		h = iota
		i
	)

	const (
		j = iota // 0
		k        // 1
	)

	fmt.Println(a, b, c, d, e, f, g, h, i)

	fmt.Println(j, k)

}

3 基本数据类型

在这里插入图片描述

3.1 布尔类型

package main

import "fmt"

func main() {

	// var 变量名 数据类型
	// bool : true false
	// bool 默认值为 false
	var isFlag bool = true
	var isFlag2 bool

	fmt.Println(isFlag)  // true
	fmt.Println(isFlag2) // false

	fmt.Printf("%T, %t\n", isFlag, isFlag) // bool, true
}

3.2 数字型

package main

import "fmt"

func main() {
	// 定义一个整形
	var age int = 18

	// 定义一个浮点型
	// 默认是6位小数打印 3.140000
	// 丢失精度时“五舍六入”
	var money float64 = 3.16

	fmt.Printf("%T, %d\n", age, age)

	fmt.Printf("%T, %.1f\n", money, money)

}
	// float运算尾数部分可能丢失, 造成精度损失, -123.00000901
	// 尽量使用float64 不要使用 float32

	// 数据类型:内存空间分配的大小是不同的

	// float64 尽量使用float64 来定义浮点类型的小数
	var num1 float32 = -123.0000901 // num1 =  -123.00009
	var num2 float64 = -123.0000901 // num2 =  -123.0000901
	fmt.Println("num1 = ", num1, "num2 = ", num2)

3.3 字符串

package main

import "fmt"

func main() {

	var str string
	str = "Hello, zhangleyao"

	fmt.Printf("%T, %s\n", str, str)

	// 单引号
	v1 := 'A'
	v2 := "A"
	// 编码表 ASCII字符吗
	// 扩展:
	// 所有的中国字的编码表:GBK
	// 全世界的编码表:Unicode编码表
	fmt.Printf("%T, %c\n", v1, v1)
	fmt.Printf("%T, %s\n", v2, v2)

	// 字符串连接 +
	fmt.Println("hello" + ",xuexiangban")

	// 转义字符  \
	fmt.Println("hello\"xuexiangban")

}

3.4 数据类型转换

package main

import "fmt"

// 类型转换
// 转换后的变量 := 要转换的类型(变量)
// 备注:整型是不能转化为bool类型的
func main() {

	a := 3   // int
	b := 5.0 // float64

	// 需求: 将int类型的a转换为 float64 类型  类型转换
	c := float64(a)
	d := int(a)

	// bool  整型是不能转化为bool类型的
	//e := bool(a)

	fmt.Printf("%T\n", a)
	fmt.Printf("%T\n", b)
	fmt.Printf("%T\n", c)
	fmt.Printf("%T\n", d)
	//fmt.Printf("%T\n", e)

}

3.5 补充:数组(array)和切片(slice)

声明数组

var arr [5]int		// 一维
var arr2 [5][5]int	// 二维

声明时初始化

var arr = [5]int{1, 2, 3, 4, 5}
// 或 arr := [5]int{1, 2, 3, 4, 5}

使用[]索引/修改数组

arr := [5]int{1, 2, 3, 4, 5}
for i:= 0;i < len(arr); i++{
    arr[i] += 100
}
fmt.Println(arr)  // [101 102 103 104 105]

数组的长度不能改变,如果想拼接2个数组,或是获取子数组,需要使用切片。切片是数组的抽象。 切片使用数组作为底层结构。切片包含三个组件:容量,长度和指向底层数组的指针,切片可以随时进行扩展

声明切片:

slice := make([]float32, 0)  // 长度为0的切片
slice2 := make([]float32, 3, 5) // [0 0 0] 长度为3容量为5的切片
fmt.Println(len(slice2), cap(slice2)) // 3 5
// 声明切片的另一种方式 [] 中不写任何值
slice3 := []float32{1, 2, 3}

使用切片:

// 添加元素,切片容量可以根据需要自动扩展
slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4]
fmt.Println(len(slice2), cap(slice2)) // 7 12
// 子切片 [start, end) 左闭右开
sub1 := slice2[3:] // [1 2 3 4]
sub2 := slice2[:3] // [0 0 0]
sub3 := slice2[1:4] // [0 0 1]
// 合并切片
combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0]
  • 声明切片时可以为切片设置容量大小,为切片预分配空间。在实际使用的过程中,如果容量不够,切片容量会自动扩展。
  • sub2... 是切片解构的写法,将切片解构为 N 个独立的元素。

3.6 补充:字典(键值对, map)

map 类似于 java 的 HashMap,Python的字典(dict),是一种存储键值对(Key-Value)的数据结构。使用方式和其他语言几乎没有区别

// 仅声明
m1 := make(map[string]int)
// 声明时初始化
m2 := map[string]string{
	"Sam": "Male",
	"Alice": "Female",
}
//       key - value
m3 := map[int]string{
	1: "zhangleyao",
}

// 赋值/修改
m1["Tom"] = 18

3.7 指针(pointer)

指针即某个值的地址,类型定义时使用符号*,对一个已经存在的变量,使用 & 获取该变量的地址。

str := "Golang"
var p *string = &str // p 是指向 str 的指针
*p = "Hello"
fmt.Println(str) // Hello 修改了 p,str 的值也发生了改变

一般来说,指针通常在函数传递参数,或者给某个类型定义新的方法时使用。Go 语言中,参数是按值传递的,如果不使用指针,函数内部将会拷贝一份参数的副本,对参数的修改并不会影响到外部变量的值。如果参数使用指针,对参数的传递将会影响到外部变量。

例如:(同C++)

func add(num int) {
	num += 1
}

func realAdd(num *int) {
	*num += 1
}

func main() {
	num := 100
	add(num)
	fmt.Println(num)  // 100,num 没有变化

	realAdd(&num)
	fmt.Println(num)  // 101,指针传递,num 被修改
}

3.8 结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:

  • Title :标题
  • Author : 作者
  • Subject:学科
  • ID:书籍ID
定义结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}


func main() {

    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})

    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})

    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}

在这里插入图片描述

访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

结构体.成员名"
package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 声明 Book1 为 Books 类型 */
   var Book2 Books        /* 声明 Book2 为 Books 类型 */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   fmt.Printf( "Book 1 title : %s\n", Book1.title)
   fmt.Printf( "Book 1 author : %s\n", Book1.author)
   fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
   fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

   /* 打印 Book2 信息 */
   fmt.Printf( "Book 2 title : %s\n", Book2.title)
   fmt.Printf( "Book 2 author : %s\n", Book2.author)
   fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
   fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}

在这里插入图片描述

结构体作为函数参数
package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 声明 Book1 为 Books 类型 */
   var Book2 Books        /* 声明 Book2 为 Books 类型 */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   printBook(Book1)

   /* 打印 Book2 信息 */
   printBook(Book2)
}

func printBook( book Books ) {
   fmt.Printf( "Book title : %s\n", book.title)
   fmt.Printf( "Book author : %s\n", book.author)
   fmt.Printf( "Book subject : %s\n", book.subject)
   fmt.Printf( "Book book_id : %d\n", book.book_id)
}

在这里插入图片描述

结构体指针

你可以定义指向结构体的指针类似于其他指针变量,格式如下:

var struct_pointer *Books

以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:

struct_pointer = &Book1

使用结构体指针访问结构体成员,使用 “.” 操作符:

struct_pointer.title
package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 声明 Book1 为 Books 类型 */
   var Book2 Books        /* 声明 Book2 为 Books 类型 */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   printBook(&Book1)

   /* 打印 Book2 信息 */
   printBook(&Book2)
}
func printBook( book *Books ) {
   fmt.Printf( "Book title : %s\n", book.title)
   fmt.Printf( "Book author : %s\n", book.author)
   fmt.Printf( "Book subject : %s\n", book.subject)
   fmt.Printf( "Book book_id : %d\n", book.book_id)
}

在这里插入图片描述

4 运算符

4.1 算术运算符

package main

import "fmt"

func main() {
	var a int = 10
	var b int = 3
	//var c int

	// + - * / % ++ --
	fmt.Println(a + b)
	fmt.Println(a - b)
	fmt.Println(a * b)
	fmt.Println(a % b)
	a++ // a = a + 1
	fmt.Println(a)
	a-- // a = a - 1
	fmt.Println(a)
}

在这里插入图片描述

4.2 关系运算符

package main

import "fmt"

func main() {

	var a int = 11
	var b int = 10

	// == 等于  = 赋值
	// 类型运算符 结果都是 bool
	fmt.Println(a == b) // 等于
	fmt.Println(a != b) // 不等于
	fmt.Println(a > b)
	fmt.Println(a < b)
	fmt.Println(a >= b)
	fmt.Println(a <= b)

	// 判断 if 如果 。。。 结果
	if a > b {
		// 执行一些 a>b 的操作
	} else { // else 否则

	}

}

在这里插入图片描述

4.3 逻辑运算符

在这里插入图片描述

4.4 位运算符(二进制)

在这里插入图片描述

4.5 赋值运算符

在这里插入图片描述

4.6 其他运算符

在这里插入图片描述

4.7 拓展:键盘输入输出

package main

import "fmt"

func main() {

	var x int
	var y float64

	// 定义了两个变量,想用键盘来录入这两个变量

	//fmt.Println()  // 打印并换行
	//fmt.Printf()  // 格式化输出
	//fmt.Print()  // 打印输出

	fmt.Scanln(&x)
	//fmt.Scanln(&x)// 接收输入 Scanl
	//fmt.Scanf()   // 接收输入 格式化输入 作业
	//fmt.Scan()    // 接收输入 作业

	fmt.Println(x)

}

5 流程控制

在这里插入图片描述

5.1 if语句

if a > 20 {
    
} // 和python一样不需要括号
else if a > 10{
    
}
else {
    
} 

5.2 switch

//和C语言一样
package main

import "fmt"

func main() {

	var a int = 90

	// 匹配 case
	switch a {
	case 90:
		fmt.Println("A")
	case 80:
		fmt.Println("B")
	case 50, 60, 70:
		fmt.Println("C")
	default:
		fmt.Println("D")
	}

	// switch 默认的条件 bool = true
	switch {
	case false:
		fmt.Println("false")
	case true:
		fmt.Println("true")
	default:
		fmt.Println("其他")
	}

}

5.3 fallthrough穿透

package main

import "fmt"

func main() {

	a := false

	switch a {
	case false:
		fmt.Println("1. case条件为false")
		fallthrough // case 穿透的,不管下一个条件满不满足, 都会执行
	case true:
		if a == false {
			break // 终止case穿透
		}
		fmt.Println("2. case条件为true")
	}

}

5.4 for

package main

import "fmt"

func main() {

	// for 条件的起始值:循环条件:控制变量自增或者自减
	// for : 循环条件
	// for {} 无限循环
	//循环10次
	i := 0
	for i < 10 {
		i++
		fmt.Println(i)
	}

	for {
		fmt.Println(i)
		i++
	}

	// 计算 1 到 10 的数字之和:
	//sum := 0
	//for i := 0; i < 1000; i++ {
	//	sum += i
	//	fmt.Println(sum)
	//}

}

5.5 string

package main

import "fmt"

func main() {

	// utf-8编码
	var str string = "hello, zhangleyao"

	fmt.Println(str)

	// 获取字符串的长度 len
	fmt.Println("字符串的长度为: ", len(str))

	// 获取指定的字节
	fmt.Println("字节打印: ", str[0])
	fmt.Printf("%c\n", str[0])

	// for
	//for i := 0; i < len(str); i++ {
	//	fmt.Printf("%c\n", str[i])
	//}

	//for range 循环 , 遍历数组、切片
	// 返回下标和对应的值,使用这个值就可以了
	for i, v := range str {
		fmt.Print(i)
		fmt.Printf(" %c ", v)
	}
}

6 函数

6.1 什么是函数

package main

import "fmt"

/*
- 函数是基本的代码块,用于执行一个任务。
- G0 语言最少有个 main() 函数。
- 你可以通过函数来划分不同功能。逻辑上每个函数执行的是指定的任务。
- 函数声明告诉了编译器函数的名称,返回类型,参数。
*/

func main() {
	fmt.Println("hello, world")

	// 调用函数 函数名()
	fmt.Println(add(1, 2))
}

//		func 函数名 (参数, 参数 ...) 函数调用后的返回值{
//			 函数体 : 执行一段代码
//	      return 返回结果
func add(a, b int) int {
	c := a + b

	return c
}

6.2 函数的声明

  • 无参无返回值的函数
  • 有一个参数的函数
  • 有两个参数的函数
  • 有一个返回值的函数
  • 有多个返回值的函数
package main

import (
	"fmt"
	"strconv"
)

func main() {
	printinfo()
	myprint("printinfo")

	// 有返回值函数,我们就需要接收函数的返回值
	c := add2(1, 2)
	myprint(strconv.Itoa(c))
	x, y := swap("123", "321")
	fmt.Println(x, y)
}

func printinfo() {
	fmt.Println("printinfo")
}

func myprint(msg string) {
	fmt.Println(msg)
}

// 有两个参数的函数
// 有一个返回值的函数
func add2(a, b int) int {
	c := a + b
	return c
}

// 有多个返回值的函数
func swap(x, y string) (string, string) {
	return y, x
}

6.3 形式参数和实际参数

package main

func main() {

	max(1, 2)
}

// max 两个数字比大小
// 形式参数: 定义函数时,用来接收外部传入数据的参数,就是形式参数
// 实际参数: 调用函数时,传给形参的实际数据叫做实际参数
func max(num1, num2 int) int {
	var result int
	if num1 > num2 {
		result = num1
	} else {
		result = num2
	}
	return result
}

6.4 值传递

package main

import "fmt"

func main() {
	getSum("haha", 2, 3, 4, 5, 6)
}

// ...可变参数
func getSum(msg string, nums ...int) {
	sum := 0
	for i := 0; i < len(nums); i++ {
		fmt.Println(nums[i])
		sum += nums[i]
	}

	fmt.Println("sum:", sum)

}

6.5 可变参数

package main

import "fmt"

func main() {
	getSum("haha", 2, 3, 4, 5, 6)
}

// ...可变参数
func getSum(msg string, nums ...int) {
	sum := 0
	for i := 0; i < len(nums); i++ {
		fmt.Println(nums[i])
		sum += nums[i]
	}

	fmt.Println("sum:", sum)

}

6.6 值传递

在这里插入图片描述

package main

import "fmt"

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

	// 传递, 拷贝arr
	update(arr)
	fmt.Println("调用修改后的数据: ", arr)
	
	// 引用传递

}

func update(arr2 [4]int) {
	fmt.Println("arr2接收的数据:", arr2)
	arr2[0] = 100
	fmt.Println("arr2修改后的数据", arr2)
}

在这里插入图片描述

6.7 引用传递

在这里插入图片描述

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] = 100
	fmt.Println("修改后的数据", s2)
}

在这里插入图片描述

6.8 函数变量的作用域

同C++和Python

在这里插入图片描述

package main

import "fmt"

// 全局变量
var name int = 100

func main() {

   // 函数体内的局部变量
   temp := 100

   // if语句、 for语句定义的一次性变量局部变量
   if b := 1; b <= 10 {
      // 语句内的局部变量
      temp := 50
      fmt.Println(temp) // 局部变量,就近原则
      fmt.Println(b)
   }
   fmt.Println(temp)
   //fmt.Println(b)
   f1()
   f2()

}

func f1() {
   a := 1
   fmt.Println(a)
   fmt.Println(name)
}

func f2() {
   //
   fmt.Println(name)
}

6.9 递归函数

同C++、python

package main

import "fmt"

func main() {
	fmt.Println(sum(100))
}

func sum(a int) int {
	if a != 0 {
		return a + sum(a-1)
	}
	return 0
}

6.10 defer延迟函数执行

主要用来进行关闭操作

package main

import "fmt"

// defer 关闭操作
func main() {
   a := 10
   fmt.Println("a=", a)
   defer f(a) // 参数就已经传递进去了,在最后执行
   a++
   fmt.Println("end a=", a)

}

func f(s int) {
   fmt.Println("函数里面的a=", s)
}

在这里插入图片描述

6.11 高级:函数的本质和数据类型

函数是个地址,可以像Python一样引用函数

package main

import "fmt"

// func() 本身就是一个数据类型
func main() {
	// f1 如果不加括号,函数就是一个变量
	// f1() 如果加了括号那就成了函数的调用
	fmt.Printf("%T\n", f3) // func()  |  func(int, int)  | func(int, int) int
	//fmt.Printf("%T", 10)      // int
	//fmt.Printf("%T", "hello") // string

	var f5 func(int, int)
	f5 = f3
	f5(1, 2)
}

func f3(a, b int) {
	fmt.Println(a, b)
}

6.12 高级:匿名函数推导

package main

import (
	"fmt"
)

func main() {
	f5()
	f2 := f5 // 函数本身也是一个变量
	f2()

	f3 := func() {
		fmt.Println("我是f3函数")
	}

	f3()

	func() {
		fmt.Println("我是匿名函数")
	}()

	func(a, b int) {
		fmt.Println(a, b)
		fmt.Println("我是匿名函数2")

	}(1, 2)

	r1 := func(a, b int) int {
		fmt.Println(a, b)
		fmt.Println("我是匿名函数2")

		return a + b
	}(1, 2)

	fmt.Println(r1)

}

func f5() {
	fmt.Println("我是f5函数")
}

// go语言可以传入func()
func f6(f func()) {

}

Go语言是支持函数式编程:

1、 将匿名桉树作为另一个函数的参数,回调函数

2、 将匿名函数作为另外一个函数的返回值,可以形成闭包结构

6.13 高级:函数式编程

高阶函数:根据go语言的数据类型的特点,可以将一个函数作为另外一个函数的参数

fun1(),fun2()

将 fun1 函数作为 fun2 这个函数的参数

fun2函数:就叫做高阶函数,接收了一个函数作为参数的函数

fun1函数:就叫做回调函数,作为另外个函数的参数

package main

import "fmt"

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

	r2 := oper(3, 4, add3)
	fmt.Println(r2)

	r3 := oper(8, 4, sub)
	fmt.Println(r3)

    // 可以直接传入匿名函数
	r4 := oper(8, 4, func(a, b int) int {
		if b == 0 {
			fmt.Println("除数不能为0")
			return 0
		} else {
			return a / b
		}
	})
	fmt.Println(r4)
}

// 高阶函数
func oper(a, b int, fun func(int, int) int) int {
	r := fun(a, b)
	return r
}

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

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

6.14 闭包结构的理解

package main

import "fmt"

/*
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构 在本例中指的是i这个变量

局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用
*/
func main() {

	r1 := increment()
	fmt.Printf("%p", &r1)

	v1 := r1()
	fmt.Println(v1)
	v2 := r1()
	fmt.Println(v2)
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println(r1())

	//
	r2 := increment()
	v3 := r2()
	fmt.Println(v3)
	fmt.Println(r1())
	fmt.Println(r2())

}

// 自增
func increment() func() int {
	// 局部变量i
	i := 0
	// 定义一个匿名函数,给变量自增并返回
	fun := func() int { // 内层函数,没有执行的
		i++
		return i
	}
	return fun
}

6.15 补充:错误处理

如果函数实现过程中,如果出现不能处理的错误,可以返回给调用者处理。比如我们调用标准库函数os.Open读取文件,os.Open 有2个返回值,第一个是 *File,第二个是 error, 如果调用成功,error 的值是 nil,如果调用失败,例如文件不存在,我们可以通过 error 知道具体的错误信息。

package main

import (
	"fmt"
	"os"
)

func main() {
	_, err := os.Open("filename.txt")
	if err != nil {
		fmt.Println(err)
	}
}

// open filename.txt: no such file or directory
// open filename.txt: The system cannot find the file specified.

可以通过 errorw.New 返回自定义的错误

package main

import (
	"errors"
	"fmt"
)

func hello(name string) error {
	if len(name) == 0 {
		return errors.New("error: name is null")
	}
	fmt.Println("Hello,", name)
	return nil
}

func main() {
	if err := hello(""); err != nil {
		fmt.Println(err)
	}
}

// error: name is null

error 往往是能预知的错误,但是也可能出现一些不可预知的错误,例如数组越界,这种错误可能会导致程序非正常退出,在 Go 语言中称之为 panic。

func get(index int) int {
	arr := [3]int{2, 3, 4}
	return arr[index]
}

func main() {
	fmt.Println(get(5))
	fmt.Println("finished")
}
$ go run .
panic: runtime error: index out of range [5] with length 3
goroutine 1 [running]:
exit status 2

在 Python、Java 等语言中有 try...catch 机制,在 try 中捕获各种类型的异常,在 catch 中定义异常处理的行为。Go 语言也提供了类似的机制 deferrecover

func get(index int) (ret int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Some error happened!", r)
			ret = -1
		}
	}()
	arr := [3]int{2, 3, 4}
	return arr[index]
}

func main() {
	fmt.Println(get(5))
	fmt.Println("finished")
}
  • 在 get 函数中,使用 defer 定义了异常处理的函数,在协程退出前,会执行完 defer 挂载的任务。因此如果触发了 panic,控制权就交给了 defer。
  • 在 defer 的处理逻辑中,使用 recover,使程序恢复正常,并且将返回值设置为 -1,在这里也可以不处理返回值,如果不处理返回值,返回值将被置为默认值 0。

6.16 补充:方法

可以理解为结构体的方法,在Java中就是把结构体看成一个类的成员变量,然后方法就是这个类的方法,可以调用这个类中的成员变量。

Go 语言中同时有函数和方法。

一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

下面定义一个结构体类型和该类型的一个方法:

package main

import (
   "fmt"  
)

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

在这里插入图片描述

6.17 补充:Go并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

例如:

go f(x, y, z)

开启一个新的 goroutine:

f(x, y, z)

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

package main

import (
        "fmt"
        "time"
)

func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("world")
        say("hello")
}

6.18 补充:通道channel

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

package main

import "fmt"

func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}

func main() {
        s := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收

        fmt.Println(x, y, x+y)
}

在这里插入图片描述

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}

在这里插入图片描述

Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

package main

import (
        "fmt"
)

func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)
}

func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

在这里插入图片描述

7 结构体、方法和接口

7.1 结构体(struct)和方法(methods)

结构体类似于其他语言中的 class,可以在结构体中定义多个字段,为结构体实现方法,实例化等。接下来我们定义一个结构体 Student,并为 Student 添加 name,age 字段,并实现 hello() 方法

type Student struct {
	name string
	age  int
}

func (stu *Student) hello(person string) string {
	return fmt.Sprintf("hello %s, I am %s", person, stu.name)
}

func main() {
	stu := &Student{
		name: "Tom",
	}
	msg := stu.hello("Jack")
	fmt.Println(msg) // hello Jack, I am Tom
}
  • 使用 Student{field: value, ...}的形式创建 Student 的实例,字段不需要每个都赋值,没有显性赋值的变量将被赋予默认值,例如 age 将被赋予默认值 0。
  • 实现方法与实现函数的区别在于,func 和函数名hello 之间,加上该方法对应的实例名 stu 及其类型 *Student,可以通过实例名访问该实例的字段name和其他方法了。
  • 调用方法通过 实例名.方法名(参数) 的方式。

除此之外,还可以使用 new 实例化:

func main() {
	stu2 := new(Student)
	fmt.Println(stu2.hello("Alice")) // hello Alice, I am  , name 被赋予默认值""
}

7.2 接口(interface)

一般而言,接口定义了一组方法的集合,接口不能被实例化,一个类型可以实现多个接口。

举一个简单的例子,定义一个接口 Person和对应的方法 getName()getAge()

type Person interface {
	getName() string
}

type Student struct {
	name string
	age  int
}

func (stu *Student) getName() string {
	return stu.name
}

type Worker struct {
	name   string
	gender string
}

func (w *Worker) getName() string {
	return w.name
}

func main() {
	var p Person = &Student{
		name: "Tom",
		age:  18,
	}

	fmt.Println(p.getName()) // Tom
}
  • Go 语言中,并不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可。
  • 实例化 Student后,强制类型转换为接口类型 Person。

在上面的例子中,我们在 main 函数中尝试将 Student 实例类型转换为 Person,如果 Student 没有完全实现 Person 的方法,比如我们将 (*Student).getName() 删掉,编译时会出现如下报错信息。

*Student does not implement Person (missing getName method)

但是删除 (*Worker).getName() 程序并不会报错,因为我们并没有在 main 函数中使用。这种情况下我们如何确保某个类型实现了某个接口的所有方法呢?一般可以使用下面的方法进行检测,如果实现不完整,编译期将会报错。

var _ Person = (*Student)(nil)
var _ Person = (*Worker)(nil)
  • 将空值 nil 转换为 *Student 类型,再转换为 Person 接口,如果转换失败,说明 Student 并没有实现 Person 接口的所有方法。
  • Worker 同上。

实例可以强制类型转换为接口,接口也可以强制类型转换为实例。

func main() {
	var p Person = &Student{
		name: "Tom",
		age:  18,
	}

	stu := p.(*Student) // 接口转为实例
	fmt.Println(stu.getAge())
}

7.3 空接口

如果定义了一个没有任何方法的空接口,那么这个接口可以表示任意类型。例如

func main() {
	m := make(map[string]interface{})
	m["name"] = "Tom"
	m["age"] = 18
	m["scores"] = [3]int{98, 99, 85}
	fmt.Println(m) // map[age:18 name:Tom scores:[98 99 85]]
}

8 并发编程

8.1 sync

Go 语言提供了 sync 和 channel 两种方式支持协程(goroutine)的并发。

例如我们希望并发下载 N 个资源,多个并发协程之间不需要通信,那么就可以使用 sync.WaitGroup,等待所有并发协程执行结束。

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func download(url string) {
	fmt.Println("start to download", url)
	time.Sleep(3 * time.Second) // 模拟耗时操作
	wg.Done()
}

func main() {
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go download("a.com/" + string(i+'0'))
	}
	wg.Wait()
	fmt.Println("Done!")
}

  • wg.Add(1):为 wg 添加一个计数,wg.Done(),减去一个计数。
  • go download():启动新的协程并发执行 download 函数。
  • wg.Wait():等待所有的协程执行结束。

在这里插入图片描述

可以看到串行需要 3s 的下载操作,并发后,只需要 1s。

8.2 channel

var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道

func download(url string) {
	fmt.Println("start to download", url)
	time.Sleep(time.Second)
	ch <- url // 将 url 发送给信道
}

func main() {
	for i := 0; i < 3; i++ {
		go download("a.com/" + string(i+'0'))
	}
	for i := 0; i < 3; i++ {
		msg := <-ch // 等待信道返回消息。
		fmt.Println("finish", msg)
	}
	fmt.Println("Done!")
}

9 单元测试(unit test)

假设我们希望测试 package main 下 calc.go 中的函数,要只需要新建 calc_test.go 文件,在calc_test.go中新建测试用例即可。

// calc.go
package main

func add(num1 int, num2 int) int {
	return num1 + num2
}
// calc_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
	if ans := add(1, 2); ans != 3 {
		t.Error("add(1, 2) should be equal to 3")
	}
}

运行 go test,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v参数。

$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      example 0.040s

10 包(Package)和模块(Modules)

10.1 Package

一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。

比如我们新建一个文件 calc.gomain.go 平级,分别定义 add 和 main 方法

// calc.go
package main

func add(num1 int, num2 int) int {
	return num1 + num2
}
// main.go
package main

import "fmt"

func main() {
	fmt.Println(add(3, 5)) // 8
}

运行 go run main.go,会报错,add 未定义:

./main.go:6:14: undefined: add

$ go run .
8

Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。

10.2 Modules

Go Modules 是 Go 1.11 版本之后引入的,Go 1.11 之前使用 $GOPATH 机制。Go Modules 可以算作是较为完善的包管理工具。同时支持代理,国内也能享受高速的第三方包镜像服务。接下来简单介绍 go mod 的使用。Go Modules 在 1.13 版本仍是可选使用的,环境变量 GO111MODULE 的值默认为 AUTO,强制使用 Go Modules 进行依赖管理,可以将 GO111MODULE 设置为 ON。

在一个空文件夹下,初始化一个 Module

$ go mod init example
go: creating new go.mod: module example

此时,在当前文件夹下生成了go.mod,这个文件记录当前模块的模块名以及所有依赖包的版本。

接着,我们在当前目录下新建文件 main.go,添加如下代码:

package main

import (
	"fmt"

	"rsc.io/quote"
)

func main() {
	fmt.Println(quote.Hello())  // Ahoy, world!
}

运行 go run .,将会自动触发第三方包 rsc.io/quote的下载,具体的版本信息也记录在了go.mod中:

module example

go 1.13

require rsc.io/quote v3.1.0+incompatible

我们在当前目录,添加一个子 package calc,代码目录如下:

demo/
   |--calc/
      |--calc.go
   |--main.go

calc.go 中写入

package calc

func Add(num1 int, num2 int) int {
	return num1 + num2
}

在 package main 中如何使用 package cal 中的 Add 函数呢?import 模块名/子目录名 即可,修改后的 main 函数如下:

package main

import (
	"fmt"
	"example/calc"
	"rsc.io/quote"
)

func main() {
	fmt.Println(quote.Hello())
	fmt.Println(calc.Add(10, 3))
}
$ go run .
Ahoy, world!
13


func add(num1 int, num2 int) int {
	return num1 + num2
}
// calc_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
	if ans := add(1, 2); ans != 3 {
		t.Error("add(1, 2) should be equal to 3")
	}
}

运行 go test,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v参数。

$ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      example 0.040s

10 包(Package)和模块(Modules)

10.1 Package

一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。

比如我们新建一个文件 calc.gomain.go 平级,分别定义 add 和 main 方法

// calc.go
package main

func add(num1 int, num2 int) int {
	return num1 + num2
}
// main.go
package main

import "fmt"

func main() {
	fmt.Println(add(3, 5)) // 8
}

运行 go run main.go,会报错,add 未定义:

./main.go:6:14: undefined: add

$ go run .
8

Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。

10.2 Modules

Go Modules 是 Go 1.11 版本之后引入的,Go 1.11 之前使用 $GOPATH 机制。Go Modules 可以算作是较为完善的包管理工具。同时支持代理,国内也能享受高速的第三方包镜像服务。接下来简单介绍 go mod 的使用。Go Modules 在 1.13 版本仍是可选使用的,环境变量 GO111MODULE 的值默认为 AUTO,强制使用 Go Modules 进行依赖管理,可以将 GO111MODULE 设置为 ON。

在一个空文件夹下,初始化一个 Module

$ go mod init example
go: creating new go.mod: module example

此时,在当前文件夹下生成了go.mod,这个文件记录当前模块的模块名以及所有依赖包的版本。

接着,我们在当前目录下新建文件 main.go,添加如下代码:

package main

import (
	"fmt"

	"rsc.io/quote"
)

func main() {
	fmt.Println(quote.Hello())  // Ahoy, world!
}

运行 go run .,将会自动触发第三方包 rsc.io/quote的下载,具体的版本信息也记录在了go.mod中:

module example

go 1.13

require rsc.io/quote v3.1.0+incompatible

我们在当前目录,添加一个子 package calc,代码目录如下:

demo/
   |--calc/
      |--calc.go
   |--main.go

calc.go 中写入

package calc

func Add(num1 int, num2 int) int {
	return num1 + num2
}

在 package main 中如何使用 package cal 中的 Add 函数呢?import 模块名/子目录名 即可,修改后的 main 函数如下:

package main

import (
	"fmt"
	"example/calc"
	"rsc.io/quote"
)

func main() {
	fmt.Println(quote.Hello())
	fmt.Println(calc.Add(10, 3))
}
$ go run .
Ahoy, world!
13
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第1章 5个例子 1 1.1 开始 1 1.2 编辑、编译和运行 3 1.3 Hello Who? 6 1.4 大数字——二维切片 8 1.5 栈——自定义类型及其方法 12 1.6 americanise示例——文件、映射和闭包 18 1.7 从极坐标到笛卡儿坐标——并发 28 1.8 练习 33 第2章 布尔与数值类型 35 2.1 基础 35 2.2 布尔值和布尔表达式 39 2.3 数值类型 40 2.3.1 整型 42 2.3.2 浮点类型 46 2.4 例子:statistics 53 2.4.1 实现一个简单的统计函数 54 2.4.2 实现一个基本的HTTP服务器 55 2.5 练习 58 第3章 字符串 60 3.1 字面量、操作符和转义 61 3.2 比较字符串 63 3.3 字符和字符串 65 3.4 字符串索引与切片 67 3.5 使用fmt包来格式化字符串 69 3.5.1 格式化布尔值 73 3.5.2 格式化整数 74 3.5.3 格式化字符 75 3.5.4 格式化浮点数 75 3.5.5 格式化字符串和切片 76 3.5.6 为调试格式化 78 3.6 其他字符处理相关的包 80 3.6.1 strings包 81 3.6.2 strconv包 86 3.6.3 utf8包 90 3.6.4 unicode包 91 3.6.5 regexp包 92 3.7 例子:m3u2pls 101 3.8 练习 106 第4章 集合类型 108 4.1 值、指针和引用类型 108 4.2 数组和切片 115 4.2.1 索引与分割切片 119 4.2.2 遍历切片 119 4.2.3 修改切片 121 4.2.4 排序和搜索切片 125 4.3 映射 128 4.3.1 创建和填充映射 129 4.3.2 映射查询 131 4.3.3 修改映射 132 4.3.4 键序遍历映射 132 4.3.5 映射反转 133 4.4 例子 134 4.4.1 猜测分隔符 134 4.4.2 词频统计 136 4.5 练习 141 第5章 过程式编程 144 5.1 语句基础 144 5.1.1 类型转换 147 5.1.2 类型断言 148 5.2 分支 149 5.2.1 if语句 150 5.2.2 switch语句 151 5.3 for循环语句 158 5.4 通信和并发语句 160 5.5 defer、panic和recover 166 5.6 自定义函数 171 5.6.1 函数参数 172 5.6.2 init()函数和main()函数 175 5.6.3 闭包 176 5.6.4 递归函数 178 5.6.5 运行时选择函数 181 5.6.6 泛型函数 183 5.6.7 高阶函数 187 5.7 例子:缩进排序 192 5.8 练习 197 第6章 面向对象编程 199 6.1 几个关键概念 199 6.2 自定义类型 201 6.2.1 添加方法 203 6.2.2 验证类型 207 6.3 接口 209 6.4 结构体 217 6.5 例子 224 6.5.1 FuzzyBool——一个单值自定义类型 224 6.5.2 Shapes——一系列自定义类型 229 6.5.3 有序映射——一个通用的集合类型 240 6.6 练习 248 第7章 并发编程 251 7.1 关键概念 252 7.2 例子 256 7.2.1 过滤器 256 7.2.2 并发的Grep 260 7.2.3 线程安全的映射 266 7.2.4 Apache报告 271 7.2.5 查找副本 278 7.3 练习 285 第8章 文件处理 287 8.1 自定义数据文件 287 8.1.1 处理JSON文件 290 8.1.2 处理XML文件 295 8.1.3 处理纯文本文件 301 8.1.4 处理Go语言二进制文件 307 8.1.5 处理自定义的二进制文件 309 8.2 归档文件 317 8.2.1 创建zip归档文件 317 8.2.2 创建可压缩的tar包 319 8.2.3 解开zip归档文件 321 8.2.4 解开tar归档文件 322 8.3 练习 324 第9章 包 326 9.1 自定义包 326 9.1.1 创建自定义的包 327 9.1.2 导入包 333 9.2 第三方包 334 9.3 Go命令行工具简介 335 9.4 Go标准库简介 336 9.4.1 归档和压缩包 336 9.4.2 字节流和字符串相关的包 336 9.4.3 容器包 337 9.4.4 文件和操作系统相关的包 339 9.4.5 图像处理相关的包 341 9.4.6 数学处理包 341 9.4.7 其他一些包 341 9.4.8 网络包 342 9.4.9 反射包 343 9.5 练习 346 附录A 后记 348 附录B 软件专利的危害 350 附录C 精选书目 353

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值