Goland入门学习

一. GO入门

目录

一. GO入门

1. cmd界面运行

2. Golang执行流程分析

3. GO开发注意事项

4. Go语言的转义字符

5. GO语言注释

6. GO的文档

7. 输出类型printf

二. 编程

1. Golang变量

2. 多变量声明

3. 变量的声明,初始化和赋值

4. 程序中 + 号的使用

5. 数据类型

6. 标识符的命名规范

7. 运算符介绍

8. 键盘输入语句

9. *原码反码补码

10. 流程控制

11. *函数(重要!!!)

12. 包

13. 递归

14. 闭包

15. defer函数---延时机制

16. **函数的传递方式

17. Go中的内置函数

18. **错误/异常机制

19. 数组

20. 切片

21. 排序和查找

22. 二维数组

23. Map

三. Go-Kit

1. Go-kit简介

2. Go-kit与Go-Micro区别

3. 当前集成工具

4. 框架结构

四. Gorm


1. cmd界面运行

 D:\Workplace\GO_shangguigu\src\go_code\project01\main>go build hello.go
 ​
 D:\Workplace\GO_shangguigu\src\go_code\project01\main>hello.exe
 Hello,World!
 ​
 //或者直接执行go run hello.go 但是原理和上面一样都要编译然后执行,只不过这个相当于一个脚本,执行起来会有点慢
 D:\Workplace\GO_shangguigu\src\go_code\project01\main>go run hello.go
 Hello,World!

2. Golang执行流程分析

 

3. GO开发注意事项

  1. Go源文件以”go“为扩展名

  2. Go应用程序的执行入口是main()函数

  3. Go语言严格区分大小写

  4. Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也是体现了Go的简洁性

  5. Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一行,否则报错

  6. Go语言定义的变量或者import的包如果没有使用到,代码就不能编译通过

  7. 大括号是成对出现的,缺一不可

4. Go语言的转义字符

\a匹配响铃符 (相当于 \x07) 注意:正则表达式中不能使用 \b 匹配退格符,因为 \b 被用来匹配单词边界, 可以使用 \x08 表示退格符。
\f匹配换页符 (相当于 \x0C)
\t匹配横向制表符(相当于 \x09)
\n匹配换行符 (相当于 \x0A)
\r匹配回车符 (相当于 \x0D)
\v匹配纵向制表符(相当于 \x0B)
\123匹配 8 進制编码所代表的字符(必须是 3 位数字)
\x7F匹配 16 進制编码所代表的字符(必须是 3 位数字)
\x{10FFFF}匹配 16 進制编码所代表的字符(最大值 10FFFF )
\Q...\E匹配 \Q 和 \E 之间的文本,忽略文本中的正则语法
\\匹配字符 \
\^匹配字符 ^
\$匹配字符 $

5. GO语言注释

  1. 行注释

    //

  2. 块注释

    /* 注释文字*/

6. GO的文档

Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国

7. 输出类型printf

格式输出
printf("%c",a)输出单个字符
printf("%d",a)输出十进制整数
printf("%f",a)printf("%f",a)
printf("%f",a)输出八进制数
printf("%s",a)printf("%s",a)
printf("%s",a)输出无符号十进制数
printf("%x",a)输出十六进制数

二. 编程

1. Golang变量

1. 说明:

  1. 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部

  2. 函数为外部声明/定义的变量叫做全局变量,作用域在整个包都有效,如果其首字母为大写,则在整个程序有效。

  3. 第一种:指定变量类型,声明后若不赋值,使用默认值

//定义变量/声明变量
	var i int
	//给 i 赋值
	i = 10
	//使用变量
	fmt.Println("i=", i)
  1. 第二种:根据值自行判定变量类型(类型推导)

	var num = 10.11
	fmt.Println("num = ", num)
  1. 第三种:省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误

	name := "tom"
	fmt.Println("name = ", name)

2. 多变量声明

package main

import "fmt"

/**
  多变量的声明
*/
func main() {
	//一次性声明多个变量
	var n1, n2, n3 int
	fmt.Println("n1 = ", n1, "n2 = ", n2, "n3", n3)

	//一次性声明多个变量的方式2
	var name, age, gender = "男", 18, "YG"
	fmt.Println("name = ", name, "age = ", age, "gender = ", gender)

	//一次性声明多个变量的方式3
	name1, age1, gender1 := "男", 18, "YG"
	fmt.Println("name1 = ", name1, "age1 = ", age1, "gender1 = ", gender1)
}
  1. 变量=变量名+值+数据类型

  2. Golang 的变量如果没有赋初值,编译器会使用默认值, 比如 int 默认值 0 string 默认值为空串, 小数默认为 0

  3. 获取数据的类型和占用字节

name := "tom"
	fmt.Println("name = ", name)
	//获取该变量的类型
	fmt.Printf("name 的类型是 %T", name)
	//返回占用的字节数
	fmt.Printf("name 的类型是 %T 占用的字节数是 %d", name, unsafe.Sizeof(name))

3. 变量的声明,初始化和赋值

 

4. 程序中 + 号的使用

  1. 当左右两边都是数值型时,则做加法运算

  2. 当左右两边都是字符串,则做字符串拼接

5. 数据类型

 

-1. 派生/复杂数据类型

1布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4派生类型: 包括:(a) 指针类型(Pointer)、(b) 数组类型 、(c) 结构化类型(struct) 、(d) Channel 类型 、(e) 函数类型 、(f) 切片类型 、(g) 接口类型(interface)、(h) Map 类型

-2. 数值型

序号类型和描述
1uint8 无符号 8 位整型 (0 到 255)
2uint16 无符号 16 位整型 (0 到 65535)
3uint32 无符号 32 位整型 (0 到 4294967295)
4uint64 无符号 64 位整型 (0 到 18446744073709551615)
5int8 有符号 8 位整型 (-128 到 127)
6int16 有符号 16 位整型 (-32768 到 32767)
7int32 有符号 32 位整型 (-2147483648 到 2147483647)
8int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

-3. 浮点型

序号类型和描述
1float32 IEEE-754 32位浮点型数
2float64 IEEE-754 64位浮点型数
3complex64 32 位实数和虚数
4complex128 64 位实数和虚数

-4. 其他数字类型

序号类型和描述
1byte 类似 uint8
2rune 类似 int32
3uint 32 或 64 位
4int 与 uint 一样大小
5uintptr 无符号整型,用于存放一个指针

bit: 计算机中的最小存储单位。byte:计算机中基本存储单元。 1byte = 8bit

