《Go 语言从入门到实战》 的学习笔记,欢迎阅读斧正。感觉该专栏整体来说对有些后端编程经验的来说比无后端编程经验的人更友好。
数据类型
运算符
算数运算符

比较运算符

用 == 比较数组
相同维数切含有相同个数元素的数组才可以比较,每个元素都相同的才相等。
import "testing"
// 相同维数切含有相同个数元素的数组才可以比较,每个元素都相同的才相等。
func TestCompareArray(t *testing.T) {
	a := [...]int{1, 2, 3, 4}
	b := [...]int{1, 3, 4, 5}
	//c := [...]int{1, 2, 3, 4, 5}
	d := [...]int{1, 2, 3, 4}
	e := [...]int{1, 4, 3, 5}
	// 输出 false
	t.Log(a == b)
	// 长度不同的两个数组,比较会得到一个编译错误
	//t.Log(a == c)
	// 输出 true
	t.Log(a == d)
	// 输出 false
	t.Log(b == e)
}
逻辑运算符

位运算符
按位清零运算符

import "testing"
const (
	Readable = 1 << iota
	Writable
	Executable
)
// 按位清零运算符
func TestBitClear(t *testing.T) {
	a := 7 // 0111
	// true true true
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
	a = a &^ Readable
	// 6
	t.Log(a &^ Readable)
	// false true true
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
循环
Go 语言只支持循环关键字 for
While条件循环
无限循环
条件
if 条件

 与其他语言差异:
- condition 表达式结果必须是布尔值
- 支持变量赋值
if var declaration; condition{
//do something
switch 条件

与其他语言差异:
- 条件表达式不限制为常量或者整数;
- 单个 case 中,可以出现多个结果选项,使用逗号分隔;
- 与C语言等规则相反,Go语言不需要用 break 来明确退出一个 case;
- 可以不设定 switch 之后的条件表达式,在此种情况下,整个 switch 结构与多个 if…else… 的逻辑作用等同
数组和切片
数组
声明

遍历

截取

切片
声明

内部结构

注意:切片不可以比较
Map
声明

访问的 key 不存在时,仍会返回 0 值,不能通过返回 nil 来判断元素是否存在(int 类型,默认是 0,如果换成 string 类型,默认就是空字符串了)
Map 与工厂

import (
	"testing"
)
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 }
	// 输出 2 4 8
	t.Log(m[1](2), m[2](2), m[3](2))
}
实现 set

字符串
与其他语言差别:
- string 是数据类型,不是引用或指针类型
- string 是只读的 byte slice,len() 函数可以它所包含的 byte 数
- string 的 byte 数组可以存放任何数据
函数
- 可以有多个返回值
- 所有参数都是值传递
- 函数可以作为变量的vi
- 函数可以作为参数和返回值
这个需要好好看看,好像还有闭包之类的知识
// 延时一秒后返回入参
func SlowFun(op int) int {
	time.Sleep(time.Second * 1)
	return op
}
// 定义一个函数timeSpent帮助记录入参函数的执行耗时
//
//	入参是一个函数,               返回也是一个函数
func timeSpent(入参函数名 func(op int) int) func(op int) int {
	return func(返回函数的参数 int) int {
		start := time.Now()
		ret := 入参函数名(返回函数的参数) // 执行有入参的该函数
		fmt.Println("该入参函数执行结果:", ret)
		fmt.Println("时间损耗:", time.Since(start).Seconds())
		return ret
	}
}
func TestFn1(t *testing.T) {
	// 给timeSpent入参一个函数, 我们返回一个函数, 这个函数执行这个入参函数并打印该的执行时间
	// tsSF 得到的就是返回的函数,我们给这个函数提供一个 「返回函数的参数」-> 10
	tsSF := timeSpent(SlowFun)
	// 打印执行
	t.Log(tsSF(10))
}
可变参数

defer 函数
延迟执行函数,可以当做 Java 中的 try…catch…finally 使用
 
面向对象

封装数据和行为
结构体定义

初始化

行为/方法定义

接口
定义
type Programmer interface {
	WriteHelloWorld() Code
}
实现
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
	return "fmt.Println(\"Hello World!\")"
}
接口变量

