Go语言学习

image

前言

Go语言(或Golang)是Google在2007年开发的一种开源编程语言,于2009年11月10日向全球公布,2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

GO部署简单、并发性好、语言设计良好、执行性能好

一、GO语言结构

Go语言的基础组成有一下几个部分:

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

例子:

//定义包名
package main

//引入fmt包
import "fmt"

//定义函数, main为入口函数
func main() {
   /* 这是我的第一个简单的程序,我是注释 */
   fmt.Println("Hello, World!")
}

二、Go基础语法

  • 行分隔符:一行代表一个语句结束,无需;号结尾
  • 注释:单行//,多行/**/
  • 标识符关键字:标识符用来命名变量、类型等程序实体,第一个字符必须是字母或下划线而不能是数字
  • 关键字: 25 个关键字或保留字 36 个预定义标识符
  • 空格:声明必须使用空格隔开,变量与运算符间加入空格,程序看起来更加美观

1.数据类型

数据类型用于声明函数和变量。 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

Go按类别划分有以下集中数据类型:

  • 布尔型(BOOL)

布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

  • 数字类型

整型 int 和浮点型 float32、float64. 整型包含: 浮点型:

其他数字类型

  • 字符串类型

字符串就是一串固定长度的字符连接起来的字符序列。 Go 的字符串是由单个字节连接起来的,字节使用 UTF-8 编码标识 Unicode 文本。

  • 派生类型
    • 指针类型(Pointer)
    • 数组类型
    • 结构化类型(struct)
    • Channel类型
    • 函数类型
    • 切片类型
    • 接口类型(interface)
    • Map类型

2.变量

变量名由字母、数字、下划线组成,其中首个字符不能为数字

申明变量的三种格式:

  • 指定变量类型,声明后若不赋值,使用默认值
var v_name v_type
v_name = value
  • 根据值自行判定变量类型: var v_name = value
  • 省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
v_name := value
例如
x := 10

多变量声明

var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

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

//出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误
vname1, vname2, vname3 := v1, v2, v3 

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

局部变量声明后如果没有使用编译时会报错。

3.常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

  • 定义常量的格式:const identifier [type] = value
- 显式类型定义: const b string = "abc"
- 隐式类型定义: const b = "abc"

例如
const LENGTH int = 10 
const WIDTH = 10 
  • 常量可以用作枚举,例如:
const (
    Unknown = 0
    Female = 1
    Male = 2
)
  • 常量可以用函数(必须是内置函数)计算表达式的值,例如len(), cap(), unsafe.Sizeof()等函数。
package main

import "unsafe"
const (
    a = "abc"
    b = len(a)
    c = unsafe.Sizeof(a)
)

func main(){
    println(a, b, c)
}
  • iota常量 特殊常量,可以认为是一个可以被编译器修改的常量 iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)

用作枚举值:

const (
    a = iota //0
    b = iota //1
    c = iota //2
)
可简写
const (
    a = iota
    b
    c
)

4.运算符

  • 算术运算符 假定 A 值为 10,B 值为 20

  • 关系运算符 假定 A 值为 10,B 值为 20。

  • 逻辑运算符 假定 A 值为 True,B 值为 False

  • 位运算符

位运算符对整数在内存中的二进制位进行操作,假定 A 为60,B 为13

注意右移n位时除以2的n次方对负数无效。

下表列出了位运算符 &, |, 和 ^ 的计算:

假定 A = 60; B = 13; 其二进制数转换为:

  • 赋值运算符

  • 其他运算符

    package main
    
    import "fmt"
    
    func main() {
       var a int = 4
       var b int32
       var c float32
       var ptr *int
    
       /* 运算符实例 */
       fmt.Printf("第 1 行 - a 变量类型为 = %T\n", a );
       fmt.Printf("第 2 行 - b 变量类型为 = %T\n", b );
       fmt.Printf("第 3 行 - c 变量类型为 = %T\n", c );
    
       /*  & 和 * 运算符实例 */
       ptr = &a    /* 'ptr' 包含了 'a' 变量的地址 */
       fmt.Printf("a 的值为  %d\n", a);
       fmt.Printf("*ptr 为 %d\n", *ptr);
    }
    
  • 优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右.下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低

可以通过使用括号来临时提升某个表达式的整体运算优先级

5.条件语句

条件语句的结构:

GO提供以下条件判断语句

if…else, switch例句

   if a < 20 {
       /* 如果条件为 true 则执行以下语句 */
       fmt.Printf("a 小于 20\n" );
   } else {
       /* 如果条件为 false 则执行以下语句 */
       fmt.Printf("a 不小于 20\n" );
   }

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   

