Go
相关资源
Golang官网https://golang.org/
Golang官网镜像
Golang中国http://www.golangtc.com/
Go语言中文网http://studygolang.com/
安装
编译器
安装包下载: 稳定版→go1.14.4 测试版→go1.15beta1
- 官网下载(需要翻墙): https://golang.org/dl/
- 官网镜像下载: https://golang.google.cn/dl/
- Go语言中文网下载: https://studygolang.com/dl
IDE
国产的LiteIDE,官网http://liteide.org/cn/
-
下载该IDE,然后解压.
.\bin\liteide.exe
点击即可使用 -
点开后,菜单栏下面有一个
Go
按钮,右面那个system就是环境.可以配置多个环境,每个环境之间的环境变量互不影响. -
编辑当前的环境可以点击它右面的黑电视.
GOROOT值为go编译器的安装路径 GOARCH值为go运行的CPU架构 GOOS值为go运行的操作系统 CGO_ENABLED值为go嵌入c代码开关 PATH 值为各GCC编译器命令位置
-
然后新建工程之类的,就跟其他IDE相同了
-
编译运行:可以直接用那个
[BR]
按键,编译运行. -
编译:那个B按钮;运行,那个R按钮.
单步调试
- 设置:菜单栏->调试->debugger/delve(这个可以更好的理解Go语言,官方说的)
- 编译代码,按那个B就可以编译了(检查语法错误)
- 设置断点,转到你要设置的行,按下F9即可
- 开始:按F5开始单步调试.再按F5执行到下一个断点;按F10,逐行运行;其他的调试指令可以点调试菜单就能用了!
好用的快捷键;
- Shift+Delete删除整行
- Ctrl+/快速注释
- Ctrl+R运行
- Ctrl+B编译
- Ctrl+F5 Ctrl+Shift+R运行
中文乱码
执行程序时,中文字符可能出现乱码.可以通过在命令行(终端)输入以下指令:chcp 65001
.具体参考如下:
chcp 65001 就是换成UTF-8代码页
chcp 936 可以换回默认的GBK
chcp 437 是美国英语
chcp 20936 是中文简体字符集GB2312
语法
源文件
- 以".go"作为拓展名
import
导入标准库或第三方包- 头部用package声明所属包名称,而且包名需要用双引号括起来
- main没有参数和返回值,且必须main包.想传参的话,可以在os.Args中保存
{
必须与函数声明或者控制结构放在同一行- 分号可以省略,必须出现的典型位置
for
循环或类似处
变量
- 用
var
定义变量,默认为零,避免意外. - 支持类型推断,通过初始值判定类型
- 短变量符号
:=
,省略var
.
var sum0 int
var sum1 int = 1
var label0 = "name"
label1 := "name"
-
查看变量的类型
fmt.Printf('%T',f)
if
与C大致架构相同,但条件表达式不用括起来
result := 1
if result > 0 {
result = 100
}else{
result = 1000
}
在条件之间可以包含一个简单语句,上述代码可以简化如下
if result := 1;result > 0{
result = 100
}else{
result = 1000
}
//此时result只在if里面有效
分支语句
- 默认fall-through,而不用手动加break.
- 简化跳转目标是相同的分支条件,用逗号空格
- 分支条件可以是任何有效的表达式
- 以true为switch条件的情况,可以省略true
result := 200
switch result {
case 100, 200:
fmt.Println(123)
default:
fmt.Println(321)
}
switch result := 300; true {
case result <= 100, result >= 1000:
fmt.Println(123)
case result > 100 && result < 1000:
fmt.Println(321)
}
switch result := 300; {
case result <= 100, result >= 1000:
fmt.Println(123)
case result > 100 && result < 1000:
fmt.Println(321)
}
随机数
package main
import (
"fmt"
"math/rand"
"time"
)
func main(){
rand.Seed(time.Now().Unix())
m := rand.Intn(10)//[0,9]
switch {
case m > 3:
fmt.Println(" > 3")
case m <= 3:
fmt.Println("<= 3")
}
}
循环
- go中只有for循环,而且不用括号括起来.
- 几种常见形式如下
i := 0
for i < 5 {
fmt.Println(i)
i++
}
/*
for 条件{
//do something
}
*/
/*
for 初始;条件;步进{
//do something
}
跟C几乎一样
*/
/*
这个不行
for i:=0;i<5{
fmt.Println(i)
i++
}*/
for i := 0; i < 5; i++ {
fmt.Println(i)
}
/*
for{
//do something
}
省略了true而已
*/
for i := 0; ; i++ {
if i > 5 {
break
}
fmt.Println(i)
}
for i := 0; ; {
if i > 5 {
break
}
fmt.Println(i)
i++
}
//缺省值都是true
-
for…range,返回集合中数据的索引和值
其中,索引是整数,值跟集合中元素相同
range右边的表达式必须是array, slice, string, map或 是指向array的指针,也可以是channel
x := []int{100, 101, 102}
for i, v := range x {
fmt.Println(i, ":", v)
fmt.Printf("%d,%d\n", i, v)
}
for i, v := range "Hello 世界" {
fmt.Printf("%d : %c\n", i, v)
}
fmt.Println(x)
函数
初代函数
-
go把返回值放在参数列表最后,左大括号之前.
而且,参数列表中
- 如果只在最后说明变量类型,则所有变量都是这个类型
- 如果每个变量不同,则需要在每个后面单独说明
/* C language
int add(int a,int b){
return a+b;
}
*/
//go language
func add(a,b int) int{
return a+b
}
func add2(a int, b float32) float32 {
return float32(a) + b
}
-
多返回值
返回参数列表用
(int,int)
这样类似的格式来声明func divide(a, b int) (int, int) { quotient := a / b remainder := a % b return quotient, remainder } func main() { a,b := divide(5,3) //必须要对应,不能少 }
-
提前确定返回值
相当于提前声明了这个变量,然后后面就不用再定义和return的时候也就不用再写具体的了
func divide2(a,b int) (quotient,remainder int){ quotient = a/b remainder = a%b return } //其他都跟之前相同
-
结合一波布尔值可以干异常判断
func divide3(a, b int) (quotient, remainder int, result bool) { if b == 0 { result = false return } result = true quotient = a / b remainder = a % b return } func main() { a, b, ok := divide3(5, 0) fmt.Println(a, b, ok) a, b, ok = divide3(9, 4) fmt.Println(a, b, ok) }
-
函数顺序没有C那样的前后关系
func main() { a,b := divide4(17,4) fmt.Println(a,b) } func divide4(a, b int) (int, int) { quotient := a / b remainder := a % b return quotient, remainder }
匿名函数
- 函数可以被当作一个数据类型来使用
- 匿名函数只能依附在其他函数里面
f := func(a,b int) int{
return a+b
}//声明了一个匿名函数f,直接把f搞成了一个函数类型
sum := f(2,3)//调用
sum2 := func(a,b int)int{
return a+b
}(6,9)
//sum2 = 15,int类型.直接调用了
-
闭包
匿名函数被称为闭包,原因是它根据一定方式使得其可见范围超过了定义范围.
go为声明匿名函数提供了简单的语法,像许多动态语言一样,这些匿名函数在它们被定义的范围内创建了词法闭包
- 闭包返回值是一个匿名函数
- 匿名函数嵌套在闭包内部
- 匿名函数引用了自身的外部变量x
- 闭包中的参数x是变量安全的,只有内部函数才能访问到
func makeAdder(x int) func(int) int {
return func(y int) int { return x + y }
}
func main() {
add5 := makeAdder(5)
add36 := makeAdder(36)
fmt.Println(add5(add36(1)))
}
//add5 : x+5
函数当作参数
函数可以当作参数进行传递,然后再其他函数内调用执行,一般称之为回调
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) //Add(y,2)
}
func main() {
callback(123, Add)
}
/*然后出一个结合版
func Add(a, b int) int {
return a + b
//fmt.Printf("The sum of %d and %d is %d\n", a, b, a+b)
}
func callback(y int, f func(int, int) int) int {
return f(y, 2) //Add(y,2)
}
func main() {
if result := callback(123, Add); result > 12345 {
fmt.Println("So Big!")
} else {
fmt.Println("Not so big?")
}
}
*/
延迟调用
defer语句,使用方法:defer 语句
,则无论函数执行是否报错,都确保再结束前将其调用.一般用于数据清理工作
如果有很多defer,从后往前执行
func Add(a, b int) int {
return a + b
}
func callback(y int, f func(int, int) int) int {
return f(y, 2) //Add(y,2)
}
func main() {
defer fmt.Println("This is a defer")
if result := callback(123, Add); result > 12345 {
fmt.Println("So Big!")
} else {
fmt.Println("Not so big?")
}
}
/*
运行结果
Not so big?
This is a defer
*/
异常恢复机制
- 内置函数panic,中断现在的控制流(程序),抛出一个异常流。
- 内置函数recover,捕获到panic的异常值并恢复。recover函数没有参数,返回值是异常本身
- recover仅再延迟函数中有效,即再defer语句中调用才可以
func main() {
f := func(x int) {
if x > 100 {
panic("参数超出范围")
} else {
fmt.Println("f成功调用")
}
fmt.Println("Oh?")
}
defer func() {
if err := recover(); err != nil {
//nil 就是C语言的 NULL
//recover相当于捕获了所有的异常
fmt.Println("程序异常退出:", err)
fmt.Println("Oh!!!!")
} else {
fmt.Println("正常退出")
}
}()
f(101)
}
程序输出为
程序异常退出: 参数超出范围
Oh!!!!
数组
留坑:数组名到底是啥东西?
原生数组
- 数组的数据类型是
[len]type
,可以套用先声明后采用的方法(采用Go的缺失值规则,默认都是0) - 直接带初始化的定义方式:
var name [len]type = [len]type{1,3,4,填够了就行}
这里一定要对应相等的长度,不然可以选择切片 - 支持短变量定义方式
b := [4]int{1,3,5,7}
- 支持索引初始化,也就是指定位置初始化.例如
array3 := [4]int{5, 3: 10}
此时的array3 = [5,0,0,10]
- 支持自动判断长度,同时兼并索引初始化,但该操作是相互影响的
array4 := [...]int{5, 10: 0}
此时的array4为[5 0 0 0 0 0 0 0 0 0 0]
.这里的[...]
就是让编译器自动判断长度的意思,但此时不可以省略,不然成了切片. - 下标从0开始,支持普通遍历
- 支持普通拷贝,也就是
b := a
func main() {
var array1 [4]int = [4]int{1, 3, 5, 7}
fmt.Println(array1)
array2 := [5]int{1, 3, 5, 7, 9}
fmt.Println(array2)
array3 := [4]int{5, 3: 10}
fmt.Println(array3)
array4 := [...]int{5, 10: 0}
fmt.Println(array4)
array5 := [...]int{
5,
19,
10: 123,
}
//这里需要注意:如果分成多行进行声明+初始化,那么你需要在最后一行的最后加上','这样才能正常结束.如果在单行就没有这个问题
fmt.Println(array5)
}
结构体数组
按照下面所说的结构体来定义,那么我们可以搞到下面的这种方式
type user struct {
name string
age byte
}
func main() {
d := [...]user{
{"Tom", 20},
{"Mary", 18},
}
fmt.Printf("%v\n%+v\n%#v\n", d, d, d)
}
输出分别为
[{Tom 20} {Mary 18}]
[{name:Tom age:20} {name:Mary age:18}]
[2]main.user{main.user{name:"Tom", age:0x14}, main.user{name:"Mary", age:0x12}}
说明:
%#v
以go语法格式输出,也就是最后一个输出%+v
以字面值方式输出,中间那个输出
多维数组
- 最常规的那种定义仍然可用
var a [2][3][4]int
- 省略长度只能用在第一维
var a [...][3][4]int
- 初始化方式与之前完全相同.
- len(array),cap(array)可以获得第一维的长度
func main() {
array1 := [...]int{
5,
19,
10: 123,
}
fmt.Println(array1)
array2 := [...][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
array3 := [...][3][4]int{
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 0, 1, 2},
},
3: {
{11, 12, 13, 14},
2: {19, 10, 11, 12},
},
}
fmt.Println(len(array2), array2)
fmt.Println(len(array3), array3)
for i, v := range array2 {
fmt.Println(i, v)
}
for i, v := range array3 {
fmt.Println(i, v)
}
}
输出结果
[5 19 0 0 0 0 0 0 0 0 123]
3 [[1 2 3] [4 5 6] [7 8 9]]
4 [[[1 2 3 4] [5 6 7 8] [9 0 1 2]] [[0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0]] [[11 12 13 14] [0 0 0 0] [19 10 11 12]]]
0 [1 2 3]
1 [4 5 6]
2 [7 8 9]
0 [[1 2 3 4] [5 6 7 8] [9 0 1 2]]
1 [[0 0 0 0] [0 0 0 0] [0 0 0 0]]
2 [[0 0 0 0] [0 0 0 0] [0 0 0 0]]
3 [[11 12 13 14] [0 0 0 0] [19 10 11 12]]
数组与指针
- 数组名不是头指针,而是一个组.对数组名取地址之后是一个数组,对应是一个引用的模式
%T
输出的是数据类型,%p
是指针.%v=value
值
func main() {
x, y := 10, 20
a := [...]*int{&x, &y}
p := &a
fmt.Printf("%T,%v\n", a, a)
fmt.Printf("%T,%p,%v,%v,%p\n", p, p, p, *p, &p)
b := [...]int{1, 2}
fmt.Println(&b, &b[0], &b[1])
pb := &b
fmt.Println(pb)
for i, v := range pb {
fmt.Println(i, " : ", v)
}
fmt.Println(b)
pb[1] += 10
fmt.Println(b)
}
输出结果
[2]*int,[0xc0000a0090 0xc0000a0098]
*[2]*int,0xc0000881f0,&[0xc0000a0090 0xc0000a0098],[0xc0000a0090 0xc0000a0098],0xc0000ca018
&[1 2] 0xc0000a00d0 0xc0000a00d8
&[1 2]
0 : 1
1 : 2
[1 2]
[1 12]
数组与函数传参
- C的数组名传参是一种传指针(因为数组名是指针);Go是传拷贝,因为数组名也不代表是数组首地址.
- 与普通赋值中的拷贝类似.如果要使用引用,考虑使用C++的引用的传递参数模式(后面一节有介绍)
- 对一个数组
a
取地址得到的是一个数组的地址*[2]int
类型
func test(x [2]int) {
fmt.Printf("x : %p,%v\n", &x, x)
}
func main() {
a := [2]int{10, 20}
var b [2]int = a
c := &a
fmt.Println("Type of c is")
fmt.Printf("%T\n", c)
fmt.Printf("a : %p,%v\n", &a, a)
fmt.Println(&a[0], &a[1], &c[0], &c[1])
fmt.Printf("b : %p,%v\n", &b, b)
fmt.Printf("c : %p,%v\n", &c, c)
test(a)
}
输出结果
Type of c is
*[2]int
a : 0xc00000c200,[10 20]
0xc00000c200 0xc00000c208 0xc00000c200 0xc00000c208
b : 0xc00000c210,[10 20]
c : 0xc000006028,&[10 20]
x : 0xc00000c280,[10 20]
数组,函数,指针
func test2(x *[2]int) {
x[1] += 120
for i, v := range x {
fmt.Println(i, v)
}
fmt.Println(x[1])
fmt.Printf("x : %p,%v\n", x, *x)
}
//使用这种方式可以当成是一个引用,(除了整体[也就是直接用数组名的时候]上用法的不同,都可以看作是一个引用)
func main() {
a := [2]int{10, 20}
var b [2]int = a
c := &a
fmt.Println("Type of c is")
fmt.Printf("%T\n", c)
fmt.Printf("a : %p,%v\n", &a, a)
fmt.Println(&a[0], &a[1], &c[0], &c[1])
fmt.Printf("b : %p,%v\n", &b, b)
fmt.Printf("c : %p,%v\n", &c, c)
test(a)
test2(&a)
fmt.Printf("a : %p,%v\n", &a, a)
}
输出结果
Type of c is
*[2]int
a : 0xc00000c200,[10 20]
0xc00000c200 0xc00000c208 0xc00000c200 0xc00000c208
b : 0xc00000c210,[10 20]
c : 0xc000006028,&[10 20]
x : 0xc00000c280,[10 20]
0 10
1 140
140
x : 0xc00000c200,[10 140]
a : 0xc00000c200,[10 140]
结构体
- 穿插一下定义,没有讲解
type user struct{
name string
age byte
}
func main() {
var d [4]user
fmt.Println(d)
}
- 定义一个结构体
type Point struct{
x,y float64
mask byte
}
- 创建对象
var p1 Point
var
- 初始化结构体
-
第一种 new的方式
t := new(T)
t现在是指向该结构体的指针 , 是 *T类型 -
第二种 var的方式
var t T
结构体现在就已经被初始化了 , 是T类型 -
字面量的方式
t := T{a, b} t := &T{} //等效于 new(T)
切片
朴素切片
这个切片不是Python那个切片,它类似于C++的动态数组。
- 创建的语法是
make([]type,len,cap)
,表示创建一个len长度的(初始是len长度),容量是cap的一个[]int,类型是[]type
的一个切片 - len不一定等于cap.如果len即将大于cap,那么cap=cap*2.
- 可以通过append(x,i)往x最后添加i,返回值是一个切片(不是直接修改原先的)
func main() {
x := make([]int, 0, 5)
fmt.Println(x, len(x), cap(x))
for i := 0; i < 10; i++ {
x = append(x, i)
}
fmt.Println(x, len(x), cap(x))
fmt.Printf("%T\n", x)
x1 := make([]int, 1, 5)
fmt.Println(x1)
var x2 []int = make([]int, 4, 5)
fmt.Println(x2)
var x3 = [...]int{1, 2, 3, 4, 5}
fmt.Printf("%T\n", x3)
fmt.Println(x3, len(x3), cap(x3))
}
[] 0 5
[0 1 2 3 4 5 6 7 8 9] 10 10
[]int
[0]
[0 0 0 0]
[5]int
[1 2 3 4 5] 5 5
数组切片
s := x[begin=0:end=len(x)]
创建一个数组x的切片,它的作用是提供一个数组的引用(高级引用?)
- 不填使用类缺失值
cap(s)
最小是len(x)-2
,len(s)
与切片的实际长度相同.但是如果切片大于len(x)-2
了,那么len(s) = cap(s)
等于实际长度.- s的改变会对应到x的改变.
- s可以通过append来增加长度,对应增加的也会影响到x.
- append如果过长,x不会变长,而是受影响到最长长度.
func main() {
x := [...]int{0, 1, 2, 3, 4, 5, 6}
s := x[2:4]
fmt.Printf("%T\n", s)
for i, v := range s {
fmt.Println(i, ":", v)
}
fmt.Println(len(s), cap(s))
s[0] = 5
fmt.Println(x)
s = append(s, 10)
fmt.Println(x)
s = append(s, 12)
s = append(s, 123)
fmt.Println(s, x)
s2 := x[0:len(x)]
fmt.Println(len(s2), cap(s2))
s3 := x[1:]
fmt.Println(s3, len(s3), cap(s3))
}
[]int
0 : 2
1 : 3
2 5
[0 1 5 3 4 5 6]
[0 1 5 3 10 5 6]
[5 3 10 12 123] [0 1 5 3 10 12 123]
7 7
[1 5 3 10 12 123] 6 6
map
跟别的map一样.
定义:gm := make(map[string]int)
相当于map<string,int> gm
增添:gm["book"]=10
;删除:delete(gm,"book")
遍历:for i,v := range gm
func main() {
gm := make(map[string]int)
gm["bed"] = 1000
gm["desk"] = 500
gm["chair"] = 300
gm["computer"] = 5000
gm["iphone"] = 6800
for i, v := range gm {
fmt.Println(i, v)
}
fmt.Println(gm["desk"])
delete(gm, "desk")
//fmt.Printf("%T\n", gm["bed"]) int?如何实现的两个返回值和一个返回值都有的?
if price, ok := gm["computer"]; ok {
fmt.Println(price, ok)
} else {
fmt.Println(price, ok)
}
if price, ok := gm["desk"]; ok {
fmt.Println(price, ok)
} else {
fmt.Println(price, ok)
}
fmt.Println(gm)
for i, v := range gm {
fmt.Println(i, v)
}
gm["book"] = 10
fmt.Println(gm)
}
bed 1000
desk 500
chair 300
computer 5000
iphone 6800
500
5000 true
0 false
map[bed:1000 chair:300 computer:5000 iphone:6800]
bed 1000
chair 300
computer 5000
iphone 6800
map[bed:1000 book:10 chair:300 computer:5000 iphone:6800]
对象
定义一个新的类
type 类名 struct{
成员 类型
}
- 成员或者函数,大写是public,小写是private
- new可以创建对象,返回是一个指针,初始化为0
- 可以建立的时候直接初始化,跟C++强制初始化一样
func main() {
type Point struct {
x, y float64
mask byte
}
var p1 Point
var p2 Point = Point{3, 4, 'a'}
var p3 *Point = &Point{30, 40, 'b'}
var p4 *Point = new(Point)
fmt.Println(p1, p1.x, p1.y, p1.mask)
fmt.Println(p2, p2.x, p2.y, p2.mask)
fmt.Println(p3, *p3, p3.x, p3.y, p3.mask)
fmt.Println(p4, *p4, p4.x, p4.y, p4.mask)
}
{0 0 0} 0 0 0
{3 4 97} 3 4 97
&{30 40 98} {30 40 98} 30 40 98
&{0 0 0} {0 0 0} 0 0 0
方法
定义了一个类之后就可以搞它的方法。但是方法一定要在类外进行定义。
传递参数的时候,需要注意self是一个拷贝还是一个引用(可以参考指针那边的)
type Point struct {
x, y, lens float64
}
func (self Point) Length() float64 {
self.lens = math.Sqrt(self.x*self.x + self.y*self.y)
return math.Sqrt(self.x*self.x + self.y*self.y)
}
func (self *Point) Scale(factor float64) {
self.x = self.x * factor
self.y = self.y * factor
}
func main() {
var p1 Point = Point{3, 4, 0}
fmt.Println(p1)
d := p1.Length()
fmt.Println(d)
fmt.Println(p1)
p1.Scale(2)
fmt.Println(p1)
}
{3 4 0}
5
{3 4 0}
{6 8 0}
接口
抽象化公共行为,定义为接口并使用它。
go的接口不要像java一样进行声明,编译器能推断出来,这既给了动态类型的表达能力又保留了静态类型检查的安全
USB接口,由USB概念提出者定义接口类,然后厂家具体根据自己的产品实现提出者所讲的接口类的所有内容。
type Printer interface {
Print()
}
//interface是任何类的父类,是一个接口类.
type users struct {
name string
age byte
}
type good struct {
name string
num, price byte
}
func (u users) Print() {
fmt.Println(u)
}
func (g good) Print() {
fmt.Println(g)
}
func main() {
u := users{"Tom", 12}
g := good{"JackL", 1, 100}
var p Printer = u
p.Print()
p = g
p.Print()
}
继承
go通过匿名组合实现类似继承的功能。
type user struct{
name string
age byte
}
type leader struct{
user
title string
}
//leader继承user
type father_people struct {
name string
age byte
}
func (f father_people) Print() {
fmt.Println(f)
}
type leader struct {
father_people
title string
}
func main() {
var l leader
l.name = "TIM"
l.age = 19
l.title = "Sale"
l2 := leader{father_people{"ITM", 22}, "Make"}
l.Print()
l2.Print()
}
并发
go func
执行的并发。每个函数之间相互不影响。但需要主函数撑到子函数运行完才可能看到子函数运行的结果。
- go程序整个运行时都是完全并发化设计的
- 凡是你看到的几乎都是goroutine方式运行
- goroutine是比普通协程或线程更高效的并发设计
- 轻松创建和处理成千上万的并发任务
- 通过go运行时映射到适当的操作系统原语
package main
import (
"fmt"
"time"
)
func taskone() {
for i := 0; i < 10; i++ {
fmt.Println("Task one print : ", i)
time.Sleep(time.Second * 2)
}
}
func tasktwo() {
for i := 0; i < 10; i++ {
fmt.Println("Task two print : ", i)
time.Sleep(time.Second * 3)
}
}
func main() {
go taskone()
go tasktwo()
fmt.Println("Main print?!")
time.Sleep(time.Second * 30)
}
Main print?!
Task two print : 0
Task one print : 0
Task one print : 1
Task two print : 1
Task one print : 2
Task two print : 2
Task one print : 3
Task one print : 4
Task two print : 3
Task one print : 5
Task two print : 4
Task one print : 6
Task one print : 7
Task two print : 5
Task one print : 8
Task two print : 6
Task one print : 9
Task two print : 7
Task two print : 8
Task two print : 9
并发的通讯
利用make(chan type)
来创建一个可以传递type类型变量的通道。其中,可以使用make(chan interface{})
传递所有类型变量。
func consumer(data chan int, done chan bool) {
for x := range data {
fmt.Println("rect : ", x, "\n")
}
done <- true
}
func producer(data chan int) {
for i := 0; i < 4; i++ {
fmt.Println("Send : ", i)
data <- i
time.Sleep(time.Second)
}
close(data)
}
func main() {
done := make(chan bool)
data := make(chan int)
go producer(data)
go consumer(data, done)
<-done
}
- 发送:
- 通过
chan <- value
来发送 - 发送会阻塞直到被接收
- 通过
ch := make(chan interface{})
ch <- 0
ch <- 'hello'
不过这个代码会报错,因为没有接受通道的东西.
- 接收:
- 通过
variable = <-chan
来接收. - 阻塞式接收.
data := <-chan
.如果没有接受到,就一直阻塞. - 非阻塞式接受.
data,ok := <-chan
如果接收到了(也就是信道是开着的,没被close)ok=true
,不然为假.这东西也满足有信号才接收的性质,如果没有信号强制接收,ok会返回一个false。 - 阻塞等同步.
<-chan
- 循环接收.
for data := range ch
遍历结果式接收到的数据,类型是通道的数据类型.
- 通过
单向信道
var variable chan<- type
只能发送的通道.var variable <-chan type
只能接收的通道.而且他们可以这么用
ch := make(chan int)
var chSendOnly chan<- int = ch
var chRecvOnly <-chan int = ch
//实际上就把ch信道给分解开了
信道的缓冲
- 无缓冲的信道
一次只能处理一个数据,发送方要等对面接收,等待过程是阻塞的。接收方的接收方式跟之前是一样的。
func consumer(data chan int, done chan bool) {
for x := range data {
time.Sleep(time.Microsecond)
//加这个是防止两个同时的输出顺序不符合我们的预期
fmt.Println("rect : ", x, "\n")
time.Sleep(time.Second)
}
done <- true
}
func producer(data chan int) {
for i := 0; i < 4; i++ {
data <- i
fmt.Println("Send : ", i, "\n")
}
close(data)
}
func main() {
done := make(chan bool)
data := make(chan int)
go producer(data)
go consumer(data, done)
<-done
}
Send : 0
rect : 0
Send : 1
rect : 1
Send : 2
rect : 2
Send : 3
rect : 3
-
有缓冲的信道
无缓冲同时只能存0个,有缓冲的信道可以认为是可以暂存的信道。
定义:
ch := make(chan int,3)
。获取当前信道已有个数:
len(ch)
。阻塞条件:
- 被填满,再次发送数据
- 为空,尝试接收数据
func recv(ch chan int, done chan bool) { for x := range ch { time.Sleep(time.Microsecond) fmt.Println("rect : ", x) time.Sleep(time.Second * 2) } done <- true } func send(ch chan int) { for i := 0; i <= 10; i++ { ch <- i fmt.Println("Send : ", i) } close(ch) } func main() { ch := make(chan int, 3) done := make(chan bool) go send(ch) go recv(ch, done) <-done }
Send : 0 Send : 1 Send : 2 Send : 3 //这里阻塞了,然后就一直一个接收一个发送 rect : 0 Send : 4 rect : 1 Send : 5 rect : 2 Send : 6 rect : 3 Send : 7 rect : 4 Send : 8 rect : 5 Send : 9 rect : 6 Send : 10//最后没有发送了,只有接收 rect : 7 rect : 8 rect : 9 rect : 10
-
超时响应机制
select{
case IO1:
//...
case IO2:
//...
default:
//...
}
依次执行IO语句,如果有一个没被阻塞,那么执行对应的语法块。并且执行完后退出select语法块。如果没有default,那么select就一直被阻塞直到有一个IO可以进行了。
同信道同函数不传递
func check_rw(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(time.Second)
fmt.Println("Wow\n")
t := <-ch
fmt.Println(t)
}
}
func main() {
ch := make(chan int)
go check_rw(ch)
time.Sleep(time.Second * 10)
}
运行结果:
c:/go/bin/go.exe build [C:/Users/123/go/src/并发]
成功: 进程退出代码 0.
C:/Users/123/go/src/并发/并发.exe [C:/Users/123/go/src/并发]
成功: 进程退出代码 0.
样例程序-剪刀石头布
func playerone(data1 chan int, result1 chan int) {
for x := 0; x < 5; x = <-result1 {
time.Sleep(time.Second * 10)
fmt.Println("Player one OK!")
data1 <- rand.Intn(3)
}
close(data1)
close(result1)
}
func playertwo(data2 chan int, result2 chan int) {
for x := 0; x < 5; x = <-result2 {
time.Sleep(time.Second * 10)
fmt.Println("Player two OK!")
data2 <- rand.Intn(3)
}
close(data2)
close(result2)
}
func onetwopk(data [2]chan int, result [2]chan int, resultt chan int) {
res1, res2 := 0, 0
for {
x1 := <-data[0]
fmt.Println("Recieve player one!")
x2 := <-data[1]
fmt.Println("Recieve player two!")
for j := 0; j < 3; j++ {
fmt.Println("Show the choose in ", (3 - j), "Seconds!")
time.Sleep(time.Second)
}
fmt.Println("Player one : ", x1, "\nPlayer two : ", x2)
if (x1+1)%3 == x2 {
res1 = res1 + 2
fmt.Println("\nPlayer one win!\n")
} else if x1 == x2 {
res1 = res1 + 1
res2 = res2 + 1
fmt.Println("\nPlayers both win!\n")
} else {
res2 = res2 + 2
fmt.Println("\nPlayer two win!\n")
}
result[0] <- res1
result[1] <- res2
if res1 >= 5 || res2 >= 5 {
resultt <- res1 - res2
break
}
}
}
func main() {
rand.Seed(time.Now().Unix())
data := [2]chan int{make(chan int), make(chan int)}
result := [2]chan int{make(chan int), make(chan int)}
resultt := make(chan int)
go playerone(data[0], result[0])
go playertwo(data[1], result[1])
go onetwopk(data, result, resultt)
ans := <-resultt
fmt.Println(ans)
}
一种不可能重复的运行结果
Player two OK!
Player one OK!
Recieve player one!
Recieve player two!
Show the choose in 3 Seconds!
Show the choose in 2 Seconds!
Show the choose in 1 Seconds!
Player one : 2
Player two : 2
Players both win!
Player one OK!
Recieve player one!
Player two OK!
Recieve player two!
Show the choose in 3 Seconds!
Show the choose in 2 Seconds!
Show the choose in 1 Seconds!
Player one : 2
Player two : 0
Player one win!
Player one OK!
Recieve player one!
Player two OK!
Recieve player two!
Show the choose in 3 Seconds!
Show the choose in 2 Seconds!
Show the choose in 1 Seconds!
Player one : 1
Player two : 2
Player one win!
4
数据类型
类型简述
数据类型 | 表示 | 大小 | 取值 | 注意 |
---|---|---|---|---|
布尔型 | bool | 1字节 | true,false | 不能用数字代替 |
整型 | int/uint | 32/64位 | 因平台而差 | |
8位整型 | int8/uint8 | 1字节 | [-128,127],[0,255] | |
字节型 | byte | 1字节 | uint8别名 | |
16位整型 | int16/uint16 | 2字节 | [-28,28-1],[0,2^16-1] | |
32位整型 | int32/uint32 | 4字节 | [-216,216-1],[0,2^32-1] | int32(rune) |
64位整型 | int64/uint64 | 8字节 | [-232,232-1],[0,2^64-1] | |
浮点型 | float32/float64 | 4/8字节 | 精确到7/15小数位 | |
复数 | complex64/128 | 8/16字节 | ||
指针 | uintptr | 32/64位 |
其他数据类型:
- 值类型:array,struct,string。
- 引用类型:slice,map,chan。
- 接口类型:interface
- 函数类型:func。
func main() {
var comp complex128 = 2 + 1i
var comp1 complex128 = 2
a := real(comp)
b := imag(comp)
c := cmplx.Sqrt(comp)
fmt.Println(comp, "\n", a, "\n", b, "\n", c, "\n", comp1)
}
零值类型
零值不是空值,是默认值。值类型为0,bool为false,string为“”。
类型转换
Go不支持隐式转换,<ValueA> := <TypeofValueA>(<ValueB>)
.
关键字
内置关键字 | 内置关键字 | 内置关键字 | 内置关键字 | 内置关键字 |
---|---|---|---|---|
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
运算符
全都是从左往右结合。以下式它们的优先级,从高向低
- ^ ! (一元运算符)
- * / % << >> & &^
- + - | ^(二元运算符)
- == != < <= > >=
- <- (channel专用)
- &&
- ||
&是清除标志位,a&b相当于二进制集合差集,从a上清除b的标志位。^按位取反或者异或。
func main() {
a := 127
b := 1
fmt.Println(a)
c := a &^ b
fmt.Println(c)//126
a = 5
b = 2
c = a &^ b
fmt.Println(c)//5
}
指针、递增递减
Go没有->,统一使用.来进行操作。默认值为nil。++和–是作为语句而不是表达式。
常量
常量的概念
常量的值编译的时候确定,定义时把var
换成const
即可。可以同时定义多个常量。
const a int = 1
const b = 'A'
const(
text = "123"
length = len(text)
num = b *20
)
const i,j,k = 1,"2","333"
常量的初值
不提供初值则用上一行的表达式。
const(
a = "A"
b//b = "A"
)
可以使用iota,常量计数器,从0开始自动递增,遇到const关键字清零。可以结合表达式进行使用。
const(
a = iota//0
b//1
c//2
d//3
)
const(
e = iota//0
f//1
)
const(
ca = iota + 10//10
cb//11
cc//12
)
内置函数
func append(slice []Type, elems ...Type) []Type
func cap(v Type) int
func close(c chan<- Type)
func complex(r, i FloatType) ComplexType
func copy(dst, src []Type) int
func delete(m map[Type]Type1, key Type)
func imag(c ComplexType) FloatType
func len(v Type) int
func make(Type, size IntegerType) Type
func new(Type) *Type
func panic(v interface{})
func real(c ComplexType) FloatType
func recover() interface{}
输入
fmt.Scanln(&c)//读一行
fmt.Scanf()//跟C是一样的
如何分工协作和功能复用
如何实现分工
- 多个文件多个package,然后import对应的package
- 函数名大写字母开头才能够进行调用
举个例子:
//分工/input/input.go
package input
import (
"fmt"
)
func Input(a1, dis, n *int) (int, int, int) {
var a, b, c int
fmt.Println("Please input a:")
fmt.Scanln(&a)
fmt.Println("Please input b:")
fmt.Scanln(&b)
fmt.Println("Please input c:")
fmt.Scanln(&c)
return a, b, c
}
//分工/calc/calc.go
package calc
func Calc(a1, dis, n int) (a int) {
a = (a1 + a1 + dis*(n-1)) * n / 2
return
}
//分工/output/output.go
package output
import "fmt"
func Output(a1, dis, n, sum int) {
fmt.Println("a1 = ", a1)
fmt.Println("dis = ", dis)
fmt.Println("n = ", n)
fmt.Println("sum = ", sum)
}
//分工/main/main.go
package main
import (
"分工/calc"
"分工/input"
"分工/output"
//默认是同目录+编译器目录
)
func main() {
var a1, dis, n, sum int
a1, dis, n = input.Input(&a1, &dis, &n)
sum = calc.Calc(a1, dis, n)
output.Output(a1, dis, n, sum)
}
同包的文件必须在一个目录下,同目录下只能有一个包。举例:删掉之前那个input。然后换成下面的也可以。
//分工/main/input.go
package main
import (
"fmt"
)
func Input(a1, dis, n *int) (int, int, int) {
var a, b, c int
fmt.Println("Please input a:")
fmt.Scanln(&a)
fmt.Println("Please input b:")
fmt.Scanln(&b)
fmt.Println("Please input c:")
fmt.Scanln(&c)
return a, b, c
}
单元测试
测试你的函数是不是对的。或者说测试你的某些模块是不是对的。
方法:建立一个*_test.go
的文件,然后这个文件中所有的函数名都应该是Test_*
开头,参数为*testing.T
类型。
终端指令:go test
或者go test -v
.LiteIDE指令:T的下拉菜单第一个。
// calc_test.go
package calc
import "testing"
func Test_sum(t *testing.T) {
if result := Calc(1, 1, 100); result != 5051 {
t.Error("Wrong")
}
}
//calc.go
package calc
func Calc(a1, dis, n int) (a int) {
a = (a1 + a1 + dis*(n-1)) * n / 2
return
}
性能测试
测试你的函数是不是对的。或者说测试你的某些模块是不是对的。
方法:建立一个*_test.go
的文件,然后这个文件中所有的函数名都应该是Benchmark_*
开头,参数为*testing.D
类型。
终端指令:go test -bench=".*" -cpuprof=calc.prof
或者go test -v -bench=".*" -cpuprof=calc.prof
.LiteIDE指令:T的下拉菜单TestBench
。
查看结果(不过要先点那个TestBuildgo test -c -bench=".*" -cpuprofile=calc.prof
):go tool pprof calc.test.exe calc.prof
然后输入
-
text
.就可以用文本的方式查看性能测试的结果. -
web
以图片的形式查看图形,是一个svg文件.
//calc_b_test.go
package calc
import "testing"
func Benchmark_Sum(b *testing.B) {
for i := 0; i < b.N; i++ {
Calc(1, 1, 100)
}
}
//calc.go
package calc
func Calc(a1, dis, n int) (a int) {
a = (a1 + a1 + dis*(n-1)) * n / 2
return
}
集成开发
思想:每个人完成自己的程序后提交到一个代码服务器上,然后需要调用的时候就从代码服务器进行调用。
协同开发工具的推荐
- Source Control: CVS/SVN、 git 和github
- Bug Tracking: Bugzilla, Trac, Roundup
- 交流工具: maillist, IM, Forum, IRC, Wiki
- 协同开发平台: sourceforge, BaseCamp
编译并执行整个系统?
go install 包源码存放路径的最后一个目录
生成可执行文件到bin,依赖的包文件到pkg
C:.
│ doc.go
│ main.go
│
├─calc
│ calc.go
│
├─input
│ input.go
│
└─output
output.go
就在这个文件夹下面输入go install
即可。
如果想要install一个具体的包,那么需要搞到他的那个目录。go install ./input
。
bin,pkg
是存放install完成之后的主程序和包程序。
功能复用
max(max(a,b),c)
这样的。
其他编译命令
-
go build 编译包,如果是main包则在当前目录生成可执行文件,其他包不会生成.a文件;
-
go install 编译包, 分别生成可执行文件和依赖包文件到%GOPATH%/bin,%GOPATH%/pkg
-
go run gofiles… 编译列出的文件,并生成可执行文件然后执行。注意只能用于main包,否则会出现go run: cannot run non-main package的错误。
-
go run是不需要设置GOPATH的,但go build和go install必须设置。
-
go run常用来测试一些功能,这些代码一般不包含在最终的项目中。
-
go test单元测试,对当前目录下的所有*_test.go文件进行编译并自动运行测试
-
go test –v。单元测试, “-v" 表示无论用例是否测试通过都会显示结果,不加"-v"表示只显示未通过的用例结果
-
go test –bench=“.*”。性能测试
-
go test –v –bench=“.*”。同时进行单元测试和性能测试,同上
-
go test -run=‘Test_xxx’。测试某个方法
Go与C交互
具体使用方法:把C语言中的一些句子搞成注释,然后在下面import"C"
,这时候想要使用C的一些东西,就可以搞成C.int,C.printf
之类的了。
关于一些参数的传递可以参考上述的参考文章。
// UseC/main.go
package main
import (
"fmt"
"unsafe"
)
/*
#include"myprint.h"
*/
import "C"
func main() {
fmt.Println("Hello World!")
s := "Hello Cgo!"
cs := C.CString(s)
C.myprint(cs)
//C.myprint(C.CString(s))
//但我不知道这样内存是啥样的
C.free(unsafe.Pointer(cs))
}
//UseC/myprint.c
#include"myprint.h"
void myprint(char* s){
printf("myprint : %s\n",s);
}
//UseC/myprint.h
#include<stdio.h>
#include<stdlib.h>
void myprint(char* s);
不过我们发现了一个有趣的现象,那就是他的输出结果并不是像我们所期待那样顺序输出的。
// UseC/main.go
package main
import "fmt"
/*
#include<stdio.h>
#include<stdlib.h>
void pt(){
printf("Hello cgo!\n");
}
int Add(int a,int b){
printf("result = %d\n",a+b);
return a+b;
}
*/
import "C"
func main() {
fmt.Println("Hello1111")
C.pt()
fmt.Println("Hello2222")
fmt.Println(C.Add(C.int(10), 12))
fmt.Println("Hello3333")
}
输出结果:
Hello1111
Hello2222
22
Hello3333
Hello cgo!
result = 22
如何读写数据
如何读取用户输入
fmt.ScanX / fmt.SscanX
前者是从标准输入输出进行读取,后者是从String中进行读取.
fmt.Scanf(format,values...)
fmt.SScanf(stringinput,format,values...)
// 输出输出 project main.go
package main
import (
"fmt"
)
var (
first_name, last_name, s string
fn, ln string
i int
f float32
input = "56.12 / 5212 / Go"
format = "%f / %d / %s"
)
func main() {
fmt.Println("Please input your full name: ")
fmt.Scanln(&first_name, &last_name)//回车结束
fmt.Printf("Hi %s %s!\n", first_name, last_name)
fmt.Scanf("%s %s", &fn, &ln)//回车结束
fmt.Printf("Hello %s %s!\n", fn, ln)
fmt.Sscanf(input, format, &f, &i, &s)
fmt.Printf("From string : %f %d %s", f, i, s)
}
Please input your full name:
Sofan He
Hi Sofan He!
SOfan
Hello SOfan !
From string : 56.119999 5212 Go
利用bufio包的buffed reader来读取数据(缓冲读取)
需要引入的包bufio,os
os使用来获取流的.os.Stdin,os.Stdout
都是*os.File类型的.
import (
"bufio"
"fmt"
"os"
)
var input_reader *bufio.Reader
var input string
var err error
func main() {
input_reader = bufio.NewReader(os.Stdin)
//创建一个读取器,与标准输入输出绑定
fmt.Println("Please enter som input : ")
input, err = input_reader.ReadString('\n')
//一直读取到回车,括号是分隔符,String是指返回值
if err == nil {
fmt.Printf("The input was : %s\n", input)
}
fmt.Println("Please enter som input : ")
inputb, errb := input_reader.ReadBytes('\n')
//一直读取到回车,括号里面的是分隔符
if errb == nil {
fmt.Printf("The input was : %s\n", inputb)
}
}
如何读写文件
所有文件的表示方式都是指向os.file的一个指针.
读文件,从磁盘中的文件流向计算机内存.
写文件,从计算机内存刘翔磁盘中的文件.
- 按行读取文件
func main() {
var input_file *os.File
var input_error, reader_error error
var input_reader *bufio.Reader
var inputstring string
input_file, input_error = os.Open("dat.txt")
if input_error != nil {
fmt.Println("Error")
return
}
defer input_file.Close()
input_reader = bufio.NewReader(input_file)
for {
inputstring, reader_error = input_reader.ReadString('\n')
if reader_error == io.EOF {
return
}
fmt.Printf("The input data is : %s\n", inputstring)
}
}
- 按照字节读取到一个
[]byte
func main() {
inputFile := "dat.txt"
outputFile := "dat_cp.txt"
buf, err := ioutil.ReadFile(inputFile)
//buf : []byte
if err != nil {
panic(err.Error())
}
fmt.Printf("%s\n", string(buf))
err = ioutil.WriteFile(outputFile, buf, 0644)//8 非 16?
if err != nil {
panic(err.Error())
}
}
0644 是filemode 定义在os\types.go
type FileMode uint32
// The defined file mode bits are the most significant bits of the FileMode.
// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
// The values of these bits should be considered part of the public API and
// may be used in wire protocols or disk representations: they must not be
// changed, although new bits might be added.
linux文件权限一般都以8进制表示,格式为abc的形式,其中a,b,c各为一个数字,分别表示User、Group、及Other对该文件的操作权限;
如果文件权限用二进制表示那么是9位bit,从左至右,1-3位数字代表文件所有者的权限,4-6位数字代表同组用户的权限,7-9数字代表其他用户的权限;
而具体的权限是由数字来表示的,读取的权限等于4,用r表示;写入的权限等于2,用w表示;执行的权限等于1,用x表示;
通过4、2、1的组合,得到以下几种权限:0(没有权限);4(读取权限);5(4+1 | 读取+执行);6(4+2 | 读取+写入);7(4+2+1 | 读取+写入+执行)
常用的linux文件权限如下:
444 r–r--r–
600 rw-------
644 rw-r–r--
666 rw-rw-rw-
700 rwx------
744 rwxr–r--
755 rwxr-xr-x
777 rwxrwxrwx
这里以755为例:
1-3位7等于4+2+1,rwx,所有者具有读取、写入、执行权限;
4-6位5等于4+1+0,r-x,同组用户具有读取、执行权限但没有写入权限;
7-9位5,同上,也是r-x,其他用户具有读取、执行权限但没有写入权限。
- 带缓冲区的读取
func main() {
inputFile, input_error := os.Open("dat.txt")
if input_error != nil {
fmt.Println("Error")
return
}
defer inputFile.Close()
input_reader := bufio.NewReader(inputFile)
buf := make([]byte, 3)
str1 := ""
for n, err := input_reader.Read(buf); n != 0; n, err = input_reader.Read(buf) {
if err != nil {
fmt.Println(err)
return
}
fmt.Println(buf, len(buf), cap(buf))
str1 = str1 + string(buf[:n])
//加上这句话之后,可以有读进来多少就存多少
}
fmt.Println(str1)
}
//一次读取两个字节的长度
[49 50] 2 2
[51 52] 2 2
[53 13] 2 2
[10 65] 2 2
[66 67] 2 2
[68 69] 2 2
[13 10] 2 2
[33 64] 2 2
[35 36] 2 2
[37 13] 2 2
[10 13] 2 2
12345
ABCDE
!@#$%
- 格式化读取,FscanX
func main() {
file, err := os.Open("dat.txt")
if err != nil {
panic(err)
}
defer file.Close()
var col1, col2, col3 []string
fmt.Printf("%T %d %d\n", col1, len(col1), cap(col1))
for {
var v1, v2, v3 string
_, err := fmt.Fscanln(file, &v1, &v2, &v3)
if err != nil {
break
}
col1 = append(col1, v1)
col2 = append(col2, v2)
col3 = append(col3, v3)
}
fmt.Printf("%T %d %d\n", col1, len(col1), cap(col1))
fmt.Println(col1)
fmt.Println(col2)
fmt.Println(col3)
}
[]string 0 0
[]string 3 4
[12345 ABCDE !@#$%]
[100 300 500]
[200 400 600]
12345 100 200
ABCDE 300 400
!@#$% 500 600
- 如何写文件(带缓冲区)
func main() {
outputFile, outputerr := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
//os.O_RDONLY,os.O_WRONLY,os.O_CREATE,os.C_TRUNC
//只读,只写,不存在就创建,存在就截断为0
if outputerr != nil {
fmt.Println("OH?")
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
outputString := "Hello World\n"
for i := 0; i < 10; i++ {
outputFile.WriteString(outputString)
//写入缓存区
}
outputWriter.Flush()
//缓存区内容紧接着被完全写入文件.相当于延时,等待同步
}
- 如何写文件(不带缓冲区)
(*os.File).WriteString(string)
就直接写进文件了!
特别注意:os.Stdout.WriteString("HE")
就可以直接把"HE"写到屏幕上了!
补充 : 写入文件可以使用
os.Create(filepath)
,无则创建,有则截断
如何拷贝文件
利用io包Copy(*os.File,*os.File)(int64,error)
传入target,source,返回是写入长度和错误信息
接口类型推断
啥是接口类型推断
如果一个类型实现了接口,那么接口类型变量里面就可以存储该类型的数据(把接口实现功能的对象插入到接口中)
如何反向知道接口类型变量里面实际保存的是哪一种类型的变量?这个问题就是接口类型推断.
方法有两种:
- comma-ok的模式
- switch测试
type People struct {
Name string
Age int
}
type Tester interface{}
func main() {
people := People{"张三", 20}
it := make([]Tester, 4)
it[0] = 1
it[1] = "Hello"
it[2] = people
it[3] = true
for i, e := range it {
if val, ok := e.(int); ok {
fmt.Printf("it[%d] type is int,val = %d\n", i, val)
} else if val, ok := e.(string); ok {
fmt.Printf("it[%d] type is string,val = %s\n", i, val)
} else if val, ok := e.(People); ok {
fmt.Printf("it[%d] type is People,val = %v\n", i, val)
} else if val, ok := e.(bool); ok {
fmt.Printf("it[%d] type is bool,val = %v\n", i, val)
}
}
fmt.Println("\n")
for i, e := range it {
switch val := e.(type) {
case int:
fmt.Printf("it[%d] type is int,val = %d\n", i, val)
case string:
fmt.Printf("it[%d] type is string,val = %s\n", i, val)
case People:
fmt.Printf("it[%d] type is People,val = %v\n", i, val)
case bool:
fmt.Printf("it[%d] type is bool,val = %v\n", i, val)
default:
fmt.Println("Unknown type of it[", i, "]")
}
}
}
//e.(type):查询e的数据类型,不能在switch语句之外的任何语句内使用
反射
啥是反射?就是逆映射的意思.
正向 : (类型,值) -> 对象
反向 : 对象 -> (类型,值)
类型推断就是反射的一种?
反射是获取程序运行时类型信息的方式.
作用:
- 让静态语言具备更加多样的运行时的动态特征
- 让程序具备自省能力,使得interface接口对象的灵活性有更大的发挥余地
reflect包的功能
- 获取原对象的Type和Value值
- 修改元对象Value的值
- 动态调用原对象的方法
- Type是被反射对象的类型信息.
Typeof()
函数进行获取.Type中的Kind()
方法也可以获取该类的具体信息. - Value是该对象的值信息.
Valueof()
函数进行获取.Value中包含一系列相关方法,如Int(),Float(),Bool()
用于返回对应类型的值.
func main() {
pi := 3.14
t := reflect.TypeOf(pi)
v := reflect.ValueOf(pi)
if t.Kind() == reflect.Float64 {
fmt.Println("Type : float64", "Value", v.Float())
fmt.Println(t, v)
}
}
搞个结构体的那种
type Student struct {
Id int
Name string
Sex bool
Grade float32
}
func (s Student) Myname() {
fmt.Println("My name is ", s.Name)
}
func (s Student) Say() {
fmt.Println("Hello! Nice to meet you!")
}
func StructInfo(o interface{}) {
t := reflect.TypeOf(o)
v := reflect.ValueOf(o)
if k := t.Kind(); k != reflect.Struct {
fmt.Println("It is not a struct ?!")
return
}
fmt.Println("Struct name is :", t.Name())
fmt.Println("Fields of the struct is : ")
for i := 0; i < t.NumField(); i++ {
y := t.Field(i)
y1, y2 := t.Field(i).Type, v.Field(i).Interface()
fmt.Println(i, ":", y, ":", y1, ":", y2)
}
fmt.Println("Method of the struct is : ")
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("%s : %v\n", method.Name, method.Type)
}
}
func main() {
stu := Student{001, "这是个名字", true, 99.8}
StructInfo(stu)
}
还可以修改原对象的值,然后动态进行调用.
排序
引入sort
包,然后用以下的几个例子看吧
[下次再更]