Go
语言关键字range
可以作用在array
,string
,slice
,map
和channel
五种数据结构上。
标签:Golang
Go语言实践技巧(2)——package定义和初始化
(1)一个文件夹下的所有文件必须属于同一个package
,一个好的规则是package
和文件夹名字相同。此外,要注意import
语句所指定的是寻找package
的path
,而不是package
的名字。关于这个话题讨论,请参见Is the package name must same with name folder name?。
另外,要注意import
的语法(参考这里):
import packagename importpath
举例如下:
Import declaration Local name of Sin
import "lib/math" math.Sin
import m "lib/math" m.Sin
import . "lib/math" Sin
在第二种情况下,m
是package
名字;第三种情况下,使用math package
可以省略package
名字。
(2)
import (
_ "github.com/xxx"
)
这种做法可以保证”github.com/xxx
“这个package
的初始化操作完成(这个package
中所有init
函数都执行),即使在当前文件中没有显式地使用到这个package
。
Go语言实践技巧(1)——变量定义
Go
语言变量定义的一个好的实践技巧是:
(1)如果变量声明时会被赋给数据类型的非“0
”值,则使用“:=
”这种方式:
a := 3
b := fun1()
(2)否则,因为所有变量声明时都会初始化为数据类型的“0
”值,则使用var
关键字定义这种方式:
var a int;
Go语言字符串浅析
在Go
语言中,字符串就是一个只读(read-only
)的,可以包含任意字节(byte
)的切片(slice
)。由于Go
源码文件都是使用UTF-8
编码的,所以直接在源码中输入的字符串也都是用UTF-8
编码的(前提是字符串里没有byte-level escapes
,即字节层面上的转义)。请看下面这个例子:
package main
import "fmt"
func main() {
s := "日志log"
fmt.Println(len(s))
}
执行结果:
9
s
是一个UTF-8
编码的字符串,“日志
”两个汉字各占3
个字节,所以一共是9
个字节。
再看一个例子:
package main
import "fmt"
func main() {
s:="\xFF\xFF"
fmt.Println(len(s))
}
执行结果:
2
虽然0xFF
不是一个合法的UTF-8
编码字符,但是因为字符串可以包含任意字节,所以s
仍是一个合法的字符串。
Go
语言引入rune
类型(实际就是int32
)来表示字符(character
),“日志log
”这个字符串包含了5
个字符,9
个字节。 可以用for range
循环来遍历UTF-8
编码字符串的每个字符:
package main
import "fmt"
func main() {
s := "日志log"
for index, runeValue := range s {
fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}
}
执行结果:
U+65E5 '日' starts at byte position 0
U+5FD7 '志' starts at byte position 3
U+006C 'l' starts at byte position 6
U+006F 'o' starts at byte position 7
U+0067 'g' starts at byte position 8
Go语言数组浅析
先看一个简单的数组定义:
var array [3]int
上面语句定义了变量array
,这个变量是一个包含3
个整数的数组类型。可以使用Go
语言内置的len
函数得到数组长度:
package main
import (
"fmt"
)
func main() {
var array [3]int
fmt.Println(len(array))
}
执行以上程序结果:
3
需要注意的是,在Go
语言中,数组的长度是数组类型的一部分,它必须是一个常量表达式,即长度的值在程序编译时是可以得到的。请看下面这个程序:
package main
import (
"fmt"
)
func print_array(array []int) {
fmt.Printf("In function, array is %v\n", array)
}
func main() {
var array [3]int
print_array(array )
}
编译这个程序会产生下面的错误:
main.go:14: cannot use array (type [3]int) as type []int in argument to print_array
Go
语言要求传入函数的形参和实参的类型必须是完全匹配的,而array
的类型是[3]int
,不是[]int
,所以编译会出错。
Go
语言函数参数都是传值调用,所以如果函数的参数是数组的话,那么实际传入的将是原数组的拷贝。请看这个例子:
package main
import (
"fmt"
)
func change_array(array [3]int) {
for i, _ := range array {
array[i] = 1
}
fmt.Printf("In function, array address is %p, value is %v\n", &array, array)
}
func main() {
var array [3]int
fmt.Printf("Original array address is %p, value is %v\n", &array, array)
change_array(array)
fmt.Printf("Changed array address is %p, value is %v\n", &array, array)
}
执行结果:
Original array address is 0xc082006560, value is [0 0 0]
In function, array address is 0xc0820065e0, value is [1 1 1]
Changed array address is 0xc082006560, value is [0 0 0]
可以看到,在change_array
函数中操作的数组地址并不是main
函数中的数组地址,而main
函数中的数组的值在调用change_array
函数前后也没有发生变化。
如果数组的元素类型是可比较的,那么数组类型就是可比较的,即可以使用“==
”和“!=
”运算符对数组进行运算。 请看下例:
package main
import "fmt"
func main() {
a := [...]int{2, 1}
b := [...]int{1, 2}
fmt.Println(a == b, a != b)
}
执行结果如下:
false true
Go语言runtime.Gosched()函数浅析
Go
语言runtime.Gosched()
函数的官方文档如下:
func Gosched
func Gosched()
Gosched yields the processor, allowing other goroutines to run. It does not suspend the current goroutine, so execution resumes automatically.
这个函数的作用是让当前goroutine
让出CPU
,好让其它的goroutine
获得执行的机会。同时,当前的goroutine
也会在未来的某个时间点继续运行。
请看下面这个例子(show.go
):
package main
import (
"fmt"
)
func showNumber (i int) {
fmt.Println(i)
}
func main() {
for i := 0; i < 10; i++ {
go showNumber(i)
}
fmt.Println("Haha")
}
执行结果如下:
[root@Fedora goEx]# go build show.go
[root@Fedora goEx]# ./show
Haha
没有打印出数字,可以看到goroutine
没有获得机会运行。
修改代码:在main
函数中加上runtime.Gosched()
:
package main
import (
"fmt"
"runtime"
)
func showNumber (i int) {
fmt.Println(i)
}
func main() {
for i := 0; i < 10; i++ {
go showNumber(i)
}
runtime.Gosched()
fmt.Println("Haha")
}
编译运行:
[root@Fedora goEx]# go build show.go
[root@Fedora goEx]# ./show
0
1
2
3
4
5
6
7
8
9
Haha
可以看到goroutine
获得了运行机会,打印出了数字。
参考资料:
(1)Gosched;
(2)What exactly does runtime.Gosched do?。
Go程序编译简介
Go
语言安装包自带的编译器是gc
。针对不同的硬件平台,Go
分别提供了一组编译工具包:比如,6a
,6c
,6g
和6l
就是针对X86-64
平台(5*
是ARM
,8*
是X86
)。此外gcc
还有一个支持Go
语言的前端:gccgo。因此如果gc
不支持你当前的平台,可以尝试用gcc
。
go build
命令默认会使用gc
编译器。假设当前目录为goEx
,在这个目录下创建hello.go
:
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
在当前目录下执行go build
命令:
[root@Fedora goEx]# ls
hello.go
[root@Fedora goEx]# go build
[root@Fedora goEx]# ls
goEx hello.go
[root@Fedora goEx]# ./goEx
Hello, world!
可以看到生成的可执行文件名字为goEx
,与目录名相同。
因为hello.go
是main package
,执行go build hello.go
会生成名为hello
的可执行文件:
[root@Fedora goEx]# go build hello.go
[root@Fedora goEx]# ls
hello hello.go
也可以使用-o
选项指定可执行文件名字:
[root@Fedora goEx]# go build -o a.out hello.go
[root@Fedora goEx]# ls
a.out hello.go
[root@Fedora goEx]# ./a.out
Hello, world!
Go语言的new函数
Go
语言有一个内置的new
函数,其定义如下:
func new
func new(Type) *Type
The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.
其输入参数是一个类型,返回是一个指向该类型内存的指针,且指针所指向的这块内存已被初始化为该类型的0
值。下面例子演示了如何使用new
函数:
package main
import (
"fmt"
)
func main() {
v := new(int)
*v++
fmt.Println(*v)
}
运行结果如下:
1
new
函数的出现早于make
和&{}
,并且它可以让人直观地认识到返回值就是一个指针,在某些情形下,这会让人更清晰地理解代码。
参考资料:
(1)http://golang.org/pkg/builtin/#new;
(2)Why is there a “new” in Go?。
Goroutine浅析
Goroutine
是Go
语言并发编程中的一个重要概念,事实上我们可以把它理解成为非常轻量级的线程(thread
)或协程(coroutine
)。每个运行的 Go
程序都至少包含一个goroutine
——运行main
函数的goroutine
。
创建goroutine
要使用go
语句:
go function(arguments)
go func(parameters) { block } (arguments)
go
后面要跟一个已经定义的函数或是一个现定义的匿名函数。执行完这条语句后,就会启动一个独立的,和其它程序中已有的goroutine
并行运行的新的goroutine
。
runtime
程序包中提供了一个GOMAXPROCS
函数,它可以用来设定实际上有多少个操作系统线程来执行Go
程序。截止到目前为止(1.4.2
版本),这个值一直默认为1
。从1.5
版本起,Go
的开发者打算把这个默认值改为可以使用的CPU
个数,并认为这会改善Go
程序的性能。看一个简单的程序:
package main
import (
"fmt"
"time"
"runtime"
)
func sleep(s string) {
for {
fmt.Println(s)
time.Sleep(time.Second)
}
}
func main() {
fmt.Println(runtime.GOMAXPROCS(0))
go sleep("Hello")
go sleep("World")
time.Sleep(1000 * time.Second)
}
main
函数第一行打印了当前系统默认设置的GOMAXPROCS
的值。另外在main goroutine
里启动了两个goroutine
,分别交替地打印“Hello
”和“World
”。执行如下:
1
Hello
World
Hello
World
Hello
World
Hello
World
......
可以看到默认的GOMAXPROCS
的值的确为1
。“Hello
”和“World
”也被交替打印了。在执行过程中,使用pstack
命令查看进程的线程调用栈:
[root@Fedora ~]# pstack 10773
Thread 1 (LWP 10773):
#0 runtime.futex () at /usr/local/go/src/runtime/sys_linux_amd64.s:278
#1 0x000000000042b197 in runtime.futexsleep () at /usr/local/go/src/runtime/os_linux.c:49
#2 0x000000000040adae in runtime.notesleep (n=0x54df18 <runtime.m0+216>) at /usr/local/go/src/runtime/lock_futex.go:145
#3 0x000000000042e669 in stopm () at /usr/local/go/src/runtime/proc.c:1178
#4 0x000000000042f4c2 in findrunnable () at /usr/local/go/src/runtime/proc.c:1487
#5 0x000000000042f881 in schedule () at /usr/local/go/src/runtime/proc.c:1575
#6 0x000000000042fb03 in runtime.park_m () at /usr/local/go/src/runtime/proc.c:1654
#7 0x000000000041f94a in runtime.mcall () at /usr/local/go/src/runtime/asm_amd64.s:186
#8 0x000000000054dab0 in runtime.g0 ()
#9 0x000000000042de04 in runtime.mstart () at /usr/local/go/src/runtime/proc.c:836
#10 0x000000000041f84c in runtime.rt0_go () at /usr/local/go/src/runtime/asm_amd64.s:106
#11 0x0000000000000001 in ?? ()
#12 0x00007ffd43803348 in ?? ()
#13 0x0000000000000001 in ?? ()
#14 0x00007ffd43803348 in ?? ()
#15 0x0000000000000000 in ?? ()
可以看到的确只有一个线程在运行。
Goroutine
的调度器(scheduler
)并不是完全抢占的(fully-preemptive
),当有GOMAXPROCS
个goroutine
并行运行时,运行的goroutine
必须要放弃执行权,其它goroutine
才有机会得到运行(比方说,调用time.Sleep()
)。此外,取决于调度器的实现,goroutine
可能在执行任何函数时,都会放弃CPU
。看下面这个程序:
package main
import (
"fmt"
)
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
执行结果如下:
hello
hello
hello
hello
hello
main goroutine
运行起来以后,并没有给另外一个goroutine
打印“world
”的机会。随着main goroutine
执行完毕,这个程序也退出了。修改程序如下:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
time.Sleep(time.Second)
}
这次执行结果如下:
hello
hello
hello
hello
hello
world
world
world
world
world
在main
函数最后加了一行time.Sleep(time.Second)
,这样main goroutine
就可以把CPU
的执行权让出来,所以另外一个goroutine
得以运行。
参考资料:
(1)Go 1.5 GOMAXPROCS Default;
(2)Some questions about goroutine;
(3)Why is time.sleep required to run certain goroutines。
Go语言“:=”用法浅析
Go
语言中可以使用“:=
”为一个新的变量完成声明以及初始化的工作,如下例所示:
i := 1
等价于:
var i = 1
要注意语句中没有变量类型,不是var i int = 1
。
“:=
”不能重新声明一个已经声明过的变量,如下例所示:
package main
import "fmt"
func main() {
var i = 1
i := 2
fmt.Println(i)
}
编译结果:
C:/Go\bin\go.exe run C:/Work/go_work/Hello.go
# command-line-arguments
.\Hello.go:8: no new variables on left side of :=
错误的原因是变量被重复声明了。
但使用“:=
”为多个变量赋值时,如果引入了至少一个新的变量,编译是可以通过的,如下例所示:
package main
import "fmt"
func main() {
var i = 1
i, err := 2, false
fmt.Println(i, err)
}
编译执行:
C:/Go\bin\go.exe run C:/Work/go_work/Hello.go
2 false
要注意这个时候,并没有重新声明“i
”变量,只是为之前声明的“i
”变量赋值。
“:=
”只能用在函数体中。它的一个重要用途是用在“if
”,“for
”和“switch
”语句的初始化,使变量成为一个“临时变量”,也就是变量的作用域仅限于这条语句。如下例所示:
package main
import "fmt"
func main() {
for j := 3; j <= 5; j++ {
fmt.Println(j)
}
fmt.Println(j)
}
编译结果:
C:/Go\bin\go.exe run C:/Work/go_work/Hello.go
# command-line-arguments
.\Hello.go:11: undefined: j
“j
”的声明作用域仅限于“for
”语句,所以编译第二个打印语句会出错。
参考资料:
(1)Short variable declarations;
(2)Assignment operator in Go language。