select是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。 select随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

6.循环语句

循环程序的流程图

for在go语言中3种形式

  • for init; condition; post { }
  • for condition { }
  • for { }

init: 一般为赋值表达式,给控制变量赋初值; condition: 关系表达式或逻辑表达式,循环控制条件; post: 一般为赋值表达式,给控制变量增量或减量。

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

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

for使用实例:

package main

import "fmt"

func main() {

   var b int = 15
   var a int

   numbers := [6]int{1, 2, 3, 5} 

   /* for 循环 */
   for a := 0; a < 10; a++ {
      fmt.Printf("a 的值为: %d\n", a)
   }

   for a < b {
      a++
      fmt.Printf("a 的值为: %d\n", a)
   }

   for i,x:= range numbers {
      fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
   }   
}

7.函数

函数是基本的代码块,用于执行一个任务,go程序最少有一个main()函数

  • 函数定义:
func function_name( [parameter list] ) [return_types] {
   函数体
}

func:函数由 func 开始声明
function_name:函数名称,函数名和参数列表一起构成了函数签名。
parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
函数体:函数定义的代码集合。

实例:

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}
  • 函数调用、返回多值
package main

import "fmt"

func swap(x, y string) (string, string) {
   return y, x
}

func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}
  • 函数参数

函数如果使用参数,该变量可称为函数的形参。 两种方式传参: (a) 值传递

/* 定义相互交换值的函数 */
func swap(x, y int) int {
   var temp int

   temp = x /* 保存 x 的值 */
   x = y    /* 将 y 值赋给 x */
   y = temp /* 将 temp 值赋给 y*/

   return temp;
}

(b) 引用传递

/* 定义交换值函数*/
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保持 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}
  • 函数用法

(a)函数作为值: 函数定义后可作为值来使用

package main

import (
   "fmt"
   "math"
)

func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9)
}

(b)闭包:闭包是匿名函数,可在动态编程中使用

package main

import "fmt"

func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
     return i  
   }
}

func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  

   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}

(c)方法:方法就是一个包含了接受者的函数

定类型的方法属于该类型的方法集,语法格式:

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

实例:

package main

import (
   "fmt"  
)

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

8.变量作用域

  • 局部变量
  • 全局变量
  • 形式参数

9.数组

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列 数组元素可以通过索引(位置)来读取(或者修改),索引从0开始,第一个元素索引为 0,第二个索引为 1,以此类推。

  • 声明

数组声明需要指定元素类型及元素个数,语法格式:var variable_name [SIZE] variable_type

例如定义一个数组 balance 长度为 10 类型为 float32:var balance [10] float32

  • 初始化

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。

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

可省略[]里的数字

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

  • 读取和赋值数组元素

数组元素通过索引读取,实例:

package main

import "fmt"

func main() {
   var n [10]int /* n 是一个长度为 10 的数组 */
   var i,j int

   /* 为数组 n 初始化元素 */         
   for i = 0; i < 10; i++ {
      n[i] = i + 100 /* 设置元素为 i + 100 */
   }

   /* 输出每个数组元素的值 */
   for j = 0; j < 10; j++ {
      fmt.Printf("Element[%d] = %d\n", j, n[j] )
   }
}
  • 多维数组

常用的多维数组声明方式:var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type ,例如:var threedim [5][10][4]int

初始化多维数组:

a = [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},   /* 第三行索引为 2 */
}

多维数组实例:

package main

import "fmt"

func main() {
   /* 数组 - 5 行 2 列*/
   var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
   var i, j int

   /* 输出数组元素 */
   for  i = 0; i < 5; i++ {
      for j = 0; j < 2; j++ {
         fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
      }
   }
}

10.指针

  • 定义

一个指针变量指向了一个值的内存地址,在使用指针前你需要声明指针

格式:var var_name *var-type

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */
package main

import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}
  • 空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil,nil指针也称为空指针, 一个指针变量通常缩写为ptr。

实例:

package main

import "fmt"

func main() {
   var  ptr *int

   fmt.Printf("ptr 的值为 : %x\n", ptr  )
}

判断空指针

if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */
  • 指针数组
ackage main

import "fmt"

const MAX int = 3

func main() {
   a := []int{10,100,200}
   var i int
   var ptr [MAX]*int;

   for  i = 0; i < MAX; i++ {
      ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
   }
}
  • 指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:

指向指针的指针变量格式:var ptr **int;

访问指向指针的指针变量值需要使用两个 * 号,如下所示:

package main

import "fmt"

