实效Go编程简析
Fri ,Jun 2 ,2017Effective Go是关于如何写出高效Go程序的文档。
分号
Go中的分号不在源码中出现,由词法分析器自动添加。当然If, For中自己写的。 规则是这样的:若在新行前的最后一个标记为标识符(包括 int 和 float64 这类的单词)、数值或字符串常量之类的基本字面或以下标记之一
break continue fallthrough return ++ -- ) }
so:
if i < f() // 错!
{ // 该行行首会被增加一个分号
g()
}
重新声明与重复赋值
Go中的短声明 := 的使用。
f, err := os.Open(name)
(some other code...)
d, err := f.Stat()
在上述的代码段中,err 在第一条语句中被声明,在第二次被重新赋值。
以声明的变量v可以再次出现在 := 有几个条件:
- 本次声明和已声明的err处于同一作用域。
- 类型与之前的类型相应。
- 在此次声明中至少有一个新声明的变量,如d.
For与Switch
注意,此处和C语言中的Goto不一样,标识符只能放在For或者Switch外面。 中间不能有其他语句,否则会报错。 执行效果是跳出当前的For或者Switch语句块,本次不会再重复进入。
如下所示,Here1,Here2分别为for和Switch工作,互不影响。
//第一段代码
Here1:
for v := a[0]; v < 5; v++ {
fmt.Println("for comming", v)
if v == 3 {
fmt.Printf("v==2 %+v \n", a)
break Here1
}
Here2:
switch {
case m == 1:
fmt.Printf("%+v \n", a)
m = 2
break Here2
case m == 2:
fmt.Printf("we get 2")
}
fmt.Printf("end : %+v \n", a)
}
return
switch还存在一个利用断言,动态判断接口类型的语法,如下
//第二段代码
var t interface{}
t = 8888
switch v := t.(type) {
default:
fmt.Printf("unexpected type %T \n", t) // %T 输出 t 是什么类型
case bool:
fmt.Printf("boolean %t\n", t) // t 是 bool 类型
case int:
fmt.Printf("integer %d\n", t) // t 是 int 类型
fmt.Printf("integer %d\n", v) // t 是 int 类型
case *bool:
fmt.Printf("pointer to boolean %t\n", *v) // t 是 *bool 类型
case *int:
fmt.Printf("pointer to integer %d\n", *v) // t 是 *int 类型
}
可命名结果形参与Defer
- Go函数的返回值或结果“形参”可被命名,并作为常规变量使用,就像传入的形参一样。
- 命名后,一旦该函数开始执行,它们就会被初始化为与其类型相应的零值;
- 若该函数执行了一条不带实参的 return 语句,则结果形参的当前值将被返回。
当可命名形参与Defer碰到一起,又会有不一样的问题。具体请看Go语言的defer,你真的懂了吗
由官网的例子,Defer函数的实参(如果该函数为方法则还包括接收者)在推迟执行时就会求值, 而不是在调用执行时才求值。所以,可以由这个特性来监控另一个函数的进入和退出。
New与Make
type test struct {
one int
two bool
three []int
}
func main() {
first := new(test)
second := make([]test,1)
third := new([]bool)
fourth := make([]bool,1)
fifth := new(bool)
sixth := make([]bool,1)
fmt.Printf("%+v \n%+v \n%+v \n%+v \n%+v \n%+v \n", *first, second, *third, fourth, *fifth, sixth)
}
结果是:
{one:0 two:false three:[]} //新声明的类型,除了切片都置零(bool类型的0就是false)。
[{one:0 two:false three:[]}] //初始化了,0 和 false, 但是内层的切片未初始化。
[] // 返回的是一个指向nil的空指针
[false] // 返回的是初始化之后的 bool 切片
false // 内置类型置零,就是false
[false] // 返回初始化资源后的 test 切片
首先,明确的第一点:new返回的是指针,make返回的是object本身。
new: 可以对确定类型的一个对象置零,对slice, map, channel等无用。
make: 只能对slice, map, channel操作,初始化。
因此,针对官网的置零与初始化的差别:对于普通的类型,new和make都可以完成初始化,但是对于slice, channel, map这三个“本质上为引用数据类型”,只能使用make.
二维切片
和C语言的多维数组相同的原理,一共有两种初始化的方法:一次申请再重新分配和多次申请。
const (
x = 2
y = 4
)
func main() {
picture := make([][]int, x) // picture[x][y]。
/*
test := make([]int,x * y)
for i:= range picture {
picture[i], test= test[:y], test[y:] //这招,太神奇。切片的magic.
}
*/
for i := range picture {
picture[i] = make([]int, y)
}
fmt.Printf("%+v \n",picture)
}
输出都是:
[[0 0 0 0] [0 0 0 0]]
映射
没有定义相等的数据类型不能用作映射的键。
打印
有一个坑需要注意:
type MyString string
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", m) // 错误:会无限递归
}
如果以以 %f 调用了Sprintf,它并不是一种字符串格式:Sprintf 只会在它需要字符串时才调用 String 方法,而 %f 需要一个浮点数值。
枚举器 iota
iota可以做表达式的一部分,该表达式隐式重复,可以表达更复杂的枚举,比C更强大。
const (
_ = iota // 忽略第一个0
x // 即x = iota
y // 即y = iota
)
结果:x = 1, y = 2
指针与值
值方法可通过指针和值调用,执行效果都是值方法。
指针方法只能通过指针来调用。如果值是可寻址的,则编译器自动将值调用指针方法转为地址调用。执行效果是指针方法。
总的来说,编译器会帮助coder保持值调用或者指针调用的效果。
断言与接口类型
type Stringer interface {
String() string
}
var value interface{} // 调用者提供的值。
switch str := value.(type) {
case string:
return str
case Stringer:
return str.String()
}
//等价于以下的代码
if str, ok := value.(string); ok { //不采用这种ok形式的话,如果断言失败,程序会崩。
return str
} else if str, ok := value.(Stringer); ok {
return str.String()
}
接口内嵌与结构体继承
Go中接口与继承的选择这部分的主要内容来自这里。
结论是:“为了不重写代码,必须使用继承。而为了存储抽象类型,调用具体类型的方法,必须用接口。”
const (
PARENT = iota
CHILD
GRANDCHILD
)
type object int
type ObjectInterface interface { // object类的接口
ObjectClass() int
}
func (test *object) add() int {
a := int(*test)
a++
*test = object(a)
return a
}
func (test object) ObjectClass() int { // object类的方法
return PARENT
}
type child struct { // child 继承 object
object
}
func (my child) ObjectClass() int { // child 实现Object 接口
return CHILD
}
func (my child) add2() int { // child 类的方法
a := int(my.object)
a++
return a
}
type ChildInterface interface { // child 接口
ObjectInterface
ChildClass() int
}
type grandchild struct { // grandchild 继承 child
child
}
func (my grandchild) ObjectClass()int{ // grand 继承 child 之后,是不用实现ObjectClass()
return GRANDCHILD // 但是会返回child。因此单独实现。
}
func (my grandchild) ChildClass()int { // 实现了child 接口。
return GRANDCHILD
}
func main() {
first := object(0)
second := child{first}
third := grandchild{second}
fmt.Printf("add: %d \n",third.add())
fmt.Printf("add: %d \n",third.add())
fmt.Printf("add: %d \n",third.add2())
fmt.Printf("ObjectClass: %d \n",third.ObjectClass())
if _, ok := interface{}(third).(ObjectInterface); ok {
fmt.Printf("first have \n")
}
if _, ok := interface{}(third).(ChildInterface); ok {
fmt.Printf("second have \n")
}
}
输出是:
add: 1
add: 2
add: 3
ObjectClass: 2
first have
second have
- 未完待续