我的golang基础

隔壁老王
• 阅读 1425

库查询 https://gowalker.org/

你应该$HOME/.profile文件增加下面设置。

搭建go的环境
step1:去golang的官网下载go的安装包
windows:go1.9.2.....msi
mac:go1.9.2......pkg
    双击傻瓜式安装
linux:go1.9.2.linux-amd64.tar.gz
    默认到下载目录下:
        需要将压缩包解压到指定的目录下:/usr/local
    命令:cd 下载
    命令:sudo tar -xzf go1.9.2.linux-amd64.tar.gz -C /usr/local

    配置环境变量:
        GOROOT:安装的目录
            bin目录:go命令

        ** GOPATH:用户编写的go的源代码的位置 **
            src:源代码
            pkg:编译后的包的位置
            bin:可执行文件

        默认在home/ruby/go
                       桌面,下载,文档。。。。。

==注意: GOPATH目录必须存在src、pkg、bin文件夹==

将goroot和gopath配置到ubuntu的配置文件中:$HOME/.profile

cd进入$HOME,
ls -a,查看文件,包含隐藏:.profile
使用vim编辑.profile
    a)vi .profile,打开该文件
    b) 输入字母i,表示进入vim的编辑模式
    c) 将goroot,gopath配置到文件中
        export PATH=$PATH:/usr/local/go/bin
        export GOPATH=$HOME/go
    d) 保存文件并退出
        点击esc,退出编辑模式
        输入:wq,保存并 退出
需要让配置好的.profile文件生效
    a)进入到/usr/local目录
    b)执行命令:source $HOME/.profile

访问go官网需要翻墙 https://golang.org/

Go语言最主要的特性:

  • 自动垃圾回收
  • 更丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 反射
  • 语言交互性

go的源码文件分三类 命令源码文件、库源码文件、测试源码文件 命令源码文件:

声明自己属于main代码包、包含无参数声明和结果声明的main函数
被安装后,相应的可执行文件会被存放到GOBIN指向的目录或<当前工作区目录>/bin下
命令源码文件是Go程序的入口,但不建议把程序都写在一个文件中

注意:同一个代码包中强烈不建议直接包含多个命令源码文件

库源码文件:

不具备命令源码文件的那两个特征的源码文件
被安装后,相应的归档文件会被存放到<当前工作区目录>/pkg/<平台相关目录>下

测试源码文件:

不具备命令源码文件的那两个特征的源码文件
名称以_test.go为后缀
其中至少有一个函数的名称以Test或Benchmark为前缀
并且,该函数接收一个类型为*testing.T或*testing.B的参数
func TestFind(t*testing.T){
    // 省略若干条语句
}

第一行代码 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 )。

导包方式import "包名"

import . "包名"不用给包名起别名,import 别名 "包名"

只做初始化源码文件使用import _ "包名"

也可以用路径指向包import "路径/包"可以是绝对路径或者相对路径

要导入多个包使用import ("包名1" "包名2")

go run 文件名,对go源码文件编译并执行

-a:强制编译相关代码,不论他们的编译结果是否是最新的
-n:打印编译过程中所需运行的命令,但不真正执行他们
-p n:并行编译,其中n为并行的数量
-v:列出被编译的代码包的名称。如果和-a连用会打印所有代码包1.3中的所有指包含Go语言自带的标准库的代码包,1.4中的所有不包含Go语言自带的标准包
-work:显示编译时创建的临时工作目录的路径,并且不删除它
-x:打印编译过程中所需运行的命令,并执行

go语言关键字| | | |
---|---|---|---|--- 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

标识符| | | | | | | | ---|---|---|---|---|---|---|---|--- append | bool | byte | cap | close | complex | complex64| complex128 | uint16 copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 int32 | int64 | iota | len | make | new | nil | panic | uint64 print | println | real | recover | string | true | uint | uint8| uintptr

占位符 含义
%% 一个%字面量
%b 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d 一个十进制数值(基数为10)
%e 以科学记数法e表示的浮点数或者复数值
%E 以科学记数法E表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以%E或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字(基数为8)
%p 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%t 以true或者false输出的布尔值
%T 使用Go语法输出的值的类型
%U 一个用Unicode表示法表示的整型码点,默认值为4个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话
%x 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示

占位符 https://studygolang.com/articles/2644

println是标准输出
printf是占位符替换输出 "%d",10

Go 语言中变量的声明必须使用空格隔开,如:

var age int; // ;可以省略
age := 1;

无空格:

fruit = apples + oranges; 

数据类型: 序号 | 类型和描述 ---|--- 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 类型


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

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

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

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
var vname1, vname2, vname3 = v1, v2, v3 //和python很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 //出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误
//这种不带声明格式的只能在函数体中出现

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

==函数体内,如果有些变量不需要声明类型,单纯地赋值也是不够的,这个值必须被使用==

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

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

常量的定义格式:

const identifier [type] = value

常量还可以用作枚举:

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出现之前,每出现一次iota,其所代表的数字会自动增加1。

iota 可以被用作枚举值:

const (
    a = iota
    b = iota
    c = iota
)
package main

import "fmt"

const (
    a = iota //0
    b         //1
    c         //2
    d = "ha" //ha
    e         //ha
    f = 100  //100
    g         //100
    h = iota //7
    i         //8
)

