go 语言特点
脚本化的语法,容易上手。
静态类型+编译性,开发、运行效率都有保证
函数式 & 面向对象 两种编程范式,
原生支持并发编程支持,降低开发成本,维护成本,以及更好的兼容性,效率。
劣势:语法糖没有 Python 和 Ruby 多。运行效率不及C,但已赶超C++,Java。第三方库不多,就是轮子少(喜欢造轮子的可以加入golang轮子大军)。
安装
官方镜像站: https://golang.google.cn/
国内官方站点: https://go-zh.org/
Linux
http://golang.org/dl/ 下载最新Go语言二进制包
wget https://dl.google.com/go/go1.13.15.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.13.15.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version
Mac
$ brew install go
## 安装制定版本
$ brew install go@1.13
Windows
访问 官方镜像站 下载 msi
安装文件。
环境变量
安装好 go 后。运行go env
就可以得到当前 go runtime 相关的环境变量了,这些变量可以通过
go env -w GO_ENV_NAME='new Value'
或是
$ env GO_ENV_NAME='new Value'
都能影响 go env
目录结构
这里讲一下 $GOPATH
也就是 ~/go
下的目录结果:
$ ls ~/go/
bin # go 可执行文件目录
pkg # go 归档文件目录
src # go 下载的代码包源码文件,存放目录
go命令
go run
go
自带了 runtime
,通过 go run file.go
就能运行程序代码了。
参数:
-a : 强制编译相关代码,不论他们的源代码是否有改变,编译结果是否最新。
-n : 打印编译过程中所需要的运行命令,但不真正执行它们
-p n : 并行编译,n为并行数量
-v : 列出被编译的代码包的名称
-work : 显示编译时创建的的临时目录,并且不删除。
-x : 打印编译过程中所需运行的命令
go build
编译出目标文件。
类比nodejs
就是,npm build
, 类比 Java
go build main.go
编译制定的 main.go 代码文件
go build
执行命令就是将当前代码包作为代码包并编译
参数:
-a 所有涉及、被引入的代码都会被重新编译。
go install
编译并安装代码包或源文件。
golang 安装代码包动作就是将编译结果移动到 pkg/平台号编号
/归档文件。
如果是可执行文件,则移动到当前工作去的 bin
目录,或者 $GOBIN
目录.
go get
从远程代码仓库上下载、安装代码包。
远程仓科可以是:git
、mercurial(HG)
、svn
,bazaar
。
代码下载后会放到 $GOPATH/src
目录中.
-d : 只执行下载动作,不执行安装动作
-fix : 先执行`修正`动作,再进行编译和安装代码包。
-x : 展示命令执行过程
-u : 下载并更新代码包
修正: 指语法向上兼容。
go 代码关键字
break //退出循环
default //选择结构默认项(switch、select)
func //定义函数
interface //定义接口
select //channel
case //选择结构标签
chan //定义channel
const //常量
continue //跳过本次循环
defer //延迟执行内容(收尾工作)
go //并发执行
map //map类型
struct //定义结构体
else //选择结构
goto //跳转语句
package //包
switch //选择结构
fallthrough //??
if //选择结构
range //从slice、map等结构中取元素
type //定义类型
for //循环
import //导入包
return //返回
var //定义变量
标示符
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
语言特色
不要求缩进,不要求末尾加分号——;,同一行代码中有多个表达式,需要用 分号 分割。没有使用的变量,包,会导致报错。
包
每个go源文件开头必须是package开头,定义自己的包
一个目录下,只能有一个包名
一个可执行的文件必须要有 main()
函数
import 引入包
两种引入风格
import "package1"
import "package2"
import (
"package1"
pa2 "package2" // 包别名,别名为 pa2
. "fmt"
_ "mysql"
)
. "fmt"
方式引入包的化,使用fmt里面的函数就可直接使用,不用带 fmt 前缀了
如果引入的包不使用,会报错, 或者加个前缀 _ 即可,这样的下划线会把引入的包的init函数执行一下。定义的变量不用,也会报错。
包内初始化函数
定义 包内 初始化函数
func init() {
}
只导入这个包部分,并运行init函数,由于导入不全,所以在代码中就不能使用这个包了。
import _ "MyPackage"
数据类型
序号 | 类型和描述 |
---|---|
1 | 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
2 | 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 |
3 | 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 |
4 | 派生类型: 包括: (a) 指针类型(Pointer) (b) 数组类型 (c) 结构化类型(struct) (d) Channel 类型 (e) 函数类型 (f) 切片类型 (g) 接口类型(interface) (h) Map 类型 |
使用 int 时,根据当前操作系统来的,64位系统对应 int64, 32位操作系统,对应int32.
变量声明
- 变量声明: var <变量名> [变量类型]
- 变量赋值: <变量名> = <值,表达式,函数返回>
- 变量声明赋值:var <变量名> [变量类型] = <值,表达式,函数返回>
- 变量声明,类型推断,并赋值 <变量名> := <值,表达式,函数返回>
分组声明
var (
i int
foo float32
name string
)
分组批量声明、赋值
var a,b,c,d int = 1,2,3,4
a,b := 1,2
特殊变量 _
变量作用域
- 函数内定义的变量称为局部变量
- 函数外定义的变量称为全局变量
- 全局变量必须使用
var
声明,局部变量可省略
作用域可以分为以下四个类型:
- 内置作用域:不需要自己声明,所有的关键字和内置类型、函数都拥有全局作用域
- 包级作用域:必須函数外声明,在该包内的所有文件都可以访问
- 文件级作用域:不需要声明,导入即可。一个文件中通过import导入的包名,只在该文件内可用
- 局部作用域:在自己的语句块内声明,包括函数,for、if 等语句块,或自定义的 {} 语句块形成的作用域,只在自己的局部作用域内可用
语句块
语句块是由花括弧({})所包含的一系列语句。
在 Go 中还有很多的隐式语句块:
- 主语句块:包括所有源码,对应内置作用域
- 包语句块:包括该包中所有的源码(一个包可能会包括一个目录下的多个文件),对应包级作用域
- 文件语句块:包括该文件中的所有源码,对应文件级作用域
- for 、if、switch等语句本身也在它自身的隐式语句块中,对应局部作用域
类型转换
- 不存在隐式转换,必须是显示
- 类型转换必须是在两种兼容的类型之间
- <变量名称> [:]= <目标类型>( <需要转换的变量名> )
类型转换精度丢失
类型断言
断言,顾名思义就是果断的去猜测一个未知的事物。在 go 语言中,interface{} 就是这个神秘的未知类型,其断言操作就是用来判断 interface{} 的类型。
var foo interface{} = 22
f, ok := foo.(int)
if !ok {
t.Log("Guess wrong ...")
}
t.Logf("The type is : %T", f)
常量
- 显示 const idenfity [type] = value
- 隐式 const identify = value () (无类型常量)
变量类型支持: bool, int, float, string
特殊常量 iota
运算
算术运算
运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
-- | 自减 | A-- 输出结果 9 |
关系运算
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
逻辑运算
运算符 | 描述 | 实例 | ||||
---|---|---|---|---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False | ||||
\ | \ | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A \ | \ | B) 为 True | |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
位运算
运算符 | 描述 | 实例 | |||
---|---|---|---|---|---|
& | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 | (A & B) 结果为 12, 二进制为 0000 1100 | |||
\ | 按位或运算符"\ | "是双目运算符。 其功能是参与运算的两数各对应的二进位相或 | (A \ | B) 结果为 61, 二进制为 0011 1101 | |
^ | 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 | (A ^ B) 结果为 49, 二进制为 0011 0001 | |||
<< | 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 | A << 2 结果为 240 ,二进制为 1111 0000 | |||
>> | 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 | A >> 2 结果为 15 ,二进制为 0000 1111 |
赋值运算
运算符 | 描述 | 实例 | |||
---|---|---|---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C | |||
+= | 相加后再赋值 | C += A 等于 C = C + A | |||
-= | 相减后再赋值 | C -= A 等于 C = C - A | |||
*= | 相乘后再赋值 | C = A 等于 C = C A | |||
/= | 相除后再赋值 | C /= A 等于 C = C / A | |||
%= | 求余后再赋值 | C %= A 等于 C = C % A | |||
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 | |||
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 | |||
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 | |||
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 | |||
\ | = | 按位或后赋值 | C \ | = 2 等于 C = C \ | 2 |
优先级
优先级 | 运算符 | 功能 | ||
---|---|---|---|---|
9 | () [] -> . | 后缀运算 | ||
8 | ! *(指针) & ++ -- +(正号) -(负号) | 单目运算 | ||
7 | * / % + - | 算术运算,加减乘除 | ||
6 | << >> | 位运算 | ||
5 | == != < <= > >= | 逻辑运算、不等、等 | ||
4 | & \ | ^ | 按位 逻辑与、或 | |
3 | \ | \ | && | 逻辑或、与 |
2 | = += -= *= 等等 | 赋值运算 | ||
1 | , | 逗号 |
一元
、赋值
这两大运算符是 从右到左
关联,其他都是 从左到右
关联。
注意:优先级 值越大则优先级越高。为了方便理解、记忆,我对没有严格按照优先级制表,只是做了个大概!!
更详细的
代码控制语句
if, else, else if
var number int = 37
if number += 4; 10 > number {
fmt.Print("less than 10:", number)
} else if 10 < number {
number -= 2
fmt.Print("greater 10:", number)
} else {
}
switch, select
package main
import (
"fmt"
"math/rand"
)
func main() {
ia := []interface{}{byte(6), 'a', uint(10), int32(-4), "CC"}
v := ia[rand.Intn(4)]
// 值 switch
switch v {
case 'a' :
fmt.Println("char: ", v)
case 10 :
fmt.Println("uint: ", v)
case -4 :
fmt.Println("int: ", v)
case 0.1 :
fallthrough
caes "0.1"
fmt.Println("float: ", v)
default :
fmt.Println("byte: ", v)
}
// 变量类型 switch
switch interface{}(v).(type) {
case string :
fmt.Printf("Case A.")
case byte :
fmt.Printf("Case B.")
case int :
fmt.Printf("Case B.")
default:
fmt.Println("Unknown!")
}
}
注意,go语言和其他语言不同的时,每个case代码末尾会自动加上break
操作, 如果你需要使用 fallthrough
来抵消默认的 break
select 用于管道
for
是的 golang
的 for
集 for
,foreach
,for in
,while
于一体。
do while
表示:golang你这么绕,不优雅
package main
import (
"fmt"
"time"
)
func main() {
map1 := map[int]string{1: "Golang", 2: "Java", 3: "Python", 4: "C"}
n := 1
for { // 省略则默认是true
if n > 3 {
break;
}
fmt.Println("for true map item: ", map1[n])
time.Sleep(1)
n++
}
for i := 1; i < 4; i++ {
fmt.Println("for i map item: ", map1[i])
}
for k,v := range map1 {
fmt.Print(k, ":", v)
}
}
goto, break, continue
goto 是跳过代码块
package main
import (
"fmt"
"time"
)
func main() {
code:
fmt.Println("do some thing~")
time.Sleep(1)
goto code
}
break 跳出并结束循环
continue 跳过当前循环
虽然不能和PHP
那样break 2
跳出多层, 单只要有goto就能干很多事了。golang给 循环 就分配了一个 for,语句跳转语句却整了那么多花样
复合数据
内建方法 make & new
内建方法就是不需要引入包就能用的
make 可以创建 slice、map、chan,返回指针类型
- slice 是可变长的数组
- map 是key-map 数据数组
- chan 是go独有的 管道
一股c编程风格扑面而来, char ptr = (char )malloc(sizeof(char) * 5);
内建方法 new
内存置0,返回传入类型的指针地址
package main
import fmt
import reflect
func main() {
mSlice := make([]string, 3)
mSlice[0] = "dog"
mSlice[1] = "cat"
mSlice[2] = "pig"
fmt.Println("animals: ", mSlice)
mMap := make(map[int]string)
mMap[10] = "dog"
mMap['2'] = "cat"
fmt.Println(reflect.TypeOf(mMap))
fmt.Println("animals :: ", mMap)
nMap := new(map[int]string)
fmt.Println(reflect.TypeOf(nMap))
}
append copy delete
slice可以使用copy,append 函数
delete 是专门用来删除 map
- append(src, ele) 追加元素
- copy(dst, src) 把src元素赋值到dst上,
- delete() 删除元素
例子:
package main
import "fmt"
func main() {
mSlice := make([]string, 3)
mSlice[0] = "dog"
mSlice[1] = "cat"
mSlice[2] = "pig"
fmt.Println("animals: ", mSlice)
// append(mSlice, "id-3") // 这样写会导致报错: append(mSlice, "id-3") evaluated but not used
mSlice = append(mSlice, "id-3")
fmt.Println("animals update:", mSlice)
fmt.Println("animals len :", len(mSlice))
fmt.Println("animals cap:", cap(mSlice))
// newSlice := make([]string) // 这样写导致报错:missing len argument to make([]string)
// newSlice := make([]string, 2) // 这样写会导致数据丢失2个,不会自动扩容
newSlice := make([]string, 3) // 不要多次定义初始化:no new variables on left side of :=
copy(mSlice, newSlice) // 这样反向copy,会导致前面的几个数组元素被置为空
// copy(newSlice, mSlice)
fmt.Println("animals dst:", mSlice)
fmt.Println("animals copy:", newSlice)
delete(mMap, 50)
fmt.Println(mMap)
}
panic & recover
异常处理
panic() 抛出异常
recover() 获取异常
报错会导致程序代码中断,不会再执行后续操作
例子:
package main
import "fmt"
import "errors"
func panicFunc() {
defer func() {
// recover()
message := recover() // 声明了message 变量就需要使用哦,不然报错
fmt.Println("panice msg: ", message)
switch message.(type) {
case string:
case error:
fmt.Println("panice error msg: ", message)
default:
}
}()
// panic("报错啦")
panic(errors.New("I am error."))
}
func main() {
panicFunc()
}
len & cap & close
len可以计算 string, array, slice, map, chan
cap 可以计算 slice, map, chan
len()
获取数组长度cap()
获取占用空间分配close()
用于关闭管道——chan
当声明一个数组时,go会预先分配一部分空间给当前数组,获取实际空间占用大小,使用cap()
不用像PHP那样,strlen(), count(), length 傻傻分不清楚了。
例子:
package main
import "fmt"
func main() {
mSlice := make([]string, 3)
mSlice[0] = "dog"
mSlice[1] = "cat"
mSlice[2] = "pig"
fmt.Println("animals: ", mSlice)
fmt.Println("animals update:", mSlice)
fmt.Println("animals len :", len(mSlice))
fmt.Println("animals cap:", cap(mSlice))
mChan := make(chan int, 1)
close(mChan)
mChan <- 1 // 会导致报错: panic: send on closed channel
}
defer
定一个当前方法关闭时,运行的代码, 压栈设计,先声明的后执行。
结构体
package main
import "fmt"
type Dog struct {
ID int
Name string
Age int32
}
func main() {
var dog Dog
dog.ID = 1
dog.Name = "haha"
dog.Age = 3
fmt.Println("print Dog Struct", dog)
dog2 := Dog{ID:2, Name:"san", Age:4}
fmt.Println("print Dog 2 Struct", dog2)
dog3 := new(Dog)
dog3.ID = 3
dog3.Name = "Tom"
dog3.Age = 5
fmt.Println("print Dog 3 Struct", dog)
}
输出
print Dog Struct {1 haha 3}
print Dog 2 Struct {2 san 4}
print Dog 3 Struct &{3 Tom 5}
属性 & 函数
接口
/* define an interface */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
...
method_namen [return_type]
}
/* define a struct */
type struct_name struct {
/* variables */
}
/* implement interface methods*/
func (struct_name_variable struct_name) method_name1() [return_type] {
/* method implementation */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* method implementation */
}
并发
指针
json
需要引入包 encoding/json
, 两个函数分别是 json.Marshal()
, json.Unmarshal()
.
注意,最后一个是英文字母小写的L
,不是1
json 序列化
package main
import "fmt"
import "encoding/json"
type ServerInfo struct {
SerName string
SerIp string
SerPort uint16
}
func main() {
server := new(ServerInfo)
server.SerName = "http-nginx"
server.SerIp = "127.0.0.1"
server.SerPort = 8080
re,err := json.Marshal(server)
if nil != err {
fmt.Println("error: ", err.Error())
} else {
fmt.Println("struct json bytes: ", re)
fmt.Println("struct json string: ", string(re))
}
mServer := make(map[string]interface{})
mServer["serverName"] = "apache2-http"
mServer["serIp"] = "192.168.30.133"
mServer["serPort"] = "3033"
mRe,err := json.Marshal(mServer)
if nil != err {
fmt.Println("error: ", err.Error())
} else {
fmt.Println("map json string: ", string(mRe))
}
}
输出
struct json bytes: [123 34 83 101 114 78 97 109 101 34 58 34 104 116 116 112 45 110 103 105 110 120 34 44 34 83 101 114 73 112 34 58 34 49 48 46 49 48 48 46 49 55 46 50 55 58 51 48 48 48 49 34 44 34 83 101 114 80 111 114 116 34 58 56 48 56 48 125]
struct json string: {"SerName":"http-nginx","SerIp":"10.100.17.27:30001","SerPort":8080}
map json string: {"serIp":"192.168.30.133","serPort":"3033","serverName":"apache2-http"}
ps: 我也不知道 10.100.17.27:30001
是怎么回事
json 反序列化
可以使用 tag 来做 mapping,
package main
import "fmt"
import "encoding/json"
type ServerInfo struct {
SerName string `json:"name"`
SerIp string `json:"ip"`
SerPort uint16 `json:"port"`
}
func main() {
// jsonStr := "{\"SerName\":\"http-nginx\",\"SerIp\":\"10.100.17.27:30001\",\"SerPort\":8080}" \\ 双引号注意转义
jsonStr := "{\"name\":\"http-nginx\",\"ip\":\"10.100.17.27:30001\",\"port\":8080}"
sServer := new(ServerInfo)
jsonBytes := []byte(jsonStr)
uerr := json.Unmarshal(jsonBytes, &sServer)
if nil != uerr {
fmt.Println("error: ", err.Error())
} else {
fmt.Println("uns struct: ", sServer)
}
jsonStr3 := `{"serIp":"192.168.30.133","serPort":"3033","serverName":"apache2-http"}` \\ 使用键盘1旁边的 ` 符号包裹双引号就不用转义了
uSer := make(map[string]interface{})
uErr := json.Unmarshal([]byte(jsonStr3), &uSer)
if nil != uErr {
fmt.Println("error: ", uErr.Error())
} else {
fmt.Println("unmar map: ", uSer)
}
}
输出
uns struct: &{http-nginx 10.100.17.27:30001 8080}
unmar map: map[serIp:192.168.30.133 serPort:3033 serverName:apache2-http]
tag
tag
这个东东把,就是json的别名,感觉这个功能是go的特色,与encoding/json
包紧密结合。
为什么会有这个东西,我估计是这个和 go命名规则 有关,go命名规则,要求public的变量开头要大写,小写开头的变量是private的,所以,json中的变量就会影响一个接口体变量的访问权限,为了不像java那样复杂,提供了方便的tag功能。
package main
import "fmt"
import "encoding/json"
type ServerInfo struct {
SerName string `json:"name"`
SerIp string `json:"ip"`
SerPort uint16 `json:"port"`
}
func main() {
server := new(ServerInfo)
server.SerName = "http-nginx"
server.SerIp = "127.0.0.1"
server.SerPort = 8080
re,err := json.Marshal(server)
if nil != err {
fmt.Println("error: ", err.Error())
} else {
fmt.Println("struct json string: ", string(re))
}
}
输出
struct json string: {"name":"http-nginx","ip":"10.100.17.27:30001","port":8080}
map json strin
go 特色语法
_
- _ 变量
这就好比是Linux 里的 /dev/null
, 由于go语言要求声明的变量必须被使用,返回的变量必须被接收,那么真有个变量没用但必须要接受怎么办呢,就把返回的参数给他。例如:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for _, v := range pow {
fmt.Printf("value is %d\n", v)
}
}
这里我们只要值,不要key的信息,返回的key不能不收不是,但我也不像把它输出出来,就让 _
来接收好了。
- _ 包
引入包, 并不直接使用这个包,运行时执行一次它的 init()
函数,
import (
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。