格式化
Go语言中为了防止格式化问题引发争论,制作了一个格式化工具gofmt
,在写完代码之后只需要gofmt -w *.go
就可以用统一的格式(比如对齐,缩进)来重写你的代码,-w
参数是重写你的文件,不加的话只会打印你的文件内容
命名
- 一个包里面的变量如果要在包外可以被使用,首字母就必须是大写
- 使用
RPC
调用的结构体里的参数也都需要首字母大写 - 包名需要都小写
- Go 中约定使用驼峰记法 MixedCaps 或 mixedCaps 而非下划线的方式来对多单词名称进行命名
分号
Go语言中语句也是由分号来结尾的,只是这些语句不显式的出现在源代码中,词法分析器会在编译过程中为每句语句的结尾添加上分号。
由于分号添加的规则,所以条件控制,循环,函数等语句的左大括号必须不能另起一行,否则会因为添加分号导致错误。
控制结构
循环只有for
// 如同 C 的 for 循环
for init; condition; post { }
// 如同 C 的 while 循环
for condition { }
// 如同 C 的 for(;;) 循环
for { }
若你想遍历数组、切片、字符串或者映射,或从信道中读取消息, range 子句能够帮你轻松实现循环。
for key,value := range oldMap {
newMap[key] = value
}
可以省略第二个键,但是要省略第一个键必须用_
switch
语法除了正常的判断值,还能判断变量类型:
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T",t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n",t) // t has type bool
case int:
fmt.Printf("integer %d\n",t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n",*t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n",*t) // t has type *int
}
switch
语法中的case
语句自带break
,所以不需要显式的写break
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
函数
func ReadFull(r Reader,buf []byte) (n int,err error) {
for len(buf) > 0 && err == nil {
var nr int
nr,err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
数据
数据有两种分配方式new
和make
new
是用来分配内存的内建函数,但它不会初始化内存,只会将内存置零。new(T)
返回一个指针, 该指针指向新分配的,类型为T
的零值。- 内建函数
make(T,args)
的目的不同于new(T)
。它只用于创建切片、映射和信道,并返回类型为T
(而非*T
)的一个已初始化 (而非置零)的值
切片
映射
map
映射与切片一样也是引用类型,将映射传入函数中,并更改了该映射的内容,则此修改对调用者同样可见- 映射的键存在时第二个返回值会返回
true
,不存在时返回false
,seconds,ok := timeZone[tz]
delete(timeZone,"PDT")
,要删除映射中的某项,可使用内建函数 delete,它以映射及要被删除的键为实参。 即便对应的键不在该映射中,此操作也是安全的。
fmt.Println
可以直接使用变量当参数,打印变量的值fmt.Printf
类似C
语言的打印方式
append
append
函数的定义如这样
func append(slice []T,elements ...T) []T
append
会在切片末尾追加元素并返回结果
x := []int{1,2,3}
x = append(x, 4, 5, 6)
- 将一个切片追加到另一个切片中
x := []int{1,2,3}
y := []int{4,5,6}
x = append(x,y...)
常量
- Go 中的常量就是不变量。它们在编译时创建,定义它们的表达式必须也是可被编译器求值的常量表达式。
- 枚举常量使用枚举器 iota 创建。由于 iota 可为表达式的一部分,而表达式可以被隐式地重复,这样也就更容易构建复杂的值的集合了
const (
// 通过赋予空白标识符来忽略第一个值
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
变量
- 变量的初始化与常量类似,但其初始值也可以是在运行时才被计算的一般表达式。
init
方法
- 若该值是可寻址的, 那么该语言就会自动插入取址操作符来对付一般的通过值调用的指针方法。在我们的例子中,变量 b 是可寻址的,因此我们只需通过 b.Write 来调用它的 Write 方法,编译器会将它重写为 (&b).Write。
- 可寻址的意思就是在内存中或者寄存器中能找到。出于方便不管是值还是指针类型的都可以调用指针类型的方法,并且最好所有方法都写成指针类型的方法。
func (p *ByteSlice) Write(data []byte) (n int,err error) {
slice := *p
*p = slice
return len(data),nil
}
接口
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
a = f // a MyFloat implements Abser
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
- 可以判断一个接口是否可以变换为某个类型。若类型断言失败,str 将继续存在且为字符串类型,但它将拥有零值,即空字符串
str,ok := value.(string)
空白标识符
- 导入某个包或声明某个变量而不使用它就会产生错误。未使用的包会让程序膨胀并拖慢编译速度, 而已初始化但未使用的变量不仅会浪费计算能力,还有可能暗藏着更大的 Bug。 然而在程序开发过程中,经常会产生未使用的导入和变量。虽然以后会用到它们, 但为了完成编译又不得不删除它们才行。空白标识符就能提供一个临时解决方案。
import (
"fmt"
"io"
"log"
"os"
)
var _ = fmt.Printf // For debugging; delete when done. // 用于调试,结束时删除。
var _ io.Reader // For debugging; delete when done. // 用于调试,结束时删除。
func main() {
fd,err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
_ = fd
}
goroutine
- 我们称之为 goroutine ,是因为现有的术语—线程、协程、进程等等—无法准确传达它的含义。 Goroutine 具有简单的模型:它是与其它 goroutine 并发运行在同一地址空间的函数。它是轻量级的, 所有消耗几乎就只有栈空间的分配。而且栈最开始是非常小的,所以它们很廉价, 仅在需要时才会随着堆空间的分配(和释放)而变化。
- Goroutine 在多线程操作系统上可实现多路复用,因此若一个线程阻塞,比如说等待 I/O, 那么其它的线程就会运行。Goroutine 的设计隐藏了线程创建和管理的诸多复杂性。
channel
- 信道与映射一样,也需要通过 make 来分配内存。其结果值充当了对底层数据结构的引用。 若提供了一个可选的整数形参,它就会为该信道设置缓冲区大小。默认值是零,表示不带缓冲的或同步的信道。
- 无缓冲信道在通信时会同步交换数据,它能确保(两个 goroutine)计算处于确定状态
- 带缓冲的信道可被用作信号量,例如限制吞吐量。