Go语言学习(一)


开新坑,从黑砖学习

第1章 入门

这本书比上一本书感觉要难得多,还是觉得黑砖还是strong,结合个人学习经历,还是先学上一本。

1.1 hello world

package main
import "fmt"
func main(){
	fmt.Println("Hello,世界")
}
  • 1.Go是编译型的语言。Go的工具链将程序的源文件转变为机器相关的原生二进制指令
  • 2.Go原生地支持Unicode,所以他可以处理所有国家的语言
  • 3.Go代码是使用包来组织的,包类似于其他语言中的库和模块。一个包由一个或多个.go源文件组成,放在一个文件夹中,该文件夹的名字描述了包的作用。
  • 4.名为main的包比较特殊,它用来定义一个独立的可执行程序,而不是库。在main包中,函数main也是特殊的。不管在什么程序中,main做什么事情,他总是程序开始执行的地方。
  • 5.GO不需要在函数或声明后面使用分号结尾,除非有多个语句或声明出现在同一行。
  • 6."{"符号必须和关键字func在同一行,不能独自成行,并且在x+y这个表达式中,换行符可以在+操作符的后面,但是不能在+操作符的前面.
  • 7.goimports可以按需管理导入声明的插入和移除。它不是标准发布版的一部分,可以通过执行下面的命令获取到:
    go get golang.org/x/tools/cmd/goimports
    

1.2 命令行参数

1.2.1 echo1

package main

import (//导入需要的包
	"fmt"
	"os"
)

func main(){
	var s, sep string //定义两个字符串类型的变量
	for i := 1; i < len(os.Args); i++ { //for循环命令行参数
		s += sep + os.Args[i]
		sep = " "
	}
	fmt.Println(s)
}
  • 1.注释以//开头
  • 2.变量未初始化,则默认为该类型的空值
  • 3.i++是语句,不是表达式,所以j=i++非法,而且Go只支持后缀,所以++i非法
  • 4.os.Args是命令行的内容,os.Arg[0]是命令行的命令,后面的是参数
    在这里插入图片描述

1.2.2 echo2

package main

import (
	"fmt"
	"os"
)

func main(){
	s, sep := "",""
	for _, arg := range os.Args[1:]{
		s += sep + arg
		sep = " "
	}
	fmt.Println(s)
}
  • 1.每一次迭代,range产生一对值:索引和这个索引处元素的值
  • 2.简短声明变量的使用要求是:=左侧存在未初始化的变量
  • 4.使用空标识符,他的名字是_(即下划线),空标识符可以用在任何语法需要变量名但是程序逻辑不需要的地方,例如丢弃每次迭代产生的无用的索引。大多数Go程序员喜欢搭配使用range_来写上面的echo程序,因为索引在os.Args上面是隐式的,所以更不容易犯错。

1.2.3 echo3

package main

import (
	"fmt"
	"os"
	"string"
)

func main(){
	fmt.Println(strings.Join(os.Args[1:]," "))
}
  • 1.如果数组庞大的时候使用+=将会效率低下,使用strings包中的Join函数

1.3 找出重复行

  1. 在这里,作者给了三个版本的dup程序,这个程序是受UNIXuniq命令启发来找到的相邻的重复行。
  2. uniq命令我也不清楚是什么,问度娘之后得知:uniq命令是UNIX/LINUX下一种文件内容去重的命令

1.3.1 dup程序1.0

这个版本输出标准输入中出现次数大于1的行,前面是次数。

//gop1.io/ch1/dup1
//dup1输出标准输入中出现次数大于1的行,前面是次数
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	count := make(map[string]int)//建立一个map类型的映射关系,string——>int
	input := bufio.NewScanner(os.Stdin)//建立一个bufio.Scanner类型的input变量
	for input.Scan() {
		count[input.Text()]++
	}
	//注意:忽略input.Err()中可能的错误
	for line, n := range count {
		if n > 1 {
			fmt.Printf("%d\t%s\n", n, line)
		}
	}
}
  • 1.map[string]int:定义了一个键为string类型,值为int型的map类型,map储存一个键/值对集合,并且提供常量时间的操作来储存、获取或测试集合中的某个元素。键可以是其值能够进行相等(==)比较的任意类型,字符串是最常见的例子;值可以是任意类型。内置的make函数可以用来新建map。
    map中的键的迭代顺序不是固定的,通常是随机的,这是为了防止程序依赖某种特定的序列,emm,这tmua的不就是python中的dict么?
  • 2.每次dup从输入读取一行内容,这一行内容就作为map中的键,对应的值递增1。
    语句counts[input.Text()]++等价于下面两个语句:
    line:=input.Text()
    counts[line]=counts[line]+1
    这里的就很有意思了,这个类似于python对dict的get操作,dict.get(s),如果s不存在返回0
    这里也是一样,如果line不存在counts中,则返回值相应类型的零值
  • 3.使用bufio包可以简便和高效的处理输入和输出。其中一个最有用的特性是称为扫描器(Scanner)的类型,它可以读取输入,以行或单词为单位断开,这是处理以行为单位的输入内容的最简单的方式
  • 4.每次调用input.Scan()读取下一行并将结尾的换行符去掉;通过调用input.Text()来读取读到的内容。Scan函数在读到新行的使用返回true,反之返回false
    在这里插入图片描述

    先输入要扫描的文本,然后使用Ctrl+C执行

  • 5.使用fmt.Printf格式化输出
    在这里插入图片描述