func main()  {
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

算术运算符 运算符 | 描述 | 实例 ---|---|---

  • | 相加 | A + B 输出结果 30
  • | 相减 | A - B 输出结果 -10
  • | 相乘 | A * B 输出结果 200 / | 相除 | B / A 输出结果 2 % | 求余 | B % A 输出结果 0
    • | 自增 | A++ 输出结果 11
    • | 自减 | A-- 输出结果 9

关系运算符 运算符 | 描述 | 实例 ---|---|--- == | 检查两个值是否相等,如果相等返回 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

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

位运算 p | q | p & q | p | q | p ^ q ---|---|---|---|--- 0 | 0 | 0 | 0 | 0 0 | 1 | 0 | 1 | 1 1 | 1 | 1 | 1 | 0 1 | 0 | 0 | 1 | 1

位运算符号 运算符 | 描述 | 实例 ---|---|--- & | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。| (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

赋值运算符 运算符 | 描述 | 实例 ---|---|--- = | 简单的赋值运算符,将一个表达式的值赋给一个左值 | 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

其他运算符 运算符 | 描述 | 实例 ---|---|--- & | 返回变量存储地址 | &a; 将给出变量的实际地址。

  • | 指针变量。 | *a; 是一个指针变量

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

判断语句 语句 | 描述 ---|--- if 语句 | if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 if...else 语句 | if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。 if 嵌套语句 | 你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。 switch 语句 | switch 语句用于基于不同条件执行不同动作。 select 语句  | select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

以下描述了 select 语句的语法:

每个case都必须是一个管道
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行;其他被忽略。
如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。 
否则:
    1.如果有default子句,则执行该语句。
    2.如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

语法

Go语言的For循环有3中形式,只有其中的一种使用分号。

和 C 语言的 for 一样:

for init; condition; post { }

和 C 的 while 一样:

for condition { }

和 C 的 for(;;) 一样:

for { }

循环语句嵌套

for [condition |  ( init; condition; increment ) | Range]
{
    for [condition |  ( init; condition; increment ) | Range]
    {
        statement(s);
    }
    statement(s);
}
  • init: 一般为赋值表达式,给控制变量赋初值;
  • condition: 关系表达式或逻辑表达式,循环控制条件;
  • post: 一般为赋值表达式,给控制变量增量或减量。

for语句执行过程如下:

①先对表达式1赋初值;
②判别赋值表达式 init 是否满足给定条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。

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

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

函数

Go 语言最少有个 main() 函数。 GO 语言每个文件最多只有一个init()函数,该函数会被自动调用用于初始化操作

你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。

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

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

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

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

函数定义解析:

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

初始化局部和全局变量

数据类型 初始化默认值
int 0
float32 0
pointer nil

声明数组

var variable_name [SIZE] variable_type

多维数组

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

声明指针

var var_name *var-type

定义指针数组

var ptr [MAX]*int

指向指针的指针

var ptr **int

定义结构体

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

结构体指针

var struct_pointer *Books

定义切片

var identifier []type

切片不需要说明长度。

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

var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)

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

make([]T, length, capacity)

len() 方法获取长度

cap() 可以测量切片最长可以达到多少

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

Map(集合)

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

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

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

/* 使用make 函数 */
chan1 := make(chan type,(.capacity))

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

递归函数

func recursion() {
    recursion() /* 函数调用自身 */
}

func main() {
    recursion()
}
斐波那契数列
package main

import "fmt"

var n1=0
var n2=0

func fibonacci(n int) int {
    n1++
    if n < 2 {
        return n
    }
    return fibonacci(n-2) + fibonacci(n-1)
}

func feibo(a,b,n int) int {
    // 优化版,需要传起始参数,如果指定a,b大于0修改n>2
    n2++
    if (n > 0) {
        return feibo(a+b, a, n-1)
    }
    return a
}

func main() {
    var i int
    var j int
    for i = 0; i < 10; i++ {
        fmt.Printf("%d\t", fibonacci(i))
    }
    fmt.Println(n1)
    for j = 0; j < 10; j++ {
        fmt.Printf("%d\t",feibo(0, 1, j))
    }
    fmt.Println(n2)
}

类型转换

type_name(expression)

==type_name 为类型,expression 为表达式。==

接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

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

错误处理

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

type error interface {
    Error() string
}

我们可以在编码中通过实现 error 接口类型来生成错误信息。

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

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

go语言深拷贝

import (
    "fmt"
    "bytes"
    "encoding/gob"
)

func main() {
    a := make(map[int]int, 0)
    b := map[int]int{1:1,2:2}
    c := deepCopy(&a, &b)
    fmt.Println(a,b,c)
}

func deepCopy(dst, src interface{}) error {
    var buf bytes.Buffer
    if err := gob.NewEncoder(&buf).Encode(src); err != nil {
        return err
    }
    return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}

时间格式

const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)
其它标记
占位符 说明 举例 输出
+ 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。 Printf("%+q", "中文") "\u4e2d\u6587"
- 在右侧而非左侧填充空格(左对齐该区域)
# 备用格式:为八进制添加前导 0(%#o) Printf("%#U", '中') U+4E2D
为十六进制添加前导 0x(%#x)或 0X(%#X),为 %p(%#p)去掉前导 0x;
如果可能的话,%q(%#q)会打印原始 (即反引号围绕的)字符串;
如果是可打印字符,%U(%#U)会写出该字符的
Unicode 编码形式(如字符 x 会被打印成 U+0078 'x')。
' ' (空格)为数值中省略的正负号留出空白(% d);
以十六进制(% x, %X)打印字符串或切片时,在字节之间用空格隔开
0 填充前导的0而非空格;对于数字,这会将填充移到正负号之后

同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。 该语句声明的变量作用域仅在 if 之内。

if v := math.Pow(x, n); v < lim {
        return v
    }
// strings包
// Author: Wjy
import (
    "strings"
    "fmt"
)

func main() {
    s1 := "hello world"
    //判断字符是否在字符串中
    fmt.Println(strings.Contains(s1, "l"))
    //判断字符串中是否有字符在s1中
    fmt.Println(strings.ContainsAny(s1, "wh"))
    fmt.Println(strings.ContainsRune(s1, 'h'))
    //判断字符的次数
    fmt.Println(strings.Count(s1, "l"))

    s2 := "2018课堂笔记.txt"
    if strings.HasPrefix(s2, "2018"){
        fmt.Println("前缀是2018")
    }
    if strings.HasSuffix(s2, "txt"){
        fmt.Println("后缀是txt")
    }
    //查找第一次出现的索引位置,不存在返回-1
    fmt.Println(strings.Index(s1, "l"))
    fmt.Println(strings.IndexAny(s1, "ado"))
    查找最后一次出现的位置
    fmt.Println(strings.LastIndex(s1, "l"))

    a := []string{"1","2","3","4"}
    b := strings.Join(a, "")
    fmt.Println(b)
    //字符串分割
    fmt.Println(strings.Split(b,""))
    //字符串分割成几个,个数是-1==splite
    fmt.Println(strings.SplitN(b, "", 3))
    //自己拼接自己
    fmt.Println(strings.Repeat("hello", 2))
    //替换n为替换次数
    fmt.Println(strings.Replace(b,"2","3",-1))
    //大写
    fmt.Println(strings.ToUpper(s1))

    s6 := " **+hello+*world** "
    //删除首尾字符
    fmt.Println(strings.Trim(s6,"*+ "))
    //删除左侧
    fmt.Println(strings.TrimLeft(s6, ""))
    //删除右侧
    fmt.Println(strings.TrimRight(s6, ""))
    //去首位空格
    fmt.Println(strings.TrimSpace(s6))

    s7:=s6[4:8]
    fmt.Println(s7)
    a:="我们去秋游"
    fmt.Println(strings.Index(a, "去"))
    fmt.Println(strings.Contains(a, "秋游"))
}
strconv转换
func ParseBool(str string) (value bool, err error)
//将字符串转换为布尔值
strconv.ParseBool("1") // true

func FormatBool(b bool) string
//FormatBool 将布尔值转换为字符串 "true" 或 "false"
strconv.FormatBool(0)

func ParseFloat(s string, bitSize int) (f float64, err error)
//将字符串转换为 float64 型,bitSize:指定浮点类型(32:float32、64:float64)

func ParseInt(s string, base int, bitSize int) (i int64, err error)
//将字符串转换为 int 类型,base:进位制(2 进制到 36 进制),bitSize:指定整数类型(0:int、8:int8、16:int16、32:int32、64:int64)

func Atoi(s string) (i int, err error)
//将字符串转换为 int 类型

func FormatInt(i int64, base int) string
//FormatUint 将 int 型整数 i 转换为字符串形式

func Itoa(i int) string
//返回数字 i 所表示的字符串类型的十进制数

浮点数到字符串
strconv.FormatFloat(float64(input_num), 'f', 6, 64)

整形到字符串
s = strconv.Itoa(i) 或者 s = FormatInt(int64(i), 10)
math模块部分函数
https://godoc.org/math
fmt.Println(math.Abs(float64(i))) //绝对值
   fmt.Println(math.Ceil(5.0))       //向上取整
   fmt.Println(math.Floor(5.8))      //向下取整
   fmt.Println(math.Mod(11, 3))      //取余数,同11%3
   fmt.Println(math.Modf(5.26))      //取整数,取小数
   fmt.Println(math.Pow(3, 2))       //x的y次方
   fmt.Println(math.Pow10(4))        // 10的n次方
   fmt.Println(math.Sqrt(8))         //开平方
   fmt.Println(math.Cbrt(8))         //开立方

list操作 https://godoc.org/container/list#New

func (e *Element) Next() *Element  //返回该元素的下一个元素,如果没有下一个元素则返回nil
func (e *Element) Prev() *Element//返回该元素的前一个元素,如果没有前一个元素则返回nil。
type List
func New() *List //返回一个初始化的list
func (l *List) Back() *Element //获取list l的最后一个元素
func (l *List) Front() *Element //获取list l的第一个元素
func (l *List) Init() *List  //list l初始化或者清除list l
func (l *List) InsertAfter(v interface{}, mark *Element) *Element  //在list l中元素mark之后插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
func (l *List) InsertBefore(v interface{}, mark *Element) *Element//在list l中元素mark之前插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
func (l *List) Len() int //获取list l的长度
func (l *List) MoveAfter(e, mark *Element)  //将元素e移动到元素mark之后,如果元素e或者mark不属于list l,或者e==mark,则list l不改变。
func (l *List) MoveBefore(e, mark *Element)//将元素e移动到元素mark之前,如果元素e或者mark不属于list l,或者e==mark,则list l不改变。
func (l *List) MoveToBack(e *Element)//将元素e移动到list l的末尾,如果e不属于list l,则list不改变。
func (l *List) MoveToFront(e *Element)//将元素e移动到list l的首部,如果e不属于list l,则list不改变。
func (l *List) PushBack(v interface{}) *Element//在list l的末尾插入值为v的元素,并返回该元素。
func (l *List) PushBackList(other *List)//在list l的尾部插入另外一个list,其中l和other可以相等。
func (l *List) PushFront(v interface{}) *Element//在list l的首部插入值为v的元素,并返回该元素。
func (l *List) PushFrontList(other *List)//在list l的首部插入另外一个list,其中l和other可以相等。
func (l *List) Remove(e *Element) interface{}//如果元素e属于list l,将其从list中删除,并返回元素e的值。

关于map嵌套赋值

type t_classmates map[string]int
 domitory := make(map[string]t_classmates)
domitory["309"] = t_classmates{"wangwu": 25, "zhaoliu": 26,}
或者
a := make(map[string]map[string]int)
c := make(map[string]int)
a["key"]=c

底层一些的地方,bufio.Scanner,ioutil.ReadFile和ioutil.WriteFile使用的都是*os.File的 Read和Write方法

switch语句写条件表达式case只能使用某个值或者变量,不能添加判断语句。
switch省略条件表达式,可当 if...else if...else 使⽤
case关键字后加fallthrough关键字紧跟该条件的条件语句会顺序执行。
#string到int  
int,err:=strconv.Atoi(string)  
#string到int64  
int64, err := strconv.ParseInt(string, 10, 64)  
#int到string  
string:=strconv.Itoa(int)  
#int64到string  
string:=strconv.FormatInt(int64,10) 

时间格式化yyyy-MM-dd hh:mm:ss
var _startDate int64 = time.Now().Unix()
var startDate string = time.Unix(_startDate, 0).Format("2006-01-02 15:04:05")

int64转10位的时间
t := time.Now().Unix()
str := strconv.FormatInt(t, 10)

数组赋值 var nums [...]int{2:1,3:2} 指定位置参数或者取下标赋值

break 可⽤用于 for、switch、select,⽽而 continue 仅能⽤用于 for 循环。


使⽤用 slice 对象做变参时,必须展开。
func main() {
    s := []int{1, 2, 3}
    println(test("sum: %d", s...))
}
func test2(s string,n ...int) string {
   var x int
   for _, i := range n{
      x += i
   }
   return fmt.Sprintf(s, x)
}
类
package main

import "fmt"

// Author: Wjy
type Worker struct {
    name string
    age int
    sex string
}

type Cat struct {
    color string
    age int
}

func (w Worker) work()  {
    fmt.Println(w)
}

func (w Worker) eat() {
    fmt.Println("工人吃")
}

func (c Cat) eat()  {
    fmt.Println(c)
}

func (c *Cat) sleep() {
    fmt.Println(c.color,(*c).age)
    //c.color = "黑"
}

func main() {
    //w := Worker{"啊",30,"男"}
    //w.work()
    //w.eat()
    //
    //c := Cat{"红",1}
    //c.eat()
    //c.sleep()
    //c1 := &c
    //(*c1).eat()

    f := 父类{"a",12}
    f.eat()
    z := 子类{父类{"b",3},"adsa"}
    z.eat()
}

type 父类 struct {
    name string
    age int
}

type 子类 struct {
    父类
    school string
}

func (f 父类) eat()  {
    fmt.Println(f)
}

func (z 子类) eat()  {
    fmt.Println(z)
}
// 排序
package main

import (
    "sort"
    "fmt"
)

// Author: Wjy
type Person struct {
    name string
    age int
}

func (p *Person) String()string {
    return fmt.Sprint(*p)
}

func main() {
    p1:=Person{"aaa",30}
    p2:=Person{"bbb",29}
    p3:=Person{"ccc",31}
    p4:=Person{"ddd",29}
    p5:=Person{"eee",35}

    s1:=make([]*Person,0)
    s1 = append(s1,&p1,&p2,&p3,&p4,&p5)

    s2 := PersionSlice(s1)
    sort.Sort(s2)
    fmt.Println(s2)
}

type PersionSlice []*Person

func (slice PersionSlice) Less(i,j int) bool {
    if slice[i].age < slice[j].age{
        return true
    }else if slice[i].age > slice[j].age{
        return false
    }else {
        return slice[i].name<slice[j].name
    }
}

func (slice PersionSlice) Swap(i,j int) {
    slice[i],slice[j]=slice[j],slice[i]
}

func (slice PersionSlice) Len() int {
    return len(slice)
}
// 接口
package main

import "fmt"

// Author: Wjy
type USB interface {
    start()
    end()
}

// 实现类
type Mouse struct {
    name string
}

func (m Mouse)start(){
    fmt.Println(m,"点点")
}

func (m Mouse)end(){
    fmt.Println(m,"ddd")
}

type Flash struct {
    name string
}

func (f Flash)start(){fmt.Println("u",f)}
func (f Flash)end(){fmt.Println(f)}

func main()  {
    m := Mouse{"罗技"}
    f := Flash{"闪迪"}
    var usb USB // 定义接口类型的变量
    // 接口对象不能访问实现类的属性
    //usb = m
    usb = f
    usb.start()

    testInterface(m)

}

func testInterface(usb USB){
    usb.start()
}
//断言
package main

import (
    "math"
    "fmt"
)
// Author: Wjy
type Shape interface {
    peri() float64
    area() float64
}
type Triangle struct {
    a,b,c float64
}
func (t Triangle)peri()float64  {
    return t.a+t.b+t.c
}
func (t Triangle)area()float64  {
    p :=t.peri() / 2
    s := math.Sqrt(p*(p-t.a)*(p-t.b)*(p-t.c))
    return s
}
func main() {
    /*
    多态
     */
     t1 := Triangle{3,4,5}
     fmt.Println(t1.peri())
     fmt.Println(t1.area())

     var s1 Shape
     s1 = t1
     fmt.Println(s1.peri())
     fmt.Println(s1.area())

     var c1 Circle
     c1 = Circle{4}
     fmt.Println(c1.peri())
     fmt.Println(c1.area())
     fmt.Println(c1.radius)

     var s2 Shape = Circle{5}
     fmt.Println(s2.area())
     fmt.Println(s2.peri())

     testShap(t1)
     // 如果定义了一个接口类型数组可以传任意实现类对象
     arr := [4]Shape{t1,s1,c1,s2}
     fmt.Println(arr)

     getType(s1)
     getType(s2)
     getType(c1)

     getTpye2(s1)
     getTpye2(t1)
}

type Circle struct {
    radius float64
}
func (c Circle)peri()float64  {
    return c.radius*2*math.Pi
}
func (c Circle)area()float64  {
    return math.Pow(c.radius, 2)*math.Pi
}
//func (c Circle)new()  {
//    fmt.Println(c,"new")
//}
func testShap(s Shape){
    fmt.Println(s.peri(),s.area())
}
// 接口转实现类,方法1
func getType(s Shape){
    //instance, ok := s.(Circle) // 判断实现类
    //fmt.Println(instance.radius, ok)

    if ins,ok:=s.(Triangle);ok{
        fmt.Println("三角形",ins.a,ins.b,ins.c)
    }else if ins,ok:=s.(Circle);ok{
        fmt.Println("圆",ins.radius)
    }else {
        fmt.Println("不是圆和三角")
    }
}
// 接口转实现类,方法2
func getTpye2(s Shape){
    switch ins:=s.(type) {
    case Triangle: fmt.Println("三角形",ins)
    case Circle: fmt.Println("圆", ins.radius)
    //case int: fmt.Println("整数")
    }
}
// 自定义error error是内置类型跟nil类似
package main

import "fmt"

// Author: Wjy

func main() {
     res1, ok := getArea(-4,6)
     if ok != nil{
         fmt.Println(ok.Error())
    }else {
        fmt.Println("面积是",res1)
    }
}

type errorRect struct {
    msg string
    wid float64
    len float64
}

func (e *errorRect) Error()string{
    return fmt.Sprintf("宽度是 %.2f,长度 %.2f, 错误信息 %s",e.wid,e.len,e.msg)
}

func getArea(wid, len float64)(float64,error){
    errorMsg := ""
    if wid < 0{
        errorMsg = "宽度为复数"
    }
    if len < 0{
        if errorMsg == ""{
            errorMsg = "长度为复数"
        }else {
            errorMsg += ",长度为复数"
        }
    }
    if errorMsg!=""{
        return 0,&errorRect{errorMsg,wid,len}
    }
    area := wid * len
    return area,nil
}
//异常处理
package main

import "fmt"

// Author: Wjy

func main() {
    //read:= bufio.NewReader(os.Stdin)
    //data,_,_:=read.ReadLine()
    //fmt.Println(string(data))

    funA()
    funB()
    fmt.Println("over")

    funC()
}

func funA()  {
    fmt.Println("funcA()...")
}
func funB()  {
    defer func(){
        if msg:=recover();msg!=nil{
            fmt.Println(msg,"已恢复")
        }
    }()
    fmt.Println("我是函数funB()...")
    for i:=1;i<=10;i++{
        fmt.Println("i:",i)
        if i == 5{
            // 让程序终端
            panic("funB,恐慌")// 打断程序的执行
        }
    }
}

func funC()  {
    defer func() {fmt.Println("funC",recover())}()
    fmt.Println("func")
}
// time包
package main

import (
    "time"
    "fmt"
    "math/rand"
)

// Author: Wjy
func main() {
    t1:=time.Now() // 获取当前时间
    fmt.Printf("%T\n",t1)
    fmt.Println(t1)
    // 指定日期时间
    t2 := time.Date(2018,10,22,16,30,28,0,time.Local)
    fmt.Println(t2)
    // 时间对象转字符串
    fmt.Println(t1.String())
    // 格式化输出日期
    fmt.Println(t1.Format("Mon Jan 2 15:04:05 -0700 MST 2006"))
    // 6-1-2-3-4-5
    fmt.Println(t1.Format("2006年01月02日 15:04:05"))
    // 字符串转时间对象
    s3 := "1999年10月10日"
    ss3,ok:=time.Parse("2006年01月02日",s3)
    fmt.Println(ss3,ok)

    t4 := time.Date(1970,1,1,1,0,0,0,time.UTC)
    fmt.Println(t4.Unix()) // 获取秒数
    fmt.Println(t1.Unix()-t4.Unix()) // 格林威治时间到现在的秒数

    fmt.Println(t1.UnixNano()-t4.UnixNano()) // 格林威治时间到现在的微秒

    y,m,d := t1.Date() // 获取当前时间对象的年,月,日
    fmt.Println(y,m,d)
    h,min,s := t1.Clock() // 获取当前时间对象的时,分,秒
    fmt.Println(h,min,s)

    year2 := t1.Year()
    fmt.Println(year2)
    fmt.Println(t1.YearDay())
    month2 := t1.Month()
    fmt.Println(month2)
    fmt.Println(t1.Day())
    fmt.Println(t1.Hour())
    fmt.Println(t1.Minute())
    fmt.Println(t1.Second())
    fmt.Println(t1.Weekday())
    fmt.Println(t1.ISOWeek()) // 年,周

    t5 := t1.Add(time.Minute)
    fmt.Println(t1,t5)
    fmt.Println(t1.Add(24*time.Hour))

    fmt.Println(t5.Sub(t1)) // 求两时间差
    //time.Sleep(time.Second*10)

    rand.Seed(time.Now().UnixNano())
    randNum := rand.Intn(10)+1
    time.Sleep(time.Duration(randNum)*time.Second)
    fmt.Println(time.Duration(randNum)*time.Second)
}
//文件操作
package main

import (
    "os"
    "fmt"
    "path"
    "path/filepath"
)

// Author: Wjy
func main() {
    file,ok := os.Stat("./src")
    if ok != nil{
        fmt.Println(ok)
        return
    }
    //os.Open("")
    fmt.Printf("%T\n",file)
    fmt.Println(file.Name())
    fmt.Println(file.IsDir())
    fmt.Println(file.Mode())
    fmt.Println(file.ModTime())
    fmt.Println(file.Size())

    filename := "E:/goland/bin"
    filename1 := "/home/path"
    fmt.Println(path.IsAbs(filename1))
    fmt.Println(filepath.IsAbs(filename))

    fmt.Println(filepath.Abs(filename))

    fmt.Println(path.Join(filename,".."))
    // 创建文件夹
    //errs:=os.MkdirAll("./newpath/w/d/s",777)
    //if errs !=nil{
    //    fmt.Println(errs)
    //}else {
    //    fmt.Println("ok")
    //}
    // 如果文件存在create会覆盖创建
    //tfile,err := os.Create("测试用的文件.txt")
    //fmt.Println(err)
    //tfile.WriteString("lakakkakakasduhiuh我i我i我i我i")
    //tfile.Close()

    //fil,err :=os.Open("测试用的文件.txt")
    //fmt.Println(err)
    //fmt.Println(fil)
    //fil.Close()

    fil1,err1:=os.OpenFile("测试用的文件.txt",os.O_RDWR|os.O_CREATE,777)
    fmt.Println(fil1,err1)
    fil1.Close()

    // 可以删除文件和空目录
    //err :=os.Remove("./测试用的文件.txt")
    //fmt.Println(err)
    //err := os.RemoveAll("目录")//删除整个目录
}
// 文件读写
package main

import (
    "os"
    "fmt"
    "io"
)

// Author: Wjy
func main() {
    /*
    打开文件
    读取文件
        file.Read([]byte)-->n,err
        n: 读取的数据个数
        err: 错误信息
    关闭文件
     */
    filename := "./测试用的文件.txt"
    file,err:=os.Open(filename)
    if err!=nil{
        fmt.Println(err)
        return
    }
    bs := make([]byte,5,5)
    for {
        n,err :=file.Read(bs)
        if n == 0 || err == io.EOF{
            fmt.Println("结束了")
            break
        }
        fmt.Println(n)
        fmt.Println(bs[:n])
        fmt.Println(string(bs[:n]))
    }
    //n,err=file.Read(bs)
    //fmt.Println(n)
    //fmt.Println(bs)
    //fmt.Println(string(bs))
    //fmt.Println(err)
    //
    //n,err=file.Read(bs)
    //if n < 5{
    //    fmt.Println(n)
    //    fmt.Println(bs)
    //    fmt.Println(string(bs[:n]))
    //    fmt.Println(err)
    //    return
    //}

    file.Close()

}
// ioutil包读写文件
package main

import (
        "os"
    "bufio"
        "strings"
)

// Author: Wjy
func main() {
    //filename1 := "./ddd.txt"
    //data, err:= ioutil.ReadFile(filename1)
    //fmt.Println(string(data),err)
    //
    //filename2 := "./ddd.py"
    //s1 := "print 'hello world'"
    //err =ioutil.WriteFile(filename2,[]byte(s1),os.ModePerm)
    //fmt.Println(err)

    // 读取字符串
    //a := "zxcuoioiasn2l3j"
    //r := strings.NewReader(a)
    //data,err :=ioutil.ReadAll(r) // 返回byte切片
    //fmt.Println(data,err)

    // 读取目录
    //l, _:=ioutil.ReadDir("./src/")
    //for _,v := range l{
    //    fmt.Println(v.Name())
    //    if v.IsDir() == true{
    //        l1,_ := ioutil.ReadDir("./src/"+v.Name())
    //        for _, j:=range l1{
    //            fmt.Println(j.Name(), j.IsDir())
    //        }
    //    }
    //}

    // 缓存读写bufio
    //filename := "ddd.txt"
    //file,_:=os.Open(filename)
    //r1 := bufio.NewReader(file)
    //data,_,_:=r1.ReadLine()
    //fmt.Println(string(data))
    //data,_,_=r1.ReadLine()
    //fmt.Println(string(data))
    //data,_,_=r1.ReadLine()
    //fmt.Println(string(data))

    //for {
    //    s1, err := r1.ReadString(10)
    //    if err == io.EOF{
    //        break
    //    }
    //    fmt.Print(s1)
    //}
    //file.Close()

    //for {
    //data,err:=r1.ReadBytes('\n')
    //if err == io.EOF{break}
    //fmt.Print(string(data))
    //}
    //file.Close()

    //r2:=bufio.NewReader(os.Stdin)
    //s2,_:=r2.ReadString('\n')
    //fmt.Println(s2)

    // 计算两日期差
    //t1 :=time.Date(2018,2,10,13,59,0,0,time.Local)
    //t2 :=time.Now()
    //subM:=t2.Sub(t1)
    //time_hour :=int(subM.Hours())
    //fmt.Println(time_hour/24)
    //
    //fmt.Println(t2.Format("2006 15:04:05"))
    //fmt.Println(int(t2.Unix()))

    //file4,_ := os.OpenFile("标准输出.txt",os.O_RDWR|os.O_CREATE,os.ModePerm)
    //r3:=bufio.NewReader(os.Stdin)
    //s3,_:=r3.ReadString('\n')
    //r4 := bufio.NewWriter(file4)
    //s4,err := r4.WriteString(s3)
    //r4.Flush()
    //file4.Close()
    //fmt.Println(s4,err)

    file5,_:=os.OpenFile("标准输出1.txt",os.O_RDWR|os.O_CREATE,os.ModePerm)
    defer file5.Close()
    r3 := bufio.NewReader(strings.NewReader("hello world"))
    //s3,_:=r3.ReadString('\n')
    r4 := bufio.NewWriter(file5)
    r4.ReadFrom(r3)
    r4.Flush()
}
// math包
var num int
    fmt.Scanln(&num)
    fmt.Printf("%T %v",num, error)
    num := 2.1
    fmt.Println(num/int(math.Pow10(3)), num%int(math.Pow10(3))/int(math.Pow10(2)))
    fmt.Println(math.Floor(num+0.5)) // 向下取整
    fmt.Println(math.Ceil(num)) // 向上取整
    fmt.Println(math.Max(1,2)) //最大值
    fmt.Println(math.Min(1,2)) // 最小值
    fmt.Println(math.Pow(2,3)) //幂数
    fmt.Println(math.Mod(11,3)) // 求余数
    fmt.Println(math.Modf(3.18)) // 整数和精度
    fmt.Println(math.Sqrt(9)) // 开平方
// defer坑
// Author: Wjy

package main

import "fmt"

// defer是栈的数据结构

func main() {
    //a:=1
    //fmt.Println(f())
    //fmt.Println(f1())
    //fmt.Println(f2())

    //fmt.Println(f3())
    //fmt.Println(f4())
    //fmt.Println(f5())
    //var pil *int
    //a := 10
    //pil = &a
    //fmt.Println(&a,&pil)
    //f6(pil)

    //var pill **int
    //pill = &pil
    //fmt.Println(**pill)

    //map1:=make(map[int]int)
    //fmt.Printf("%p\n",map1)
    //fmt.Println(&map1)

    //arr := [2]int{1,2}
    //arr1 := &arr
    //arr1[0] = 100
    //fmt.Println(arr1)

    a:=[...]int{1,2,3}
    f7(&a)

    b := 1
    c := 2
    d := [2]*int{&b,&c}
    f8(d)
    fmt.Println(b,c)
}

func f()(result int){
    defer func() {result++}()
    return 0
}

func f1()(r int){
    t := 5
    defer func() {t = t+5}()
    return t
}

func f2()(r int){
    defer func(r int) {r=r+5}(r)
    return 1
}

func f3() (result int) {
    result = 0
    func() {result++}()
    return
}

func f4() (r int) {
    t:=5
    r=t
    func() {t=t+5}()
    return
}

func f5() (r int) {
    r = 1
    func(r int){
        r = r +5
    }(r)
    return
}

func f6(a *int)  {
    fmt.Println(*a)
}

func f7(a *[3]int)  {
    fmt.Println(*a)
}

func f8(a [2]*int)  {
    fmt.Println(a)
    *a[0] = 111
    fmt.Println(a)
}

计时器

// timer
package main

import (
    "time"
    "fmt"
)

// Author: Wjy
func main() {
    /*
    计时器
     */
     timer1:=time.NewTimer(3*time.Second)
     fmt.Printf("%T\n", timer1)
     fmt.Println(time.Now())
     time1 := <- timer1.C // 定时器 返回 <- chan Time
    fmt.Println(time1)
     timer1.Reset(2*time.Second)
     time2 := <- timer1.C
    fmt.Println(time2)
     time3 := time.After(3*time.Second)
     fmt.Println(time3)
     timer1.stop() // 停止计时器
     ok := timer1.restart(1*time.Second) // 重置计时器
     fmt.Println(ok)
     // 定时器, 定时任务
     tk := time.NewTicker(1 * time.Second)
    i := 0
    for {
        <-tk.C
        fmt.Println(1)
        i++
        if i ==5{
            break
        }
    }
     }

生成随机时间对象

rand.Seed(time.Now().UnixNano())
a:=time.Duration(rand.Intn(1000))
fmt.Println(a)

同步等待组

package main

import (
        "sync"
    "fmt"
        )
var count int = 1
// Author: Wjy
func main() {
    var s sync.WaitGroup
    s.Add(4) // 协程并发数量
    go 卖票("窗口1",&s)
    go 卖票("窗口2",&s)
    go 卖票("窗口3",&s)
    go 卖票("窗口4",&s)
    s.Wait() // 等待并发数量为0停止主程序
}

func 卖票(str string,s *sync.WaitGroup){
    for count <= 100{
        fmt.Println(str,count)
        count++
    }
    s.Done() 
}

同步等待加锁

package main

import (
    "sync"
    "fmt"
    "math/rand"
    "time"
)
var count int = 100
var Mutex sync.Mutex
var s sync.WaitGroup
// Author: Wjy
func main() {
    s.Add(4)
    go 卖票("窗口1")
    go 卖票("窗口2")
    go 卖票("窗口3")
    go 卖票("窗口4")
    s.Wait()
}

func 卖票(str string){
    rand.Seed(time.Now().UnixNano())
    for {
        Mutex.Lock()
        if count > 0{
            time.Sleep(time.Duration(rand.Intn(1000)))
            fmt.Println(str,count)
            count--
    }else {
            fmt.Println("卖完了")
            Mutex.Unlock()
            break
        }
        Mutex.Unlock()
    }
    s.Done()

}

互斥锁

package main

import (
    "sync"
    "fmt"
    "time"
)

// Author: Wjy
func main() {
    var mutes sync.Mutex

    fmt.Println("锁定")
    mutes.Lock()
    fmt.Println("已经锁定")
    for i:=1;i<=3;i++{
        go func(i int) {
            fmt.Println(i,"即将")
            mutes.Lock()
            fmt.Println(i,"已经")
        }(i)
    }
    time.Sleep(5*time.Second)
    fmt.Println("解锁")
    mutes.Unlock()
    fmt.Println("已经解锁")
    time.Sleep(3*time.Second)
}

读写锁

// 读写锁读锁可以并发,写锁只能等待解锁
package main

import (
    "sync"
    "fmt"
    "math/rand"
    "time"
)

// Author: Wjy


var n int = 100
var wg sync.WaitGroup
var rwm sync.RWMutex
func main() {
    // 创建10个协程
    wg.Add(10)

    for i:=1;i<=5;i++{
        go read(i)
    }
    for i:=1;i<=5;i++{
        go write(i)
    }
    wg.Wait()
}

func write(i int)  {
    defer wg.Done()
    rand.Seed(time.Now().UnixNano())
    rwm.Lock()
    fmt.Println("写操作",i)
    randnum := rand.Intn(100)+1
    n = randnum
    fmt.Println(i,"写入",randnum)
    rwm.Unlock()
}

func read(i int) {
    defer wg.Done()
    rwm.RLock()
    fmt.Println("读操作",i)
    v := n
    fmt.Println(i,"读取了",v)
    rwm.RUnlock()
}

Cond实现了一个条件变量

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var count int = 4
    var wg sync.WaitGroup
    wg.Add(5)

    // 新建 cond
    var mutex sync.Mutex
    cond := sync.NewCond(&mutex)

    for i := 0; i < 5; i++ {//0,1,2,3,4
        go func(i int) {//g1,g2,g3,g4,g5
            //0, 1, 2, 3, 4
            // 争抢互斥锁的锁定
            cond.L.Lock() //g1

            // 条件是否达成count:1
            for count > i {//0,1,2
                cond.Wait()//g0,g1,g2
                fmt.Printf("收到一个通知 goroutine%d\n", i)
            }

            fmt.Printf("goroutine%d 执行结束\n", i)

            cond.L.Unlock()
            wg.Done()
        }(i)
    }

    // 确保所有 goroutine 启动完成
    time.Sleep(time.Millisecond * 20)
    // 锁定一下,我要改变 count 的值
    fmt.Println("broadcast...")
    cond.L.Lock()
    count -= 1 // 3
    cond.Broadcast()
    fmt.Println("第一次:广播结束。。")
    cond.L.Unlock()

    time.Sleep(2*time.Second)
    fmt.Println("-------------------------")
    fmt.Println("signal...")
    cond.L.Lock()
    count -= 2 // 1
    cond.Signal()
    fmt.Println("第二次:单发通知结束。。")
    cond.L.Unlock()


    time.Sleep(2*time.Second)
    fmt.Println("------------------------")
    fmt.Println("broadcast...")
    cond.L.Lock()
    count -= 1 // 0
    cond.Broadcast()
    fmt.Println("第三次:广播结束。。")
    cond.L.Unlock()

    wg.Wait()
}

内置原子性数值操作和其他操作

package main

import (
    "fmt"
    "sync/atomic"
    "sync"
    "runtime"
)

// Author: Wjy
func main() {
    // 针对数值
    var n int64 = 3
    fmt.Println("n的输出")
    newn := atomic.AddInt64(&n, 1)
    fmt.Println(newn)
    // 替换
    atomic.SwapInt64(&n, 9)
    fmt.Println(n)
    // 比较交换 原值,是否是某个值,如果是交换
    atomic.CompareAndSwapInt64(&n, 9,10)
    fmt.Println(n)
    // 一次性操作, 只会执行一次
    var count int64= 0
    once :=sync.Once{}
    for i:=1;i<=10;i++{
        once.Do(func() {
            count++
        })
    }
    fmt.Println(count)
    // 临时对象池, 如果获取不到数据就从给定的函数返回值获取
    fun:= func() interface{}{
    return atomic.AddInt64(&count,1)
    }
    pool := sync.Pool{New:fun}
    // 获取数据
    fmt.Println(pool.Get())
    pool.Put(10)
    pool.Put(8)
    pool.Put(2)
    pool.Put(3)
    fmt.Println(pool.Get())
    //fmt.Println(pool.Get())
    //fmt.Println(pool.Get())
    //fmt.Println(pool.Get())

    // 执行GC()
    runtime.GC()
    pool.New = nil
    fmt.Println(pool.Get())
}

正则

package main

import (
    "regexp"
    "fmt"
)

// Author: Wjy
func main() {
    //a := "我们是程序员,我们不一样"
    //r :=regexp.MustCompile(`我.{2}`) // 规则
    //list :=r.FindAllStringSubmatch(a,-1) // 查找所有符合的返回[[我们是] [我们不]]
    //fmt.Println(list[0][0])

    //b := "3.14 567 agsa 1.23 7. 8.99 lasdg 6.66"
    //r1 ,err:= regexp.Compile(`\d+\.\d+`)
    //if err != nil{
    //    fmt.Println("缺少规则")
    //}
    //str := r1.FindAllString(b,-1)
    //fmt.Println(str)

    c := `
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <title>Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/static/img/go.ico">
    <link rel="apple-touch-icon" type="image/png" href="/static/img/logo2.png">
    <meta name="author" content="polaris <polaris@studygolang.com>">
    <meta name="keywords" content="中文, 文档, 标准库, Go语言,Golang,Go社区,Go中文社区,Golang中文社区,Go语言社区,Go语言学习,学习Go语言,Go语言学习园地,Golang 中国,Golang中国,Golang China, Go语言论坛, Go语言中文网">
    <meta name="description" content="Go语言文档中文版,Go语言中文网,中国 Golang 社区,Go语言学习园地,致力于构建完善的 Golang 中文社区,Go语言爱好者的学习家园。分享 Go 语言知识,交流使用经验">
    </head>
    <div>啊啊啊</div>
    <div>不不不</div>
    <div>产产产</div>
    <div>顶顶顶
asd
asd</div>
    <frameset cols="15,85">
    <frame src="/static/pkgdoc/i.html">
    <frame name="main" src="/static/pkgdoc/main.html" tppabs="main.html" >
    <noframes>
    </noframes>
    </frameset>
    </html>`
    re,err := regexp.Compile(`<div>(?s:(.*?))</div>`)
    if err != nil{
        fmt.Println(err)
        return
    }
    // 取分组值
    fmt.Println(re.FindAllStringSubmatch(c,-1)[1][1])
}

结构体生成json

package main

import (
        "fmt"
    "encoding/json"
)

// Author: Wjy
func main() {
    // 结构体转json
    s := IT{"itcase", []string{"Go","C++","Python","Test"},true, 666.666}
    //js,err := json.Marshal(s)
    js, err := json.MarshalIndent(s, "", " ") // 格式化
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Println(string(js))
}

type IT struct {
    Company string `json:"company"` // 二次编码成小写
    Subjects []string `json:"-"` // 此字段不会输出
    IsOk bool `json:",string"` // 以字符串形式显示
    Price float64
}

map转json

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
)

// Author: Wjy

func main() {
    m := make(map[string]interface{}, 4)
    m["company"] = "itcast"
    m["subjects"] = []string{"Go","C++","Python","Test"}
    m["isok"] = strconv.FormatBool(true)
    m["price"] = strconv.FormatFloat(666.666,'f',3,64)

    result,err := json.Marshal(m)
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Println(string(result))
}

json转结构体

package main

import (
    "encoding/json"
    "fmt"
)

// Author: Wjy
func main() {
    json1 := `{"company":"itcast","isok":"true","price":"666.666","subjects":["Go","C++","Python","Test"]}`
    var tmp IT
    err := json.Unmarshal([]byte(json1), &tmp)
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Printf("%T\n",tmp.IsOk)
    // 如果只定义json数据部分字段结构体则只会取到定义部分数据
    type IT2 struct {
        Company string `json:"company"`
    }
    var tmp2 IT2
    err = json.Unmarshal([]byte(json1), &tmp2)
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Println(tmp2)
}

type IT struct {
    Company string `json:"company"` // 二次编码成小写
    Subjects []string `json:"subjects"`
    IsOk bool `json:"isok,string"`
    Price float64 `json:"price,string"`
}

json转map

package main

import (
    "encoding/json"
    "fmt"
)

// Author: Wjy
func main() {
    map1 := make(map[string]interface{})
    json1 := `{"company":"itcast","isok":true,"price":666.666,"subjects":["Go","C++","Python","Test"]}`

    err := json.Unmarshal([]byte(json1), &map1)
    if err != nil{
        fmt.Println(err)
        return
    }
    //fmt.Println(map1)

    for k, v := range map1{
        switch data:=v.(type) {
        case bool:map1[k] = data
            map1[k] = data
        case string:
        case []interface{}:map1[k] = data
        case float64:map1[k]= data
        }
    }
    fmt.Printf("%T",map1["subjects"])
}

runtime包

package main

import (
    "fmt"
    "runtime"
)

// Author: Wjy
func main() {
    fmt.Println(runtime.NumCPU()) //逻辑cpu核数
    runtime.GOMAXPROCS(4) // 最大并发量,cpu核心数
    go func() {
        for i:=0;i<5;i++{
            runtime.Goexit() // 退出协程
            fmt.Println("go")
        }
    }()

    for i:=0;i<2;i++{
        runtime.Gosched() // 让出时间片
        fmt.Println("hello")
    }
}

tcp客户端

package main

import (
    "net"
    "fmt"
    "bufio"
    "os"
)

// Author: Wjy
func main() {
    conn, err := net.Dial("tcp",":5000")
    if err != nil{
        fmt.Println(err)
        return
    }
    defer conn.Close()
    buf := make([]byte, 1024)
    fmt.Println("输入内容")
    for {
        read := bufio.NewReader(os.Stdin)
        data, _,_ := read.ReadLine()
        conn.Write([]byte(data))
        n, err := conn.Read(buf)
        if err!= nil{
            fmt.Println(err)
            break
        }
        if string(buf[:n]) == "byby"{
            break
        }
        fmt.Println(string(buf[:n]))
    }



}

tcp服务端

package main
import (
    "net"
    "fmt"
    "strings"
)
// Author: Wjy
func main() {
    conn, err := net.Listen("tcp", ":5000")
    if err != nil{
        fmt.Println(err)
        return
    }
    defer conn.Close()
    fmt.Println("服务已启动")
    for {
        data, err := conn.Accept()
        if err != nil{
            fmt.Println(err)
            return
        }
        go HandleConn(data)
    }
}

func HandleConn(data net.Conn)  {
    addr := data.RemoteAddr().String()
    defer data.Close()
    buf := make([]byte, 2048)
    for {
        n, err := data.Read(buf)
        if err != nil{
            fmt.Println(err)
            return
        }
        if string(buf[:n]) == "quite"{
            data.Write([]byte("byby"))
            return
        }
        fmt.Println(addr, ":", string(buf[:n]))
        data.Write([]byte(strings.ToUpper(string(buf[:n]))))
    }
}

tcp服务端

package main
import (
    "net"
    "fmt"
    "time"
)
// Author: Wjy
func main() {
    client,err := net.Listen("tcp",":8001") // 服务端监听端口
    if err != nil{
        fmt.Println("服务器异常:", err)
        return
    }
    fmt.Println("服务已启动")
    for {
        conn, err := client.Accept()
        fmt.Println("客户已连接:",conn.RemoteAddr().String())
        if err != nil{
            fmt.Println("连接异常:",err)
            continue
        }
        go 处理(conn)
    }
}
func 处理(conn net.Conn)  {
    buf := make([]byte, 2048)
    for {
        n, err := conn.Read(buf)
        if err != nil{
            fmt.Println("客户离开:",err)
            return
        }
        go 发送(string(buf[:n]), conn)
    }
    defer conn.Close()
}

func 发送(s string, conn net.Conn)  {
    conn.Write([]byte(time.Now().String() +":"+ s))
}

tcp客户端

package main

import (
    "net"
    "fmt"
        "bufio"
    "os"
    "io"
)
// Author: Wjy
func main() {
    conn, err := net.Dial("tcp",":8001") // 客户端接口
    if err != nil{
        fmt.Println(err)
        return
    }
    defer conn.Close()

    buf := make([]byte, 2048)
    go func() {
        for {
            n, err := conn.Read(buf)
            if n == 0 || err == io.EOF{
                fmt.Println(err)
                return
            }
            fmt.Println(string(buf[:n]))
        }
    }()

    for {
        reader := bufio.NewReader(os.Stdin)
        data, err:= reader.ReadString('\n')
        if err != nil{
            fmt.Println("read:", err)
            return
        }
        if data[:len(data)-1] == "quite"{
            break
        }
        conn.Write([]byte(data[:len(data)-1]))
    }
}

聊天服务器需要nc工具

package main
import (
    "fmt"
    "net"
    "strings"
    "time"
    "syscall"
)

// 用户结构体
type Client struct {
    C    chan string //用户发送数据的管道
    Name string      // 用户名
    Addr string      // 网络地址
}

// 保存在线用户
var onlineMap map[string]Client

// 通讯的管道
var message = make(chan string)

func main() {
    //1. 监听
    listener, err := net.Listen("tcp", "127.0.0.1:5000")

    if err != nil {
        fmt.Println("net.Listen:", err)
        return
    }
    defer listener.Close()

    // 新开一个协成,转发消息,只要有消息来了就遍历map发消息
    go Manager()

    //2. 主协成,阻塞等待用户
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("listener.Accept:", err)
            continue //记住
        }
        // 处理用户连接
        go HandleConn(conn)
    }
}
//处理用户连接
func HandleConn(conn net.Conn) {
    defer conn.Close()
    // 获取用户地址
    cliAddr := conn.RemoteAddr().String()
    // 创建一个结构体
    cli := Client{make(chan string), cliAddr, cliAddr}

    // 结构体添加到map
    onlineMap[cliAddr] = cli

    // 新开一个协成,专门给当前客户端发送信息
    go WriteMsgToClient(cli, conn)

    // 广播某个用户在线
    message <- MakeStr(cli, "login")

    // 提示我是谁
    cli.C <- MakeStr(cli, "I here")

    //定义一个管道判断是否主动退出
    isQuit := make(chan bool)
    //定义一个通道判断是否超时
    isTimeOut := make(chan bool)

    // 新开一个协成,接受用户发送过来的数据
    go func() {
        buf := make([]byte, 1024*2)
        for {
            n, err := conn.Read(buf)
            if n == 0 { // 对方断开
                isQuit <- true
                fmt.Println("conn.Read:", err)
                return
            }
            // 获取用户信息
            userMsg := string(buf[:n-1]) // windows nc测试多个换行

            // 处理字符
            if len(userMsg) == 3 && userMsg == "who" {
                //遍历map给当前用户发送所有成员
                conn.Write([]byte("user list:\n"))
                for _, tmp := range onlineMap {
                    userMsg = tmp.Addr + ":" + tmp.Name + "\n"
                    conn.Write([]byte(userMsg))
                }
            } else if len(userMsg) >= 8 && userMsg[:6] == "rename" {
                // rename|yoyo
                name := strings.Split(userMsg, "|")[1] //窃取后面一个
                //重新复制
                cli.Name = name
                onlineMap[cliAddr] = cli

                //广播给自己
                cli.C <- MakeStr(cli, "rename successfully")

            } else {
                // 转发此内容
                message <- MakeStr(cli, userMsg)
            }

            // 进来这里就有数据不超时
            isTimeOut <- true
        }
    }()
    for {
        // 通过select检车channel流动
        select {
        case <-isQuit:
            delete(onlineMap, cliAddr)        //移除当前用户
            message <- MakeStr(cli, "logout") //广播当前用户下线

            return
        case <-isTimeOut:

        case <-time.After(10 * time.Second):
            delete(onlineMap, cliAddr)
            message <- MakeStr(cli, "timeout")
            return
        }
    }

}
func Manager() {
    // 给map分配空间
    onlineMap = make(map[string]Client)

    for {
        msg := <-message // 没有消息就阻塞

        // 遍历map给每个成员发消息
        for _, cli := range onlineMap {
            cli.C <- msg // 消息给管道!
        }
    }
}
// 给当前发送消息
func WriteMsgToClient(cli Client, conn net.Conn) {
    // 给当前客服端发送信息
    for msg := range cli.C {
        conn.Write([]byte(msg + "\n"))
    }
}
// 制作格式字符串
func MakeStr(cli Client, str string) string {
    return "[" + cli.Addr + "]" + cli.Name + ":" + str
}

