helloworld.go
package main
import "fmt"
func main(){
fmt.Println("Hello,世界")
}
Go语言提供的工具通过一个单独的命令go调用,go命令有一系列子命令。
run命令编译一个或多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。
$ go run helloworld.go
Go语言原生支持Unicode,它可以处理全世界任何语言的文本
如果不只是一次性实验,希望能够编译这个程序,保存编译结果以备将来之用,使用build子命令。会生成helloworld的可执行二进制文件(Windows下是helloworld.exe),之后可以随时运行它,不需任何处理。
$ go build helloworld.go
package:包。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。每个源文件都以一条package声明语句开始,表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。
fmt包:含有格式化输出、接收输入的函数。Println是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。
main包:定义一个独立可执行的程序,而不是一个库。在main里的main函数是整个程序执行的入口。
Go语言编译过程没有警告信息。
import声明必须跟在文件的package声明之后。
函数func、变量var、常量const、类型type
一个函数的声明由func关键字、函数名、参数列表、返回值列表以及包含在大括号里的函数体组成。
函数的左括号{必须和func函数声明在同一行上且位于末尾,不能独占一行,而在表达式x+y中,可在+后换行,不能在+前换行。
gofmt工具把代码格式化为标准格式,这个格式化工具没有任何可以调整代码格式的参数。
goimports,可以根据代码需要,自动地添加或删除import声明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装:
$ go get golang.org/x/tools/cmd/goimports
输入来自于程序外部:文件、网络连接、其他程序的输出、敲键盘的用户、命令行参数或其他类似输入源。
os包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可以从os包的Args变量获取;os包外部使用os.Args访问该变量。os包外部使用os.Args访问该变量
os.Args变量是一个字符串(string)的切片(slice),切片长度动态变化,s[i]访问单个元素,s[m:n]获取子序列,左闭右开。os.Args[0]是命令本身的名字,其他参数则是程序启动时传给它的参数。
Unix里echo命令的一份实现,echo把它的命令行参数打印成一行。
ech1.go
//Echo1 prints its command-line arguments
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)
}
不断地把新文件追加到末尾来构造字符串
:=是短变量声明的一部分,这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句。
自增语句和自减语句,不是表达式,j=i++非法,++和–都只能放在变量名后面,–i也非法。
Go语言只有for循环这一种循环语句,for循环有多种形式,其中一种:
for initialization;condition;post{
//zero or more statements
}
for的三部分不需要括号包括,每个都可以省略,initialization、condition、post
for循环的另一种形式,在某种数据类型的区间(range)上遍历,如字符串切片。
echo2.go
//Echo2 prints its command-line arguments
package main
import{
"fmt"
"os"
}
func main(){
s,sep:="",""
for _,arg:=range os.Args[1:]{
s+=sep+arg
sep=" "
}
fmt.Println(s)
}
每次循环迭代,range产生一对值;索引以及该索引处的元素值,这个例子不需要索引,Go语言不允许使用无用的局部变量,此处使用空标识符_(用于任何语法需要变量名但程序逻辑不需要的时候)。
声明变量的方式如下:
s:=""//只能用在函数内部,而不能用于包变量
var s string//依赖于字符串的默认初始化零值机制,初始化为“”
var s=""//用的很少,除非同时声明多个变量
var s string=""//显示表明变量类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了
如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使strings包的Join函数。
echo3.go
func main(){
fmt.Println(strings.Join(os.Args[1:]," "))
}
只想看输出值:
fmt.Println(os.Args[1:])
输出结果放到一对方括号,切片都会被打印成这种格式。
Unix的uniq命令,其寻找相邻的重复行。
dup.go
//Dup1 prints the text of each line that appears more than
//once in the standard input,preceded by its count.
package main
import(
"bufio"
"fmt"
"os"
)
func main(){
counts:=make(map[string]int)
input:=bufio.NewScanner(os.Stdin)
for input.Scan(){
counts[input.Text()]++
}
//NOTE:ignoring potential errors from input.Err()
for line,n:=range counts{
if n>1{
fmt.Printf("%d\t%s\n",n,line)
}
}
}
内置函数make创建空map,这个例子中键是字符串,值是整数。
bufio包,它使处理输入和输出方便又高效。Scanner类型,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。
使用短变量声明创建bufio.Scanner类型的变量input。每次调用input.Scan(),即读入下一行,并移除行末的换行符,读取的内容可以调用input.Text()得到。
Scan()函数在读到一行时返回true,不再有输入时返回false。
fmt.Printf函数对一些表达式产生格式化输出。
%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 | 变量的类型 |
%% | 字面上的百分号标志(无操作数) |
转义字符:制表符\t和换行符\n | |
Printf:不会换行,format | |
Println:换行,line |
很多程序要么从标准输入中读取数据,要么从一系列具名文件中读取数据。
dup2.go
//Dup2 prints the count and text of lines that appear more than once
//in the input.It reads from stdin or from a list of named files.
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()]++
}
//NOTE:ignoring potential errors from input.Err()
}
os.Open函数返回两个值,第一个值是被打开的文件(*os.File),第二个值是内置error类型的值。如果err等于内置值nil,那么文件被成功打开,如果错误,像标准错误流打印一条信息,然后dup继续处理下一个文件。
countLines函数在其声明前被调用,函数和包级别的变量可以任意顺序声明,并不影响其被调用。
map是一个由make创建的数据结构的引用。
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)
}
}
}
一口气把全部输入数据读到内存中,一次分割为多行,然后处理它们。
ReadFile函数来自io/ioutil包,读取指定文件的全部内容,返回一个字节切片,必须把它转换为string。strings.Spilt函数把字符串分割成子串的切片。
//Lissajous generates GIF animations of random Lissajous figures
package main
import(
"image"
"image/color"
"image/gif"
"io"
"math"
"math/rand"
"os"
)
//复合声明,slice切片
var palette=[]color.Color{color.white,color.Black}
//常量列表
const (
whiteIndex=0//first color in palette
blackIndex=1//next color in palette
)
func main(){
rand.Seed(time.Now().UTC().UnixNano())
lissajous(os.Stdout)
}
func lissajous(out io.Writer){
const(
cycles=5
res=0.001
size=100
nframs=64
delay=8
)
freq:=rand.Float64()*3.0
//结构体,内部变量LoopCount字段会被设置为nframes
anim:=gif.GIF{LoopCount:nframs}
phase:=0.0
//循环64次,每次都会生成一个单独的动画帧
for i:=0,i<nframes;i++{
//生成一个包含两种颜色的201*201大小的图片,白色和黑色
//所有像素点都会被默认设置为其零值
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.Imge=append(anim.Image,img)
}
gif.EncodeAll(out,&anim)
}
结构体内部的变量可以以一个点(.)来进行访问
基于http获取信息的方式,获取相应的url,并将源文本打印出来
fetch.go
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)
}
}
同时去获取所有的URL,并发编程,打印获取的内容大小和经过的时间
package main
import(
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
func main(){
start:=time.Now()
ch:=make(chan string)
//对每个命令行参数,我们都创建一个goroutine
for _,url:=range os.Args[1:]{
go fetch(url,ch)
}
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(error)
return
}
//把相应的Body内容拷贝到ioutil.Discard输出流中,可以把这个变量看作一个垃圾桶,可以往里面写一些不需要的数据,我们需要这个方法返回的字节数而不想要其内容
nbytes,err:=io.Copy(ioutil.Discard,resp.Body)
resp.Body.Close()
if err!=nil{
ch<-fmt.Sprintf("while reading %s:%v\n",url,err)
return
}
secs:=time.Since(start).Seconds()
ch<-fmt.Sprintf("%.2fs %7d %s",secs,nbytes,url)
}
goroutine是一种函数的并发执行方式,而channel是用在goroutine之间进行参数传递。main函数本身也运行在一个goroutine中,而go function则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数。
当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutinue往这个channel里写入或者接收值。避免异步执行还没有完成时main函数提前退出。
switch多路选择
switch coinflip(){
case "heads":
heads++
case "tails":
tails++
default:
fmt.Println("landed on edge!")
}
go语言并不需要显示地在每一个case后写break,语言默认执行完case后的逻辑语句会自动退出。如果想要相邻的几个case都执行同一逻辑的话,需要自己显式地写上fallthrough语句来覆盖这种默认行为。
switch可以不带操作对象,和switch true等价,无tag switch
func Signum(x int) int{
switch{
case x>0:
return +1
default:
return 0
case x<0:
return -1
}
}
指针是一种直接存储了变量的内存地址的数据类型,go语言中没有指针运算,不可以对指针进行加或减操作。指针是可见的内存地址,&操作符可以返回一个变量的内存地址,*操作符可以获取指针指向的变量内容。