-5. 浮点型的使用细节

  1. Golang 浮点类型有固定的范围和字段长度,不受具体 OS(操作系统)的影响。

  2. Golang 的浮点型默认声明为 float64 类型

  3. 通常情况下,应该使用 float64 ,因为它比 float32 更精确。[开发中,推荐使用 float64

-6. 字符类型

Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存。

字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。也 就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的。

  1. 如果我们保存的字符在 ASCII 表的,比如[0-1, a-z,A-Z..]直接可以保存到 byte

  2. 如果我们保存的字符对应码值大于 255,这时我们可以考虑使用 int 类型保存

  3. 如果我们需要安装字符的方式输出,这时我们需要格式化输出,即 fmt.Printf

细节

  1. 字符常量是用单引号('')括起来的单个字符。例如:var c1 byte = 'a' var c2 int = '中' var c3 byte = '9'

  2. Go 中允许使用转义字符 '\’来将其后的字符转变为特殊字符型常量。例如:var c3 char = ‘\n’ // '\n'表示换行符

  3. Go 语 言 的 字 符 使 用 UTF-8 编 码 , 如 果 想 查 询 字 符 对 应 的 utf8 码 值 查看字符编码(UTF-8) 英文字母-1 个字节 汉字-3 个字节

  4. 在 Go 中,字符的本质是一个整数,直接输出时,是该字符对应的 UTF-8 编码的码值。

  5. 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的 unicode 字

-7. 布尔类型

  1. 布尔类型也叫 bool 类型,bool 类型数据只允许取值 true 和 false(不能用数字表示)

  2. bool 类型占 1 个字节。

  3. bool 类型适于逻辑运算,一般用于程序流程控制

-8. 字符串类型

  1. Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本,这样 Golang 统一使用 UTF-8 编码,中文 乱码问题不会再困扰程序员。

  2. 字符串一旦赋值了,字符串就不能修改了:在 Go 中字符串是不可变的

  3. 字符串的两种表示形式

    1. 双引号, 会识别转义字符

    2. 反引号( ``),以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果

    3. 字符串拼接方式

      var str ="hello" + "world"
      str += "haha"
    4. 当一行字符串太长时,需要使用到多行字符串,可以如下处理

      //注意加号要保留在上一行
      var str ="hello" + "world"+"hello" +
      		"world"+"hello" + 
      		"world"

-9. 基本数据类型默认值

数据类型默认值
整型0
浮点型0
字符型"" 空串
布尔类型false

-10. 基本数据类型的相互转换

Golang 和 java / c 不同,Go 在不同类型的变量之间赋值时需要显式转换。也就是说 Golang 中数 据类型不能自动转换

  1. 基本语法

     表达式 T(v) 将值 v 转换为类型 T
         T: 就是数据类型,比如 int32,int64,float32 等等
         v: 就是需要转换的变量

    代码演示

     var i int32 = 100
     var n1 float32 = float32(i)
     var n2 int8 = int8(i)
     var n3 int64 = int64(i)
    1. Go 中,数据类型的转换可以是从 表示范围小-->表示范围大,也可以 范围大--->范围小

    2. 被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化

    3. 在转换中,比如将 int64 转成 int8 【-128---127】 ,编译时不会报错,只是转换的结果是按 溢出处理,和我们希望的结果不一样。 因此在转换时,需要考虑范围

-11. 基本数据类型转String类型

 
package main
 ​
 import "fmt"
 ​
 /**
   多变量的声明
 */
 func main() {
     var num1 int = 99
     var num2 float64 = 23.456
     var b bool = true
     var myChar byte = 'h'
     var str string
 ​
     str = fmt.Sprintf("%d", num1)
     fmt.Printf("str type %T str=%q\n", str, str)
     str = fmt.Sprintf("%f", num2)
     fmt.Printf("str type %T str=%q\n", str, str)
     str = fmt.Sprintf("%t", b)
     fmt.Printf("str type %T str=%q\n", str, str)
     str = fmt.Sprintf("%c", myChar)
     fmt.Printf("str type %T str=%q\n", str, str)
 }
 //输出
 /*
 str type string str="99"
 str type string str="23.456000"
 str type string str="true"
 str type string str="h"
 */
 ​
 // 方法二是使用 strconv 包的函数
 // 还有就是strconv的一个Itoa函数可以转
 // 例:
 var num5 int64 = 4567
 str = strconv.Itoa(int(num5)) //转换成了String类型

-12. String转基本数据类型

使用strconv包的函数

注意返回值的类型,主要是以返回值的类型来决定,如果不符合自己的要求就将结果强制转换类型

package main
import (
	"fmt"
	"strconv"
)
/**
  String转基本数据类型
*/
func main() {
	var str string = "true"
	var b bool
	//因为这个ParseBool函数式返回值是两个,把不需要返回的值就用_代替
	b, _ = strconv.ParseBool(str)
	fmt.Printf("b type %T b = %v ", b, b)//b type bool b = true
}

	var str2 string = "12345"
	var n1 int64
	var n2 int
	n1, _ = strconv.ParseInt(str2, 10, 64)
	n2 = int(n1)
	fmt.Printf("n1 type %T n1 = %v \n", n1, n1)//n1 type int64 n1 = 12345
	fmt.Printf("n2 type %T n2 = %v\n", n2, n2)//n2 type int n2 = 12345

	var str3 string = "123.456"
	var f1 float64
	f1, _ = strconv.ParseFloat(str3, 64)
	fmt.Printf("f1 type %T f1 = %v\n", f1, f1)//f1 type float64 f1 = 123.456

细节

  1. 在String类型转换成基本数据类型时,如果字符串不是数字的话,比如“hello”,Golang会直接将其转换成0

  2. 所以类型转换不成功时,会变成默认值0,或者bool型的话会变成false

-13. 指针类型

  1. 基本数据类型,变量存的就是值,也叫值类型

  2. 获取变量的地址,用&,比如:var num int ,获取num的地址:&num

  3. 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值 eg:var ptr *int = &num

  4. 获取指针类型所指向的值,使用:*,比如:var ptr *int,

package main

import "fmt"

/**
  指针类型
*/
func main() {
	//基本数据类型在内存布局
	var i int = 10
	// i 的地址是什么,&i
	fmt.Println("i 的地址 = ", &i)//i 的地址 =  0xc000014090
	//1. ptr 是一个指针变量
	//2. ptr 的类型 *int
	//3. ptr 本身的值是&i
	var ptr *int = &i
	fmt.Printf("ptr=%v\n", ptr) //ptr=0xc000014090
	fmt.Printf("ptr 指针的地址 = %v\n", &ptr)//ptr 指针的地址 = 0xc000006030
	fmt.Printf("ptr 指向的值=%v", *ptr)//ptr 指向的值=10
}
  1. 案列演示

    要求:

    1. 写一个程序,获取一个int变量num的地址,并显示到终端

    2. 将num的地址赋值给指针ptr,并通过ptr去修改num的值

    package main
    
    import "fmt"
    
    /**
      指针类型的案例演示
    */
    func main() {
    	/*
    		1. 写一个程序,获取一个int变量num的地址,并显示到终端
    		2. 将num的地址赋值给指针ptr,并通过ptr去修改num的值
    	*/
    	var num int = 9
    	fmt.Printf("num address = %v \n", &num)//num address = 0xc000014090
    	var ptr *int
    	ptr = &num
    	*ptr = 10 //此时值被修改成10
    	fmt.Printf("ptr的值是:%v\n", *ptr)//ptr的值是:10
    	fmt.Printf("num address = %v \n", num)//num address = 10
    }
    1. 指针的使用细节

      1. 值类型:都有对应的指针类型,形式为 *数据类型,eg:int的对应指针就是 *int,

        float32对应的指针类型就是 *float32.以此类推。

      2. 值类型包括,基本数据类型int系列,float系列,bool,string,数组和结构体struct系列

-14. 值类型和引用类型

  1. 值类型:基本数据类型 int系列,float系列,bool,string,数组和结构体struct

  2. 引用类型,指针,slice切片,map,管道,interface等都是引用类型;

  3. 值类型和引用类型的使用特点

    1. 值类型:变量直接存储值,内存通常在栈中分配

       

    2. 引用类型:变量存储的是一个地址,这个地址对应的空间才是真正存储数据值,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。

       

    3. 内存中的栈区和堆区的示意图

       

6. 标识符的命名规范

-1.概念

  1. Golang 对各种变量、方法、函数等命名时使用的字符序列称为标识符

  2. 凡是自己可以起名字的地方都叫标识符

-2. 命名规则

  1. 由 26 个英文字母大小写,0-9 ,_ 组成

  2. 数字不可以开头。var num int //ok var 3num int //error

  3. Golang 中严格区分大小写

    var num int

    var Num int

    说明:在 golang 中,num 和 Num 是两个不同的变量

  4. 标识符不能包含空格

  5. 下划线"_"本身在 Go 中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但 是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用

  6. 不能以系统保留关键字作为标识符(一共有 25 个),比如 break,if 等等...

-3. 包的命名规范

  1. 包名:保持 package 的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,不要和 标准库不要冲突 fmt

     

  2. 变量名、函数名、常量名:采用驼峰法 举例:

    var stuName string = “tom” 形式: xxxYyyyyZzzz ...

   3.如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写,则只能 在本包中使用 (**注:可以简单的理解成,首字母大写是公开的,首字母小写是私有的) ,在 golang 没有 public , private 等关键字。

-4. 预定义字符和系统保留关键字

  1. 系统保留的关键字

    go的25个关键字:
    break default func interface select
    case defer go map struct
    chan else goto package switch
    const fallthrough if range type
    continue for import return var
    
    关键词解释
    1.break
    break 用于跳出循环
    2.default
    用于选择结构的默认选项(switch、select)
    3.func
    函数定义
    4.interface
    定义接口
    5.select
    go语言特有的channel选择结构
    6.case
    选择结构标签
    7.chan
    定义channel
    8.const
    常量
    9.continue
    跳过本次循环
    10.defer
    延迟执行内容(收尾工作)有点类似C++的析构,但是它是再函数结尾的时候去执行(也就是栈即将被释放的时候)
    11.go
    并发执行
    12.map
    map类型
    13.struct
    定义结构体
    14.else
    选择结构
    15.goto
    跳转语句
    16.package
    包
    17.switch
    选择结构
    18.fallthrough
    如果case带有fallthrough,程序会继续执行下一条case,不会再判断下一条case的值
    19.if
    选择结构
    20.range
    从slice、map等结构中取元素
    21.type
    定义类型
    22.for
    循环
    23.import
    导入包
    24.return
    返回
    25.var
    定义变量
  2. 系统的预定义标识符

     

7. 运算符介绍

  1. 算术运算符

  2. 赋值运算符

  3. 比较运算符/关系运算符

  4. 逻辑运算符

  5. 位运算符

  6. 其它运算符

注:go里面没有三目运算符

-1. 算术运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20

运算符描述实例
+相加A + B 输出结果 30
-相减A - B 输出结果 -10
*相乘A * B 输出结果 200
/相除B / A 输出结果 2
%求余B % A 输出结果 0
++自增A++ 输出结果 11
--自减A-- 输出结果 9
  1. ++ 和 -- 只能独立使用 不能直接赋值 eg: a = i++;是错误方法

  2. Golang 的++ 和 -- 只能写在变量的后面,不能写在变量的前面,即:只有 a++ a-- 没有 ++a --

  3. Golang 的设计者去掉 c / java 中的 自增自减的容易混淆的写法,让 Golang 更加简洁,统一。(强 制性的)

-2. 关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20

运算符描述实例
==检查两个值是否相等,如果相等返回 True 否则返回 False。(A == B) 为 False
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。(A != B) 为 True
>检查左边值是否大于右边值,如果是返回 True 否则返回 False。(A > B) 为 False
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。(A < B) 为 True
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。(A >= B) 为 False
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。(A <= B) 为 True

关系运算符的结果都是 bool 型,也就是要么是 true,要么是 false

-3. 逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。

运算符描述实例
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。(A && B) 为 False
||逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。(A || B) 为 True
!逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。!(A && B) 为 True

-4. 位运算符

位运算符对整数在内存中的二进制位进行操作。

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

pqp & qp | qp ^ q
00000
01011
11110
10011

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

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:

运算符描述实例
&按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。(A & B) 结果为 12, 二进制为 0000 1100
|按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或(A | B) 结果为 61, 二进制为 0011 1101
^按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。(A ^ B) 结果为 49, 二进制为 0011 0001
<<左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。A << 2 结果为 240 ,二进制为 1111 0000
>>右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。A >> 2 结果为 15 ,二进制为 0000 1111

-5. 赋值运算符

运算符描述实例
=简单的赋值运算符,将一个表达式的值赋给一个左值C = A + B 将 A + B 表达式结果赋值给 C
+=相加后再赋值C += A 等于 C = C + A
-=相减后再赋值C -= A 等于 C = C - A
*=相乘后再赋值C *= A 等于 C = C * A
/=相除后再赋值C /= A 等于 C = C / A
%=求余后再赋值C %= A 等于 C = C % A
<<=左移后赋值C <<= 2 等于 C = C << 2
>>=右移后赋值C >>= 2 等于 C = C >> 2
&=按位与后赋值C &= 2 等于 C = C & 2
^=按位异或后赋值C ^= 2 等于 C = C ^ 2
|=按位或后赋值C |= 2 等于 C = C | 2

-6. 其它运算符

运算符描述实例
&返回变量存储地址&a; 将给出变量的实际地址。
*指针变量。*a; 是一个指针变量

-7. 三元运算符实现

go中没有三元运算符,但是可以用if...else...实现

-8. 运算符优先级

优先级运算符
5* / % << >> & &^
4+ - | ^
3== != < <= > >=
2&&
1||

 

8. 键盘输入语句

 

9. *原码反码补码

 

10. 流程控制

-1. 顺序控制

-2. 分支控制

  1. 单分支

    if ... {

    }

  2. 双分支

    if ...{

    }else{

    }

  3. 多分支

    if ...{

    }else if ...{

    }else{

    }

package main

import (
	"fmt"
	"math"
)

/**
  多分支语句解方根
*/
func main() {
	var a float64 = 2.0
	var b float64 = 4.0
	var c float64 = 2.0
	m := b*b - 4*a*c
	if m > 0 {
		x1 := (-b + math.Sqrt(m)) / 2 * a
		x2 := (-b - math.Sqrt(m)) / 2 * a
		fmt.Printf("x1 = %v x2 = %v", x1, x2)
	} else if m == 0 {
		x1 := (-b + math.Sqrt(m)) / 2 * a
		fmt.Printf("x1 = %v", x1)
	} else {
		fmt.Println("无解...")
	}
}

-3. 循环控制

1. for循环

  1. 语法格式

    for 循环变量初始化; 循环条件; 循环变量迭代 {
    循环操作(语句)
    }
for i := 1; i <= 10; i++ {
		fmt.Println("Hello World!")
	}
  1. for循环使用细节

    1. 循环条件返回的是一个布尔值

    2. for循环的第二种写法

      for 循环判断条件{
          //循环执行语句
      }
      
      j := 1
      for j <= 10 {
          fmt.Println("World Hello!")
          j++
      }

    3. for循环的第三种写法

      for{
          //循环执行语句
      }
      k := 1
      for {
          if k <= 10 {
              fmt.Println("Hello!")
          } else {
              break
          }
          k++
      }

    4. Golang 提供 for-range 的方式,可以方便遍历字符串和数组

    5. 字符串遍历方式1

      //字符串遍历方式1
      	var str string = "hello,world!"
      	for i := 0; i < len(str); i++ {
      		fmt.Printf("%c \n", str[i])
      	}

    6. 字符串遍历方式2 for-range

      var str2 string = "hurry go~"
      for index, val := range str2 {
      	fmt.Printf("index = %d, val = %c\n", index, val)
      }
      /* 输出结果
      index = 0, val = h
      index = 1, val = u
      index = 2, val = r
      index = 3, val = r
      index = 4, val = y
      index = 5, val =  
      index = 6, val = g
      index = 7, val = o
      index = 8, val = ~
      */

    7. 遍历字符串的实现细节

      在UTF-8编码中,一个中文字符等于三个字节,一个中文标点符号占三个字节;一个英文字符等于一个字节,一个英文标点占一个字节;一个数字符号等于一个字节。”

      如果在字符串中含有中文,那么传统的字符串方式,就是错误,会出现乱码。究其原因就是传统的对字符串遍历就是按照字节来遍历,而一个汉字在utf8编码中是对应3个字节

      //将上述代码修改一下
      fmt.Println("----------------------")
      var str3 string = "Hello World 格物钛!"
      str4 := []rune(str3) //切片 就是把str转成[]rune
      for i := 0; i < len(str4); i++ {
          fmt.Printf("%c\n",str4[i])
      }

    8. 对应 for-range 遍历方式而言,是按照字符方式遍历。因此如果有字符串有中文,也是 ok的!

2. 在go中是没有while和 do while的循环的

想要实现相应的效果的话就是搭配使用for循环来实现相应效果

  1. 实现while循环效果

     

  2. 实现do ... while... 效果 ​​​​​​

     

3. 多重循环

  1. 将一个循环放在另一个循环体内,就形成了嵌套循环。在外边的 for 称为外层循环在里面的 for 循环称为内层循环。【建议一般使用两层,最多不要超过 3 层】

  2. 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为 false 时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。

  3. 外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次

4. 案列

//统计 3 个班成绩情况,每个班有 5 名同学,求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]
   func main() {
   	/*
   		统计 3 个班成绩情况,每个班有 5 名同学,求出各个班的平均分和所有班级的平均分[学生的成
   		绩从键盘输入]
   	*/
   	var classNum int = 2
   	var stuNum int = 5
   	var totalSum float64 = 0.0
   	for j := 1; j <= classNum; j++ {
   		sum := 0.0
   		for i := 1; i <= stuNum; i++ {
   			var score float64
   			fmt.Printf("请输入第%d班 第%d个学生的成绩", j, i)
   			fmt.Scanln(&score)
   			//累计总分
   			sum += score
   		}
   		fmt.Printf("第%d个班级的平均分是%v\n", j, sum/float64(stuNum))
   		//将各个班的总成绩累加到totalSum上
   		totalSum += sum
   	}
   	fmt.Printf("将各个班级的总成绩%v,所有班级的平均分是%v\n",totalSum,totalSum/float64(stuNum))
   }
   /* 输出结果
   请输入第1班 第1个学生的成绩50
   请输入第1班 第2个学生的成绩20
   请输入第1班 第3个学生的成绩30
   请输入第1班 第4个学生的成绩90
   请输入第1班 第5个学生的成绩60
   第1个班级的平均分是50
   请输入第2班 第1个学生的成绩100
   请输入第2班 第2个学生的成绩50
   请输入第2班 第3个学生的成绩55
   请输入第2班 第4个学生的成绩56
   请输入第2班 第5个学生的成绩59
   第2个班级的平均分是64
   将各个班级的总成绩570,所有班级的平均分是114
   *///统计 3 个班成绩情况,每个班有 5 名同学,求出各个班的平均分和所有班级的平均分[学生的成绩从键盘输入]
   func main() {
   	/*
   		统计 3 个班成绩情况,每个班有 5 名同学,求出各个班的平均分和所有班级的平均分[学生的成
   		绩从键盘输入]
   	*/
   	var classNum int = 2
   	var stuNum int = 5
   	var totalSum float64 = 0.0
   	for j := 1; j <= classNum; j++ {
   		sum := 0.0
   		for i := 1; i <= stuNum; i++ {
   			var score float64
   			fmt.Printf("请输入第%d班 第%d个学生的成绩", j, i)
   			fmt.Scanln(&score)
   			//累计总分
   			sum += score
   		}
   		fmt.Printf("第%d个班级的平均分是%v\n", j, sum/float64(stuNum))
   		//将各个班的总成绩累加到totalSum上
   		totalSum += sum
   	}
   	fmt.Printf("将各个班级的总成绩%v,所有班级的平均分是%v\n",totalSum,totalSum/float64(stuNum))
   }
   /* 输出结果
   请输入第1班 第1个学生的成绩50
   请输入第1班 第2个学生的成绩20
   请输入第1班 第3个学生的成绩30
   请输入第1班 第4个学生的成绩90
   请输入第1班 第5个学生的成绩60
   第1个班级的平均分是50
   请输入第2班 第1个学生的成绩100
   请输入第2班 第2个学生的成绩50
   请输入第2班 第3个学生的成绩55
   请输入第2班 第4个学生的成绩56
   请输入第2班 第5个学生的成绩59
   第2个班级的平均分是64
   将各个班级的总成绩570,所有班级的平均分是114
   */

打印金字塔

func pyramid() {
	var totalLevel int = 10

	//表示层数
	for i := 1; i <= totalLevel; i++ {
		//打印空格
		for k := 1; k <= totalLevel-i; k++ {
			fmt.Print(" ")
		}
		//表示每层打印多少 *
		for j := 1; j <= 2*i-1; j++ {
			if j == 1 || j == 2*i-1 || i == totalLevel {
				fmt.Print("*")
			} else {
				fmt.Print(" ")
			}
		}
		fmt.Println()
	}
}
/*运行结果
         *
        * *
       *   *
      *     *
     *       *
    *         *
   *           *
  *             *
 *               *
*******************
*/

九九乘法表

func jiujiu() {
	for i := 1; i <= 9; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%v * %v = %v \t", i, j, i*j)
		}
		fmt.Println()
	}
}

