Translation is not yet available. Showing original content.
go
什么是 Go
是一门并发支持、垃圾加收的编译型系统编程语言,具有静态编译语言的高性能和动态语言的,主要特点如下
- 类型安全和内存安全
- 以非常直观和极低代价的方案实现高并发
- 高效的垃圾回收机制
- 快速编译(同时解决了 C 语言中头文件太多的问题)
- UTF-8 支持
安装
- 源码安装
- 标准包安装
- 第三方安装
标准包安装,一路下一步。安装完后,会自动添加如下环境变量,使用go env查看
GOARCH="amd64" # cup架构
GOBIN="/usr/local/go/bin"
GOCACHE="/Users/willzhao/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/willzhao/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/3q/z9zfyn256718rwvw071831tr0000gn/T/go-build200798760=/tmp/go-build -gno-record-gcc-switches -fno-common"
根据约定,GOPATH 下需要建立 3 个目录
- bin (存放编译后生成的可执行文件,这个文件里生成的文件一般拿到上一级去执行,要不可能会提示找不到依赖包)
- pkg (存放编译后生成的包文件)
- src (存放项目源码)
常用命令
- go get 获取远程包(如果在 git 上获取要先安装 git,如果是在 google code 上下载远程包的话需要安装 hg
- go run 直接运行程序
- go build 测试编译,检查是否有编译错误(什么样的源码可以编译成可执行文件呢? package 名称为 main 的,如果不是就不会生成可执行文件)
- go fmt 格式化源码(部分 ide 在保存时自动调用)
- go install 编译包文件并编译整个程序(会找 main 的包,在 bin 文件夹中生成可执行文件)
go install是建立在 GOPATH 上的,无法在独立的目录里使用go installGOPATH下的 bin 目录放置的是使用go install生成的可执行文件,可执行文件的名称来自于编译时的包名go install输出目录始终为 GOPATH 下的 bin 目录,无法使用-o 附加参数进行自定义GOPATH下的 pkg 目录放置的是编译期间的中间文件- go test 运行测试文件,名字形如
xxx_test.go,正常的文件不在命名成这样 - godoc 查看文档 godoc fmt,可以建立本地的网站来查看 godoc -http:8080
hello world
package main
import (
"fmt"
)
func main(){
fmt.Print("Hello World! 你好世界!") // 注意Print
}
// 好多的教程没提,这里最好是使用idear的开发环境来做 它可以设置一个project path
// 目录结构 如上面所提src pkg bin
// 通过查看idear的run configuration确认,编译命令执行如下
/*
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/willzhao/Documents/Develop/go/hello_world:/usr/local/go #gosetup
/usr/local/go/bin/go build -i -o /Users/willzhao/Documents/Develop/go/hello_world/bin/HelloWorldRun main #gosetup 注意main是包名
/Users/willzhao/Documents/Develop/go/hello_world/bin/HelloWorldRun #gosetup
*/
Go 的内置关键字(25 个)
25 个,并且都是小写
| break | default | func | interface | select |
| case | defer | go | map | struct |
| chan | else | goto | package | switch |
| const | fallthrough | if | range | type |
| continue | for | import | return | var |
注释
- // 单行
/* */多行
程序一般结构
- Go 程序是通过 package 来组织(与 Python 类似) 必须放在非注释的第一行。
- 只有 package 名称为 main 的包可以包含 main 函数,一般建议 package 的名称和目录名一致 package 名称不要使用驼峰标记法,使用下划线
- 别名调用 如
import std "fmt" - 省略调用 如
import . "fmt"使用时可以直接使用Print来调用(不建议这样使用,导致功能不知道是哪个包导入的) - 省略调用和别名调用不能同时使用
- 一个可执行程序有且仅有一个 main 包
- 通过 import 关键字来导入其它非 main 包,导入多个包时可以用一个 import 加括号,导入包如果没有用,编译时会报错
- 通过
const关键字来进行常量定义 - 通过函数体外部使用 var 关键字来进行全局变量的声明与赋值
- 通过 type 关键字来进行结构 struct 或接口 interface 声明
- 通过 func 关键字来进行函数声明
可见性规则
Go 语言中,使用大小写来决定常量、变量、类型、接口、结构或函数是否可以被外部包所调用,可以快速确定是否调用错误
- 首字母小写即为 private
- 首字母大写即为 public
声明多个常量变量
// 常量定义
const (
PI = 3.14
const1 = "1"
const2 = 2
const3 = 3
)
// 全局变量声明与赋值,不能在函数体内声明
var (
name = "gopher"
name1 = "1"
name2 = 2
name3 = 3
)
// 一般类型声明
type (
newType int
type1 float32
type2 string
type3 byte
)
数据类型
- 布尔型 bool
- 长度 1 个字节
- 取值范围 true, false
- 不能用其它值代表 true 或 false
- 整型 int/uint
- 根据平台可能为 32 或 64 位
- 8 位整型 int8/uint8
- 长度 1 字节
- -128~127/0~255
- 字节型 byte(uint8 别名)
- 16 位整型 int16/uint16
- 长度 2 字节
- -32768~32767/0~65536
- 32 位整型 int32(rune)/uint32
- 长度 4 字节
- 64 位整型 int64/uint64
- 浮点型 float32/float64
- 长度 4/8 字节
- 小数位精确到 7/15 小数位
- 复数 complex64/complex128
- 长度 8/16 字节
- 足够保存指针的 32 位或 64 位整数型 uintptr
- 其它值类型
- array/struct/string
- 引用类型
- slice/map/chan
- 接口类型 inteface
- 函数类型 func
以上类型的默认类型
// 数值为0 bool是false string为"" 数组则根据它所指定的类型
// math.MinInt8 math有一些常量来定义值范围边界
package main
import (
"fmt"
"math"
)
type (
byte int8
rune int32
)
单个变量的声明与赋值
- 变量声明格式:var 变量名 变量类型
- 变量赋值格式:变量名称=表达式
- 声明的同时赋值:var 变量名称 变量类型 = 表达式 这里的变量类型可以省略,编译器可以推断出来类型
package main
import (
"fmt
)
func main(){
var b int;
// 或 变量声明与赋值的最简写法
a:=1
}
多个变量的声明与赋
- 全局变量的声明可以使用 var()的方式进和简写
- 全局变量的声明不可以省略 var,但可以并行方式 即 var ()
- 所有变量都可以使用类型推断
- 局部变量不可以使用 var()的方式简写,只能使用并行方式
package main
import (
"fmt"
)
var (
aaa = "hello"
sss,bbb= 1,2
// ccc := 3 这方式是错的
)
func Test(){
// 多变量声明
var a,b,c,d int
// 多个变量的赋值
a,b,c,d = 1,2,3,4
// 多个变量声明的同时赋值
var e,f,g,h int = 5,6,7,8
// 省略变量类型,由系统推断
var i,j,k,l = 9,10,11,12
// 多个变量声明与赋值的最简写法
i,m,n,o :=13,14,15,16
aa,_,cc,dd = 1,2,3,4 //_是忽略符号
}
类型转换:不存在隐式转换,所有类型转换必须显式声明,转换必须在两咱兼容的之间
类型与变量
package main
import (
"fmt"
"strconv"
)
func main(){
var a int = 65
b:=string(a)
fmt.Print(b) // A
b = strconv.Itoa(a)
a = strconv.Atoi(b)
fmt.Print(b)
fmt.Print(a)
}
常量定义
- 常量的值在编译时就已经确定
- 常量定义格式与变量基本相同
- 等号右侧必须是常量或者常量表达式
- 常量表达式中的函数必须是内置函数
const a int = 1
const b = 'A'
const (
c = a
d = b
)
const (
e = 1
f
g
)
// f g 等一上行定义的值
const (
a,b = 1,"2"
c,d
)
// c = 1 d="2" 格式要一致(与上一行)
// iota 从第一个计数
const (
a = 'A'
b = iota
c = 'B'
d = iota
)
// 65
// 1
// 66
// 3
// 在每个组中都是从0开始加1的
// 常量不想被外包使用,则定义时不要以大写母开头,比如使用划线或小写字母如c
// 可以做一个常量枚举
const (
Monday = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
运算符
- ^ ! (一元)
- * / % \<\< >> & &^
- + - | ^ (二元)
- == != \< \<= >= >
- \<- (专门用于 channel)
- 位运算符 & | ^异或 &^(位清空 and not)
- &&
- ||
这里说明一下^&
// 计算x&^y 首先我们先换算成2进制 0000 0010 &^ 0000 0100 = 0000 0010 如果ybit位上的数是0则取x上对应位置的值, 如果ybit位上为1则取结果位上取0
package main
import (
"fmt"
)
func main(){
var x,y int = 2,4
fmt.Print(x^&y) // 2
}
实现一个计算机存储单位枚举
const (
_ = iota // B = iota
KB float64 = 1 << (iota*10)
MB float64 = 1 << (iota*10)
GB float64 = 1 << (iota*10)
TB float64 = 1 << (iota*10)
PB float64 = 1 << (iota*10)
EB float64 = 1 << (iota*10)
ZB float64 = 1 << (iota*10)
YB float64 = 1 << (iota*10)
)
指针
Go 虽然保留了指针,但与其它编程语言不同的是,在 Go 当中不支持指针运算”->“运算符,而直接采用"."选择符来操作指针目标对象的成员
- 操作符”&“取变量地址,使”*“通过指针间接访问目标对象
- 默认值为 nill 而 NULL
package main
import (
"fmt"
)
func main(){
a:=1;
var b *int = &a
fmt.Print(*b) // 1
}
递增递减
++ —— 在 Go 语言中是不能作为表达式而是作为语句,所以只能写在单独一行
条件表达式
- if 没有括号,可以使用初始化语句,并且是块级作用域
- 左括号必须放在 if 语句同一行
package main
import (
"fmt"
)
func main(){
a := 10
if a :=4; a > 0 {
fmt.Println(a)
}
fmt.Println(a)
}
循环语句 for
只有 for 一个循环语句,但它支持 3 种形式,
- 初始化和步进表达式可以是多个值
- 条件语句每次都会被重新检查,因此不建议在条件语句中使用函数,尽量提前计算好并以变量或常量代替
- 左大括号必须和条件语句一行
func main(){
a:=1
for {
a++
if a>3 {
break
}
}
fmt.Printf(a)
}
// 2
func main() {
a := 1
for a <= 3 {
a++
}
fmt.Println(a)
}
// 3
func main() {
a := 1
for i := 0;i<3;i++ {
a++
}
fmt.Println(a)
}
switch
- 可以使用任何类型或表达式作为条件语句
- 不需要写 break,一旦条件符合自动终止
- 如希望继续执行下一个 case,需要使用 fallthrough 语句
- 支持一个初始化表达式(可以是并行方式),右侧需要跟分号
- 左大括号必须和条件语句在同一行
func main() {
a := 1
switch a {
case 0:
fmt.Println("a=0")
case 1:
fmt.Println("a=1")
}
fmt.Println(a)
}
func main() {
a := 1
switch {
case a>=0:
fmt.Println("a=0")
fallthrough
case a>=1:
fmt.Println("a=1")
default:
fmt.Println("None")
}
fmt.Println(a)
}
func main() {
switch a := 1; { //注意右侧的分号,a的作用域范围只在这个switch块中
case a>=0:
fmt.Println("a=0")
fallthrough
case a>=1:
fmt.Println("a=1")
}
}
跳转语句
goto break continue
- 三个语法都可以配合标签使用
- 标签名区分大小写,若不使用会造成编译错误
- break 和 continue 配合标签可以用于多层循环的跳出,跳出 LABEL 标识的
- goto 是调整执行位置,与其它 2 语句配合标签的结果并不相同
func main() {
LABEL:
for i:=0;i<10;i++ {
if i>2 {
break LABEL
} else {
fmt.Println(i)
}
}
}
func main() {
LABEL:
for i:=0;i<10;i++ {
for {
fmt.Println(i)
continue LABEL
}
}
}
func gotest() {
i := 0
LABEL:
for i < 10 {
for {
fmt.Println(i)
i++
//goto LABEL
continue LABEL
}
}
}
数组
- 定义
var <varName>[n]<type> = 0 - 长度也是类型的一部分,因此具有不同长度的数组为不同类型
- 注意区分指向数组的指针和指针数组
- 数组在 Go 中为值类型
- 数组之间可以使用==或!=进行比较,但不能使用
或 - 可以使用 new 来创建数组,此方法返回一个指向数组的指针
- 支持多维数组
var a [2]int
var b [2]int
var c [2]string
a := [2]{1,2}
b := [20]{19:1} //第20个位置设置成1,其它设置成0值
c := [...]int{1,2,3,4,5} //长度为5的数组
d := [...]int{0:1,1:2,2:3}
e := [...]int(19:1) //长度20
// 指向数组的指针
var p *[20]int = &e
// 指针数组
x,y := 1,2
a := [...]*int{&x,&y} //保存的是指针
// 类型的不同不能进行直接比较
// 如果用new 创建的数组它返回的就是一个指向数组的指针
p := new([10]int) //p可以直接用索引来操作数组
p[1] = 2
fmt.Println(p)
// 多维数组
a :=[2][3]{
{1,1,1},
{2,2,2},
}
fmt.Println(a)
// 冒泡排序
func main() {
a := [...]int(5,6,3,9)
fmt.Println(a)
num := len(a)
for i:=0;i<num;i++ {
for j:=i+1;j<num;j++ {
if a[i]<a[j] {
tmp:=a[i]
a[i] = a[j]
a[j] = tmp
}
}
}
fmt.Println(a)
}
切片
- 本身并不是数组,它指向底层的数组
- 作为为变长数组的替代方案,可以关联底层数组的局部或全部
- 为引用类型
- 可以直接创建或从底层数组获取生成
- 使用 len()获取元素个数,cap()获取容量
- 一般使用 make()创建
- 如果多个 slice 指向相同的底层数组,其中一个的值改变会影响全部
make([]T,len,cap),其中 cap 可以省略,则和 len 值相同- len 表示存数的元素个数,cap 表示容量
func main() {
//这样就是一个slice
var s1 []int
fmt.Println(s1)
a:=[10]int{}
fmt.Println(a)
s2:=a[5:10] //索引开始(包含)结束(不包含)
s3:=a[5:len(a)] //索引开始(包含)结束(不包含)
s4:=a[5:] //索引开始(包含)结束(不包含)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(s4)
// make
s5 := make([]int,22,1) //参数2是多少个,参数3 初始容量,当容量超出初始容量,它每次都会增加一倍
}
func t1() {
s1:=make([]int,3,10)
fmt.Println(len(s1),cap(s1))
a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
sa:=a[2:5]
fmt.Println(string(sa))
sb:=a[3:5]
fmt.Println(string(sb))
}
- reslice
- reslice 时索引被 slice 的切片为准
- 索引不可以超过被 slice 切片的容量 cap()值
- 索引越界不会导致底层数组的重新分配而是引发错误
- append
- 可以在 slice 尾部追回元素
- 可以将一个 slice 追回在另一个 slice 尾部
- 如果最终长度未超过追回到 slice 容量则返回原始 slice
- 如果超过追回到的 slice 容量则将重新分配数组并拷贝原始数据
- copy
// reslice
a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
sa:=a[2:5] //cde
sb:=sa[1:3] //de
fmt.Println(string(sb))
// append
s1:=make([]int,3,6)
fmt.Printf("%p\n",s1)
s1 = append(s1,1,2,3)
fmt.Printf("%v %p\n",s1,s1)
s1 = append(s1,1,2,3)
fmt.Printf("%v %p\n",s1,s1)
/*
0xc000018120
[0 0 0 1 2 3] 0xc000018120
[0 0 0 1 2 3 1 2 3] 0xc00006e060
*/
// copy
map
- 类似其它语言中的哈希或者字典,以 key-value 形式存储数据
- key 必须是支持==或!==比较运算类型,不可以是函数,map 或 slice
- map 查找比线性搜索快很多,但比使用索引访问数据的类型慢 100 倍
- map 使用 make()创建,支持
:=这种简写方式 make([keyType]valueType,cap)cap 表示容量,可省略- 超出容量时会自动扩容,但尽量提供一个合理的初始值
- 使用 len()获取元素个数
- 键值对不存在时自动添加,使用 delete()删除某键值对
- 使用用
for range对 map 和 slice 进行迭代操作
func main() {
var m map[int]string
m = make(map[int]string)
// var m map[int]string = make(map[int]string)
// m:= make(map[int]string)
m[1] = "OK"
a = m[1]
delete(m,1)
// 更复杂的map
var m = map[int]map[int]string //m 的value又是一个map
m = make(map[int]map[int]string)
a,ok := m[2][1]
fmt.Println(ok)
if !ok {
m[2] = make(map[int]string)
}
m[2][1] = "GOOD"
a, ok = m[2][1]
fmt.Println(ok)
// for range
for k,v := range map {
}
}
func t1() {
sm:=make([]map[int]string,5) //声明一个slice 值是map[int]string
for _,v:=range sm {
v=make(map[int]string,1) //v每次都是一个copy,所以不能这样写
fmt.Println(v)
}
fmt.Println(sm)
}
func t2() {
sm:=make([]map[int]string,5) //声明一个slice 值是map[int]string
for i :=range sm {
sm[i]=make(map[int]string,1) //v每次都是一个copy,所以不能这样写
sm[i][1] = "OK"
fmt.Println(sm[i])
}
fmt.Println(sm)
}
// 对map的间接排序
对 map 的间接排序
package main
import (
"fmt"
"sort"
)
func main() {
m:=map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
s:=make([]int,len(m))
i:=0
for k,_ := range m {
s[i] = k
i++
}
// sort.Ints(s) // 没有这句,可以看一下打印结构
fmt.Println(s)
}
// map 键值对调
func main() {
m1:=map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
fmt.Println(m1)
m2:=make(map[string]int)
for k,v := range m1 {
m2[v] = k
}
fmt.Println(m2)
}
函数 function
- Go 函数不支持嵌套,重载,默认参数
- 定义函数使用关键字 func,且左大括号不能另起一行
- 函数也可以作为一种类型使用
- 支持以以下特性
- 无需声明原型
- 不定长变参
- 多返回值
- 命名返回值参数
- 匿名函数
- 闭包
func A(a ...int)(int,int,int){
// ...不定长可变参
a,b,c = 1,2,4
return //默认返回a b c // 返回参数可以写成a,b,c int
// 注意传入的时候都是拷贝 一种情况是拷贝值 另一种情况是拷贝地址(还是指向同一个位置)
}
// 匿名函数
func main(){
a:=func(){
fmt.Println("func A")
}
a()
// 调用闭包
f:=closure(10)
fmt.Println(f(1))
fmt.Println(f(2))
}
// 闭包
func closure(x int) func(int)int{
return func (y int)int {
return x*y
}
}
defer
类似析构,执行方式类似其它语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行。
- 即使函数发生严重错误也会执行
- 支持匿名函数的调用
- 常用的资源清理、文件关闭、解锁以及记录时间等操作
- 通过与匿名函数配合可在 return 之后修改函数计算结果
- 如果函数体内某变量作为 defer 时匿名函数的参数,则在定义 defer 即已经获得了拷贝,否则则是引用某个变量的地址
- Go 没有异常机制,但有 panic/recover 模式来处理错误
- panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效
func main() {
for i:=0;i<3;i++ {
// 当main函数执行完闭后才开始执行,而且i是(因为是闭包,是地址的拷贝)每次输出3
defer func() {
fmt.Println(i)
}()
}
// 如果在传入i会怎么样?
for i:=0;i<3;i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}
}
// panic之前注册defer来恢复到正常执行
func main() {
A()
B()
C()
}
func A() {
fmt.Println("Func A")
}
func B() {
defer func(){
if err:=reocver(); err!=nil {
fmt.Println("Recover in B")
}
}()
panic("Panic in B")
}
func C() {
fmt.Println("func C")
}
闭包,最外层不能是一个匿名函数
// 分析结果
package main
import (
"fmt"
)
func main() {
var fs = [4]func(){}
for i:=0;i<4;i++ {
defer fmt.Println("defer i=",i)
defer func(){
fmt.Println("defer_closure i=", i) // 闭包 引用值拷贝
}()
fs[i] = func(){
fmt.Println("closure i = ",i) // 闭包 引用值拷贝
}
}
for _,f:=range fs {
f()
}
}
结构 struct
- Go 中的 struct 与 c 中的 struct 非常相似,并且 Go 没有 class
- 使用
type <name> struct{}定义结构,名称遵循可见规则 - 支持指向自身的指针类型成员
- 支持匿名结构,可用作成员或定义成员变量
- 匿名结构也可以用于 map 的值
- 可以使用字面值对结构进行初始化
- 允许直接通过指针来读写结构成员
- 相同类型的成员可进行直接拷贝赋值
- 支持
==与!=比较运算符,但不支持>或< - 支持匿名字段,本质上是定义了某个类型名为名称字段
- 嵌入结构作为匿名字段看起来像继承,但不是继承
- 可以使用匿名指针
// 定义结构
type test struct{}
// 定义人
type person struct{
Name string,
Age int
}
func main() {
// a:=test{}
a:=person{}
a.Name = "joe"
a.Age = 19
fmt.Println(a)
// 可以使用字面值初始化
b:=person{
Name:"Will",
Age:18
}
fmt.Println(b)
A(b) // 值的拷贝
fmt.Perintln(b) //age 18
B(&b) //第次都要对b取地址是不是很麻烦,我可以在定义b时就写成指针 如b:=&person{Name:"Will",Age:18}, b就是指向结构的指针
// 使用属性时也不用像c下样*b.Name 而是直接写b.Name="OK"
fmt.Perintln(b) //age 13
}
func A(per person) {
per.Age=13
fmt.Println("A",per)
}
// 如果想修改传入的结构类型,则需要传入地址拷贝
func B(per *person) {
per.Age=13
fmt.Println("A",per)
}
// 匿名结构
func main() {
a: = struct {
Name string
Age int
}{
Name:"Joe",
Age:19
}
fmt.Println(a)
}
// 如下
type person struct {
Name string
Age int
// 匿名结构
Contact struct {
Phone,City string
}
}
func main() {
a:=person{Name:"joe",Age:18}
a.Contact.Phone = "12xxx"
a.Contact.City = "Bei Jing"
fmt.Println(a)
}
// 字段匿名,必须严格按照声明的顺序
type person struct {
string,
int
}
func main(){
a:=person{"joe",19} //调换顺序会报错
fmt.Println(a)
// 相同结构可以赋值 相同类型之间
b:=person{}
b=a
fmt.Println(b)
}
// 怎样实现类似继承的功能 嵌入
type human struct{
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct{
human
Name string
Age int
}
func main() {
a:=teacher{Name:"Joe",Age:18,human:human{Sex:0}}
b:=student{Name:"Jay",Age:19,human:human{Sex:1}}
a.Name = "joe2"
a.Age=13
a.human.Sex = 2
// a.Sex=3 可以直接使用
fmt.Println(a,b)
// 嵌入结构怎么赋值
}
如果匿名字段和外层结构有同名字段,应该如何进行操作?
type A struct {
B
// C 会报错 编译器无法定你要使用哪个Name
Name string
}
type B struct {
Name string
}
type C struct {
Name string
}
func main(){
a:=A{Name:"A",B:B{Name:"B"}}
fmt.Println(a.Name,a.B.Name)
}
方法 method
- Go 中虽然没有 class,但依旧有 method
- 通过显示说明 receiver 来实现与某个类型的组合
- 只能为同一个包中的类型定义方法
- receiver 可以是类型的值或指针
- 不存在方法重载
- 可以使用值或指针来调用方法,编译器会自动完成转换
- 从某种意义上说,方法是函数的语法糖,因为 receiver 其实就是方法所接收的第一个参数
- 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构方法
- 类型别名不会拥有底层类型所附带的方法
- 方法可以调用结构中的非公开字段
type A struct {
Name string
}
type B struct {
Name string
}
func main() {
a:=A{}
a.Print()
fmt.Println(a.Name)
b:=B{}
b.Print()
fmt.Println(b.Name)
}
// 定义方法,先写括号定义了A这个类型有一个方法Pring
func (a *A) Print(){ //引入拷贝
a.Name="AA"
fmt.Println("A")
}
func (b B) Print(){
fmt.Println("A")
}
// 类型别名和方法的组合
type TZ int
func main(){
var a TZ
a.Print()
}
// 第一个括号就是receiver 就是谁来接收它
func (a *TZ) Print(){
fmt.Println("TZ")
}
方法和函数
type TZ int
func main(){
var a TZ
a.Print() //method value
(*TZ).Print(&a) // method expression
}
func (a *TZ) Print(){
fmt.Println("TZ")
}
// 方法是可以访问结构的私有字段,大写小写只对package的限制的。在本包内都是可见的
// 根据为结构增加方法的知识,尝试声明一个底层类型为int的类型,并实现调用某个方法就递增100
// 如 a:=0 调用a.Increase()之后,a从0变成100
type TZ int
func (tz *TZ) Increase(num int){
*tz+=TZ(num) //要做强制类型转换
}
func main() {
// 因为不是结构类型所以要使用var
var a TZ
a.Increase(100)
fmt.Println(a)
}
接口 interface
- 不需要显式说明你实现了哪个接口,只要实现了相同签名的功能,编译器就会认是实现了相应的接口
- 接口是一个或多个方法签名的集合
- 只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显式声明实现了哪个接口,这称为 Structural Typing
- 接口可以匿名嵌入其它接口,或嵌入到结构中
- 将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制器的状态,也无法获取指针
- 只有当接口存储的类型和对象都为 nil 时,接口才等于 nil
- 接口调用不会做 receiver 的自动转换
- 接口同样支持匿名字段方法
- 接口也可以实现类似 OOP 中的多态
- 空接口可以作为任何类型数据的容器
type USB interface {
Name() string
Connect()
}
type PhoneConnector struct{
name string
}
func (pc PhoneConnector) Name() string {
return pc.name
}
func (pc PhoneConnector) Connect() {
fmt.Println("Connect:", pc.name)
}
func main() {
var a USB
a = PhoneConnect{"PhoneConnector"}
// 可以简写成a:= PhoneConnect{"PhoneConnector"} , 但这样写不能直观的看出来是否是实现了接口USB
a.Connect()
Disconnect(a)
}
func Disconnect(usb USB){
// 判断类型
if pc,ok:=usb.(PhoneConnect);ok {
fmt.Println("Disconnected")
return
}
fmt.Println("Unknown device")
}
// 可以允许传递空接口
func Disconnect(usb interface{}){
// 判断类型
if pc,ok:=usb.(PhoneConnect);ok {
fmt.Println("Disconnected")
return
}
fmt.Println("Unknown device")
// type switch
switch v:=usb.(type){
case PhoneConnector:
fmt.Println("Disconnected",v.name)
default:
fmt.Println("Unknown device.")
}
}
// 嵌入接口
type USB interface {
Name() string
Connecter
}
type Connecter interface {
Connect()
}
// 类型转换 USB可以转换成Connecter 但Connecter不能转换成USB
反射 reflection
- 反射可大大提高程序的灵活性,使得 interface{}有更大的发挥余地
- 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
- 反射会将匿名字段作为独立字段(匿名字段本质)
- 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 point-interface
- 通过反射可以“动态”调用方法
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("Hello world.")
}
type Manger struct{
User
title string
}
func main() {
u:=User(1,"OK",12)
Info(u)
// 取manager的匿名字段
m:=Manager{User:User{1,"ok",32},title:"boss"1}
t:=reflect.TypeOf(m)
fmt.Printf("%#v\n",t.Field(0))
fmt.Printf("%#v\n",t.Field(1))
//也可以使用下面这种方法
fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1})) //取匿名字段的第二个字段
}
func Info(o interface{}) {
t:=reflect.TypeOf(o)
fmt.Println("Type:",t.Name())
// 判断类型
if k:=t.Kind();k!=reflect.Struct{
fmt.Println("XX")
return
}
v:=reflect.ValueOf(o)
fmt.Println("Fields:")
for i:=0;i<t.NumField();i++ {
f:=t.Field(i)
val:=v.Field(i).Interface()
fmt.Printf("%6s:%v=%v\n",f.Name,f.Type,val)
}
// 打印方法
for i:=0;i<t.NumMethod();i++{
m:=t.Method(i)
fmt.Printf("%6s:%v\n",m.Name,m.Type)
}
}
type User struct {
Id int
Name string
Age int
}
func (u User) Hello(name string){
fmt.Println("Hello",name," my name is ",u.Name)
}
func main() {
x:=123
v:=reflect.ValueOf(x)
v.Elem().SetInt(999)
fmt.Println(x)
// 处理复杂结构
u:=User{1,"OK",12}
Set(&u)
fmt.Println(u)
// 反射调用方法
v:=reflect.ValueOf(u)
mv:=v.MethodByName("Hello")
args:=[]reflect.Value{reflect.ValueOf("joe")}
mv.Call(args)
}
func Set(o interface{}){
v:=reflect.ValueOf(o)
if v.Kind()==reflect.Ptr&&!v.Elem().CanSet() {
fmt.Println("不能修改")
return
}else{
v=v.Elem()
}
f:=v.FieldByName("Name")
if !f.IsValid() {
fmt.Println("BAD")
return
}
if f.Kind()==reflect.String {
f.SetString("HaHa")
}
}
并发 concurrency
并发不是并行,并发主要由切换时间片来实现“同时”运行,在并行则是直接利用多核实现多线程的运行,但 go 可以设置使用核数,以发挥多枋计算机的能力,Goroutine 奉行通过通信来共享内存,而不是共享内存来通信;
定义一个函数,使用 go 命令
// 怎么运行一个go routine
package main
import (
"fmt"
"time"
)
func main() {
go Go()
time.Sleep(2*time.Second)
}
func Go() {
fmt.Println("Go! go! go!")
}
// 以上的是最简单的go routine
// 以上这种方式无法做一Go()执行完毕后,通知main函数,“我执行完毕了,可以结束了”
// 要做到这种通信的能力,我们使用channel
channel
- channel 是 goroutine 的沟通桥梁,大都是阻塞同步的
- 通过 make 创建,close 关闭
- 可以使用 for range 来迭代不断操作 channel
- 可以设备单向或双向通道
- 可以设置缓存大小,在未被填满前不会发生阻塞
func main() {
c:=make(chan bool)
go func(){
fmt.Println("GO GO GO!!!")
c <- true //执行go go go 打印,把channel的一个返回值通知main可以结束
}()
<-c //main函数执行到这个等待channel的返回值,当有返回值结束
}
// 使用for range来迭代
func main() {
c:=make(chan bool)
go func(){
fmt.Println("go go go")
c <- true
close(c)
}()
for v :=range c {
fmt.Println(v)
}
}
// 使用channel迭代时,必须有一个地方明确关闭它,并且是关闭成功的。否则程序会变成死锁的
// 有缓存与无缓存的主要区别
// 无缓存时 是阻塞的
// 有缓存时
func main() {
c:=make(chan bool)
go func() {
fmt.Println("Go Go Go")
<-c
}()
c <- true //无缓存时它会等到这个值被读掉,再结束
}
// 而有缓存时,
func main() {
c:make(chan bool,1)
go func(){
fmt.Println("Go Go Go")
<-c
}()
c <- true //有缓存时它会把值放到缓存中,爱读不读:);不会执行匿名函数,
}
// 即有缓存是异步的,无缓存是同步阻塞的
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
c:=make(chan bool)
for i:=0;i<10;i++ {
go Go(c,i)
}
<-c
}
func Go(c chan bool,index int) {
a:=1
for i:=0;i<100000000;i++ {
a+=i
}
fmt.Prinln(i,a)
if index == 9 {
c <- true
}
}
// 上例中,在使用多核提交效率时,用最后一个channel的值来判断结束是不对的
// 导致有些channel没有执行就退出了
// 要解决这个问题可以使用缓存 ,如下
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
c:=make(chan bool,10) //有10个channel就创建10个缓存
for i:=0;i<10;i++ {
go Go(c,i)
}
for i:=0;i<10;i++{
<-c //取10次channel的值就结束了
}
}
func Go() {
a:=1
for i:=0;i<100000000;i++ {
a+=i
}
fmt.Prinln(i,a)
c <- true
}
// 第二种解决方案,通过sync包来解决的,通过wait group
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
// 创建waitGroup来实现,main函数主要是来判断waitgroup的完成数来结束
wg:=sync.WaitGroup{}
wg.Add(10)
for i:=0;i<10;i++ {
go Go(wg,i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup,index int) {
a:=1
for i:=0;i<100000000;i++ {
a+=i
}
fmt.Prinln(i,a)
wg.Done() //标记完成一个
}
select
- 可以处理一个或多个 channel 的发送与接收
- 同时有多个可用的 channel 时按Random顺序处理
- 可用空的 select 来阻塞 main 函数
- 可设置超时
package main
import(
"fmt"
)
func main() {
c1,c2:=make(chan int),make(chan string)
o:=make(chan bool)
go func(){
for{
select{
case v,ok:=<-c1:
if !ok {
o<-true
break
}
fmt.Println("c1",v)
case v,ok:=<-c2
if !ok{
o<-true
break
}
fmt.Println("c2",v)
}
}
}()
c1<-1
c2<-"Hi"
c1<-3
c2<-"hello"
close(c1)
close(c2)
<-o
}
将 select 作为发送者来实现一个应用
package main
import (
"fmt"
)
func main() {
c:=make(chan int)
go func(){
for v:=range c{
fmt.Println(v)
}
}()
for{
select {
case c<-0:
case c<-1:
}
}
}
// 超时 c一直没有输入值
func main() {
c:=make(chan bool)
select{
case v:<-c:
fmt.Println(v)
case <-time.After(3*time.Second):
fmt.Println("")
}
}
项目
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6714