http包

package main

import (
    "net/http"
    "fmt"
)

// Author: Wjy
func main() {
    // 注册处理函数,用户连接,自动调用指定处理函数
    http.HandleFunc("/", HandConn)
    // 监听绑定
    http.ListenAndServe("0.0.0.0:8002", nil)
}

func HandConn(w http.ResponseWriter, req *http.Request)  {
    // w 给客户端回复数据, req 读取客户端发送的数据
    fmt.Println(req.Method)  // 请求方法
    fmt.Println(req.Header) // 请求头
    fmt.Println(req.URL) // 请求资源路径
    fmt.Println(req.Body) // 请求体

    w.Write([]byte("hello go"))

    if req.Method == "POST"{
        buf := make([]byte, 2048)
        n, _:=req.Body.Read(buf)
        fmt.Println(string(buf[:n]))
    }
}

http客户端

package main

import (
    "net/http"
    "fmt"
)

// Author: Wjy
func main() {
    response, err := http.Get("http://www.baidu.com")
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Println(response.Status) // 例如"200 OK"
    fmt.Println(response.StatusCode) // 例如200
    fmt.Println(response.Proto) // 例如"HTTP/1.0"
    fmt.Println(response.ProtoMajor ) // 例如1
    fmt.Println(response.ProtoMinor) // 例如0
    fmt.Println(response.Header)
    fmt.Println(response.ContentLength )
    buf := make([]byte, 1024)
    var s string
    for {
        n,_:=response.Body.Read(buf)
        if n == 0 {
            break
        }
        s+=string(buf[:n])
    }
    fmt.Println(s)
}

