by 小强,2019-06-13html
go语言是目前很是火热的语言,普遍应用于服务器端,云计算,kubernetes容器编排等领域。它是一种开源的编译型程序设计语言,支持并发、垃圾回收机制以提高应用程序性能。它既具备像c这种静态编译型语言的高性能,又具有像python这种动态语言的高效性。不少go程序员都是从C++,Java等面向对象语言由于工做的须要转过来的,所以没有必要从0开始学习go,当初本身的想法是找一篇半小时入门go的博客,貌似没有相似的好文章=_=。搜到的都是一些从小白入手的臃肿教程,学习起来太慢!!!so,打算写这篇go语言快速入门的指南。python
本文写做思路重点在于和C++语言的不一样之处入手,强调go的特性,注重快速入门,所以不介绍一些很是基础的知识,很是基础的一些知识能够去看go语言圣经。关于go环境的安装配置以及vscode编辑器的配置在以前的博客已经介绍,请移步。本文总体组织结构以下:git
在学习任何一门语言的时候,首先都是给出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函数执行前执行。数组
初学者按照如下步骤编写go程序:缓存
1)在工做目录(好比D:\go\development)的src文件夹中建立源文件helloworld.go;.服务器
2)直接将helloworld.go拖入vscode进行编辑;闭包
3)在vscode的终端输入go run helloworld.go,程序就会输出hello world!并发
类型的零值:就是默认值,int默认是0,bool默认是false,字符串默认是空字符串。
类型别名方法格式:
//type 别名 基本类型 type byte int8
全局变量不能省略var关键字,函数内的变量声明能够省略。 go语言中使用关键字func声明函数,关键字后面跟函数名、参数名以及返回值。
go语言使用关键字var来声明变量,声明的通常格式以下所示:
var <variableName> [varibleType] var count int
在声明变量的同时可使用=给变量赋初值:
var count int = 10
其中变量类型int也能够省略,编译器会依据赋的初值自动推断变量类型:
var count = 10
在声明变量的同时还容许省略掉关键字“var”,使用":"取代。
count := 10
常量的声明格式以下所示:
const <constName> [constType] = <赋值表达式>
const a = 1 const ( b, c = 2,3 ) const d,f = 4,5 const ( a = iota //0 b = iota //1 )
go的运算符均是从左至右结合。
优先级(从高到底)
其中位运算符介绍:
实际例子:
6: 0110 11:1011 ------------- &: 0010 |: 1111 ^: 1101 &^:0100
Go虽然保留了指针,但不一样点在于go不支持指针运算以及->运算符,而直接采用.选择符来操做指针目标对象的成员。
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当作一个类型,后面的{}就是初始化操做。
if a > 1 { fmt.Println(a) } if b :=1;b > 1 { }
注意else必须和if的右括号}在同一行才行,否则出错。
if a { }else {}
if后面定义的变量,属于if语句的局部变量,只能在对应的if-else中使用,不能在外部使用。之间经过;分割语句。
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 key, value := range oldMap { newMap[key] = value }
关键字range会返回两个值,第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。而不是返回对该元素的引用。
switch var1 { case val1: ... case val2: ... default: ... }
跳转语句goto,break,continue
定义数组的格式: 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)
须要注意的是全部值类型变量在赋值或做为参数传递的时候将产生一次复制操做。若是将数组做为函数的参数类型,则在函数调用是将该参数将发生数据复制,函数体内没法修改数组的内容,由于函数体内操做是变量的一个副本。
多维数组的声明以下所示,其中第一维度行的数量是能够省略的,使用...代替。
arr := [2][3]int{ {1, 2, 3}, {2, 3, 4}} 表示2个元素,每一个元素是一维数组,有三个元素。
切片是数组的一个引用,它会生成一个指向数组的指针,并经过切片长度关联到底层数组部分或者所有元素,还提供了一系列对数组的管理功能(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关键字进行访问,同数组。
//从数组初始化 var arr = [...]int{1,2,3} var slice_a []int slice_a = arr[1:2]//下标位置,[1,2),包括首位置,不包含末尾的2位置
6.2.2 切片的操做
- Reslice:
- Append
append(s1,s2...)
- copy
copy(s1,s2),必须保证s1有足够的空间来存储s2的值。
- 多维切片
slice := [][]int{{1, 2}, {3, 4}}
使用切片作值函数传递时,以值的形式传递切片,因为切片的尺寸很小,因此成本很低,与切片关联的数据保存在底层数组中,不属于切片自己,因此切片的效率很高。slice的拷贝可使用 s2 := s1[:],拷贝首元素省略,拷贝末尾元素也能够省略,:表示拷贝所有元素。
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进行初始化操做。
for k,v := range m {}
func functionName(参数列表) 返回值 { functionBody . . . return 语句 }
func A (a ...int) {}
func f(i int) func() int { return func() int { i++ return i } }
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的地址。
go语言的method相似于一个函数,只是函数名前多了个绑定类型参数---receiver,基本格式以下:
func (recv receiver_type) methodName (参数列表)(返回值){...}
method中的receiver能够是内置类型、自定义类型、结构体或指针类型。
不一样包中大小写变量方法才有权限的区别,同一个包中能够访问private字段的内容,大写的public权限能够被不一样包之间访问。type tz int,记住tz i和int i仍是不一样的类型,前面的i属于tz类型。要二者相加必须使用强制类型转换。
接口是用来定义行为的类型,这些被定义的行为不禁接口直接实现,而是经过方法由用户定义的类型实现。
类型断言
接口转换
MethodByName()方法使用原对象的方法名name获取该方法的Value值,若是所访问的方法不存在,MethodByName会返回0.
在go语言中传递给方法的参数要和方法定义的参数类型保持一致,为了处理变参这种复杂状况,传递给被调用方法的参数一般首先保存在一个Slice中,而后在复制到参数列表中。
var varName chan elementType var c chan int //和普通变量相比,只是增长了一个chan ch := make(chan int) // 使用make函数直接声明并初始化channel
Channel的主要用途是在不一样的Goroutine之间传递数据,它使用通道运算符<-接收和发送数据,将一个数据发送(写入)至channe的方法是 ch <- value
value := <- ch
close(chanName)
,在关闭一个channel以后,用户还须要判断Channel是否关闭,可使用多重返回值的方法:value,ok := <- ch
只须要看第二个bool返回值极客,若是返回值是false则表示Channel已关闭。
var chanName chan <- ElementType
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指的是被缓冲的数据对象的数量,而不是指内存大小。
ch := nake(chan int,1024)
该例建立了一个1024的int类型的Channel。select机制每一个case语句必须是一个I/O操做,其基本结构以下:
select { case <- chan1: //若是chan1成功读取数据,则进行该case处理语句。 case <- chan2: //若是chan2成功读取数据,则进行改该case处理语句。 default: // 若是上面都没有成功,则进入default处理流程。 }