你应该$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
说明
reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型
reflect.ValueOf:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值
也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是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
说明
- 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
- 转换的时候,要区分是指针还是指
- 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
未知原有类型【遍历探测其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的具体变量及其类型的步骤为:
- 先获取interface的reflect.Type,然后通过NumField进行遍历
- 再通过reflect.Type的Field获取其Field
- 最后通过Field的Interface()得到对应的value
通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为:
- 先获取interface的reflect.Type,然后通过NumMethod进行遍历
- 再分别通过reflect.Type的Method获取对应的真实的方法(函数)
- 最后对结果取其Name和Type得知具体的方法名
- 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
- 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
说明
- 需要传入的参数是* float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
- 如果传入的参数不是指针,而是变量,那么
- 通过Elem获取原始值对应的对象则直接panic
- 通过CanSet方法查询是否可以设置返回false
- newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
- reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
- 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】
- 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
说明
要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。
[]reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。
reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,如果reflect.Value'Kind不是一个方法,那么将直接panic。
本来可以用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慢主要有两个原因
涉及到内存分配以及后续的GC;
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)
}
}