go语言快速入门教程

       go快速入门指南

                                                                                   by 小强,2019-06-13html

      go语言是目前很是火热的语言,普遍应用于服务器端,云计算,kubernetes容器编排等领域。它是一种开源的编译型程序设计语言,支持并发、垃圾回收机制以提高应用程序性能。它既具备像c这种静态编译型语言的高性能,又具有像python这种动态语言的高效性。不少go程序员都是从C++,Java等面向对象语言由于工做的须要转过来的,所以没有必要从0开始学习go,当初本身的想法是找一篇半小时入门go的博客,貌似没有相似的好文章=_=。搜到的都是一些从小白入手的臃肿教程,学习起来太慢!!!so,打算写这篇go语言快速入门的指南。python

       本文写做思路重点在于和C++语言的不一样之处入手,强调go的特性,注重快速入门,所以不介绍一些很是基础的知识,很是基础的一些知识能够去看go语言圣经。关于go环境的安装配置以及vscode编辑器的配置在以前的博客已经介绍,请移步。本文总体组织结构以下:git

  1. go程序开发的通常结构
  2. 基本数据类型
  3. 变量的声明和赋值
  4. 运算符和指针
  5. 判断语句if和循环语句for
  6. 数组、切片、map
  7. 函数function
  8. 方法、接口、反射
  9. 并发

 1.go程序开发的通常结构


    在学习任何一门语言的时候,首先都是给出hello world的示例,所以本文也难免俗,看看第一个go语言程序:程序员

/*1.1 template.go*/
//
当前程序的包名 package main //导入其余的包 import ( "fmt" )//由main函数做为函数入口 func main () { fmt.Println("Hello World!") }

  和python语言很像,go程序都必须包含在一个包package中,go程序通常由三部分组成:包声明部分、第三方包导入部分和函数声明部分。go语言使用关键字package声明要建立的包;使用import导入第三方包;使用关键字func声明要建立的函数。编程

       按照惯例,处于同一个文件里的代码文件,必须使用同一个包名,包和文件夹的名字相同。Go编译器不容许声明导入某个包却不使用。使用下划线可让编译器接收这类导入,而且调用对应包内的全部代码文件中定义的init函数。 init函数会在main函数执行前执行。数组

1.1 编写go程序步骤

  初学者按照如下步骤编写go程序:缓存

  1)在工做目录(好比D:\go\development)的src文件夹中建立源文件helloworld.go;.服务器

  2)直接将helloworld.go拖入vscode进行编辑;闭包

  3)在vscode的终端输入go run helloworld.go,程序就会输出hello world!并发