-4. *switch分支控制

  1. switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上到下逐一测 试,直到匹配为止。

  2. 匹配项后面也不需要再加 break

  1. golang 的 case 后的表达式可以有多个,使用 逗号 间隔.

  2. golang 中的 case 语句块不需要写 break , 因为默认会有,即在默认情况下,当程序执行完 case 语 句块后,就直接退出该 switch 控制结构。

  3. 注意事项

    1. case/switch 后是一个表达式( 即:常量值、变量、一个有返回值的函数等都可以。

    2. case 后的各个表达式的值的数据类型,必须和 switch 的表达式数据类型一致

       

    3. case 后面可以带多个表达式,使用逗号间隔。比如 case 表达式 1, 表达式 2 ...

       

    4. case 后面的表达式如果是常量值(字面量),则要求不能重复case 后面不需要带 break , 程序匹配到一个 case 后就会执行对应的代码块,然后退出 switch,如 果一个都匹配不到,则执行 defaul。

       

    5. default 语句不是必须的。

    6. switch 后也可以不带表达式,类似 if --else 分支来使用。

       

    7. switch 后也可以直接声明/定义一个变量,分号结束,不推荐!!!

       

    8. switch 穿透-fallthrough ,如果在 case 语句块后增加 fallthrough ,则会继续执行下一个 case,也 叫 case穿透

       

    9. Type Switchswitch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际指向的 变量类型

    10. 案列

      package main
      
      import (
      	"fmt"
      )
      
      /**
        switch语句
      */
      
      func test(char byte) byte {
      	return char + 1
      }
      
      func main() {
      	var key byte
      	fmt.Println("请输入一个字符:a,b,c,d")
      	fmt.Scanf("%c", &key)
      	switch test(key) + 1 {
      	case 'a':
      		fmt.Println("周一!")
      	case 'b':
      		fmt.Println("周二!")
      	case 'c':
      		fmt.Println("周三!")
      		fallthrough
      	case 'd':
      		fmt.Println("周四!")
      	default:
      		fmt.Println("输入有误!")
      	}
      
      	switch grade := 90; {
      	case grade >= 90:
      		fmt.Println("输出成功!")
      	}
      	fmt.Println("----------------------------")
      	var x interface{}
      	var y = 10.0
      	x = y
      	switch i := x.(type) {
      	case nil:
      		fmt.Printf("x的类型~:%T", i)
      	case int:
      		fmt.Printf("x 是 int型")
      	case float64:
      		fmt.Printf("x 是 float64型")
      	case func(int) float64:
      		fmt.Printf( "x 是 func(int) 型")
      	case bool,string:
      		fmt.Printf("x 是 bool 或 string型")
      	default:
      		fmt.Printf("未知型~!")
      	}
      }

  4. switch 和 if比较

    1. 如果判断的具体数值不多,而且符合整数、浮点数、字符、字符串这几种类型。建议使用 swtich 语句,简洁高效。

    2. 其他情况:对区间判断和结果为 bool 类型的判断,使用 if,if 的使用范围更广

-5. *Break语句

  1. break 语句用于终止某个语句块的执行,用于中断当前 for 循环或跳出 switch 语

//生成随机数 当生成99时退出循环
func f1() {
	var count int = 0
	//给随机数设置一个种子
	rand.Seed(time.Now().UnixNano())
	for {
		//生成一个随机数
		n := rand.Intn(100) + 1
		fmt.Println(n)
		count++
		if n == 99 {
			break
		}
	}
}
  1. Break使用细节分析

    break 语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块

    //指定标签跳转
    func f2() {
    lable:
    	for i := 0; i < 4; i++ {
    		for j := 0; j < 10; j++ {
    			if j == 2 {
    				break lable
    			}
    			fmt.Println("j=", j)
    		}
    	}
    }

    1. break 默认会跳出最近的 for 循环

    2. break 后面可以指定标签,跳出标签对应的 for 循环

-6. *Continue 语句

  1. continue 语句用于结束本次循环,继续执行下一次循环。

  2. continue 语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环 , 这 个和前面的 break 标签的使用的规则一样.

//continue指定标签跳转
func f3() {
lable:
	for i := 0; i < 4; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				continue lable
			}
			fmt.Println("j=", j)
		}
	}
}