自定义类型
type IntConv func(op int) int 注意 func
复合
Go 不支持继承,但是可以通过复合的方式来复用
匿名类型嵌入
import (
	"fmt"
	"testing"
)
type Pet struct {
}
func (p *Pet) Speak() {
	fmt.Print("...")
}
func (p *Pet) SpeakTo(host string) {
	p.Speak()
	fmt.Println("\n", host)
}
type Dog struct {
	//p *Pet
	Pet // 内嵌的结构类型没法当继承来使用
}
不是继承,如果把内部的 struct 看做父类,外部的 struct 看成子类,会发现以下问题:
- 不支持子类替换
- 子类并不是继承了父类的方法
- 父类的定义的方法无法访问子类的数据和方法
多态
代码:
import (
	"fmt"
	"testing"
)
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!\")"
}
// interface 作为入参,必须是指针实例
func writeFirstProgram(p Programmer) {
	fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}
func TestPolymorphism(t *testing.T) {
	goProg := &GoProgrammer{}// 注意 &,取地址或者理解为指针
	javaProg := new(JavaProgrammer)
	writeFirstProgram(goProg)
	writeFirstProgram(javaProg)
}
空接口
- 空接口可以表示任何类型(可以理解为 Java 中的 Object)
- 通过断言来将空接口转换为指定类型v, ok := p.(int) // ok = true 时转换成功
import (
	"fmt"
	"testing"
)
// 空接口可以代表任何类型
func DoSomething(p interface{}) {
	// if 版本:
	//if i, ok := p.(int); ok {
	//	fmt.Println("Integer", i)
	//	return
	//}
	//if i, ok := p.(string); ok {
	//	fmt.Println("string", i)
	//	return
	//}
	//fmt.Println("Unknown Type")
	// switch 版本:
	switch v := p.(type) {
	case int:
		fmt.Println("Integer", v)
	case string:
		fmt.Println("string", v)
	default:
		fmt.Println("Unknown Type")
	}
}
func TestEmptyInterfaceAssertion(t *testing.T) {
	DoSomething(1)
	DoSomething("test")
	DoSomething(true)
}
接口使用原则

 第一行可以理解为 单一职责,第三行可以理解为 接口隔离原则
错误机制
- 没有异常机制
- error 类型实现了 error 接口
- 可以通过 errors.New 来快速创建错误实例errors.New("it's a arrer
实践
定义不同的错误变量,以便于判断错误类型

及早失败,避免嵌套
感觉类似于 卫语句
func GetFibonacci1(str string) {
	var (
		i    int
		err  error
		list []int
	)
	if i, err = strconv.Atoi(str); err == nil {
		if list, err = GetFibonacci(i); err == nil {
			fmt.Println(list)
		} else {
			fmt.Println("Error", err)
		}
	} else {
		fmt.Println("Error", err)
	}
}
// GetFibonacci2 相对于 GetFibonacci1 来说,避免了错误嵌套,感觉类似 Java 中卫语句?
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 vs os.Exit
后者不会调用 defer 执行的函数,后者退出时不输出当前调用栈信息
recover
错误恢复代码,类似于 Java 中的 try...catch... 中的 catch
import (
	"errors"
	"fmt"
	"testing"
)
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!"))
}
// 输出:
// Start
// recovered from  Something wrong!
// --- PASS: TestPanicVxExit (0.00s)
使用的时候不能随便 recover,不要随便捕获异常,该抛错误的就抛错误,要不不知道什么错。
package
- 基本的复用模块单元(另外,首字母大写表明可被包外代码访问)
- 代码的 package 可以和所在目录不一致
- 同一目录里的 Go 代码的 package 要保持一致
init 方法
- 在 main 被执行前,所有依赖的 package 的 init 方法都会被执行
- 不同包的 init 函数按照包导入的依赖关系决定执行顺序
- 每个包可以有多个 init 函数
- 包的每个源文件也可以有多个 init 函数,这点比较特殊
测试
单元测试
内置单元测试框架
- Fail,Error:该测试失败,该测试继续,其他测试继续执行
- FailNow,Fatal:该测试失败,该测试中止,其他测试继续执行
- 代码覆盖率:go test-v-cover
- 断言:https://github.com/stretchr/testify
import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"testing"
)
func TestSquare(t *testing.T) {
	inputs := [...]int{1, 2, 3}
	expected := [...]int{1, 4, 9}
	for i := 0; i < len(inputs); i++ {
		ret := square(inputs[i])
		if ret != expected[i] {
			t.Errorf("input is %d, the expected is %d, the actual %d",
				inputs[i], expected[i], ret)
		}
	}
}
func TestErrorInCode(t *testing.T) {
	fmt.Println("Start")
	t.Error("Error")
	fmt.Println("End")
}
func TestFailInCode(t *testing.T) {
	fmt.Println("Start")
	t.Fatal("Error")
	fmt.Println("End")
}
func TestSquareWithAssert(t *testing.T) {
	inputs := [...]int{1, 2, 3}
	expected := [...]int{1, 4, 9}
	for i := 0; i < len(inputs); i++ {
		ret := square(inputs[i])
		assert.Equal(t, expected[i], ret)
	}
}
Benchmark

 相关命令(非 Windows 把双引号去掉):