百度爬虫

package main

import (
    "fmt"
    "net/http"
    "regexp"
    "sync"

    "strings"
    "os"
    "io"
    "time"
)


// Author: Wjy
func main() {
    // https://tieba.baidu.com/f?kw=%E5%9C%B0%E4%B8%8B%E5%9F%8E%E4%B8%8E%E5%8B%87%E5%A3%AB&ie=utf-8&pn=50
    var mu sync.WaitGroup
    var start, end int

    fmt.Printf("输入其实页(>=1):")
    fmt.Scan(&start)
    fmt.Printf("输入终止页(>=起始页):")
    fmt.Scan(&end)
    start_time := time.Now().Unix()
    DoWork(start, end, &mu) // 开始任务
    fmt.Println("用时:",time.Now().Unix()-start_time)

}

func DoWork(start, end int, mu *sync.WaitGroup)  {
    fmt.Printf("正在爬取 %d 到 %d 的页面\n", start,end)
    // 列表页url
    base_url := `https://tieba.baidu.com/f?kw=地下城与勇士&ie=utf-8&pn=%d`
    for i:=start;i<=end;i++{
        client := &http.Client{}
        fmt.Println("第",i,"页")
        mu.Add(1)
        // 构建url
        url := fmt.Sprintf(base_url,(i-1)*50)
        // 构建get请求头
        resp, _ := http.NewRequest("GET",url,nil)
        resp.Header.Add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36")
        // 执行请求
        response, err := client.Do(resp)
        if err != nil{
            fmt.Println("1",err)
            continue
        }
        path := fmt.Sprintf("./dnf贴吧%d",i)
        _, err = os.Stat(path)
        if os.IsNotExist(err) {
            os.Mkdir(path,os.ModePerm)
        }
        go HttpResponse(response, path, mu) // 解析列表页响应
    }
    mu.Wait() // 等待文件写入
}

