Go语言入门

 

Go 语言特点

  • 简洁、快速、安全
  • 并行、有趣、开源
  • 内存管理、数组安全、编译迅速

 

Go 语言用途

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或相似用途的巨型中央服务器的系统编程语言。python

对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。golang

 

Go 语言结构

Go 语言的基础组成有如下几个部分:编程

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

 

package main

import "fmt"

func main() {
   /* 这是个人第一个简单的程序 */
   fmt.Println("Hello, World!")
}

  

让咱们来看下以上程序的各个部分:数组

    1. 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪一个包,如:package main。package main表示一个可独立执行的程序,每一个 Go 应用程序都包含一个名为 main 的包。安全

    2. 下一行 import "fmt" 告诉 Go 编译器这个程序须要使用 fmt 包(的函数,或其余元素),fmt 包实现了格式化 IO(输入/输出)的函数。服务器

    3. 下一行 func main() 是程序开始执行的函数。main 函数是每个可执行程序所必须包含的,通常来讲都是在启动后第一个执行的函数(若是有 init() 函数则会先执行该函数)。数据结构

    4. 下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最多见的注释形式,你能够在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不能够嵌套使用,多行注释通常用于包的文档描述或注释成块的代码片断。闭包

    5. 下一行 fmt.Println(...) 能够将字符串输出到控制台,并在最后自动增长换行字符 \n。 
      使用 fmt.Print("hello, world\n") 能够获得相同的结果。 
      Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。若是没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。并发

    6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就能够被外部包的代码所使用(客户端程序须要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符若是以小写字母开头,则对包外是不可见的,可是他们在整个包的内部是可见而且可用的(像面向对象语言中的 protected )。app

 

注意

须要注意的是 { 不能单独放在一行

 

Go 语言基础语法

Go 标记

Go 程序能够由多个标记组成,能够是关键字,标识符,常量,字符串,符号。如如下 GO 语句由 6 个标记组成:

fmt.Println("Hello, World!")

  

行分隔符

在 Go 程序中,一行表明一个语句结束。每一个语句不须要像 C 家族中的其它语言同样以分号 ; 结尾,由于这些工做都将由 Go 编译器自动完成。

 

Go 语言变量

 

var identifier type

 

变量声明

第一种,指定变量类型,若是没有初始化,则变量默认为零值

 

var v_name v_type
v_name = value

零值就是变量没有作初始化时系统默认设置的值。

 

第二种,根据值自行断定变量类型。

var v_name = value

 

第三种,省略 var, 注意 := 左侧若是没有声明新的变量,就产生编译错误,格式:

v_name := value

 这种不带声明格式的只能在函数体中出现

 

多变量声明

 

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不须要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出如今 := 左侧的变量不该该是已经被声明过的,不然会致使编译错误


// 这种因式分解关键字的写法通常用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

 

 

值类型和引用类型

全部像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:

当使用等号 = 将一个变量的值赋值给另外一个变量时,如:j = i,其实是在内存中将 i 的值进行了拷贝:

基本类型是值类型

 

更复杂的数据一般会须要使用多个字,这些数据通常使用引用类型保存。

一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。

这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。

同一个引用类型的指针指向的多个字能够是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也能够将这些字分散存放在内存中,每一个字都指示了下一个字所在的内存地址。

当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。

 

 

简短形式,使用 := 赋值操做符

这是使用变量的首选形式,可是它只能被用在函数体内,而不能够用于全局变量的声明与赋值。使用操做符 := 能够高效地建立一个新的变量,称之为初始化声明。

 

注意事项

若是在相同的代码块中,咱们不能够再次对于相同名称的变量使用初始化声明

若是你声明了一个局部变量却没有在相同的代码块中使用它,一样会获得编译错误

可是全局变量是容许声明但不使用。

同一类型的多个变量能够声明在同一行。

多变量能够在同一行进行赋值,用逗号分隔。这被称为 并行 或 同时 赋值。

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 其实是一个只写变量,你不能获得它的值。这样作是由于 Go 语言中你必须使用全部被声明的变量,但有时你并不须要使用从一个函数获得的全部返回值。

并行赋值也被用于当一个函数返回多个返回值时,好比这里的 val 和错误 err 是经过调用 Func1 函数同时获得:val, err = Func1(var1)。

 

 

Go 语言常量

常量中的数据类型只能够是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

const identifier [type] = value

你能够省略类型说明符 [type],由于编译器能够根据变量的值来推断其类型。

 

 

Go 语言条件语句

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}else if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}else {
  
}

  

select?

 

Go 语言循环语句

for init; condition; post { }

 

当只有condition的时候和c语言的while相似

 

当什么都没有的时候至关于c语言的for(;;)

 

for 循环的 range 格式能够对 slice、map、数组、字符串等进行迭代循环。格式以下:

for key, value := range oldMap {
    newMap[key] = value
}

 

 

Go 语言函数

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数。

你能够经过函数来划分不一样功能,逻辑上每一个函数执行的是指定的任务。

函数声明告诉了编译器函数的名称,返回类型,和参数。

Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数能够接受不一样类型参数并返回该类型的长度。若是咱们传入的是字符串则返回字符串的长度,若是传入的是数组,则返回数组中包含的元素个数。

 

Go 语言函数定义格式以下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

 

默认状况下,Go 语言使用的是值传递,即在调用过程当中不会影响到实际参数。

想实现引用传递就使用指针,传参数的地址

 

Go 语言函数闭包

Go里有函数类型的变量,这样,虽然不能在一个函数里直接声明另外一个函数,可是能够在一个函数中声明一个匿名函数类型的变量,此时的匿名函数称为闭包(closure)。

 匿名函数是指不须要定义函数名的一种函数实现方式

闭包就是可以读取其余函数内部变量的函数。 
只有函数内部的子函数才能读取局部变量,所以能够把闭包简单理解成”定义在一个函数内部的函数”。

匿名函数能够直接赋值给一个变量或直接执行

 

虽然i是局部变量可是只要闭包还在使用,那么被闭包引用的变量就会一直存在 
而i除了在内部匿名函数中能够访问外,没法经过其余方式处理,所以保证了i的安全性

 

闭包被返回赋予一个同类型的变量时,同时赋值的是整个闭包的状态,该状态会一直存在外部被赋值的变量中,直到这个变量被销毁,整个闭包也被销毁。

由此得出如下两点
1.内函数对外函数 的变量的修改,是对变量的引用
2.变量被引用后,它所在的函数结束,这变量也不会立刻被烧毁

闭包函数出现的条件:
1.被嵌套的函数引用到非本函数的外部变量,并且这外部变量不是“全局变量”;
2.嵌套的函数被独立了出来(被父函数返回或赋值 变成了独立的个体),而被引用的变量所在的父函数已结束.

Golang并发中的闭包

占坑

 

 

Go 语言函数方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者能够是命名类型或者结构体类型的一个值或者是一个指针。全部给定类型的方法属于该类型的方法集。

语法格式以下:

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

 

Go 语言变量做用域

局部变量的做用域只在函数体内,参数和返回值变量也是局部变量。

全局变量能够在整个包甚至外部包(被导出后)使用。

形式参数会做为函数的局部变量来使用

 

Go 语言数组

Go 语言数组声明须要指定元素类型及元素个数,语法格式以下:

var variable_name [SIZE] variable_type

 

如下演示了数组初始化:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

 

Go 语言向函数传递数组

void myFunction(param []int){

}

形参能够指定数组大小也能够不指定

 

Go 语言指针

nil 指针也称为空指针。相似于NULL

 

Go 语言结构体

结构体定义须要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体有中有一个或多个成员。type 语句设定告终构体的名称。结构体的格式以下:

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}

 

声明语法格式:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

 

使用结构体变量或者结构体指针访问结构体成员,都使用 "." 操做符:

 

Go 语言切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,能够追加元素,在追加时可能使切片的容量增大。

你能够声明一个未指定大小的数组来定义切片

切片不须要说明长度。

或使用make()函数来建立切片:

var slice1 []type = make([]type, len)

也能够简写为

slice1 := make([]type, len)

这里 len 是数组的长度而且也是切片的初始长度。

 

 

append() 和 copy() 函数

slice = append(slice, data1,data2......)
//在slice里追加数据

copy(slice2,slice1)
//复制slice1的内容到slice2

 

 

Go 语言范围(Range)

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。

 

Go 语言Map(集合)

可使用内建函数 make 也可使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

若是不初始化 map,那么就会建立一个 nil map。nil map 不能用来存放键值对

map必需要用make()进行初始化才能使用

 

range mp 会返回两个值,key和value

mp[key]会返回两个值,value和bool bool表示map中是否有value和key对应,若是没有的话value是0,bool是False

delete() 函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。

 

Go 语言接口

Go 语言提供了另一种数据类型即接口,它把全部的具备共性的方法定义在一块儿,任何其余类型只要实现了这些方法就是实现了这个接口。

Interface 是一组抽象方法(未具体实现的方法/仅包含方法名参数返回值的方法)的集合,若是实现了 interface 中的全部方法,即该类/对象就实现了该接口。

Interface 的声明格式:

type interfaceName interface {  
    //方法列表  
}  

 

Interface 能够被任意对象实现,一个类型/对象也能够实现多个 interface;
interface的变量能够持有任意实现该interface类型的对象。

 

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

var inter interface_name
inter = new(struct_name)

inter.method_namen()

  