- go test -bench=“.”
- go test -bench=“.” -benchmem
BDD
Behavior Driven Development,让业务领域的专家参与开发。
 
感觉理解起来就是写清需求……
BDD In Go
项目网站:https://github.com/smartystreets/goconvey
安装:go get -u github.com/smartystreets/goconvey/convey
启动 UI:$GOPATH/bin/goconvey
不安全编程
理解的 Go 的不安全编程是指用到了 unsafe 包中的指针:使用"不安全编程"帮你绕过 Go 语言中的类型检测系统
合理的类型转换以及原子操作不算不安全编程
import (
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"
	"unsafe"
)
type Customer struct {
	Name string
	Age  int
}
// 理解的 Go 的不安全编程是指用到了 unsafe 包中的内容:https://www.cnblogs.com/traditional/p/11890639.html
func TestUnsafe(t *testing.T) {
	i := 10
	f := *(*float64)(unsafe.Pointer(&i))
	t.Log(unsafe.Pointer(&i))
	t.Log(f)
}
// The cases is suitable for unsafe
type MyInt int
// 合理的类型转换
func TestConvert(t *testing.T) {
	a := []int{1, 2, 3, 4}
	b := *(*[]MyInt)(unsafe.Pointer(&a))
	t.Log(b)
}
// 原子类型操作
func TestAtomic(t *testing.T) {
	var shareBufPtr unsafe.Pointer
	writeDataFn := func() {
		data := []int{}
		for i := 0; i < 100; i++ {
			data = append(data, i)
		}
		atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data))
	}
	readDataFn := func() {
		data := atomic.LoadPointer(&shareBufPtr)
		fmt.Println(data, *(*[]int)(data))
	}
	var wg sync.WaitGroup
	writeDataFn()
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			for i := 0; i < 10; i++ {
				writeDataFn()
				time.Sleep(time.Microsecond * 100)
			}
			wg.Done()
		}()
		wg.Add(1)
		go func() {
			for i := 0; i < 10; i++ {
				readDataFn()
				time.Sleep(time.Microsecond * 100)
			}
			wg.Done()
		}()
	}
	wg.Wait()
}
常见任务
- 这些没太花重心学,后期直接看 web 相关框架
内置 JSON 解析
利用 反射 实现,通过 FeildTag 来标识对应的 json 值
var jsonStr = `{
	"basic_info":{
	  	"name":"Mike",
		"age":30
	},
	"job_info":{
		"skills":["Java","Go","C"]
	}
}	`
func TestEmbeddedJson(t *testing.T) {
	e := new(Employee)
	err := json.Unmarshal([]byte(jsonStr), e)
	if err != nil {
		t.Error(err)
	}
	fmt.Println(*e)
	if v, err := json.Marshal(e); err == nil {
		fmt.Println(string(v))
	} else {
		t.Error(err)
	}
}
easyjson
是用代码生成的
相关代码:easyjson
HTTP 服务
import (
	"fmt"
	"net/http"
	"time"
)
func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello World!")
	})
	http.HandleFunc("/time/", func(w http.ResponseWriter, r *http.Request) {
		t := time.Now()
		timeStr := fmt.Sprintf("{\"time\": \"%s\"}", t)
		w.Write([]byte(timeStr))
	})
	http.ListenAndServe(":8080", nil)
}
路由规则
- URL分为两种,末尾是 /:表示一个子树,后面可以跟其他子路径;末尾不是 /,表示一个叶子,固定的路径
- 以 / 结尾的URL可以匹配它的任何子路径,比如 /images会匹配 /images/cute-cat.jpg
- 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理
- 如果没有找到任何匹配项,会返回404错误
default router

更好的 router
httprouter: A high performance HTTP request router that scales well
import (
	"fmt"
	"log"
	"net/http"
	"github.com/julienschmidt/httprouter"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Welcome!\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}
func main() {
	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/hello/:name", Hello)
	log.Fatal(http.ListenAndServe(":8080", router))
}
构建 RESTful
这个看这里的代码吧:geektime/go_learning/code/ch45/roa/resource_oriented_arc.go


