func HttpResponse(response *http.Response, path string, mu *sync.WaitGroup)  {
    buf := make([]byte, 2048)
    var body string
    for {
        n, err := response.Body.Read(buf)
        if err == io.EOF || n == 0{
            break
        }
        body += string(buf[:n])
    }
    response.Body.Close()
    re, err := regexp.Compile(`href="(.*?)"`)
    urls := re.FindAllStringSubmatch(body, -1)
    if err != nil{
        fmt.Println("3",err)
    }
    //fmt.Println(urls)
    count := 1
    for _, v := range urls{
        if strings.HasPrefix(v[1], "/p"){
            mu.Add(1)
            fmt.Println("正在下载第", count, "页")
            go content(&v[1], path, mu)
            fmt.Println("完成",count)
            count++
        }
    }
    mu.Done()
}

func content(s *string,path string, mu *sync.WaitGroup)  {
    resp, err := http.Get("https://tieba.baidu.com"+*s)
    if err != nil{
        fmt.Println("下载时出错", err)
        return
    }
    buf := make([]byte, 2048)
    var str string
    for {
        n, err := resp.Body.Read(buf)
        if err == io.EOF || n == 0{
            break
        }
        str += string(buf[:n])
    }
    resp.Body.Close()
    re, err := regexp.Compile(`<title>(.*?)</title>`)
    if err != nil{
        fmt.Println("标题未匹配:",err)
        return
    }
    title := re.FindStringSubmatch(str)
    f, _ := os.OpenFile(path+"/"+title[1]+".html",os.O_CREATE|os.O_WRONLY,os.ModePerm)
    f.Write([]byte(str))
    f.Close()
    mu.Done()
}

