Go语言圣经
第1章 入门
1.2 命令行参数
// echo1 输出其命令行参数
package main
import (
"os"
"fmt"
"strings"
)
func main () {
var s , sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i]
sep = ""
}
fmt.Println(s)
fmt.Println(strings.Join(os.Args[1:])," ")
}
// echo2 输出其命令行参数
package main
import (
"os"
"fmt"
)
func main () {
s , sep := "",""
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
fmt.Println(s)
}
// echo3
func main () {
fmt.Println(strings.Join(os.Args[1:])," ")
}
fmt.Println(os.Args[1:])
1.3 找出重复行
package main
import (
"bufio"
"os"
"fmt"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
// 注意:忽略input.Err()中可能的错误
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
// dup2 打印输入中多次出现的行的个数和文本
// 它从stdin或指定的文件列表读取
package main
import (
"os"
"fmt"
"bufio"
)
func main() {
counts := make(map[string]int)
files := os.Args[1:]
if len(files) == 0 {
counLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
continue
}
countLines(f, counts)
f.Close()
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
func countLines(f *os.File, counts map[string]int) {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
//注意:忽略input.Err()中可能的错误
}
//简化dup3:第一,它仅读取指定的文件,而非标准输入,因为ReadFile需要一个文件名作为参数;
//第二,我们将统计行数的工作放回main函数中,因为它当前仅在一处用到。
package main
import (
"os"
"io/ioutil"
"fmt"
"strings"
)
func main() {
counts := make(map[string]int)
for _, filename := range os.Args[1:] {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
continue
}
for _, line := range strings.Split(string(data), "\n") {
counts[line]++
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
}
ReadFile函数返回一个可以转化成字符串的字节slice,这样它可以被string.Split分割。
1.5 获取一个URL
//fetch输出从URL获取的内容
package main
import (
"os"
"net/http"
"fmt"
"io/ioutil"
)
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
1.6 并发获取多个URL
//fetchall 并发获取URL并报告它们的时间和大小
package main
import (
"time"
"os"
"fmt"
"net/http"
"io"
)
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch) // 启动一个goroutine
}
for range os.Args[1:] {
fmt.Println(<-ch) //从通道ch接收
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
func fetch(url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err) //发送到通道ch
return
}
nbytes, err := io.Copy(ioutil.Disssscard, resp.Body)
resp.Body.Close() //不要泄露资源
if err != nil {
ch <- fmt.Fprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
1.7 一个Web服务器
//server1 是一个迷你回声服务器
package main
import (
"net/http"
"fmt"
)
func main() {
http.HandleFunc(“/”,handler) //回声请求调用处理程序
}
//处理程序回显请求URL r的路径部分
func handler (w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URl.Path = %q\n", r.URL.Path)
}
//server2是一个迷你的回声和计数器服务器
package main
import (
"sync"
"net/http"
"log"
"fmt"
)
var mu sync.Mutex
var count int
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/count", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
//处理程序回显请求的URL的路径部分
func handler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
count++
mu.Unlock()
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
//counter回显目前为止调用的次数
func counter(w http.ResponseWriter, r *http.Request) {
mu.Lock()
fmt.Fprintf(w, "Count %d\n", count)
mu.Unlock()
}
server3
/处理程序回显HTTP请求
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
for k, v := range r.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
fmt.Fprintf(w, "Host = %q\n", r.Host)
fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
if err := r.ParseForm(); err != nil {
log.Print(err)
}
for k, v := range r.Form {
fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
}
}
指针:Go提供了指针,它的值是变量的地址。Go语言中指针显式可见。使用&操作符可以获取一个变量的地址,使用*操作符可以获取指针引用的变量的值,但是指针不支持算术运算。
方法和接口:一个关联了命名类型的函数称为方法。接口可以用相同的方式处理不同的具体类型的抽象类型,它基于这些类型所包含的方法,而不是类型的描述或实现。
- 程序结构
与其他编程语言一样,Go语言中的大程序都从小的基本组件构建而来:变量存储值;简单表达式通过加和减等操作合并成大的;基本类型通过数组和结构体进行聚合;表达式通过if和for等控制语句来决定执行顺序;语句被组织成函数用于隔离和复用;函数被组织成源文件和包。
如果一个实体在函数中声明,它只在函数局部有效。如果声明在函数外,它将对包里面的所有源文件可见。
短变量声明可以用来声明和初始化局部变量。
指针的值是一个变量的地址。
2.4.1 多重复值
x, y = y, x
a[i],a[j] = a[j], a[i]
使用辗转求余法计算两个整数的最大公约数
func gcd(x, y int) int {
for y != 0 {
x, y = y, x%y
}
return x
}
计算斐波那契数列的第n个数
func fib(n int) int {
x, y := 0, 1
for i := 0; i < n; i++ {
x, y = y, x+y
}
return x
}
2.6.2 包初始化
包定义一个PopCount函数,它返回一个数字中被置位的个位,即在一个uint64的值中,值为1的位的个数,这称为种群统计。它使用init函数来针对每一个可能的8位值预计算一个结果表pc,这样PopCount只需要将8个快查表的结果相加而不用进行64位的计算。(这个不是最快的统计位算法)
package popcount
//pc[i] 是i的种群统计
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
//PopCount返回x的种群统计(置位的个数)
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))]) +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
注意,init中的range循环只使用索引;值不是必需的,所以没必要包含进来。循环可以重写为下面的形式:
for i, _ := range pc {
2.7 作用域
声明的作用域是声明在程序文本中出现的区域,它是一个编译时属性。变量的生命周期是变量在程序执行期间能被程序的其他部分所引用的起止时间,它是一个运行时属性。
func main() {
x := "hello!"
for i := 0; i < len(x); i++ {
x:= x[i]
if x != '!' {
x := x + 'A' - 'a'
fmt.Printf("%c", x) //"HELLO" (每次迭代一个字母)
}
}
}
像for循环一样,除了本身的主体块之外,if和swith语言还会创建隐式的词法块。
下面的if-else链展示x和y的作用域:
if x:= f(); x == 0{
fmt.Println(x)
} else if y := g(x); x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Println(x, y) //编译错误: x与 y在这里不可见
第二个if语句嵌套在第一个中,所以第一个语句的初始化部分变量在第二个语句中是可见的。
同样的规则可以应用于switch语句:条件对应一个块,每个case语言体对应一个块。
短变量声明依赖一个明确的作用域。
在另一个var声明中声明err,避免使用:=
var cwd string
func init() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
- 基本数据
Go的数据类型分为四大类:基本类型、聚合类型、引用类型、接口类型。基础类型包括数字、字符串和布尔型。聚合类型-数组和结构体-是通过组合各种简单类型得到的更复杂的数据类型。引用是一大分类,其中包含多种不同类型,如指针,map,函数,以及通道。它们的共同点是全都间接指向程序变量或状态,于是操作引用数据的效果就会遍及该数据的全部引用。
3.1 整数
rune类型是int32类型的同义词,常常用于指明一个值是Unicode码点。这两个名称可互换使用。同样,
byte类型是uint8类型的同义词,强调一个值是原始数据,而非量值。最后,还有一种无符号整数uintptr,
其大小并不明确,但足以完整存放指针。uintptr类型仅仅用于底层编程,例如在Go程序与C程序库或操作系统的接口界面。