Python 程序员的 Golang 学习指南(III): 入门篇
基础语法
类型和关键字
- 类型
// 基础类型
布尔类型: bool
整型: int8,uint8,int16,uint16,int32,uint32,int64,uint64,int,rune,byte,complex128, complex64,其中,byte 是 int8 的别名
浮点类型: float32 、 float64
复数类型: complex64 、 complex128
字符串: string
字符类型: rune(int32的别名)
错误类型: error
// 复合类型
指针(pointer)
数组(array)
切片(slice)
字典(map)
通道(chan)
结构体(struct)
接口(interface)
- 关键字
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
变量
Go 同其他语言不同的地方在于变量的类型在变量名的后面,不是 int a
,而是 a int
。至于为什么这么定义,Go 的官方博客有给出解释,有兴趣的可以参考下。
变量定义语法如下:
var a int
a = 2
// 或者
a := 2
// 同时定义多个变量
var (
a int
b bool
)
// 同时给多个变量赋值
a, b := 2, true
操作符
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
控制结构
Go 语言支持如下的几种流程控制语句:
- 条件语句,对应的关键字为 if、else 和 else if;
- 选择语句,对应的关键字为 switch、case 和 select;
- 循环语句,对应的关键字为 for 和 range;
- 跳转语句,对应的关键字为 goto。
值得一提的是,Go 语言并不支持 do 或者 while 关键字,而是对 for 关键字做了增强,以实现类似的效果,如下:
for {
// 实现无限循环,慎用!
}
常用内置函数
- len:计算(字符串,数组或者切片,map)长度
- cap:计算(数组或者切片,map)容量
- close:关闭通道
- append:追加内容到切片
- copy:拷贝数组/切片内容到另一个数组/切片
- delete:用于删除 map 的元素
array, slice 和 map
// array
a := [3]int{ 1, 2, 3 } // 等价于 a := [...]int{ 1, 2, 3 }
// slice
s := make([]int , 3) // 创建一个长度为 3 的 slice
s := append(s, 1) // 向 slice 追加元素
s := append(s, 2)
// map
m := make(map[string]int) // 使用前必须先初始化
m["golang"] = 7
关于 array, slice 和 map 的更多惯用法,有一篇文章介绍的挺详细,有兴趣的可以看看。
函数
Go 语言的函数有如下特性:
- 不定参数
由于 Go 语言不支持函数重载(具体原因见 Go Language FAQ),但我们可以通过不定参数实现类似的效果。
func myfunc(args ...int) {
// TODO
}
// 可通过如下方式调用
myfunc(2)
myfunc(1, 3, 5)
- 多返回值
与 C、C++ 和 Java 等开发语言的一个极大不同在于,Go 语言的函数或者成员的方法可以有多 个返回值,这个特性能够使我们写出比其他语言更优雅、更简洁的代码。
func (file *File) Read(b []byte) (n int, err error)
// 我们可以通过下划线(_)来忽略某个返回值
n, _ := f.Read(buf)
- 匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式,它并不是一个新概念,最早可以回溯 到 1958 年的 Lisp 语言。但是由于各种原因,C 和 C++ 一直都没有对匿名函数给以支持,其他的各 种语言,比如 JavaScript、C# 和 Objective-C 等语言都提供了匿名函数特性,当然也包含Go语言。
匿名函数由一个不带函数名的函数声明和函数体组成,如下:
func(a, b int) bool {
return a < b
}
匿名函数可以直接赋值给一个变量或者直接执行:
f := func(a, b int) bool {
return a < b
}
func(a, b int) bool {
return a < b
}(3, 4) // 花括号后直接跟参数列表表示函数调用
- 闭包
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者 任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含 在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环 境(作用域)。
Go 的匿名函数就是一个闭包。我们来看一个例子:
package main
import "fmt"
func main() {
j := 5
a := func() func() {
i := 10
return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
程序输出如下:
i, j: 10, 5
i, j: 10, 10
错误处理
Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try...catch...finally
这种异常,因为 Go 语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在 Go 语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为0了),才使用 Go 中引入的Exception处理:defer, panic, recover。
用法如下:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("recovered:", recover())
}()
panic("not good")
}
关于 Go 语言的错误处理机制和传统的 try...catch...finally
异常机制孰优孰劣,属于仁者见仁,智者见智,这里不做赘速。有兴趣的同学可以去看看知乎上的讨论:Go 语言的错误处理机制是一个优秀的设计吗?。
面向对象 -> 一切皆类型
Python 推崇“一切皆对象”,而在 Go 语言中,类型才是一等公民。
我们可以这样定义一个结构体:
type Name struct {
First string
Middle string
Last string
}
同样也可以定义基础类型:
type SimpleName string
还能给任意类型定义方法:
func (s SimpleName) String() string { return string(s) }
// 或者
func (s string) NoWay()
Golang VS Python
最后我们通过几个例子来比较一下 Golang 与 Python 的一些基本用法,如下:
生成器(Generator)
- Python 版本
def fib(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
yield a
for x in fib(10):
print x
print 'done'
- Golang 版本
package main
import "fmt"
func fib(n int) chan int {
c := make(chan int)
go func() {
a, b := 0, 1
for i := 0; i < n; i++ {
a, b = b, a+b
c <- a
}
close(c)
}()
return c
}
func main() {
for x := range fib(10) {
fmt.Println(x)
}
}
装饰器(Decorator)
- Python 版本
from urlparse import urlparse, parse_qs
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
def auth_required(myfunc):
def checkuser(self):
user = parse_qs(urlparse(self.path).query).get('user')
if user:
self.user = user[0]
myfunc(self)
else:
self.wfile.write('unknown user')
return checkuser
class myHandler(BaseHTTPRequestHandler):
@auth_required
def do_GET(self):
self.wfile.write('Hello, %s!' % self.user)
if __name__ == '__main__':
try:
server = HTTPServer(('localhost', 8080), myHandler)
server.serve_forever()
except KeyboardInterrupt:
server.socket.close()
- Golang 版本
package main
import (
"fmt"
"net/http"
)
var hiHandler = authRequired(
func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi, %v", r.FormValue("user"))
},
)
func authRequired(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.FormValue("user") == "" {
http.Error(w, "unknown user", http.StatusForbidden)
return
}
f(w, r)
}
}
func main() {
http.HandleFunc("/hi", hiHandler)
http.ListenAndServe(":8080", nil)
}
猴子补丁(Monkey patching)
- Python 版本
import urllib
def say_hi(usr):
if auth(usr):
print 'Hi, %s' % usr
else:
print 'unknown user %s' % usr
def auth(usr):
try:
auth_url = 'localhost'
r = urllib.urlopen(auth_url + '/' + usr)
return r.getcode() == 200
except:
return False
def sayhitest():
# Test authenticated user
globals()['auth'] = lambda x: True
say_hi('John')
# Test unauthenticated user
globals()['auth'] = lambda x: False
say_hi('John')
if __name__ == '__main__':
sayhitest()
- Golang 版本
package main
import (
"fmt"
"net/http"
)
func sayHi(user string) {
if !auth(user) {
fmt.Printf("unknown user %v\n", user)
return
}
fmt.Printf("Hi, %v\n", user)
}
var auth = func(user string) bool {
authURL := "localhost"
res, err := http.Get(authURL + "/" + user)
return err == nil && res.StatusCode == http.StatusOK
}
func testSayHi() {
auth = func(string) bool { return true }
sayHi("John")
auth = func(string) bool { return false }
sayHi("John")
}
func main() {
testSayHi()
}
相关链接:
https://blog.golang.org/gos-declaration-syntax
https://se77en.cc/2014/06/30/array-slice-map-and-set-in-golang/
https://golang.org/doc/faq#overloading
https://www.zhihu.com/question/27158146
https://talks.golang.org/2013/go4python.slide