1.2 go的语法要点

  • go语言语句结束不使用分号,直接另起一行便可
  • go语言规定,函数、控制结构(if,for,switch)等的左大括号“{”必须和函数声明或控制结构放在同一行
  • 可见性规则:go语言使用大小写来决定常量、变量、类型、接口、结构或函数是否能够被外部包所调用。  
    • 函数名首字母小写,即为private。
    • 函数名首字母大写,即为public。

2 .基本数据类型


  

  • 布尔型bool长度为1字节,取值范围true,false,不一样点:注意不能使用数字0/1来表明true或者false
  • 整型:int/uint,根据运行平台多是32或者64
    • 8为整型:int8/uint8,长度是1字节,取值范围:-128~127/0-255
    • 计算方法,2^8 / 2给负数部分,剩下分一个给0,最后的部分给整数部分。
    • int16/uint16,int32/uint32,int64/uint64
  • 浮点型:float32/float64
    • 长度:4/8字节,小数位,精确到7/15小数位
    • 注意go没有double类型。
  • 复数:complex64/complex128
    • 长度:8/16字节
  • 足够保存指针的32为或64为整数型:uintptr
  • 其余值类型:array,struct,string
  • 引用类型:slice(切片,特有类型),map(哈希表),chan(通道)
  • 接口类型:interface
  • 函数类型:func

类型的零值:就是默认值,int默认是0,bool默认是false,字符串默认是空字符串。
类型别名方法格式:

//type 别名 基本类型
type byte int8

 

 

3.变量的声明和赋值


  •  全局变量不能省略var关键字,函数内的变量声明能够省略。 go语言中使用关键字func声明函数,关键字后面跟函数名、参数名以及返回值。

  • 全局变量的声明可使用var()的方式进行简写
  • 全局变量的声明不能够省略var,可是可以使用并行方式
  • 全部变量均可以使用类型推断
  • 局部变量不可使用var()的方式简写,只能使用并行方式。

3.1 变量声明

  go语言使用关键字var来声明变量,声明的通常格式以下所示:

var <variableName> [varibleType]
var count int

 

  在声明变量的同时可使用=给变量赋初值:

var count int = 10

  其中变量类型int也能够省略,编译器会依据赋的初值自动推断变量类型:

var count = 10

  在声明变量的同时还容许省略掉关键字“var”,使用":"取代。

count := 10

 

3.2 常量的声明

  常量的声明格式以下所示:

const <constName> [constType] = <赋值表达式>

 

  • 常量的值在编译时就已经肯定
  • 常量的定义格式与变量基本相同
  • 等号右侧必须是常量或者常量表达式
  • 常量表达式中的函数必须是内置函数
  • 在定义常量组是,若是不提供初始值,则表示将使用上行的表达式
  • 使用相同的表达式不表明具备相同的值
  • iota是常量的计数器,从0开始,组中每定义一个常量自动递增1
  • 经过初始化规则与iota能够达到枚举的效果
  • 每遇到一个const关键字,iota就会重置为0
  • 注意常量的定义必须是大写字母。可是若是是大写字母的话,就会变成public变量,为了避免被包外部使用,通常在前面加_或者c。
const a = 1
const (
    b, c = 2,3
)
const d,f = 4,5
const (
    a = iota  //0
    b = iota  //1
)

 

4.运算符和指针


 4.1 运算符

    go的运算符均是从左至右结合。
    优先级(从高到底)

  • ^ !(一元运算符)
  • / % << >> & &^(二元运算符)
  • == != < <= >= >
  • <- (专门用于channel)
  • && (好处是运行第一个不知足要求就不在执行接下来的表达式)
  • ||

    其中位运算符介绍:

    实际例子:

6: 0110
11:1011
-------------
&: 0010
|: 1111
^: 1101
&^:0100

 

  • & 与:都是1的时候结果才能是1
  • | 或:只要有一个是1就是1
  • ^ 两个只能有一个是1才能是1
  • &^第二个计算数是1的时候将第一个计算数对应位置为0,若是第二个计算数该位是0的话,对应第一个计算数的位不变。

4.2 指针

    Go虽然保留了指针,但不一样点在于go不支持指针运算以及->运算符,而直接采用.选择符来操做指针目标对象的成员。

  • 操做符&去变量地址,使用*经过指针间接访问目标对象
  • 默认值为nil而非NULL
  • 递增递减语句
    在go当中,++ 与--是做为语句而不是做为表达式。必须单独做为一行,只能A++这种形式。
A++//只能做为单独的语句放在一行,且只能++放在右边
x, y := 1, 2
var p = [2]*int{&x, &y}
fmt.Println(*p[0])

var arr = [2]int{x, y}
pf := &arr
fmt.Println(*pf)

    其实就是将[]*int当作一个类型,后面的{}就是初始化操做。

5.判断语句if和循环语句for 


 5.1 判断语句if

  • 条件表达式没有括号
  • 支持一个初始化表达式(能够是并行方式)
  • 左大括号必须和条件语句或else在同一行
  • 支持单行模式
  • 初始化语句中的变量为block级别,同时隐藏外部同名变量
if a > 1 {
    fmt.Println(a)
}
if b :=1;b > 1 { }

注意else必须和if的右括号}在同一行才行,否则出错。

if a {
    
}else {}

if后面定义的变量,属于if语句的局部变量,只能在对应的if-else中使用,不能在外部使用。之间经过;分割语句。

5.2 循环语句for

  • go只有for一个循环语句关键字,但支持3种形式
  • 初始化和步进表达式能够是多个值
  • 条件语句每次循环都会被从新检查,所以不建议在条件语句中使用函数,尽可能提早计算好条件并以变量或常量代替。
  • 左大括号必须和条件语句在同一行
1. for init; condition; post {} // 和c语言的for同样
2. for condition {} //while
3. for {}  //for(;;)

