Go语言学习
[TOC]
Day 02 官方文档补充
格式化
- tab缩进
注释
- 单行//
- 多行/*..*/
命名
- 包名:小写字母命名,不应使用下划线或驼峰记法。
- 接口名
- 只包含一个方法的接口应当以该方法的名称加上 - er 后缀来命名,如 Reader、Writer、 Formatter、CloseNotifier 等
- 驼峰记法:Go 中约定使用驼峰记法 MixedCaps 或 mixedCaps 而非下划线的方式来对多单词名称进行命名
分号
Go语言使用分号结尾,但词法分析器会使用一条简单的规则来自动插入分号,代码中不必写分号。
如果新行前的标记为语句的末尾,则插入分号.
if i < f() // wrong!
{ // wrong! 此处词法分析器会插入分号。
g()
}
控制结构
Go中无do...while,或者while...,全部用for代替。
switch 要更灵活一点;if 和 switch 像 for 一样可接受可选的初始化语句; 此外,还有一个包含类型选择和多路通信复用器的新控制结构:select。
没有圆括号,而其主体必须始终使用大括号括住。
// if
if x > 0 {
return y
}
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
//
for key, value := range oldMap {
newMap[key] = value
}
// 去掉value
for key := range m {
if key.expired() {
delete(m, key)
}
}
// 去掉key
for _, value := range m {
//
}
// switch 自上而下逐一进行求值直到匹配为止
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
// case 可通过逗号分隔来列举相同的处理条件
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
函数
多值返回:
func (file *File) Write(b []byte) (n int, err error)
可命名结果形参:Go 函数的返回值或结果 “形参” 可被命名,并作为常规变量使用,就像传入的形参一样。 命名后,一旦该函数开始执行,它们就会被初始化为与其类型相应的零值; 若该函数执行了一条不带实参的 return 语句,则结果形参的当前值将被返回。
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
}
- Defer:推迟执行函数
- 典型的例子就是解锁互斥和关闭文件。
- 被推迟的函数按照后进先出(LIFO)的顺序执行
// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}
数据
Go 提供了两种分配原语,即内建函数 new 和 make。
-
new:用来分配内存的内建函数, 但与其它语言中的同名函数不同,它不会初始化内存,只会将内存置零。
- new(T) 会为类型为 T 的新项分配已置零的内存空间, 并返回它的地址,也就是一个类型为 *T 的值。用 Go 的术语来说,它返回一个指针, 该指针指向新分配的,类型为 T 的零值
- “零值属性” 是传递性的
-
构造函数与复合字面量:初始化函数,复合字面量表达式以创建新的实例
- File{fd, name, nil, 0} (按顺序全部列出)
- File{fd: fd, name: name} (字段: 值 对的形式明确地标出元素,初始化字段时就可以按任何顺序出现,未给出的字段值将赋予零值)
- new(File) 和 &File{} 是等价的
-
make:只用于创建切片、映射和信道,并返回类型为 T(而非 *T)的一个已初始化 (而非置零)的值。
- 三种类型本质上为引用数据类型,它们在使用前必须初始化
- 只适用于映射、切片和信道且不返回指针
var p *[]int = new([]int) // 分配切片结构;*p == nil;基本没用
var v []int = make([]int, 100) // 切片 v 现在引用了一个具有 100 个 int 元素的新数组
- Slice 切片通过对数组进行封装,为数据序列提供了更通用、强大而方便的接口
- 只要切片不超出底层数组的限制,它的长度就是可变的,只需将它赋予其自身的切片即可
- 切片的容量 cap(slices) 可取得的最大长度
- 切片的长度 len(slices)
- 追加数据 append(slices, data []int) 若数据超出其容量,则会重新分配该切片
- map key-value
- 仅需判断映射中是否存在某项而不关心实际的值,可使用 空白标识符 (_)来代替该值的一般变量
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}
初始化
- 常量
- 在编译时初始化,即便它们可能是函数中定义的局部变量。
- 常量只能是数字、字符(符文)、字符串或布尔值
- 不支持math.Sin(math.Pi/4)等需要在运行时调用的定义
- 枚举常量使用枚举器 iota 创建
- iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
- 使用iota能简化定义,在定义枚举时很有用。
const (
ZERO = iota //0
ONE = 1
TWO = iota //1
)
-
常量
- 其初始值可以是在运行时才被计算的一般表达式
-
init 函数
- 每个源文件都可以通过定义自己的无参数 init 函数来设置一些必要的状态。 (其实每个文件都可以拥有多个 init 函数。)
- 包中的所有变量声明都通过它们的初始化器求值后 init 才会被调用, 而那些 init 只有在所有已导入的包都被初始化后才会被求值。init 结束就意味着初始化结束
- 除了不能被表示成声明的初始化外,init 函数还常被用在程序真正开始执行前,检验或校正程序的状态。
方法
- 不能被表示成声明的初始化外,init 函数还常被用在程序真正开始执行前,检验或校正程序的状态。
- 若该值是可寻址的, 那么该语言就会自动插入取址操作符来对付一般的通过值调用的指针方法。 b.Write 编译器会将它重写为 (&b).Write
接口和其他类型
空白标识符
- 多重赋值,可使用空白标识符来丢弃无关的值
- 未使用的导入和变量
- 导入某个包或声明某个变量而不使用它就会产生错误。
- 未使用的包会让程序膨胀并拖慢编译速度, 而已初始化但未使用的变量不仅会浪费计算能力,还有可能暗藏着更大的 Bug
- 空白标识符来引用已导入包中的符号,可让编译器停止关于未使用导入的报错,
- 将未使用的变量 赋予空白标识符也能关闭未使用变量错误。
package main
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
}
- 为副作用而导入
- 导入某个包只是为了其副作用, 而没有任何明确的使用, 可使用空白标识符(若它有名字而我们没有使用,编译器就会拒绝该程序。)
并发
Go 语言另辟蹊径,它将共享的值通过信道传递,实际上,多个独立执行的线程从不会主动共享
不要通过共享内存来通信,而应通过通信来共享内存。
Goroutine 具有简单的模型:它是与其它 goroutine 并发运行在同一地址空间的函数。它是轻量级的, 所有消耗几乎就只有栈空间的分配。而且栈最开始是非常小的,所以它们很廉价, 仅在需要时才会随着堆空间的分配(和释放)而变化。
Goroutine 在多线程操作系统上可实现多路复用,因此若一个线程阻塞,比如说等待 I/O, 那么其它的线程就会运行。Goroutine 的设计隐藏了线程创建和管理的诸多复杂性。
Channels
信道与映射一样,也需要通过 make 来分配内存。其结果值充当了对底层数据结构的引用。 若提供了一个可选的整数形参,它就会为该信道设置缓冲区大小。默认值是零,表示不带缓冲的或同步的信道。
无缓冲信道在通信时会同步交换数据,它能确保(两个 goroutine)计算处于确定状态。
接收者在收到数据前会一直阻塞。若信道是不带缓冲的,那么在接收者收到值前, 发送者会一直阻塞;若信道是带缓冲的,则发送者直到值被复制到缓冲区才开始阻塞; 若缓冲区已满,发送者会一直等待直到某个接收者取出一个值为止。
Go 最重要的特性就是信道是一等值,它可以被分配并像其它值到处传递。 这种特性通常被用来实现安全、并行的多路分解。
Parallelization
- 并发是用可独立执行的组件构造程序的方法,
- 并行则是为了效率在多 CPU 上平行地进行计算
尽管 Go 的并发特性能够让某些问题更易构造成并行计算, 但 Go 仍然是种并发而非并行的语言,且 Go 的模型并不适合所有的并行问题
Select
select 语句用于在多个发送/接收信道操作中进行选择。
select 语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select 会随机地选取其中之一执行。
该语法与 switch 类似,所不同的是,这里的每个 case 语句都是信道操作。
package main
import (
"fmt"
"time"
)
func server1(ch chan string) {
time.Sleep(6 * time.Second)
ch <- "from server1"
}
func server2(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
//from server2
错误
type error interface {
Error() string
}
// PathError 记录一个错误以及产生该错误的路径和操作。
type PathError struct {
Op string // "open"、"unlink" 等等。
Path string // 相关联的文件。
Err error // 由系统调用返回。
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
//PathError 的 Error 会生成如下错误信息:
//open /etc/passwx: no such file or directory
Panic
panic 函数,它会产生一个运行时错误并终止程序 (但请继续看下一节)。该函数接受一个任意类型的实参(一般为字符串),并在程序终止时打印。 它还能表明发生了意料之外的事情,比如从无限循环中退出了。
Recover
WEB
package main
import (
"flag"
"html/template"
"log"
"net/http"
)
// 地址端口
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
//模板
var templ = template.Must(template.New("qr").Parse(templateStr))
//主程序
func main() {
flag.Parse()
//将 QR 函数绑定到服务器的根路径
http.Handle("/", http.HandlerFunc(QR))
//调用 http.ListenAndServe 启动服务器
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe:", err)
}
}
//表单值传给 templ.Execute 执行因而重写了 HTML 文本
func QR(w http.ResponseWriter, req *http.Request) {
templ.Execute(w, req.FormValue("s"))
}
//从 {{if .}} 到 {{end}} 的代码段仅在当前数据项(这里是点 .)的值非空时才会执行。
const templateStr = `
<html>
<head>
<title>QR Link Generator</title>
</head>
<body>
{{if .}}
<img src="http://chart.apis.google.com/chart?chs=300x300&cht=qr&choe=UTF-8&chl={{.}}" />
<br>
{{.}}
<br>
<br>
{{end}}
<form action="/" name=f method="GET"><input maxLength=1024 size=70
name=s value="" title="Text to QR Encode"><input type=submit
value="Show QR" name=qr>
</form>
</body>
</html>
`
FAQ
- mac 中安装golang调试器dlv出现抛错
debugserver or lldb-server not found: install XCode's command line tools or lldb-server
重新安装下xcode-select就好了
xcode-select --install
reference
有疑问加站长微信联系(非本文作者)