-7. *goto语句和return语句

  1. Go 语言的 goto 语句可以无条件地转移到程序中指定的行。

  2. goto 语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能。

  3. 在 Go 程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序 都产生困难

//goto语法结构
goto label
...
label:statement

Return函数

  1. return 使用在方法或者函数中,表示跳出所在的方法或函数

  2. 如果 return 是在普通的函数,则表示跳出该函数,即不再执行函数中 return 后面代码,也可以 理解成终止函数。

  3. 如果 return 是在 main 函数,表示终止 main 函数,也就是说终止程序

11. *函数(重要!!!)

为完成某一功能的程序指令(语句)的集合,称为函数。

//函数的基本语法
func 函数名(形参列表) (返回值列表){
    //执行语句
    return 返回值列表
}

*函数中使用的细节

1. 基本数据类型和数组默认都是值传递

基本数据类型和数组默认都是值传递的,即进行值拷贝,在函数内修改,不会影响到原来的值。(这一点要与java中的数组引用传递区别开!)

2. 函数中的变量是局部的,函数外不生效

3. 修改函数外的变量

如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量!

//用指针类型才能改变函数中的值
func test(n *int) {
	*n =*n + 10
	fmt.Println("此时n的值是:",*n)
}

4. Go函数不支持重载!!!!!!!!!!(与java相反)

5. 函数也是一种数据类型

在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数的变量了。通过该变量可以对函数调用

//定义一个简单函数
func getSum(n1 int, n2 int) int {
	return n1 + n2
}
func main() {
	//将函数类型赋值给a
	a := getSum
	fmt.Printf("a的类型%T,getSum类型是%T \n", a, getSum)
    //此时a就具备了函数getSum的功能!
	res := a(10, 40)
	fmt.Println("res= ", res)
}
/*
a的类型func(int, int) int,getSum类型是func(int, int) int
res=  50
*/

6.函数既然是一种数据类型,因此在Go中,函数可以作为形参

//定义一个简单函数
func getSum(n1 int, n2 int) int {
   return n1 + n2
}

//函数既然是一种数据类型,因此在Go中,函数可以作为形参
func myfun(n func(int, int) int, num1 int, num2 int) int {
   return n(num1, num2)
}
func main() {
	res2 := myfun(getSum, 50, 60)
	fmt.Println("res2 = ", res2)
}
/*****************************************/
//上面这个可以变成这样
type myInt func(int, int) int

func getSum(n1 int, n2 int) int {
   return n1 + n2
}

//函数既然是一种数据类型,因此在Go中,函数可以作为形参
func myfun(n myInt, num1 int, num2 int) int {
   return n(num1, num2)
}
func main() {
	res2 := myfun(getSum, 50, 60)
	fmt.Println("res2 = ", res2)
}

7. GO支持自定义数据类型

基本语法:

type 自定义数据类型名 数据类型 //就相当于一个别名

//案例
type myInt int //这时myInt就等价于int来使用
type mySum func(int,int)int //这时mySum就等价于一个函数类型func(int,int)int
/*---------------------------*/
//注意,给myInt取了别名后,在go中myInt和int虽然都是int类型,但是go认为myInt和int是两个类型,所以后面会强制转换
type myInt int
var num1 myInt
var num2 int
num1 = 40
num2 = int(num1) // 强制转换一下
/*---------------------------*/

8. 支持对函数返回值命名

//支持对函数返回值命名,可以在return中不写东西,返回也可以无序!
func getSumAndSub(n1 int, n2 int) (sum int, sub int) {
	sum = n1 + n2
	sub = n1 - n2
	return
}

func main() {
	a,b:=getSumAndSub(1,2)
	fmt.Println(a," ",b)
}

9. 使用 _ 标识符,忽略返回值

10. Go支持可变参数

//可变参数的使用
func sum(n int, args ...int) int {
	sum := n
	//遍历 args
	for i := 0; i < len(args); i++ {
		sum += args[i]
	}
	return sum
}
func main(){
   	//测试可变参数
	res := sum(10, 0, 5, 6)
	fmt.Println("res=", res)
}

11. init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init函数会在main函数前被调用

//init函数,通常可以在init函数中完成初始化工作
package main

import "fmt"

/**
  多重循环案列
*/
func main() {
	fmt.Println("main()...函数")
}
func init() {
	fmt.Println("init()...函数")
}
/* 输出结果
init()...函数
main()...函数
*/

1. init()函数细节

  1. 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程是:先是全局变量定义-->init函数-->main函数

package main

import "fmt"

/**
  init函数
*/
//先是全局变量定义-->init函数-->main函数
var age = test()

func test() int{
	fmt.Println("test()...函数")
	return 90
}
func main() {
	fmt.Println("main()...函数")
}
//init函数,通常可以在init函数中完成初始化工作
func init() {
	fmt.Println("init()...函数")
}
/*
test()...函数
init()...函数
main()...函数
*/
  1. init函数最主要的作用,就是完成一些初始化工作

12. 匿名函数

实现的三种方式

  1. 在定义匿名函数时就直接调用

  2. 将匿名函数赋给一个变量(函数变量),在通过该变量来调用匿名函数。

  3. 全局匿名函数

    1. 如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。

package main

import "fmt"

/**
  匿名函数
*/
//全局匿名函数
var (
	//Fun1就是一个全局匿名函数
	Fun1 = func(n1 int, n2 int) int {
		return n1 * n2
	}
)

func main() {
	fmt.Println("main()...函数")
	//使用匿名函数直接完成
	res1 := func(n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)
	fmt.Println("res1 = ", res1)

	//将匿名函数交给变量
	a := func(n1 int, n2 int) int {
		return n1 + n2
	}
	res2 := a(10, 30)
	fmt.Println("res2 = ", res2)

	//全局匿名函数的使用
	res3 := Fun1(4, 9)
	fmt.Println("res3 = ", res3)
}

12. 包

go 的每一个文件都是属于一个包的,也就是说 go 是以包的形式来管理文件和项目目录结构 的

1. 包的三大作用

  1. 区分相同名字的函数、变量等标识符

  2. 当程序文件很多时,可以很好的管理项目

  3. 控制函数、变量等访问范围,即作用域

》打包基本语法

package 包名

》引入包的基本语法

import “包的路径"

2. 包使用的注意事项

  1. 在给一个文件打包时,该包对应一个文件夹,比如这里的 utils 文件夹对应的包名就是 utils, 文件的包名通常和文件所在的文件夹名一致,一般为小写字母。

  2. 当一个文件要使用其它包函数或变量时,需要先引入对应的包

    1. 引入方式 1:import "包名"

    2. 引入方式 2: import ( "包名" "包名" )

  3. package 指令在 文件第一行,然后是 import 指令。

  4. 在 import 包时,路径从 $GOPATH 的 src 下开始,不用带 src , 编译器会自动从 src 下开始引入

  5. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言 的 public ,这样才能跨包访问。比如 utils.go

  6. 在访问其它包函数,变量时,其语法是 包名.函数名, 比如这里的 main.go

  7. 如果包名较长,Go 支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了则需要使用别名来访问该包的函数和变量。

  8. 在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义

  9. 果你要编译成一个可执行程序文件,就需要将这个包声明为 main , 即 package main .这个就 是一个语法规范,如果你是写一个库 ,包名可以自定义

13. 递归

案例

//斐波那契数组
func Fib(n int) int{
	if n == 1 || n == 2 {
		return 1
	}else {
		return Fib(n-1)+Fib(n-2)
	}
}

案例二

//函数调用
func FunSum(n int) int{
	if n == 1 {
		return 3 
	}else {
		return 2*Fib(n-1)+1
	}
}

14. 闭包

基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

package main

import "fmt"

/**
  闭包
*/

//累加器
func AddUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}

//判断是否带后缀,不带的话就自动加上
func makeSuffix(suffix string) func (string) string{
	return func(name string) string {
		//如果name没有指定后缀,则加上,否则直接返回原来的名字
		if !strings.HasSuffix(name,suffix){
			return name + suffix
		}
		return name
	}
}

func main() {
	f:=AddUpper()
	fmt.Println(f(1))  //11
	fmt.Println(f(2))  //13
	fmt.Println(f(3))  //16
    
    
	res := makeSuffix(".jpg")
	fmt.Println(res("jjj.jpg"))  //jjj.jpg
	fmt.Println(res("nbb"))    //nbb.jpg
}

15. defer函数---延时机制

在函数中,程序员经常需要创建资源(比如:数据库连接,文件句柄,锁等),为了在函数中执行完毕后,及时的释放资源。产生了defer延时机制

注意:在defer将语句放入到栈时,也会将相关的值拷贝同时入栈!!!!!

package main

import "fmt"

/**
  defer延时机制
*/

func sum(n1 int, n2 int) int {
    //遵循先入后出
	defer fmt.Println("defer1 = ", n1)
	defer fmt.Println("defer2 = ", n2)

	res := n1 + n2
	fmt.Println("Ok res = ", res)
	return res
}

func main() {
	res := sum(10, 20)
	fmt.Println("main 中 res =", res)
}
/* 执行结果
Ok res =  30
defer2 =  20
defer1 =  10
main 中 res = 30
*/

总结:

  1. defer的最重要价值就是在当函数执行完毕后,可以及时的释放函数创建的资源

func test(){
    //释放数据库资源
    connect = openDatabase
    defer connect.close()
}
  1. 在defer后,可以继续使用创建资源

  2. 当函数完毕后,系统依次从defer栈中,取出语句,关闭资源

  3. 这种机制,非常简介,程序员不用在担心什么时候在关闭资源

16. **函数的传递方式

1. 值传递

值类型:基本数据类型 int系列,float系列,bool,string,数组和结构体struct

2. 引用传递

不管是值传递还是引用传递,其实传递的都是变量的副本,不同的是,值传递的是值得拷贝,引用传递的是地址的拷贝。

引用类型,指针,slice切片,map,管道,interface等都是引用类型;

17. Go中的内置函数

  1. len:用来求长度,比如:string,array,slice,map,channel

  2. new:用来分配内存,主要用来分配值类型,比如 int,float32,struct...返回的是指针

     

package main

import "fmt"

/**
  内置函数  new
*/

func main() {
   //start := time.Now().Unix()
   //end := time.Now().Unix()
   //fmt.Printf("该函数执行所用时间是:%v 秒!", end-start)
   num1 := 100
   fmt.Printf("num1的类型%T , num1的值 = %v , num1的地址%v \n", num1, num1, &num1)
   num2 := new(int)
   *num2 = 100
   fmt.Printf("num2的类型%T,num2的值 = %v , num2的地址%v ,num2的值是%v\n", num2, num2, &num2,*num2)
}
/* 运行环境
num1的类型int , num1的值 = 100 , num1的地址0xc000014090
num2的类型*int,num2的值 = 0xc000014098 , num2的地址0xc000006030 ,num2的值是100
*/
  1. make:用来分配内存主要用来分配引用类型,比如chan,map,slice

18. **错误/异常机制

Go与java区别

  1. go语言最求简介优雅,所以不支持传统的try...catch...finally处理

  2. go中引入的处理方式为:defer,panic,recover

  3. 处理过程为:go抛出一个panic异常,然后defer中通过recover捕获这个异常,然后正常处理。

  4. 默认情况下,发生错误,程序就会退出

  5. 希望发生错误,可以捕获错误,并进行处理,保证程序可以继续执行

package main

import (
   "fmt"
   "time"
)

/**
  异常处理
*/
func testError() {
   //defer + recover 来捕获异常
   defer func() {
      //receover()内置函数,可以捕获到异常
      err := recover()
      //说明捕获异常
      if err != nil {
         fmt.Println("err=", err)
         fmt.Printf("err的类型是%T \n", err)
         //此处就可以将该错误信息发生给管理员
      }
   }()
   num1 := 10
   num2 := 0
   res := num1 / num2
   fmt.Println("res=", res)
}

func main() {
   //start := time.Now().Unix()
   //end := time.Now().Unix()
   //fmt.Printf("该函数执行所用时间是:%v 秒!", end-start)
   testError()
   for {
      fmt.Println("发生错误后下面的代码!")
      time.Sleep(time.Second)
   }
}
/* 运行结果
err= runtime error: integer divide by zero
err的类型是runtime.errorString
发生错误后下面的代码!
发生错误后下面的代码!
*/

1. 自定义错误异常

go语言中,支持自定义错误,使用errors.New和panic内置函数

  1. error.New("错误说明"),会返回一个error类型的值,表示一个错误

  2. panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数,可以接收error类型的变量,输出错误信息,并退出程序

package main

import (
	"errors"
	"fmt"
)

/**
  异常处理
*/

//函数读取已配置文件init。conf的信息
//如果文件名传入不正确,我们返回一个自定义错误
//以下自定义异常处理
func readConf(name string) (err error) {
	if name == "config.ini" {
		//读取...
		return nil
	} else {
		//返回一个自定义错误
		return errors.New("读取文件错误...")
	}
}

func testReadConf() {
	err := readConf("config2.ini")
	if err != nil {
		//如果读取文件发送错误,就输出这个错误,并终止程序!!!!
		panic(err)
	}
	fmt.Println("testReadConf后面的代码")
}

func main() {
	testReadConf()
	fmt.Println("main后面的代码")
}

/*  执行结果
panic: 读取文件错误...

goroutine 1 [running]:
main.testReadConf()
        D:/Workplace/GoLand/awesomeProject/src/go_code/chapter03/demo19/main.go:46 +0x65
main.main()
        D:/Workplace/GoLand/awesomeProject/src/go_code/chapter03/demo19/main.go:58 +0x29
*/

19. 数组

1. 数组与切片

-1.数组定义的四种方式

 
//第一种
 //var <数组名称> [<数组长度>]<数组元素>
 var arr [2]int
     arr[0]=1
     arr[1]=2
 ​
 //第二种
 //var <数组名称> = [<数组长度>]<数组元素>{元素1,元素2,...}
 var arr = [2]int{1,2}
 //或者
 arr := [2]int{1,2}
 ​
 //第三种
 //var <数组名称> [<数组长度>]<数组元素> = [...]<元素类型>{元素1,元素2,...}
 var arr = [...]int{1,2}
 //或者
 arr := [...]int{1,2}
 ​
 //第四种
 //var <数组名称> [<数组长度>]<数组元素> = [...]<元素类型>{索引1:元素1,索引2:元素2,...}
 var arr = [...]int{1:1,0:2}
 //或者
 arr := [...]int{1:1,0:2}

-2. 数组的案例

 
package main
 ​
 import "fmt"
 ​
 /**
   数组
 */
 func test01() {
     totalNum := 204.4
     //将转换的值交给avgNum
     avgNum := fmt.Sprintf("%.2f", totalNum/6)
     fmt.Printf("totalNum = %v avgNum = %v", totalNum, avgNum)
 }
 ​
 //数组使用
 func test02() {
     var hens [5]float64
     hens[0] = 1
     hens[1] = 2
     hens[2] = 3
     hens[3] = 4
     hens[4] = 5
     totalWeight := 0.0
     for i := 0; i < len(hens); i++ {
         totalWeight += hens[i]
     }
     avgweight2 := fmt.Sprintf("%.2f", totalWeight/float64(len(hens)))
     fmt.Printf("totalWeight2 = %v avgWeight = %v", totalWeight, avgweight2)
 }
 ​
 func main() {
     test02()
 }
 /*
 [1 2 3 4 5]
 */

-3.总结:

  1. 数组的地址可以通过数组名来获取 &intArr

  2. 数组的第一个元素地址,就是数组的首地址

  3. 数组的各个元素的地址间隔是依据数组的类型决定的:比如int64->8 int32->4

    func test03() {
    	var intArr [3]int
    	intArr[0] = 10
    	intArr[1] = 20
    	intArr[2] = 30
    	fmt.Println(intArr)
    	fmt.Printf("intArr的地址 = %p intArr[0]的地址 %p intArr[1]的地址%p intArr[2]的地址%p", &intArr, &intArr[0], &intArr[1], &intArr[2])
    }
    
    /*
    [10 20 30]
    intArr的地址 = 0xc0000a0120 intArr[0]的地址 0xc0000a0120 intArr[1]的地址0xc0000a
    0128 intArr[2]的地址0xc0000a0130
    */

  4. for-range结构遍历

    基本语法

    for index,value:=range array0 {

    ...

    }

    1. 第一个返回值 index是数组的下标

    2. 第二个value是在该下标的位置的值

    3. 他们都是仅在for循环内部可见的局部变量

    4. 遍历数组元素的时候,如果不想使用下标index,可以把下标index标为下划线_

    5. index和value的名称不是固定的,即程序员可以自行指定,一般命名为index和value

    func test04() {
    	heroes := [...]string{"松江", "吴用", "卢俊义"}
    	for i, v := range heroes {
    		fmt.Printf("i=%v v=%v\n", i, v)
    		fmt.Printf("heroes[%d] = %v\n", i, heroes[i])
    	}
    }
    /* 运行结果
    i=0 v=松江
    heroes[0] = 松江
    i=1 v=吴用
    heroes[1] = 吴用
    i=2 v=卢俊义
    heroes[2] = 卢俊义
    */

2. 数组使用细节

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度固定,不能动态变化

  2. var arr []int 这时arr就是一个slice切片

  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用

  4. 数组创建后,如果没有赋值,有默认值

    1. 数值类型的数组:默认值为0

    2. 字符串数组:默认为“”

    3. bool :默认是false

  5. 使用数组的步骤

    1. 声明数组并开辟空间

    2. 给数组元素赋值

    3. 使用数组

  6. 数组的下标是从0开始的

  7. 数组下标必须在指定范围内使用,否则报panic,数组越界

  8. go的数组属于值类型,默认情况下是值传递,因此进行值拷贝。数组间不会相互影响

  9. 如想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)

20. 切片

1. 基本介绍&创建

  1. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制

  2. 切片的使用和数组类似,遍历切片,访问切片的元素和求切片长度len(slice)都一样

  3. 切片的长度是可以变化的,可以理解为一个动态的数组

  4. 切片定义的基本语法

     var 切片名 []类型
     比如:var a[]int

     

 
func main() {
     //定义数组
     var intArr [5]int = [...]int{1, 22, 33, 66, 99}
     //声明切片
     //1.slice就是切片名
     //2.intArr[1:3] 表示 slice 引用到intArr这个数组
     //3.引用intArr数组的起始下标为1,最后的下标为3(不包含3)
     slice := intArr[1:3]
     fmt.Println("intArr=", intArr)
     fmt.Println("slice 的元素是 = ", slice)
     fmt.Println("slice 的元素个数 = ", len(slice))
     //切片的容量可以动态变化
     fmt.Println("slice 的容量 = ", cap(slice))
 ​
     slice[0] = 55
     fmt.Println("slice 的元素是 = ", slice)
     fmt.Println("intArr 的元素是 = ", intArr)
 ​
 }
 /*
 intArr= [1 22 33 66 99]
 slice 的元素是 =  [22 33]
 slice 的元素个数 =  2
 slice 的容量 =  4
 slice 的元素是 =  [55 33]
 intArr 的元素是 =  [1 55 33 66 99]
 */
  1. 切片创建的第二种方式,用make创建

    基本语法:

    var 切片名 []type = make([]type,len,[cap])

2. 切片总结:

  1. 通过make创建切片可以指定切片的大小和容量

  2. 如果没有给切片的各个元素赋值,那么就会使用默认值int-->0,float-->0,bool-->false

  3. 通过make方式创建的切片对应的数组是由make底层维护,对外不可见。

  4. 切片创建的第三种方式

     package main
     ​
     import "fmt"
     ​
     /**
       切片
     */
     ​
     //创建切片的第一种方式,直接切定义好的数组
     func sliceTest01() {
         //定义数组
         var intArr [5]int = [...]int{1, 22, 33, 66, 99}
         //声明切片
         //1.slice就是切片名
         //2.intArr[1:3] 表示 slice 引用到intArr这个数组
         //3.引用intArr数组的起始下标为1,最后的下标为3(不包含3)
         slice := intArr[1:3]
         fmt.Println("intArr=", intArr)
         fmt.Println("slice 的元素是 = ", slice)
         fmt.Println("slice 的元素个数 = ", len(slice))
         //切片的容量可以动态变化
         fmt.Println("slice 的容量 = ", cap(slice))
     ​
         slice[0] = 55
         fmt.Println("slice 的元素是 = ", slice)
         fmt.Println("intArr 的元素是 = ", intArr)
     }
     ​
     //创建切片的第二种方式 使用make来创建
     func sliceTest02() {
         //定义切片
         var slice []float64 = make([]float64, 5, 10)
         slice[1] = 10
         slice[3] = 20
         //对于切片,必须make使用
         fmt.Println(slice)
         fmt.Println("slice的size", len(slice))
         fmt.Println("slice的容量", cap(slice))
     }
     ​
     //创建切片的第三种方式
     func sliceTest03() {
         var strSlice []string = []string{"tom", "jack", "mary"}
         fmt.Println("strSlice= ",strSlice)
         fmt.Println("strSlice 的size =  ",len(strSlice))
         fmt.Println("strSlice 的容量 =  ",cap(strSlice))
     }
     func main() {
         fmt.Println("----------------------")
         sliceTest01()
         fmt.Println("----------------------")
         sliceTest02()
         fmt.Println("----------------------")
         sliceTest03()
     }
     /* 运行结果
     ----------------------
     intArr= [1 22 33 66 99]
     slice 的元素是 =  [22 33]
     slice 的元素个数 =  2
     slice 的容量 =  4
     slice 的元素是 =  [55 33]
     intArr 的元素是 =  [1 55 33 66 99]
     ----------------------
     [0 10 0 20 0]
     slice的size 5
     slice的容量 10
     ----------------------
     strSlice=  [tom jack mary]
     strSlice 的size =   3
     strSlice 的容量 =   3
     */

第一种方式和第二种方式的创建区别总结:

方式1:直接引用数组,这个数组是事先存在的,程序可见

方式2:通过make创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的

3. 切片的遍历

 //切片的遍历 常规方法
 func forSlice01() {
     var arr [5]int = [...]int{1, 2, 3, 4, 5}
     slice := arr[1:4]
     for i := 0; i < len(slice); i++ {
         fmt.Printf("slice[%v] = %v \n", i, slice[i])
     }
 }
 ​
 //切片的遍历,for-range遍历
 func forSlice02() {
     var arr [5]int = [...]int{1, 2, 3, 4, 5}
     slice := arr[1:4]
     for i, v := range slice {
         fmt.Printf("slice[%v] = %v \n", i, v)
     }
 }
 func main() {
     forSlice01()
     fmt.Println("----------------------")
     forSlice02()
 }
 /* 运行结果
 slice[0] = 2
 slice[1] = 3
 slice[2] = 4
 ----------------------
 slice[0] = 2
 slice[1] = 3
 slice[2] = 4
 */

4. 切片注意细节

 

!!切片可以继续切片

 

 

5. 切片拷贝

 //切片拷贝
 func testCorySlice() {
     var slice []int = []int{1, 2, 3, 4, 5}
     var sliceCopy = make([]int, 10)
     copy(sliceCopy, slice)
     fmt.Println("slice = ", slice)
     fmt.Println("sliceCopy = ", sliceCopy)
 }
 /* 运行结果
 slice =  [1 2 3 4 5]
 sliceCopy =  [1 2 3 4 5 0 0 0 0 0]
 */

6. 切片练习

 
//切片练习
 func testSlice() {
     var slice []int
     var arr [5]int = [...]int{1, 2, 3, 4, 5}
     slice = arr[:]
     var slice2 = slice
     slice2[0] = 10
     fmt.Println("slice2", slice2)
     fmt.Println("slice", slice)
     fmt.Println("arr", arr)
 }
 /* 运行结果
 slice2 [10 2 3 4 5]
 slice [10 2 3 4 5]
 arr [10 2 3 4 5]
 */

7. 切片和String

 

//切片和string
func sliceAndString() {
	str := "HelloWorld"
	slice := str[6:]
	fmt.Println("slice = ", slice)
}
/* 运行结果
slice =  orld
*/

8. 如何修改字符串

//用切片改string类型
func sliceChangeString() {
	str := "HelloWorld"
	slice := str[6:]
	fmt.Println("slice = ", slice)
	arr := []byte(str)
	arr[0] = 'Y'
	str = string(arr)
	fmt.Println("str = ", str)
	fmt.Println("以上代码不会转换中文")
	arr1 := []rune(str)
	arr1[2] = '北'
	str = string(arr1)
	fmt.Println("str = ", str)
}

/* 运行结果
slice =  orld
str =  YelloWorld
以上代码不会转换中文
str =  Ye北loWorld
*/

9. 切片小案例菲波那切数列

func testFibn(n int) []uint64 {
   //声明一个切片
   fbnSlice := make([]uint64, n)
   fbnSlice[0] = 1
   fbnSlice[1] = 1

   for i := 2; i < n; i++ {
      fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2]
   }
   return fbnSlice
}
/* 运行结果
[1 1 2 3 5 8 13 21 34 55]
*/

21. 排序和查找

1. 冒泡排序

//冒泡排序
func bubbleSort(arr *[5]int) {
	fmt.Println("排序前的顺序:", *arr)
	//定义临时变量
	temp := 0
	for j := 0; j < len(arr)-1; j++ {
		for i := 0; i < len(arr)-j-1; i++ {
			if (*arr)[i] > (*arr)[i+1] {
				temp = (*arr)[i]
				(*arr)[i] = (*arr)[i+1]
				(*arr)[i+1] = temp
			}
		}
	}
	fmt.Println("排序后的顺序:", *arr)
}

func main() {
	arr := [5]int{24, 55, 89, 1, 5}
	bubbleSort(&arr)
}
/*
排序前的顺序: [24 55 89 1 5]
排序后的顺序: [1 5 24 55 89]
*/

2. 顺序查找

//顺序查找
func findP() {
   names := [4]string{"红", "黄", "绿", "紫"}
   var heroName = ""
   fmt.Println("请输入要查找的颜色:")
   fmt.Scanln(&heroName)

   //顺序查找一
   for i := 0; i < len(names); i++ {
      if heroName == names[i] {
         fmt.Printf("找到%v,下标是%v\n", heroName, i)
         break
      } else if i == (len(names) - 1) {
         fmt.Printf("没有找到%v \n", heroName)
      }
   }

   //顺序查找二
   index := -1
   for i := 0; i < len(names); i++ {
      if heroName == names[i] {
         index = i
         break
      }
   }
   if index != -1 {
      fmt.Printf("找到%v,下标是%v\n", heroName, index)
   }
}

3. 二分查找

//二分查找
func binaryFind(arr *[6]int, leftIndex int, rightIndex int, findVal int) {
   if leftIndex > rightIndex {
      fmt.Println("找不到")
      return
   }
   mid := (leftIndex + rightIndex) / 2
   if (*arr)[mid] > findVal {
      binaryFind(arr, leftIndex, mid-1, findVal)
   } else if (*arr)[mid] < findVal {
      binaryFind(arr, mid+1, rightIndex, findVal)
   } else {
      fmt.Printf("找到了,下标为:%v \n", mid)
   }
}

func main() {
   arr := [6]int{24, 55, 89, 1, 5}
   //bubbleSort(&arr)
   binaryFind(&arr,0,len(arr),1)
}
/* 运行结果
找到了,下标为:3 
*/

22. 二维数组

1.定义

  1. 先声明再赋值

    1. 语法:var 数组名[大小] [大小]类型

    2. eg: var arr[2] [3]int[] []

  2. 直接使用初始化

    1. 声明:var 数组名[大小] [大小]类型 = [大小] [大小]类型{{初值..},{初值...}}

/**
  二维数组
*/
func test1() {
	//先声明后赋值
	var arr [4][6]int
	fmt.Println(len(arr), "+", len(arr[0]))
	arr[1][2] = 2
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[0]); j++ {
			fmt.Printf("[%v][%v] = %v ", i, j, arr[i][j])
		}
		fmt.Println()
	}

	//直接初始化
	var arr2 [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}}
	fmt.Println("arr2 = ", arr2)
}

2. 二维数组的遍历

//二维数组遍历
func traversArr() {
   var arr [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}}
   //第一种遍历
   for i := 0; i < len(arr); i++ {
      for j := 0; j < len(arr[i]); j++ {
         fmt.Println(arr[i][j])
      }
      fmt.Println()
   }

   //第二种遍历
   for i, v := range arr {
      for j, v2 := range v {
         fmt.Printf("arr[%v][%v] = %v", i, j, v2)
      }
      fmt.Println()
   }
}

23. Map

1. 声明方式(三种方式)

 

 

!!!!!声明时不会分配内存的,所以初始化需要make,分配后才能赋值和使用!!!!!

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
key_data_type 是键的类型
value_data_type 是值的类型

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

如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对

2. 案列

package main

import "fmt"