字符串字面量可以包含类似转义序列(escape sequence)来表示不可见字符。Printf默认不写换行符。按照约定,诸如log.Printtf和fmt.Errorf之类的格式化函数以f结尾,使用个fmt.Printf相同的格式化规则;而那些以ln结尾的函数(如Println)则使用%v的方式来格式化参数,并在最后追加换行符。

1.3.2 dup程序2.0

从文件中读取内容并判断重复行

package main

import (
	"fmt"
	"bufio"
	"os"
)

func countLines(f *os.File,counts map[string]int){
	input := bufio.NewScanner(f)
	for input.Scan(){
		counts[input.Text()]++
	}
}

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)
		}
	}
}

在这里插入图片描述

函数os.Open返回两个值。
第一个是打开的文件(*os.File),该文件随后被Scanner读取。
第二个是内置的error类型的值。

  • 简单的错误处理是使用Fprintln和%v在标准错误流上输出一条信息,%v可以使用默认格式显示任意类型的值;错误处理后,dup开始处理下一个文件;continue语句让循环进入下一个迭代。
  • 这里忽略了input.Scan读取文件过程中的错误。
  • 对countLines的调用出现在其声明之前。函数和其他包级别的实体可以以任何次序声明。
  • 在函数countLines中的参数类型map,是一个使用make创建的数据结构的引用。当一个map被传递给一个函数时,函数接收到这个引用的副本,所以被调用函数中对于map数据结构中的改变对函数调用者使用的map引用也是可见的。在实例中,countLines函数在counts map中插入的值在main函数中也是可见的。

1.3.3 dup程序3.0

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)
		}
	}
}

在这里插入图片描述
这个版本相较于上一个版本来说,是一次性将所有的数据读入内存,上一个版本是采用流式处理方法

  • ReadFile()函数(io/ioutil包)读取整个命名文件的内容
  • strings.Split()函数,将一个字符串分割为一个由子串组成的slice。(Split是前面介绍过的strings.Join的反操作)
  • 在dup3中我们进行了简化操作:第一,它仅读取指定的文件,而非标准输入,因为ReadFile需要一个文件名作为参数;第二,我们将统计行数的工作放回main函数中,因为它当前仅在一处用到。

1.4 GIF动画

//存在问题,实现失败
package main

import (
	"image"
	"image/color"
	"image/gif"
	"io"
	"log"
	"net/http"
	"math"
	"time"
	"math/rand"
	"os"
)

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)
}

func lissajous(out io.Writer){
	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
		anim.Delay = append(anim.Delay, delay)
		anim.Image = append(anim.Image, img)
	}
	gif.EncodeAll(out, &anim) //注意:忽略编码错误
}
  • 在导入那些由多段路径如image/color组成的包之后,使用路径最后的一段来引用这个包、所以变量color.White属于image/color包,gif.GIF属于image/gif
  • 表达式[]color.Color{}gif.GIF{}是复合字面量,即用一系列元素的值初始化Go的复合类型的紧凑表达方式。这里,第一个是slice,第二个是结构体。

1.5 获取一个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)
	}
}
  • http.Get函数产生一个HTTP请求,如果没有出错,返回结果存在响应结构resp里面。其中的Body域包含服务器端响应的一个可读取数据流。随后ioutil.ReadAll读取整个相应结果并存入b。关闭Body数据流来避免资源泄露,使用Printf将响应输出到标准输出

1.6 获取多个URL

package main