init: 通常为赋值表达式,给控制变量赋初值;必定在这里赋初值,否则出错
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 通常为赋值表达式,给控制变量增量或减量。

for语句执行过程以下:
1)先对表达式1赋初值;
2)判别赋值表达式init是否知足给定条件,若其值为真,知足循环条件,则执行循环体内语句,而后执行post,进入第二次循环,再判别condition;不然判断condition的值为假,不知足条件,就终止for循环,执行循环体外语句。

  • for 循环的 range 格式能够对 slice、map、数组、字符串等进行迭代循环。格式以下:
for key, value := range oldMap {
    newMap[key] = value
}

关键字range会返回两个值,第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。而不是返回对该元素的引用。

  • switch语句
    switch语句默认状况下case最后自带break语句,匹配成功后就不会执行其余case,若是咱们须要执行后面的case,可使用fallthrough。
    其中var1能够是任何类型,val1和val2能够是同类型的任意值,类型不被局限于常量或证书,但必须是相同的类型,或者最终结果为相同类型的表达式。能够同时测试多个可能符合条件的值,使用逗号分隔它们。例如 case val1,val2,val3。
switch var1 {
   case val1:
       ...
   case val2:
       ...
   default:
       ...
}

跳转语句goto,break,continue

  • 三个语法均可以配合标签使用。
  • 标签名区分大小写,若不适用会形成编译错误。
  • Break与continue配合标签可用于多层循环的跳出。
  • Goto是调整执行位置,与其余2个语句配合标签的结果并不相同。 标签值是包含接下来的一个语句,continue是退出这个标签的值。

6 .数组、切片、map


 6.1 数组

  定义数组的格式: var <varName> [n]<type> ,n >= 0(表示数组的元素个数)。

var a [2]int
var b [1]int

  记住ab是不一样的类型,不能直接赋值,元素个数也是数组类型的一种。须要使用循环语句进行操做。 也能够不指定数组元素的个数。

a := [...]int{1,2,3} //元素个数为3个
a := [...]int{0:1,1:2}//位置0赋值为1,位置1赋值为2
a := new([10]int)
  • 数组长度也是类型的一部分,所以具备不一样长度的数组为不一样类型。
  • 注意区分指向数组的指针和指针数组
  • 数组在go中为值类型,不是引用类型,会所有拷贝值
  • 数组之间可使用==或!=进行比较,但不可使用<或>
  • 可使用new来建立数组,此方法返回一个指向数组的指针
  • go支持多维数组。

  须要注意的是全部值类型变量在赋值或做为参数传递的时候将产生一次复制操做。若是将数组做为函数的参数类型,则在函数调用是将该参数将发生数据复制,函数体内没法修改数组的内容,由于函数体内操做是变量的一个副本。

  多维数组的声明以下所示,其中第一维度行的数量是能够省略的,使用...代替。

arr := [2][3]int{
    {1, 2, 3},
    {2, 3, 4}}
表示2个元素,每一个元素是一维数组,有三个元素。

 6.2 切片slice

  切片是数组的一个引用,它会生成一个指向数组的指针,并经过切片长度关联到底层数组部分或者所有元素,还提供了一系列对数组的管理功能(append,copy),能够随时动态的扩充存储空间。属于变长数组,至关于C++的vector。建立切片的格式以下:

var sliceName []dataType

  建立切片时,不须要指定切片的长度。下面是一个具体的例子。

var slice1 []int

6.2.1 初始化方法

  1)若是引用底层数组的元素,初始化方法以下:

slice1 = array[start : end]

//如下是三种方式
slice1 = array1
slice1 = array1[ : ]
slice1 = array[0 : len(array1)]

  2)直接建立切片

  即在定义的同时初始化切片元素,以下例:

var slice1 = []int{1,2,3,4,5}

  3)使用make函数建立切片

  下式表示,建立整型切片slice1,元素个数为5,元素初值为0,并预留10个元素的存储空间。