func main() {

   var a int
   var ptr *int
   var pptr **int

   a = 3000

   /* 指针 ptr 地址 */
   ptr = &a

   /* 指向指针 ptr 地址 */
   pptr = &ptr

   /* 获取 pptr 的值 */
   fmt.Printf("变量 a = %d\n", a )
   fmt.Printf("指针变量 *ptr = %d\n", *ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}
  • 指针作为函数参数

向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。

以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的值:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交换前 a 的值 : %d\n", a )
   fmt.Printf("交换前 b 的值 : %d\n", b )

   /* 调用函数用于交换值
   * &a 指向 a 变量的地址
   * &b 指向 b 变量的地址
   */
   swap(&a, &b);

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *x = *y      /* 将 y 赋值给 x */
   *y = temp    /* 将 temp 赋值给 y */
}

11.结构体

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

数组可以存储同一类型的数据,但在结构体中可以为不同项定义不同的数据类型。

  • 定义结构体

结构体定义需要使用 type 和 struct 语句:

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

struct 语句定义一个新的数据类型,结构体有中有一个或多个成员 type 语句设定了结构体的名称

  • 使用结构体

语法格式:

variable_name := structure_variable_type {value1, value2...valuen}

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

实例如下:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {

    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})

    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})

    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}
  • 访问结构体成员

使用 . 操作符,格式为:结构体.成员名

实例如下:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 声明 Book1 为 Books 类型 */
   var Book2 Books        /* 声明 Book2 为 Books 类型 */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   fmt.Printf( "Book 1 title : %s\n", Book1.title)
   fmt.Printf( "Book 1 author : %s\n", Book1.author)
   fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
   fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

   /* 打印 Book2 信息 */
   fmt.Printf( "Book 2 title : %s\n", Book2.title)
   fmt.Printf( "Book 2 author : %s\n", Book2.author)
   fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
   fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}
  • 作为函数参数
package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 声明 Book1 为 Books 类型 */
   var Book2 Books        /* 声明 Book2 为 Books 类型 */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   printBook(Book1)

   /* 打印 Book2 信息 */
   printBook(Book2)
}

func printBook( book Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}
  • 结构体指针

指向结构体的指针类似于其他指针变量,格式如下:var struct_pointer *Books

以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:struct_pointer = &Book1;

使用结构体指针访问结构体成员,使用 “.” 操作符:struct_pointer.title;

实例如下:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* Declare Book1 of type Book */
   var Book2 Books        /* Declare Book2 of type Book */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   printBook(&Book1)

   /* 打印 Book2 信息 */
   printBook(&Book2)
}
func printBook( book *Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}

12.切片

切片(slice)是对数组的抽象,长度不固定,可追加元素,可理解为动态数组

  • 定义切片

可以声明一个未指定大小的数组来定义切片:var identifier []type

切片不需要说明长度。

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

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

也可以简写为

slice1 := make([]type, len)

也可以指定容量,其中capacity为可选参数。

make([]T, length, capacity)

  • 初始化切片

    a. 直接初始化

    s :=[] int {1,2,3 }, 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3

    b. 从数组初始化

    s := arr[:], 初始化切片s,是数组arr的引用

    s := arr[startIndex:endIndex] 
    将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片
    
    s := arr[startIndex:] 
    缺省endIndex时将表示一直到arr的最后一个元素
    
    s := arr[:endIndex] 
    缺省startIndex时将表示从arr的第一个元素开始
    

    c. 从切片s初始化切片s1

    s1 := s[startIndex:endIndex]

    d. 通过内置函数make()初始化切片s

    s :=make([]int,len,cap), []int 标识为其元素类型为int的切片

  • 切片函数

切片可索引,可由**len()获取长度,可计算容量,用cap()**测量切片最长可达到多少。

一个切片在未初始化之前默认为 nil,长度为 0

实例:

package main
import "fmt"

func main() {
   var numbers = make([]int,3,5)
   printSlice(numbers)
   
   var numbers2 []int
   printSlice(numbers2)
   if(numbers2 == nil){
      fmt.Printf("切片是空的")
   }
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。 下面的代码描述了从拷贝切片的 copy() 方法和向切片追加新元素的 append() 方法

package main

import "fmt"

func main() {
   var numbers []int
   printSlice(numbers)

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers)

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers)

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)   
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:

package main

import "fmt"

