命名规范:
- function 的命名有讲究, 首字母是否大写代表了 该函数的可见性, 大写表示可见,小写表示包外不可见
- package name 一般简洁简短即可单词小写,因为import时候可以重命名,所以不用担心命名碰撞
- package name 一般也是source dir的名字,例如 the package in
src/encoding/base64
is imported as"encoding/base64"
but has namebase64
, notencoding_base64
and notencodingBase64
. - package name 引入code中时候,引入包名,使用类似于bufio.Reader 包名+函数名方式更好,更清晰。
- 接口名字, 只有一个方法的接口, 命名通常选择 用方法的名字+er后缀来 完成命名 或者和 struct 构造器差不多的命名
- 接口不用下划线结合方式命名,用MixCapital 方式,也就是首字母大写方式
flow control:
- case 可以选择多个值
-
example:
func shouldEscape(c byte) bool { switch c { case ' ', '?', '&', '=', '#', '+', '%': return true } return false }
defer 是LIFO的结构,defer 函数会在函数出栈的时候执行,但是defer 参数的执行会立马执行,就能以一种较优雅方式来实现函数tracking
例子:
package main import "fmt" func trace(s string) string { func un(s string) { func a() { func b() { func main() { |
output: entering: b
in b
entering: a
in a
leaving: a
leaving: b
new 和 make关键字
- new 直接返回struct 对应指针,并且没有初始化内存结构,被称为zero value,对应的有些 struct 如果能不初始化内存就能使用,就会是一个比较方便的状态。如 sync.Mutex,bytes.Buffer
- new(struct) 返回的数据,当struct 没有额外变量的时候,new 函数返回的数据和 &struct{} 是相同的
- 至于go 没有构造函数这个问题:
- Golang中没有设计构造函数. 取而代之的, 设计Golang的大师希望你用普通函数去实现构造的任务.
- make ,只能用于 map,slice,channel的初始化,初始化的时候,都会赋予默认的内存空间,不会==nil,这个 和new struct,struct里面的变量 没有赋予内存空间 ==nil 不同,至于make返回的是不是指针这个问题
- effective go里面是这样解释的, it returns an initialized (not zeroed) value of type
T
(not*T
). 但是这不意味着,这些结构在传参的时候,会进行值拷贝,因为以map为例 ,map本身其实是一个指针type,传参也不会拷贝map里面的值,导致性能消耗。 - slice 内部其实也是由指针 定义的数据结构,然后传参也是传递指针参数, type slice struct { array unsafe.Pointer len int cap int }
- channel 也是由指针传递 值的拷贝
slice 和 array
array 是值,赋值和函数传参都会导致拷贝值,而想要不经常拷贝值,请用slice
array 的size 是其中的一部分属性, The size of an array is part of its type. The types [10]int
and [20]int
are distinct.
map
获取 map 中不存在的值,返回的是默认值,不是nil, 例如 int 的默认值是0,struct 的默认值就是 {} 空的 struct
go风格中获取是否存在map中的key, 可以通过
_, present := timeZone[tz] ,第二个参数为是否存在,前面的为值
类型转换的参数传参 .(type) 返回第二个参数也为辅助判断是否ok的参数,error也是在返回的第二个参数,由此可得,go风格中,辅助判断是否获取结果成功的参数一般为第二个位置,第一个位置一般是想要的结果,通过判断第二个参数得出第一个结果是否成功拿到
关于默认值和nil
go中默认值和nil 是不一样的,某些类型,如int 的默认值为0,可以想象到的基本类型的默认值 为基本默认值,如bool,float64,string等,而struct的默认值都不为nil,为{} ,默认值为nil的 类型,有slice,map,channel,interface,point等,编译器会提示不能和nil相等的都不会为nil
关于一个比较trick的地方,string的方法,如何输出内容,比较容易出错的地方
Method, value vs points
关于 是 value绑定method,还是pointer 绑定method 有两种不同的表现
- go是值传递,value绑定method的话,意味值,你在这个method改变value的值,函数调用处的value是改变不到值的,因为改变的value的拷贝,而pointer却能改变值
- 而且这两种绑定方式,一般会让函数接口有不同的设计,因为value你在绑定函数里面改变值没有用,所以一般会返回新的value值,而pointer即不用,pointer可以遵照go的常见库的设计,可以返回其他信息和err信息,直接上代码
-
type ByteSlice []byte
func (slice ByteSlice) Append(data []byte) []byte {
// Body exactly the same as the Append function defined above.
}
This still requires the method to return the updated slice. We can eliminate that clumsiness by redefining the method to take a pointer to a ByteSlice as its receiver, so the method can overwrite the caller's slice.func (p *ByteSlice) Append(data []byte) {
slice := *p
// Body as above, without the return.
*p = slice
}用指针绑定struct 的函数,也更方便成实现既有接口的函数,例如很多自带接口,都会返回 err信息以及其他,所以实现这类接口,一般用的都是 pointer,举个例子:Writer 接口
func (p *ByteSlice) Write(data []byte) (n int, err error) {
slice := *p
// Again as above.
*p = slice
return len(data), nil
}
then the type *ByteSlice satisfies the standard interface io.Writer, which is handy. For instance, we can print into one.var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7) - 还有再解释下,绑定在value的函数和绑定到pointer的函数表现的区别,指针绑定的函数, value绑定的函数不能改变原 value的值,而指针绑定的函数能改变原value的值
interface and method
- go 里面的接口,经常是方法比较少的,这样方便实现 和组合
- xx.(type) 能获取该 value的type, 指定转化为某种 type 可以使用 tmpVa,ok:=xxx.(XXtype) 来实现, 不同的interface, struct 在声明的时候已经注明了 type ,例如 type A struct{} , type B interface{}, 当ok为
- false的时候,tmpVa返回的是默认值, type 是自定义一个自己的type的关键字,也可以type 函数为新类型,type OneFunc func() , type A int ,type Float float64等等都是合理的,通过自己声明的type,可以绑定一些其他的方法, interfaces are just sets of methods, which can be defined for (almost) any type.
4. 即使是函数type 也能实现接口,这个在http 包里面有所体现,看起来有点奇怪但是却实用
// The HandlerFunc type is an adapter to allow the use of // ServeHTTP calls f(w, req).
http.Handle("/args", http.HandlerFunc(ArgServer)) 所以,这个的封装,可以用在http 包下的server函数
|
函数type也可以实现接口,这个比较巧妙的做法
接口内嵌,或者说继承概念,可以通过,
type Reader interface { type Writer interface { // ReadWriter is the interface that combines the Reader and Writer interfaces. |
concurrency
go 里面的闭包的概念,和其他语言类似,函数里面调用了其他子函数,子函数能利用的信息是父函数传入的相关信息
func Announce(message string, delay time.Duration) { go func() { time.Sleep(delay) fmt.Println(message) }() // Note the parentheses - must call the function. }
关于 for range 里面的,for 循环里面的variable 是共享变量, 有时候要注意其值的拷贝
举一例子:
func Serve(queue chan *Request) { func Serve(queue chan *Request) { |
channel
- select 用在channel 接受值上面,好处是可以加 default 或者是超时选项,普通的channel接收值表达式,只能阻塞等待
-
tmpFun := func(aChan chan int, comment string) {
fmt.Println(comment)
select {
case a1 := <-aChan:
fmt.Println(a1)
case <-time.After(10 * time.Second):
fmt.Println("time out")
//default:
// fmt.Println("default value")
}
相比 a1:=<- aChan 只能阻塞多了几个选项
error
error 接口方法, Error() string ,如何保持应该有的信息,和原来的error信息,可以使用组合的方式保持原有error信息, for example:
// PathError records an error and the operation and func (e *PathError) Error() string { |
panic and recover
panic 表示的是没有预料到的异常,可以通过 直接在defer 函数里面 调用 recover() 函数来停止这种异常的持续向上抛出,通过这种方式,不影响整体程序的运行.
panic的处理,可以通过 type 方式来只处理,自己有意直接抛出的panic,然后其他的panic继续向上抛出,直至有人捕获或者直接退出
// Error is the type of a parse error; it satisfies the error interface. // error is a method of *Regexp that reports parsing errors by // Compile returns a parsed representation of the regular expression. |