/**
  Map集合
*/
func testMap() {
	//声明的第一种方式
	//声明时不会分配内存的,所以初始化需要make,分配后才能赋值和使用
	var countryCapitalMap map[string]string /*创建集合 */
	//make相当于给map开辟数据空间
	countryCapitalMap = make(map[string]string)

	//声明的第二种方式
	//countryCapitalMap := make(map[string]string)

	//声明的第三种方式
	//var countryCapitalMap2 map[string]string= map[string]string{
	//	"F": "中",
	//	"C": "国",
	//}
	// countryCapitalMap2 := map[string]string{
	//	"F": "中",
	//	"C": "国",
	//}

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

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

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

func main() {
	testMap()
}
/* 运行结果
France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
India  首都是 新德里
American 的首都不存在
*/

 

3. map的Crud操作

//map的增删改
func mapCrud() {
	cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "天津"
	cities["no3"] = "上海"

	//修改 添加操作
	cities["no3"] = "上海~"
	fmt.Println(cities)

	//删除操作
	delete(cities, "no1")
	fmt.Println(cities)

	//map查找
	val, ok := cities["no1"]
	if ok {
		fmt.Printf("存在 值为%v\n",val)
	}else {
		fmt.Println("不存在")
	}

	//一次性删除所有的key
	cities = make(map[string]string)
	fmt.Println(cities)

}
/** 运行结果
map[no1:北京 no2:天津 no3:上海~]
map[no2:天津 no3:上海~]
不存在
map[]
*/

4. Map遍历

两种遍历

//map遍历
func traverseMap() {
	cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "天津"
	cities["no3"] = "上海"
	for k, v := range cities {
		fmt.Printf(" k = %v  v = %v \n", k, v)
	}

	//稍微复杂一点的遍历
	studentMap := make(map[string]map[string]string)

	studentMap["stu01"] = make(map[string]string, 3)
	studentMap["stu01"]["name"] = "tom"
	studentMap["stu01"]["sex"] = "男"
	studentMap["stu01"]["address"] = "上海"

	studentMap["stu02"] = make(map[string]string, 3)
	studentMap["stu02"]["name"] = "marry"
	studentMap["stu02"]["sex"] = "女"
	studentMap["stu02"]["address"] = "北京"
	for k1, v1 := range studentMap {
		fmt.Println("k1 = ", k1)
		for k2, v2 := range v1 {
			fmt.Printf("\t k2 = %v v2 = %v \n", k2, v2)
		}
		fmt.Println()
	}
}
/* 运行结果
GOROOT=D:\Application\go\go1.15.15.windows-amd64\go #gosetup
GOPATH=D:\Workplace\GoLand\awesomeProject;D:\Workplace\GoPro #gosetup
D:\Application\go\go1.15.15.windows-amd64\go\bin\go.exe build -o C:\Users\Administrator\AppData\Local\Temp\___go_build_main_go__3_.exe D:/Workplace/GoLand/awesomeProject/src/go_code/chapter03/demo24/main.go #gosetup
C:\Users\Administrator\AppData\Local\Temp\___go_build_main_go__3_.exe #gosetup
 k = no1  v = 北京 
 k = no2  v = 天津 
 k = no3  v = 上海 
k1 =  stu01
	 k2 = address v2 = 上海 
	 k2 = name v2 = tom 
	 k2 = sex v2 = 男 

k1 =  stu02
	 k2 = sex v2 = 女 
	 k2 = address v2 = 北京 
	 k2 = name v2 = marry 
*/

5. Map切片

 

 
//map切片
 func sliceMap() {
    //声明一个map切片
    var monsters []map[string]string
    monsters = make([]map[string]string, 2)
    if monsters[0] == nil {
       monsters[0] = make(map[string]string, 2)
       monsters[0]["name"] = "牛头"
       monsters[0]["age"] = "500"
    }
    if monsters[1] == nil {
       monsters[1] = make(map[string]string, 2)
       monsters[1]["name"] = "牛头"
       monsters[1]["age"] = "500"
    }
 ​
    //以下的添加是错误的 不能实现动态添加
    //if monsters[2] == nil {
    // monsters[2] = make(map[string]string, 2)
    // monsters[2]["name"] = "牛头"
    // monsters[2]["age"] = "500"
    //}
 ​
    //以下是正确的动态添加数据
    //使用append动态增加monster
    //先定义一个monster信息
    newMonster := map[string]string{
       "name": "append添加",
       "age":  "200",
    }
 ​
    monsters = append(monsters, newMonster)
 ​
    fmt.Println(monsters)
 }
 ​
 /* 运行结果
 [map[age:500 name:牛头] map[age:500 name:牛头] map[age:200 name:append添加]]
 */

三. Go-Kit

1. Go-kit简介

Go-kit 并不是一个微服务框架,而是一套微服务工具集,我们可以用工具Go-kit为 Go 创建微服务,包含包和接口,有点类似于JAVA Spring Boot,但是没那么强大。可以利用Go-kit提供的API和规范可以创建健壮的,可维护性高的微服务体系,它提供了用于实现系统监控和弹性模式组件的库,例如日志记录、跟踪、限流和熔断等,这些库可以协助开发人员提高微服务架构的性能和稳定性。

2. Go-kit与Go-Micro区别

Go-Kit 是一个微服务的标准库。它可以提供独立的包,通过这些包,开发者可以用来组建自己的应用程序。微服务架构意味着构建分布式系统,这带来了许多挑战,Go-Kit 可以为多数业务场景下实施微服务软件架构提供指导和解决方案。

  Go-Micro 是一个面向微服务的可插拔 RPC 框架,可以快速启动微服务的开发。Go-Micro 框架提供了许多功能,无须重新“造轮子”,所以开发者可以花更多的时间在需要关注的业务逻辑上。但是 Go-Micro 在快速启动微服务开发的同时,也牺牲了灵活性,并且将 gRPC 强制为默认通信类型,更换组件不如 Go-Kit 简便。

3. 当前集成工具

ircuit breaker断路器hystrix-go gobreaker handy breaker
Metrics 指标prometheus, dogstatsd, influx,graphite 等多个平台
服务发现consul, dnssrv, etcd, eureka, lb, zookeeper
Request TracingOpentracing, LightStep, AppDash, Zipkin

4. 框架结构

-1. 三层架构

     1.传输层(Transport): 用于网络通信,服务通常使用 HTTP 或 gRPC 等网络传输方式,或使用 NATS 等发布订阅系统相互通信。除此之外,Go-Kit 还支持使用 AMQP 和 Thrift 等多种网络通信模式。。
     2.接口层(EndPoint): 定义Request、Response格式,并可以使用装饰器(闭包)包装函数,以此来实现各个中间件嵌,比如在请求响应的时候添加日志等。 在Go-Kit中,服务中的每个对外提供的接口方法都会被定义为一个端点,以便在服务器和客户端之间进行网络通信。每个端点通过使用 HTTP 或 gRPC 等具体通信模式对外提供服务。
    3.服务层(Service): 这里就是我们具体的业务逻辑实现,包括核心业务逻辑、接口等相关信息存放,。它不会也不应该进行 HTTP 或 gRPC 等具体网络传输,或者请求和响应消息类型的编码和解码。。

-2. 调用关系

详情参见:kit框架详解(基于go-kit)_rabbit0206的博客-CSDN博客_go-kit

client访问微服务http server;经过transport层是decode获得http包request数据;然后交个endpoint处理,endpoint在go-kit是一个重要的概念,代表了一个可执行的服务,表现形式是一个函数type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error);endpoint将request传给业务函数处理,返回response,最后在一层层返回给client。

 

4.1 Endpoint(端点)

Go kit首先解决了RPC消息模式。其使用了一个抽象的 endpoint 来为每一个RPC建立模型。

endpoint通过被一个server进行实现(implement),或是被一个client调用。这是很多 Go kit组件的基本构建代码块。

4.2 Circuit breaker(回路断路器)

Circuitbreaker(回路断路器) 模块提供了很多流行的回路断路lib的端点(endpoint)适配器。回路断路器可以避免雪崩,并且提高了针对间歇性错误的弹性。每一个client的端点都应该封装(wrapped)在回路断路器中。

4.3 Rate limiter(限流器)

ratelimit模块提供了到限流器代码包的端点适配器。限流器对服务端(server-client)和客户端(client-side)同等生效。使用限流器可以强制进、出请求量在阈值上限以下。

4.4 Transport(传输层)

transport 模块提供了将特定的序列化算法绑定到端点的辅助方法。当前,Go kit只针对JSON和HTTP提供了辅助方法。如果你的组织使用完整功能的传输层,典型的方案是使用Go在传输层提供的函数库,Go kit并不需要来做太多的事情。这些情况,可以查阅代码例子来理解如何为你的端点写一个适配器。目前,可以查看 addsvc的代码来理解Transport绑定是如何工作的。我们还提供了针对Thirft,gRPC,net/rpc,和http json的特殊例子。对JSON/RPC和Swagger的支持在计划中。

4.5 Logging(日志)

服务产生的日志是会被延迟消费(使用)的,或者是人或者是机器(来使用)。人可能会对调试错误、跟踪特殊的请求感兴趣。机器可能会对统计那些有趣的事件,或是对离线处理的结果进行聚合。这两种情况,日志消息的结构化和可操作性是很重要的。Go kit的log 模块针对这些实践提供了最好的设计。

4.6 Metrics(Instrumentation)度量/仪表盘

直到服务经过了跟踪计数、延迟、健康状况和其他的周期性的或针对每个请求信息的仪表盘化,才能被认为是“生产环境”完备的。Go kit 的 metric 模块为你的服务提供了通用并健壮的接口集合。可以绑定到常用的后端服务,比如 expvarstatsdPrometheus

4.7 Request tracing(请求跟踪)

随着你的基础设施的增长,能够跟踪一个请求变得越来越重要,因为它可以在多个服务中进行穿梭并回到用户。Go kit的 tracing 模块提供了为端点和传输的增强性的绑定功能,以捕捉关于请求的信息,并把它们发送到跟踪系统中。(当前支持 Zipkin,计划支持Appdash

4.8 Service discovery and load balancing(服务发现和负载均衡)

如果你的服务调用了其他的服务,需要知道如何找到它(另一个服务),并且应该智能的将负载在这些发现的实例上铺开(即,让被发现的实例智能的分担服务压力)。Go kit的 loadbalancer模块提供了客户端端点的中间件来解决这类问题,无论你是使用的静态的主机名还是IP地址,或是 DNS的 SRV 记录,Consul,etcd 或是 Zookeeper。并且,如果你使用定制的系统,也可以非常容易的编写你自己的 Publisher,以使用 Go kit 提供的负载均衡策略。(目前,支持静态主机名、etcd、Consul、Zookeeper)

四. Gorm

官方文档 :Create | GORM - The fantastic ORM library for Golang, aims to be developer friendly.Create Recorduser := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}result := db.Create(&user) // pass pointer of data to Createuser.ID // returns inserted data's primaryhttps://gorm.io/docs/create.html

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值