func main() {
   /* 创建切片 */
   numbers := []int{0,1,2,3,4,5,6,7,8}   
   printSlice(numbers)

   /* 打印原始切片 */
   fmt.Println("numbers ==", numbers)

   /* 打印子切片从索引1(包含) 到索引4(不包含)*/
   fmt.Println("numbers[1:4] ==", numbers[1:4])

   /* 默认下限为 0*/
   fmt.Println("numbers[:3] ==", numbers[:3])

   /* 默认上限为 len(s)*/
   fmt.Println("numbers[4:] ==", numbers[4:])

   numbers1 := make([]int,0,5)
   printSlice(numbers1)

   /* 打印子切片从索引  0(包含) 到索引 2(不包含) */
   number2 := numbers[:2]
   printSlice(number2)

   /* 打印子切片从索引 2(包含) 到索引 5(不包含) */
   number3 := numbers[2:5]
   printSlice(number3)

}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

13.范围(range)

range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。

数组和切片中它返回元素的索引和索引对应的值,在集合中返回key-value对的 key值。

实例如下:

package main
import "fmt"
func main() {
   //这是我们使用range去求一个slice的和。使用数组跟这个很类似
   nums := []int{2, 3, 4}
   sum := 0
   for _, num := range nums {
       sum += num
   }
   fmt.Println("sum:", sum)
   //在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
   for i, num := range nums {
       if num == 3 {
           fmt.Println("index:", i)
       }
   }
   //range也可以用在map的键值对上。
   kvs := map[string]string{"a": "apple", "b": "banana"}
   for k, v := range kvs {
       fmt.Printf("%s -> %s\n", k, v)
   }
   //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
   for i, c := range "go" {
       fmt.Println(i, c)
   }
}

14.集合(map)

map是一种无序的键值对的集合,通过key来快速检索数据。

  • 定义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,不能用来存放键值对.

实例如下:

package main

import "fmt"

func main() {
    var countryCapitalMap map[string]string /*创建集合 */
    countryCapitalMap = make(map[string]string)

    /* map插入key - value对,各个国家对应的首都 */
    countryCapitalMap [ "France" ] = "Paris"
    countryCapitalMap [ "Italy" ] = "罗马"
    countryCapitalMap [ "Japan" ] = "东京"
    countryCapitalMap [ "India " ] = "新德里"

    /*使用键输出地图值 */ for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [country])
    }

    /*查看元素在集合中是否存在 */
    captial, ok := countryCapitalMap [ "美国" ] /*如果确定是真实的,则存在,否则不存在 */
    /*fmt.Println(captial) */
    /*fmt.Println(ok) */
    if (ok) {
        fmt.Println("美国的首都是", captial)
    } else {
        fmt.Println("美国的首都不存在")
    }
}
  • delete() 函数

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

package main

import "fmt"

func main() {
    /* 创建map */
    countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}

    fmt.Println("原始地图")

    /* 打印地图 */
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [ country ])
    }

    /*删除元素*/ delete(countryCapitalMap, "France")
    fmt.Println("法国条目被删除")

    fmt.Println("删除元素后地图")

    /*打印地图*/
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [ country ])
    }
}

15.接口

接口把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

  • 定义接口
/* 定义接口 */
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] {
   /* 方法实现*/
}

实例:

package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

16.异常处理

go通过内置的错误接口提供了非常简单的错误处理机制。 error类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

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

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

实例:

package main

import (
    "fmt"
)

// 定义一个 DivideError 结构
type DivideError struct {
    dividee int
    divider int
}

// 实现 `error` 接口
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`
    return fmt.Sprintf(strFormat, de.dividee)
}

// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
        dData := DivideError{
            dividee: varDividee,
            divider: varDivider,
        }
        errorMsg = dData.Error()
        return
    } else {
        return varDividee / varDivider, ""
    }

}

func main() {

    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
        fmt.Println("100/10 = ", result)
    }
    // 当被除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
        fmt.Println("errorMsg is: ", errorMsg)
    }

}

17.并发

Go支持并发,我们只需要通过go关键字来开启goroutine即可。

goroutine是轻量级线程,goroutine的调度是由golang运行时进行管理的。

goroutine 语法格式:go 函数名( 参数列表 ) , 例如: go f(x, y, z)

go允许使用go语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

实例

package main

import (
        "fmt"
        "time"
)

func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("world")
        say("hello")
}

18.通道(channel)

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

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

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

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

以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

package main

import "fmt"

func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}

func main() {
        s := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收

        fmt.Println(x, y, x+y)
}
  • 通道缓冲区

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

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

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

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}
  • 遍历和关闭通道

go通过range关键字来实现遍历读取道的数据,类似于数组或切片。格式如下:

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

package main

import (
        "fmt"
)

func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)
}

func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

三、参考

1. gobyexample


go

2939 Words

2019-01-25 21:13 +0000