var slice1 = make([]int, 5, 10)

  对切片的访问方式能够经过下标的方式访问,也能够经过range关键字进行访问,同数组。

  • 其自己并非数组,它指向底层的数组,使用[]来作声明
  • 做为变长数组的替代方案,能够关联底层数组的局部或所有
  • 属于引用类型
  • 能够直接建立或从底层数组获取生成
  • 使用len()获取元素个数,cap()获取容量
  • 通常使用make()建立
  • 若是多个slice指向相同底层数组,其中一个的值改变会影响所有
  • make([]T,len,cap)
  • 其中cap能够省略,则和len的值相同
  • len表示存数的元素个数,cap表示容量
//从数组初始化
var arr = [...]int{1,2,3}
var slice_a []int
slice_a = arr[1:2]//下标位置,[1,2),包括首位置,不包含末尾的2位置

 6.2.2 切片的操做

   - Reslice:

  • Reslice时索引以被slice的切片为准
  • 索引不能够超过被slice的切片容量的cap()值
  • 索引越界不会致使底层数组的从新分配而是引起错误

   - Append

  • 能够在slice尾部追加元素
  • 能够将一个slice追加在另外一个slice尾部
  • 若是最终长度未超过追到到slice的容量则返回原始slice
  • 若是超过追加到的slice的容量则将从新分配数组并拷贝原始数据
  • 使用...运算符将一个切片的全部元素追加到另外一个切片中
append(s1,s2...)

- copy

     copy(s1,s2),必须保证s1有足够的空间来存储s2的值。

- 多维切片

slice := [][]int{{1, 2}, {3, 4}}

     使用切片作值函数传递时,以值的形式传递切片,因为切片的尺寸很小,因此成本很低,与切片关联的数据保存在底层数组中,不属于切片自己,因此切片的效率很高。slice的拷贝可使用 s2 := s1[:],拷贝首元素省略,拷贝末尾元素也能够省略,:表示拷贝所有元素。

6.3 map 

  map就是理解为C++里面的map,是key-value类型,也称为字典或者哈希表。

6.3.1 声明格式

var mapName map[keyType] valueType

var map1 map[string] int

  在该例中,声明了一个键值类型为字符串,值类型为整型的字典map1。

6.3.2 字典的初始化和建立

  使用“{ }”操做符对字典进行初始化操做,或者使用make()函数来建立字典。初始化或者建立后,就可使用“=”操做符对字典动态的增添数据项了。

var map1 map[string] int {}
map1["key1"] = 1

  也可使用下面的方式进行建立:

var map1 map[string] int
map1 = make(map[string] int)
map1["key1"] = 1

6.3.3 map的访问和操做

  map经过key来访问value,访问格式以下所示:

Value = mapName[Key]

  map的查找:若是查找的key存在,则将key对应的value值赋予v,OK为true,反之,若是Key不存在,则v等于0,OK为false。

v,OK := mapName[Key]

  map的删除

  delete()用于删除容器内的元素,也能够用于删除map内的键值对,例如:

  下面将从map1中删除键值为key1的键值对,若是key1这个键不存在,那么这个调用将什么也不会发生。

delete(map1,“key1”)
  • 相似其余语言中的哈希表或者字典,以key-value形式存储数据

  • key必须是支持==或!=比较运算的类型,不能够是函数、map或者slice

  • map查找比线性搜索快不少,但比使用索引访问数据的类型慢100倍

  • map使用make()建立,支持:=这种简写方式。

  • make([keyType]valueType,cap),cap表示容量,可省略

  • 超出容量时会自动扩容,但尽可能提供一个合理的初始值

  • 使用len()获取元素个数

  • 键值对不存在时自动添加,使用delete()删除某键值对

  • 使用for range对map和slice进行迭代操做

  • 记住每一个map都必须进行单独的初始化操做。 使用make进行初始化操做。有几层map就须要使用几回make进行初始化操做。

  • map的迭代操做
for k,v := range m {}

7.函数function


 

  • go函数不支持嵌套,重载和默认参数
  • 但支持如下特性
    • 无需声明原型 、不定长度变参、多返回值、命名返回值参数
    • 匿名函数、闭包
  • 定义函数使用关键字func,且左大括号不能另起一行
  • 函数也能够做为一种类型使用
  • 函数声明的基本结构以下:
func functionName(参数列表) 返回值 {
    functionBody
        .
        .
        .
    return 语句
}
  • 不定长变参的使用
    不定长变参使用...表示,要放在全部的参数最后面,传入的a变为一个slice
