目录
-
GOROOT:Go的根目录
-
GOPATH:用户工作区,源码必须放这里
-
系统PATH下增加$GOROOT/bin:可以直接执行的命令
-
src源码
-
pkg go install命令 归档文件 .a
-
bin 可执行文件
编译
- 直接执行
go run hello_world.go
- 编译
go build hello_world.go
应用程序入口
- package 必须是main(目录不强制)
- func 必须是main
- 文件名不强制main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
退出
func main 不支持返回值
os.Exit 立即中止,返回状态
命令行参数
func main 不支持传入参数
os.Args获取命令行参数
func main() {
if len(os.Args) > 1{
fmt.Println("Hello ",os.Args[1]) //获取命令行参数
}
os.Exit(-1) //异常退出
}
测试程序
- 源码文件_test结尾,xxx_test.go
- 测试方法Test开头 func TestXX(t *testing.T)
first_test.go
package test
import "testing"
func TestFirst(t *testing.T){
t.Log("test");
}
变量
//全局
var c int
func TestFirst(t *testing.T){
//变量声明
var a int = 1
var b int = 1
//简写
var (
a int = 1
b int = 1
)
//类型推断
a := 1
b := 1
fmt.Println(a)
for i:=0; i<5; i++{
fmt.Println(b)
tmp := a
a = b
b = tmp + a
}
变量交换
a,b = b,a
常量
连续常量
//连续常量
const(
Mon = 1 + iota
Thus
Web
)
//一般常量
const(
GOOGLE = "go"
BAIDU = "bd"
)
//位运算常量
const(
READABLE = 1 << iota //1的二进制 1
WRITEABLE //左移一位 10
EXECABLE //左移一位 100
)
func TestConst(t *testing.T){
t.Log(Mon,Thus,Web)
t.Log(GOOGLE,BAIDU)
t.Log(READABLE,WRITEABLE,EXECABLE)
}
数据类型
bool
string
//标注位数可以忽略平台差别导致的问题
//int uint在32位机器是32位 64位机器是64位
int int8 int16 int32 int64
unit unit8 unit16 uint64 uintptr
byte //uint8的别名
rune //int32别名 unicode编码值
float32 float64
complex64 complex128
类型转换
- 不支持隐式转换(包括原类型和别名类型) type MyInt int64
类型预定义值
- math.MaxInt64
- math.MaxFloat64
- math.MaxUint64
指针类型
- 不支持指针运算
- string是值类型,默认值是空字符串
func TestType(t *testing.T){
var a int = 1
var b int64
b = int64(a) //显式转换
t.Log(b)
}
func TestPtr(t *testing.T){
a := 1
aPtr := &a
t.Log(aPtr) //指针地址 0xc00000a298
t.Logf("%T %T",a,aPtr) // %T格式化符号 (int *int)
}
默认值
- *int ------- nil
- int ------- 0
- float32 ------- 0
- bool ------- false
比较运算
- 数组比较条件(编译错误):
- 相同维
- 相同个数
- 每个元素都相等才是相等
循环
- 只支持for,不需要括号
//一般循环
func TestLoop(t *testing.T){
n := 0
for n <= 5{
t.Log(n)
n++
}
}
//无限循环
func TestRunLoop(t *testing.T){
n := 0
for{
n++
t.Log(n)
}
}
判断
- 不需要括号
- condition结果布尔
- 支持变量赋值
func TestCondition(t *testing.T){
// 变量表达式;条件
if v,err := someFun(2); err == nil{
t.Log("正常",v)
}else{
t.Log("错误",err)
}
}
func someFun(b int) (result int,err error){
err = nil
if b == 1 {
result = b
}else{
err = errors.New("Test Error")
}
return
}
数组
声明
- var a [3]int //声明并初始化为默认零值
- a[0] = 1
- b := [3]int{1, 2, 3} //声明同时初始化
- c := [2][2]int{{1, 2}, {3, 4}} //多维数组初始化
遍历
func TestTravelArray(t *testing.T) {
a := [...]int{1, 2, 3, 4, 5} //不指定元素个数
for idx/*索引*/, elem/*元素*/ := range a {
fmt.Println(idx, elem)
}
}
截取
- a[开始索引(包含), 结束索引(不包含)]
a[1:2] //2
切片
声明
- var s0 []int
- s0 = append(s0, 1)
- s := []int{}
- s1 := []int{1, 2, 3}
- s2 := make([]int, 2, 4)
/*[]type, len, cap
其中len个元素会被初始化为默认零值,未初始化元素不可以访问
*/
-数组 vs 切片:伸缩 比较
map
声明
- m := map[string]int{"one": 1, "two": 2, "three": 3}
- m1 := map[string]int{}
- m1["one"] = 1
- m2 := make(map[string]int, 10 /Initial Capacity/)
元素访问
//m["four"]返回两个值,存在的话第二个是bool的true
if v, ok := m["four"]; ok {
t.Log("four", v)
} else {
t.Log("Not existing")
}
遍历foreach
m := map[string]int{"one": 1, "two": 2, "three": 3}
for k, v := range m {
t.Log(k, v)
}
map实现工厂模式
- Map 的 value 可以是⼀个⽅法
- 与 Go 的 Dock type 接⼝⽅式⼀起,可以⽅便的实现单⼀⽅法对象的⼯⼚模式
func TestMapWithFunValue(t *testing.T) {
m := map[int]func(op int) int{}
m[1] = func(op int) int { return op }
m[2] = func(op int) int { return op * op }
m[3] = func(op int) int { return op * op * op }
t.Log(m[1](2), m[2](2), m[3](2)) //以2为输入参数
}
set的实现
基本定义
- 元素的唯⼀性
- 基本操作
-
- 添加元素
-
- 判断元素是否存在
-
- 删除元素
-
- 元素个数
func TestMapForSet(t *testing.T) {
mySet := map[int]bool{}
//添加元素
mySet[1] = true
n := 3
//判断元素是否存在
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n)
}
//判断元素长度
t.Log(len(mySet))
//删除元素
delete(mySet, 1)
}
字符串
- string 是数据类型,不是引⽤或指针类型
- string 是只读的 byte slice, len 函数可以它所包含的 byte 数(其实就是类似字节数组)
- string 的 byte 数组可以存放任何数据(可见字符 不可见字符)
func TestString(t *testing.T) {
var s string
t.Log(s) //初始化为默认零值“”
s = "hello"
t.Log(len(s)) //长度 5
s[1] = '3' //string是不可变的byte slice,这样写 编译错误
s = "xE4xBAxFF" //可以存储任何二进制数据
t.Log(s) //严
t.Log(len(s)) //长度 3
//byte & unicode区别
s = "中"
t.Log(len(s)) //是byte数
c := []rune(s) //能取出字符串里面的unicode(rune切片)
t.Log(len(c)) //长度1
// t.Log("rune size:", unsafe.Sizeof(c[0]))
t.Logf("中 unicode %x", c[0])
t.Logf("中 UTF8 %x", s)
}
func TestStringToRune(t *testing.T) {
s := "中华人民共和国"
for _, c := range s {
t.Logf("%[1]c %[1]d", c) //汉字 编码
}
}
- Unicode 是⼀种字符集(code point 相当于标准)
- UTF8 是 unicode 的存储实现 (转换为字节序列的规则)
字符 | 中 |
---|---|
Unicode | 0x4E2D |
UTF-8 | 0xE4B8AD |
string/[]byte | [0xE4,0xB8,0xAD] |
字符串操作函数
//用逗号分隔成切片
func TestStringFn(t *testing.T) {
s := "A,B,C"
parts := strings.Split(s, ",")
for _, part := range parts {
t.Log(part)
}
//字符串连接
t.Log(strings.Join(parts, "-"))
}
func TestConv(t *testing.T) {
//整数转字符串
s := strconv.Itoa(10)
t.Log("str" + s)
//字符串转整形
if i, err := strconv.Atoi("10"); err == nil {
t.Log(10 + i)
}
}
函数:一等公民
- 可以有多个返回值
- 所有参数都是值传递: slice, map, channel 会有传引⽤的错觉(其实也是传值,因为slice对应的数组,整个数据结构有指针指向同一数组)
- 函数可以作为变量的值
- 函数可以作为参数和返回值
- 多返回值
func returnMultiValues() (int, int) {
return rand.Intn(10), rand.Intn(20)
}
//调用时忽略一个返回值
a, _ := returnMultiValues()
- 装饰器模式(计时函数)
func timeSpent(inner func(op int) int) func(op int) int {
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent:", time.Since(start).Seconds())
return ret
}
}
func slowFun(op int) int {
time.Sleep(time.Second * 1)
return op
}
//调用
tsSF := timeSpent(slowFun)
t.Log(tsSF(10))
可变参数
//参数会转化成数组
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
defer 延迟最后执行
- 类似 try .. finally,就上panic的异常中断也会继续执行
func TestDefer(t *testing.T) {
defer func() {
t.Log("Clear resources")
}()
t.Log("Started")
panic("Fatal error”) //defer仍会执⾏
}
面对对象
封装数据
type Employee struct {
Id string
Name string
Age int
}
- 创建实例
e := Employee{"0", "Bob", 20}
e1 := Employee{Name: "Mike", Age: 30}
e2 := new(Employee) //注意这⾥返回的引⽤/指针,相当于 e := &Employee{}
e2.Id = "2" //与其他主要编程语⾔的差异:通过实例的指针访问成员不需要使⽤->
e2.Age = 22
e2.Name = “Rose
t.Logf("%T",e2) //*指针类型
封装行为
//第⼀种定义⽅式在实例对应⽅法被调⽤时,实例的成员会进⾏值复制
func (e Employee) String() string {
return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}
- 要修改值就要用这种
//通常情况下为了避免内存拷⻉我们使⽤第⼆种定义⽅式
func (e *Employee) String() string {
return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}
接口
接口定义
type Programmer interface {
WriteHelloWorld() string
}
接口实现 Duck Type
- 有鸭子的特征就是鸭子
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
return "fmt.Println("Hello World")"
}
调用
func TestClient(t *testing.T) {
var p Programmer
p = new(GoProgrammer)
t.Log(p.WriteHelloWorld())
}
特征
- 接⼝为⾮⼊侵性,实现不依赖于接⼝定义(其实就是理解为可以先写完实现,发现可以抽象成接口,就直接抽象出来)
- 所以接⼝的定义可以包含在接⼝使⽤者包内
接口变量
// 接口 = 实现
var prog Coder = &GoProgrammer{}
-
接口初始化后
-
类型
type GoProgrammer struct {}
- 数据(goprogramer的一个实例)
&GoProgrammer{}
自定义类型
- type IntConvertionFn func(n int) int
- type MyPoint int
//简化便于阅读
type IntConv func(op int) int
func timeSpent(inner IntConv) IntConv {
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent:", time.Since(start).Seconds())
return ret
}
}
func slowFun(op int) int {
time.Sleep(time.Second * 1)
return op
}
func TestFn(t *testing.T) {
tsSF := timeSpent(slowFun)
t.Log(tsSF(10))
}
复合(集成)
-
Go 不⽀持继承,但可以通过复合的⽅式来复⽤
-
父类
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Print("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
- 子类(持有父类的指针)
type Dog struct {
p *Pet
}
//重载父类的方法
func (d *Dog) Speak() {
fmt.Print("Wang!")
}
//直接继承
func (d *Dog) Speak() {
d.p.Speak()
}
- 测试
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("Chao")
}
匿名嵌套类型
- 没法当成继承来用,不支持LSP,不支持访问子类方法,就算子类重载了父类的方法,也无法被调用
type Dog struct {
Pet
}
//这里没法被调用
func (d *Dog) Speak() {
fmt.Print("Wang!")
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.speak // ... 调用了父类的speak
}
多态
type Code string
type Programmer interface {
WriteHelloWorld() Code
}
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println("Hello World!")"
}
type JavaProgrammer struct {
}
func (p *JavaProgrammer) WriteHelloWorld() Code {
return "System.out.Println("Hello World!")"
}
func writeFirstProgram(p Programmer) {
fmt.Printf("%T %v
", p, p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T) {
goProg := &GoProgrammer{}
javaProg := new(JavaProgrammer)
writeFirstProgram(goProg)
writeFirstProgram(javaProg)
}
空接口
- 可以表示任何类型,类似object
- 通过断⾔来将空接⼝转换为制定类型
//转换后的值,是否成功
v, ok := p.(int) //ok=true 时为转换成功
func DoSomething(p interface{}) {
//如果传入的参数能断言成整形
if i, ok := p.(int); ok {
fmt.Println("Integer", i)
return
}
if s, ok := p.(string); ok {
fmt.Println("stirng", s)
return
}
fmt.Println("Unknow Type")
//简化版
switch v := p.(type) {
case int:
fmt.Println("Integer", v)
case string:
fmt.Println("String", v)
default:
fmt.Println("Unknow Type")
}
}
func TestEmptyInterfaceAssertion(t *testing.T) {
DoSomething(10)
DoSomething("10")
}
最佳实践
- 倾向于使⽤⼩的接⼝定义,很多接⼝只包含⼀个⽅法
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
- 较⼤的接⼝定义,可以由多个⼩接⼝定义组合⽽成
type ReadWriter interface {
Reader
Writer
}
- 只依赖于必要功能的最⼩接⼝
func StoreData(reader Reader) error {
...
}
错误机制
- 没有异常机制
- error 类型实现了 error 接⼝
type error interface {
Error() string
}
-
可以通过 errors.New 来快速创建错误实例,不过接受者只能通过字符串匹配来区分错误类型
-
最佳实践:及早错误,避免嵌套
errors.New("n must be in the range [0,100]")
var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")
// 返回正常值或者错误类型
func GetFibonacci(n int) ([]int, error) {
if n < 2 {
return nil, LessThanTwoError
}
if n > 100 {
return nil, LargerThenHundredError
}
fibList := []int{1, 1}
for i := 2; /*短变量声明 := */ i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
//没有错误
return fibList, nil
}
// 错误检查机制
func TestGetFibonacci(t *testing.T) {
if v, err := GetFibonacci(1); err != nil {
//区分不同错误类型
if err == LessThanTwoError {
fmt.Println("It is less.")
}
t.Error(err)
} else {
t.Log(v)
}
}
//只有所有地方都没有错误,才输出结果,推荐用法
func GetFibonacci2(str string) {
var (
i int
err error
list []int
)
if i, err = strconv.Atoi(str); err != nil {
fmt.Println("Error", err)
return
}
if list, err = GetFibonacci(i); err != nil {
fmt.Println("Error", err)
return
}
fmt.Println(list)
}
panic
- panic ⽤于不可以恢复的错误
- panic 退出前会执⾏ defer 指定的内容
- panic 的参数是一个空接口
Exit
- os.Exit 退出时不会调⽤ defer 指定的函数
- os.Exit 退出时不输出当前调⽤栈信息
recover(相当于try整个函数catch到defer里面的recover判断)
小心用
- 形成僵⼫服务进程,导致 health check 失效
- “Let it Crash!” 往往是我们恢复不确定性错误的最好⽅法。(等守护进程拉起)
- 只有在延迟函数的内部,调用 recover 才有用。在延迟函数内调用 recover,可以取到 panic 的错误信息,并且停止 panic 续发事件(Panicking Sequence),程序运行恢复正常。如果在延迟函数的外部调用 recover,就不能停止 panic 续发事件。
defer func() {
if err := recover(); err != nil {
//恢复错误(不建议只打log,可能会导致不可预知行为)
debug.PrintStack()
//恢复后也可以打印调用栈
}
}()
func TestPanicVxExit(t *testing.T) {
defer func() {
if err := recover(); err != nil {
fmt.Println("recovered from ", err)
}
}()
fmt.Println("Start")
panic(errors.New("Something wrong!"))
}
package
- 基本复⽤模块单元,以⾸字⺟⼤写来表明可被包外代码访问(编译不了)
- 代码的 package 可以和所在的⽬录不⼀致(与JAVA不同)
- 同⼀⽬录⾥的 Go 代码的 package 要保持⼀致(编译不了)
环境变量
export GOPATH="/Users/xxx/yy/go:/Users/xxx/go_learning"
export PATH="$HOME/.cargo/bin:$PATH"
- /ch15/my_series.go
package series
import "fmt"
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init2")
}
func Square(n int) int {
return n * n
}
func GetFibonacciSerie(n int) []int {
ret := []int{1, 1}
for i := 2; i < n; i++ {
ret = append(ret, ret[i-2]+ret[i-1])
}
return ret
}
本地包导入
- /ch15/client/package_test.go
package client
import (
"ch15/series"
"testing"
)
func TestPackage(t *testing.T) {
t.Log(series.GetFibonacciSerie(5))
t.Log(series.Square(5))
}
init 构造
- 在 main 被执⾏前,所有依赖的 package 的 init ⽅法都会被执⾏
- 不同包的 init 函数按照包导⼊的依赖关系决定执⾏顺序(go自动处理)
- 每个包可以有多个 init 函数(go特点)
- 包的每个源⽂件也可以有多个 init 函数,这点⽐较特殊
如何用别人的包
- 通过 go get 来获取远程依赖
- go get -u 强制从⽹络更新远程依赖(必须先做这步才能用)
- 如果要自己提交代码到github,注意代码在 GitHub 上的组织形式,以适应 go get
- 直接以代码路径开始,不要有 src
package remote
import (
"testing"
cm "github.com/easierway/concurrent_map" //这里起了别名
)
func TestConcurrentMap(t *testing.T) {
m := cm.CreateConcurrentMap(99)
m.Set(cm.StrKey("key"), 10)
t.Log(m.Get(cm.StrKey("key")))
}
依赖管理
- 同⼀环境下,不同项⽬使⽤同⼀包的不同版本(GOPATH GOROOT)
- ⽆法管理对包的特定版本的依赖
verdor
随着 Go 1.5 release 版本的发布, vendor ⽬录被添加到除了 GOPATH 和
GOROOT 之外的依赖⽬录查找的解决⽅案。在 Go 1.6 之前,你需要⼿动
的设置环境变量
查找依赖包路径的解决⽅案如下:
- 当前包下的 vendor ⽬录
- 向上级⽬录查找,直到找到 src 下的 vendor ⽬录
- 在 GOPATH 下⾯查找依赖包
- 在 GOROOT ⽬录下查找
常用工具
- godep https://github.com/tools/godep
- glide https://github.com/Masterminds/g
- dep https://github.com/golang/dep
https://img2020.cnblogs.com/blog/456913/202006/456913-20200615224755733-1108195631.jpg