此文适合有一定语言基础的读者
go简介
语言哲学
C语言是纯过程式的,这和它产生的历史背景有关。Java语言则是激进的面向对象主义推崇者,典型表现是它不能容忍体系里存在孤立的函数。而Go语言没有去否认任何一方,而是用批判吸收的眼光,将所有编程思想做了一次梳理,融合众家之长,但时刻警惕特性复杂化,极力维持语言特性的简洁,力求小而精
Go语言反对函数和操作符重载(overload),而C++、Java和C#都允许出现同名函数或操作符,只要它们的参数列表不同。
其次,Go语言支持类、类成员方法、类的组合,但反对继承,反对虚函数(virtual function)和虚函数重载。确切地说,Go也提供了继承,只不过是采用了组合的文法来提供
type Foo struct {
Base // struct类型
...
}
func (foo *Foo) Bar() {
...
}
Go语言也放弃了构造函数(constructor)和析构函数(destructor)
在放弃了大量的OOP特性后,Go语言送上了一份非常棒的礼物:接口(interface)。Go语言中的接口与其他语言最大的一点区别是它的非侵入性。
Go语言实现类的时候无需从接口派生,只要实现的接口定义的方法就表示实现了该接口。
以上摘自许式伟的《Go语言编程》
优点
- 够简单
- 语言级别支持并发编程
- 错误处理新规范
- 自动垃圾回收
- 匿名函数与闭包
- 类型与接口
- 反射
- 语言交互(主要与c)
- 丰富的内置类型
缺点
- 错误处理比较蛋疼,需要写很多err判断
一、hello golang!
package main
import "fmt"
func main() {
fmt.Println("hello golang!")
}
使用任意编辑器编写保存,命令行中:go run hello.go即可运行
可以在https://play.golang.org 上直接运行,不需要安装环境
关键字
�关键字 | �作用 |
---|---|
package | 代码所属包 |
import | 导入依赖包,不能导入不使用的包 |
main | 主函数入口,无参数无返回值,命令行参数保存在os.Args中 |
func | 函数声明 |
go | 开启协程(并发核心) |
map | 字典类型, map[string]bool |
delete | 专用来删除字典中的元素 |
chan | 通道,协程通信核心 |
select | 同时读取多个chan的信息 |
close | 用来关闭通道 |
make | 用来创建chan或map |
type | 类型定义,定义任意需要的类型 |
struct | C中的结构体,但可以定义方法以实现类功能 |
interface | 接口类型,用来定义接口 |
new | 新建对象, 并获得它的指针 |
range | 类似python的range,配合for遍历列表、数组或map的元素 |
defer | 自动关闭资源,退出代码体(一般是函数)时执行 |
error | error接口,只要实现Error()string方法就是一个error接口 |
panic | 抛出异常,如果不用recover捕获则会终止程序 |
recover | 捕获panic异常,防止程序终止,与recover结合 |
数值定义 | --- |
const | 常量声明 |
var | 变量声明 |
数值类型 | --- |
bool | 布尔 |
string | 字符 |
int,int8,int16,int32,int64 | int长度与平台相关 |
uint,uint8,uint16,uint32,uint64 | uint长度与平台相关 |
uintptr | 同指针,32位平台为4字节,64位八字节 |
byte | 等价于uint8 |
rune | 等价于uint32,单个unicode字符 |
float32,float64 | |
complex64,complex128 | 复数类型, value = 3.2+12i |
操作符
赋值:=,:=
数值运算:+,-,*,/,%
比较运算:>,<,==,>=,<=,!=
位运行:>>,<<,,&,|与x(取反)
�特殊操作符 | �作用 |
---|---|
:= | 无需指定类型即可赋值,i, j := true, "hello"** |
_ | 可以赋任意值的空对象,_ = "string"** |
二、语法快速过滤
与其他语言比较
注释:Go程序的代码注释与C++保持一致,支付块注释与行注释
/*
块注释
*/
// 行注释
无java/c中的结束分号:;
package
类似java的包,一般与目录同名,只有运行目录时包名必须为main,一个目录不能出现多个包名
import
import (
"fmt"
mr "math/rand"
_ "encoding/json"
)
- mr 是”math/rand”的别名,可用来避免重名/长名
- _ 表示初始化包,但并不使用
也可以
import "fmt"
import mr "math/rand"
import _ "encoding/json"
明显上一种能减少不少代码量
注意:只有大写字母开头的方法或对象可以被导出使用,类似公有私有概念
const
const Pi = 3.14 // 默认类型为float64
const x,y int = 1,2 // 指定类型
// 也可以集中定义
const(
Big = 1 << 100
Small = Big >> 99
)
// 如果为同样的值,可省略不写
const (
a = 1
b // b = 1
c // c = 1
d // d = 1
)
// 可以使用iota加1,iota从0开始
const (
a = 1 + iota
b // b = 2
c // c = 3
d // d = 4
)
// 遇到const关键字置0
const (
e = iota // e = 0
)
var
var i int
var x,y,z int // 声明多个变量
var x,y,z int = 1,2,3 // 声明多个变量并初始化,int可以省略
var i,j = true, "hello" // 多变量类型同时赋值
var( // 集中声明,与导包方式一样
v1 int
v2 string
)
v3 := "no var init" // 只能在函数体内使用此方法
x,y = y,x // 多重赋值
_,i,_ = x,y,z // 使用'_'屏蔽不需要的值
- 注意go变量名放在类型之前,这个与c,java相反,对同类型变量可以只留下最后一个类型声名
- 注意不能使用
var i bool,j string = true, "hello"
进行赋值
map
var myMap map[string]string // 字典声名,声明的map是空值,需要用make()创建
myMap = make(map[string]string)
func
func add(x int,y int) int{ // 这里也可以写成add(x,y int)
return x + y
}
// 函数内声明的变量必须被使用,否则会报错
func nouse(){
var no string
// 变量未使用报错
// 可以使用'_ = no'来避免报错
}
// 可以是空函数体
func empty(){
}
// 可变参数
func vary(args... int) {
fmt.Println(args)
}
// vary() -> [] 可以为空
// vary(1) -> [1]
// vary(1,2,3) -> [1 2 3]
// 混合使用固定参数与可变参数,可变参数放在最后,不能与固定参数共用参数类型
func fix_vary(fix int, args...int) { // fix可以不使用
fmt.Println(args)
}
// 多值返回, 这个函数可以直接用多重赋值实现x,y = y,x
func swap(x, y int) (int,int){
return y, x
}
// 返回值指定变量名
func split(sum int) (x,y int) {
x = sum / 5
y = sum % 5
return // 当指定变量名时,返回可以略去不写
}
// 匿名函数
func(x,y int)int{
return x+y
}
// 匿名函数直接执行, 直接在函数定义后面加上参数即可
func(x,y int)int{
return x+y
}(2,3) // 传入参数2,3运行
// 匿名函数赋值
f := func(x,y int)int{
return x+y
}
流程控制中的条件表达式,不需要小括号
if else, goto
// 普通用法同其他语言
// 特殊用法, 可以初始化变量
if i:=j;i>0{
// ... do sth about i
}
switch,case,select
switch不需要用break来退出一个case,默认退出
switch i {
case 0:
// ...
case 1:
// ...
// ...
default:
// ...
}
// switch后面的表达式不是必须的
switch{
case char == 'a':
// ...
case char == 'b':
// ...
}
// 单个case可出现多个可选结果
switch 1 {
case 1,2,3:
// ...
default:
// ...
}
// 可以初始化变量,可以不做任何处理
switch os:=runtime.GOOS;os{
case "darwin":
case "linux":
}
for ... range()
// 常规使用同c
// 死循环
for{
// do something
}
// 多重赋值, 实现反序
a := []int{1, 5, 3, 4, 2, 6}
for i, j := 0, len(a) - 1; i < j; i, j = i + 1, j - 1 {
a[i], a[j] = a[j], a[i]
}
fmt.Println(a) // [6 2 4 3 5 1]
// 遍历列表
for index,value := range(alist){
// ...
}
// 遍历字典
for key,value := range(amap){
// ...
}
defer
f,err := os.Open(filename)
if err != nil{
log.Println("")
return
}
defer f.Close() // 程序处理完后续代码退出或异常退出时执行
// ... 其他处理
�三、struct,另类的类
给类型加上方法便是对象,用type定义自己的类型,然后加上方法
type Myint int
func (m Myint)Less(b Myint) bool{
return m < b
}
var a Myint = 1
if a.Less(2){ // 对天显示的类型2会自动处理成相应类型
// do something
}
因为一般对象有不少属性,所以一般用struct来定义对象
type Person struct{
Name string
Age int
}
func (p Person)GetName()string{
return p.Name
}
// 需要修改对象成员时用指针
func (p *Person)SetName(name string){
p.Name = name
}
对象继承,c中的struct匿名组合
type Base struct{
Name string
}
func (b *Base) Name()string{...}
type Foo struct{
Base //匿名实现继承,Base中的方法,Foo可以直接使用
...
}
var foo Foo
foo.Name() // Name()为Base中的方法
可见性,没有private,protected,public,只有大写开头的成员才能导出包外被使用
�四、接口
用interface定义,接口不需要被继承,任何对象只要实现了接口中的方法就实现了此接口
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
// 以上实现了四个接口, 下面定义一个File对象
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error
var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)
// 因为File实现了所有接口的方法,所有File可以赋值给四个接口变量
接口查询,查看对象是否实现接口 (使用类型判断方法)
var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
// ...
}
接口组合,可以像struct一样组合成一个新接口
type ReadWriter interface {
IReader
IWriter
}
// 完全等同于下面写法
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
空接口interface{},可以接受任何类型
{
var v1 interface{} = 1 // int interface{}
var v2 interface{} = "abc" // string interface{}
var v3 interface{} = &v2 // *interface{} interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
}
五、类型与转换
类型查询,使用.(target type)来判断类型
var v1 interface{} = ...
switch v := v1.(type) {
case int:
// v为 int
case string:
// v为 string
//...
}
tes := make(map[string]interface{})
tes["a"] = "abc"
tes["b"] = 123
value, ok := tes["a"].(string) // "abc", true
value2,ok2 := tes["a"].(int) // "", false
类型强转,只有兼容的对象可以进行转换
var var1 int = 7
var2 := float64(var1)
var3 := int64(var1)
var4 := new(int32) // var4为指针变量
var5 := (*int32)(var4)
六、并发
关键字go简单暴力实现并发,不同于线程/进程,更轻量级的协程
package main
import "fmt"
func Add(x, y int) {z := x + y
fmt.Println(z) }
func main() {
for i := 0; i < 10; i++ {
go Add(i, i) // 并发计算
}
}
// 打印顺序如下,若要console能显示,需要加time.Sleep(),否则没来得及打印就退出了
// 4
// 6
// 8
// 10
// 2
// 14
// 12
// 16
// 0
// 18
并发通信
// c思路go实现
package main
import "fmt"
import "sync"
import "runtime"
var counter int = 0
func Count(lock *sync.Mutex) {
lock.Lock()
counter++
fmt.Println(counter)
lock.Unlock()
}
func main() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock)
}
for {
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()
if c >= 10 {
break
}
}
}
// go思路实现
package main
import "fmt"
func Count(ch chan int) {
ch <- 1 // 放入1
fmt.Println("Counting")
}
func main() {
chs := make([]chan int 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range(chs) {
<-ch //这里是读出数据,如果数据不读取会阻塞其他协程写入数据
}
}
channel,chan与map类似,没有make时是不能使用的,所以声明与make最好一起,
var ch1 chan int
var ch2 chan<- float64 // 单向channel, 只用于写float64数据
var ch3 <-chan int // 单向channel, 只用于读取int数据
ch := make(chan int) // 不带缓冲的channel,如果ch中没数据则读取会被阻塞,如果ch中有数据则写入会被阻塞
go func(){
ch <- 1 // 写入数据
}
i := <-ch // 取数据并使用
<- ch // 取数据但不使用, 这里将死锁
x, ok = <- ch // ok表示有没有取到数据,与类型判断与map取值类似
// 带缓冲的channel
chbuf := make(chan int,100) // 没有数据时阻塞读取,数据塞满100个时阻塞写入
单向通道一般用于函数参数定义
func single_r(ch <-chan int){
// do sth, 只能读取数据
}
func single_w(ch chan<- int){
// do sth, 只能写入数据
}
var ch chan int // 定义一个双向通道,在对应的函数内只能读或写
single_r(ch)
singe_w(ch)
select,与switch类似,但是是专用多通道读取
LOOP:
for { // 循环读取
select {
case <-chan1:
// 如果chan1成功读到数据,则进行case处理语句
case chan2 <- 1:
// 如果chan2成功读到数据,则进行case处理语句
break LOOP // @LOOP, 退出for循环
default:
// �如果上面都没成功,则进入default处理流程
// 一般用 <-time.After(duration)替代default做读写默认超时处理
}
}
// 读取某个chan中所有数据
for i := range(ch){
// do
}
因为select也有break语句,所以需要使用类似goto的标记来标识退出地方
通道经常使用的特殊数据:
chan <- struct{}{}
语义为将一个空数据传递给channel.因为struct{}{}占用的内存非常小,而且我们对数据内容也不关心,通常用来做信号量来处理
�七、C语言交互
将c代码用/*,*/包含起来,紧挨着写import "C"即可, 不需要特别编译处理即可直接执行
package main
/*
#include <stdio.h>
void hello() {
printf("Hello, Cgo! -- From C world.\n");
}
*/
import "C"
func Hello() {
C.hello()
}
func main() {
Hello()
}
// 运行结果:Hello, Cgo! -- From C world.
需要传入参数到C函数时,需要用C的类型转换后再传进去
C.char()
C.schar()(signed char)
C.uchar()(unsigned char)
C.short()
C.ushort()(unsigned short)
C.int()
C.uint()(unsigned int)
C.long()
C.ulong()(unsigned long)
C.longlong()(long long)
C.ulonglong()(unsigned long long)
C.float()
C.double()
八、异常处理
** defer, panic(), recover()异常处理**,不再有烦琐的try...exception
当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,然后返回到调用函数,逐层向上执行panic,直到所属的goroutine中所有正在执行的函数被终止
recover()函数用来终止panic流程,放在defer关键词后面的函数中执行
defer func(){
if err := recover(); err !=nil{
// 处理错误
}
}
// ...
panic("something error") // 这个panic将被上面的defer捕获,阻止程序退出
九、学习资源
https://gobyexample.com, 这里有大量使用常规使用案例
https://play.golang.org, 线上执行环境,可演示简单程序