func A (a ...int) {}
  • 闭包closure
    闭包就是在一个函数中声明一个匿名函数,而后返回这个匿名函数。
func f(i int) func() int {
     return func() int {
         i++
         return i
     }
}
  • defer
    •  执行方式相似其余语言的析构函数,在函数体执行结束以后按照调用顺序的相反顺序逐个执行
    •  即便函数发生严重错误也会执行
    •  支持匿名函数的调用
    •  经常使用于资源清理、文件关闭、解锁以及记录时间等操做
    •  经过与匿名函数配合可在return以后修改函数计算结果
    •  若是函数体内某个变量做为defer时匿名函数的参数,则在定义defer时即已经得到了拷贝,不然则是引用某个变量的地址。
    •     go没有异常机制,但有panic/recover模式来处理错误
    •     panic能够在任何地方引起,但recover只有在defer调用的函数中有效

      defer,panic,recover

       go中能够抛出一个panic的异常,而后在defer中经过recover捕获这个异常,而后正常处理

func B() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recover in B")
        }
    }()
    panic("Panic in B")
}
  • 代码分析
func main() {
    var fs = [4]func(){}

    for i := 0; i < 4; i++ {
        defer fmt.Println("defer i = ", i)
        defer func() {
            fmt.Println("defer_closure i = ", i)
        }()
        fs[i] = func() { fmt.Println("closure i = ", i) }
    }

    for _, f := range fs {
        f()
    }
}

由于defer是逆序执行的,在i变为4以后,闭包中指向的是i的地址,因此闭包中的i的值都是指向i=4的地址。

8 方法、接口和反射


 

8.1 方法

go语言的method相似于一个函数,只是函数名前多了个绑定类型参数---receiver,基本格式以下:

func (recv receiver_type) methodName (参数列表)(返回值){...}
  • 记住命名规范,在结构体中的字段名和方法名必须首字母大写

    method中的receiver能够是内置类型、自定义类型、结构体或指针类型。

  • go虽没有class,可是有method
  • 经过显示说明receiver来实现与某个类型的组合
  • 只能为同一个包中的类型定义方法
  • receiver能够是类型的值或者指针
  • 不存在方法重载
  • 可使用值或指针来调用方法,编译器会自动完成转换
  • 从某种意义来讲,方法是函数的语法糖,由于receiver其实就是方法所接收的第一个采纳数
  • 若是外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
  • 类型别名不会拥有底层类型所附带的方法
  • 方法能够调用结构中的非公开字段

  不一样包中大小写变量方法才有权限的区别,同一个包中能够访问private字段的内容,大写的public权限能够被不一样包之间访问。type tz int,记住tz i和int i仍是不一样的类型,前面的i属于tz类型。要二者相加必须使用强制类型转换。

8.2 接口

  • 接口是一个或多个方法签名的集合
  • 只要某个类型拥有该接口的全部方法签名,即算实现该接口,无需显示声明实现了哪一个接口,这称为Structural Typing
  • 接口只有方法声明,没有实现,没有数据字段
  • 接口能够匿名嵌入其余接口,或嵌入到结构中
  • 将对象赋值给接口时,会发生拷贝。而接口内部存储的是指向这复制品的指针,既没法修改复制品的状态,也没法获取指针
  • 只有当接口存储的类型和对象都为nil时,接口才等于nil
  • 接口调用不会作receiver的自动转换
  • 接口一样支持匿名字段方法
  • 接口也可实现相似oop中的多态
  • 空接口能够做为任何类型数据的容器

接口是用来定义行为的类型,这些被定义的行为不禁接口直接实现,而是经过方法由用户定义的类型实现。
类型断言

  • 经过类型断言的ok pattern能够判断接口中的数据类型
  • 使用type switch则可针对空接口进行比较全面的类型判断

接口转换

  • 能够将拥有超集的接口转换为子集的接口

