一、入门
1、hello world
- Go不需要加分号
- “{” 符号必须和关键字func在同一行,不能独自成行。并且在 x+y 这个表达式中,换行符可以在+操作符的后面,但是不能在+操作符的前面
- 为了在一个包下的多个文件能同时编写main方法,可以在无用的文件前面加
//go:build ignore
// +build ignore
2、 命令行参数
- os包提供一些函数和变量。命令行参数以os包中Args名字的变量供程序访问,在os包外面,使用os.Args这个名字。变量os.Args是一个字符串slice。
- slice可以理解为一个动态容量的顺序数组s。
- 通过s[i]来访问单个元素
- 通过s[m:n]来访问一段连续子区间
- s[m:n]表示一个从第m个到第n-1个元素的slice
- os.Args[1 : len(os.Args)]
- 如果m或n缺失,默认分别是0或len(s)。故上式可以简写为os.Args[1:]。
- 数组长度用len(s)表示
- 注释以//开头,写在一个包的声明之前
- j = i++是不合法的,并且仅支持后缀,所以–i也是不合法的
- for是Go里的唯一循环语句
for initialization; condition; post{
//...
}
//传统的while循环
for condition{
//...
}
//传统的无限循环
for{
//...
}
- 循环的索引变量i在for循环开始处声明。:= 符号用于短变量声明
func main() {
var s, sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i]
sep = " "
}
fmt.Println(s)
}
- Go不允许存在无用的临时变量,解决办法是可以使用空标识符:“_”(即下划线)
func main() {
s, sep := "", ""
for _, arg := range os.Args[1:] {
s += sep + arg
sep = ""
}
fmt.Println(s)
fmt.Print(2)
}
- range用法补充
/*
Go 语言中 range 关键字用于
·for循环中迭代数组(array)、切片(slice)、链表(channel)或集合(map)的元素;
·在数组和切片中它返回元素的索引值,
·在集合中返回 key-value 对的 key 值。
*/
package main
import "fmt"
func main() {
//使用range去求一个slice的和。类似于使用数组
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在数组上使用range将传入index和值两个变量
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range也可以用在map的键值对上
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Println("%s -> %s\n", k, v)
}
//range也可以用来枚举Unicode字符串。
//第一个参数是字符的索引,第二个是字符(Unicode的值)本身
for i, c := range "go" {
fmt.Println(i, c)
}
}
- 以下几种声明字符串变量的方式是等价的:
s := ""
var s string
var s = ""
var s string = ""
- 一个包下只能有一个main()方法,可以在不需要编译的文件前面加上 // +build ignore
- 如果连接涉及的数据量很大, 这种方式代价高昂。 一种简单且高效的解决方案是使用 strings 包的 Join 函数:
func main() {
fmt.Println(strings.Join(os.Args[1:], " "))
}
- 最后, 如果不关心输出格式, 只想看看输出值, 或许只是为了调试, 可以用 Println 为我们格式化输出
fmt.Println(os.Args[1:])
- 这条语句的输出结果跟 strings.Join 得到的结果很像, 只是被放到了一对方括号里。 切片都会被打印成这种格式。
3、找出重复行
dup1
//dup1输出标准输入中出现次数大于1的行,前面是次数
func main() {
//内置的函数make可以用来新建map
counts := make(map[string]int)
//map 存储一个键/值对集合,结构为map[key]value
//key支持能进行相等(==)比较的任意类型;
//值可以是任意类型
//这个例子中,键的类型是字符串,值是int
input := bufio.NewScanner(os.Stdin)
//bufio包创建一个标准输入流
for input.Scan() {
//等价于:counts[input.Text()]++
line := input.Text()
if line == "bye" {
break //输入bye时结束输入
}
counts[line]++
}
//注意:忽略input.Err()中可能的错误
for line, n := range counts { //使用range遍历输入
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
//f代表格式化输出format
//用来格式化的函数都会在末尾以f字母结尾(译 注:f后对应format或fmt缩写),
//比如log.Printf,fmt.Errorf,同时还有一系列对应以ln结尾的函数(译注:ln后对应line 缩写),这些函数默认以%v来格式化他们的参数,并且会在输出结束后在 后自动加上一个换行符。
}
}
}
运行示例
- Printf函数有超过10个这样的转义字符,Go程序员称为verb
- 制表符 \t 换行符 \n
dup2
- 许多程序既可以像dup一样从标准输入进行读取,也可以从具体的文件读取。这个版本的dup程序可以从标准输入或一个文件列表进行读取,使用os.Open函数来逐个打开
//dup2 打印输入中多次出现的行的个数和文本
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.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()中可能的错误
}
运行示例
- 这个版本的dup使用“流式”模式读取输入,然后按需拆分为行,这样原理上程序可以处理海量的输入。
dup3
- 这个版本的dup将一次读取整个输入到大块内存,一次性地分割所有行,然后处理这些行。
- ReadFile函数(来自io/ioutil包):读取整个命名文件的内容
- strings.Split函数:将一个字符串分割为一个由子串组成的slice(其中Split是strings.Join的反操作)
- dup3的简化之处:
- 仅读取指定的文件,而非标准输入,因为ReadFile需要一个文件名作为参数
- 将统计行数的工作放回main函数,因为它当前仅在一处用到
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)
//ReadFile函数返回一个可以转化为字符串的字节slice
//这样它可以被strings.Split分割
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.Println("%d\t%s\n", n, line)
}
}
}
练习
- 修改dup2,出现重复的行时打印文件名称
package main
import (
"bufio"
"fmt"
"os"
)
type LnFile struct {
Count int
FileNames []string
}
func main() {
counts := make(map[string]*LnFile)
files := os.Args[1:]
if len(files) == 0 {
countLine(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
}
countLine(f, counts)
f.Close()
}
}
for line, n := range counts {
if n.Count > 1 {
fmt.Printf("%d\t%v\t%s\n", n.Count, n.FileNames, line)
}
}
}
func countLine(f *os.File, counts map[string]*LnFile) {
input := bufio.NewScanner(f)
for input.Scan() {
key := input.Text()
_, ok := counts[key]
if ok {
counts[key].Count++
counts[key].FileNames = append(counts[key].FileNames, f.Name())
} else {
counts[key] = new(LnFile)
counts[key].Count = 1
counts[key].FileNames = append(counts[key].FileNames, f.Name())
}
}
}
运行示例
4、GIF动画
//lissajous 产生随机利萨茹图形的GIF动画
package main
import (
"bytes"
"image"
"image/color"
"image/gif"
"io"
"io/ioutil"
"math"
"math/rand"
"time"
)
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0 // 画板中的第一种颜色
blackIndex = 1 // 画板中的下一种颜色
)
func main() {
//rand.Seed(time.Now().UTC().UnixNano())
//if len(os.Args) > 1 && os.Args[1] == "web" {
// handler := func(w http.ResponseWriter, r *http.Request) {
// lissajous(w)
// }
// http.HandleFunc("/", handler)
// log.Fatal(http.ListenAndServe("localhost:8000", nil))
// return
//}
//lissajous(os.Stdout)
//如果是rand.Int,此时获取的随机数,都是重复的一些随机数据
//下面这种写法使用了当前时间播种伪随机数生成器,可以保证每次随机都是随机的
rand.Seed(time.Now().UTC().UnixNano())
//改为直接输出字节文件
buf := &bytes.Buffer{}
lissajous(buf)
if err := ioutil.WriteFile("output.gif", buf.Bytes(), 0666); err != nil {
panic(err)
}
}
func lissajous(out io.Writer) {
const ( //const用于给常量命名
cycles = 5 // 完整的x振荡器变化的个数
res = 0.001 // 角度分辨率
size = 100 // 图象画布包含 [-size..+size]
nframes = 64 // 动画中的帧数
delay = 8 // 以10ms为单位的帧间延迟
)
freq := rand.Float64() * 3.0 // y振荡器的相对频率
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
//append的作用:在原切片末尾添加元素
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
gif.EncodeAll(out, &anim) // 注意:忽略编码错误
}
运行示例
练习
- 练习1.5:修改前面的Lissajous程序里的调色板,由黑色改为绿色。我们可以用 color.RGBA{0xRR, 0xGG, 0xBB, 0xff}来得到 #RRGGBB 的色值,三个十六进制的字符串分别代表红、绿、蓝像素
var palette = []color.Color{color.RGBA{0, 0, 0, 0xFF},
color.RGBA{0, 0xFF, 0, 0xFF}}
运行示例
- 练习1.6:修改Lissajous程序,修改其调色板来生成更丰富的颜色,然后修改SetColorIndex的第三个参数,看看显示结果吧
package main
import (
"bytes"
"image"
"image/color"
"image/gif"
"io"
"io/ioutil"
"math"
"math/rand"
"time"
)
const nColors = 10
func main() {
seed := time.Now()
//Unix将t作为Unix时间返回,即自UTC时间1970年1月1日起经过的秒数。结果不依赖于与t相关联的位置。
//类unix操作系统通常将时间记录为32位的秒数,但是由于这里的方法返回一个64位的值,它在过去或未来的数十亿年里都是有效的。
rand.Seed(seed.Unix())
var palette []color.Color //palette调色板
for i := 0; i < nColors; i++ {
r := uint8(rand.Uint32() % 256)
g := uint8(rand.Uint32() % 256)
b := uint8(rand.Uint32() % 256)
palette = append(palette, color.RGBA{r, g, b, 0xff})
}
buf := &bytes.Buffer{}
lissajous1(buf, palette)
if err := ioutil.WriteFile("output2.gif", buf.Bytes(), 0666); err != nil {
panic(err)
}
}
func lissajous1(out io.Writer, palette []color.Color) {
const (
cycles = 5
res = 0.001
size = 100
nframes = 64
delay = 8
)
freq := rand.Float64() * 3.0
anim := gif.GIF{LoopCount: nframes}
phase := 0.0
for i := 0; i < nframes; i++ {
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
img := image.NewPaletted(rect, palette)
nColor := uint8(i % len(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), nColor)
}
phase += 0.1
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
gif.EncodeAll(out, &anim)
}
运行示例
5、获取一个URL
- 程序 fetch 展示从互联网获取信息的最小需求,它获取每个指定URL的内容,然后不加解析的输出。
- http.Get 函数产生一个 HTTP 请求
- 如果没有出错,返回结果存在响应结构 resp 里面,其中 resp 的 Body 域包含服务器端响应的一个可读取数据流
- 利用 ioutil.ReadAll 可以读取 Body 字段中的全部内容
- Body.Close 用于关闭 Body 流来避免资源泄露,并使用Printf将响应输出到标准输出
- os.Exit 函数终止进程,并且返回一个 status 状态错误码,其值为1