《Go语言圣经》笔记第一章

  1. go run xx.go这个 命令会将一个或多个文件名以.go结尾的源文件,和关联库链接到一起,然后运行终的可执行文件
  2. go build xx.go 一次性编译程序,创建一个二进制文件,可以在任何时候去运行这个文件,不需要其它的任何处理
  3. go get 从网络上获取代码,并将这些代码放在对应的目录中
  4. package 。一个package会包含一个或多个.go结束的源代码文件。每一 个源文件都是以一个package xxx的声明语句开头的这行声明语 句表示该文件是属于哪一个package,紧跟着是一系列import的package名,表示这个文件中引入的 package。再之后是本文件本身的代码。
  5. package main是一个比较特殊的package。这个package里会定义一个独立的程序,这个程序是可以运行 的,而不是像其它package一样对应一个library。
  6. main 函数也是一个特殊函数,它是整个程序的入口,。main函数所做的事情就是我们程 序做的事情。当然了,main函数一般是通过是调用其它packge里的函数来完成自己的工作。
  7. 这也正是因为go语言必须引入所有要用到的package的原则,假如你没有在代码里import需要用到的 package,程序将无法编译通过,同时当你import了没有用到的package,也会无法编译通过。
  8. 一个函数的声明包含func这个关键字、函数名、参数列表、返回结果列表(我们例子里的main函数参数 列表和返回值都是空的)以及包含在大括号里的函数体。
  9. Go语言是一门不需要分号作为语句或者声明结束的语言,除非要在一行中将多个语句、声明隔开。然而 在编译时,编译器会主动在一些特定的符号(译注:比如行末是,一个标识符、一个整数、浮点数、虚 数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符 ++、–、)、]或}中的一个) 后添加分号,所以在哪里加分号合适是取决于Go语言代码的。例如:在Go 语言中的函数声明和 { 大括号必须在同一行,而在x + y这样的表达式中,在+号后换行可以,但是在 +号前换行则会有问题(译注:以+结尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔 符,从而导致编译错误)
  10. go fmt xx.go 将代码格式化为标准模式

命令行参数

大多数程序都是处理输入产生输出,通常情况下输入都来自程序外部:比如文件,网络连接、其他程序的输出、用户的键盘、命令行的参数或其他类似的输入源。

  1. os这个package提供了与操作系统无关的,与系统交互的一些函数和相关变量,运行时程序的命令行参数可以通过os包中一个叫Args 的变量来获取:当在os包外使用该变量时,需要使用os.Args来访问。
  2. os.Args是一个切片,os.Args[0]是命令执行的命令本身,其他元素则是执行该命令时传递给这个程序的参数。
    练习:
package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"
)
func main(){
	fmt.Print("使用for循环实现echo\r\n")
	var s,sep string
	sep=" "
	for i:=0;i<len(os.Args);i++{
		sep="index: "+strconv.Itoa(i)
		s+=sep+os.Args[i]+" \r\n"
	}
	fmt.Println(s)
	fmt.Print("使用join实现echo\r\n")
	fmt.Print(strings.Join(os.Args[1:]," "))
}

Printf的转换 verb(动词)
在这里插入图片描述

  1. 查找重复的行:
func main() {
	counts := make(map[string]int)
	//map 是一个键值对类型的结构map[key]value,
	//key支持任意数据类型,只要该类型能够用==比较
	input := bufio.NewScanner(os.Stdin)
	//创建一个标准输入流
	//对input.Scanner的每一次调用都会调入一个新行,并且会 自动将其行末的换行符去掉
	for input.Scan() {
		line:=input.Text()
		if line == "bye" {
			break //输入bye时结束输入
		}
		counts[line]++//默认更新key从0开始
		//等价于counts[line] = counts[line] + 1
	}

	for line1, n := range counts { //使用range遍历输入
		if n >0 {
			fmt.Printf("%d\t%s\n", n, line1)
			//f代表格式化输出format
			//printf默认不会在输出内容后面加上换行符
			//用来格式化的函数都会在末尾以f字母结尾(译 注:f后对应format或fmt缩写),比如log.Printf,fmt.Errorf,同时还有一系列对应以ln结尾的函 数(译注:ln后对应line缩写),这些函数默认以%v来格式化他们的参数,并且会在输出结束后在 后自动加上一个换行符。
		}
	}
}

  1. 从标准流中读入文件名, 然后用os.Open来打开每一个文件获取内容
