所有的抉择都有因为产品的需要
Go诞生于Google, 是由以下三位计算机领域的大师写的
由于出身名门,Go在诞生之初就吸引了大批开发者的关注。诞生十年以来,已经有很多基于Go的应用,一直有传言Go在将来是要取代现在Java的位置。对于一门只有十年历史的新语言,Go的发展势头是相当迅猛的。国外的docker就是用Go写的,国内的七牛团队广泛使用Go。
而今如火如荼的区块链技术更是将Go推上了热潮。IOT设备连接协议也是Go语言开发技术居多。由此可见未来是属于Go和Python的天下。
go环境搭建
在官网下载go的安装包
如果是mac,可直接用brew安装:
$ brew intall go
查看go版本
$ go version
go version go1.10.3 darwin/amd64
go环境变量配置
GOROOT golang安装路径
GOPATH go工作环境中常常用到的一个很重要的环境变量。具体用途:go命令常常需要用到的,
如go run,go install, go get等。
GOBIN go install编译存放路径。不允许设置多个路径。可以为空
go env 查看环境变量,三个重要的变量GOPATH、GOROOT、GOBIN
vi /etc/profile.d/go.sh
GOPATH="~/goProject1/:~/goProject2/:~/goProject3/"
GOROOT="/usr/server/go"
GOOS=linux
PATH=$GOROOT/bin:$PATH
GOPATH目录结构
goProject // (goProject为GOPATH目录)
-- bin // golang编译可执行文件存放路径,可自动生成。
-- pkg // golang编译的.a中间文件存放路径,可自动生成。
-- src // 源码路径。按照golang默认约定,go run,go install等命令的当前工作路径(即在此路径下执行上述命令)
goProject // goProject 为GOPATH目录
-- bin
-- myApp1 // 编译生成
-- myApp2 // 编译生成
-- myApp3 // 编译生成
-- pkg
-- src
-- common 1
-- common 2
-- common utils ...
-- myApp1 // project1
-- models
-- controllers
-- others
-- main.go
-- myApp2 // project2
-- models
-- controllers
-- others
-- main.go
-- myApp3 // project3
-- models
-- controllers
-- others
-- main.go
go开发编辑器
关于编辑器,推荐使用jetbrains出品的golang
第一个go程序
按照官网的demo,运行第一个go程序
新建hello.go文件
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
运行go可以有两种方式:
类似于python,php这样的脚本语言,直接运行,一步到位
$ go run hello.go
如java,c++ 一样,要进行编译,再运行编译输出的可执行文件
$ go build hello.go # 生成可执行文件 hello
$ ls
hello hello.go
$ ./hello # 运行可执行文件,运行速度极快
Hello, 世界
go是一门有着语法洁癖的语言,如强制规定了大括号的写法。长期以来,关于大括号是否要另起一行,是程序员界的圣战,且这个圣战跨越了语言,操作系统,编辑器。战斗双方多年来厮杀无数回合,不分胜负。python之父Guido van Rossum直接取消了大括号,完全采用缩进,绕过了圣战。与之相比,go就显得很霸道了,直接下了死命令: "大括号只能在当前行开始,不能另起一行,另起一行是异教徒,直接编译出错!" 让我们来试下。
对于另起一行的强迫症程序员,想用go惟有屈服。此外,如果程序中有冗余变量,或引入了没有用到的包,都会报错
package main
import "fmt" // 引入了fmt包,但没有使用
func main() {
}
报错:
$ go build hello.go
# command-line-arguments
./hello.go:3:8: imported and not used: "fmt"
go的设计理念既简洁又严谨,用强制性的规则保证了代码的一致性。
命名规则
变量的命名规则:以字母或下划线开头,对大小写敏感。
Go内置关键字共25个
Go语言预定义标识符(区分大小写)共38个
Go的注释方法
// :单行注释
/* */ :多行注释
Go程序的一般结构
//当前程序的包名
package main
//导入其它的包
import "fmt"
//全部变量的声明与赋值
var name = "gopher"
//一般类型的声明
type newType int
//结构的声明
type gopher struct{}
//接口的声明
type golang interface{}
//由main函数作为程序入口点启动
func main() {
fmt.Println("Hello World!")
}
包的导入
别名:
import std "fmt"
省略调用(这样可以不用写包名,直接调用):
import . "fmt"
初始化,但不调用:
import _ "github.com/go-sql-driver/mysql"
可见性规则
只有首字母为大写的才能被其它包调用(类似面向对象里面的属性public 和private)
作用域
1、在代码块声明的变量,只在块内有效
2、在函数内部声明的变量,只在函数内有效
3、在函数外部声明的变量,在整个包内都有效。如果变量名称是大写的,即在整个程序中都有效
4、如果变量名要共享到其他包,就需要将包名改成大写,创建以下目录结构:
$ tree -L 2
├── main
│ └── main.go
└── test
└── test.go
main.go
package main
import (
"fmt"
"../test" // 引入test包
)
func main() {
fmt.Println(test.NAME) // 获取test包的NAME变量
}
test.go
package test
var NAME = "myname" // 变量名大写,才能被其他包引用,类似于java中的public关键字
运行结果
$ go run main/main.go
myname
你可以试下将test中的NAME改为name,go会报错,小写的变量是模块的私有变量,其他模块无法引用
常量与运算符
1.常量的定义
常量的值在编译时就已经确认
常量的定义格式与变量基本相同
等号右侧必须是常量或者常量表达式
常量表达式中的函数必须是内置函数
//定义单个常量
const a int = 1
const b = "A"
const (
text = "123"
length = len(text)
num = b * 20
)
//同时定义多个变量
const i, j, k = 1, "2", "3"
const (
text2 ,length2, num2 = "456", len(text2), k* 10
)
2.常量的初始化规则
在定义常量组时,如果不提供初始值,则表示使用上行的表达式
var a = 1
const (
b = a
//此时报错,因为全局变量在编译时不能确定值,常量必须是编译时能确定值的或使用内置函数获得的值(eg:len())
)
const (
a = "A"
b
c
//此时 a,b,c都为"A"
)
const (
a, b = 1, "A"
c, d
//a,c为1,b,d为"A"。此时注意下行的常量个数必须与上行数量保持一致
)
3.常量的枚举
使用相同的表达式不代表具有相同的值
iota是常量的计数器,从0开始,组中每定义1个常量自动递增1
通过初始化规则与iota可以达到枚举的效果
每遇到一个const关键字,iota就会重置为0
const (
a = "A"
b
c = iota
d //d的值为3,因为在常量组中,定义了4个变量所以iota计数器从0开始计数4次,所以为3
)
4.运算符
//优先级从高到低
* / % << >> & &^
= - | ^
== != < <= >= >
&&
||
例子:
/*
6的二进制:0110 第一个
10的二进制:1011 第二个
---------
& 0010 = 2 (两个都是1才为1)
| 1111 = 15 (一个是1就是1)
^ 1101 = 13 (两个只有一个是1才是1)
$^ 0100 = 4 (第二个为1则是0,否则与第一位相同)
*/
基础使用方法参考 以下链接
- [Go 语言教程] (http://www.runoob.com/go/go-tutorial.html)
Python 和 Golang的不同与相似之处
变量
go在定义变量的规范上,有些反人类。c++, java 都是在变量名之前声明数据类型,而go却别出心裁的将数据类型声明置于变量名后面。
在Python 里面想建立一个变数的时候就直接建立
# -*- coding: UTF-8 -*-
counter = 100 # 赋值整型变量
miles = 1000.0 # 浮点型
name = "John" # 字符串
那么Golang 呢?在Golang 中变数分为几类:「新定义」、「预先定义」、「自动新定义」、「覆盖」。让我们来看看范例:
// 新定义:定义新的 a 变数为字串型别,而且值是「foo」
var a string = "foo"
// 预先定义:先定义一个新的 b 变数为字串型别但是不赋予值
var b string
// 自动新定义:让 Golang 依照值的内容自己定义新变数的资料型态
c := "bar"
// 覆盖:先前已经定义过 a 了,所以可以像这样直接覆盖其值
a = "fooooooo"
打印输出-Echo
在Python中你会很常用到Print来显示文字,像这样。
Python
print("Foo") // 輸出:Foo
A = "Bar"
print(A) // 輸出:Bar
B = "Hello"
print(B +", world!") // 輸出:Hello, world!
C = [1, 2, 3]
print(C) // 輸出:[1 2 3]
Golang
然而在Golang中你会需要fmt套件,关于「什么是套件」的说明你可以在文章下述了解。
fmt.Println("Foo") // 輸出:Foo
A := "Bar"
fmt.Println(A) // 輸出:Bar
B := "Hello"
fmt.Printf("%s, world!", B) // 輸出:Hello, world!
C := []int{1, 2, 3}
fmt.Println(C) // 輸出:[1 2 3]
函数-Function
这很简单,而且两个语言的用法相差甚少,下面这是Python:
Python
# -*- coding:utf-8 -*-
def test():
return "Hello, world!";
print(test()) // 輸出:Hello, world!
Golang
只是Golang 稍微聒噪了一点,你必须在函式后面宣告他最后会回传什么资料型别。
func test() string {
return "Hello, world!"
}
fmt.Println(test()) // 輸出:Hello, world!
多值回传-Multiple Value
在Python 中你要回传多个资料你就会用上元组,然后将资料放入元组里面,像这样。
Python
# -*- coding:utf-8 -*-
def test():
return ("YamiOdymel", 123456)
username, time = test()
print(username, time) #輸出:YamiOdymel 123456
Golang
然而在Golang 中你可以不必用到一个阵列,函式可以一次回传多个值:
func test() (string, int) {
return "YamiOdymel", 123456
}
username, time := test()
fmt.Println(username, time) // 輸出:YamiOdymel 123456
匿名函式-Anonymous Function
两个语言的撰写方式不尽相同。
Python
在Python中,对匿名函数提供了有限支持。以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数lambda x: x * x实际上就是:
def f(x):
return x * x
# Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
Golang
a := func() {
fmt.Println("Hello, world!")
}
a() // 輸出:Hello, world!
阵列(列表)-Array
Python
创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可。如下所示:
list1 = ['physics', 'chemistry', 1997, 2000]
list2 = [1, 2, 3, 4, 5 ]
list3 = ["a", "b", "c", "d"]
print(list2[0]) // 輸出:1
print(list3[2]) // 輸出:c
与字符串的索引一样,列表索引从0开始。列表可以进行截取、组合等。
Golang
var a [2]string
a[0] = "foo"
a[1] = "bar"
fmt.Println(a[0]) // 輸出:foo
切片-Slice
可供「裁切」而且供自由扩展的列表。
slice() 函数实现切片对象,主要用在切片操作函数里的参数传递。
Python
格式: [start:end:step]
参数说明:
start -- 起始位置
stop -- 结束位置
step -- 间距
下实例展示了 slice 的使用方法:
>>>myslice = slice(5) # 设置截取5个元素的切片
>>> myslice
slice(None, 5, None)
>>> arr = range(10)
>>> arr
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> arr[myslice] # 截取 5 个元素
[0, 1, 2, 3, 4]
提取最后N个字符:
>>> letter = 'abcdefghijklmnopqrstuvwxyz'
>>> letter[-3:]
'xyz'
Golang
a := []string{"foo", "bar"}
fmt.Println(a[0]) // 輸出:foo
我们刚才有提到你可以「切」他,记得吗?这有点像是PHP中的array_slice(),但是Golang直接让Slice「内建」了这个用法,其用法是:slice[开始:結束]。
p := []int{1, 2, 3, 4, 5, 6}
fmt.Println(p[0:1]) // 輸出:[1]
fmt.Println(p[1:1]) // 輸出:[] (!注意這跟 PHP 不一樣!)
fmt.Println(p[1:]) // 輸出:[2, 3, 4, 5, 6]
fmt.Println(p[:1]) // 輸出:[1]
映照-Map
有键名和键值的阵列。
你可以把「映照」看成是一个有键名和键值的阵列,但是记住:「你需要事先定义其键名、键值的资料型态」,这仍限制你没办法在映照中存放多种不同型态的资料。
Python
data={} # 先声明字典
data["username"] = "YamiOdymel";
data["password"] = "2016 Spring";
print(data["username"]) # 輸出:YamiOdymel
以下实例展示了 map() 函数映射的使用方法:
map(function, iterable, ...)
>>>def square(x) : # 计算平方数
... return x ** 2
...
>>> map(square, [1,2,3,4,5]) # 计算列表各个元素的平方
[1, 4, 9, 16, 25]
>>> map(lambda x: x ** 2, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]
# 提供了两个列表,对相同位置的列表数据进行相加
>>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
[3, 7, 11, 15, 19]
Golang
在Golang里可就没这么简单了,你需要先用make()宣告map。
data := make(map[string]string)
data["username"] = "YamiOdymel"
data["password"] = "2016 Spring"
fmt.Println(data["username"]) // 輸出:YamiOdymel
接口-Interface
也许你不喜欢「接口」这个词,但用「介面」怕会误导大众,所以,是的,接下来会继续称其为「接口」。还记得你可以在Python 并不关心对象是什么类型,到底是不是鸭子,只关心行为。
Python
下面的代码就是一个简单的鸭子类型
#coding=utf-8
class Duck:
def quack(self):
print "Quaaaaaack!"
class Bird:
def quack(self):
print "bird imitate duck."
class Doge:
def quack(self):
print "doge imitate duck."
def in_the_forest(duck):
duck.quack()
duck = Duck()
bird = Bird()
doge = Doge()
for x in [duck, bird, doge]:
in_the_forest(x)
Golang
Golang 也一样并不关心对象是什么类型,只关心行为。!正因为Golang中的interface{}可以接受任何内容,所以你可以把它拿来存放任何型态的资料。
mixedData := []interface{}{"foobar", 123456}
mixedData2 := make(map[string]interface{})
mixedData2["username"] = "YamiOdymel"
mixedData2["time"] = 123456
逆向处理-Defer
当我们程式中不需要继续使用到某个资源或是发生错误的时候,我们索性会将其关闭或是抛弃来节省资源开销,例如Python里的读取文档:
因此Golang中,defer通常用来释放函数内部变量,或者捕获错误发生。
Python 中,捕获错误发生使用try方法,内部变量释放使用gc模块释放,没有推迟执行
Python
try:
with open('/etc/passwd') as f:
for line in f:
print(line)
except:
#如果在读取文件的过程中,产生了异常,那么就会捕获到
#比如 按下了 ctrl+c
pass
finally:
f.close()
print('关闭文件')
Golang
在Golang中,你可以使用defer来在函式结束的时候自动执行某些程式(其执行方向为反向)。所以你就不需要在函式最后面结束最前面的资源。
defer可以被称为「推迟执行」,实际上就是在函式结束后会「反序」执行的东西,例如你按照了这样的顺序定义defer:A->B->C->D,那么执行的顺序其实会是D->C->B->A,这用在程式结束时还蛮有用的,让我们看看Golang如何改善上述范例。
handle := file.Open("example.txt")
defer file.Close() // 关闭档案但「推迟执行」,所有程式结束后才会执行这裡
if errorA {
errorHandlerA()
}
if errorB {
errorHandlerB()
}
跳往-Goto
这东西很邪恶,不是吗?又不是在写BASIC,不过也许有时候你会在Python 用上呢。尽量不要使用。
Python
没有Goto方法,可以使用三方模块实现,但不推荐使用:
1、代码会很难看
2、Goto是一个很古老的方法
Golang
goto a
fmt.Println("foo")
a:
fmt.Println("bar") // 輸出:bar
回环-Loops
Golang中仅有for一种回圈但却能够达成foreach、while、for多种用法。普通for回圈写法在两个语言中都十分相近。
Python中循环有While, 和for()
for循环
Python
for v in range(4,8):
print(v)
Golang
在Golang请记得:如果你的i先前并不存在,那么你就需要定义它,所以下面这个范例你会看见i := 0。
for i := 0; i < 3; i++ {
fmt.Println(i) // 輸出 012
}
j := 0
for ; j < 5 ; j++ {
fmt.Println(j) // 輸出:01234
}
foreach方式
Python里面可以使用range达成和一样的foreach方式。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
fruits = ['banana', 'apple', 'mango']
for index in range(len(fruits)):
print '当前水果 :', fruits[index]
print("Good bye!")
Golang里面虽然仅有for()但却可以使用range达成和一样的foreach方式。
data := []string{"a", "b", "c"}
for index, value := range data {
fmt.Printf("%d%s|", index, value) // 輸出:0a|1b|2c|
}
for index := range data {
fmt.Printf("%d|", index) // 輸出:0|1|2|
}
for _, value := range data {
fmt.Printf("%s|", value) // 輸出:a|b|c|
}
重复-While
一个while(條件)回圈在PHP里面可以不断地执行区块中的程式,直到條件为false为止。
Python
count = 0
while count< 10:
print("you are my sunshine "),
count = count + 1
Golang
在Golang里也有相同的做法,但仍是透过for回圈,请注意这个for回圈并没有任何的分号(;),而且一个没有条件的for回圈会一直被执行。
i := 0
for i < 3 {
i++
fmt.Println(i) // 輸出:123
}
for {
fmt.Println("WOW") // 輸出:WOWWOWWOWWOWWOW...
}
重复-Do While
Golang
在Golang中则没有相关函式,但是你可以透过一个无止尽的for回圈加上条件式来让他结束回圈。
i := 0
for {
i++
fmt.Println(i) // 輸出:123
if i > 2 {
break
}
}
可以在Golang中使用很邪恶的goto,实现LOOP。
i := 0
LOOP:
i++
fmt.Println(i) // 輸出:123
if i < 3 {
goto LOOP
}
抛出和捕捉异常-Try & Catch
也许你在Python中更常用的会是try .. Exception
,在大型商业逻辑时经常看见如此地用法,实际上这种用法令人感到聒噪(因为你会需要一堆try
区块):
- Too many try/catch block for PDO
- Too many try/catch blocks. Is this proper?
- Is this too many lines and too many nested blocks?
PHP
# -*- coding: UTF-8 -*-
try:
1 / 0
except Exception as e:
'''异常的父类,可以捕获所有的异常'''
print "0不能被除"
else:
'''保护不抛出异常的代码'''
print "没有异常"
finally:
print "最后总是要执行我"
Golang
Golang中并没有try .. catch
,实际上Golang也不鼓励这种行为(Golang推荐逐一处理错误的方式),倘若你真想办倒像是捕捉异常这样的方式,你确实可以使用Golang中另类处理错误的方式(可以的话尽量避免使用这种方式):panic()
, recover()
, defer
。
你可以把panic()
当作是throw
(丢出错误),而这跟PHP的exit()
有87%像,一但你执行了panic()
你的程式就会宣告而终,但是别担心,因为程式结束的时候会呼叫defer
,所以我们接下来要在defer
停止panic()
。
关于defer
上述已经有提到了,他是一个反向执行的宣告,会在函式结束后被执行,当你呼叫了panic()
结束程式的时候,也就会开始执行defer
,所以我们要在defer
内使用recover()
让程式不再继续进行结束动作,这就像是捕捉异常。
recover()
可以看作catch
(捕捉),我们要在defer
里面用recover()
解决panic()
,如此一来程式就会回归正常而不会被结束。
// 建立一個模仿 try&catch 的函式供稍後使用
func try(fn func(), handler func(interface{})) {
// 這不會馬上被執行,但當 panic 被執行就會結束程式,結束程式就必定會呼叫 defer
defer func() {
// 透過 recover 來從 panic 狀態中恢復,並呼叫捕捉函式
if err := recover(); err != nil {
handler(err)
}
}()
// 執行可能帶有 panic 的程式
fn()
}
func foo(number int) {
if number < 10 {
panic("number is less than 10")
}
if number > 10 {
panic("number is greater than 10")
}
}
func main() {
try(func() {
foo(9)
}, func(e interface{}) {
fmt.Println(e) // 輸出:number is less than 10
})
try(func() {
foo(11)
}, func(e interface{}) {
fmt.Println(e) // 輸出:number is greater than 10
})
}
类别-Class
在Golang 中没有类别,但有所谓的「建构体(Struct)」和「接口(Interface)」,这就能够满足几乎所有的需求了,这也是为什么我认为Golang 很简洁却又很强大的原因。
让我们先用Python 建立一个类别,然后看看Golang 怎么解决这个问题。
在Golang 中没有类别,但有所谓的「建构体(Struct)」和「接口(Interface)」,这就能够满足几乎所有的需求了,这也是为什么我认为Golang 很简洁却又很强大的原因。
让我们先用Python 建立一个类别,然后看看Golang 怎么解决这个问题。
Python
# -*- coding: UTF-8 -*-
class Employee:
'所有员工的基类'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print "Total Employee %d" % Employee.empCount
def displayEmployee(self):
print "Name : ", self.name, ", Salary: ", self.salary
"创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
"创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount
Golang
虽然Golang没有类别,但是「建构体(Struct)」就十分地堪用了,首先你要知道在Golang中「类别」的成员还有方法都是在「类别」外面所定义的,这跟PHP在类别内定义的方式有所不同,在Golang中还有一点,那就是他们没有public、private、protected的种类。
// 先定義一個 Foobar 建構體,然後有個叫做 a 的字串成員
type Foobar struct {
a string
}
// 定義一個屬於 Foobar 的 test 方法
func (f *Foobar) test () {
// 接收來自 Foobar 的 a(等同於 PHP 的 `$this->a`)
fmt.Println(f.a)
}
b := &Foobar{a: "hello, world!"}
b.test() // 輸出:hello, world!
相似的操作
日期-Date
切割字串-Split
关联阵列-Associative Array
是否存在-if a: 检测列表、字典元组是否存在
指针-Pointer
套件/汇入/汇出-Package / Import / Export
「Goroutine」和「Channel」是Golang的精髓,最重要的卖点
Go 是一种非常高效的语言,高度支持并发性。同时,它也像 C++和 Java 一样快。虽然相比于 Python 和 Ruby,使用 Go 建立东西需要更多的时间,但在后续的代码优化上可以节省大量时间。