《365天深入理解Go语言》Deep understanding of Golang.
本书籍是记录自己在学习Go语言的过程中遇到的问题与思考。写作过程中大量参考借鉴甚至是复制了其他类似的项目。感谢每一个开源项目,致敬每一位Gopher!尽可能的熟练使用Go语言,尽可能的深入理解Go语言。努力成为Go语言特长型程序员。学习Go语言,面向信仰编程! 作者:0e0w。Less is More or Less is Less.
本项目创建于2020年9月1日,最近的一次更新时间为2022年11月8日。本项目会持续更新,直到海枯石烂。
项目暂时计划共七章。项目未完成,持续更新整理中!今天你学习Go语言了吗?
基础不牢地动山摇,此理论不仅适用于建筑行业,在学习Go语言中更是这样!最基础的内容,也需要反复学习,多次试验,刻意练习,之后直至达到深入理解的境界!
本节说明:本节介绍Go语言的历史与发展。
程序语言概述:程序语言是用来定义计算机程序的形式语言。它是一种被标准化的交流技巧,用来向计算机发出指令,一种能够让程序员准确地定义计算机所需要使用数据的计算机语言,并精确地定义在不同情况下所应当采取的行动。最早的编程语言是在电脑发明之前产生的,当时是用来控制提花织布机及自动演奏钢琴的动作。在电脑领域已发明了上千不同的编程语言,而且每年仍有新的编程语言诞生。
Go语言概述:Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。通过Go语言可以容易的构造出简单、可靠且高效的软件。 Go语言本身也是开源的,编译器、库和工具的源代码都可以免费获得。
Go语言特点:Go语言和其他语言相比的优势是什么?
Go语言资源:有大量的教程和代码想项目案例。
Go语言安装:官网下载后,直接按照安装说明安装即可。
Linux:在Ubuntu虚拟机里面开发使用Go语言:
wget https://golang.google.cn/dl/go1.18.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
vi ~/.bashrc
export PATH=/usr/local/go/bin:$PATH
source .bashrc
Windows:https://golang.google.cn/dl/go1.18.windows-amd64.msi
https://golang.google.cn/dl/go1.18.windows-amd64.msi
macOS
https://golang.google.cn/dl/go1.18.darwin-amd64.pkg
Go环境变量:
设置GOPATH:
mkdir ~/go
echo "GOPATH=$HOME/go" >> ~/.bashrc
echo "export GOPATH" >> ~/.bashrc
echo "PATH=\$PATH:\$GOPATH/bin # Add GOPATH/bin to PATH for scripting" >> ~/.bashrc
source ~/.bashrc
设置Go env
go env -w GO111MODULE=off
Go语言编辑器:
Go语言命令:
运行go程序:
go run main.go
打包成可执行程序:
// 直接编译
go build main.go
//去掉符号表去掉调试信息
go build -ldflags "-w -s" main.go
go build -ldflags="-s -w" main.go
//实现无窗口运行
go build -ldflags "-w -s -H windowsgui" main.go
生成不同平台下的可执行程序:需配置GOPATH。
// Linux下编译Mac, Windows平台的64位可执行程序:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
// Linux下编译Mac, Windows平台的32位可执行程序:
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=386 go build main.go
// Windows下编译Mac, Linux平台的64位可执行程序:将下面代码保存为bat格式执行即可。
@echo off
go build -ldflags "-w -s" Banli.go
@echo off
SET CGO_ENABLED=0
SET GOOS=windows
SET GOARCH=386
go build -o Banli32.exe -ldflags "-w -s" Banli.go
@echo off
SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build -o Banli_linux -ldflags "-w -s" Banli.go
@echo off
SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build -o Banli_darwin -ldflags "-w -s" Banli.go
// Mac下编译Linux, Windows平台的64位可执行程序:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
安装依赖:
go mod tidy
go mod download
go get -u github.com/0e0w/365GoLang
格式化Go代码:
gofmt
查看Go环境配置:
go env
第三方包进行部署:
go install
其他的go命令:
bug start a bug report
build compile packages and dependencies
clean remove object files and cached files
doc show documentation for package or symbol
env print Go environment information
fix update packages to use new APIs
fmt gofmt (reformat) package sources
generate generate Go files by processing source
get add dependencies to current module and install them
install compile and install packages and dependencies
list list packages or modules
mod module maintenance
run compile and run Go program
test test packages
tool run specified go tool
version print Go version
vet report likely mistakes in packages
从GOPATH/src目录开始引入包,需关闭go mod模式:
export GO111MODULE=off
Go语言代理:
Go语言大量项目托管在Github,导致国内IP进行构建程序时会很慢,可使用下列的代理加快构建。
https://mirrors.aliyun.com/goproxy/
https://goproxy.io/zh/
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
一个例子:Hello World!
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
在完成包的 import 之后,开始对常量、变量和类型的定义或声明。
引入包:使用import圆括号进行引入包。
Go语言名称:
Go语言关键字:关键字是一些特殊的用来帮助编译器理解和解析源代码的单词。Go语言中有25个关键字或保留字。关键字只能用在程序语法允许的地方,不能作为名称使用。
预定义标识符:三十几个内置的预申明的常量、类型和函数。所有的类型名、变量名、常量名、跳转标签、包名和包的引入名都必须是标识符。
Go语言声明:声明是给一个程序实体命名,并设定其部分或全部属性。
有4个主要的声明:变量(var)、常量(const)、类型(type)、函数(func)
函数的声明包含一个名字、一个参数列表、一个可选的返回值列表以及函数体。
Go编码规范:https://golang.org/ref/spec
可见性规则:
命名规范:
语法惯例:
注释:尽管高级编程语言代码比底层机器指令友好和易懂,我们还是需要一些注释来帮助自己和其他程序员理解我们所写的代码。
本节说明:本节介绍Go语言变量的相关内容。
程序语言的变量:变量(英语:Variable,scalar)是指一个包含部分已知或未知数值或信息(即一个值)的存储地址,以及相对应之符号名称(识别字)。通常使用变量名称引用存储值;将名称和内容分开能让被使用的名称独立于所表示的精确消息之外。电脑源代码中的识别字能在执行期间绑扎一个值,且该变量的值可能在程序执行期间改变。 程序设计中的变量不一定能直接对应到数学中所谓的变量之概念。在程序设计中,变量的值不一定要为方程或数学公式之一部分。程序设计中的变量可使用在一段可重复的程序:在一处赋值,然后使用于另一处,接着在一次赋值,且以相同方式再使用一次(见迭代)。程序设计中的变量通常会给定一个较长的名称,以描述其用途;数学中的变量通常较为简洁,只给定一、两个字母,以方便抄写及操作。 一个变量的存储地址可以被不同的识别字所引用,这种情况称之为别名。使用其中一个识别字为变量赋值,将会改变透过另一个识别字访问的值。
Go语言变量概述:
Go语言标准变量声明:
使用var声明,声明之后需要进行初始化。未初始化时是对应数据的零值。
Go语言变量的命名规则遵循骆驼命名法。
下列的因式分解关键字的写法一般用于声明全局变量。
var (
a int
b bool
str string
浮点 float32 // 中文可以作为变量标识符
)
// 变量a,b都是指针类型
var a, b *int
声明变量之后,变量会自动初始化,初始值对应类型的零值。当一个变量被var声明之后,系统自动赋予它该类型的零值。所有的内存在 Go 中都是经过初始化的。数字类型对应的是0、布尔类型对应的是flase、字符串类型对应的是""、接口和引用类型对应的是nil。
var name type = expression
var i, j, k int
var b, f, s, = true, 2.3, "four"
Go语言短变量声明:
在函数内部声明变量可以使用短变量声明方式。
变量初始化时省略变量的类型会由系统自动推断。短变量声明时会同时进行初始化。
短变量声明是变量声明的首选形式,但是只能用在函数体内,不可以用于全局变量的声明与赋值。
全局变量和简式声明的变量尽量不要同名,否则很容易产生偶然的变量隐藏Accidental Variable Shadowing。
使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。name := expression
a := 5
a, b, c := 5, 7, "abc"
Go语言变量赋值:
多变量可以在同一行进行赋值,也称为并行或同时或平行赋值。
a, b, c = 5, 7, "abc"
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:
val, err = Func1(var1)
Go语言空白标识符 _ :
Go语言零值nil:
变量参考:
本节案例:
本节说明:本节介绍Go语言常量的内容。
程序语言的常量:
Go语言常量概述:
Go语言常量定义:
常量的定义格式:const identifier [type] = value,例如:
const Pi = 3.14159
Go语言常量定义可以指定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它与字面常量一样,是无类型(untyped)的常量。
一个未指定类型的常量被使用时,会根据其使用环境而推断出它所需要具备的类型。
显式类型定义:const b string = "abc"
隐式类型定义:const b = "abc"
常量也可以在单行进行多重赋值:
const a, b, c = 1, false, "str" //多重赋值
Go语言 特殊常量:iota
iota 在 const关键字出现时将被重置为 0,const 中每新增一行常量声明将使 iota 计数一次。
iota 可理解为 const 语句块中的行索引。
iota 可以被用作枚举值。第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1。
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
a = iota
b
c
)
如果对b重新赋值之后,a, b, c分别为0, 8, 8,新的常量b声明后,iota 不再向下赋值,后面常量如果没有赋值,则继承上一个常量值。
const (
a = iota
b = 8
c
)
使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:
type ByteSize float64
const (
_ = iota // 通过赋值给空白标识符来忽略值
KB ByteSize = 1<<(10*iota)
MB
GB
TB
)
本节说明:本节介绍Go语言的基本数据类型。
程序语言的数据类型:
Go语言基本数据类型概述:
在Go语言中共有N种,分别是:布尔、数值、字符串、
在Go语言中,数据类型用于声明函数和变量。
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
在大多数高级编程语言中,数据通常被抽象为各种类型(type)和值(value)。
一个类型可以看作是值的模板。一个值可以看作是某个类型的实例。
大多数编程语言支持自定 义类型和若干预定义类型(即内置类型)。
一门语言的类型系统是这门语言的灵魂。Go语言拥有独特的灵魂。
一、布尔类型:
布尔型的值只可以是常量 true 或者 false。布尔类型的初值是false。
两个类型相同的值可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。
非运算符:!T -> false、!F -> true
和运算符:T && T -> true、T && F -> false、F && T -> false、F && F -> false
或运算符:T || T -> true、T || F -> true、F || T -> true、F || F -> false
二、数值类型:
Go语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。整型 int 和浮点型 float32、float64。
Go语言中没有 float 类型。Go语言中只有 float32 和 float64。没有double类型。
整型的零值为 0,浮点型的零值为 0.0。
整数:rune是int32的内置别名。
int8(-128 -> 127)
int16(-32768 -> 32767)
int32(-2,147,483,648 -> 2,147,483,647)
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:byte是uint8的内置别名。
uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
float32(+- 1e-45 -> +- 3.4 * 1e38)
float64(+- 5 * 1e-324 -> 107 * 1e308)
字节型:byte 型,也就是uint8类型。代表了ASCII码的一个字符。
vat bt byte
bt = 'a'
fmt.Println(bt)
//打印97
三、字符串类型:
从逻辑上说,一个字符串值表示一段文本。 在内存中,一个字符串存储为一个字节 (byte)序列。
Go语言中可以使用反引号或者双引号来定义字符串。反引号表示原生的字符串,不进行转义。
Go语言中的string类型是一种值类型,存储的字符串是不可变的,如果要修改string内容需要将string转换为[]byte或[]rune,并且修改后的string内容是重新分配的。
Go语言中的string类型是一种值类型,存储的字符串是不可变的,如果要修改string内容需要将string转换为[]byte或[]rune,并且修改后的string内容是重新分配的。
字符串是字节的定长数组。字符串的零值是为长度为零的字符串,即空字符串 ""。
一般的比较运算符(==、!=、<、<=、>=、>)通过在内存中按字节比较来实现字符串的对比。可以通过函数 len() 来获取字符串所占的字节长度,例如:len(str)。
在Go中,字符串值是UTF-8编码的, 甚至所有的Go源代码都必须是UTF-8编码的。
字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数。
解释字符串:该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\\:反斜杠自身
非解释字符串:
`This is a raw string \n` 中的 `\n\` 会被原样输出。
字符串拼接:使用运算符+、fmt.Sprintf()、strings.Join()、bytes.Buffer、strings.Builder
标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。
格式化输出:fmt.Printf()
%d //整型格式
%c //字符型
%s //字符串格式
%v //自动匹配类型
%T //数据类型
数据类型转换:int(a)
组合类型分类:
// 假设T为任意一个类型,Tkey为一个支持比较的类型。
*T // 一个指针类型
[5]T // 一个元素类型为T、元素个数为5的数组类型
[]T // 一个元素类型为T的切片类型
map[Tkey]T // 一个键值类型为Tkey、元素类型为T的映射类型
// 一个结构体类型
struct {
name string
age int
}
// 一个函数类型
func(int) (bool, string)
// 一个接口类型
interface {
Method0(string) int
Method1() (int, bool)
}
// 几个通道类型
chan T
chan<- T
<-chan T
类型的种类:
每种上面提到的基本类型和组合类型都对应着一个类型种类(kind)。除了这些种类,今后将要介绍的非类型安全指针类型属于另外一个新的类型种类。
所以,目前(Go 1.15),Go有26个类型种类。
语法-类型定义:
在Go语言中,我们可以用type关键字来定义新的类型。
一些类型定义的例子:
// 下面这些新定义的类型和它们的源类型都是基本类型。
type (
MyInt int
Age int
Text string
)
// 下面这些新定义的类型和它们的源类型都是组合类型。
type IntPtr *int
type Book struct{author, title string; pages int}
type Convert func(in0 int, in1 bool)(out0 int, out1 string)
type StringArray [5]string
type StringSlice []string
func f() {
// 这三个新定义的类型名称只能在此函数内使用。
type PersonAge map[string]int
type MessageQueue chan string
type Reader interface{Read([]byte) int}
}
类型别名声明:
给一个类型另取一个别名:
type bigint int64
var a bigint
type (
Name = string
Age = int
)
type table = map[string]int
type Table = map[Name]Age
定义类型和非定义类型:
有名类型和无名类型:
底层类型:
在Go语言中,每个类型都有一个底层类型。
一个内置类型的底层类型为它自己。
unsafe标准库包中定义的Pointer类型的底层类型是它自己。
一个非定义类型(必为一个组合类型)的底层类型为它自己。
在一个类型声明中,新声明的类型和源类型共享底层类型。
一个例子:
// 这四个类型的底层类型均为内置类型int。
type (
MyInt int
Age MyInt
)
// 下面这三个新声明的类型的底层类型各不相同。
type (
IntSlice []int // 底层类型为[]int
MyIntSlice []MyInt // 底层类型为[]MyInt
AgeSlice []Age // 底层类型为[]Age
)
// 类型[]Age、Ages和AgeSlice的底层类型均为[]Age。
type Ages AgeSlice
值、值部、值尺寸
本节案例:
本节说明:本节介绍Go语言指针(pointer)的相关内容。
程序语言的指针:
Go语言指针概述:
Go语言指针操作:
一、获取指针值:
二、指针引用:
内存地址介绍:
指针类型和值:
在Go语言中,一个非定义指针类型的字面形式为*T,其中T为一个任意类型。
类型T称为指针类型*T的基类型(base type)。 如果一个指针类型的基类型为T,则我们可以称此指针类型为一个T指针类型。
虽然我们可以声明定义指针类型,但是一般不推荐这么做。非定义指针类型的可读性更高。
如果一个指针类型的底层类型是*T,则它的基类型为T。
如果两个非定义指针类型的基类型为同一类型,则这两个非定义指针类型亦为同一类型。
如果一个指针类型的基类型为T,则此指针类型的值只能存储类型为T的值的地址。
一个值的地址是指此值的直接部分占据的内存的起始地址。
一些指针类型的例子:
*int // 一个基类型为int的非定义指针类型。
**int // 一个多级非定义指针类型,它的基类型为*int。
type Ptr *int // Ptr是一个定义的指针类型,它的基类型为int。
type PP *Ptr // PP是一个定义的多级指针类型,它的基类型为Ptr。
指针类型的零值的字面量使用预声明的nil来表示。一个nil指针(常称为空指针)中不存储任何地址。
指针解引用:
package main
import "fmt"
func main() {
p0 := new(int) // p0指向一个int类型的零值
fmt.Println(p0) // (打印出一个十六进制形式的地址)
fmt.Println(*p0) // 0
x := *p0 // x是p0所引用的值的一个复制。
p1, p2 := &x, &x // p1和p2中都存储着x的地址。
// x、*p1和*p2表示着同一个int值。
fmt.Println(p1 == p2) // true
fmt.Println(p0 == p1) // false
p3 := &*p0 // <=> p3 := &(*p0)
// <=> p3 := p0
// p3和p0中存储的地址是一样的。
fmt.Println(p0 == p3) // true
*p0, *p1 = 123, 789
fmt.Println(*p2, x, *p3) // 789 789 123
fmt.Printf("%T, %T \n", *p0, x) // int, int
fmt.Printf("%T, %T \n", p0, p1) // *int, *int
}
Go指针的一些限制:
本节说明:本节介绍Go语言数组(array)的相关内容。
程序语言的数组:
Go语言数组概述:
Go语言数组声明:
Go语言数组声明需要指定元素类型及元素个数。语法格式如下:
var 数组变量名 [元素数量]Type
var a [3]int // 定义三个整数的数组
Go 语言中的数组是一种值类型,所以可以通过 new() 来创建:
var arr1 = new([5]int)
初始化数组:
访问数组元素:
数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。
var team [3]string
team[0] = "hammer"
team[1] = "soldier"
team[2] = "mum"
for k, v := range team {
fmt.Println(k, v)
}
多维数组:
数数组通常是一维的,但是可以用来组装成多维数组例如:
[3][5]int
[2][2][2]float64
将数组传递给函数:
把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象:
1、传递数组的指针
2、使用数组的切片
本节案例:
数组参考:
本节说明:本节介绍Go语言切片(slice)的相关内容。
程序语言的切片:
Go语言切片概述:
Go语言切片声明:
切片有俩种定义方式:[]type、make()
var 切片变量名 []type // 声明一个未指定大小的数组来定义切片
var silice = []int{2,4,6}
var silice = []string{"aaa","bbb","ccc"}
var slice1 []type = make([]type, len,cap)// 使用make()函数来创建切片
将切片传递给函数:函数的参数为切片
func sum(a []int) int {
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}
return s
}
func main() {
var arr = [5]int{0, 1, 2, 3, 4}
sum(arr[:])
}
Go语言切片重组:
切片初始化:
空(nil)切片:
切片参考:参考1:Go Slice全面指南、切片参考2
本节案例:
本节说明:本节介绍集合Go语言映射(Map)的相关内容。
程序语言的映射
Go语言映射概述:
Go语言申明映射:
map是引用类型,可以使用如下声明:
var map1 map[keytype]valuetype
var map1 map[string]int
map1 = map[string]int{"one": 1, "two": 2}
可以使用内建函数 make 也可以使用 map 关键字来定义 Map。
// 声明变量,默认 map 是 nil
var map_variable map[key_data_type]value_data_type
// 使用 make 函数
map_variable := make(map[key_data_type]value_data_type)
var m map[string]int
// 声明但未初始化map,此时是map的零值状态
map1 := make(map[string]string, 5)
map2 := make(map[string]string)
// 创建了初始化了一个空的的map,这个时候没有任何元素
map3 := map[string]string{}
// map中有三个值
map4 := map[string]string{"a": "1", "b": "2", "c": "3"}
一个示例:
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"])
}
for-range与map
// 使用 for 循环构造 map
for key, value := range map1 {
...
}
// 只获取值
for _, value := range map1 {
...
}
// 只获取 key
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}
Go语言容器:
非定义容器类型的字面表示:
数组类型:[N]T
切片类型:[]T
映射类型:map[K]T
说明:T可为任意类型,表示一个容器类型的元素类型。N必须为非负整数常量。K必须为一个可比较类型,K指定了映射类型的键值类型。
一些例子:
const Size = 32
type Person struct {
name string
age int
}
// 数组类型
[5]string
[Size]int
[16][]byte // 元素类型为一个切片类型:[]byte
[100]Person // 元素类型为一个结构体类型:Person
// 切片类型
[]bool
[]int64
[]map[int]bool // 元素类型为一个映射类型:map[int]bool
[]*int // 元素类型为一个指针类型:*int
// 映射类型
map[string]int
map[int]bool
map[int16][6]string // 元素类型为一个数组类型:[6]string
map[bool][]string // 元素类型为一个切片类型:[]string
map[struct{x int}]*int8 // 元素类型为一个指针类型:*int8;
// 键值类型为一个结构体类型。
查看容器值的长度和容量:
读取和修改容器元素:
添加和删除容器元素:
使用内置make函数来创建切片和映射:
使用内置new函数来创建容器值:
从数组或者切片派生切片(取子切片):
使用内置copy函数来复制切片元素:
遍历容器元素:
一个例子:
for key, element = range aContainer {
// 使用key和element ...
}
把数组指针当做数组来使用:
单独修改一个切片的长度或者容量:
容器参考:容器参考链接1
本节说明:本节介绍Go语言结构体(struct)的相关内容。
程序语言的结构体:
Go语言结构体概述:
Go语言声明结构体:
结构体定义需要使用 type 和 struct 语句。结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
struct {
title, author string
pages int
X, Y bool
}
type T struct {a, b int}
也是合法的语法,它更适用于简单的结构体。
一个空结构体:struct {}
一旦定义了结构体类型,它就能用于变量的声明。
结构体的字段可以是任何类型,甚至是结构体本身,也可以是函数或者接口。可以声明结构体类型的一个变量,然后像下面这样给它的字段赋值:
var s T
s.a = 5
s.b = 8
一些结构体例子:
package main
import "fmt"
type struct1 struct {
i1 int
f1 float32
str string
}
func main() {
ms := new(struct1)
ms.i1 = 10
ms.f1 = 15.5
ms.str= "Chris"
fmt.Printf("The int is: %d\n", ms.i1)
fmt.Printf("The float is: %f\n", ms.f1)
fmt.Printf("The string is: %s\n", ms.str)
fmt.Println(ms)
}
package main
import (
"fmt"
)
type Book struct {
title, author string
pages int
}
func main() {
book := Book{"Go语言笔记", "0e0w", 365}
fmt.Println(book)
// 使用带字段名的组合字面量来表示结构体值。
book = Book{author: "0e0w", pages: 365, title: "Go语言笔记"}
book = Book{}
book = Book{author: "0e0w"}
// 使用选择器来访问和修改字段值。
var book2 Book // <=> book2 := Book{}
book2.author = "Tapir"
book2.pages = 300
fmt.Println(book.pages) // 300
}
func f() {
book1 := Book{pages: 300}
book2 := Book{"Go语言笔记", "0e0w", 365}
book2 = book1
// 上面这行和下面这三行是等价的。
book2.title = book1.title
book2.author = book1.author
book2.pages = book1.pages
}
访问结构体成员:
结构体.成员名
结构体特性:
本节案例:
package main
import (
"fmt"
)
type Human struct {
name string // 姓名
Gender string // 性别
Age int // 年龄
string // 匿名字段
}
type Student struct {
Human // 匿名字段
Room int // 教室
int // 匿名字段
}
func main() {
//使用new方式
stu := new(Student)
stu.Room = 102
stu.Human.name = "Titan"
stu.Gender = "男"
stu.Human.Age = 14
stu.Human.string = "Student"
fmt.Println("stu is:", stu)
fmt.Printf("Student.Room is: %d\n", stu.Room)
fmt.Printf("Student.int is: %d\n", stu.int) // 初始化时已自动给予零值:0
fmt.Printf("Student.Human.name is: %s\n", stu.name) // (*stu).name
fmt.Printf("Student.Human.Gender is: %s\n", stu.Gender)
fmt.Printf("Student.Human.Age is: %d\n", stu.Age)
fmt.Printf("Student.Human.string is: %s\n", stu.string)
// 使用结构体字面量赋值
stud := Student{Room: 102, Human: Human{"Hawking", "男", 14, "Monitor"}}
fmt.Println("stud is:", stud)
fmt.Printf("Student.Room is: %d\n", stud.Room)
fmt.Printf("Student.int is: %d\n", stud.int) // 初始化时已自动给予零值:0
fmt.Printf("Student.Human.name is: %s\n", stud.Human.name)
fmt.Printf("Student.Human.Gender is: %s\n", stud.Human.Gender)
fmt.Printf("Student.Human.Age is: %d\n", stud.Human.Age)
fmt.Printf("Student.Human.string is: %s\n", stud.Human.string)
}
本节说明:本节介绍表达式、语句和简单语句的相关内容。
程序语言的语法:
Go语言语法概述:
简单语句类型:
非简单语句类型:
三种基本的流程控制:
特定类型相关的流程控制代码块:
本节案例
本节说明:本节介绍Go运算符相关内容。
数学中的运算符:
程序语言的运算符号:
Go语言运算符:
Go语言运算符-算术运算符:
算术运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
-- | 自减 | A-- 输出结果 9 |
Go语言运算符-比较运算符:
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
Go语言运算符-逻辑运算符:
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑与 | (A && B) 为 False |
|| | 逻辑或 | (A || B) 为 True |
! | 逻辑非 | !(A && B) 为 True |
Go语言运算符-位运算符:
位运算符对整数在内存中的二进制位进行操作。 下表列出了位运算符 &,|,和 ^ 的计算:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
运算符 | 描述 | 实例 |
---|---|---|
& | 其功能是参与运算的两数各对应的二进位相与。 | (A & B) 结果为 12, 二进制为 0000 1100 |
| | 其功能是参与运算的两数各对应的二进位相或 | (A | B) 结果为 61, 二进制为 0011 1101 |
^ | 二进位相异或,两对应的二进位相异时结果为1 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | A << 2 结果为 240 ,二进制为 1111 0000 | |
>> | A >> 2 结果为 15 ,二进制 |
Go语言运算符-赋值运算符:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C *= A 等于 C = C * A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
Go语言运算符-波浪运算符:
package main
import "log"
type customString string
func Println[T ~int | ~string](v T) {
log.Println(v)
}
func main() {
Println(customString("Hello world!"))
}
Go语言运算符-其他运算符:
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
运算符优先级:
优先级 | 运算符 |
---|---|
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
几个特殊运算符:
本节案例:
本节说明:本节介绍Go语言条件判断语句的相关内容。
程序语言的条件判断:
Go语言条件判断概述:
if语句:
if 语句用于测试某个条件(布尔型或逻辑型)的语句。由一个布尔表达式或关系运算符后紧跟一个或多个语句组成。
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
if...else 语句:
if 语句后可以使用可选的else语句, else语句中的表达式在布尔表达式为 false 时执行。
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
if 嵌套语句:
你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。
if 布尔表达式 1 {
/* 在布尔表达式 1 为 true 时执行 */
if 布尔表达式 2 {
/* 在布尔表达式 2 为 true 时执行 */
}
}
三层嵌套。
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
if条件判断语句案例:
案例1:
if a := 10; a == 10 {
fmt.Println("=10")
} else if a > 10 {
fmt.Println(">10")
} else if a < 10 {
fmt.Println("<10")
} else {
fmt.Println("这是不可能的!")
}
案例2
if a := 10; a == 10 {
fmt.Println("10")
} else {
fmt.Println("not 10")
}
案例3
a := 10 // 初始化赋值语句
if a == 10 {
fmt.Println("10")
} else {
fmt.Println("not 10")
}
switch 语句:
switch语句是存在多个条件判断的情况下,分别执行其对应的语句。
switch语句默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case。
如果我们需要执行后面的 case,可以使用 fallthrough 。
switch后面可以写变量本身。和case后面的变量进行比较之后进行执行对应语句。
switch var1 {
case val1:
...
case val2:
...
default:
...
}
任何支持进行相等判断的类型都可以作为测试表达式的条件,包括 int、string、指针等。
package main
import "fmt"
func main() {
var num1 int = 7
switch { //无条件执行
case num1 < 0:
fmt.Println("Number is negative")
case num1 > 0 && num1 < 10:
fmt.Println("Number is between 0 and 10")
default:
fmt.Println("Number is 10 or greater")
}
}
select 语句:
select 结构,用于 channel 的选择。
select 是 Go 中的一个控制结构,类似于 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
select没有条件表达式,一直在等待分支进入可运行状态。
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
注意:Go 没有三目运算符,所以不支持 ?: 形式的条件判断。
本节案例:
本节说明:本节介绍Go语言循环语句的相关内容。
程序语言的循环:
Go语言循环语句概述:
for循环:重复执行语句
Go语言中只有 for 结构可以重复执行某些语句。
for循环是一个循环控制结构,可以执行指定次数的循环。
Go语言的for循环有 3 种形式,只有其中的一种使用分号。
基于计数器的迭代:
for 初始化条件; 判断条件; 条件变化 {}
for init; condition; post { }
// init: 一般为赋值表达式,给控制变量赋初值;
// condition: 关系表达式或逻辑表达式,循环控制条件;
// post: 一般为赋值表达式,给控制变量增量或减量。
// 在循环中同时使用多个计数器:
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}
基于条件判断的迭代:
package main
import "fmt"
func main() {
var i int = 5
for i >= 0 {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
}
}
无限循环:
条件语句是可以被省略的,如 i:=0; ; i++ 或 for { } 或 for ;; { }(;; 会在使用 gofmt 时被移除):这些循环的本质就是无限循环。最后一个形式可以被改写为 for true { },但一般情况下都会直接写
for { }
for t, err = p.Token(); err == nil; t, err = p.Token() {
...
}
package main
import "fmt"
func main() {
sum := 0
for {
sum++ // 无限循环下去
}
fmt.Println(sum) // 无法输出
}
for-range 结构:
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。
在循环中可以同时使用多个计数器:
for key, value := range oldMap {
newMap[key] = value
}
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)
}
}
循环嵌套:在循环内使用循环。
使用方法:
for [condition | ( init; condition; increment ) | Range]
{
for [condition | ( init; condition; increment ) | Range]
{
statement(s);
}
statement(s);
}
使用循环嵌套来输出 2 到 100 间的素数:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var i, j int
for i=2; i < 100; i++ {
for j=2; j <= (i/j); j++ {
if(i%j==0) {
break; // 如果发现因子,则不是素数
}
}
if(j > (i/j)) {
fmt.Printf("%d 是素数\n", i);
}
}
}
循环控制语句:
break 语句:
break可用于for、switch、select语句中。
用于循环语句中跳出循环,并开始执行循环之后的语句。
break 在 switch(开关语句)中在执行一条 case 后跳出语句的作用。
在多重循环中,可以用标号 label 标出想 break 的循环。
break
for {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
if i < 0 {
break
}
}
i := 0
for { // for循环条件永真,会一直循环
if i >= 10 { // if条件判断i是否大于10
break // if大于10的话就break跳出for循环
}
i++ // i+1
g.Println(i) // 打印i
}
continue语句:
continue只能用于for循环。跳过当前循环执行下一次循环语句。
for 循环中,执行 continue 语句会触发 for 增量语句的执行。
在多重循环中,可以用标号 label 标出想 continue 的循环。
continue
package main
func main() {
for i := 0; i < 10; i++ {
if i == 5 { // 判断i等于5时跳出for循环
continue
}
print(i)
print(" ")
}
}
goto 语句:
Go语言的 goto 语句可以无条件地转移到过程中指定的行。
goto语句可以用在任何地方,但是不能跨函数使用。
goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。
但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。
goto End //goto是关键字,End是用户起的名字,叫做标签
.......
End:
fmt.Print("aaa")
package main
func main() {
i:=0
HERE:
print(i)
i++
if i==5 {
return // 跳出函数
}
goto HERE
}
return语句:
如果 return 语句使用在普通的 函数 中,则表示跳出该函数,不再执行函数中 return 后面的代码,可以理解成终止函数。
如果 return 语句使用在 main 函数中,表示终止 main 函数,也就是终止程序的运行。
本节案例:
本节说明:本节介绍Go语言函数的相关内容。
程序语言的函数:
Go语言函数概述:
Go语言声明函数:
函数声明会告诉编译器,函数的名称,返回类型和参数。
Go语言函数不支持输入参数默认值。每个返回结果的默认值是它的类型的零值。
任何一个函数都不能被声明在另一个函数体内。 虽然匿名函数可以定义在函数体内,但匿名函数定义不属于函数声明。
Go语言函数基本组成:关键字func、函数名、参数列表、返回值、函数体和返回语句。语法如下:
func 函数名(参数) 返回类型{
函数体
}
无参数无返回的函数:
func MyFunc(){
}
有参数无返回的函数:
func main() {
Myfunc("url", "t")
}
func Myfunc(aa, bb string) {
fmt.Println(aa)
fmt.Println(bb)
}
不定参数类型:不定参数只能是形参中的最后一个参数。
func Myfunc(args ...int) {
fmt.Println(aa)
fmt.Println(bb)
}
无参数有返回的函数:有返回值的函数需要通过return返回。
func MyFunc() int {
}
有参数有返回的函数:
func main() {
max, min := MaxAndMin(10, 20)
fmt.Println(max, min)
}
func MaxAndMin(a, b int) (max, min int) {
if a > b {
max = a
min = b
} else {
max = b
min = a
}
return
}
Go语言函数参数:
函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:func f(int, int, float64)。
函数如果使用参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。
没有参数的函数通常被称为niladic函数(niladic function),就像main.main()。
调用函数,可以通过两种方式来传递参数:
1、按值传递(call by value):值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
2、按引用传递(call by reference):引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
package main
import "fmt"
func main() {
fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6))
// var i1 int = MultiPly3Nums(2, 5, 6)
// fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1)
}
func MultiPly3Nums(a int, b int, c int) int {
// var product int = a * b * c
// return product
return a * b * c
}
Go语言函数返回值:
Go语言的函数通常情况下会有多个返回值。
尽量使用命名返回值:这样会使代码更清晰、更简短,更加容易读懂。
一个例子:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}
Go语言函数调用:
已经声明的函数可以通过它的名称和一个实参列表来调用。
函数的声明可以出现在它的调用之前,也可以出现在它的调用之后。
函数调用可以被延迟执行或者在另一个协程(goroutine)中执行。
当创建函数时,定义了函数需要做什么,通过调用该函数来执行指定任务。
函数调用流程:先调用的函数后返回。先进后出。
调用函数,向函数传递参数,并返回值。
pack1.Function(arg1, arg2, …, argn)
上面的代码中Function 是 pack1 包里面的一个函数,括号里的是被调用函数的实参(argument):这些值被传递给被调用函数的形参。
package main
func main() {
greeting()
}
func greeting() {
println("aa")
}
一个函数也可以作为其他函数的参数。只要这个被调用函数的返回值个数、返回值类型和返回值的顺序与调用函数所需求的实参是一致的。
函数也可以以申明的方式被使用,作为一个函数类型,就像:
type binOp func(int, int) int
这里不需要函数体 {}。
延迟函数调用:
在Go语言中,一个函数调用可以跟在一个defer关键字后面,形成一个延迟函数调用。
和协程调用类似,被延迟的函数调用的所有返回值必须全部被舍弃。
当一个函数调用被延迟后,它不会立即被执行。它将被推入由当前协程维护的一个延迟调用堆栈。
延迟调用例子:
defer fmt.Println("3")
defer fmt.Println("2")
fmt.Println("1")
// 打印 1 2 3
Go语言回调函数:
一个函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。
package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
Go语言递归函数:
Go语言匿名函数:
当我们不希望给函数起名字的时候,可以使用匿名函数,例如:func(x, y int) int { return x + y }。
关键字 defer经常配合匿名函数使用,它可以用于改变函数的命名返回值。
匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。
包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。
Go语言中的所有的自定义函数(包括声明的函数和匿名函数)都可以被视为闭包。一个闭包继承了函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁。
一个匿名函数在定义后可以被立即调用。
闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包装。
func() {
sum := 0
for i := 1; i <= 1e6; i++ {
sum += i
}
}()
函数用法:
本节案例:
本节说明:本节介绍Go语言方法相关内容。
程序语言的方法:
Go语言方法概述:
Go语言声明方法:
T
必须是一个定义类型;T
必须和此方法声明定义在同一个代码包中;T
不能是一个指针类型;T
不能是一个接口类型。Go语言方法接受者:
Go语言方法案例:
示例一:函数和方法的使用对比
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q)) // "5", method call
}
示例二:
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
}
本节说明:本节介绍Go语言接口(interface)的相关内容。
程序语言接口:
Go语言接口概述:
Go语言接口类型是对其他类型行为的概括与抽象。
Go语言的接口非常灵活,通过接口可以实现很多面向对象的特性。
Go语言接口定义了一组方法,这些方法不包含具体实现代码。它们是抽象的。接口里也不能包含变量。
Go语言中的所有类型包括自定义类型都实现了interface{}接口,所有的类型如string、 int、 int64甚至是自定义的结构体类型都拥有interface{}空接口,这一点interface{}和Java中的Object类比较相似。
一个接口可以包含一个或多个其他的接口,但是在接口内不能嵌入结构体,也不能嵌入接口自身。
空接口interface{}可以被当做任意类型的数值。
接口的定义是非常棒的,而且也是实现Go语言中多态性的唯一途径。
Go语言中的接口都很简短,通常它们会包含0个或最多3个方法。
使用接口可以使代码更具有普适性。
接口类型的未初始化变量的值为nil。
var i interface{} = 99 // i可以是任何类型
i = 44.09
i = "All" // i 可接受任意类型的赋值
Go语言接口定义:
接口是一组抽象方法的集合,它必须由其他非接口类型实现,不能自我实现。Go 语言通过它可以实现很多面向对象的特性。通过如下格式定义接口:
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
Go语言接口案例:
示例1:
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func main() {
sq1 := new(Square)
sq1.side = 2
var areaIntf Shaper
areaIntf = sq1
fmt.Printf("The square has area: %f\n", areaIntf.Area())
}
示例2:接口嵌套接口。接口File包含了ReadWrite和Lock的所有方法,它还额外有一个Close()方法。
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
接口排序:
本节说明:本节介绍Go语言并发协程(goroutine)相关内容。
程序语言并发:
Go语言协程goroutine概述:
协程goroutine用法:
协程goroutine案例:
案例一:使用延时来返回协程的返回值
func main() {
start := time.Now()
go tester()
time.Sleep(1 * time.Millisecond)
end := time.Now()
delta := end.Sub(start)
g.Println(delta)
}
func tester() {
i := 0
HERE:
g.Println(i)
i++
if i == 10 {
return
}
goto HERE
}
恐慌:一些致命性错误不属于恐慌。对于官方标准编译器来说,很多致命性错误(比如堆栈溢出和内存不足)不能被恢复。它们一旦产生,程序将崩溃。
恢复:
本节案例:
本节说明:本节介绍Go语言通道(channel)的相关内容。
Go语言通道channel概述:
通道类型和值:
chan T
表示一个元素类型为T
的双向通道类型。 编译器允许从此类型的值中接收和向此类型的值中发送数据。
2、字面形式chan<- T
表示一个元素类型为T
的单向发送通道类型。 编译器不允许从此类型的值中接收数据。
3、字面形式<-chan T
表示一个元素类型为T
的单向接收通道类型。 编译器不允许向此类型的值中发送数据。通道值的比较:
通道操作:
同一个操作符 <- 既用于发送也用于接收,但Go会根据操作对象弄明白该干什么。
Go语言中有五种通道相关的操作。假设一个通道为ch,下面列出了这五种操作的语法或者函数调用: 1、调用内置函数close来关闭一个通道:
close(ch)
2、向通道ch发送一个值v。ch不能为单向接收通道。<-称为数据发送操作符。
ch <- v
3、从通道ch接收一个值。
<-ch
4、查询一个通道的容量。
cap(ch)
5、查询一个通道的长度。
len(ch)
通道可以分为三类: 1、零值(nil)通道。 2、非零值但已关闭的通道。 3、非零值并且尚未关闭的通道。
操作 | 一个零值nil通道 | 一个非零值但已关闭的通道 | 一个非零值且尚未关闭的通道 |
---|---|---|---|
关闭 | 产生恐慌 | 产生恐慌 | 成功关闭(C) |
发送数据 | 永久阻塞 | 产生恐慌 | 阻塞或者成功发送(B) |
接收数据 | 永久阻塞 | 永不阻塞(D) | 阻塞或者成功接收(A) |
死锁:
恐慌绝望:
通道案例:
通道案例一:
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}
通道案例二:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int) // 一个非缓冲通道
go func(ch chan<- int, x int) {
time.Sleep(time.Second)
// <-ch // 此操作编译不通过
ch <- x*x // 阻塞在此,直到发送的值被接收
}(c, 3)
done := make(chan struct{})
go func(ch <-chan int) {
n := <-ch // 阻塞在此,直到有值发送到c
fmt.Println(n) // 9
// ch <- 123 // 此操作编译不通过
time.Sleep(time.Second)
done <- struct{}{}
}(c)
<-done // 阻塞在此,直到有值发送到done
fmt.Println("bye")
}
通道案例三:
package main
import "fmt"
func main() {
c := make(chan int, 2) // 一个容量为2的缓冲通道
c <- 3
c <- 5
close(c)
fmt.Println(len(c), cap(c)) // 2 2
x, ok := <-c
fmt.Println(x, ok) // 3 true
fmt.Println(len(c), cap(c)) // 1 2
x, ok = <-c
fmt.Println(x, ok) // 5 true
fmt.Println(len(c), cap(c)) // 0 2
x, ok = <-c
fmt.Println(x, ok) // 0 false
x, ok = <-c
fmt.Println(x, ok) // 0 false
fmt.Println(len(c), cap(c)) // 0 2
close(c) // 此行将产生一个恐慌
c <- 7 // 如果上一行不存在,此行也将产生一个恐慌。
}
通道案例四:
package main
import (
"fmt"
"time"
)
func main() {
var ball = make(chan string)
kickBall := func(playerName string) {
for {
fmt.Print(<-ball, "传球", "\n")
time.Sleep(time.Second)
ball <- playerName
}
}
go kickBall("张三")
go kickBall("李四")
go kickBall("王二麻子")
go kickBall("刘大")
ball <- "裁判" // 开球
var c chan bool // 一个零值nil通道
<-c // 永久阻塞在此
}
通道案例五:
// chanmain
package main
import (
"fmt"
)
func main() {
done := make(chan bool)
data := make(chan int)
go xfz(data, done)
go scz(data)
<-done
}
func xfz(data chan int, done chan bool) {
for x := range data {
fmt.Println("recv:", x)
}
done <- true
}
func scz(data chan int) {
for i := 0; i < 100; i++ {
data <- i
}
close(data)
}
本节案例:
本节说明:本节介绍Go语言测试的相关内容。
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节介绍Go语言中的错误处理。
错误处理介绍:
错误处理:
Go 有一个预先定义的 error 接口类型:
type error interface {
Error() string
}
定义错误:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节介绍Go语言面向对象的相关内容。
Go面向对象:
本节案例:
本节说明:本节介绍Go语言中泛型的相关内容。
泛型介绍:
Go语言中的泛型:
泛型的优点?为什么需要泛型?
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节介绍Go语言内置函数的相关内容。
Go内置函数介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本章节包括大量的案例,这些案例是Go语言官方标准库的使用教学。也包括优秀的第三方库,利用这些第三方库可以构建更完善的项目代码。
本节说明:本节介绍Go语言库包的相关内容。
Go语言标准库概述:
包发布:
本节案例:
本节说明:本节介绍命令控制的相关内容。主要是flag标准库的使用。
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节介绍Go语言中请求响应的内容。
HTTP请求响应:
请求响应包:
请求响应案例:
访问并读取页面:
package main
import (
"fmt"
"net/http"
)
var urls = []string{
"http://www.google.com",
"http://golang.org",
"http://blog.golang.org",
}
func main() {
for _, url := range urls {
resp, err := http.Head(url)
if err != nil {
fmt.Println("Error:", url, err)
}
fmt.Println(url, ": ", resp.Status)
}
}
发送GET参数请求:
发送POST参数请求:
payloads := "http://www.baidu.com/admin"
postStr := `username=admin&password=123456`
req, err := http.NewRequest("POST", payloads, strings.NewReader(postStr))
req.Header.Set("Accept-Encoding", "")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0")
req.Header.Set("Content-Type", " application/x-www-form-urlencoded")
payloads := "http://www.baidu.com/admin"
base64Table2 := "REJTVEVQIFYzLjAgICAgI"
re, e := base64.StdEncoding.DecodeString(base64Table2)
if e != nil {
return nil
}
data := string(re)
req, err := http.NewRequest("POST", payloads, strings.NewReader(data))
req.Header.Set("Accept-Encoding", "")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
payloads := "http://www.baidu.com/admin"
data := url.Values{"start":{"0"}, "offset":{"xxxx"}}
req, err := http.NewRequest("POST", payloads, strings.NewReader(data.Encode())
req.Header.Set("Accept-Encoding", "")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
发送cookie参数请求:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节介绍Go语言处理文本文件格式的相关内容,包括文件的读取、文件的写入等。
Go文本处理:也就是处理TXT格式的文件。
按行读取文本文件:
ioutil:案例一:按行读取之按行打印内容
b, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println("Open file error!", err)
}
fmt.Println(string(b))
return string(b)
os.File:案例二:按行读取之按行打印内容
package main
import (
"fmt"
"os"
)
func main() {
userFile := "asatxie.txt"
fl, err := os.Open(userFile)
if err != nil {
fmt.Println(userFile, err)
return
}
defer fl.Close()
buf := make([]byte, 1024)
for {
n, _ := fl.Read(buf)
if 0 == n {
break
}
os.Stdout.Write(buf[:n])
}
}
bufio:案例三:按行读取之按行打印内容
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func main() {
fileName := "ip.txt"
file, err := os.OpenFile(fileName, os.O_RDWR, 0666)
if err != nil {
fmt.Println("Open file error!", err)
return
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
panic(err)
}
var size = stat.Size()
fmt.Println("file size=", size)
buf := bufio.NewReader(file)
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
// 实际编程中处理line即可
fmt.Println(line)
if err != nil {
if err == io.EOF {
fmt.Println("File read ok!")
break
} else {
fmt.Println("Read file error!", err)
return
}
}
}
}
案例四:
func main(){
lines, err := LineReader("file.txt", 0)
if err != nil {
}
for line := range lines {
fmt.Println(line)
}
}
func LineReader(filename string, noff int64) (chan string,error) {
fp, err := os.Open(filename)
if err != nil {
return nil, err
}
// if offset defined then start from there
if noff > 0 {
// and go to the start of the line
b := make([]byte, 1)
for b[0] != '\n' {
noff--
//fp.Seek(noff, os.SEEK_SET)
fp.Read(b)
}
noff++
}
out := make(chan string)
go func() {
defer fp.Close()
// we need to close the out channel in order
// to signal the end-of-data condition
defer close(out)
scanner := bufio.NewScanner(fp)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
noff, _ = fp.Seek(0, os.SEEK_CUR)
out <- scanner.Text()
}
}()
return out, nil
}
把文本文件作为参数:
把结果写入到文本文件中:
案例一:
package main
import (
"fmt"
"os"
)
func main() {
userFile := "astaxie.txt"
fout, err := os.Create(userFile)
if err != nil {
fmt.Println(userFile, err)
return
}
defer fout.Close()
for i := 0; i < 10; i++ {
fout.WriteString("Just a test!\r\n")
fout.Write([]byte("Just a test!\r\n"))
}
}
案例二:将值传递给domain即可
fileName := "is.txt"
fd, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
s := strings.Join([]string{domain, "\t", "\n"}, "")
buf := []byte(s)
fd.Write(buf)
fd.Close()
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节是用来介绍Go日志的相关内容,包括日志记录等。
log标准库:
package main
import (
"io"
"log"
"os"
"time"
)
func main() {
// 日志保存到文件中-开始
filename := time.Now().Format("app-20060102150405") + ".log"
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
defer f.Close()
writers := []io.Writer{
f,
os.Stdout}
fileAndStdoutWriter := io.MultiWriter(writers...)
logger := log.New(fileAndStdoutWriter, "", log.Ldate|log.Ltime)
// 日志保存到文件中-结束
logger.Println("Hello World!")
}
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:本节介绍Go语言中电子邮件的发送内容。
net/smtp标准库:
本节案例:
本节说明:本节介绍Go语言中输入输出的相关内容。
Go语言输入输出:
输入案例参考:
案例1:
package main
import (
"fmt"
)
func main() {
var a int
fmt.Printf("请输入a:")
fmt.Scanf("%d", &a)
//fmt.Scan(&a)
fmt.Println("a =", a)
}
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
排序算法介绍:
Go排序算法:
本节案例:
本节说明:
分治算法介绍:
Go分治算法:
本节案例:
本节说明:
动态规划算法介绍:
Go动态规划算法:
本节案例:
本节说明:
贪心算法介绍:
Go贪心算法:
本节案例:
本节说明:本节介绍二叉树算法的相关内容。
二叉树算法介绍:
Go贪心算法:
本节案例:
本节说明:
回溯算法介绍:
Go回溯算法:
本节案例:
本节说明:
搜索算法介绍:
Go搜索算法:
本节案例:
本节说明:
随机算法:
Go随机算法:
本节案例:
本节说明:
数论算法介绍:
Go数论算法:
本节案例:
本节说明:
完全算法介绍:
Go完全算法:
本节案例:
本节说明:本节介绍漏桶算法的相关内容。
漏桶算法介绍:
Go漏桶算法:
本节案例:
本章节是在深入学习Go语言的基础上开发安全项目的实例。包括域名扫描、漏洞扫描、密码爆破、病毒免杀等项目。方便在红队攻防领域或是渗透测试领域有自己开发的完善的武器库。
本节说明:本节介绍通过Go语言进行子域名发现的相关内容。
子域名发现方法:二级三级域名的发现无外乎下面的这几种方法。
子域名爆破原理:
子域名字典整理:
项目成品:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本章节是深入理解Go语言必须要学习的内容。包括Go语言底层的功能实现方式,通过深入阅读Go语言源码达到真正深入理解Go语言的境界。
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例:
本章节是关于逆向工程的内容,包括Go语言开发项目的逆向工程等。
本节说明:
Go语言介绍:
本节案例:
本节说明:
Go语言介绍:
Go语言命令:
本节案例: