• [技术干货] Go 语言切片(Slice)
    Go 语言切片是对数组的抽象。Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。定义切片你可以声明一个未指定大小的数组来定义切片:var identifier []type切片不需要说明长度。或使用 make() 函数来创建切片:var slice1 []type = make([]type, len)也可以简写为slice1 := make([]type, len)也可以指定容量,其中 capacity 为可选参数。make([]T, length, capacity)这里 len 是数组的长度并且也是切片的初始长度。切片初始化s :=[] int {1,2,3 } 直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。s := arr[:] 初始化切片 s,是数组 arr 的引用。s := arr[startIndex:endIndex] 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。s := arr[startIndex:] 默认 endIndex 时将表示一直到arr的最后一个元素。s := arr[:endIndex] 默认 startIndex 时将表示从 arr 的第一个元素开始。s1 := s[startIndex:endIndex] 通过切片 s 初始化切片 s1。s :=make([]int,len,cap) 通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片。len() 和 cap() 函数切片是可索引的,并且可以由 len() 方法获取长度。切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。以下为具体实例:实例package mainimport "fmt"func main() {   var numbers = make([]int,3,5)   printSlice(numbers)}func printSlice(x []int){   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)}以上实例运行输出结果为:len=3 cap=5 slice=[0 0 0]空(nil)切片一个切片在未初始化之前默认为 nil,长度为 0,实例如下:实例package mainimport "fmt"func main() {   var numbers []int   printSlice(numbers)   if(numbers == nil){      fmt.Printf("切片是空的")   }}func printSlice(x []int){   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)}以上实例运行输出结果为:len=0 cap=0 slice=[]切片是空的切片截取可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:实例package mainimport "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)}执行以上代码输出结果为:len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]numbers == [0 1 2 3 4 5 6 7 8]numbers[1:4] == [1 2 3]numbers[:3] == [0 1 2]numbers[4:] == [4 5 6 7 8]len=0 cap=5 slice=[]len=2 cap=9 slice=[0 1]len=3 cap=7 slice=[2 3 4]append() 和 copy() 函数如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。实例package mainimport "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)}以上代码执行输出结果为:len=0 cap=0 slice=[]len=1 cap=1 slice=[0]len=2 cap=2 slice=[0 1]len=5 cap=6 slice=[0 1 2 3 4]len=5 cap=12 slice=[0 1 2 3 4]
  • [技术干货] Go 语言结构体
    Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:Title :标题Author : 作者Subject:学科ID:书籍ID定义结构体结构体定义需要使用 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}实例如下:实例package mainimport "fmt"type Books struct {   title string   author string   subject string   book_id int}func main() {    // 创建一个新的结构体    fmt.Println(Books{"Go 语言", "www.run.com", "Go 语言教程", 6495407})    // 也可以使用 key => value 格式    fmt.Println(Books{title: "Go 语言", author: "www.run.com", subject: "Go 语言教程", book_id: 6495407})    // 忽略的字段为 0 或 空   fmt.Println(Books{title: "Go 语言", author: "www.run.com"})}输出结果为:{Go 语言 www.run.com Go 语言教程 6495407}{Go 语言 www.run.com Go 语言教程 6495407}{Go 语言 www.run.com  0}访问结构体成员如果要访问结构体成员,需要使用点号 . 操作符,格式为:结构体.成员名"结构体类型变量使用 struct 关键字定义,实例如下:实例package mainimport "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.run.com"   Book2.subject = "Python 语言教程"   Book2.book_id = 649570   /* 打印 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)}以上实例执行运行结果为:Book 1 title : Go 语言Book 1 author : www.run.comBook 1 subject : Go 语言教程Book 1 book_id : 6495407Book 2 title : Python 教程Book 2 author : www.runcomBook 2 subject : Python 语言教程Book 2 book_id : 6495700结构体作为函数参数你可以像其他数据类型一样将结构体类型作为参数传递给函数。并以以上实例的方式访问结构体变量:实例package mainimport "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.run.com"   Book1.subject = "Go 语言教程"   Book1.book_id = 6495407   /* book 2 描述 */   Book2.title = "Python 教程"   Book2.author = "www.run.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)}以上实例执行运行结果为:Book title : Go 语言Book author : www.run.comBook subject : Go 语言教程Book book_id : 6495407Book title : Python 教程Book author : www.run.comBook subject : Python 语言教程Book book_id : 6495700结构体指针你可以定义指向结构体的指针类似于其他指针变量,格式如下:var struct_pointer *Books以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:struct_pointer = &Book1使用结构体指针访问结构体成员,使用 "." 操作符:struct_pointer.title接下来让我们使用结构体指针重写以上实例,代码如下:实例package mainimport "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.run.com"   Book1.subject = "Go 语言教程"   Book1.book_id = 6495407   /* book 2 描述 */   Book2.title = "Python 教程"   Book2.author = "www.run.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)}以上实例执行运行结果为:Book title : Go 语言Book author : www.runoob.comBook subject : Go 语言教程Book book_id : 6495407Book title : Python 教程Book author : www.runoob.comBook subject : Python 语言教程Book book_id : 6495700
  • [技术干货] go语言指针
    Go 语言指针Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。接下来让我们来一步步学习 Go 语言指针。我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。以下实例演示了变量在内存中地址:实例package mainimport "fmt"func main() {   var a int = 10      fmt.Printf("变量的地址: %x\n", &a  )}执行以上代码输出结果为:变量的地址: 20818a220现在我们已经了解了什么是内存地址和如何去访问它。接下来我们将具体介绍指针。什么是指针一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:var var_name *var-typevar-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:var ip *int        /* 指向整型*/var fp *float32    /* 指向浮点型 */本例中这是一个指向 int 和 float32 的指针。如何使用指针指针使用流程:定义指针变量。为指针变量赋值。访问指针变量中指向地址的值。在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。实例package mainimport "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 )}以上实例执行输出结果为:a 变量的地址是: 20818a220ip 变量储存的指针地址: 20818a220*ip 变量的值: 20Go 空指针当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针。nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。一个指针变量通常缩写为 ptr。查看以下实例:实例package mainimport "fmt"func main() {   var  ptr *int   fmt.Printf("ptr 的值为 : %x\n", ptr  )}以上实例输出结果为:ptr 的值为 : 0空指针判断:if(ptr != nil)     /* ptr 不是空指针 */if(ptr == nil)    /* ptr 是空指针 */
  • [技术干货] go语言数组
    Go 语言数组Go 语言提供了数组类型的数据结构。数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。声明数组Go 语言数组声明需要指定元素类型及元素个数,语法格式如下: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}我们也可以通过字面量在声明数组的同时快速初始化数组: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}或balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}如果设置了数组的长度,我们还可以通过指定下标来初始化元素://  将索引为 1 和 3 的元素初始化balance := [5]float32{1:2.0,3:7.0}初始化数组中 {} 中的元素个数不能大于 [] 中的数字。如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小: balance[4] = 50.0以上实例读取了第五个元素。数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。访问数组元素数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:var salary float32 = balance[9]以上实例读取了数组 balance 第 10 个元素的值。以下演示了数组完整操作(声明、赋值、访问)的实例:实例 1package mainimport "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] )   }}以上实例执行结果如下:Element[0] = 100Element[1] = 101Element[2] = 102Element[3] = 103Element[4] = 104Element[5] = 105Element[6] = 106Element[7] = 107Element[8] = 108Element[9] = 109实例 2package mainimport "fmt"func main() {   var i,j,k int   // 声明数组的同时快速初始化数组   balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}   /* 输出数组元素 */         ...   for i = 0; i < 5; i++ {      fmt.Printf("balance[%d] = %f\n", i, balance[i] )   }      balance2 := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}   /* 输出每个数组元素的值 */   for j = 0; j < 5; j++ {      fmt.Printf("balance2[%d] = %f\n", j, balance2[j] )   }   //  将索引为 1 和 3 的元素初始化   balance3 := [5]float32{1:2.0,3:7.0}     for k = 0; k < 5; k++ {      fmt.Printf("balance3[%d] = %f\n", k, balance3[k] )   }}以上实例执行结果如下:balance[0] = 1000.000000balance[1] = 2.000000balance[2] = 3.400000balance[3] = 7.000000balance[4] = 50.000000balance2[0] = 1000.000000balance2[1] = 2.000000balance2[2] = 3.400000balance2[3] = 7.000000balance2[4] = 50.000000balance3[0] = 0.000000balance3[1] = 2.000000balance3[2] = 0.000000balance3[3] = 7.000000balance3[4] = 0.000000
  • [技术干货] go语言函数的作用域
    Go 语言变量作用域作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。Go 语言中变量可以在三个地方声明:函数内定义的变量称为局部变量函数外定义的变量称为全局变量函数定义中的变量称为形式参数接下来让我们具体了解局部变量、全局变量和形式参数。局部变量在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。以下实例中 main() 函数使用了局部变量 a, b, c:实例package mainimport "fmt"func main() {   /* 声明局部变量 */   var a, b, c int    /* 初始化参数 */   a = 10   b = 20   c = a + b   fmt.Printf ("结果: a = %d, b = %d and c = %d\n", a, b, c)}以上实例执行输出结果为:结果: a = 10, b = 20 and c = 30全局变量在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。全局变量可以在任何函数中使用,以下实例演示了如何使用全局变量:实例package mainimport "fmt"/* 声明全局变量 */var g intfunc main() {   /* 声明局部变量 */   var a, b int   /* 初始化参数 */   a = 10   b = 20   g = a + b   fmt.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)}以上实例执行输出结果为:结果: a = 10, b = 20 and g = 30Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。实例如下:实例package mainimport "fmt"/* 声明全局变量 */var g int = 20func main() {   /* 声明局部变量 */   var g int = 10   fmt.Printf ("结果: g = %d\n",  g)}以上实例执行输出结果为:结果: g = 10形式参数形式参数会作为函数的局部变量来使用。实例如下:实例package mainimport "fmt"/* 声明全局变量 */var a int = 20;func main() {   /* main 函数中声明局部变量 */   var a int = 10   var b int = 20   var c int = 0   fmt.Printf("main()函数中 a = %d\n",  a);   c = sum( a, b);   fmt.Printf("main()函数中 c = %d\n",  c);}/* 函数定义-两数相加 */func sum(a, b int) int {   fmt.Printf("sum() 函数中 a = %d\n",  a);   fmt.Printf("sum() 函数中 b = %d\n",  b);   return a + b;}以上实例执行输出结果为:main()函数中 a = 10sum() 函数中 a = 10sum() 函数中 b = 20main()函数中 c = 30
  • [技术干货] go语言函数
    Go 语言函数函数是基本的代码块,用于执行一个任务。Go 语言最少有个 main() 函数。你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。函数声明告诉了编译器函数的名称,返回类型,和参数。Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。函数定义Go 语言函数定义格式如下:func function_name( [parameter list] ) [return_types] {   函数体}函数定义解析:func:函数由 func 开始声明function_name:函数名称,参数列表和返回值类型构成了函数签名。parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。函数体:函数定义的代码集合。实例以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 和 num2,并返回这两个参数的最大值:实例/* 函数返回两个数的最大值 */func max(num1, num2 int) int {   /* 声明局部变量 */   var result int   if (num1 > num2) {      result = num1   } else {      result = num2   }   return result }函数调用当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。调用函数,向函数传递参数,并返回值,例如:实例package mainimport "fmt"func main() {   /* 定义局部变量 */   var a int = 100   var b int = 200   var ret int   /* 调用函数并返回最大值 */   ret = max(a, b)   fmt.Printf( "最大值是 : %d\n", ret )}/* 函数返回两个数的最大值 */func max(num1, num2 int) int {   /* 定义局部变量 */   var result int   if (num1 > num2) {      result = num1   } else {      result = num2   }   return result }以上实例在 main() 函数中调用 max()函数,执行结果为:最大值是 : 200函数返回多个值Go 函数可以返回多个值,例如:实例package mainimport "fmt"func swap(x, y string) (string, string) {   return y, x}func main() {   a, b := swap("Google", "Runoob")   fmt.Println(a, b)}以上实例执行结果为:Runoob Google
  • [技术干货] Go中的if和循环
    Go 语言条件语句条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。下图展示了程序语言中条件语句的结构:Go 语言提供了以下几种条件判断语句:语句 描述if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成。if...else 语句 if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。if 嵌套语句 你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。switch 语句 switch 语句用于基于不同条件执行不同动作。select 语句 select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。注意:Go 没有三目运算符,所以不支持 ?: 形式的条件判断。Go 语言循环语句在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。Go 语言提供了以下几种类型循环处理语句:循环类型 描述for 循环 重复执行语句块循环嵌套 在 for 循环中嵌套一个或多个 for 循环循环控制语句循环控制语句可以控制循环体内语句的执行过程。GO 语言支持以下几种循环控制语句:控制语句 描述break 语句 经常用于中断当前 for 循环或跳出 switch 语句continue 语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。goto 语句 将控制转移到被标记的语句。无限循环如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式来执行无限循环:实例package mainimport "fmt"func main() {    for true  {        fmt.Printf("这是无限循环。\n");    }}
  • [技术干货] go语言常量
    Go 语言常量常量是一个简单值的标识符,在程序运行时,不会被修改的量。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。常量的定义格式:const identifier [type] = value你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。显式类型定义: const b string = "abc"隐式类型定义: const b = "abc"多个相同类型的声明可以简写为:const c_name1, c_name2 = value1, value2以下实例演示了常量的应用:实例package mainimport "fmt"func main() {   const LENGTH int = 10   const WIDTH int = 5      var area int   const a, b, c = 1, false, "str" //多重赋值   area = LENGTH * WIDTH   fmt.Printf("面积为 : %d", area)   println()   println(a, b, c)   }以上实例运行结果为:面积为 : 501 false str常量还可以用作枚举:const (    Unknown = 0    Female = 1    Male = 2)数字 0、1 和 2 分别代表未知性别、女性和男性。常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:实例package mainimport "unsafe"const (    a = "abc"    b = len(a)    c = unsafe.Sizeof(a))func main(){    println(a, b, c)}以上实例运行结果为:abc 3 16iotaiota,特殊常量,可以认为是一个可以被编译器修改的常量。iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。iota 可以被用作枚举值:const (    a = iota    b = iota    c = iota)第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:const (    a = iota    b    c)iota 用法实例package mainimport "fmt"func main() {    const (            a = iota   //0            b          //1            c          //2            d = "ha"   //独立值,iota += 1            e          //"ha"   iota += 1            f = 100    //iota +=1            g          //100  iota +=1            h = iota   //7,恢复计数            i          //8    )    fmt.Println(a,b,c,d,e,f,g,h,i)}以上实例运行结果为:0 1 2 ha ha 100 100 7 8再看个有趣的的 iota 实例:实例package mainimport "fmt"const (    i=1<<iota    j=3<<iota    k    l)func main() {    fmt.Println("i=",i)    fmt.Println("j=",j)    fmt.Println("k=",k)    fmt.Println("l=",l)}以上实例运行结果为:i= 1j= 6k= 12l= 24iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3。
  • [技术干货] go语言变量
    Go 语言变量变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。声明变量的一般形式是使用 var 关键字:var identifier type可以一次声明多个变量:var identifier1, identifier2 type实例package mainimport "fmt"func main() {    var a string = "Runoob"    fmt.Println(a)    var b, c int = 1, 2    fmt.Println(b, c)}以上实例输出结果为:Runoob1 2变量声明第一种,指定变量类型,如果没有初始化,则变量默认为零值。var v_name v_typev_name = value零值就是变量没有做初始化时系统默认设置的值。实例package mainimport "fmt"func main() {    // 声明一个变量并初始化    var a = "RUNOOB"    fmt.Println(a)    // 没有初始化就为零值    var b int    fmt.Println(b)    // bool 零值为 false    var c bool    fmt.Println(c)}以上实例执行结果为:RUNOOB0false数值类型(包括complex64/128)为 0布尔类型为 false字符串为 ""(空字符串)以下几种类型为 nil:var a *intvar a []intvar a map[string] intvar a chan intvar a func(string) intvar a error // error 是接口实例package mainimport "fmt"func main() {    var i int    var f float64    var b bool    var s string    fmt.Printf("%v %v %v %q\n", i, f, b, s)}输出结果是:0 0 false ""第二种,根据值自行判定变量类型。var v_name = value实例package mainimport "fmt"func main() {    var d = true    fmt.Println(d)}输出结果是:true
  • [技术干货] go语言的基础语法
    Go 标记Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:fmt.Println("Hello, World!")6 个标记是(每行一个):1. fmt2. .3. Println4. (5. "Hello, World!"6. )行分隔符在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。以下为两个语句:fmt.Println("Hello, World!")fmt.Println(":runoob.com")注释注释不会被编译,每一个包应该有相关注释。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:// 单行注释/* Author by 菜鸟教程 我是多行注释 */标识符标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。以下是有效的标识符:mahesh   kumar   abc   move_name   a_123myname50   _temp   j   a23b9   retVal以下是无效的标识符:1ab(以数字开头)case(Go 语言的关键字)a+b(运算符是不允许的)字符串连接Go 语言的字符串可以通过 + 实现:实例package mainimport "fmt"func main() {    fmt.Println("Google" + "Runoob")}
  • [技术干货] go语言的结构
    Go Hello World 实例Go 语言的基础组成有以下几个部分:包声明引入包函数变量语句 & 表达式注释接下来让我们来看下简单的代码,该代码输出了"Hello World!":实例package mainimport "fmt"func main() {   /* 这是我的第一个简单的程序 */   fmt.Println("Hello, World!")}让我们来看下以上程序的各个部分:第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。下一行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。下一行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。执行 Go 程序让我们来看下如何编写 Go 代码并执行它。步骤如下:打开编辑器如Sublime2,将以上代码添加到编辑器中。将以上代码保存为 hello.go打开命令行,并进入程序文件保存的目录中。输入命令 go run hello.go 并按回车执行代码。如果操作正确你将在屏幕上看到 "Hello World!" 字样的输出。$ go run hello.goHello, World!我们还可以使用 go build 命令来生成二进制文件:$ go build hello.go $ lshello    hello.go$ ./hello Hello, World!注意需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:实例package mainimport "fmt"func main()  {  // 错误,{ 不能在单独的行上    fmt.Println("Hello, World!")}
  • [技术干货] 华为大咖分享:Gopher China2020 华为云的go语言云原生实战经验(后附PPT下载)
    回复本贴查看下载完整版PPT点击 →《华为云DevCloud大咖分享汇总(附PPT下载)》[hide]https://devcloud.cn-north-4.huaweicloud.com/codehub/project/8e8846873de740e396de90d8182b63fd/codehub/1284981/home/commit/4c77cfc5d0ea1b56a91e5df12605bb5f508a503d?ref=master&filePath=Gopher%20China2020%20%E5%8D%8E%E4%B8%BA%E4%BA%91%E7%9A%84go%E8%AF%AD%E8%A8%80%E4%BA%91%E5%8E%9F%E7%94%9F%E5%AE%9E%E6%88%98%E7%BB%8F%E9%AA%8C.pdf&isFile=true[/hide]
  • [问题求助] 运行startGoClient.sh 提示: ./go-client: No such file or directory
    运行根据自己的项目修改的startGoClient.sh文件,总是报错:这是项目结构config内部的结构
  • [问题求助] 输入./startGoClient.sh,提示:./go-client: No such file or directory
    参考go-demo-sdk编写了对智能合约进行增删查改的命令,建立了应用项目,按照华为官网的提示配置文件和运行相关命令,可是在输入之后提示:./startGoClient.sh: line 28: ./go-client: No such file or directory./startGoClient.sh: line 31: ./go-client: No such file or directory尝试以各种方式重新构建项目,还是不行,请问大家都是怎么建立包含命令的项目的呀?有遇到类似情况的伙伴可以分享一下解决办法吗?
  • [技术干货] 快来,这里有23种设计模式的Go语言实现(一)
    【摘要】 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。前言从1995年GoF提出23种设计模式到现在,25年过去了,设计模式依旧是软件领域的热门话题。在当下,如果你不会一点设计模式,都不好意思说自己是一个合格的程序员。设计模式通常被定义为:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。从定义上看,设计模式其实是一种经验的总结,是针对特定问题的简洁而优雅的解决方案。既然是经验总结,那么学习设计模式最直接的好处就在于可以站在巨人的肩膀上解决软件开发过程中的一些特定问题。然而,学习设计模式的最高境界是习得其中解决问题所用到的思想,当你把它们的本质思想吃透了,也就能做到即使已经忘掉某个设计模式的名称和结构,也能在解决特定问题时信手拈来。好的东西有人吹捧,当然也会招黑。设计模式被抨击主要因为以下两点:1、设计模式会增加代码量,把程序逻辑变得复杂。这一点是不可避免的,但是我们并不能仅仅只考虑开发阶段的成本。最简单的程序当然是一个函数从头写到尾,但是这样后期的维护成本会变得非常大;而设计模式虽然增加了一点开发成本,但是能让人们写出可复用、可维护性高的程序。引用《软件设计的哲学》里的概念,前者就是战术编程,后者就是战略编程,我们应该对战术编程Say No!(请移步《专家教你系列<三>:写出的代码复杂度太高?看下专家怎么说》)2、滥用设计模式。这是初学者最容易犯的错误,当学到一个模式时,恨不得在所有的代码都用上,从而在不该使用模式的地方刻意地使用了模式,导致了程序变得异常复杂。其实每个设计模式都有几个关键要素:适用场景、解决方法、优缺点。模式并不是万能药,它只有在特定的问题上才能显现出效果。所以,在使用一个模式前,先问问自己,当前的这个场景适用这个模式吗?《设计模式》一书的副标题是“可复用面向对象软件的基础”,但并不意味着只有面向对象语言才能使用设计模式。模式只是一种解决特定问题的思想,跟语言无关。就像Go语言一样,它并非是像C++和Java一样的面向对象语言,但是设计模式同样适用。本系列文章将使用Go语言来实现GoF提出的23种设计模式,按照创建型模式(Creational Pattern)、结构型模式(Structural Pattern)和行为型模式(Behavioral Pattern)三种类别进行组织,文本主要介绍其中的创建型模式。单例模式(Singleton Pattern)简述单例模式算是23中设计模式里最简单的一个了,它主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。在程序设计中,有一些对象通常我们只需要一个共享的实例,比如线程池、全局缓存、对象池等,这种场景下就适合使用单例模式。但是,并非所有全局唯一的场景都适合使用单例模式。比如,考虑需要统计一个API调用的情况,有两个指标,成功调用次数和失败调用次数。这两个指标都是全局唯一的,所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路,随着指标数量的增多,你会发现代码里类的定义会越来越多,也越来越臃肿。这也是单例模式最常见的误用场景,更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。如何判断一个对象是否应该被建模成单例?通常,被建模成单例的对象都有“中心点”的含义,比如线程池就是管理所有线程的中心。所以,在判断一个对象是否适合单例模式时,先思考下,这个对象是一个中心点吗?Go实现在对某个对象实现单例模式时,有两个点必须要注意:(1)限制调用者直接实例化该对象;(2)为该对象的单例提供一个全局唯一的访问方法。对于C++/Java而言,只需把类的构造函数设计成私有的,并提供一个static方法去访问该类点唯一实例即可。但对于Go语言来说,即没有构造函数的概念,也没有static方法,所以需要另寻出路。我们可以利用Go语言package的访问规则来实现,将单例结构体设计成首字母小写,就能限定其访问范围只在当前package下,模拟了C++/Java中的私有构造函数;再在当前package下实现一个首字母大写的访问函数,就相当于static方法的作用了。在实际开发中,我们经常会遇到需要频繁创建和销毁的对象。频繁的创建和销毁一则消耗CPU,二则内存的利用率也不高,通常我们都会使用对象池技术来进行优化。考虑我们需要实现一个消息对象池,因为是全局的中心点,管理所有的Message实例,所以将其实现成单例,实现代码如下: package msgpool ... // 消息池 type messagePool struct { pool *sync.Pool } // 消息池单例 var msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } // 访问消息池单例的唯一方法 func Instance() *messagePool { return msgPool } // 往消息池里添加消息 func (m *messagePool) AddMsg(msg *Message) { m.pool.Put(msg) } // 从消息池里获取消息 func (m *messagePool) GetMsg() *Message { return m.pool.Get().(*Message) } ...测试代码如下: package test ... func TestMessagePool(t *testing.T) { msg0 := msgpool.Instance().GetMsg() if msg0.Count != 0 { t.Errorf("expect msg count %d, but actual %d.", 0, msg0.Count) } msg0.Count = 1 msgpool.Instance().AddMsg(msg0) msg1 := msgpool.Instance().GetMsg() if msg1.Count != 1 { t.Errorf("expect msg count %d, but actual %d.", 1, msg1.Count) } } // 运行结果 === RUN TestMessagePool --- PASS: TestMessagePool (0.00s) PASS以上的单例模式就是典型的“饿汉模式”,实例在系统加载的时候就已经完成了初始化。对应地,还有一种“懒汉模式”,只有等到对象被使用的时候,才会去初始化它,从而一定程度上节省了内存。众所周知,“懒汉模式”会带来线程安全问题,可以通过普通加锁,或者更高效的双重检验锁来优化。对于“懒汉模式”,Go语言有一个更优雅的实现方式,那就是利用sync.Once,它有一个Do方法,其入参是一个方法,Go语言会保证仅仅只调用一次该方法。 // 单例模式的“懒汉模式”实现 package msgpool ... var once = &sync.Once{} // 消息池单例,在首次调用时初始化 var msgPool *messagePool // 全局唯一获取消息池pool到方法 func Instance() *messagePool { // 在匿名函数中实现初始化逻辑,Go语言保证只会调用一次 once.Do(func() { msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } }) return msgPool } ...建造者模式(Builder Pattern)简述在程序设计中,我们会经常遇到一些复杂的对象,其中有很多成员属性,甚至嵌套着多个复杂的对象。这种情况下,创建这个复杂对象就会变得很繁琐。对于C++/Java而言,最常见的表现就是构造函数有着长长的参数列表: MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...)而对于Go语言来说,最常见的表现就是多层的嵌套实例化: obj := &MyObject{ Field1: &Field1 { Param1: &Param1 { Val: 0, }, Param2: &Param2 { Val: 1, }, ... }, Field2: &Field2 { Param3: &Param3 { Val: 2, }, ... }, ... }上述的对象创建方法有两个明显的缺点:(1)对对象使用者不友好,使用者在创建对象时需要知道的细节太多;(2)代码可读性很差。针对这种对象成员较多,创建对象逻辑较为繁琐的场景,就适合使用建造者模式来进行优化。建造者模式的作用有如下几个:1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。3、对多个对象复用同样的对象创建逻辑。其中,第1和第2点比较常用,下面对建造者模式的实现也主要是针对这两点进行示例。Go实现考虑如下的一个Message结构体,其主要有Header和Body组成: package msg ... type Message struct { Header *Header Body *Body } type Header struct { SrcAddr string SrcPort uint64 DestAddr string DestPort uint64 Items map[string]string } type Body struct { Items []string } ...如果按照直接的对象创建方式,创建逻辑应该是这样的: // 多层的嵌套实例化 message := msg.Message{ Header: &msg.Header{ SrcAddr: "192.168.0.1", SrcPort: 1234, DestAddr: "192.168.0.2", DestPort: 8080, Items: make(map[string]string), }, Body: &msg.Body{ Items: make([]string, 0), }, } // 需要知道对象的实现细节 message.Header.Items["contents"] = "application/json" message.Body.Items = append(message.Body.Items, "record1") message.Body.Items = append(message.Body.Items, "record2")虽然Message结构体嵌套的层次不多,但是从其创建的代码来看,确实存在对对象使用者不友好和代码可读性差的缺点。下面我们引入建造者模式对代码进行重构: package msg ... // Message对象的Builder对象 type builder struct { once *sync.Once msg *Message } // 返回Builder对象 func Builder() *builder { return &builder{ once: &sync.Once{}, msg: &Message{Header: &Header{}, Body: &Body{}}, } } // 以下是对Message成员对构建方法 func (b *builder) WithSrcAddr(srcAddr string) *builder { b.msg.Header.SrcAddr = srcAddr return b } func (b *builder) WithSrcPort(srcPort uint64) *builder { b.msg.Header.SrcPort = srcPort return b } func (b *builder) WithDestAddr(destAddr string) *builder { b.msg.Header.DestAddr = destAddr return b } func (b *builder) WithDestPort(destPort uint64) *builder { b.msg.Header.DestPort = destPort return b } func (b *builder) WithHeaderItem(key, value string) *builder { // 保证map只初始化一次 b.once.Do(func() { b.msg.Header.Items = make(map[string]string) }) b.msg.Header.Items[key] = value return b } func (b *builder) WithBodyItem(record string) *builder { b.msg.Body.Items = append(b.msg.Body.Items, record) return b } // 创建Message对象,在最后一步调用 func (b *builder) Build() *Message { return b.msg }测试代码如下: package test ... func TestMessageBuilder(t *testing.T) { // 使用消息建造者进行对象创建 message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() if message.Header.SrcAddr != "192.168.0.1" { t.Errorf("expect src address 192.168.0.1, but actual %s.", message.Header.SrcAddr) } if message.Body.Items[0] != "record1" { t.Errorf("expect body item0 record1, but actual %s.", message.Body.Items[0]) } } // 运行结果 === RUN TestMessageBuilder --- PASS: TestMessageBuilder (0.00s) PASS从测试代码可知,使用建造者模式来进行对象创建,使用者不再需要知道对象具体的实现细节,代码可读性也更好。工厂方法模式(Factory Method Pattern)简述工厂方法模式跟上一节讨论的建造者模式类似,都是将对象创建的逻辑封装起来,为使用者提供一个简单易用的对象创建接口。两者在应用场景上稍有区别,建造者模式更常用于需要传递多个参数来进行实例化的场景。使用工厂方法来创建对象主要有两个好处:1、代码可读性更好。相比于使用C++/Java中的构造函数,或者Go中的{}来创建对象,工厂方法因为可以通过函数名来表达代码含义,从而具备更好的可读性。比如,使用工厂方法productA := CreateProductA()创建一个ProductA对象,比直接使用productA := ProductA{}的可读性要好。2、与使用者代码解耦。很多情况下,对象的创建往往是一个容易变化的点,通过工厂方法来封装对象的创建过程,可以在创建逻辑变更时,避免霰弹式修改。工厂方法模式也有两种实现方式:(1)提供一个工厂对象,通过调用工厂对象的工厂方法来创建产品对象;(2)将工厂方法集成到产品对象中(C++/Java中对象的static方法,Go中同一package下的函数)Go实现考虑有一个事件对象Event,分别有两种有效的时间类型Start和End: package event ... type Type uint8 // 事件类型定义 const ( Start Type = iota End ) // 事件抽象接口 type Event interface { EventType() Type Content() string } // 开始事件,实现了Event接口 type StartEvent struct{ content string } ... // 结束事件,实现了Event接口 type EndEvent struct{ content string } ...1、按照第一种实现方式,为Event提供一个工厂对象,具体代码如下: package event ... // 事件工厂对象 type Factory struct{} // 更具事件类型创建具体事件 func (e *Factory) Create(etype Type) Event { switch etype { case Start: return &StartEvent{ content: "this is start event", } case End: return &EndEvent{ content: "this is end event", } default: return nil } }测试代码如下: package test ... func TestEventFactory(t *testing.T) { factory := event.Factory{} e := factory.Create(event.Start) if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = factory.Create(event.End) if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEventFactory --- PASS: TestEventFactory (0.00s) PASS2、按照第二种实现方式,分别给Start和End类型的Event单独提供一个工厂方法,代码如下: package event ... // Start类型Event的工厂方法 func OfStart() Event { return &StartEvent{ content: "this is start event", } } // End类型Event的工厂方法 func OfEnd() Event { return &EndEvent{ content: "this is end event", } }测试代码如下: package event ... func TestEvent(t *testing.T) { e := event.OfStart() if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = event.OfEnd() if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEvent --- PASS: TestEvent (0.00s) PASS抽象工厂模式(Abstract Factory Pattern)简述在工厂方法模式中,我们通过一个工厂对象来创建一个产品族,具体创建哪个产品,则通过swtich-case的方式去判断。这也意味着该产品组上,每新增一类产品对象,都必须修改原来工厂对象的代码;而且随着产品的不断增多,工厂对象的职责也越来越重,违反了单一职责原则。抽象工厂模式通过给工厂类新增一个抽象层解决了该问题,如上图所示,FactoryA和FactoryB都实现·抽象工厂接口,分别用于创建ProductA和ProductB。如果后续新增了ProductC,只需新增一个FactoryC即可,无需修改原有的代码;因为每个工厂只负责创建一个产品,因此也遵循了单一职责原则。Go实现考虑需要如下一个插件架构风格的消息处理系统,pipeline是消息处理的管道,其中包含了input、filter和output三个插件。我们需要实现根据配置来创建pipeline ,加载插件过程的实现非常适合使用工厂模式,其中input、filter和output三类插件的创建使用抽象工厂模式,而pipeline的创建则使用工厂方法模式。各类插件和pipeline的接口定义如下: package plugin ... // 插件抽象接口定义 type Plugin interface {} // 输入插件,用于接收消息 type Input interface { Plugin Receive() string } // 过滤插件,用于处理消息 type Filter interface { Plugin Process(msg string) string } // 输出插件,用于发送消息 type Output interface { Plugin Send(msg string) } package pipeline ... // 消息管道的定义 type Pipeline struct { input plugin.Input filter plugin.Filter output plugin.Output } // 一个消息的处理流程为 input -> filter -> output func (p *Pipeline) Exec() { msg := p.input.Receive() msg = p.filter.Process(msg) p.output.Send(msg) }接着,我们定义input、filter、output三类插件接口的具体实现: package plugin ... // input插件名称与类型的映射关系,主要用于通过反射创建input对象 var inputNames = make(map[string]reflect.Type) // Hello input插件,接收“Hello World”消息 type HelloInput struct {} ​ func (h *HelloInput) Receive() string { return "Hello World" } // 初始化input插件映射关系表 func init() { inputNames["hello"] = reflect.TypeOf(HelloInput{}) } package plugin ... // filter插件名称与类型的映射关系,主要用于通过反射创建filter对象 var filterNames = make(map[string]reflect.Type) // Upper filter插件,将消息全部字母转成大写 type UpperFilter struct {} ​ func (u *UpperFilter) Process(msg string) string { return strings.ToUpper(msg) } // 初始化filter插件映射关系表 func init() { filterNames["upper"] = reflect.TypeOf(UpperFilter{}) } package plugin ... // output插件名称与类型的映射关系,主要用于通过反射创建output对象 var outputNames = make(map[string]reflect.Type) // Console output插件,将消息输出到控制台上 type ConsoleOutput struct {} ​ func (c *ConsoleOutput) Send(msg string) { fmt.Println(msg) } // 初始化output插件映射关系表 func init() { outputNames["console"] = reflect.TypeOf(ConsoleOutput{}) }然后,我们定义插件抽象工厂接口,以及对应插件的工厂实现: package plugin ... // 插件抽象工厂接口 type Factory interface { Create(conf Config) Plugin } // input插件工厂对象,实现Factory接口 type InputFactory struct{} // 读取配置,通过反射机制进行对象实例化 func (i *InputFactory) Create(conf Config) Plugin { t, _ := inputNames[conf.Name] return reflect.New(t).Interface().(Plugin) } // filter和output插件工厂实现类似 type FilterFactory struct{} func (f *FilterFactory) Create(conf Config) Plugin { t, _ := filterNames[conf.Name] return reflect.New(t).Interface().(Plugin) } type OutputFactory struct{} func (o *OutputFactory) Create(conf Config) Plugin { t, _ := outputNames[conf.Name] return reflect.New(t).Interface().(Plugin) }最后定义pipeline的工厂方法,调用plugin.Factory抽象工厂完成pipelien对象的实例化: package pipeline ... // 保存用于创建Plugin的工厂实例,其中map的key为插件类型,value为抽象工厂接口 var pluginFactories = make(map[plugin.Type]plugin.Factory) // 根据plugin.Type返回对应Plugin类型的工厂实例 func factoryOf(t plugin.Type) plugin.Factory { factory, _ := pluginFactories[t] return factory } // pipeline工厂方法,根据配置创建一个Pipeline实例 func Of(conf Config) *Pipeline { p := &Pipeline{} p.input = factoryOf(plugin.InputType).Create(conf.Input).(plugin.Input) p.filter = factoryOf(plugin.FilterType).Create(conf.Filter).(plugin.Filter) p.output = factoryOf(plugin.OutputType).Create(conf.Output).(plugin.Output) return p } // 初始化插件工厂对象 func init() { pluginFactories[plugin.InputType] = &plugin.InputFactory{} pluginFactories[plugin.FilterType] = &plugin.FilterFactory{} pluginFactories[plugin.OutputType] = &plugin.OutputFactory{} }测试代码如下: package test ... func TestPipeline(t *testing.T) { // 其中pipeline.DefaultConfig()的配置内容见【抽象工厂模式示例图】 // 消息处理流程为 HelloInput -> UpperFilter -> ConsoleOutput p := pipeline.Of(pipeline.DefaultConfig()) p.Exec() } // 运行结果 === RUN TestPipeline HELLO WORLD --- PASS: TestPipeline (0.00s) PASS原型模式(Prototype Pattern)简述原型模式主要解决对象复制的问题,它的核心就是clone()方法,返回Prototype对象的复制品。在程序设计过程中,往往会遇到有一些场景需要大量相同的对象,如果不使用原型模式,那么我们可能会这样进行对象的创建:新创建一个相同对象的实例,然后遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。这种方法的缺点很明显,那就是使用者必须知道对象的实现细节,导致代码之间的耦合。另外,对象很有可能存在除了对象本身以外不可见的变量,这种情况下该方法就行不通了。对于这种情况,更好的方法就是使用原型模式,将复制逻辑委托给对象本身,这样,上述两个问题也都迎刃而解了。Go实现还是以建造者模式一节中的Message作为例子,现在设计一个Prototype抽象接口: package prototype ... // 原型复制抽象接口 type Prototype interface { clone() Prototype } ​ type Message struct { Header *Header Body *Body } ​ func (m *Message) clone() Prototype { msg := *m return &msg }测试代码如下: package test ... func TestPrototype(t *testing.T) { message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() // 复制一份消息 newMessage := message.Clone().(*msg.Message) if newMessage.Header.SrcAddr != message.Header.SrcAddr { t.Errorf("Clone Message failed.") } if newMessage.Body.Items[0] != message.Body.Items[0] { t.Errorf("Clone Message failed.") } } // 运行结果 === RUN TestPrototype --- PASS: TestPrototype (0.00s) PASS总结本文主要介绍了GoF的23种设计模式中的5种创建型模式,创建型模式的目的都是提供一个简单的接口,让对象的创建过程与使用者解耦。其中,单例模式主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点;建造者模式主要解决需要创建对象时需要传入多个参数,或者对初始化顺序有要求的场景;工厂方法模式通过提供一个工厂对象或者工厂方法,为使用者隐藏了对象创建的细节;抽象工厂模式是对工厂方法模式的优化,通过为工厂对象新增一个抽象层,让工厂对象遵循单一职责原则,也避免了霰弹式修改;原型模式则让对象复制更加简单。下一篇文章,将介绍23种设计模式中的7种结构型模式(Structural Pattern),及其Go语言的实现。