引言:该系列笔记,是作者自学go语言过程中整理的Go语言笔记,比较适合入门者学习参考。
Go语言原生支持Unicode,它可以处理全世界任何语言的文本。
fmt包,就含有格式化输出、接收输入的函数。可以对写好的源码文件的格式进行校对
main函数是整个程序执行时的入口
包:由单个目录下的一个或者是多个.go源码文件组成,一般目录的命名就定义了包的作用。
源码文件的结构
开头必是以一条package声明语句开始,表示该源码文件属于那个包
接下来是一系列导入(import)的包,如果在编译运行的时候如果缺少使用的包或者导多了没用到,都会导致编译失败。
有多个包的情况下可以用列表的形式规范导入的包:
import (
"fmt"
"os"
)
导入的顺序并不重要,在用fmt格式化时,会按照首字母进行排序
随后就是组成程序的函数、变量、常量、类型的声明语句(定义的关键字为 func var const type)
go和python一样,不用在每行结束的时候都加上分号,这点比C语言好。这是因为在编译的时候,编译器会自动给每行后面的换行符替换成分号。但是!
要注意一些特殊情况,比如说在定义函数的时候:
func func_name ()
{
}
这样就是不行的,左花括号必须和func关键字在同一行,还有:
x + y
这个 + 号必须要和左边的元素在同一行,不然就是有语法错误,不通过编译,比如说:
x +
y
这样是可以的。
go语言编写的程序获取外部参数的方式,就只有通过os.Args来获取。
os.Args是一个字符串(string)的切片(slice)
切片是go里面的一个概念
访问单个元素可以通过os.Args[i],获取子数组可以通过os.Args[m:n],和python一样也是采用左闭右开的方式,也就是说os.Args[m:n]获取的数据有[m: n-1]个
os.Args[0]是命令本身的名字,除开这个其他的元素就是传递给程序的参数。
os.Args[1: len(os.Args)],是获取到所有传递的参数,可以简写为 os.Args[1:]
和python不一样的是,注释python以#开头单行注释,Go以//开头单行注释
注意看! 这段代码的实现了仿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)
}
其中main函数里定义了两个s、sep变量,他们的类型是string。变量会在声明时直接初始化。如果变量没有显示初始化,就会被隐式初始化并且其值会等于该类型的"零值"
如果是数值就是0,如果是字符串就是空字符串""
这里的s += sep + os.Args[i],等价于 s = s + sep + os.Args[i]
:= 是短声明变量的一部分,根据他们的初始值为他们赋予适当类型的语句,说白了就是靠猜。
声明变量的好几种方式:
sep := ""
var sep string
var sep = ""
var sep string = ""
实践中常用第一种和第二种。第一种最简洁但只能用在函数内部,不能用于包变量。第二种适合初始值比较重要就显式指定变量,不然就隐式指定变量。
i++是语句,对应的还有i–,他们是语句,j = i++是非法的。而且++和–都是放在变量的后面,放前面是非法的(++i)。
Go语言只有for一种循环语句,for循环有多种形式
for initialization; condition; post {
...
}
// initialization、condition、post都可以省略
for {
...
} // 代表了无限循环
initialization如果存在,必须是一条简单语句,即短变量声明、自增语句、赋值语句或函数调用。
condition是一个Boolean类型的值,其值在每次循环开始之前进行计算。如果为true就执行循环体语句,如果为false就结束循环。
post语句在每次循环结束后执行,之后再对condition求值。
for循环的另一种方式,在某数据类型的区间(range)上遍历,对上面的仿echo程序代码做变更:
package main
import (
"fmt"
"os"
)
var s, sep = "", ""
func main() {
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
fmt.Println(s)
}
这个例子中并不需要元素值的索引,但是range要求要处理元素,就必须处理索引。这里会想到索引赋值给一个临时变量不用就是了,但是Go语言不允许无用的局部变量,会导致编译错误。
解决办法是用空标识符,即下划线"_",这样就能满足程序需要变量名但不需要索引的时候处理。
这里用 := 隐式的索引os.Args[1:],比显式的索引os.Args[1:],更容易写对。
这是用for循环去累加后面的字符串,数据量大的话,这种方式代价就很高昂。我们可以用strings包里的Join函数去处理,耗时上也比for方式短:
package main
import (
"fmt"
"os"
"strings"
)
func main () {
fmt.Println(strings.Join(os.Args[1:], " "))
}
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是内置函数,这里用make函数创建了空map,map是存储了key/value的集合,类似python中的字典,但它是无序的,也就是说每次遍历map集合,它输出的值顺序都不一样。
map的方括号中指定了key的类型,后面的int则是指定了值的类型。
bufio包是处理输入输出的包。NewScanner方法是读取输入并将其拆成行或者单词,是处理行形式的输入最简单的方法。
for input.Scan() {…}是每次调用input.Scan()都会读入下一行,并移除行末的换行符。
input.Text()是获取input.Scan()得到的值,这里的counts[input.Text()]++,意思是每次都会把读到的行在集合中创建一个元素,key值为Text()内容,其值为int++。
map中不含某个键时,在首次读到新行counts[line]的值将会被计算为其类型的零值,int即为0。
fmt.Printf(“%d\t%s\n”, n, line)
这里的Printf和Println有区别,Printf(Print format)是格式化输出的方法,Println会以自然状态的方式输出结果。
%d和%s在Go里面叫做动词(verb),常用的动词有以下这些:
%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 变量的类型
%b 二进制类型
%% 字面上的百分号标志(无操作数)
Printf不会换行,Println相当于%v的效果,在输出结构后面会加上一个换行符
使用一次的函数,可以直接写进main函数里,如果是多次调用,可以单独拎出来定义。
函数的定义在Go里面没有顺序之分,不像python里面如果是定义了一个函数,调用这个函数的指令,必须在定义这个函数之后。
resp.Body.Close关闭resp的Body流,防止资源泄漏。
注意看下面这段代码:
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) // start a goroutine
}
for range os.Args[1:] {
fmt.Println(<-ch) // receive from channel 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) // send to channel ch
return
}
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close() // don't leak resources
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)
}
channel是一个在goroutine之间传递参数的这么一个变量
使用指令go function 会创建一个goroutine,并在这个goroutine中异步执行function的功能
创建channel变量的方式,在main中函数创建:
ch := make(chan string),这个string是指定这个channel所属的变量类型
main函数本身也会创建一个goroutine异步运行
当一个goroutine尝试在一个channel里写或者接收参数的时候,这个goroutine会阻塞调用处,直到另一个goroutine从这个channel里接收或者写入值,
这样两个goroutine才会继续channel操作之后的逻辑
传递的方式 ch <- expression,<- ch