命令行参数
程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量
os.Args变量是一个字符串(string)的切片(slice)
os.Args的第一个元素,os.Args[0],是命令本身的名字;其它的元素则是程序启动时传给它的参数。s[m:n]形式的切片表达式,产生从第m个元素到第n-1个元素的切片,下个例子用到的元素包含在os.Args[1:len(os.Args)]切片中。如果省略切片表达式的m或n,会默认传入0或len(s),因此前面的切片可以简写成os.Args[1:]
文件操作
打开文件获取文件流
// files 文件名称列表 不会自动关闭文件流需要手动关闭
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()
}
// 配合读取流使用
func countLines(f *os.File, counts map[string]int) {
    // 这里只要是实现流io.Reader接口都行
	input := bufio.NewScanner(f)
	// 读取一行数据
	for input.Scan() {
		counts[input.Text()]++
		if counts[input.Text()] > 1{
			fmt.Println(f.Name())
		}
	}
}
读取文件全部内容
// ReadFile 会自己关闭文件
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]++
	}
}
获取http请求中的内容
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)
}
switch
Go语言并不需要显式地在每一个case后写break,语言默认执行完case后的逻辑语句会自动退出。当然了,如果你想要相邻的几个case都执行同一逻辑的话,需要自己显式地写上一个fallthrough语句来覆盖这种默认行为。不过fallthrough语句在一般的 程序中很少用到。
Go语言里的switch还可以不带操作对象(译注:switch不带操作对象时默认用true值代替,然后将每个case的表达式和true值进行比较);可以直接罗列多种条件,像其它语言里面的多个 if else一样
func test(x int) int {
    switch {
        case  x > 0:
            xxx
        case x < 0:
            xxx
        default:
            xxxx
    }
}
相关定义
指针
一个变量对应了一个保存了变量对应类型值的内存空间。普通变量在声明语句创建时被绑定到一个变量名,比如叫x的变量,但是也有很多变量始终以表达式方式引入,例如x[i]或x.f变量。
一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我们可以直接读取或更新对应变量的值,而不需要知道该变量的名字。
如果用“varxint”声明语句声明一个x变量,那么&x表达式(取x变量的内存地址)将产生一个指向该整数变量的指针,指针对应的数据类型是int,指针被称之为“指向int类型的指针”。如果指针名字为p,那么可以说“p指针指向变量x”,或者说“p指针保存了x变量的内存地址”。同时p表达式对应p指针指向的变量的值。一般p表达式读取指针指向的变量的值,这里为int类型的值,同时因为p对应一个变量,所以该表达式也可以出现在赋值语句的左边,表示更新指针所指向的变量的值
对于聚合类型每个成员——比如结构体的每个字段、或者是数组的每个元素——也都是对应一个变量,因此可以被取地址。
任何类型的指针的零值都是nil。如果p!=nil测试为真,那么p是指向某个有效变量。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
package main
import "fmt"
var p = f()
func f() *int  {
	v := 1
	return &v
}
func main()  {
	fmt.Println(f()==f())
}
// 返回false
虽然变量v的值都是1,但每次调用的时候都不是同一个变量,所有是false.
flag
// ./test -n true -s 12
// 启动参数为n,描述为后面那一串,默认值为false
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")
func main() {
	flag.Parse()
	fmt.Print(strings.Join(flag.Args(), *sep))
	if !*n {
		fmt.Println()
	}
}
变量
一个创建变量的方法是调用用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为 *T
p := new(int)   // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // 0
*p = 2          // 设置 int 匿名变量的值为 2
fmt.Println(*p) // 2
每次调用new函数都是返回一个新的变量的地址,因此下面两个地址是不同的
p := new(int)
q := new(int)
fmt.Println(p == q) // false
当然也可能有特殊情况:如果两个类型都是空的,也就是说类型的大小是0,例如struct{}和[0]int,有可能有相同的地址(依赖具体的语言实现)(译注:请谨慎使用大小为0的类型,因为如果类型的大小位0好话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看runtime.SetFinalizer函数相关文档)
对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型(译注:如果T是指针类型,可能会需要用小括弧包装T,比如(*int)(0))。只有当两个类型的底层基础类型相同时,才允许这种转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。如果x是可以赋值给T类型的值,那么x必然也可以被转为T类型,但是一般没有这个必要
Go语言的自动圾收集器
基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果
因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。
var global *int
func f() {
    var x int
    x = 1
    global = &x
}
func g() {
    y := new(int) *y = 1
}
f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量y将是不可达的,也就是说可以马上被回收的。因此,y并没有从函数g中逃逸,编译器可以选择在栈上分配*y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
// 斐波那契数列的第N个数
func fib(n int) int {
    x, y := 0, 1
    for i := 0; i < n; i++ {
       x, y = y, x+y
    }
    return x
}
init函数
每个文件都可以拥有多个init函数。在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用
每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依然的包都已经完成初始化工作了