在 C/C++ 当中可以通过指针申请堆栈空间,指向某个变量空间,可以说不会指针,就不会 C/C++, 然而在 go 中也用指针的用法,其和 C/C++ 类型用法相差不大
在 go 中, 一个变量对应了类型值的内存空间。一个指针的值是另一个变量的空间内存地址,那么通过指针,我们可以在不需要知道该变量的名字的情况下直接读或更新对应变量的值。
如下:
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "1"
*p = 2 // equivalent to x = 2
fmt.Println(x) // "2"
另外,我们还要清楚对于任意类型的指针变量的零值都是 nil , 若测试到 p != nil, 那么 p 可以证明是一个指向某个有效变量的指针
var pInt *int // *int of type, pInt equal to nil
fmt.Println(pInt == nil) // true
var value int = 5
pInt = &value // pInt points to value
fmt.Println(pInt == nil) // false
在 C/C++ 当中如果我们返回了局部变量的地址,那是不安全的,具体原理不在详说, 但是在 go 语言当中,返回局部变量的地址是安全的。
例如下面的代码,调用 f 函数时创建局部变量 v,在局部变量地址被返回之后依然有效,因为指针 p 依然引用这个变量:
var p = f()
func f() *int {
v := 1
return &v
}
每次调用 f() 函数都会返回不同的结果
fmt.Println(f() == f()) // false
另外,在 go 语言当中 ,若是 p++ 操作,将会出现 invalid operation 的错误,若是 *p++ 操作,将会出现对其指针指向的值进行 ++ 操作.
func incr(p *int) int {
*p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!!
return *p
}
v := 1
incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3)
flag 包实现了命令行参数的解析
为了更好的帮我们理解 flag 包, 我们将会用指定的划分划分,划分程序的输入参数。包含了两个可选的命令行参数:-n 用于忽略行尾的换行符,-s sep用于指定分隔字符(默认是空格) ,代码如下:
package main
import(
"flag"
"fmt"
"strings"
)
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()
}
}
分析:
调用flag.Bool函数会创建一个新的对应布尔型标志参数的变量。它有三个属性:第一个是的命令行标志参数的名字“n”,然后是该标志参数的默认值(这里是false),最后是该标志参数对应的描述信息。如果用户在命令行输入了一个无效的标志参数,或者输入-h或-help参数,那么将打印所有标志参数的名字、默认值和描述信息。类似的,调用flag.String函数将于创建一个对应字符串类型的标志参数变量,同样包含命令行标志参数对应的参数名、默认值、和描述信息。程序中的sep和n变量分别是指向对应命令行标志参数变量的指针,因此必须用sep和n形式的指针语法间接引用它们。
当程序运行时,必须在使用标志参数对应的变量之前调用先flag.Parse函数,用于更新每个标志参数对应变量的值(之前是默认值)。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问,返回值对应对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误,默认将打印相关的提示信息,然后调用os.Exit(2)终止程序。
运行路径:
$GOPATH/src/gopl/ch2/echo4
运行命令:
$ cd $GOPATH/src/gopl/ch2/echo4
$ go run echo4.go -s \| a b c d e
$ go run echo4.go -s \| -n a b c d e
$ go run echo4.go -help