0
点赞
收藏
分享

微信扫一扫

《Go语言圣经》第一章

上古神龙 2022-03-11 阅读 61

一、入门

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
举报

相关推荐

0 条评论