Go 基础
参考链接
[Golang教程 - 菜鸟教程](Go 基础教程 - Golang教程 - 菜鸟教程l)
Hello world
package main // 包名称
import "fmt"
func main() { // main: 函数名
fmt.Println("Hello world") // Hello world
fmt.Println("1+1=",1+1) // 1+1= 2
var name = "golong" // 变量名
fmt.Println(name)
}
Go 标识符规则
标识符的名称必须以字母或下划线(_)开头。并且名称中可能包含字母“ a-z”或“ A-Z”或数字0-9,以及字符“
标识符的名称不能以数字开头。
标识符的名称区分大小写。
关键字不能用作标识符名称。
标识符名称的长度没有限制,但是建议仅使用4到15个字母的最佳长度。
例如:
// 有效的标识符:
_geeks23
geeks
gek23sd
Geeks
geeKs
geeks_geeks
// 无效的标识符:
212geeks
if
default
Go 内置常量、类型、函数
// 常量
true(真), false(假), iota(计数器), nil(空)
// 类型
int, in和uint都包含相同的大小,无论是32位还是64位。
int8, 8位有符号整数
int16, 16位有符号整数
int32, 32位有符号整数
int64, 64位有符号整数
uint, in和uint都包含相同的大小,无论是32位还是64位。
uint8, 8位无符号整数
uint16, 16位无符号整数
uint32, 32位无符号整数
uint64, 64位无符号整数
uintptr, 它是无符号整数类型。它的宽度未定义,但是可以容纳指针值的所有位。
float32,
float64,
complex128,
complex64,
bool,
byte, 它是int8的同义词。
rune, 它是int32的同义词,也表示Unicode代码点
string,
error
// 函数
make, 分配并初始化切片、map、channel对象;
len, 返回长度,取决于具体类型;字符串返回字节数;channel返回缓存元素的个数;
cap, 返回容量,取决于具体类型;切片返回底层数组长度;channel返回缓存容量;
new, 分配内存,返回类型指针;不初始化切片,map,channel;
append, 将元素追加到切片,返回更新后的切片;若容量不足,则新分配一个底层数组;
copy, 切片复制,返回被复制的数量,即len(src)、len(dst)中较小的;支持字符串复制到字节切片;
close, 关闭channel;必须是双向channel或只发送channel;已关闭的channel在接收时第二参数返回false;
delete, 根据键删除map中的元素;
complex,
real,
imag,
panic, 终止程序,引发异常;
recover defer函数中调用,捕获panic错误信息;
Go 关键字(25个)
break
case
chan
const
continue
default
defer
else
fallthrough
for
func
go
goto
if
import
interface
map
package
range
return
select
struct
switch
type
var
GO 数据类型
基本类型:数字(int),字符串(string)和布尔值(bool)属于此类别。
聚合类型:数组和结构属于此类别。
引用类型:指针,切片,map集合,函数和Channel属于此类别。
接口类型
GO println和printf区别
Println :可以打印出字符串,和变量
Printf : 只可以打印出格式化的字符串,可以输出字符串类型的变量
//使用字符串
package main
import "fmt"
func main() {
//用于存储字符串的str变量
str := "nhooo"
//显示字符串的长度
fmt.Println("字符串的长度:%d", len(str)) //字符串的长度:%d 5
fmt.Printf("字符串的长度:%d", len(str)) //字符串的长度:5
//显示字符串
fmt.Println("\n字符串是: %s", str) // 字符串是: %s nhooo
fmt.Printf("\n字符串是: %s", str) // 字符串是: nhooo
// 显示str变量的类型
fmt.Println("\nstr的类型是: %T", str) // str的类型是: %T nhooo
fmt.Printf("\nstr的类型是: %T", str) // str的类型是: string
}
Go 变量
变量命名规则: 变量名称必须以字母或下划线(_)开头。并且名称中可能包含字母“ a-z”或“ A-Z”或数字0-9,以及字符" _"
创建变量的方法
使用var关键字
使用短变量声明运算符(:=)---> 只能在局部使用,不能全局
声明变量
package main import "fmt" func main() { // 方法一: 变量声明和初始化,显示类型 var _var1 = 1 // 方法二: 变量声明和初始化,指定变量类型,不声明值 var _var2 int var _var3 string var _var4 float64 // 方法三:同时声明和初始化多个同类型变量 var _var5, _var6, _var7 int = 111, 222 , 333 // 方法四:同时声明和初始化多个不同类型变量 var _var8, _var9, _var10 = 2, "abc", 11.44
// 方法五:短变量声明, 内置变量 _var11 := 2 _var12 := "3" _var13 := 11.11 // 方法五:短变量声明, 多变量声明 _var14, _var15, _var16 := 800, "NHOOO", 47.56 fmt.Printf("变量类型: %T", _var1,_var2,_var3,_var4,_var5,_var6,_var7,_var8,_var9,_var10,_var11,_var12,_var13,_var14,_var15,_var16)
}
变量类型: int%!(EXTRA int=0, string=, float64=0, int=111, int=222, int=333, int=2, string=abc, float64=11.44, int=2, string=3, float64=11.11, int=800, string=NHOOO, float64=47.56)
## Go 变量作用域
- 局部变量(在块或函数内部声明)
- 全局变量(在块或函数外部声明)
> 局部变量: 函数内定义的变量,函数外无法调用
> 全局变量: 函数外定义的变量,可在任何部分访问
> 如果同时存在同一的变量名称,局部变量 > 全局变量
```go
//全局变量
package main
import "fmt"
// 全局变量声明
var _var1 int = 100
func main() {
// 主函数内部的局部变量
var _var2 int = 200
//显示全局变量
fmt.Printf("全局变量 _var1 的值是 : %d\n", _var1 ) // 100
//显示局部变量
fmt.Printf("局部变量 _var2 的值是 : %d\n", _var2 ) // 200
//调用函数
display()
}
func display() {
// 显示全局变量
fmt.Printf("全局变量 _var1 的值是 : %d\n", _var1 ) // 100
}
Go 语言常量
常量就像变量一样声明,但是使用 const 关键字作为前缀来声明具有特定类型的常量。不能使用 := 语法声明。
典型
package main
import "fmt"
func main() {
const mydefault = true // 赋值声明变量
type myBool bool
var new1Bool = mydefault
var new2Bool myBool = mydefault
// new1Bool = new2Bool 不允许这样使用
fmt.Println(new1Bool)
fmt.Println(new2Bool)
}
Go 运算符
算数运算符
+ - * / %
% 当第一个操作数除以第二个操作数时,'%'运算符返回余数。例如,x%y。
package main
import "fmt"
func main() {
a := 1
b := 2
fmt.Println(a+b) // 3
fmt.Println(a-b) // -1
fmt.Println(a*b) // 2
fmt.Println(a/b) // 0
fmt.Println(a%b) // 1
}
关系运算符
== , != , > , < , >= , <=
package main
import "fmt"
func main() {
a := 1
b := 2
fmt.Println(a==b) // 返回: false
fmt.Println(a!=b) // 返回: true
fmt.Println(a>b) // 返回: false
fmt.Println(a<b) // 返回: true
fmt.Println(a>=b) // 返回: false
fmt.Println(a<=b) // 返回: true
}
逻辑运算符
&&(逻辑AND), ||(逻辑或), !(逻辑非)
package main
import "fmt"
func main() {
p := 1
q := 2
if p != q && p <= q {
fmt.Println("True") // True
}
if p != q || p <= q {
fmt.Println("True") // True
}
if !(p == q) {
fmt.Println("True") // True
}
}
按位运算符(没看懂)
&(按位与):将两个数字作为操作数,并对两个数字的每一位进行“与”运算。仅当两个位均为1时,AND的结果才为1。
| (按位或):将两个数字作为操作数,并对两个数字的每一位进行“或”运算。两个位中的任何一个为1,OR的结果为1。
^(按位XOR):将两个数字作为操作数,并对两个数字的每一位进行XOR。如果两个位不同,则XOR的结果为1。
<<(左移):取两个数字,左移第一个操作数的位,第二个操作数决定移位的位数。
(右移):取两个数字,右移第一个操作数的位,第二个操作数决定移位的位数。
&^(AND NOT):(按位清除)运算符,该运算符的实际操作为&(^)操作。
package main
import "fmt"
func main() {
p := 456
q := 793
fmt.Printf("计算结果是: p & q = %d", p & q) // 计算结果是: p & q = 264
fmt.Printf("\n计算结果是: p | q = %d", p | q) // 计算结果是: p | q = 985
fmt.Printf("\n计算结果是: p ^ q = %d", p ^ q) // 计算结果是: p ^ q = 721
fmt.Printf("\n计算结果是: p << q = %d", p << q) // 计算结果是: p << q = 0
fmt.Printf("\n计算结果是: p >> q = %d", p >> q) // 计算结果是: p >> q = 0
fmt.Printf("\n计算结果是: p &^ q = %d", p &^ q) // 计算结果是: p &^ q = 192
}
赋值运算符
赋值运算符用于为变量赋值(运算) 注意: 右侧的值必须与左侧的变量具有相同的数据类型,否则编译器将抛出错误
= , += , -= , *= , /= , %= , &= , ^= , |=
package main
import "fmt"
func main() {
p := 1
q := 2
// “=”(简单赋值)
p = q
fmt.Println(p) // 2
// “+=”(加法赋值)
p += q
fmt.Println(p) // 4, 由于上面运算已经赋值,所以是 p=2+q
//“-=”(减法赋值)
p -= q
fmt.Println(p) // 2,由于上面运算已经赋值,所以是 p=4-q
// “*=”(乘法赋值)
p *= q
fmt.Println(p) // 4,由于上面运算已经赋值,所以是 p=2*q
// “/=”(除法赋值)
p /= q
fmt.Println(p) // 2,由于上面运算已经赋值,所以是 p=4/2
// “%=”(求模赋值)
p %= q
fmt.Println(p) // 2,由于上面运算已经赋值,所以是 p=4/2
}
杂项运算符
&:此运算符返回变量的地址。
*:此运算符提供指向变量的指针。
<-:该运算符的名称为接收。它用于从通道接收值。
package main
import "fmt"
func main() {
a := 23
b := &a
fmt.Println(*b) // 23
*b = 78
fmt.Println(a) // 78
}
Go 类型转换
注意:由于Golang具有强大的类型系统,因此不允许在表达式中混合使用数字类型(例如加,减,乘,除等),并且不允许在两个混合类型之间执行赋值类型。
package main
import "fmt"
func main() {
/*
// 显示类型转换
var a int = 985
var b uint = uint(a)
var c int64 = int64(a)
var d float64 = float64(a)
fmt.Printf("转换类型: %T",b,c,d) // 转换类型: uint%!(EXTRA int64=985, float64=985)
*/
/*
// 计算平均值
var a int = 446
var b int = 23
var c float32
c = float32(a) / float32(b)
fmt.Printf("平均值 = %f",c) // 平均值 = 19.391304
*/
}
Go 条件判断
package main
import "fmt"
func main() {
var a int = 100
// if 语句
if(condition) {
//condition为真,执行
}
// if...else 语句
if (condition) {
// if条件为true,执行此代码块
} else {
// if条件为false,执行此代码块
}
// 嵌套if 语句
if (condition1) {
// 当条件1为真时执行
if (condition2) {
// 当条件2为真时执行
}
}
// if...else...if 语句
if(condition_1) {
//condition_1为true,执行这里的代码块
} else if(condition_2) {
//condition_2为true,执行这里的代码块
}else {
//没有条件为true时,执行这里代码块
}
}
Go 循环语句
如果数组,字符串,切片或map集合为空,则for循环不会抛出错误并继续其流程。换句话说,如果数组,字符串,切片或map为nil,则for循环的迭代次数为零。
语法:
for initialization; condition; post{
// 语句....
}
initialization: 可选,循环开始之前执行(如变量声明,递增,赋值,函数)
condition: 布尔表达式,该表达式在循环的每次迭代开始时计算。如果条件语句的值为true,则执行循环。
post: for循环体之后执行。在post语句之后,条件语句再次计算条件语句的值是否为false,然后循环结束
for简单循环
package main
import "fmt"
func main() {
// initialization 是 i := 0 , condition 是 i < 4 ; post 是 i++
// 意思: 定义变量i=0,从0开始,直到 i < 4 为真,每次循环之后 i+1
for i :=0; i < 4; i++ {
fmt.Println(i)
}
}
// 输出:
0
1
2
3
for无线循环
package main
import "fmt"
func main() {
// 无限循环
for {
fmt.Printf("nhooo\n")
}
}
for循环用作while循环
package main
import "fmt"
func main() {
//while循环
//for循环执行到
//i <3条件为真
i:= 0
for i < 3 {
i += 2
}
fmt.Println(i)
}
for循环数组
package main
import "fmt"
func main() {
// 定义一个数组
_var := bytestring{"a","b","c"}
// index和j是分配迭代值的变量。它们也称为迭代变量。
// 第二个变量,即j是可选的。
// 范围表达式在循环开始之前被评估一次。
// index和j存储 _var 的值
// index存储单个字符串和的索引号
// j存储给定数组的单个字符串
for index,j := range _var{
fmt.Println(index, j)
}
}
// 输出
0 a
1 b
2 c
for循环字符串
package main
import "fmt"
func main() {
for index, j := range "abcd" {
fmt.Println(index, j ,string(j))
// 0 97 a
// 1 98 b
// 2 99 c
// 3 100 d
}
for index, j := range "abcd" {
fmt.Printf("%U 的索引值为 %d\n", j, index,string(j))
// %!(EXTRA string=a)U+0062 的索引值为 1
// %!(EXTRA string=b)U+0063 的索引值为 2
// %!(EXTRA string=c)U+0064 的索引值为 3
// %!(EXTRA string=d)
}
}
for循环map的键和值对
package main
import "fmt"
func main() {
res := map[int]string{
111: "a",
222: "b",
333: "c",
}
for k, v := range res {
fmt.Println(k, v)
}
}
// 输出
111 a
222 b
333 c
for通道
package main
import "fmt"
func main() {
// 使用 channel
chnl := make(chan int)
go func() {
chnl <- 100
chnl <- 1000
chnl <- 10000
chnl <- 100000
close(chnl)
}()
for i := range chnl {
fmt.Println(i)
}
}
for控制语句
break ---> 终止其所在的循环或语句
continue ---> 在循环中,表示结束本次循环,进行下次循环。
goto ---> 表示无条件跳转到标号处执行。
package main
import "fmt"
func main() {
_x := 0
Lable1: for _x < 8 {
if(_x == 5) {
_x = _x + 1
goto Lable1
}
fmt.Println("值为:", _x)
_x++;
}
}
// 输出
值为: 0
值为: 1
值为: 2
值为: 3
值为: 4
值为: 6
值为: 7
Go Switch几种用法
表达式 switch
类型 switch
语法:
switch optstatement; optexpression{
case expression1: Statement..
case expression2: Statement..
...
default: Statement..
}
示例一: 可选语句结合表达式
package main
import "fmt"
// 同时使用两种语句, 如可选语句, day:=4 和表达式 如:day
func main() {
switch day := 2; day{
case 1:
fmt.Println(1)
case 2:
fmt.Println(2) // 输出2
case 3:
fmt.Println(3)
default:
fmt.Println("I don't know")
}
}
示例二: 无可选,无表达式
switch语句不带可选语句和表达式
package main
import "fmt"
func main() {
var value int = 2
switch {
case value == 1:
fmt.Println(1)
case value == 2:
fmt.Println(2)
case value == 3:
fmt.Println(3)
default:
fmt.Println("I don't know")
}
}
示例三: case多个值
package main
import "fmt"
func main() {
var value string = "e"
//没有默认语句的switch语句,case语句中的多个值
switch value {
case "a":
fmt.Println("aaa")
case "b", "c":
fmt.Println("bbb")
case "d", "e", "f":
fmt.Println("ccc")
}
}
示例四: 类型switch
package main
import (
"fmt"
)
func outType(v interface{}) {
switch v.(type) {
case int:
fmt.Println(v, "is int")
case string:
fmt.Println(v, "is string")
case bool:
fmt.Println(v, "is bool")
}
}
func main() {
outType(1024) // 1024 is int
outType("pig") // pig is string
outType(true) // true is bool
}
Go 避免死锁
语法
select{ case SendOrReceive1: // 语句 case SendOrReceive2: // 语句 case SendOrReceive3: // 语句 ....... default: // Statement }
设置默认值
package main
import "fmt"
func main() {
// 创建通道
c := make(chan int)
select {
case <-c:
default:
fmt.Println("default")
}
}
Go 函数
简单函数
语法
func function_name(Parameter_list)(Return_type){
// function body.....
}
func: Go关键字,用于创建函数
function_name: 函数名称
Parmeter_list: 包含函数参数名称和类型
Return_type: 可选,包含函数返回值类型,如果在函数中使用return_type,则必须在函数中使用return语句。
package main
import "fmt"
// 此函数需要传两个 int 类型的值, 返回的值也是 int 类型
func abc(length, width int) int {
res := length + width
return res
}
func main() {
fmt.Println(abc(10,10)) // 调用函数
}
可变参函数
当您要在函数中传递切片时,使用可变参数函数。
当我们不知道参数的数量时,使用可变参数函数。
在程序中使用可变参数函数时,它可以增加程序的可读性。
package main
import (
"fmt"
"strings"
)
// 可变参数函数联接字符串
func jsonStr(element ...string) string{
return strings.Join(element, "-")
}
func main() {
// 无参数
fmt.Println(jsonStr())
// 多参数
fmt.Println(jsonStr("a","b"))
fmt.Println(jsonStr("a","b","c","d"))
//在可变函数中传递一个切片
element:= []string{"geeks", "FOR", "geeks"}
fmt.Println(jsonStr(element...))
}
匿名函数
定义格式
func(参数列表)(返回参数列表){
函数体
}
在定义时调用匿名函数
func(data int) {
fmt.Println("hello", data)
}(100) // (100),表示对匿名函数进行调用,传递参数为 100。
将匿名函数赋值给变量
// 将匿名函数体保存到f()中
f := func(data int) {
fmt.Println("hello", data)
}
f(100)
匿名函数用作回调函数
实现对切片的遍历操作,遍历中访问每个元素的操作使用匿名函数来实现,用户传入不同的匿名函数体可以实现对元素不同的遍历操作
简单实用
package main
import (
"fmt"
)
func main() {
// 匿名函数,无参数
func() {
fmt.Println("hello world")
}()
// 匿名函数,有参数
func(ele string) {
fmt.Println(ele)
}('hello world')
}
匿名函数作为参数传递给其他函数。
package main
import (
"fmt"
)
// 匿名函数作为参数传递
func GFG(i func(p, q string)string) {
fmt.Println(i ("Geeks", "for"))
}
func main() {
// 匿名函数,传参
value := func(p, q string) string{
return p + q + "mmp"
}
GFG(value)
}
从另一个函数返回匿名函数。(常用于回调函数)
package main
import (
"fmt"
)
// 返回匿名函数
func GFG() func(i, j string)string{
myf := func(i,j string)string{
return i + j + "hello world"
}
return myf
}
func main() {
// 调用匿名函数并传值
value := GFG()
fmt.Println(value("weblcome ", "to "))
}
Go 函数调用
按值调用
函数内部进行的任何更改都不会反映在调用者的实际参数中
package main
import "fmt"
func modify(Z int) {
fmt.Println(Z) // 10
Z = 79
}
func main() {
var Z int = 10
fmt.Println("调用前:", Z) // 10
modify(Z) // 调用修改函数
fmt.Println("调用后:", Z) // 10
}
引用调用
指针概念: 在函数调用中,我们传递变量的地址,并使用解引用运算符*修改值
package main
import (
"fmt"
)
func modify(Z *int) {
*Z = 70
}
func main() {
var Zz int = 10
modify(&Zz) // 通过引用调用传递变量Z地址,指针方式
fmt.Println(Zz) // 70
}
函数返回值
首先先看下函数语法
func function_name(parameter_list)(return_type_list){
// code...
}
function_name:它是函数的名称。
parameter-list:它包含函数参数的名称和类型。
return_type_list:这是可选的,它包含函数返回的值的类型。如果在函数中使用return_type,则必须在函数中使用return语句。
返回多个值
package main
import (
"fmt"
)
// myfunc返回3个int类型的值
func myfunc(p, q int) (int,int,int) {
return p - q, p * q, p + q
}
func main() {
var _var1, _var2, _var3 = myfunc(5,7)
fmt.Println(_var1,_var2,_var3) // -2 35 12
}
返回值命名
允许为返回值提供名称。你也可以在代码中使用这些变量名。
语法
// name1和name2是返回值的名称,而para1和para2是函数的参数。
func function_name(para1, para2 int)(name1 int, name2 int){
// code...
}
或
func function_name(para1, para2 int)(name1, name2 int){
// code...
}
package main
import (
"fmt"
)
func myfunc(p,q int) (age1 int, age2 int) {
age1 = p + q
age2 = p * q
return // Go编译器将自动返回参数
}
func main() {
var res1, res2 = myfunc(2, 4)
fmt.Println(res1, res2) // 6 8
}
Go 空白标识符
只需使用_(下划线)。它允许编译器忽略该特定变量的错误(declared and not used)。
package main
import "fmt"
func main() {
//调用函数
//函数返回两个值
//分配给mul和空白标识符(忽略)
mul, _ := mul_div(105, 7)
//只使用mul变量
fmt.Println("105 x 7 = ", mul)
}
//函数返回两个
//整数类型的值
func mul_div(n1 int, n2 int) (int, int) {
//返回值
return n1 * n2, n1 / n2
}
Go 上下文函数
init()函数的主要目的是初始化无法在全局上下文中初始化的全局变量。
package main
import "fmt"
//多个init()函数,必须放在 main 之前
func init() {
fmt.Println("Welcome to init() function")
}
func init() {
fmt.Println("Hello! init() function")
}
func main() {
fmt.Println("Welcome to main() function")
}
Go Defer关键字
同一程序中允许多个defer语句,并且它们按LIFO(后进先出)顺序执行
在defer语句中,将在执行defer语句时(而不是在调用它们时)评估参数。
defer语句通常用于确保在完成文件处理后关闭文件,关闭通道或捕获程序中的紧急情况。
语法
// 函数
defer func func_name(parameter_list Type) return_type{
// Code
}
// 方法
defer func (receiver Type) method_name(parameter_list){
// Code
}
defer func (parameter_list)(return_type){
// code
}()
后进先出延迟执行
package main
import "fmt"
func add(a1, a2 int) int {
res := a1 + a2
fmt.Println("Result: ", res)
return 0
}
func main() {
fmt.Println("Start")
// 多个延迟,已LIFO顺序执行
defer fmt.Println("End")
defer add(1,2)
defer add(3,4)
fmt.Println("烟")
}
// 执行顺序
Start
烟
Result: 7
Result: 3
End
Go 方法(看不懂)
Go 结构体(看不懂)
Go 数组
语法
var array_name[length]Type
或
var array_name[length]Typle{item1, item2, item3, ...itemN}
var创建数组
package main
import "fmt"
func main() {
// 创建字符串类型数组
var arr [3]string
// 使用索引分配元素
arr[0] = "a"
arr[1] = "b"
arr[2] = "c"
// 使用索引输出元素
fmt.Println(arr[0],arr[1],arr[2]) // a b c
}
简声明创建数组
array_name:= [length]Type{item1, item2, item3,...itemN}[object Object]
package main
import "fmt"
func main() {
// 数组的简写声明
arr := [4]string{"a","b","c"}
// for 循环访问数组
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}
多维数组
数组是一维的,但是允许创建多维数组。多维数组是相同类型数组的数组
语法
Array_name[Length1][Length2]..[LengthN]Type
package main
import "fmt"
func main() {
// 使用 var 关键字创建二维数组
// var arr1 [2][2]int
// arr1[0][0] = 100
// arr1[0][1] = 200
// arr1[1][0] = 300
// arr1[1][1] = 400
// 使用简单声明二维数组
arr := [3][3]string {
{"a","b","c"},
{"1","2","3"},
{"m","n","p"},
}
for x := 0; x < len(arr); x++ {
for y := 0; y < len(arr); y++ {
fmt.Println(arr[x][y])
}
fmt.Println("------------")
}
}
// 输出
a
b
c
------------
1
2
3
------------
m
n
p
------------
省略号
... 在长度位置处可见,则数组的长度由初始化的元素确定
//数组中省略号的使用方法
package main
import "fmt"
func main() {
//创建大小已确定的数组,根据其中元素的数量,使用省略号
myarray := [...]string{"a", "b", "c", "d"}
fmt.Println("数组元素: ", myarray) // 数组元素: [a b c d]
//数组的长度 由...决定 使用len()方法
fmt.Println("数组的长度为:", len(myarray)) // 数组的长度为: 4
}
数组比较
package main
import "fmt"
func main() {
arr1 := [3]int{9, 7, 6}
arr2 := [...]int{9, 7, 6}
arr3 := [...]int{6, 7, 9}
//使用==运算符比较数组
fmt.Println(arr1 == arr2) // true
fmt.Println(arr1 == arr3) // false
}
Go Defer关键字
同一程序中允许多个defer语句,并且它们按LIFO(后进先出)顺序执行
在defer语句中,将在执行defer语句时(而不是在调用它们时)评估参数。
defer语句通常用于确保在完成文件处理后关闭文件,关闭通道或捕获程序中的紧急情况。
语法
// 函数
defer func func_name(parameter_list Type) return_type{
// Code
}
// 方法
defer func (receiver Type) method_name(parameter_list){
// Code
}
defer func (parameter_list)(return_type){
// code
}()
后进先出延迟执行
package main
import "fmt"
func add(a1, a2 int) int {
res := a1 + a2
fmt.Println("Result: ", res)
return 0
}
func main() {
fmt.Println("Start")
// 多个延迟,已LIFO顺序执行
defer fmt.Println("End")
defer add(1,2)
defer add(3,4)
fmt.Println("烟")
}
// 执行顺序
Start
烟
Result: 7
Result: 3
End
Go 方法(看不懂)
Go 结构体(看不懂)
Go 数组
语法
var array_name[length]Type
或
var array_name[length]Typle{item1, item2, item3, ...itemN}
使用var关键字创建数组
package main
import "fmt"
func main() {
// 创建字符串类型数组
var arr [3]string
// 使用索引分配元素
arr[0] = "a"
arr[1] = "b"
arr[2] = "c"
// 使用索引输出元素
fmt.Println(arr[0],arr[1],arr[2]) // a b c
}
使用简声明
array_name:= [length]Type{item1, item2, item3,...itemN}[object Object]
package main
import "fmt"
func main() {
// 数组的简写声明
arr := [4]string{"a","b","c"}
// for 循环访问数组
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}
多维数组
数组是一维的,但是允许创建多维数组。多维数组是相同类型数组的数组
语法
Array_name[Length1][Length2]..[LengthN]Type
package main
import "fmt"
func main() {
// 使用 var 关键字创建二维数组
// var arr1 [2][2]int
// arr1[0][0] = 100
// arr1[0][1] = 200
// arr1[1][0] = 300
// arr1[1][1] = 400
// 使用简单声明二维数组
arr := [3][3]string {
{"a","b","c"},
{"1","2","3"},
{"m","n","p"},
}
for x := 0; x < len(arr); x++ {
for y := 0; y < len(arr); y++ {
fmt.Println(arr[x][y])
}
fmt.Println("------------")
}
}
// 输出
a
b
c
------------
1
2
3
------------
m
n
p
------------
省略号
... 在长度位置处可见,则数组的长度由初始化的元素确定
//数组中省略号的使用方法
package main
import "fmt"
func main() {
//创建大小已确定的数组,根据其中元素的数量,使用省略号
myarray := [...]string{"a", "b", "c", "d"}
fmt.Println("数组元素: ", myarray) // 数组元素: [a b c d]
//数组的长度 由...决定 使用len()方法
fmt.Println("数组的长度为:", len(myarray)) // 数组的长度为: 4
}
数组比较
package main
import "fmt"
func main() {
arr1 := [3]int{9, 7, 6}
arr2 := [...]int{9, 7, 6}
arr3 := [...]int{6, 7, 9}
//使用==运算符比较数组
fmt.Println(arr1 == arr2) // true
fmt.Println(arr1 == arr3) // false
}
数组复制
语法
// 按值创建数组的副本, 副改变,主不变
arr := arr1
// 通过引用创建数组的副本, 副本改变,主数组也改变
arr := &arr1
数组作为函数参数
将1或多维数组传递给该函数
语法
//对于指定大小的数组
func function_name(variable_name [size]type){
// Code
}
//对于无大小的数组
func function_name(variable_name []type){
// Code
}
package main
import "fmt"
func myfunc(a [6]int, size int) int { // 接受两个参数,第一个参数6位int类型,第二个参数int类型, 返回int类型
var k, val, r int
for k = 0; k < size; k++ {
val += a[k]
}
r = val / size
return r
}
func main() {
//创建数组
var arr = [6]int{1,2,3,4,5,6}
// 初始化数组,并将数组作为参数传递
var res int = myfunc(arr, 6)
fmt.Println(res)
}
切片的使用
数组是固定大小
切片大小可伸缩
在内部,切片和数组相连,切片是对基础数组的引用
切片中的第一个索引位置始终为0,而最后一个索引位置将为(切片的长度– 1)。
切片声明: []T 或 []T{} 或 []T{value1, value2, value3, ...value n}
package main
import "fmt"
func main() {
// 定义一个数组,数组长度最大是8, 数组索引下标从0开始
arr := [8]string{"a","b","c","d","e","f","g"}
fmt.Println("数组:",arr) // 数组: [a b c d e f g ]
// 如何定义切片
// 1、使用var关键字,创建切片
// var my_slice_1 = []string{"nhooos", "for", "nhooos"}
// 2、创建切片,使用简写声明
// my_slice_2 := []int{12, 45, 67, 56, 43, 34, 45}
// 切片数组
myslice := arr[1:3] // 切片下标1开始,3结束,不包括3,也就是长度2
fmt.Println("切片:",myslice) // 切片: [b c]
fmt.Println("切片长度:", len(myslice)) // 切片长度:2 (3-1,两个数值)
fmt.Println("切片容量:",cap(myslice)) // 切片容量: 7 (最多存储)
fmt.Println(arr[0:]) // [a b c d e f g ]
fmt.Println(arr[:0]) // []
fmt.Println(arr[:3]) // [a b c]
fmt.Println(arr[:]) // [a b c d e f g ]
}
make()函数
make()函数用于创建一个空切片
语法:
func make([]T, len, cap) []T ;
[]T 类型, len 长度, cap 容量(可选,默认和长度一样))
package main
import "fmt"
// 语法: func make([]T, len, cap) []T ; []T 类型, len 长度, cap 容量(可选,默认和长度一样))
func main() {
var _slice = make([]int, 4, 7)
fmt.Printf("切片内容: %v,\n切片长度: %d, \n切片容量: %d\n", _slice, len(_slice), cap(_slice))
// 切片内容: [0 0 0 0],
// 切片长度: 4,
// 切片容量: 7
}
遍历切片方法
package main
import "fmt"
func main() {
// 创建切片, 并遍历切片
_slice2 := []string{"a","b","c"}
// 方法一
for s := 0; s < len(_slice2); s++ {
fmt.Println(_slice2[s])
}
// 方法
// 如果您不想获取元素的索引值,则可以使用空格(_)代替索引变量
for _, v := range _slice2 {
fmt.Println(v)
}
}
多维切片使用
package main
import "fmt"
func main() {
// 多维切片
_slice3 := [][]string{
[]string{"a","aa"},
[]string{"b","bb"},
{"c","cc"},
{"d","dd"},
}
fmt.Println(_slice3)
}
切片比较判断
判断两个[]byte是否相等
package main
import (
"bytes"
"fmt"
)
func main() {
// 判断两个[]byte是否相等
a := []byte{0,1,2,3}
b := []byte{0,1,2,3}
c := []byte{1,1,2,3}
fmt.Println(bytes.Equal(a,b)) // true
fmt.Println(bytes.Equal(a,c)) // false
}
使用reflect判断slice是否相等
深度比较两个对象包括它们内部包含的元素是否都相等: 语法: func DeepEqual(x, y interface{}) bool
牺牲了性能
package main
import (
"reflect"
"fmt"
)
func main() {
a := []int{1, 2, 3, 4}
b := []int{1, 3, 2, 4}
c := []int{1, 2, 3, 4}
// 见证黑魔法
fmt.Println(reflect.DeepEqual(a,b)) // false
fmt.Println(reflect.DeepEqual(a,c)) // true
}
手写切片是否相等判断
性能比 reflect 快
package main
import (
"fmt"
)
// 以 int 类型为
func testEq(var1, var2 []int) bool {
// 如果切片为空,那么另一个切片也必须为空
if(var1 == nil) != (var2 == nil) {
return false;
}
// 判断切片长度,如果两个切片长度不相等
if len(var1) != len(var2) {
return false;
}
// for 循环遍历,通过下标判断是否一样
for i := range var1 {
if var1[i] != var2[i] {
return false
}
}
// 如果都通过,则返回 true
return true;
}
func main() {
a := []int{1, 2, 3, 4}
b := []int{1, 3, 2, 4}
c := []int{1, 2, 3, 4}
// 见证黑魔法
fmt.Println(testEq(a,b)) // false
fmt.Println(testEq(a,c)) // true
}
切片排序
Go语言的标准库提供了sort包,其中包含用于对int,float64和字符串切片进行排序的不同类型的排序方法。 这些函数始终按升序对可用元素进行切片排序
package main
import (
"fmt"
"sort"
)
func main() {
//创建切片
slc1 := []string{"Python", "Java", "C#", "Go", "Ruby"}
slc2 := []int{45, 67, 23, 90, 33, 21, 56, 78, 89}
//切片使用排序函数
sort.Strings(slc1)
sort.Ints(slc2)
// IntsAreSorted 判断是否已经按照升序排序,如果是返回true, 如果不是返回false
fmt.Println(sort.IntsAreSorted(slc2))
fmt.Println("\n排序后:")
fmt.Println("Slice 1: ", slc1) // Slice 1: [C# Go Java Python Ruby]
fmt.Println("Slice 2: ", slc2) // Slice 2: [21 23 33 45 56 67 78 89 90]
}
切片分割
语法
func Split(o_slice, sep []byte) [][]byte o_slice是字节片 sep是分隔符。 如果sep为空,则它将在每个UTF-8序列之后拆分
切片传递给函数
package main
import "fmt"
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := []int{89,90,91}
modify(a[:])
fmt.Println(a) // [90 90 91]
}
字符串操作
字符串比较
package main
import (
"strings"
"fmt"
)
func main() {
a := "aabb"
b := "hello"
// 方式一,没有比较运算符来的方便
fmt.Println(strings.Compare(a,b)) // a < b, 返回 -1
fmt.Println(strings.Compare(a,a)) // a = a, 返回 0
fmt.Println(strings.Compare(b,a)) // b > a, 返回 1
// 方式二,使用比较运算符
fmt.Println(a == b) // 不相等,返回 false
fmt.Println(a > b) // a 没有 b 字符串多,返回 false
fmt.Println(a < b) // b 比 a 字符串多,返回 true
// 方式三,使用 strings.EqualFold
fmt.Println(strings.EqualFold("aa","aa")) // 返回 true
fmt.Println(strings.EqualFold("aa","aabb")) // 返回 false
}
是否存在某个字符或子串
语法
// 子串 substr 在 s 中,返回 true ---> 整体字符必须在 s 中存在
func Contains(s, substr string) bool
// chars 中任何一个 Unicode 代码点在 s 中,返回 true ---> 如果当中随便一个字符在 s 中,那么就返回 true
func ContainsAny(s, chars string) bool
// Unicode 代码点 r 在 s 中,返回 true
func ContainsRune(s string, r rune) bool
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Contains("team", "t")) // true
fmt.Println(strings.Contains("team", "ta")) // false
fmt.Println(strings.ContainsAny("team", "i")) // false
fmt.Println(strings.ContainsAny("failure", "u & i")) // true
}
子串出现次数 ( 字符串匹配 )
当 sep 为空时,Count 的返回值是:utf8.RuneCountInString(s) + 1 Count 是计算子串在字符串中出现的无重叠的次数
语法
func Count(s, sep string) int
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Count("fivevev", "vev")) // 1 , 无重叠次数
fmt.Println(strings.Count("aba", "a")) // 2, 字符出现的次数
fmt.Println(strings.Count("aba", "")) // 4, sep步数为空,则 +1
fmt.Println(len("aa")) // 2, 字符数量
}
Fields 和 FieldsFunc
该包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。
语法
func Fields(s string) []string
func FieldsFunc(s string, f func(rune) bool) []string
Fields
用一个或多个连续的空格分隔字符串 s,返回子字符串的数组(slice)如果字符串 s 只包含空格,则返回空列表 ([]string 的长度为 0)
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("Field: %q", strings.Fields(" a b c"))
}
// 结果
Field: ["a" "b" "c"]
FieldsFunc
可以通过回调函数来指定分隔字符串 s 的字符, 也可自定义回调
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Println(strings.FieldsFunc(" foo bar baz ", unicode.IsSpace))
}
// 结果
[foo bar baz]
Split 和 SplitAfter、 SplitN 和 SplitAfterN
这四个函数都是通过 sep 进行分割,返回[]string。如果 sep 为空,相当于分成一个个的 UTF-8 字符 Split(s, sep) 和 SplitN(s, sep, -1) 等价;SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价。
语法
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.Split("a,b,c", "")) // ["a" "," "b" "," "c"]
fmt.Printf("%q\n", strings.Split("a,b,c", ",")) // ["a" "b" "c"]
fmt.Printf("%q\n", strings.SplitAfter("a,b,c", ",")) // ["a," "b," "c"]
fmt.Printf("%q\n", strings.SplitN("a,b,c", ",", 2)) // ["a" "b,c"]
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c,d,f", ",", 2)) // ["a," "b,c,d,f"]
}
字符串是否有某个前缀或后缀
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.HasPrefix("aaabbb","a")) // true, 判断前缀
fmt.Println(strings.HasSuffix("aaabbb","b")) // true,判断后缀
}
字符或子串在字符串中出现的位置
// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
func Index(s, sep string) int
// 在 s 中查找字节 c 的第一次出现,返回第一次出现的索引
func IndexByte(s string, c byte) int
// chars 中任何一个 Unicode 代码点在 s 中首次出现的位置
func IndexAny(s, chars string) int
// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int
// Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int
// 有三个对应的查找最后一次出现的位置
func LastIndex(s, sep string) int
func LastIndexByte(s string, c byte) int
func LastIndexAny(s, chars string) int
func LastIndexFunc(s string, f func(rune) bool) int
字符串 JOIN 操作
将字符串数组(或 slice)连接起来可以通过 Join 实现,函数签名如下:
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Join([]string{"name=aaa","age=18"},"&")) // 使用&拼接切片或者数组
}
// 结果
name=aaa&age=18
字符串重复几次
将 s 重复 count 次,如果 count 为负数或返回值长度 len(s)*count 超出 string 上限会导致 panic
语法
func Repeat(s string, count int) string
fmt.Println("ba" + strings.Repeat("na", 2))
// 输出结果
banana
字符替换
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
mapping := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z': // 大写字母转小写
return r + 32
case r >= 'a' && r <= 'z': // 小写字母不处理
return r
case unicode.Is(unicode.Han, r): // 汉字换行
return '\n'
}
return -1 // 过滤所有非字母、汉字的字符
}
fmt.Println(strings.Map(mapping, "Hello你#¥%……\n('World\n,好Hello^(&(*界gopher..."))
}
//
hello
world
hello
gopher
字符串子串替换
语法
// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string //-> old 是 s 当中需要替换的字符
// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) string
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) // oinky oinky oink
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) // moo moo moo
fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo")) // moo moo moo
大小写转换
语法
func ToLower(s string) string
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string
func Title(s string) string // 将 s 每个单词的首字母大写
func ToTitle(s string) string // 将 s 的每个字母大写
func ToTitleSpecial(c unicode.SpecialCase, s string) string // 将 s 的每个字母大写,并且会将一些特殊字母转换为其对应的特殊大写字母。
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ToLower("AAA")) // 全部转换小写
fmt.Println(strings.ToUpper("aaa")) // 全部转换大写
fmt.Println(strings.ToLower("Ā Á Ǎ À")) // 全部转换特殊字符为小写
fmt.Println(strings.ToUpper("ā á ǎ à")) // 全部转换特殊字符为大写
fmt.Println(strings.Title("āáǎà ōóǒò êēéěè")) // 首字母大写
fmt.Println(strings.ToTitle("āáǎà ōóǒò êēéěè")) // 全部大写
fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "āáǎà ōóǒò êēéěè")) // 全部大写包含特殊字符
}
修剪
// 将 s 左侧和右侧中匹配 cutset 中的任一字符的字符去掉
func Trim(s string, cutset string) string
// 将 s 左侧的匹配 cutset 中的任一字符的字符去掉
func TrimLeft(s string, cutset string) string
// 将 s 右侧的匹配 cutset 中的任一字符的字符去掉
func TrimRight(s string, cutset string) string
// 如果 s 的前缀为 prefix 则返回去掉前缀后的 string , 否则 s 没有变化。
func TrimPrefix(s, prefix string) string
// 如果 s 的后缀为 suffix 则返回去掉后缀后的 string , 否则 s 没有变化。
func TrimSuffix(s, suffix string) string
// 将 s 左侧和右侧的间隔符去掉。常见间隔符包括:'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL)
func TrimSpace(s string) string
// 将 s 左侧和右侧的匹配 f 的字符去掉
func TrimFunc(s string, f func(rune) bool) string
// 将 s 左侧的匹配 f 的字符去掉
func TrimLeftFunc(s string, f func(rune) bool) string
// 将 s 右侧的匹配 f 的字符去掉
func TrimRightFunc(s string, f func(rune) bool) string
x := "!!!@@@你好,!@#$ Gophers###$$$"
fmt.Println(strings.Trim(x, "@#$!%^&*()_+=-")) // 你好,!@#$ Gophers
fmt.Println(strings.TrimLeft(x, "@#$!%^&*()_+=-")) // 你好,!@#$ Gophers###$$$
fmt.Println(strings.TrimRight(x, "@#$!%^&*()_+=-")) // !!!@@@你好,!@#$ Gophers
fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n")) // Hello, Gophers
fmt.Println(strings.TrimPrefix(x, "!")) // !!@@@你好,!@#$ Gophers###$$$
fmt.Println(strings.TrimSuffix(x, "$")) // !!!@@@你好,!@#$ Gophers###$$
f := func(r rune) bool {
return !unicode.Is(unicode.Han, r) // 非汉字返回 true
}
fmt.Println(strings.TrimFunc(x, f)) // 你好
fmt.Println(strings.TrimLeftFunc(x, f)) // 你好,!@#$ Gophers###$$$
fmt.Println(strings.TrimRightFunc(x, f)) // !!!@@@你好
Replacer 类型
进行多个替换
r := strings.NewReplacer("<", "<", ">", ">") // < 替换成 < > 替换成 >
fmt.Println(r.Replace("This is <b>HTML</b>!")) // This is <b>HTML</b>!
替换结果写入到文件
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
Go 语言结构体(struct)
不支持继承但支持组合的轻量级类。
声明结构、 访问结构、修改结构、指针地址
package main
import (
"fmt"
)
type _Class struct {
name string
gender string
age int
}
func main() {
// 声明 struct 类型变量
var student _Class
fmt.Println(student) // { 0} , int 类型,默认为0
// 声明初始化结构
_st1 := _Class{"tom","boy", 18} // 方法一
_st2 := _Class{name:"tom", gender:"boy", age:18} // 方法二
_st3 := _Class{name:"tom"} // 方法三
fmt.Println(_st1) // {tom boy 18}
fmt.Println(_st2) // {tom boy 18}
fmt.Println(_st3) // {tom 0}
// 访问定义的结构文字
fmt.Println(_st1.name) // tom
fmt.Println(_st1.gender) // boy
fmt.Println(_st1.age) // 18
// 修改结构值
_st1.name = "google"
fmt.Println(_st1) // {google boy 18}
// 定义变量,并获取指向结构体的指针,既内存地址
_get := &_Class{"a","b",1}
fmt.Println((*_get).name) // a
fmt.Println(_get.name) // a
}
Go 结构体比较
如果结构彼此相等(就其字段值而言),则运算符和方法均返回true;否则,返回false。并且,如果比较的变量属于不同的结构,则编译器将给出错误
// 使用 reflect 下的 DeeplyEqual 方法
package main
import (
"fmt"
"reflect"
)
type _Class struct {
name string
gender string
age int
}
func main() {
_st1 := _Class{"a","女",18}
_st2 := _Class{"a","女",18}
_st3 := _Class{"a","女",19}
fmt.Println(reflect.DeepEqual(_st1, _st2)) // true
fmt.Println(reflect.DeepEqual(_st1, _st3)) // false
}
Go 嵌套结构体
语法
type struct_name_1 struct{
// Fields
}
type struct_name_2 struct{
variable_name struct_name_1
}
package main
import (
"fmt"
)
type User struct {
name string
gender string
age int
}
// 嵌套结构
type Group struct {
group User
}
func main() {
_g := Group{
group: User{name:"tom", gender: "girl", age: 18}, // 另起一行必须要加上,号
}
fmt.Println(_g) // {{tom girl 18}}
fmt.Println(_g.group.name) // tom, 访问结构属性
}
Go 匿名结构和字段
匿名结构
package main
import (
"fmt"
)
func main() {
// 创建和初始化一次性匿名结构
Student := struct {
name string
gender string
age int
}{
name: "张三",
gender: "女",
age: 18,
}
fmt.Println(Student) // {张三 女 18}
_Student
}
匿名字段
package main
import "fmt"
//创建一个结构匿名字段
type student struct {
int
string
float64
}
// Main function
func main() {
// 将值分配给匿名,学生结构的字段
value := student{123, "Bud", 8900.23}
fmt.Println("入学人数 : ", value.int) // 入学人数 : 123
fmt.Println("学生姓名 : ", value.string) // 学生姓名 : Bud
fmt.Println("套餐价格 : ", value.float64) // 套餐价格 : 8900.23
}
不允许
// 不允许创建两个或多个相同类型的字段
type student struct{
int
int
}
结构体内嵌类型
package main
import "fmt"
type Group struct {
group string
}
type User struct {
uname string
Group
}
func main() {
var _u User
_u.uname = "小明"
_u.group = "技术部"
fmt.Println(_u) // {小明 {技术部}}
}
函数用作结构体字段
//使用匿名函数作为Go结构中的一个字段
package main
import "fmt"
//创建结构
type Author struct {
name string
language string
Tarticles int
Particles int
Pending func(int, int) int
}
func main() {
//初始化结构字段
result := Author{
name: "Sonia",
language: "Java",
Tarticles: 340,
Particles: 259,
Pending: func(Ta int, Pa int) int {
return Ta - Pa
},
}
fmt.Println("作者姓名: ", result.name)
fmt.Println("语言: ", result.language)
fmt.Println("文章总数: ", result.Tarticles)
fmt.Println("发表文章总数: ", result.Particles)
fmt.Println("待处理文章: ", result.Pending(result.Tarticles, result.Particles))
// 作者姓名: Sonia
// 语言: Java
// 文章总数: 340
// 发表文章总数: 259
// 待处理文章: 81
}
Go指针
指针的引用
//使用匿名函数作为Go结构中的一个字段
package main
import "fmt"
func main() {
b := 255
var a *int = &b
fmt.Println(a) // 变量的指针地址 0xc00018c000
fmt.Println(b) // 变量值
}
指针解引用
//使用匿名函数作为Go结构中的一个字段
package main
import "fmt"
func main() {
b := 255
a := &b // 引用
fmt.Println(a) // 打印指针地址
fmt.Println(*a) // 解引用
}
传递指针给函数
//使用匿名函数作为Go结构中的一个字段
package main
import "fmt"
func change(val *int) {
*val = 55
}
func main() {
a := 59
b := &a // 把 b 的值指向 a 的指针地址
change(b) // 传递指针给函数,并修改指针地址
fmt.Println(a)
}
函数返回指针地址
package main
import "fmt"
func res() *int {
//局部变量, 函数内部使用简短运算符声明
status := 200
return &status
}
func main() {
//调用函数
response := res()
fmt.Println(response) // 0xc000014048 指针地址
fmt.Println(*response) // 200 变量值
}
结构体指针
package main
import "fmt"
type group struct {
name string
}
func main() {
test := group{"abc"}
pts := &test
pts.name = "xyz"
fmt.Println(pts) // &{xyz}
fmt.Println(*pts) // {xyz}
fmt.Println(test) // {xyz}
}
指针比较
== 运算符:如果两个指针都指向同一个变量,则此运算符返回true。或如果两个指针都指向不同的变量,则返回false。
!= 运算符:如果两个指针都指向同一个变量,则此运算符返回false。或如果两个指针都指向不同的变量,则返回true。
package main
import "fmt"
func main() {
val1 := 2345
val2 := 567
//创建和初始化指针
var p1 *int
p1 = &val1
p2 := &val2
p3 := &val1
// 使用 != 运算符比较指针
res1 := &p1 != &p2
fmt.Println("p1指针不等于p2指针吗: ", res1)
res2 := p1 != p2
fmt.Println("p1指针不等于p2指针吗: ", res2)
res3 := p1 != p3
fmt.Println("p1指针不等于p3指针吗: ", res3)
res4 := p2 != p3
fmt.Println("p2指针不等于p3指针吗: ", res4)
res5 := &p3 != &p1
fmt.Println("p3指针不等于p1指针吗: ", res5)
}
指针容量和长度
package main
import "fmt"
func main() {
// 创建一个容量为8个的数组
arr := [8]int{1,2,3,4,5,6,7,8}
// 定义循环变量类型
// var x int
// 创建一个容量为 5 的指针
var p [5]*int
// 给p分配指针地址
for x := 0; x < len(p); x++ {
p[x] = &arr[x]
}
// 打印容量为5的指针地址的值
for x := 0; x < len(p); x++ {
fmt.Println(*p[x])
}
fmt.Println("数组容量:", cap(arr))
fmt.Println("指针容量:", cap(p))
fmt.Println("指针容量:", cap(p))
fmt.Println("指针长度:", len(p))
// 1
// 2
// 3
// 4
// 5
// 数组容量: 8
// 指针容量: 5
// 指针容量: 5
// 指针长度: 5
}
Go 方法接收器
方法简单示例
package main
import "fmt"
// 创建一个个结构体
type Student struct {
name string
gender string
age int
}
// 定义Reciver()方法,作为 Student 接收器类型
func (e Student) Reciver() {
fmt.Printf("Student of %s is %s %d", e.name, e.gender, e.age)
}
// 赋值,并调用 Student 类型的 Reciver() 方法
func main() {
emps := Student{"Tom", "women", 18}
emps.Reciver() // Student of Tom is women 18
}
// 理解: 在 Student 结构体类型上创建了一个 Reciver() 方法, Reciver() 在方法的内部访问了接收器 e Student,
// 并使用接收器 e,并打印 Student 的 name、gender 和 age 这 3 个字段
等同于
// Reciver() 方法被转化为一个函数,Student 结构体被当做参数传递给它。
package main
import "fmt"
// 创建一个个结构体
type Student struct {
name string
gender string
age int
}
// Reciver()方法被转化为一个函数,把 Student 当做参数传入。
func Reciver(e Student) {
fmt.Printf("Student of %s is %s %d", e.name, e.gender, e.age)
}
// 赋值,并调用 Student 类型的 Reciver() 方法
func main() {
emps := Student{"Tom", "women", 18}
Reciver(emps) // Student of Tom is women 18
}
在结构上定义接收器方法
package main
import (
"fmt"
"math"
)
// 结构体 R1
type R1 struct {
lenght int
width int
}
// 结构体 R2
type R2 struct {
radius float64
}
// R1 接收器, 使用 return 返回
func (r R1) gago() int {
return r.lenght + r.width
}
// R2 接收器, 使用 return 返回
func (c R2) gago() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
r := R1{lenght: 5, width: 10}
fmt.Println(r.gago()) // 15
c := R2{radius: 12}
fmt.Println(c.gago()) // 452.3893421169302
}
指针接收器与值接收器
指针接收器: 考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体,这是很昂贵的。在这种情况下使用指针接收器,结构体不会被拷贝,只会传递一个指针到方法内部使用。
值接收器: 除了指针接收器外,在其他的所有情况,都可以被使用值接收器
package main
import (
"fmt"
)
type Student struct {
name string
age int
}
/* 使用值接收器 */
func (e Student) ModifyName(newName string) {
e.name = newName
}
/* 使用指针接收器 */
func (e *Student) ModifyAge(newAge int) {
e.age = newAge
}
func main() {
e := Student{name: "tom", age: 20,}
e.ModifyName("jack aaaa") // 调用值接收器
fmt.Println(e.name) // tom,发现前后都是一样,值没有改变
e.ModifyAge(50)
fmt.Println(e.age) // 50, 发现值已经改变,变成50
}
匿名字段的方法
package main
import (
"fmt"
)
type address struct {
city string
state string
}
func (e address) fullAddress() {
fmt.Println(e.city , e.state)
}
// 匿名结构体
type person struct {
firstName string
lastName string
address
}
func main() {
p := person{
firstName: "张",
lastName: "三",
address: address{
city: "BeiJing",
state: "JinShuiQu",
},
}
p.fullAddress() // BeiJing JinShuiQu
}
在方法中使用值接收器 与 在函数中使用值参数
当一个函数有一个值参数,它只能接受一个值参数
当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
func area(r rectangle) {
fmt.Printf("Area Function result: %d\n", (r.length * r.width))
}
func (r rectangle) area() {
fmt.Printf("Area Method result: %d\n", (r.length * r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
area(r) // Area Function result: 50, 等同如下
r.area() // Area Method result: 50, 等同如上
p := &r
/*
compilation error, cannot use p (type *rectangle) as type rectangle
in argument to area
*/
//area(p)
p.area()//通过指针调用值接收器
}
在方法中使用指针接收器 与 在函数中使用指针参数
和值参数相类似,函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
// 定义了一个接受指针参数的函数 perimeter
func perimeter(r *rectangle) {
fmt.Println("perimeter function output:", 2*(r.length+r.width))
}
// 定义了一个有一个指针接收器的方法
func (r *rectangle) perimeter() {
fmt.Println("perimeter method output:", 2*(r.length+r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
p := &r //pointer to r
perimeter(p) // perimeter function output: 30
p.perimeter() // perimeter method output: 30
/*
cannot use r (type rectangle) as type *rectangle in argument to perimeter
*/
//perimeter(r)
r.perimeter()//使用值来调用指针接收器 perimeter method output: 30
}
在非结构体上的方法
package main
import "fmt"
// 为内置类型 int 创建一个类型别名,然后创建一个以该类型别名为接收器的方法
// 为 int 创建了一个类型别名 myInt。我们定义了一个以 myInt 为接收器的的方法 add。
type myInt int
func (a myInt) add(b myInt) myInt {
return a + b
}
func main() {
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
fmt.Println("Sum is", sum)
}
Go 接口
静态接口
动态接口
语法:
type interface_name interface{
//方法签名
}
接口的声明与实现
package main
import "fmt"
// 定义接口
type MyApi interface {
// 定义接口下的方法,返回 rune 类型切片
Response() []rune
}
// 声明类型
type MyString string
// 接收器接受类型 MyString,并添加 Response() []rune 方法
func (ms MyString) Response() []rune {
// 方便操作,重新赋值返回值
var _data []rune
// 循环遍历类型 MyString, 也就是 ms 传值字符串
for _, i := range ms {
// 单引号表示字符,双引号表示字符串
if i == 'a' || i == 'e' {
_data = append(_data, i)
}
}
return _data
}
func main() {
name := MyString("Sam Anderson")
var api MyApi
// name 是 MyString 类型,api 类型是 Myapi
// 把 name 赋值给 api, api.Response() 调用 MyString 类型 Response()方法
api = name
fmt.Printf("%c", api.Response()) // [a e]
}
接口的实际用途
根据公司员工的个人薪资,计算公司的总支出。为了简单起见,我们假定支出的单位都是美元。 公司员工有着不同的薪资结构,以永久工和合同工薪酬为例
package main
import (
"fmt"
)
// 创建接口, 薪资计算器
type SalaryCalculator interface {
CalculateSalary() int
}
// 永久雇员工资结构体
type Permanent struct {
empId int
basicpay int
pf int
}
// 合同员工工资结构体
type Contract struct {
empId int
basicpay int
}
// 永久雇员的工资是基本工资和公积金的总和
func (p Permanent) CalculateSalary() int {
return p.basicpay + p.pf
}
// 合同工的工资是单独的基本工资
func (c Contract) CalculateSalary() int {
return c.basicpay
}
/*
作用说明:总费用是通过遍历SalaryCalculator切片并求和来计算的雇员个人的工资
方法说明:该方法接收一个 SalaryCalculator 接口的切片([]SalaryCalculator)作为参数
*/
func totalExpense(s []SalaryCalculator) {
// 设置初始总费用为:0
expense := 0
for _, v := range s {
expense = expense + v.CalculateSalary()
}
fmt.Printf("公司每月工资总费用 $%d", expense)
}
func main() {
pemp1 := Permanent{1, 5000, 20} // 第一个永久雇员的工资
pemp2 := Permanent{2, 6000, 30} // 第二个永久雇员的工资
cemp1 := Contract{3, 3000} // 合同工的工资
employees := []SalaryCalculator{pemp1, pemp2, cemp1}
totalExpense(employees) // 执行上面的方法
}
接口的内部表示
把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。
Test 接口只有一个方法 Tester(),而 MyFloat 类型实现了该接口。 我们把变量 f(MyFloat 类型)赋值给了 t(Test 类型)。现在 t 的具体类型为 MyFloat,而 t 的值为 89.7。
package main
import "fmt"
// 定义接口, Test 接口只有一个方法 Tester()
type Test interface {
Tester()
}
// 定义变量 MyFloat 为 float64
type MyFloat float64
// 定义接收器, MyFloat 类型实现了该接口
func (m MyFloat) Tester() {
fmt.Println(m)
}
func main() {
var t Test // 赋值 t 为接口
f := MyFloat(98.1) // 赋值 f 的变量值
t = f // 把 f (MyFloat) 类型的值赋值给 t ,t 的类型变成 MyFloat
t.Tester() // 调用接收器方法, 输出: 98.1
}
空接口
没有包含方法的接口称为空接口。空接口表示为interface{}。由于空接口没有方法,因此所有类型都实现了空接口。
package main
import "fmt"
// describe(i interface{}) 函数接收空接口作为参数,因此,可以给这个函数传递任何类型
func describe(i interface{}) {
fmt.Printf("Type = %T, value = %v\n", i, i)
}
func main() {
s := "Hello World"
describe(s) // Type = string, value = Hello World
i := 55
describe(i) // Type = int, value = 55
// 匿名结构体
strt := struct {
name string
}{
name: "Hungry",
}
describe(strt) // Type = struct { name string }, value = {Hungry}
}
类型断言
类型断言的语法是 i.(T)
package main
import "fmt"
// 如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。
// 如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错。
// v, ok := i.(T)
func assert(i interface{}) {
v, ok := i.(int)
fmt.Println(v, ok)
}
func main() {
var s interface{} = 56
assert(s) // 56 true
var i interface{} = "Steven Paul"
assert(i) // 0 false
}
类型选择(Type Switch)
i.(T) , 而对于类型选择,类型 T 由关键字 type 代替
package main
import "fmt"
// switch i.(type) 表示一个类型选择。每个 case 语句都把 i 的具体类型和一个指定类型进行了比较。如果 case 匹配成功,会打印出相应的语句
func FindType(i interface{}) {
switch i.(type) {
case string:
fmt.Printf("String %s\n", i.(string)) // String aaa
case int:
fmt.Printf("Int %d\n", i.(int)) // Int 111
default:
fmt.Printf("Unknow type\n") // Unknow type
}
}
func main() {
FindType("aaa")
FindType(111)
FindType(111.11)
}
类型与接口比较
还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。
package main
import "fmt"
// 定义一个接口
type Describer interface {
Db()
}
// 定义结构体
type Person struct {
name string
age int
}
// 接收器
func (p Person) Db() {
fmt.Printf("%s is %d years old", p.name, p.age)
}
// 定义一个方法
func findType(i interface{}) {
switch v := i.(type) {
case Describer:
v.Db()
default:
fmt.Printf("Unknow type\n")
}
}
func main() {
findType("aaaa") // Unknow type
// 结构体 Person 实现了 Describer 接口
p := Person{
name: "girl",
age: 18,
}
findType(p) // girl is 18 years old
}
指针接受者与值接受者
package main
import "fmt"
// 定义一个接口
type Describer interface {
Db()
}
// 定义一个结构体
type Person struct {
name string
age int
}
// 定义一个值接收器实现接口
func (p Person) Db() {
fmt.Printf("%s is %d years old\n", p.name, p.age)
}
type Address struct {
state string
country string
}
// 定义一个指针接收器实现接口
func (a *Address) Db() {
fmt.Printf("Statee %s Country %s", a.state, a.country)
}
func main() {
var d1 Describer // 赋值接口变量
p1 := Person{"tom", 29}
d1 = p1 // 赋值值给接口
d1.Db() // 调用接口方法: tom is 29 years old
p2 := Person{"Harry", 18}
d1 = &p2 // 赋值指针给接口
d1.Db() // 调用接口方法: Harry is 18 years old
/*
使用值接受者声明的方法,既可以用值来调用,也能用指针调用
*/
var d2 Describer
a := Address{"China","ShangHai"}
fmt.Println(&a) // &{China ShangHai} ,可以看出 &{China ShangHai}
d2 = &a
d2.Db() // 调用接口方法: Statee China Country ShangHai
/*
对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,
*/
}
实现多个接口
package main
import (
"fmt"
)
// 薪资计算器接口
type SalaryCalculator interface {
DisplaySalary()
}
// 离开计算器接口
type LeaveCalculator interface {
CalculateLeavesLeft() int
}
type Employee struct {
firstName string
lastName string
basicPay int
pf int
totalLeaves int
leavesTaken int
}
func (e Employee) DisplaySalary() {
fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}
func (e Employee) CalculateLeavesLeft() int {
return e.totalLeaves - e.leavesTaken
}
func main() {
e := Employee {
firstName: "Naveen",
lastName: "Ramanathan",
basicPay: 5000,
pf: 200,
totalLeaves: 30,
leavesTaken: 5,
}
var s SalaryCalculator = e
s.DisplaySalary() // Naveen Ramanathan has salary $5200
var l LeaveCalculator = e
fmt.Println("\nLeaves left =", l.CalculateLeavesLeft()) // Leaves left = 25
}
// 声明了两个接口:SalaryCalculator 和 LeaveCalculator。
// 定义了结构体 Employee, 实现了 SalaryCalculator 接口的 DisplaySalary 方法, 又实现了LeaveCalculator 接口里的 CalculateLeavesLeft 方法
// 于是 Employee 就实现了 SalaryCalculator 和 LeaveCalculator 两个接口。
接口的零值
接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。
package main
import "fmt"
type Describer interface {
Describe()
}
func main() {
var d1 Describer
if d1 == nil {
fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
// d1 is nil and has type <nil> value <nil>
}
}
结合值和接口使用
指针类型实现接口:
package main
import (
"fmt"
)
type Stu struct{}
func (St *Stu) Show() {
fmt.Println("aaaaa")
}
type People interface {
Show()
}
func main() {
var p People = &Stu{} // 传递结构体指针
p.Show() // aaaaa
}
值类型实现接口:
package main
import (
"fmt"
)
type Stu struct{}
func (St Stu) Show() {
fmt.Println("aaaaa")
}
type People interface {
Show()
}
func main() {
var p People = Stu{} // 值类型
p.Show() // aaaaa
}
通过函数封装接口实现:
package main
import (
"fmt"
)
type Stu struct{}
func (St *Stu) Show() {
fmt.Println("aaaaa")
}
type People interface {
Show()
}
// 实现接口
func Line() People {
var stu *Stu
return stu
}
func main() {
p := Line()
p.Show()
}
Go 并发
Go 编程语言原生支持并发。Go 使用 Go 协程(Goroutine) 和信道(Channel)来处理并发
Go 协程
启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。
如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Helo world")
}
func main() {
go hello()
// time.Sleep(1 * time.Second) , 如果想输出 hello 函数方法,使用休眠,以便等待其他协程执行完毕
fmt.Println("main") // main
}
// 调用了 go hello() 之后,程序控制没有等待 hello 协程结束,立即返回到了代码下一行,打印 main function。接着由于没有其他可执行的代码,Go 主协程终止,于是 hello 协程就没有机会运行了。
启动多个 Go 协程(并发)
启动了两个 Go 协程。现在,这两个协程并发地运行。numbers 协程首先休眠 250 微秒,接着打印 1,然后再次休眠,打印 2,依此类推,一直到打印 5 结束。alphabete 协程同样打印从 a 到 e 的字母,并且每次有 400 微秒的休眠时间。 Go 主协程启动了 numbers 和 alphabete 两个 Go 协程,休眠了 3000 微秒后终止程序。
package main
import (
"fmt"
"time"
)
func numbers() {
for i := 0; i <= 5;i ++ {
time.Sleep(250 * time.Millisecond)
fmt.Printf("%d",i)
}
}
func alphabets() {
for i := 'a'; i <= 'e'; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%c ", i)
}
}
func main() {
go numbers()
go alphabets()
time.Sleep(3000 * time.Millisecond)
fmt.Println("main terminated")
}
// 0a 12b 3c 45d e main terminated
信道(channel)
无缓冲信道
Go 协程之间的管道,通过使用信道,数据也可以从一端发送,在另一端接收。实现 Go 协程间的通信。
信道的声明
所有信道都关联了一个类型。信道只能运输这种类型的数据,而运输其他类型的数据都是非法的。
package main
import (
"fmt"
)
func main() {
var a chan int // 定义信道 a, 类型 int,默认零值
if a == nil {
a := make(chan int) // 用 make 来定义信道
fmt.Printf("Type of a is %T", a) // Type of a is chan int
}
}
信道进行发送和接收
默认是阻塞
data := <- a // 读取信道 a
a <- data // 写入信道 a
package main
import (
"fmt"
)
// Go 主协程发生了阻塞,等待信道 done 发送的数据。该信道作为参数传递给了协程 hello,
// hello 打印出 Hello world , done 写入数据。
// 当完成写入时,Go 主协程会通过信道 done 接收数据,于是它解除阻塞状态,打印出文本 main
/*
创建了一个 bool 类型的信道 done,并把 done 作为参数传递给了 hello 协程
通过信道 done 接收数据, 这一行代码发生了阻塞,
除非有协程向 done 写入数据,否则程序不会跳到下一行代码
于是,这就不需要用以前的 time.Sleep 来阻止 Go 主协程退出了。
*/
func hello(done chan bool) {
fmt.Println("Hello world")
done <- true // done 接收写入数据
}
func main() {
done := make(chan bool)
go hello(done)
<- done // 读取数据
fmt.Println("main")
}
/*
Hello world
main
*/
在一个单独的 Go 协程计算平方和,而在另一个协程计算立方和,最后在 Go 主协程把平方和与立方和相加。
package main
import (
"fmt"
)
func calcSquares(number int, squareop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit
number /= 10
}
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
cubeop <- sum
}
func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares + cubes)
}
死锁
当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。
package main
func main() {
ch := make(chan int)
ch <- 5
}
// 错误信息
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox249677995/main.go:6 +0x80
单向信道
信道转换,把一个双向信道转换成唯送信道或者唯收(Receive Only)信道都是行得通的
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
cha1 := make(chan int)
go sendData(cha1)
fmt.Println(<-cha1)
}
我们创建了一个双向信道 cha1。在第 11 行 cha1 作为参数传递给了 sendData 协程。在第 5 行,函数 sendData 里的参数 sendch chan<- int 把 cha1 转换为一个唯送信道。于是该信道在 sendData 协程里是一个唯送信道,而在 Go 主协程里是一个双向信道。该程序最终打印输出 10。
关闭信道和使用 for range 遍历信道
成功接收信道所发送的数据,那么 ok 等于 true。而如果 ok 等于 false
v, ok := <- ch
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <- ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
producer 协程会从 0 到 9 写入信道 chn1,然后关闭该信道。 主函数有一个无限的 for 循环,使用变量 ok 检查信道是否已经关闭。如果 ok 等于 false,说明信道已经关闭,于是退出 for 循环。如果 ok 等于 true,会打印出接收到的值和 ok 的值。
Received 0 true
Received 1 true
Received 2 true
Received 3 true
Received 4 true
Received 5 true
Received 6 true
Received 7 true
Received 8 true
Received 9 true
优化
for range 循环用于在一个信道关闭之前,从信道接收数据。
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
// for range 循环从信道 ch 接收数据,直到该信道关闭。一旦关闭了 ch,循环会自动结束。该程序会输出:
for v := range ch {
fmt.Println("Received ",v)
}
}
Received 0
Received 1
Received 2
Received 3
Received 4
Received 5
Received 6
Received 7
Received 8
Received 9
缓冲信道
创建一个有缓冲(Buffer)的信道。只在缓冲已满的情况,才会阻塞向缓冲信道(Buffered Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据。
语法
// 信道有缓冲 capacity 应该大于 0。无缓冲信道的容量默认为
ch := make(chan type, capacity)
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 6; i++ {
ch <- i
fmt.Println("Successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
// 缓冲值是 2
ch := make(chan int, 2)
go write(ch)
// <- ch
time.Sleep(1 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(1 * time.Second)
}
}
Successfully wrote 0 to ch
Successfully wrote 1 to ch
read value 0 from ch
Successfully wrote 2 to ch
read value 1 from ch
Successfully wrote 3 to ch
read value 2 from ch
Successfully wrote 4 to ch
read value 3 from ch
Successfully wrote 5 to ch
read value 4 from ch
read value 5 from ch
死锁
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 2)
ch <- "naveen"
ch <- "paul"
ch <- "steve"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
在上面程序里,我们向容量为 2 的缓冲信道写入 3 个字符串。当在程序控制到达第 3 次写入时,由于它超出了信道的容量,因此这次写入发生了阻塞。现在想要这次写操作能够进行下去,必须要有其它协程来读取这个信道的数据。但在本例中,并没有并发协程来读取这个信道,因此这里会发生死锁(deadlock)。程序会在运行时触发 panic
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox274756028/main.go:11 +0x100
长度 vs 容量
缓冲信道的容量是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。
缓冲信道的长度是指信道中当前排队的元素个数。
package main
import (
"fmt"
)
func main() {
ch := make(chan string, 3)
ch <- "naveen"
ch <- "paul"
fmt.Println("capacity is", cap(ch)) // 信道容量 capacity is 3
fmt.Println("length is", len(ch)) // 信道长度 length is 2
fmt.Println("read value", <-ch) // 打印内容 read value naveen
fmt.Println("new length is", len(ch)) // 使用过后的长度 new length is 1
}
WaitGroup 工作池
WaitGroup 用于等待一批 Go 协程执行结束。程序控制会一直阻塞,直到这些协程全部执行完毕
假设我们有 3 个并发执行的 Go 协程(由 Go 主协程生成)。Go 主协程需要等待这 3 个协程执行结束后,才会终止。这就可以用 WaitGroup 来实现。
package main
import (
"fmt"
"sync"
"time"
)
func process(i int, wg *sync.WaitGroup) {
fmt.Println("started Goroutine ", i)
time.Sleep(2 * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
wg.Done() // 减少计数器,调用 WaitGroup 的 Done() 方法
}
func main() {
no := 3
var wg sync.WaitGroup // 创建 WaitGroup 类型变量,初始值为 0
for i := 0; i < no; i++ {
wg.Add(1) // 调用 WaitGroup 的 Add 并传递一个 int 时.,WaitGroup 的计数器会加上 Add 的传参
go process(i, &wg)
}
wg.Wait() // Wait() 方法会阻塞调用它的 Go 协程,直到计数器变为 0 后才会停止阻塞
fmt.Println("All go routines finished executing")
}
// 由于 Go 协程的执行顺序不一定
started Goroutine 2
started Goroutine 1
started Goroutine 0
Goroutine 0 ended
Goroutine 1 ended
Goroutine 2 ended
All go routines finished executing
工作池缓冲信道实现
1、创建一个 Go 携程池,监听一个等待作业分配的输入型缓冲信道
2、将作业添加到该输入型缓冲信道中
3、作业完成后,再将结果写入一个输出型缓冲信道
4、从输出型缓冲信道读取并打印结果
package main
import (
"fmt"
"sync"
"time"
"math/rand"
)
// 第一步: 创建结构体,表示作业和结果
type Job struct {
id int
randomno int // 用于计算其每位数之和
}
type Result struct {
job Job // 表示所对应的作业
sumofdigits int // 表示计算的结果(每位数字之和)
}
// 第二步:创建用于接受作业和写入结果的缓冲信道
var jobs = make(chan Job, 10)
var results = make(chan Result, 10)
// digits 函数的任务实际上就是计算整数的每一位之和,最后返回该结果
func digits(number int) int {
// 默认结果未 0
sum := 0
// 赋值变量
no := number
for no != 0 {
digits := no % 10
sum += digits
no /= 10
}
// 为了模拟出 digits 在计算过程中花费了一段时间,我们在函数内添加了两秒的休眠时间
time.Sleep(1 * time.Second)
return sum
}
// 然后,我们写一个创建工作协程的函数
/*
创建一个工作者 worker, 读取 jobs 信道的数据,根据当前的 job 和 digits 的返回值,
创建了一个 Result 结构体变量,然后将结果写入 results 缓冲信道。
worker 函数接收一个 WaitGroup 类型的 wg 作为参数,当所有的 jobs 完成时候,调用 Done() 方法
*/
func worker(wg *sync.WaitGroup) {
for job := range jobs {
output := Result{job, digits(job.randomno)}
results <- output
}
wg.Done()
}
// creatWorkerPool 函数创建了一个 Go 协程的工作池
/*
函数的参数是需要创建的工作协程的数量
在创建 Go 协程之前,它调用了 wg.Add(1)方法,于是 WaitGroup 计数器递增,
接下来,我们创建工作协程,并向 worker 函数传递 wg 地址。
创建了需要的工作协程后,函数调用了 wg.Wait(),等待所有的 Go 协程执行完毕。
所有协程完成执行之后,函数会关闭 results 信道。
因为所有协程都已经执行完毕,于是不再需要向 results 信道写入数据
*/
func createWorkerPool(noOfWorkers int) {
var wg sync.WaitGroup
for i := 0; i < noOfWorkers; i++ {
wg.Add(1)
go worker(&wg)
}
wg.Wait()
close(results)
}
// 现在我们已经有了工作池,继续编写一个函数,把作业分配给工作者
/*
allocate 函数接收所需创建的作业数量作为输入参数,生成了最大值为 9 的伪随机数,并使用该随机数创建了 Job 结构体变量。
这个函数把 for 循环的计数器 i 作为 id,最后把创建的结构体变量写入 jobs 信道。当写入所有的 job 时,它关闭了 jobs 信道。
*/
func allocate(noOfJobs int) {
for i := 0; i < noOfJobs ; i++ {
randomno := rand.Intn(9)
job := Job{i, randomno}
jobs <- job
}
close(jobs)
}
// 创建一个读取 results 信道和打印输出的函数。
/*
result 函数读取 results 信道,并打印出 job 的 id、输入的随机数、该随机数的每位数之和。
result 函数也接受 done 信道作为参数,当打印所有结果时,done 会被写入 true。
*/
func result(done chan bool) {
for result := range results {
fmt.Printf("Job id %d, input random no %d , sum of digits %d\n", result.job.id, result.job.randomno, result.sumofdigits)
}
done <- true
}
func main() {
startTime := time.Now()
noOfJobs := 10
go allocate(noOfJobs)
done := make(chan bool)
go result(done)
noOfWorkers := 5
createWorkerPool(noOfWorkers)
<-done
endTime := time.Now()
diff := endTime.Sub(startTime)
fmt.Println("total time taken ", diff.Seconds(), "seconds")
}
Job id 4, input random no 4 , sum of digits 4
Job id 1, input random no 6 , sum of digits 6
Job id 0, input random no 5 , sum of digits 5
Job id 2, input random no 2 , sum of digits 2
Job id 3, input random no 2 , sum of digits 2
Job id 5, input random no 6 , sum of digits 6
Job id 7, input random no 8 , sum of digits 8
Job id 6, input random no 7 , sum of digits 7
Job id 8, input random no 4 , sum of digits 4
Job id 9, input random no 6 , sum of digits 6
total time taken 2.000395302 seconds
随着工作协程数量增加,完成作业的总时间会减少。你们可以练习一下:在 main 函数里修改 noOfJobs 和 noOfWorkers 的值,并试着去分析一下结果。
select(信道操作)
select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select 会随机地选取其中之一执行。该语法与 switch 类似,所不同的是,这里的每个 case 语句都是信道操作
默认情况
在没有 case 准备就绪时,可以执行 select 语句中的默认情况(Default Case)。这通常用于防止 select 语句一直阻塞。
package main
import (
"fmt"
"time"
)
func process(ch chan string) {
time.Sleep(10500 * time.Millisecond)
ch <- "process successfull"
}
func main() {
ch := make(chan string)
go process(ch)
for {
time.Sleep(1000 * time.Millisecond)
select {
case v := <- ch:
fmt.Println("received value:", v)
return
default:
fmt.Println("No have value")
}
}
}
/*
并发地调用了 process 协程之后,主协程启动了一个无限循环。
这个无限循环在每一次迭代开始时,都会先休眠 1000 毫秒(1 秒),然后执行一个 select 操作。
在最开始的 10500 毫秒中,由于 process 协程在 10500 毫秒后才会向 ch 信道写入数据,因此 select 语句的第一个 case(即 case v := <-ch:)并未就绪。
所以在这期间,程序会执行默认情况,该程序会打印 10 次 no value received。
在 10.5 秒之后,process 协程会在第 10 行向 ch 写入 process successful。
现在,就可以执行 select 语句的第一个 case 了,程序会打印 received value: process successful,然后程序终止。该程序会输出:
*/
死锁与默认情况
package main
func main() {
ch := make(chan string)
select {
case <-ch:
}
}
// 创建了一个信道 ch。我们在 select 内部(第 6 行),试图读取信道 ch。由于没有 Go 协程向该信道写入数据,因此 select 语句会一直阻塞,导致死锁。该程序会触发运行时 panic,报错信息如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox416567824/main.go:6 +0x80
如果存在默认情况,就不会发生死锁,因为在没有其他 case 准备就绪时,会执行默认情况。我们用默认情况重写后
package main
import "fmt"
func main() {
ch := make(chan string)
select {
case <-ch:
default:
fmt.Println("default case executed")
}
}
如果 select 只含有值为 nil 的信道,也同样会执行默认情况。
package main
import "fmt"
func main() {
var ch chan string
select {
case v := <-ch:
fmt.Println("received value", v)
default:
fmt.Println("default case executed")
}
}
/*
ch 等于 nil,而我们试图在 select 中读取 ch(第 8 行)。如果没有默认情况,select 会一直阻塞,导致死锁。由于我们在 select 内部加入了默认情况,程序会执行它
*/
随机选取
当 select 由多个 case 准备就绪时,将会随机地选取其中之一去执行。
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(1 * time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
空 select
除非有 case 执行,select 语句就会一直阻塞着。在这里,select 语句没有任何 case,因此它会一直阻塞,导致死锁。
ackage main
func main() {
select {}
}
Mutex(加锁)
临界区: 当程序并发地运行时,多个 Go 协程不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为临界区
Mutex 用于提供一种加锁机制(Locking Mechanism),可确保在某时刻只有一个协程在临界区运行,以防止出现竞态条件。
方法
mutex.Lock()
mutex.Unlock()
mutex.Lock()
x = x + 1
mutex.Unlock()
// 如果有一个 Go 协程已经持有了锁(Lock),当其他协程试图获得该锁时,这些协程会被阻塞,直到 Mutex 解除锁定为止
使用 Mutex
我们创建了 1000 个 Go 协程。如果每个协程对 x 加 1,最终 x 期望的值应该是 1000, 使用 Mutex,修复竞态条件的问题。
package main
import (
"fmt"
"sync"
)
var x = 0
func increment(wg *sync.WaitGroup, m *sync.Mutex) {
m.Lock()
x = x + 1
m.Unlock()
wg.Done()
}
/*
我们修改了 increment 函数,将增加 x 的代码(x = x + 1)放置在 m.Lock() 和 m.Unlock()之间。现在这段代码不存在竞态条件了,因为任何时刻都只允许一个协程执行这段代码。
*/
func main() {
var w sync.WaitGroup
var m sync.Mutex // 定义加锁
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, &m)
}
w.Wait()
fmt.Println("Final value of x", x) // Final value of x 1000
}
使用信道处理竞态条件
信道来处理竞态条件
package main
import (
"fmt"
"sync"
)
var x = 0
func increment(wg *sync.WaitGroup,ch chan bool) {
ch <- true
x = x + 1
<- ch
wg.Done()
}
func main() {
var w sync.WaitGroup
ch := make(chan bool, 1)
for i := 0; i < 10; i++{
w.Add(1)
go increment(&w,ch)
}
w.Wait()
fmt.Println("Finshed value of x", x) // Finshed value of x 10
}
Mutex vs 信道
当 Go 协程需要与其他协程通信时,可以使用信道。
而当只允许一个协程访问临界区时,可以使用 Mutex。
63 + 49 + 39.60
Go实现面向对象
使用结构体,而非类
Go 不支持类,而是提供了结构体。结构体中可以添加方法。这样可以将数据和操作数据的方法绑定在一起,实现与类相似的效果。
opp/
├── employee
│ └── employee.go
└── main.go
employee.go
package employee
import (
"fmt"
)
type employee struct {
firstName string
lastName string
totalLeaves int
leavesTaken int
}
/*
Go 不支持类,但结构体能够很好地取代类,而以 New(parameters) 签名的方法可以替代构造器。
提供了一个可引用的 New 函数,该函数接收必要的参数,返回一个新创建的 employee 结构体变量。
*/
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
e := employee {firstName, lastName, totalLeave, leavesTaken}
return e
}
func (e employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}
main.go
package main
import "oop/employee"
func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining() // Sam Adolf has 10 leaves remaining
}
Go 组合取代继承
Go 不支持继承,但它支持组合(Composition)。组合一般定义为“合并在一起”。汽车就是一个关于组合的例子:一辆汽车由车轮、引擎和其他各种部件组合在一起。
通过嵌套结构体进行组合
例如: 组合的典型例子就是博客帖子。每一个博客的帖子都有标题、内容和作者信息
package main
import (
"fmt"
)
/*
1、创建 author 结构体, author 字段有 firename、lastname、bio
2、添加了 fullName() 方法,其中 author 作为接受者类型,该方法返回作者的全名
*/
type author struct {
firstName string
lastName string
bio string
}
func (a author) fullName() string {
return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}
/*
1、post 结构体的字段有 title、content, 嵌套匿名字段 author
2、post 可以访问 author 结构体的所有字段和方法
3、post 结构体添加了 details() 方法,用于打印标题、内容和作者的全名与简介
*/
type post struct {
title string
content string
author // 嵌套匿名字段 author
}
func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.fullName())
fmt.Println("Bio: ", p.bio)
}
func main() {
author1 := author{"张", "三", "Golang"}
post1 := post{"标题", "内容", author1}
post1.details()
}
Title: 标题
Content: 内容
Author: 张 三
Bio: Golang
结构体切片的嵌套
进一步处理这个示例,使用博客帖子的切片来创建一个网站
package main
import (
"fmt"
)
/*
1、创建 author 结构体, author 字段有 firename、lastname、bio
2、添加了 fullName() 方法,其中 author 作为接受者类型,该方法返回作者的全名
*/
type author struct {
firstName string
lastName string
bio string
}
func (a author) fullName() string {
return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}
/*
1、post 结构体的字段有 title、content, 嵌套匿名字段 author
2、post 可以访问 author 结构体的所有字段和方法
3、post 结构体添加了 details() 方法,用于打印标题、内容和作者的全名与简介
*/
type post struct {
title string
content string
author // 嵌套匿名字段 author
}
func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.fullName())
fmt.Println("Bio: ", p.bio)
}
/*
结构体切片的嵌套,
使用博客帖子的切片来创建一个网站
*/
type website struct {
posts []post // 结构体不能嵌套一个匿名切片,需要指定一个字段名来嵌套
}
func (w website) contents() {
fmt.Println("Contents of Website\n")
for _, v := range w.posts {
v.details()
fmt.Println()
}
}
func main() {
author1 := author{"张", "三", "Golang"}
post1 := post{
"Inheritance in Go",
"Go supports composition instead of inheritance",
author1,
}
post2 := post{
"Struct instead of Classes in Go",
"Go does not support classes but methods can be added to structs",
author1,
}
post3 := post{
"Concurrency",
"Go is a concurrent language and not a parallel one",
author1,
}
w := website{
posts: []post{post1,post2, post3},
}
w.contents()
}
Contents of Website
Title: Inheritance in Go
Content: Go supports composition instead of inheritance
Author: 张 三
Bio: Golang
Title: Struct instead of Classes in Go
Content: Go does not support classes but methods can be added to structs
Author: 张 三
Bio: Golang
Title: Concurrency
Content: Go is a concurrent language and not a parallel one
Author: 张 三
Bio: Golang
63 + 49 + 39.60
多态
所有实现了接口的类型,都可以把它的值保存在一个接口类型的变量中。在 Go 中,我们使用接口的这种特性来实现多态。
使用接口实现多态
假设某个虚构的组织所获得的收入来源于两个项目:fixed billing 和 time and material。该组织的净收益等于这两个项目的收入总和
package main
import "fmt"
// 定义一个接口, 接口里有两个方法
type Inome interface {
calculate() int // 计算并返回项目的收入
source() string // 返回项目名称
}
// 项目类型一
type FixedBilling struct {
projectName string // 项目名称
biddedAmount int // 该项目投标的金额
}
// 项目类型二
type TimeAndMaterial struct {
projectName string // 项目名称
noOfHours int
hourlyRate int
}
// 下一步我们给这些结构体类型定义方法,计算并返回实际收入和项目名称。
// 由于 FixedBilling 和 TimeAndMaterial 两个结构体都定义了 Income 接口的两个方法:calculate() 和 source(),
// 因此这两个结构体都实现了 Income 接口。
func (fb FixedBilling) calculate() int {
return fb.biddedAmount // 返回FixedBilling实际收入
}
func (fb FixedBilling) source() string {
return fb.projectName // 返回FixedBilling项目名称。
}
func (tm TimeAndMaterial) calculate() int {
return tm.noOfHours * tm.hourlyRate // 返回 TimeAndMaterial 实际收入,收入等于 noOfHours 和 hourlyRate 的乘积
}
func (tm TimeAndMaterial) source() string {
return tm.projectName // 返回 TimeAndMaterial 项目名称。
}
/*
声明一个 calculateNetIncome 函数,用来计算并打印总收入。
接收一个 Income 接口类型的切片作为参数。该函数会遍历这个接口切片,并依个调用 calculate() 方法,计算出总收入。该函数同样也会通过调用 source() 显示收入来源。根据 Income 接口的具体类型,程序会调用不同的 calculate() 和 source() 方法。于是,我们在 calculateNetIncome 函数中就实现了多态。
*/
func calculateNetIncome(ic []Inome) {
var netincome int = 0
for _, i := range ic {
fmt.Printf("Income From %s = $%d\n", i.source(), i.calculate())
netincome += i.calculate()
}
fmt.Printf("Net income of organisation = $%d", netincome)
}
/*
main 函数中,我们创建了三个项目,有两个是 FixedBilling 类型,一个是 TimeAndMaterial 类型。接着我们创建了一个 Income 类型的切片,存放了这三个项目。由于这三个项目都实现了 Interface 接口,因此可以把这三个项目放入 Income 切片。最后我们将该切片作为参数,调用了 calculateNetIncome 函数,显示了项目不同的收益和收入来源。
如果在该组织以后增加了新的收入来源,calculateNetIncome 无需修改一行代码,就可以正确地计算总收入了。:smile:
*/
func main() {
project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
incomeStreams := []Inome{project1, project2, project3} // 定义接口传值切片
calculateNetIncome(incomeStreams)
}
Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Net income of organisation = $19000
新增收益流(动态添加了新项目)
// 上面代码添加
type Advertisement struct {
adName string
CPC int
noOfClicks int
}
func (a Advertisement) calculate() int {
return a.CPC * a.noOfClicks
}
func (a Advertisement) source() string {
return a.adName
}
// 上面main代码修改如下
func main() {
project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}
popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}
incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}
calculateNetIncome(incomeStreams)
}
Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Income From Banner Ad = $1000
Income From Popup Ad = $3750
Net income of organisation = $23750
尽管我们新增了收益流,但却完全没有修改 calculateNetIncome 函数。这就是多态带来的好处。由于新的 Advertisement 同样实现了 Income 接口,所以我们能够向 incomeStreams 切片添加 Advertisement。calculateNetIncome 无需修改,因为它能够调用 Advertisement 类型的 calculate() 和 source() 方法。
错误处理
示例
package main
import (
"fmt"
"os"
)
/*
在处理错误时,通常都是将返回的错误与 nil 比较
nil 值表示了没有错误发生,而非 nil 值表示出现了错误
*/
func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err) // open /test.txt: no such file or directory
return
}
fmt.Println(f.Name())
}
错误类型的表示
type error interface {
Error() string
}
获取错误详细信息的各种方法
1、断言底层结构体类型,使用结构体字段获取更多信息
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("/test.txt")
// 使用了类型断言(Type Assertion)来获取 error 接口的底层值
if err, ok := err.(*os.PathError); ok {
fmt.Println("File at path", err.Path, "failed to open")
return
}
fmt.Println(f.Name(), "opened successfully")
}
2、断言底层结构体类型,调用方法获取更多信息
编写一个程序,断言 *DNSError 类型,并调用这些方法来确定该错误是临时性错误,还是由超时导致的。
package main
import (
"fmt"
"net"
)
func main() {
addr, err := net.LookupHost("golangbot123.com")
if err, ok := err.(*net.DNSError); ok {
if err.Timeout() {
fmt.Println("operation timed out")
} else if err.Temporary() {
fmt.Println("temporary error")
} else {
fmt.Println("generic error: ", err)
}
return
}
fmt.Println(addr)
}
3、直接比较
查询了模式为 [ 的文件,然而这个模式写的不正确。我们检查了该错误是否为 nil。为了获取该错误的更多信息,将 error 直接与 filepath.ErrBadPattern 相比较。如果该条件满足,那么该错误就是由模式错误导致的
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, error := filepath.Glob("[")
if error != nil && error == filepath.ErrBadPattern {
fmt.Println(error)
return
}
fmt.Println("matched files", files)
}
4、不可忽略错误
package main
import (
"fmt"
"path/filepath"
)
func main() {
// 通过使用 _ 空白标识符,我忽略了 Glob 函数返回的错误
files, _ := filepath.Glob("[")
fmt.Println("matched files", files) // matched files []
}
使用 New 函数创建自定义错误
package main
import (
"errors"
"fmt"
"math"
)
/*
定义方法,接收一个 float64 类型参数,返回 float64 和 error
New 函数自定义返回的错误信息
*/
func circleArea(radius float64) (float64, error) {
if radius < 0 {
return 0, errors.New("radius is less than zero")
}
return math.Pi * radius * radius, nil
}
func main() {
radius := -20.0
area, err := circleArea(radius)
// 检查错误是否等于 nil。如果不是 nil,我们会打印出错误并返回,否则我们会打印出圆的面积
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Area of circle %0.2f", area)
}
使用 Errorf 给错误添加更多信息
上面错误是固定的,如果我们想给错误信息添加额外的信息,比如把半径值打印出来
Errorf 函数会根据格式说明符,规定错误的格式,并返回一个符合该错误的字符串。
package main
import (
"fmt"
"math"
)
func circleArea(radius float64) (float64, error) {
if radius < 0 {
return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
}
return math.Pi * radius * radius, nil
}
func main() {
radius := -20.0
area, err := circleArea(radius)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Area of circle %0.2f", area)
}
使用结构体类型和字段提供错误的更多信息
package main
import (
"fmt"
"math"
)
type areaError struct {
err string
radius float64
}
func (e *areaError) Error() string {
return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}
func circleArea(radius float64) (float64, error) {
if radius < 0 {
return 0, &areaError{"radius is negative", radius}
}
return math.Pi * radius * radius, nil
}
func main() {
radius := -20.0
area, err := circleArea(radius)
if err != nil {
if err, ok := err.(*areaError); ok {
fmt.Printf("Radius %0.2f is less than zero", err.radius)
return
}
fmt.Println(err)
return
}
fmt.Printf("Area of rectangle1 %0.2f", area)
}
使用结构体类型的方法来提供错误的更多信息
编写一个计算矩形面积的程序。如果长或宽小于零,程序就会打印出错误。
// 第一步就是创建一个表示错误的结构体。
// 有了错误类型,我们来实现 error 接口,并给该错误类型添加两个方法,使它提供了更多的错误信息
package main
import "fmt"
type areaError struct {
err string //error description
length float64 //length which caused the error
width float64 //width which caused the error
}
// Error() string 方法中返回了关于错误的描述
func (e *areaError) Error() string {
return e.err
}
// 当 length 小于零时,lengthNegative() bool 方法返回 true
func (e *areaError) lengthNegative() bool {
return e.length < 0
}
// 当 width 小于零时,widthNegative() bool 方法返回 true
func (e *areaError) widthNegative() bool {
return e.width < 0
}
// rectArea 函数检查了长或宽是否小于零,如果小于零,rectArea 会返回一个错误信息,否则 rectArea 会返回矩形的面积和一个值为 nil 的错误。
func rectArea(length, width float64) (float64, error) {
err := ""
if length < 0 {
err += "length is less than zero"
}
if width < 0 {
if err == "" {
err = "width is less than zero"
} else {
err += ", width is less than zero"
}
}
if err != "" {
return 0, &areaError{err, length, width}
}
return length * width, nil
}
/*
我们检查了错误是否为 nil。如果错误值不是 nil,我们会在下一行断言 *areaError 类型。
然后,我们使用 lengthNegative() 和 widthNegative() 方法,检查错误的原因是长度小于零还是宽度小于零。
这样我们就使用了错误结构体类型的方法,来提供更多的错误信息。
如果没有错误发生,就会打印矩形的面积。
*/
func main() {
length, width := -5.0, -9.0
area, err := rectArea(length, width)
if err != nil {
if err, ok := err.(*areaError); ok {
if err.lengthNegative() {
fmt.Printf("error: length %0.2f is less than zero\n", err.length)
}
if err.widthNegative() {
fmt.Printf("error: width %0.2f is less than zero\n", err.width)
}
return
}
fmt.Println(err)
return
}
fmt.Println("area of rect", area)
}
error: length -5.00 is less than zero
error: width -9.00 is less than zero
panic|recover
有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 panic 来终止程序
可以认为 panic 和 recover 与其他语言中的 try-catch-finally 语句类似
什么时候应该使用 panic
发生了一个不能恢复的错误,此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic,因为如果不能绑定端口,啥也做不了。
panic 有两个合理的用例。
1、发生了一个不能恢复的错误,此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic,因为如果不能绑定端口,啥也做不了。
2、发生了一个编程上的错误。 假如我们有一个接收指针参数的方法,而其他人使用 nil 作为参数调用了它。在这种情况下,我们可以使用 panic,因为这是一个编程错误:用 nil 参数调用了一个只能接收合法指针的方法。
panic 示例
package main
import (
"fmt"
)
func fullName(firstName *string, lastName *string) {
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
// 当出现了 panic 时,程序就会终止运行,打印出传入 panic 的参数
panic: runtime error: last name cannot be nil
// 接着打印出堆栈跟踪
goroutine 1 [running]:
main.fullName(0x1040c128, 0x0)
/tmp/sandbox135038844/main.go:12 +0x120
main.main()
/tmp/sandbox135038844/main.go:20 +0x80
发生 panic 时的 defer
当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止。
发生 panic 时,首先执行了延迟函数,接着控制返回到函数调用方,调用方的延迟函数继续运行,直到到达顶层调用函数。
package main
import (
"fmt"
)
func fullName(firstName *string, lastName *string) {
defer fmt.Println("deferred call in fullName")
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
This program prints,
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nil
goroutine 1 [running]:
main.fullName(0x1042bf90, 0x0)
/tmp/sandbox060731990/main.go:13 +0x280
main.main()
/tmp/sandbox060731990/main.go:22 +0xc0
recover
recover 是一个内建函数,用于重新获得 panic 协程的控制。
只有在延迟函数的内部,调用 recover 才有用
在延迟函数内调用 recover,可以取到 panic 的错误信息,并且停止 panic 续发事件(Panicking Sequence),程序运行恢复正常
如果在延迟函数的外部调用 recover,就不能停止 panic 续发事件。
package main
import (
"fmt"
)
// recoverName() 函数调用了 recover(),返回了调用 panic 的传参
func recoverName() {
if r := recover(); r!= nil {
fmt.Println("recovered from ", r)
}
}
func fullName(firstName *string, lastName *string) {
// 当 fullName 发生 panic 时,会调用延迟函数 recoverName(),
// 它使用了 recover() 来停止 panic 续发事件。
defer recoverName()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
recovered from runtime error: last name cannot be nil
returned normally from main
deferred call in main
panic,recover 和 Go 协程
只有在相同的 Go 协程中调用 recover 才管用。recover 不能恢复一个不同协程的 panic
package main
import (
"fmt"
"time"
)
func recovery() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}
func a() {
defer recovery()
fmt.Println("Inside A")
go b() // 不在同一个协程
time.Sleep(1 * time.Second)
}
func b() {
fmt.Println("Inside B")
panic("oh! B panicked")
}
func main() {
a()
fmt.Println("normally returned from main")
}
Inside A
Inside B
panic: oh! B panicked
goroutine 5 [running]:
main.b()
/tmp/sandbox388039916/main.go:23 +0x80
created by main.a
/tmp/sandbox388039916/main.go:17 +0xc0
由 go b() 修改为 b(),就可以恢复 panic 了, 因为 panic 发生在与 recover 相同的协程里。
恢复后获得堆栈跟踪
当我们恢复 panic 时,我们就释放了它的堆栈跟踪。实际上,在上述程序里,恢复 panic 之后,我们就失去了堆栈跟踪。
有办法可以打印出堆栈跟踪,就是使用 Debug 包中的 PrintStack 函数。
import (
"fmt"
"runtime/debug"
)
func r() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
debug.PrintStack()
}
}
func a() {
defer r()
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}
func main() {
a()
fmt.Println("normally returned from main")
}
// 首先已经恢复了 panic,打印出 Recovered runtime error: index out of range。此外,我们也打印出了堆栈跟踪。在恢复了 panic 之后,还打印出 normally returned from main。
Recovered runtime error: index out of range
goroutine 1 [running]:
runtime/debug.Stack(0x1042beb8, 0x2, 0x2, 0x1c)
/usr/local/go/src/runtime/debug/stack.go:24 +0xc0
runtime/debug.PrintStack()
/usr/local/go/src/runtime/debug/stack.go:16 +0x20
main.r()
/tmp/sandbox949178097/main.go:11 +0xe0
panic(0xf0a80, 0x17cd50)
/usr/local/go/src/runtime/panic.go:491 +0x2c0
main.a()
/tmp/sandbox949178097/main.go:18 +0x80
main.main()
/tmp/sandbox949178097/main.go:23 +0x20
normally returned from main
头等函数
可以把函数赋值给变量,也可以把函数作为其他函数的参数或者返回值
匿名函数
1、 匿名函数赋值变量
package main
import "fmt"
func main() {
a := func() {
fmt.Println("Hello wolrd")
}
a()
fmt.Printf("%T", a)
}
Hello wolrd
func()
2、匿名函数立即调用
package main
import (
"fmt"
)
func main() {
func() {
fmt.Println("hello world first class function")
}()
}
hello world first class function
3、向匿名函数传递参数
package main
import (
"fmt"
)
func main() {
func(n string) {
fmt.Println("Welcome", n)
}("Gophers")
}
Welcome Gophers
自定义的函数类型
package main
import "fmt"
// 创建一个新的函数类型 add, 接收两个整型类型,并返回一个整型
type add func(a int, b int) int
func main() {
var a add = func(a int, b int) int {
return a + b
}
s := a(5, 6)
fmt.Println("Sum", s) // Sum 11
}
高阶函数
高阶函数(Hiher-order Function)定义为:满足下列条件之一的函数:
接收一个或多个函数作为参数
返回值是一个函数
把函数作为参数,传递给其它函数
package main
import "fmt"
// 函数 simple 接收一个函数参数(该函数接收两个 int 参数,返回一个 a 整型)
func simple(a func(a, b int) int) {
fmt.Println(a(60,7))
}
func main() {
// 创建了一个匿名函数 f,其签名符合 simple 函数的参数
f := func(a, b int) int {
return a + b
}
simple(f) // 67
}
在其它函数中返回函数
package main
import (
"fmt"
)
// simple 函数返回了一个函数,并接受两个 int 参数,返回一个 int。
func simple() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
}
func main() {
/*
调用了 simple 函数。我们把 simple 的返回值赋值给了 s
调用了 s,并向它传递了两个 int 参数
*/
s := simple()
fmt.Println(s(60, 7)) // 67
}
闭包
当一个函数所访问的变量定义在函数体的外部时,称这样的匿名函数为闭包
package main
import "fmt"
func main() {
a := 5
// 访问了变量 a,而 a 存在于函数体的外部。因此这个匿名函数就是闭包。
func() {
fmt.Println("a=",a) // a= 5
}()
}
每一个闭包都会绑定一个它自己的外围变量
package main
import (
"fmt"
)
// 函数 appendStr 返回了一个闭包
func appendStr() func(string) string {
t := "Hello"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}
func main() {
a := appendStr()
b := appendStr()
fmt.Println(a("World")) // Hello World
fmt.Println(b("Everyone")) // Hello Everyone
fmt.Println(a("Gopher")) // Hello World Gopher
fmt.Println(b("!")) // Hello Everyone !
}
头等函数的实际用途
package main
import (
"fmt"
)
type student struct {
firstName string
lastName string
grade string
country string
}
/*
filter 的第二个参数是一个函数
这个函数接收 student 参数,返回一个 bool 值
*/
func filter(s []student, f func(student) bool) []student {
var r []student
for _, v := range s {
if f(v) == true {
r = append(r, v)
}
}
return r
}
func main() {
s1 := student{
firstName: "Naveen",
lastName: "Ramanathan",
grade: "A",
country: "India",
}
s2 := student{
firstName: "Samuel",
lastName: "Johnson",
grade: "B",
country: "USA",
}
s := []student{s1, s2}
// 这个函数作为参数传递给了 filter 函数
f := filter(s, func(s student) bool {
if s.grade == "B" {
return true
}
return false
})
fmt.Println(f)
}
示例二: 对切片的每个元素执行相同的操作,并返回结果
package main
import (
"fmt"
)
func iMap(s []int, f func(int) int) []int {
var r []int
for _, v := range s {
r = append(r, f(v))
}
return r
}
func main() {
a := []int{5, 6, 7, 8, 9}
r := iMap(a, func(n int) int {
return n * 5
})
fmt.Println(r) // [25 30 35 40 45]
}
反射
反射就是程序能够在运行时检查变量和值,求出它们的类型
reflect 包
reflect.Type: 表示 interface{} 具体类型
reflect.Value: 表示它的具体值
reflect.TypeOf() 返回 reflect.Type
reflect.ValueOf() 返回 reflect.Value
reflect.Kind: 表示该类型的特定类别
NumField(): 返回结构体中字段的数量
Field(): 返回字段 i 的 reflect.Value。
reflect 实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值
reflect.Type 和 reflect.Value
// 通用查询生成器,可以适用于任何结构体类型
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
// createQuery 函数接收 interface{} 作为参数
// 程序打印了接口的具体类型和具体值。
func createQuery(q interface{}) {
t := reflect.TypeOf(q) // reflect.TypeOf 接收了参数 interface{},返回了reflect.Type
v := reflect.ValueOf(q) // 返回了 reflect.Value
fmt.Println("Type ", t) // Type main.order
fmt.Println("Value ", v) // Value {456 56}
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
reflect.Kind
Type 表示 interface{} 的实际类型(在这里是 main.Order),而 Kind 表示该类型的特定类别
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q) // 实际类型main.order
k := t.Kind() // 该类型的特定类别(在这里是 struct)。
fmt.Println("Type ", t) // Type main.order
fmt.Println("Kind ", k) // Kind struct
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
NumField() 和 Field() 方法
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
/*
1、判断类型是否是结构体
2、NumField 方法只能在结构体上使用
*/
if reflect.ValueOf(q).Kind() == reflect.Struct {
v := reflect.ValueOf(q)
fmt.Println("Number of fields", v.NumField())
for i := 0; i < v.NumField(); i++ {
fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
}
}
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56
Int() 和 String() 方法
Int 和 String 可以帮助我们分别取出 reflect.Value 作为 int64 和 string。
package main
import (
"fmt"
"reflect"
)
func main() {
a := 56
x := reflect.ValueOf(a).Int()
fmt.Printf("type:%T value:%v\n", x, x) // type:int64 value:56
b := "Naveen"
y := reflect.ValueOf(b).String()
fmt.Printf("type:%T value:%v\n", y, y) // type:string value:Naveen
}
完整实例(通用查询器)
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
address string
salary int
country string
}
func createQuery(q interface{}) {
if reflect.ValueOf(q).Kind() == reflect.Struct {
t := reflect.TypeOf(q).Name()
query := fmt.Sprintf("insert into %s values(", t)
v := reflect.ValueOf(q)
for i := 0; i < v.NumField(); i++ {
switch v.Field(i).Kind() {
case reflect.Int:
if i == 0 {
query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
} else {
query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
}
case reflect.String:
if i == 0 {
query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())
} else {
query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())
}
default:
fmt.Println("Unsupported type")
return
}
}
query = fmt.Sprintf("%s)", query)
fmt.Println(query)
return
}
fmt.Println("unsupported type")
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
e := employee{
name: "Naveen",
id: 565,
address: "Coimbatore",
salary: 90000,
country: "India",
}
createQuery(e)
i := 90
createQuery(i)
}
Map(集合)
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
语法
/* 声明变量, 默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数*/
map_variable := make(map[key_data_type]value_data_type)
// 如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
map 示例
package main
import "fmt"
func main() {
var contryMap map[string]string // 创建集合
contryMap = make(map[string]string)
// map 插入key - value 对
contryMap ["中国"] = "北京"
contryMap ["美国"] = "纽约"
contryMap ["泰国"] = "曼谷"
contryMap ["英国"] = "伦敦"
// 使用键输出对应的首都地址
for contry := range contryMap {
fmt.Println(contry, " 首都是", contryMap [contry])
}
// 查看元素在集合中是否存在
capital, ok := contryMap ["迪拜"]
if(ok) {
fmt.Println("迪拜 首都是", capital)
} else {
fmt.Println("迪拜 首都不存在")
}
}
英国 首都是 伦敦
中国 首都是 北京
美国 首都是 纽约
泰国 首都是 曼谷
迪拜 首都不存在
delete 函数
delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key
package main
import "fmt"
func main() {
/* 创建map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
/*删除元素*/
delete(countryCapitalMap, "France")
/*打印地图*/
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
}
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi
Last updated