反射详解 https://studygolang.com/articles/12348?fr=sidebar

interface 和 反射 在讲反射之前,先来看看Golang关于类型设计的一些原则

  • 变量包括(type, value)两部分

    • 理解这一点就知道为什么nil != nil了
  • type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型

  • 类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.

接下来要讲的反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。

在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:

(value, type)

value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。

例如,创建类型为*os.File的变量,然后将其赋给一个接口变量r:

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

var r io.Reader
r = tty

接口变量r的pair中将记录如下信息:(tty, *os.File),这个pair在接口变量的连续赋值过程中是不变的,将接口变量r赋给另一个接口变量w:

var w io.Writer
w = r.(io.Writer)

接口变量w的pair与r的pair相同,都是:(tty, *os.File),即使w是空接口类型,pair也是不变的。

interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。

Golang的反射reflect

reflect的基本功能TypeOf和ValueOf

既然反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。那么在Golang的reflect反射包中有什么样的方式可以让我们直接获取到变量内部的信息呢? 它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf() 和 reflect.TypeOf(),看看官方的解释

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
func ValueOf(i interface{}) Value {...}

翻译一下:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0


// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}

翻译一下:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil

reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value,示例如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var num float64 = 1.2345

    fmt.Println("type: ", reflect.TypeOf(num))
    fmt.Println("value: ", reflect.ValueOf(num))
}

