golang快速入门

此文适合有一定语言基础的读者

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语言编程》

优点

  1. 够简单
  2. 语言级别支持并发编程
  3. 错误处理新规范
  4. 自动垃圾回收
  5. 匿名函数与闭包
  6. 类型与接口
  7. 反射
  8. 语言交互(主要与c)
  9. 丰富的内置类型

缺点

  1. 错误处理比较蛋疼,需要写很多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"
)
  1. mr 是”math/rand”的别名,可用来避免重名/长名
  2. _ 表示初始化包,但并不使用

也可以

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            // 使用'_'屏蔽不需要的值
  1. 注意go变量名放在类型之前,这个与c,java相反,对同类型变量可以只留下最后一个类型声名
  2. 注意不能使用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, 线上执行环境,可演示简单程序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 能力模型 选择题 [primary] 下面属于关键字的是()A. funcB. defC. structD. cl...
    _张晓龙_阅读 24,800评论 14 224
  • fmt格式化字符串 格式:%[旗标][宽度][.精度][arg索引]动词旗标有以下几种:+: 对于数值类型总是输出...
    皮皮v阅读 1,089评论 0 3
  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,399评论 1 46
  • 全球性的慈善活动家。 不断追逐、积累金钱,不停更新换代所拥有的一切,是另一种形式的饥饿。我们的三个有毒迷思:资源紧...
    馨晴的能量小站阅读 253评论 0 0
  • 好的爱情电影,从来不缺少的,就是情话和泪点。但是,好的爱情却与情话和泪点无关,就像是一杯水,更多的情愫在其中,却还...
    missfrankxi阅读 343评论 0 0