package main

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

func main() {
	counts := make(map[string]int)
	files := os.Args[1:] //获取参数中的文件名
	fmt.Println(files)
	if len(files) == 0 {
		coutLines(os.Stdin, counts)
	} else {
		for _, arg := range files {
			fmt.Println(arg)
			f, err := os.Open(arg)
			//os.Open函数会返回两个值。第一个值是一个打开的文件类型(*os.File),这个对象在下面的程序中被 Scanner读取
			//os.Open返回的第二个值是一个Go语言内置的error类型,如果为nil则打开陈宫
			if err != nil {
				fmt.Fprintf(os.Stderr, "dup2:%v", err)
				continue //如果产生错误终止本次循环换然后继续
			}
			coutLines(f, counts)
			//countLines 在调用之前就被使用了?在Go语言里,函数和包 级别的变量可以以任意的顺序被声明,并不影响其被调用
			f.Close()
		}

	}
	//注:使用range遍历map,其迭代的顺序是不确定的,
	for line, n := range counts {
		if n > 1 {
			fmt.Printf("%d\t%s\n", n, line)
		}
	}
}

func coutLines(f *os.File, counts map[string]int) {
	input := bufio.NewScanner(f)
	//scanner对象从程序的标准输入中读取内容,对input.Scanner的每一次调用都会调入一个新行,并且会自动将其行末的换行符去掉;结果用input.Text()得到,Scan()方法在读到了新行的时候会返回true,而在没有新行读入时,会返回false。
	for input.Scan() {
		fmt.Println(input.Text())
		counts[input.Text()]++
		//虽然Go语言的函数是按值传递的,但是map这个结构内部封装了指针,所以修改副本也相当于修改本体
	}
	fmt.Println(counts)
}

  1. 另一种
package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

func main() {
	counts := make(map[string]int)
	for _, filename := range os.Args[1:] {
		//读取文件ReadFile函数,返回byte类型的slice。
		//这个slice必须转换为string,之后才能够用strings.Split方法来处理
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error!:%v\n", err)
			continue
		}

		for _, line := range strings.Split(string(data), "\n") {
			counts[line]++ //逐行读入
			print(line)
		}
	}

	for line, n := range counts {
		if n > 0 {
			fmt.Printf("%d\t%s\n", n, line)
		}
	}
}

  1. 当导入了一个路径包含有多个单词的package时,比如image/color通常只需要用最后一个单词表示这个包就可以。比如color.White这个变量是指image/color包里的变量
  2. 常量的声明:常量是程序编译后运行是始终不会变化的两,常量声明和变量声明一般都会出现在包级别,所以这些常量在整个包中都是可以共享的。或者你也可以把常量声明定义在函数体内部,那么这种常量就只能在函数体内用。目前常量 声明的值必须是一个数字值、字符串或者一个固定的boolean值。
  3. 获取URL
package main

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

func main() {
	for _, url := range os.Args[1:] {
		resp, err := http.Get(url) //获取参数中的url并发送包请求
		//处理异常
		if err != nil {
			fmt.Fprintf(os.Stderr, "fetch:%v\n", err)
			os.Exit(1)
		}
		//获取响应
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Fprint(os.Stderr, "fetch:reading %s:%v\n", url, err)
			os.Exit(1)
		}
		fmt.Printf("%s", b)
	}
}

注:运行时输入的url参数要加上协议名

>go run getURL.go http://www.4399.com

  1. 并发访问多个URL