import(
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

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.Discard, resp.Body)
	resp.Body.Close()//不要泄漏资源
	if err != nil{
		ch <- fmt.Sprintf("while reading %s: %v", url, err)
		return
	}
	secs := time.Since(start).Seconds()
	ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)
}
  • 使用go并发执行多个fetch操作,这个进程使用的时间不超多耗时最长时间的获取任务,而不是所有获取任务总的时间。
  • 通道是一种允许某一例程向另一例程传递指定类型的值的通信机制。main函数在一个goroutine中执行,然后go语句创建额外的goroutine。
  • 当一个goroutine试图在一个通道上进行发送或接收操作时,它会阻塞,直到另一个goroutine试图进行接收或发送操作才传递值,并开始处理两个goroutine。

1.7 一个简单的Web服务器

server1.0

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main(){
	http.HandleFunc("/",handler) //回升请求调用处理程序
	log.Fatal(http.ListenAndServe("localhost:8000",nil))
}

//处理程序回显请求URL r的路径部分
func handler(w http.ResponseWriter, r *http.Request){
	fmt.Fprintf(w,"URL.Path = %q\n",r.URL.Path)
}
  • main函数将一个处理函数和以/开头的URL链接在一起,代表所有的URL使用这个函数,然后启动服务器监听进入8000端口处的请求。
  • 一个请求由一个http.Request类型的结构体表示,他包含很多关联的域,其中一个是请求的URL。当一个请求到达时,它被转交给处理函数,并从请求的URL中提取路径部分(/hello),使用fmt.Printf格式化,然后作为响应发送回去

server2.0

package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"
)

var mu sync.Mutex
var count int

func main(){
	http.HandleFunc("/",handler) //回升请求调用处理程序
	http.HandleFunc("/count",counter)
	log.Fatal(http.ListenAndServe("localhost:8000",nil))
}

//处理程序回显请求URL r的路径部分
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()
}
  • 请求/count调用counter,其他的调用handler。以/结尾的处理模式匹配所有含有这个前缀的URL。
  • 两个并发的请求试图同时更新计数值count,他可能会不一致的增加,程序会产生一个严重的竞态bug。为避免该问题,必须确保最多只有一个goroutine在同一时间访问变量,这正是mu.Lock()和mu.Unlock()语句的作用

server3.0

处理函数可以报告他接收到的消息头和表单数据,这样可以方便服务器审查和调试请求:

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\n] = %q\n",k,v)
	}
}
  • 这里是在if语句中嵌套调用ParseForm的,Go允许一个简单的语句(如一个局部变量声明)跟在if条件的后面,这在错误处理的时候特别有用。

    也可以这样写:

    err := r.ParseForm()
    if err != nil {
    	log.Print(err)
    }
    

    但是合并后的语句更短而且可以缩小err变量的作用域

1.8 其他内容

控制流

if、for已经在前面介绍过,在这里提一嘴switch语句,它是多路分支控制。

switch coinflip(){
case "heads":
	heads++
case "tails":
	tails++
default:
	fmt.Println("landed on edge!")
}
  • 默认的case语句(default)可以放在任何位置,case语句不想C语言那样从上到下贯穿执行(fallthrough语句可以改写这个行为)。
func Signum(x int) int {
	switch{
	case x>0:
		return +1
	default:
		return 0
	case x<0:
		return -1
	}
}
  • switch语句不需要操作数,他就像一个case语句列表,每条case语句都是一个布尔表达式:
  • 上面的switch语句称为无标签(tagless)选择,他等价于switch true。
  • 与for和if语句类似,switch可以包含一个可选的简单语句:一个短变量声明,一个递增或赋值语句,或者一个函数调用,用来判断条件前设置一个值。

命名类型

type声明给已有类型命名。因为结构体类型通常很长,所以他们基本上都独立命名。

type Point struct{
	X, Y int
}
var p Point

指针

Go提供了指针,它的值是变量d额地址。在一些语言(比如C)中,指针基本是没有约束的。其他语言中,指针称为"引用",并且除了到处传递之外,它不能做其他的事情。Go做了一个折中,指针显式可见。使用&操作符可以获取一个变量的地址,使用*操作符可以获取指针引用的变量的值,但是指针不支持算术运算。

方法和接口

一个关联了命名类型的函数称为方法。Go里面的方法可以关联到几乎所有的命名类型。接口可以用相同的方式处理不同的具体类型的抽象类型,它基于这些类型所包含的方法,而不是类型的描述或实现。

在这里插入图片描述

注释

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是兔不是秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值