接口变量能够直接指向实现了该接口的变量,而后这个接口变量只能调用本身定义的方法

 

Go 错误处理

Go 语言经过内置的错误接口提供了很是简单的错误处理机制。

error类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

 

咱们能够在编码中经过实现 error 接口类型来生成错误信息。

函数一般在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

Go语言(golang)的错误设计是经过返回值的方式,来强迫调用者对错误进行处理,要么你忽略,要么你处理(处理也能够是继续返回给调用者),对于golang这种设计方式,咱们会在代码中写大量的if判断,以便作出决定。 

func main() {
	conent,err:=ioutil.ReadFile("filepath")
	if err !=nil{
		//错误处理
	}else {
		fmt.Println(string(conent))
	}
}

 

咱们也能够实现error接口来自定义Error类型

func test_error(x int) (int, error) {
	if (x != 0) {
		return -x, nil
	} else {
		return x, &error_test{97}
	}
}

type error_test struct {
	key int
}

func (x *error_test) Error() string {
	return string(x.key) + " error test !"
}

 

Go 并发

Goroutines

Go 容许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不一样的、新建立的 goroutine 来执行一个函数。 同一个程序中的全部 goroutine 共享同一个地址空间。

Goroutines 能够看做是轻量级线程。建立一个 goroutine 很是简单,只须要把 go 关键字放在函数调用语句前。       

go f()

 

Channels

Channels 容许 go routines 之间相互通讯。你能够把 channel 看做管道,goroutines 能够往里面发消息,也能够从中接收其它 go routines 的消息。

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个 goroutine 之间经过传递一个指定类型的值来同步运行和通信。操做符 <- 用于指定通道的方向,发送或接收。若是未指定方向,则为双向通道。

声明一个通道很简单,咱们使用chan关键字便可,通道在使用前必须先建立:

ch := make(chan int)

注意:默认状况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

 

通道缓冲区

通道能够设置缓冲区,经过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道容许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据能够放在缓冲区里面,能够等待接收端去获取数据,而不是马上须要接收端去获取数据。

不过因为缓冲区的大小是有限的,因此仍是必须有接收端来接收数据的,不然缓冲区一满,数据发送端就没法再发送数据了。

注意:若是通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。若是通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;若是缓冲区已满,则意味着须要等待直到某个接收方获取到一个值。接收方在有值能够接收以前会一直阻塞。

 

Go 遍历通道与关闭通道

Go 经过 range 关键字来实现遍历读取到的数据,相似于与数组或切片。格式以下:

for i := range ch{

}

 

 

v, ok := <-ch

若是通道接收不到数据后 ok 就为 false,这时通道就可使用 close() 函数来关闭。

 

Channel Blocking

Channels 阻塞 goroutines 发生在各类情形下。这能在 goroutines 各自欢快地运行以前,实现彼此之间的短暂同步。

 

Unbuffered Channels

它们不同凡响的地方在于每次只有一份数据能够经过。

 

Buffered Channels

buffered 和 unbuffered channels 工做原理相似,但有一点不一样—在须要另外一个 gorountine 取走数据以前,咱们能够向 buffered channel 发送多份数据。

 

匿名的 Goroutines

若是只须要调用一次函数,经过这种方式咱们可让它在本身的 goroutine 中运行,而不须要建立一个正式的函数声明。函数匿名。

 

main 函数是一个 goroutine

main 函数确实运行在本身的 goroutine 中!更重要的是要知道,一旦 main 函数返回,它将关掉当前正在运行的其余 goroutines。这就是为何咱们在 main 函数的最后设置了一个定时器—它建立了一个 channel,并在 5 秒后发送一个值。

 

你能够遍历 channel

为了确保接收到channel里的全部信息,咱们在处理channel里的数据时须要遍历channel,为了防止range被阻塞,发送完全部数据后发送方应该调用close(channel)来关闭channel防止接收方阻塞。

 

对 channel 进行非阻塞读

利用 Go 的 select case 语句能够实现对 channel 的非阻塞读。经过使用这这种语句,若是 channel 有数据,goroutine 将会从中读取,不然就执行默认的分支。

myChan := make(chan string)

go func(){
    myChan <- "Message!"
}()

select {
    case msg := <- myChan:
        fmt.Println(msg)
    default:
        fmt.Println("No Msg")
}
<-time.After(time.Second * 1)

select {
    case msg := <- myChan:
        fmt.Println(msg)
    default:
        fmt.Println("No Msg")
}

  

 

对 channel 进行非阻塞写

非阻塞写也是使用一样的 select case 语句来实现,惟一不一样的地方在于,case 语句看起来像是发送而不是接收。

select {
    case myChan <- "message":
        fmt.Println("sent the message")
    default:
        fmt.Println("no message sent")
}
相关文章
相关标签/搜索