运行结果:
type:  float64
value:  1.2345

说明

  1. reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型

  2. reflect.ValueOf:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值

  3. 也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是reflect.Type和reflect.Value这两种

从relfect.Value中获取接口interface的信息

当执行reflect.ValueOf(interface)之后,就得到了一个类型为”relfect.Value”变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,我们可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明。

已知原有类型【进行“强制转换”】

已知类型后转换为其对应的类型的做法如下,直接通过Interface方法然后强制转换,如下:

realValue := value.Interface().(已知的类型)

示例如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var num float64 = 1.2345

    pointer := reflect.ValueOf(&num)
    value := reflect.ValueOf(num)

    // 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
    // Golang 对类型要求非常严格,类型一定要完全符合
    // 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
    convertPointer := pointer.Interface().(*float64)
    convertValue := value.Interface().(float64)

    fmt.Println(convertPointer)
    fmt.Println(convertValue)
}

运行结果:
0xc42000e238
1.2345

说明

  1. 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
  2. 转换的时候,要区分是指针还是指
  3. 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
    未知原有类型【遍历探测其Filed】
    很多情况下,我们可能并不知道其具体类型,那么这个时候,该如何做呢?需要我们进行遍历探测其Filed来得知,示例如下:
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (u User) ReflectCallFunc() {
    fmt.Println("Allen.Wu ReflectCallFunc")
}

func main() {

    user := User{1, "Allen.Wu", 25}

    DoFiledAndMethod(user)

}

// 通过接口来获取任意参数,然后一一揭晓
func DoFiledAndMethod(input interface{}) {

    getType := reflect.TypeOf(input)
    fmt.Println("get Type is :", getType.Name())

    getValue := reflect.ValueOf(input)
    fmt.Println("get all Fields is:", getValue)

    // 获取方法字段
    // 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
    // 2. 再通过reflect.Type的Field获取其Field
    // 3. 最后通过Field的Interface()得到对应的value
    for i := 0; i < getType.NumField(); i++ {
        field := getType.Field(i)
        value := getValue.Field(i).Interface()
        fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
    }

    // 获取方法
    // 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
    for i := 0; i < getType.NumMethod(); i++ {
        m := getType.Method(i)
        fmt.Printf("%s: %v\n", m.Name, m.Type)
    }
}