func main(){
	start:=time.Now()
	//使用make创建了一个产地string类型参数的channel
	ch:=make(chan string)
	for _,url:=range os.Args[1:]{
		go fetch(url,ch)//开启一个线程放问url
	}
	for range os.Args[1:]{
		fmt.Println(<-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)
		return
	}
	//吧响应的内容移动到到IOUtil.Discard流中(这是一个垃圾桶,可以用来写一些不需要的数据)
	nbytes,err:=io.Copy(ioutil.Discard,resp.Body)
	resp.Body.Close()//防止内存泄露?
	if err!=nil{
		ch<-fmt.Sprintf("while reading%s:%v",url,err)
	}
	secs:=time.Since(start).Seconds()
	ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)//将数据传入管道
}

在这里插入图片描述

i. 把响应的内容移动到IOUtil.Discard流中(这是一个垃圾桶,可以用来写一些不需要的数据)
ii. 当一个goroutine尝试在一个channel上做send或者receiver操作时,这个goroutine会阻塞在调用处,直 到另一个goroutine往这个channel里写入、或者接收了值,这样两个goroutine才会继续执行操作
10.Web服务:
例中返回当前用户正在访问的url

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

func main(){
	http.HandleFunc("/",handler)
	log.Fatal(http.ListenAndServe("localhost:8000",nil))
}
func handler(w http.ResponseWriter,r *http.Request){
	fmt.Fprintf(w,"URL.Path=%q\n",r.URL.Path)
}
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))
}
func handler(w http.ResponseWriter,r *http.Request){
	mu.Lock()
	count++
	mu.Unlock()
	fmt.Fprintf(w,"URL.Path=%q\n",r.URL.Path)
}
func counter(w http.ResponseWriter,r *http.Request){
	mu.Lock()
	fmt.Fprintf(w, "Count %d\n", count)
	mu.Unlock()
}

我们必须保证每次修改变量的多只能有一个goroutine,这 也就是代码里的mu.Lock()和mu.Unlock()调用将修改count的所有行为包在中间的目的。

加上更丰富的信息
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))
}
func handler(w http.ResponseWriter,r *http.Request){
	mu.Lock()
	count++
	mu.Unlock()
	fmt.Fprintf(w,"URL.Path=%q\n",r.URL.Path)
	//打印请求包的信息
	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,"RemoteAdder=%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",k,v)
	}
}
func counter(w http.ResponseWriter,r *http.Request){
	mu.Lock()
	fmt.Fprintf(w, "Count %d\n", count)
	mu.Unlock()
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言圣经》是一本广受好评的教材,旨在帮助读者系统地学习和掌握Go语言。这本书以简明清晰的方式介绍了Go语言的各种特性、语法和用法,是入门和进阶者的理想选择。 首先,该书提供了对Go语言基础知识的全面介绍。它从简单到复杂地解释了Go语言的基本概念,诸如变量、函数、循环和条件语句等等。通过丰富的例子和练习,读者能够逐步理解和掌握这些概念。 其次,该书详细介绍了Go语言的高级特性和用法。读者可以学习到Go语言的面向对象编程、并发编程、网络编程等关键技术。并发编程是Go语言的一个独特特性,对于提高程序性能和扩展能力非常重要。 此外,该书还包含了对常见问题和陷阱的讲解,帮助读者避免一些常见的错误和陷阱。同时,书中提供了大量的案例和实践项目,读者可以通过实际操作来巩固所学内容。 《Go语言圣经》以其简洁明了的风格和对细节的深入讲解而闻名。无论是作为初学者的入门指南,还是作为有经验的开发者的参考书,这本书都能满足读者的需求。此外,该书的PDF版本能够方便地在线或离线阅读,为读者提供了更加便捷的学习体验。 综上所述,《Go语言圣经》是一本内容丰富、权威性强的优秀教材。它不仅适合Go语言的初学者,也适用于那些想要深入学习和掌握Go语言的开发者。无论是在线阅读还是PDF版本,读者都能够方便地获取和利用这本宝贵的学习资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值