gopl_hello.go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world!")
}
运行: go run gopl_hello.go
编译:go build gopl_hello.go
代码格式化工具: gofmt
自动添加或删除import 声明:goimports
package
声明语句开始,这个例子里就是package main
, 表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。main
函数也很特殊,它是整个程序执行时的入口。package
声明后面的import
声明扮演的角色。必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。 import
声明必须跟在文件的package
声明之后。os
包
获取外部变量 os.Args
是一个字符串的切片 对标python中的 sys.args
有python经验真的很容易理解。
先看 python 例子实现:py_echo1.py
import sys
print(" ".join(sys.argv[1:]))
echo go语言示例1:gopl_echo1.go
// 实现 linux echo 字符串回显功能,回显输入的值。
package main
import (
"fmt"
"os"
)
func main() {
var s, sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i]
sep = " "
}
fmt.Println(s)
}
注释 \\
声明变量 var
, 若没有显式初始化,则赋予零值,数值类型是0, 字符串类型是空字符串。
字符串连接符 +
赋值运算符 +=
短变量声明(short variable declaration) :=
(这不就是python中的海象运算符(赋值表达式 Assignment Expressions)嘛!语义也也一样!) 可省略 var
声明。
循环语句 for
go 语言只有for
这一种循环语句(python等语言 还有 while)
for initialization; condition; post {
// zero or more statements
}
initialization 可选,必须是简单语句(短变量声明,自增语句,赋值语句或函数调用)
condition 可选, 是个布尔表达式(boolean expression), 在每次循环迭代开始时计算。true
即执行循环体。
post 可选, 在循环体执行结束后执行,之后再次对 condition
求值,为false
时循环结束。
典型情况:
// "while" loop 仅保留 condition
for condition {
// ...
}
// infinite loop 可用 break 或 return 语句终止循环
for {
// ...
}
第二种实现方式,还是先看 python 例子实现:py_echo2.py
import sys
print(" ".join(w for _, w in enumerate(sys.argv[1:]))) # 还是 py_echo1.py 更简洁
echo go语言示例2:gopl_echo2.go
// 实现 linux echo 字符串回显功能,回显输入的值。
package main
import (
"fmt"
"os"
)
func main() {
s, sep:= "", " "
for _, arg:= range os.Args[1:] {
s += sep + arg
}
fmt.Println(s)
}
range
对标python中的 enumerate
, 语言是互通的,有python经验真的很容易理解。_
(下划线),空标识符可用于任何语法需要变量名但程序逻辑不需要的时候,丢弃不需要s := "" // 短变量声明,最简洁,但只能用在函数内部,而不能用于包变量。
var s string // 依赖于字符串的默认初始化零值机制,被初始化为""。
var s = ""
var s string = "" // 显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余。
echo go语言示例3:gopl_echo3.go
package main
import (
"fmt"
"os"
"strings"
)
func main() {
fmt.Println(strings.Join(os.Args[1:], " "))
}
strings.Join(string_slice,"")
修改echo
程序, 使其能够打印 os.Args[0]
, 即被执行命令本身的名字。
gopl_echo_ex1_0.go
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args[0])
}
直接这样写输出的其实并不满足要求,会把执行程序一起打印./gopl_echo_ex1_0.go
以下做了改进,使用Split
分割了执行命令。
PS: 嘿嘿, golang Split
切片取最后一值与python
不同,不支持 [-1]
呦!
gopl_echo_ex1.go
package main
import (
"fmt"
"os"
"strings"
)
func main() {
sp := strings.Split(os.Args[0], "/")
value := sp[len(sp)-1]
fmt.Println(value)
}
构建 go build gopl_echo_ex1.go
运行 ./gopl_echo_ex1 a b c d
输出 gopl_echo_ex1
修改echo
程序, 使其能够打印 每个参数的索引和值,每个一行。
gopl_echo_ex2.go
package main
import (
"fmt"
"os"
"strings"
)
func main() {
for index, value := range os.Args {
if index != 0 {
fmt.Println(index, value)
} else {
sp := strings.Split(value, "/")
value := sp[len(sp)-1]
fmt.Println(index, value)
}
}
}
构建 go build gopl_echo_ex2.go
运行 ./gopl_echo_ex2 a b c d
输出
0 gopl_echo_ex2
1 a
2 b
3 c
4 d
了解1.6节 time
包,11.4节标准测试程序,以得到系统性的性能评测。
// TODO 待补充
dup go语言示例1:gopl_dup1.go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
for line, n := range counts {
if n > 1 {
fmt.Println(n, line)
}
}
}
gopl_dup1.txt
Hello World!
666
Spaceack
Spaceack
Spaceack
Hello World!
Spaceack
888
Hello World!
666
需要用重定向符<
这样运行:go run gopl_dup1.go < gopl_dup1.txt
。结果如下:
3 Hello World!
2 666
4 Spaceack
2
可以看到,两个空白行也被记录。
map
类似 python 的 dict
, 存储(k-v)的集合。 常数时间存,取操作。make
内置函数创建空map
map
迭代的顺序随机bufio
包用来处理输入输出,Scanner
类型读取输入并将其拆成行或单词。input.Scan()
,读入下一行并移除末尾换行符。input.Text() 读取内容
tmp.Printf
参数格式转换字符(conversion character)%d 十进制整数
%x, %o, %b 十六进制,八进制,二进制整数。
%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00
%t 布尔:true或false
%c 字符(rune) (Unicode码点)
%s 字符串
%q 带双引号的字符串"abc"或带单引号的字符'c'
%v 变量的自然形式(natural format)
%T 变量的类型
%% 字面上的百分号标志(无操作数)
dup go语言示例2:gopl_dup2.go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
files := os.Args[1:]
if len(files) == 0 {
countLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Fprint(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()]++
}
}
直接运行:go run gopl_dup2.go gopl_dup1.txt
。结果如下:
4 Spaceack
2
3 Hello World!
2 666
os.Open
函数反回两个值。第个是被打开的文件(*os.File),其后被Scanner
读取。第二个值是内置error
类型的值。如果err
等于内置值nil
,那么文件被成功打开。读取文件,直到文件结束。然后用close
关闭该文件,并释放占用的资源。countLines
函数在声明前被调用。函数和包级别的变量(package-level entities)可以任意声明顺序,不影响被调用。dup go语言示例3:gopl_dup3.go
package main
import (
"fmt"
"io/ioutil"
"os"
"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)
}
}
}
直接运行: go run gopl_dup3.go gopl_dup1.txt
。结果如下:
3
3 Hello World!
2 666
4 Spaceack
ReadFIle
函数返回一个字节切片(byte slice),需要转换为string
才能使用strings.Split
分割。bufio.Scanner
, ioutil.ReadFile, ioutil.WriteFIle
都使用*os.FIle
的Read
和write
方法。使用高级函数会更容易。修改dup2
程序, 出现重复的行时打印文件名称。
gopl_dup2_ex1.go
利萨如(Lissajous figures)图形:协振子在两个纬度上振动所产生的曲线。
Lissajous go语言示例1:gopl_lissajous.go
go build gopl_lissajous.go
./gopl_lissajous > lissajous_1,gif
// Lissajous generates GIF animations of random Lissajous figures.
package main
import (
"image"
"image/color"
"image/gif"
"io"
"math"
"math/rand"
"os"
"time"
)
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0 // first color in palette
blackIndex = 1 // next color in palette
)
func main() {
// The sequence of images is deterministic unless we seed
// the pseudo-random number generator using the current time.
// Thanks to Randall McPherson for pointing out the omission.
rand.Seed(time.Now().UTC().UnixNano())
lissajous(os.Stdout)
}
func lissajous(out io.Writer) {
const (
cycles = 5
res = 0.001 // angular resolution
// number of complete x oscillator revolutions
size = 100
// image canvas covers [-size..+size]
nframes = 64 // number of animation frames
delay = 8 // delay between frames in 10ms units
)
freq := rand.Float64() * 3.0 // relative frequency of y oscillator
anim := gif.GIF{LoopCount: nframes}
phase := 0.0 // phase difference
for i := 0; i < nframes; i++ {
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
img := image.NewPaletted(rect, palette)
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
blackIndex)
}
phase += 0.1
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
}
[]color.Color{...}
与 gif.GIF{...}
是实例化Go语言里的复合类型的一种写法。分别生成slice
切片和 struct
结构体。修改前面的Lissajous程序里的调色板,由黑色改为绿色。
我们可以用color.RGBA{0xRR, 0xGG, 0xBB, 0xff}来得到#RRGGBB这个色值,三个十六进制的字符串分别代表红、绿、蓝像素。
var palette = []color.Color{color.RGBA(color.RGBA{0xAA, 0xCC, 0xBB, 0xff})}
修改Lissajous程序,修改其调色板来生成更丰富的颜色,然后修改SetColorIndex的第三个参数,看看显示结果吧。
var palette = []color.Color{color.RGBA(color.RGBA{0x11, 0xDD, 0xBB, 0xDD}), color.RGBA(color.RGBA{0xFF, 0xCC, 0x33, 0x55})}
gopl_fetch1.go
// Fetch prints the content found at a URL.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
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)
}
}
net/http
包, http.Get
创建HTTP请求。ioutil.ReadAll
函数从response中读取到全部内容;resp.Body.Close
关闭resp的Body流,防止资源泄露。函数调用io.Copy(dst, src)
会从src中读取内容,并将读到的结果写入到dst中,使用这个函数替代掉例子中的ioutil.ReadAll
来拷贝响应结构体到os.Stdout
,避免申请一个缓冲区(例子中的b)来存储。记得处理io.Copy
返回结果中的错误。
修改fetch这个范例,如果输入的url参数没有http://前缀的话,为这个url加上该前缀。你可能会用到strings.HasPrefix这个函数。
修改fetch打印出HTTP协议的状态码,可以从resp.Status变量得到该状态码。
gopl_fetch_ex1.go (ex1.7~1.9)
// Fetch prints the content found at a URL.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
for _, url := range os.Args[1:] {
if !strings.HasPrefix(url, "http://") {
url = "http://" + url
}
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := io.Copy(os.Stdout, resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%d", b)
fmt.Printf("%s", resp.Status)
}
}
并发编程
Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小,因此,创建一个goroutine的代价很小,创建百万级的goroutine完全是可行的。go使用 goroutines
和 channels
处理并发编程。
标准库(语言自带的电池)
I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格 式和编解码协议。
切片
为动态数组提供了有效的随机存取的性能
其它
1. 词法作用域
2. 嵌套函数
3. 自动垃圾回收
4. 包系统
5. 函数作为一等公民
6. 系统调用接口
7. 只读的UTF8字符串
8. 多返回值
没有的特性:隐式的数值转换,构造函数和析构函数,运算符重载,默认参数,继承(go使用组合简单对象来构建复杂对象。具体类型和抽象类型(接口)间的关系是隐式的),泛型,异常,宏,函数修饰,线程局部存储
顺序通信进程(communicating sequential processes,CSP):一组中间没有共享状
态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。