字符串
与其它主要编程语言的差异
- string是数据类型,不是引用或指针类型
- string是只读的byte slice,len函数可以是它包含的byte数
- string的byte数组可以存放任何数据
Unicode UTF8
- Unicode是一种字符集(code point)
- UTF8是unicode的存储实现(转换为字节序列的规则)
编码与存储
字符 | "中" |
---|---|
Unicode | 0x4E2D |
UTF-8 | 0xE4B8AD |
string/[]byte | [0xE4, 0xB8, 0xAD] |
常用字符串函数
- strings 包 (https://golang.org/pkg/strings/)
- strconv 包 (https://golang.org/pkg/strconv/)
函数:一等公民
与其它主要编程语言的差异
- 1> 可以有多个返回值
- 2> 所有参数都是值传递: slice, map, channel 会有传引用的错觉
- 3> 函数可以作为变量的值
- 4> 函数可以作为参数和返回值
学习函数式编程
- book
函数: 可变参数及defer
可变参数
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
defer 函数
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"
行为(方法)定义
与其它主要编程语言的差异
type Employee struct {
Id string
Name string
Age int
}
//第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
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)
}
Duck Type 式接口实现
//接口定义
type Programmer interface {
WriteHelloWorld() Code
}
//接口实现
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
Go 接口
与其它主要编程语言的差异
- 1> 接口为非入侵性, 实现不依赖与接口定义
- 2> 所以接口的定义可以包含在接口使用者包内
自定义类型
1. type IntConvertionFn fun(n int) int
2. type MyPoint int
多态
空接口与断言
- 1> 空接口可以表示任何类型
- 2> 通过断言来将空接口转换为制定类型
v, ok := p.(int) //ok=true 时为转换成功
Go接口最佳实践
倾向于使用小的接口定义,很多接口只包含一个方法
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 {
...
}
Go的错误机制
与其它主要编程语言的差异
- 1> 没有异常机制
- 2> error类型实现了error接口
- 3> 可以通过errors.New来快速创建错误实例
type error interface {
Error() string
}
errors.New("n must be in the range[0, 11]")
最佳实践
定义不同的错误变量,以便于判断错误类型
var LessThanTwoError error = errors.New("n must be greater than 2")
var GreaterThanHundredError error = errors.New("n must be less than 100")
...
func TestGetFibonacci(t *testing.T) {
var list []int
list, err := GetFibonacci(-10)
if err == LessThanTwoError {
t.Error("Need a larger number")
}
if err == GreaterThanHundredError {
t.Error("Nedd a larger number")
}
}
及早失败,避免嵌套
panic
- 1> panic 用于不可以恢复的错误
- 2> panic退出前会执行defer指定的内容
panic vs os.Exit
- 1> os.Exit退出时不会调用defer指定的函数
- 2> os.Exit退出时不输出当前调用栈信息
recover
/* !< java */
try {
...
} catch(Throwable t) {
}
/* !< C++ */
try {
...
} catch(...) {
}
/* !< golang */
defer func() {
if err := recover(); err != nil {
//恢复错误
}
}()
最常见的"错误恢复"
defer func {
if err := recover(); err != nil {
log.Error("recoverd panic", err)
}
}()
//这种错误修复非常危险
当心! recover成为恶魔
- 1> 形成僵尸服务进程,导致health check失效
- 2> "Let it Crash!"往往是我们恢复不确定性错误的最好方法