8.3 反射reflection

  • 反射可大大提升程序的灵活性,使得interface{}有更大的发挥余地
  • 反射使用type()和valueof函数从接口中获取目标对象信息
  • 反射会将匿名字段做为独立字段
  • 想要利用反射修改对象状态,前提是interface.data是settable,即pointer-interface
  • 经过反射能够动态调用方法

  MethodByName()方法使用原对象的方法名name获取该方法的Value值,若是所访问的方法不存在,MethodByName会返回0.
在go语言中传递给方法的参数要和方法定义的参数类型保持一致,为了处理变参这种复杂状况,传递给被调用方法的参数一般首先保存在一个Slice中,而后在复制到参数列表中。

9.并发


  • 协程coroutine
    协程本质是一种用户态线程,不须要操做系统进行抢占性调度,并且在真正执行的时候中寄存于线程中。所以,协程系统开销极小,能够有效提升线程任务的并发性,避免高并发模式下线程并发的缺点。协程最大的优点在于轻量级,能够轻松建立上百万个而不是致使系统资源衰竭,系统最多能建立的进程、线程的数量却少的可怜。使用协程的优势是编程简单,结果清晰。可是缺点就是须要语言的支持,若是语言不支持,则须要用户在程序中自行进行调度。
  • 定义普通函数以后,调用时候前面加上go关键字就能够了。
  • 通道Channel
    • Channel是goroutine沟通的桥梁,大都是阻塞同步的
    • 经过make进行建立,close关闭
    • channel是引用类型
    • 可使用for range来迭代不断操做channel
    • 能够设置单向或双向通道
    • 能够设置缓存大小,在未被填满前不会发生阻塞
    • Select
      • 可处理一个或多个channel的发送与接收
      • 同时有多个可用的channel时按随机顺序处理
      • 可用空的select来阻塞main函数
      • 可设置超时
var varName chan elementType
var c chan int //和普通变量相比,只是增长了一个chan
ch := make(chan int) // 使用make函数直接声明并初始化channel

9.1 channel数据的接收和发送

Channel的主要用途是在不一样的Goroutine之间传递数据,它使用通道运算符<-接收和发送数据,将一个数据发送(写入)至channe的方法是 ch <- value

  • 向Channel写入数据一般会致使程序阻塞,知道其余Goroutine从这个Channe中读取数据,从Channel中接收(读取)数据的语法以下: value := <- ch
    若是没有写入数据,那么在读取的时候也会阻塞,直到写入数据为止。
  • 关闭Channel的方法close(chanName),在关闭一个channel以后,用户还须要判断Channel是否关闭,可使用多重返回值的方法:
value,ok := <- ch

只须要看第二个bool返回值极客,若是返回值是false则表示Channel已关闭。

  • 只有发送端(另外一端正在等待接收)才能关闭Channel,只有接收端才能得到关闭状态,Close调用不是必需的,可是若是接收端使用range或者循环接收数据,就必须调用Close,不然就会致使“throw: all gorountines are asleep-deadlock”
  • 单向Channel
    • 只能接收的Channel变量定义格式var chanName chan <- ElementType
    • 只能发送的Channel变量定义格式var chanName <- chan ElementType 在定义了Channel以后,还须要对其进行初始化操做,能够由一个已定义的双向Channel转换而来。
ch := make(chan int)
chRead := <- chan int(ch)
chWrite := chan <- int(ch)

- 异步Channel
        在Goroutine间传输大量数据的时候,可使用异步通道(Asynchronous-channel),类比消息队列的效果。
异步Channel就是给Channel设定一个buffer值,在buffer未写满的状况下,不阻塞发送操做。buffer指的是被缓冲的数据对象的数量,而不是指内存大小。

  • 异步Channel的建立方法:ch := nake(chan int,1024)该例建立了一个1024的int类型的Channel。

9.2 select机制和超时机制

select机制每一个case语句必须是一个I/O操做,其基本结构以下:

select {
    case <- chan1:
        //若是chan1成功读取数据,则进行该case处理语句。
    case <- chan2:
        //若是chan2成功读取数据,则进行改该case处理语句。
    default:
        // 若是上面都没有成功,则进入default处理流程。
}
  • 超时机制超时机制是一种解决通讯死锁问题的机制,一般会设置一个超时参数,通讯双方若是在设定的时间内仍然没有处理完任务,则该处理过程就会被终止,并返回对应的超时信息。
相关文章
相关标签/搜索