运行结果:
get Type is : User
get all Fields is: {1 Allen.Wu 25}
Id: int = 1
Name: string = Allen.Wu
Age: int = 25
ReflectCallFunc: func(main.User)

说明

通过运行结果可以得知获取未知类型的interface的具体变量及其类型的步骤为:

  1. 先获取interface的reflect.Type,然后通过NumField进行遍历
  2. 再通过reflect.Type的Field获取其Field
  3. 最后通过Field的Interface()得到对应的value

通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为:

  1. 先获取interface的reflect.Type,然后通过NumMethod进行遍历
  2. 再分别通过reflect.Type的Method获取对应的真实的方法(函数)
  3. 最后对结果取其Name和Type得知具体的方法名
  4. 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
  5. struct 或者 struct 的嵌套都是一样的判断处理方式

    通过reflect.Value设置实际变量的值

    reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。

示例如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    var num float64 = 1.2345
    fmt.Println("old value of pointer:", num)

    // 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
    pointer := reflect.ValueOf(&num)
    newValue := pointer.Elem()

    fmt.Println("type of pointer:", newValue.Type())
    fmt.Println("settability of pointer:", newValue.CanSet())

    // 重新赋值
    newValue.SetFloat(77)
    fmt.Println("new value of pointer:", num)

    ////////////////////
    // 如果reflect.ValueOf的参数不是指针,会如何?
    pointer = reflect.ValueOf(num)
    //newValue = pointer.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”
}

运行结果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value of pointer: 77

说明

  1. 需要传入的参数是* float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
  2. 如果传入的参数不是指针,而是变量,那么
    • 通过Elem获取原始值对应的对象则直接panic
    • 通过CanSet方法查询是否可以设置返回false
  3. newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
  4. reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
  5. 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】
  6. struct 或者 struct 的嵌套都是一样的判断处理方式
通过reflect.ValueOf来进行方法的调用

这算是一个高级用法了,前面我们只说到对类型、变量的几种反射的用法,包括如何获取其值、其类型、如果重新设置新值。但是在工程应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法【函数】的调用。比如我们要做框架工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么我们通过什么手段来扩展让用户能够自定义呢?关键点在于用户的自定义方法是未可知的,因此我们可以通过reflect来搞定

示例如下:

// 反射取值
val := v.Field(i).Interface()
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (u User) ReflectCallFuncHasArgs(name string, age int) {
    fmt.Println("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal User.Name:", u.Name)
}

func (u User) ReflectCallFuncNoArgs() {
    fmt.Println("ReflectCallFuncNoArgs")
}

// 如何通过反射来进行方法的调用?
// 本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调动mv.Call

func main() {
    user := User{1, "Allen.Wu", 25}

    // 1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
    getValue := reflect.ValueOf(user)


    // 一定要指定参数为正确的方法名
    // 2. 先看看带有参数的调用方法
    methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
    args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
    methodValue.Call(args)

    // 一定要指定参数为正确的方法名
    // 3. 再看看无参数的调用方法
    methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
    args = make([]reflect.Value, 0)
    methodValue.Call(args)
}


运行结果:
ReflectCallFuncHasArgs name:  wudebao , age: 30 and origal User.Name: Allen.Wu
ReflectCallFuncNoArgs

说明

  1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理

  2. reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。

  3. []reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。

  4. reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,如果reflect.Value'Kind不是一个方法,那么将直接panic。

  5. 本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调用methodValue.Call

Golang的反射reflect性能

Golang的反射很慢,这个和它的API设计有关。在 java 里面,我们一般使用反射都是这样来弄的。

Field field = clazz.getField("hello");
field.get(obj1);
field.get(obj2);

这个取得的反射对象类型是 java.lang.reflect.Field。它是可以复用的。只要传入不同的obj,就可以取得这个obj上对应的 field。

但是Golang的反射不是这样设计的:

type_ := reflect.TypeOf(obj)
field, _ := type_.FieldByName("hello")

这里取出来的 field 对象是 reflect.StructField 类型,但是它没有办法用来取得对应对象上的值。如果要取值,得用另外一套对object,而不是type的反射

type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")

这里取出来的 fieldValue 类型是 reflect.Value,它是一个具体的值,而不是一个可复用的反射对象了,每次反射都需要malloc这个reflect.Value结构体,并且还涉及到GC。

小结

Golang reflect慢主要有两个原因

  1. 涉及到内存分配以及后续的GC;

  2. reflect实现里面有大量的枚举,也就是for循环,比如类型之类的。

总结

上述详细说明了Golang的反射reflect的各种功能和用法,都附带有相应的示例,相信能够在工程应用中进行相应实践,总结一下就是:

  • 反射可以大大提高程序的灵活性,使得interface{}有更大的发挥余地

    • 反射必须结合interface才玩得转
    • 变量的type要是concrete type的(也就是interface变量)才有反射一说
  • 反射可以将“接口类型变量”转换为“反射类型对象”

    • 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
  • 反射可以将“反射类型对象”转换为“接口类型变量

    • reflect.value.Interface().(已知的类型)
    • 遍历reflect.Type的Field获取其Field
  • 反射可以修改反射类型对象,但是其值必须是“addressable”

    • 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 pointer-interface
  • 通过反射可以“动态”调用方法

  • 因为Golang本身不支持模板,因此在以往需要使用模板的场景下往往就需要使用反射(reflect)来实现

// 反射
func main() {
    //db, _ := sql.Open("goracle", "e_tyxb/e_tyxb@172.16.50.67:1521/orcl")
    //db.SetMaxOpenConns(10)
    //db.SetMaxIdleConns(1)
    //defer db.Close()
    //res, _ := db.Exec("select * from DEAL_ORDER_CUST_TYTT")
    //fmt.Println(res)
    u := User{1, "ok", 2}
    //d := Manager{u, "no"}
    Info(&u)
}

type User struct {
    Id int
    Name string
    Age int
}

type Manager struct {
    User
    Title string
}

func (u User) Hello()  {
    fmt.Println("hello")
}

func Info(o interface{})  {
    //t := reflect.TypeOf(o)
    //fmt.Println("Type:", t.Name())

    //if k:=t.Kind();k!=reflect.Struct{
    //    fmt.Println("xx")
    //}
    //
    //v := reflect.ValueOf(o)
    //fmt.Println("Fields:")
    //
    //for i:=0;i<t.NumField();i++{
    //    //f := t.Field(i)
    //    val := v.Field(i).Interface()
    //    fmt.Println(val)
    //}

    //t := reflect.TypeOf(o)
    //fmt.Println(t.FieldByIndex([]int{0, 1}))

    v := reflect.ValueOf(o)
    if v.Kind()==reflect.Ptr &&! v.Elem().CanSet(){
        return
    }
    v = v.Elem()
    //f := v.FieldByName("Title")
    if f:=v.FieldByName("Name");f.Kind()==reflect.String{

        f.SetString("byby")
    }
    fmt.Println(v)
}

Log的使用

const (
    dirpath = `E:\goland\src\测试目录`
)
var (
    log_ *log.Logger
    errorfile = path.Join(dirpath, `\error.log.` + time.Now().Format("2006_01_02"))
    infofile = path.Join(dirpath, `\info.log.` + time.Now().Format("2006_01_02"))
    debugfile = path.Join(dirpath, `\debug.log.` + time.Now().Format("2006_01_02"))
    warnfile = path.Join(dirpath, `\warn.log.` + time.Now().Format("2006_01_02"))

    Debug = log.New(filepath(debugfile),"Debug: ",log.Ldate|log.Ltime|log.Lshortfile)
    Error = log.New(filepath(errorfile),"Error: ",log.Ldate|log.Ltime|log.Lshortfile)
    Info = log.New(filepath(infofile),"Info: ",log.Ldate|log.Ltime|log.Lshortfile)
    Warn = log.New(filepath(warnfile),"Warn: ",log.Ldate|log.Ltime|log.Lshortfile)
)
// Author: Wjy

func init() {
    _, err:=os.Stat(dirpath)
    if os.IsNotExist(err) {
        os.MkdirAll(dirpath,os.ModePerm)
    }
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Golang学习系列第一天:安装golang
0. ssh连接linux(我用的centos7),略1. golang下载由于Golang官网https://golang.org/(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgola
Stella981 Stella981
3年前
Mac下配置Golang环境
1.go的官网:https://golang.org/ 下载地址:https://golang.org/dl/ ps:因为windows的影响我用的apk版本的安装比较简单,后面也会介绍环境变量的配置同样是apk安装的前提.!(https://oscimg.oschina.net/oscnet/a87e53ebcaa5b1f5e34a
Wesley13 Wesley13
3年前
Go初识
Go初识下载安装包:https://golang.org/dl/(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgolang.org%2Fdl%2F)什么是Go语言Go语言也称为Golang,是由Google公司开发的一种静态强类型、编译型、
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
隔壁老王
隔壁老王
Lv1
千万程序员队伍中的一员。我住隔壁我姓王,同事们亲切得称呼我隔壁老王
文章
20
粉丝
2
获赞
7