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

- 查找重复的行:
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来格式化他们的参数,并且会在输出结束后在 后自动加上一个换行符。
}
}
}
- 从标准流中读入文件名, 然后用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)
}
- 另一种
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)
}
}
}
- 当导入了一个路径包含有多个单词的package时,比如image/color通常只需要用最后一个单词表示这个包就可以。比如color.White这个变量是指image/color包里的变量
- 常量的声明:常量是程序编译后运行是始终不会变化的两,常量声明和变量声明一般都会出现在包级别,所以这些常量在整个包中都是可以共享的。或者你也可以把常量声明定义在函数体内部,那么这种常量就只能在函数体内用。目前常量 声明的值必须是一个数字值、字符串或者一个固定的boolean值。
- 获取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
- 并发访问多个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()
}