《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要

1.1 Hello World

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

  • Go语言的代码通过包(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成, 目录定义包的作用。每个源文件都以一条是package 声明语句开始,这个例子里就是package main , 表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。
  • main函数也很特殊,它是整个程序执行时的入口。
  • 必须告诉编译器源文件需要哪些包,这就是跟随在package声明后面的import声明扮演的角色。必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。
  • import声明必须跟在文件的package声明之后。

1.2 命令行参数

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经验真的很容易理解。
  • Go语言不允许使用无用的局部变量(local variables)
  • 空标识符(blank identifier)_ (下划线),空标识符可用于任何语法需要变量名但程序逻辑不需要的时候,丢弃不需要
    的循环索引, 保留元素值。
  • 声明变量的4种方式, 一般使用前两种形式。 ,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。
    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,"")

练习 1.1:

修改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

练习 1.2:

修改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.3:

了解1.6节 time包,11.4节标准测试程序,以得到系统性的性能评测。

// TODO 待补充

1.3 查找重复的行

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.FIleReadwrite方法。使用高级函数会更容易。

练习 1.4:

修改dup2程序, 出现重复的行时打印文件名称。
gopl_dup2_ex1.go

1.4 GIF动画

利萨如(Lissajous figures)图形:协振子在两个纬度上振动所产生的曲线。《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要_第1张图片
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结构体。
  • anim是一个gif.GIF类型的struct变量。
  • 其内部变量LoopCount字段会被设置为nframes,其它的字段会被设置为各自类型默认的零值。
  • struct内部的变量可以以一个点(.)来进行访问。
    《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要_第2张图片
  • 外层循环会循环64次,每一次都会生成一个单独的动画帧。生成了一个包含两种颜色的201*201大小的图片,白色和黑色。所有像素点都会被默认设置为其零值。将结果append到anim中的帧列表末尾,并设置一个默认的80ms的延迟值。循环结束后所有的延迟值被编码进了GIF图片中,并将结果写入到输出流。
  • 内层循环设置两个偏振值。x轴偏振使用sin函数。y轴偏振也是正弦波,但其相对x轴的偏振是一个0-3的随机值,初始偏振值是一个零值,随着动画的每一帧逐渐增加。循环会一直跑到x轴完成五次完整的循环。每一步它都会调用SetColorIndex来为(x, y)点来染黑色。
    《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要_第3张图片

练习 1.5:

修改前面的Lissajous程序里的调色板,由黑色改为绿色。
我们可以用color.RGBA{0xRR, 0xGG, 0xBB, 0xff}来得到#RRGGBB这个色值,三个十六进制的字符串分别代表红、绿、蓝像素。

var palette = []color.Color{color.RGBA(color.RGBA{0xAA, 0xCC, 0xBB, 0xff})}

《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要_第4张图片

练习 1.6:

修改Lissajous程序,修改其调色板来生成更丰富的颜色,然后修改SetColorIndex的第三个参数,看看显示结果吧。

var palette = []color.Color{color.RGBA(color.RGBA{0x11, 0xDD, 0xBB, 0xDD}), color.RGBA(color.RGBA{0xFF, 0xCC, 0x33, 0x55})}

《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要_第5张图片

1.5. 获取URL

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请求。
  • resp 的 Body字段包括一个可读的服务器响应流。ioutil.ReadAll函数从response中读取到全部内容;
  • resp.Body.Close关闭resp的Body流,防止资源泄露。
  • 无论哪种失败原因,我们的程序都用了os.Exit函数来终止进程,并且返回一个status错误码,其值为1。

练习 1.7

函数调用io.Copy(dst, src)会从src中读取内容,并将读到的结果写入到dst中,使用这个函数替代掉例子中的ioutil.ReadAll来拷贝响应结构体到os.Stdout,避免申请一个缓冲区(例子中的b)来存储。记得处理io.Copy返回结果中的错误。

练习 1.8

修改fetch这个范例,如果输入的url参数没有http://前缀的话,为这个url加上该前缀。你可能会用到strings.HasPrefix这个函数。

练习 1.9

修改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语言特性

  • 并发编程
    Go语言提供了基于CSP的并发特性支持。Go语言的动态栈使得轻量级线程goroutine的初始栈可以很小,因此,创建一个goroutine的代价很小,创建百万级的goroutine完全是可行的。go使用 goroutineschannels 处理并发编程。

  • 标准库(语言自带的电池)
    I/O操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格 式和编解码协议。

  • 切片
    为动态数组提供了有效的随机存取的性能

  • 其它

    1. 词法作用域
    2. 嵌套函数
    3. 自动垃圾回收
    4. 包系统
    5. 函数作为一等公民
    6. 系统调用接口
    7. 只读的UTF8字符串
    8. 多返回值
    

    没有的特性:隐式的数值转换,构造函数和析构函数,运算符重载,默认参数,继承(go使用组合简单对象来构建复杂对象。具体类型和抽象类型(接口)间的关系是隐式的),泛型,异常,宏,函数修饰,线程局部存储

顺序通信进程(communicating sequential processes,CSP):一组中间没有共享状
态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。

你可能感兴趣的:(Go,golang,开发语言,后端)