入门基础
基本数据类型
文章目录
Go 语言按类别有以下几种数据类型:
-
布尔型:
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true
。 -
数字类型:
整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。 -
字符串类型:
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。 -
派生类型:
包括:(a) 指针类型(Pointer)
(b) 数组类型
© 结构类型(struct)
(d) Channel 类型
(e) 函数类型
(f) 切片类型
(g) 接口类型(interface)
(h) Map 类型
数字类型:
ioto : 初始值0 每次使用+1
Go 也有基于架构的类型,例如:int、uint 和 uintptr,这些类型的长度都是根据运行程序所在的操作系统类型所决定的。
类型 | 符号 | 长度范围 |
---|---|---|
uint8 | 无符号 | 8位整型 (0 到 255) |
uint16 | 无符号 | 16位整型 (0 到 65535) |
uint32 | 无符号 | 32位整型 (0 到 4294967295) |
uint64 | 无符号 | 64位整型 (0 到 18446744073709551615) |
int8 | 有符号 | 8位整型 (-128 到 127) |
int16 | 有符号 | 16位整型 (-32768 到 32767) |
int32 | 有符号 | 32位整型 (-2147483648 到 2147483647) |
int64 | 有符号 | 64位整型 (-9223372036854775808 到 9223372036854775807) |
浮点型:
主要是为了表示小数,也可细分为float32和float64两种。浮点数能够表示的范围可以从很小到很巨大,这个极限值范围可以在math包中获取,math.MaxFloat32表示float32的最大值,大约是3.4e38,math.MaxFloat64大约是1.8e308,两个类型最小的非负值大约是1.4e-45和4.9e-324。
float32大约可以提供小数点后6位的精度,作为对比,float64可以提供小数点后15位的精度。通常情况应该优先选择float64,因此float32的精确度较低,在累积计算时误差扩散很快,而且float32能精确表达的最小正整数并不大,因为浮点数和整数的底层解释方式完全不同。
类型 | 长度 |
---|---|
float32 | IEEE-754 32位浮点型数 |
float64 | IEEE-754 64位浮点型数 |
其他数字类型:
类型 | 长度 |
---|---|
byte | 类似 uint8 |
rune | 类似 int32 |
uint32 | 或 64 位 |
int | 与 uint 一样大小 |
uintptr | 无符号整型,用于存放一个指针 |
字符串:
只读的Unicode字节序列,Go语言使用UTF-8格式编码Unicode字符,每个字符对应一个rune类型。一旦字符串变量赋值之后,内部的字符就不能修改,英文是一个字节,中文是三个字节。
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)
而一个range循环会在每次迭代时,解码一个UTF-8编码的符文。每次循环时,循环的索引是当前文字的起始位置,以字节为单位,代码点是它的值(rune)。
使用range迭代字符串时,需要注意的是range迭代的是Unicode而不是字节。返回的两个值,第一个是被迭代的字符的UTF-8编码的第一个字节在字符串中的索引,第二个值的为对应的字符且类型为rune(实际就是表示unicode值的整形数据)。
const s = "Go语言"
for i, r := range s {
fmt.Printf("%#U : %d\n", r, i)
}
程序输出:
U+0047 ‘G’ : 0
U+006F ‘o’ : 1
U+8BED ‘语’ : 2
U+8A00 ‘言’ : 5
复数:
复数类型相对用的很少,主要是数学学科专业会用上。分为两种类型 complex64和complex128 前部分是实体后部分是虚体。
类型 | 长度 |
---|---|
complex64 | 32位实数和虚数 |
complex128 | 64位实数和虚数 |
Unicode(UTF-8)
你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘
(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)
不过 Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune 也是 Go 当中的一个类型,并且是 int32 的别名。
在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U。
因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则总是紧跟着长度为 4 的 16 进制数,前缀 \U 紧跟着长度为 8 的 16 进制数。
var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
复数
Go 拥有以下复数类型:
complex64 (32 位实数和虚数)
complex128 (64 位实数和虚数)
复数使用 re+imi 来表示,其中 re 代表实数部分,im 代表虚数部分,i 为虚数单位。
示例:
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)// 输出: 5 + 10i
如果 re 和 im 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:
c = complex(re, im)
函数 real© 和 imag© 可以分别获得相应的实数和虚数部分。
变量
标识符 identifiers、 关键字 keywords 、 运算符 operators 、标点符号 punctuation 、 字面量 literals
//完整语法 var varName dataType [ = value] var a int = 1 -> a:= 1
**变量名:**由字母、数字(首个字符不能为数字)、下划线( _ 被认为是字母 )
标识符 = 直面量 { 直面量 | 字符/数字} .
_: 是一个只写变量 ,获取不到值
_,a = 5,6 其中5是抛弃值
另外,在Go语言中,如果引入的包未使用,也不能通过编译。有时我们需要引入的包,比如需要init(),或者调试代码时我们可能去掉了某些包的功能使用,你可以添加一个下划线标记符,_,来作为这个包的名字,从而避免编译失败。下滑线标记符用于引入,但不使用。
package main import ( _ "fmt" "log" "time" ) var _=log.println func main(){ _=time.Now }
关键字
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 |
const 标识符(类型) = value const Pi = 3.14159
//基本类型 bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr
可见性
标识符:以大写字母开头时,才可以被外部包代码所有
导出
Lf int //外部 可见 lf int //外部 不可见
包名必须要小写
注释
// 行注释
/*段注释*/
// Axxx 函数注释
func Axxx(){}
// Deprecated: Old 老旧方法,不建议使用
func Old(){}
包
package lf
import "weixiao" //加载 weixiao 包
import (
"fmt"
"weixiao1"
"weixiao2"
)//多导入
fmt.Println("调用包中函数")
import( ."fmt") //调用方法省略包名
Println("简写") // 等于 fmt.Println("调用包中函数")
import( f "fmt") //别名
f.Println("别名调用")
import(
_"fmt"
_"github.com/go-sql-driver/mysql"
) // _引入包,但不直接使用包里的函数,调用 init函数
标准库
unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
syscall-os-os/exec:
os: 提供给我们一个平台无关性的操作系统功能接口,采用类UNIX设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
os/exec: 提供我们运行外部操作系统命令和程序的方式。
syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
archive/tar 和 /zip-compress:压缩(解压缩)文件功能。
fmt-io-bufio-path/filepath-flag:
fmt: 提供了格式化输入输出功能。
io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
bufio: 缓冲输入输出功能的封装。
path/filepath: 用来操作在当前系统中的目标文件名路径。
flag: 对命令行参数的操作。
strings-strconv-unicode-regexp-bytes:
strings: 提供对字符串的操作。
strconv: 提供将字符串转换为基础类型的功能。
unicode: 为 unicode 型的字符串提供特殊的功能。
regexp: 正则表达式功能。
bytes: 提供对字符型分片的操作。
math-math/cmath-math/big-math/rand-sort:
math: 基本的数学函数。
math/cmath: 对复数的操作。
math/rand: 伪随机数生成。
sort: 为数组排序和自定义集合。
math/big: 大数的实现和计算。
container-/list-ring-heap: 实现对集合的操作。
list: 双链表。
ring: 环形链表。
time-log:
time: 日期和时间的基本操作。
log: 记录程序运行时产生的日志。
encoding/Json-encoding/xml-text/template:
encoding/Json: 读取并解码和写入并编码 Json 数据。
encoding/xml:简单的 XML1.0 解析器。
text/template:生成像 HTML 一样的数据与文本混合的数据驱动模板。
net-net/http-html:
net: 网络数据的基本操作。
http: 提供了一个可扩展的 HTTP 服务器和客户端,解析 HTTP 请求和回复。
html: HTML5 解析器。
runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。
运算符
// 算数、逻辑、位运算和 java一样
&a; //返回变量实际地址
*a; //是一个指针变量
字符串
标准库 中主要有4个包
bytes、strings、strconv、unicode
strings:
提供许多字符串查询,替换,比较,截断,拆分和合并功能
bytes:
包提供类似功能,针对式 字符串有相同结构的()byte类型。
strconv:
提供了布尔型、整数型、浮点数和对应字符串转换,还提供了双引号转译转换
unicode:
提供了idDigit、isLetter、IsUpper、IsLower类似功能,给字符串分类
str := "lf \n weixiao"
lf
weixiao
str := `lf \n weixiao` //不会被转译
lf \nweixiao
Go语言中的String 类型是一种值类型,存储的字符串时不可变,如果要修改string内容需要将string转换为 ()byte 或()rune ,并且修改后string内容时重新分配的
// byte 和rune 的区别 type byte = unit8 type rune = int32
len(str) //获取字符串长度 !!! 注意 内置len 获取的是 编码长度,而不是中文字个数
str(0) //获取第一个字节
str(len(str)-1)//获取最后一个字节
//go 语言隐式解码中文
package main
import (
"fmt"
)
func main() {
s := "Go语言四十二章经"
for k, v := range s {
fmt.Printf("k:%d,v:%c == %d\n", k, v, v)
}
}
程序输出:
k:0,v:G == 71
k:1,v:o == 111
k:2,v:语 == 35821
k:5,v:言 == 35328
k:8,v:四 == 22235
k:11,v:十 == 21313
k:14,v:二 == 20108
k:17,v:章 == 31456
k:20,v:经 == 32463
数组
var arrAge = [3]int{1,2,3}
var arrNmae = [3]string{2: "索引2的位置初始化", 1: "索引1的位置初始化"}
var arrPack = [...]int{1, 10: 20} // 数组长度根据 现有元素下标决定
var arr = new([5]int) //new 数组 这是*[5]int 是指针(引用)
遍历数组
遍历数组可以使用for 条件循环,页可以使用 for-range。这两种fro结构对与切片(slilces)来说也同样适用
var arrage = [2]int{1,2,3}
for i,v:=range arrAge{
fmt.Printf("%d 年龄 :%d/n" ,i,v)
}
0年龄: 1
1年龄: 2
2年龄: 3
切片
切片(slice)是对底层数组一个连续片段的引用,所以切片是一个引用类型,切片提供对应该数组中编号的元素序列的访问,未初始化切片的值nil
切片长度获取 len() ,切片长度是可变的元素通过 0 - len()-1 来获取
切片容量计算函数 cap(),切片长度用户不会超过他的容量,此等式永远成立: 0<=len(s)<=cap(s)
一旦初始化,切片始终与保存其元素的基础数组相关联。因此,切片会和其他拥有同一基础数组的其他切片共享存储:相比之下,不同的数组总是代表不同的存储
切片下面的数组可以延伸超过切片末端。容量是切片长度之外的数组长度的总和
使用内置函数make()可以给切片初始化,该函数指定切片类型和指定长度和可选容量的参数
var identifier ()type (不需要说明长度) //切片在未初始化之前默认未nil,长度为0
//lf 切片 是由数组 arr 从 start 索引 到end-1 索引之间的元素构成的子集 切分数组 start:end 被称为切片表达式
var lf []type = arr[start:end]
var x = []int{1,2,3,4,5} //创建长度为4的切片
//当相关数组还没有定义时,可以使用make()函数来创建一个切片,同时创建号相关数组
var lf []type = make([]type,len,cap) //len数组长度,cap容量是可选参数
lf := make ([]int ,10,50) //简写
//分配一个有50个int值的数组, 创建一个长度为10,容量为50的切片 lf,该切片指向数组的前10个元素
//从数组中生成一个新的切片
a := [5]{1,2,3,4,5}
t := a[1:3:5] // a[low : high : max]
// t 容量是 5-1 ,长度是 是 3-1
package main
import "fmt"
func main() {
sli := make([]int, 5, 10)
fmt.Printf("切片sli长度和容量:%d, %d\n", len(sli), cap(sli))
fmt.Println(sli)
newsli := sli[:cap(sli)]
fmt.Println(newsli)
var x = []int{2, 3, 5, 7, 11}
fmt.Printf("切片x长度和容量:%d, %d\n", len(x), cap(x))
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5] // a[low : high : max] max-low的结果表示容量 high-low为长度
fmt.Printf("切片t长度和容量:%d, %d\n", len(t), cap(t))
// fmt.Println(t[2]) // panic ,索引不能超过切片的长度
}
程序输出:
切片sli长度和容量:5, 10
[0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
切片x长度和容量:5, 5
切片t长度和容量:2, 4
切片重组 reslice
slicel := make([]type start_length,capacity)
改变切片长度得到新的切片,
slicel = slicel(0:end)
end是末尾索引当我们在一个切片基础上重新划分一个切片时,新的切片会继续引用原有切片的数组。如果你忘了这个行为的话,在你应用分配大量临时切片用于创建新的切片来引用原来数据的一小部分时,会导致那一预期的内存使用。
临时切片一直被引用时会消耗内存
使用 cpoy() 来拷贝数组
raw := make([]byte , 10000)
res := make([]byte ,3)
copy(res ,raw[:3]) //copy raw的前3个 到res
return res. //函数结束时 raw 内存被释放
s := []int{1,1}
s1 := append(s,2)
// 在原切片后面加数据 如果数组容量不够,则会生成新的切片
s1 = {1,1,2}
陈旧的切片
多个切片可以引用同一个底层数组,在某些情况下,在一个切片中添加新的数据,在原有数组无法保持更多新数据时,将导致分配一个新的数组。而其他的切片还指向老的数组。
map
//切片时不能作为key的
var map1 map[keyType]valueType
var map1 make(map[key]value) //make 来分配内存
map的可以访问,val1,isPresent := map(key1) 或者 val1 = map1(key1) 的方法获取key1对应值val1
if _,ok:=x["tow"]; !ok{
fmt.Prlentln("on entry")
}
map1 := make(map[string]string , 5) //声明但未初始化
map2 := make(map[string]string)
map3 := map[string]string{} //声明了一个空map
map4 := map[string]string{"a":"v1" , "b":"v2"} //初始化并赋值
delete(map,'a') //删除key a
range 语句中的值
rangfe 语句中的值时原数据的拷贝,不是引用
data := []int{1,2,3} for _,v:=range data{ //操作 数据拷贝}
需要操作引用值
data := []int{1,2,3} for _,_:=range data{ //操作 引用}
流程控制
switch
switch var1{
case val1:
...
case val2:
...
default:
....
}
switch{
case condition1:
...
case condition2 :
....
default:
....
}
select
select 时go 语言中的一个控制结构,类似switch语句,主要用于处理
异步通道操作
所有情况都会涉及通信操作,因此select会监听分支语句中通道的读写,当分支中的通道读写操作为非组赛状态时,将会触发相应的动作。select语句会选择一组可以发送或者接收的操作中的一个分支继续执行,select没有条件表达式,一直在等待分组进入可运行状态。
select 中的 case语句必须是一个channel 操作
select 中的 default 子句总时可以运行的
- 如果有多个分支都可以运行,select会伪随机公平地选出一个执行,其他分支不会执行
- 如果没有运行的分支,且有default语句,那么会执行default的动作
- 如果没有可运行的分支,切眉有default语句,select将
阻塞
,直到某个分支可以运行
package main
import(
"fmt"
"time"
)
func main(){
var c1,c2,c3 chan int
var i1,i2 int
select{
case i1 = <-c1:
fmt.Plrintf("收到" , i1 , "from c1")
case c2 <-i2
fmt.Printf("发送" , i2,"to c2")
case i3 , ok := (<- c3):
if ok{
fmt.Printf("接收" , i3 ,"from c3")
}else{
fmt.Printf("c3 是关闭的")
}
case <-time.After(time.Second * 3): //超时推出
fmt.Prinln("请求超时")
}
}
for 循环
for 初始化语句 ;条件语句 ; 修饰符 {}
for {} //无限循环
for - range
for-range 结构是go语言特有的一种迭代结构,在许多情况下都非常有用,可以迭代任何集合、数组、字典
for ix ,val := range coll{}
val 始终为集合中对应索引的值的副本,一般指具有只读性,对他的操作都不会影响原数据
注意: val为指针,则会产生指针的副本,依旧可以修改集合中的原值
if语句由布尔表达式后紧跟一个或多个语句组成
if true/false { ...}
brak作用范围是该语句出现的最内部结构
continue忽略剩余循环开始下一次
label 标签,结束内部结构 (例如: continue 后指向 label 的位置)
异常处理
任何时候需要一个新的错误类型,都可以使用errors (必须先import)包的errors。New 函数接收合适的错误信息来创建
err := errors.New("异常信息") func Sqrt ( f float64)(float64 ,error){ if f < 0 { return 0 , errors.New("异常信息") } }
通常想要返回包含错误参数信息的字符串,可以使用 fmt.Error() , 这和fmt.Prinff()完全一样,接收有一个或多个格式占位符的格式化字符串和相应数量的占位变量。
if f<0{ return 0,fmt.Error("错误信息 %f" , f) }
panic 非常严重不可恢复错误
必须先声明
defer
否则不能捕获到异常,普通函数执行时发生异常,则开始defer处理完在返回。多层嵌套的函数调用
panic()
,可以马上中止当前函数的执行,所有的defer语句都会保证执行并把控制权交还给接收到异常的函数调用者。这样向上冒泡直到最顶层,并执行每一层的defer
,这个终止过程就是panicking
- 在包内部,总时应该从异常中 recover;不允许显示超出包范围的panic()
- 向包的调用者返回错误值
recover()
函数的调用仅当它在defer函数中直接调用时才有效
package main
import( "fmt")
func div (a,b int){
defer func(){
if r:= recover();r != nil{
fmt.Printf("捕获到异常: %s\n" , r)
}
}()
if b<0{ panic("除数需要大于0")}
fmt.Println("余数为:", a/b)
}
func main(){
div(10,0) //捕捉内部的异常
//捕获到异常 runtime error: integer divide by zero
div(10,-1) //捕捉主动的异常
//捕获到异常 除数需要大于0
}
recover 从异常中恢复
这个函数被用于异常或错误场景中恢复;让程序可以从panicking重新获得控制权,停止终止过程。
recover()只能在defer修饰的函数汇总使用;用于去的异常调用中传递过来的错误值,如果正常执行recover()返回的是nil,且没有其他效果
func protect(g func()){
defer func(){
log.Println("完成")
//即使panic ,println 也正常执行
if err := recover;err!=nil{
log.Printf("启动运行 %v" ,err)
}
}()
log.Println("开始")
er() //可能会出先异常的地方
}
defer
- 当defer 被声明时,其参数会被实时解析
- defer执行顺序为先进后出
- defer可以读取有返回值,可以改变有名返回参数值
// 规则二 defer执行顺序为先进后出
package main
import "fmt"
func main() {
defer fmt.Print(" !!! ")
defer fmt.Print(" world ")
fmt.Print(" hello ")
}
//输出: hello world !!!
package main
import (
"fmt"
)
func main() {
fmt.Println("=========================")
fmt.Println("return:", fun1())
fmt.Println("=========================")
fmt.Println("return:", fun2())
fmt.Println("=========================")
fmt.Println("return:", fun3())
fmt.Println("=========================")
fmt.Println("return:", fun4())
}
func fun1() (i int) {
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer2: 2
}()
// 规则二 defer执行顺序为先进后出
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer1: 1
}()
// 规则三 defer可以读取有名返回值(函数指定了返回参数名)
return 0 //这里实际结果为2。如果是return 100呢
}
func fun2() int {
var i int
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer2: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer1: 1
}()
return i
}
func fun3() (r int) {
t := 5
defer func() {
t = t + 5
fmt.Println(t)
}()
return t
}
func fun4() int {
i := 8
// 规则一 当defer被声明时,其参数就会被实时解析
defer func(i int) {
i = 99
fmt.Println(i)
}(i)
i = 19
return i
}
程序输出:
=========================
defer1: 1
defer2: 2
return: 2
=========================
defer1: 1
defer2: 2
return: 0
=========================
10
return: 5
=========================
99
return: 19
函数介绍
函数关键字 func 、函数名、参数列表、返回值、函数体和返回语句
func 函数名(参数列表)(返回值列表){
//函数体
return
}
除了main()、init()函数外,其他所有类型的函数都可以有参数与返回值
如果两个函数的参数列表和返回值列表的变量类型–对应,那么两个函数就有相同的签名,下面ta和tb具有相同的函数签名
func ta (a,b int ,z float32) bool func tb (a,b int ,z float32) (bool)
函数可以赋值给变量
package main import ( "fmt" "time" ) type funcType func(time.Time) // 定义函数类型funcType func main() { f := func(t time.Time) time.Time { return t } // 方式一:直接赋值给变量 fmt.Println(f(time.Now())) var timer funcType = CurrentTime // 方式二:定义函数类型funcType变量timer timer(time.Now()) funcType(CurrentTime)(time.Now()) // 先把CurrentTime函数转为funcType类型,然后传入参数调用 // 这种处理方式在Go 中比较常见 } func CurrentTime(start time.Time) { fmt.Println(start) }
函数调用
参数一般传递的是副本,需要修改原本值时,穿入
function(&arg)
此时传递的是一个指针。在函数调用时,像切片、字典、接口、通道等这样的引用类型都是默认使用引用类传递
内置函数
go语言拥有一些内置函数,内置函数预先声明,它们像任何其他函数一样被调用
内置函数 | 说明 |
---|---|
close | 用于通道,对于通道c,内置函数close©将不再通道c上发送值。如果c是仅接收通道,则会出错。发送或关闭已关闭的通道会导致运行是错误。关闭nil |
new、make | new和make 均是用于分配内存,new用于值类型的内存分配,并且置为0。make只用于slice、map以及channel这三种引用数据类型的内存分配和初始化。new(T)分配类型T的零值并返回其地址,也就是指向类型T指针,make(T)它返回类型值(不是T) |
调用 | T类型 | 结果 |
---|---|---|
make(T,n) | slice | T为切片类型,长度和容量为n |
make(T,n,m) | slice | T为切片类型,长度为n,容量为m(n<=m,否则错误) |
make(T) | map | 字典 |
make(T,n) | map | T位字典类型,初始化n个元素的空间 |
make(T) | channel | T为通道类型,无缓冲区 |
make(T,n) | channel | T位通道类型,缓冲区长度为n |
- len()
- cap() 容量
- append()
- copy()
- delete()
匿名函数
lf := func(x,y int) int {return x+y} //赋值
lf(1,2) //调用
func(x,y int)int{return x+y}(3,4) //赋值调用
func(){fmt.Prinrl("直接调用")}()
type 自定一类型
自定义:结构体、接口、类型、类型别名等
type LF int //自定义 LF 为新的类型
// LF 类型是没有 int 的相关方法,需要自己重新定义
var tt LF = 5 //LF类型的 基础机构是int
go语言是静态语言,类型转换没有隐式的。
valueOfTypeB = TypeB(valueOfTypeA) 类型B的值 = 类型B(类型A的值)
定义类型别名
1.9版本中实现,可以将别名类型和原类型这两个类型视为同一个类型,函数也一致
type LF = int
结构体
由一系列的属性组成,每个属性都有自己的类型和值
结构体类型和字段名遵循可见性规则
方法Method 可以访问这些数据,就好像他们是这个独立实体的一部分
结构是值类型,因此可以通过new来函数来创建
// 结构题中 非空字段名称必须唯一
type Lf struct{
age , x int
_ float32 //填充
A *[]int
F func()
}
struct {
T1 // 字段名 T1
*T2 // 字段名 T2
P.T3 // 字段名 T3
*P.T4 // f字段名T4
x, y int // 字段名 x 和 y
}
type S struct{ a int;b float64}
new(S) //分配内存返回指针
s := User{"Lf",20} //初始化
s := User{name:"LF",age:20}
s := User{age:20}
&Type{} = new(Type)
// 结构体不可见 使用工厂模式返回
type bitmap struct {
Size int
data []byte
}
func NewBitmap(size int) *bitmap {
div, mod := size/8, size%8
if mod > 0 {
div++
}
return &bitmap{size, make([]byte, div)}
}
//字段标签 tag
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string "学生名字" // 结构体标签
Age int "学生年龄" // 结构体标签
Room int `json:"Roomid"` // 结构体标签
}
func main() {
st := Student{"Titan", 14, 102}
fmt.Println(reflect.TypeOf(st).Field(0).Tag)
fmt.Println(reflect.TypeOf(st).Field(1).Tag)
fmt.Println(reflect.TypeOf(st).Field(2).Tag)
}
程序输出:
学生名字
学生年龄
json:"Roomid"
type Lf struct{ name string}
type lFweixiao struct{
Lf //匿名内嵌 类似于继承
int //匿名内嵌
}
接口
//接口中内嵌接口
type L1 interface{ write() }
type L2 interface{ read() }
type L3 interface( L1 ,L2)
定义了一组方法的集合,方法被定义,还没有实现
var i interface{} = 99 //i可是任意类型
i = "str"
i = 10.11
类型断言
value , ok:=varI.(T) //类型断言
//来检测某个时刻接口变量varl是否包含类型 T 的值
//varl 是一个接口变量
//更安全的使用
var varl I
varl =T("str")
if v, ok:=varI.(T);ok(){
fmt.Println("varI类型断言结果为 :",v) //varl 已经转换为T类型
varl.f()
}
typr-switch 类型判断
var value interface{}
switch:=value.type(type){
case string:
fmt.Println("value类型推断为string",str)
....
}
var varl I
varl := T("myString")
if v,ok:=var.(T);ok{
fmt.Println("varl类型断言结果为",v)
varl.f()
}
方法定义
func (recv receiver_type) methodName(parameter_list) (retrun_value_list){}
func ( user User ) myU(){} //接收器 (user User) user 是User实列
接收器不能接收指针和接口
接收器可以是 类型的指针
type Myint int
func (mi *MyInt) print(){
fmt.Println("myint" , *mi) //指针接收器,指针方法
}
func (mi Myint)echo(){
fmt.Println("myint",mi) //值接收器,值方法
}
并发 协程
go语言在语言上支持并发,
goroutine
是go语言提供的一种用户态线程,有时我们也称之为协程。轻量线程一般开销为4k,并且它们跑在同一个内存和线程之上时,就需要一个调度器来维护这些协程,确保所有的协程都有能使用的cpu,并且尽可能公平使用cpu资源。
调度器主要由4个重要部分,m、g、p、sched
- M:work thread 系统线程内核、操作系统管理
- P:processor 衔接M和G调度上下文,负责执行G与M对接,P的数量可以通过
GOMAXPROCS()
来设置,它其实也代表了真正的并发度 - G: goroutine 协程实体,包括了调用栈,重要的调度信息,例如channel等
runtime.NumCpu() //返回当前cpu的内核数
runtime.GOMAXPROCS(2) //设置运行时最大可执行的cpu数
runtime.NumGoroutine() //当前正在运行的协程数
P维护这个队列 runqueue,GO语言,启动一个协程go function
就行,所有没有一个go语句被执行,runqueue队列的就在末尾加入一个协程。在下一个调度点就从,runqueue去除一个协程
通道 channel
go奉行通过通信来共享内存,而不是共享内存来通信,
channel数协程中间互相通信的通道
协程之间可以通过它发送消息和接收消息。可以传递指针
通道和消息类型也有关系,一个通道只能传递 (发送send或接收 receive)类型的值,这需要在声明通道时指定。默认通道时阻塞的(叫做无缓冲通道)
var channel chan int = make(chan int)
channel :=make(chan int)
//定义接收通道
receive_only:= make (<-chan int)
//定义发送通道
send_only := make(chan<- int)
//可同时发送接收
send_receive:= make(chan int)
//带缓冲区的通道
c:= make(chan int ,10)
当通道中的数据没有被取走,通道时阻塞的,就不能在往通道中送数据,(类似与java唯一队列)
同步锁
go 语言包中syn提供了两种类型的锁:
sync.Mutex和sync.RWMutex 前者时互斥锁,后者是读写锁
var lck sync.Mutex
func foo(){
lck.Lock()
defer lck.unLock()
....
}
sync.waitgroup
等待一组线程集执行完,才会继续向下执行。
主线程goroutine 调用 Add来设置等待线程的 goroutine 数量。然后,每个线程 goroutine 运行,并在完成后调用Done。同时,Wait用来阻塞,直到所有线程 goroutine 完成才会向下执行Add(-1)和Done()效果一样
sync.once
保证once 只执行一次
sync.map
1.9Go 新特性,原生支持并发安全的map。
sync.Map 和map 有较大差异
func main(){
var m sync.Map
m.Store("name","lf")
m.Store("name","wx")
v ,ok:=m.LoadOrStore("name1","tt")//key不存,则存入
v ,ok:=m.LoadOrStore("name","tt")//key存在,不会修改 v
v, ok = m.Load("name") //获取 kye name 的v
f:=func(k,v interface{})bool{
//遍历 m k,v是每一个值
}
m.Delete("name") //删除
}
指针
指针变量在32位计算机上占4b,64位计算机上占8b
go语言中,指针类标识指向给定类型 符号*
T 将以类型T为基础,生成指针类型T,初始化值nil
type Point3D struct{x,y,z float64}
var point *Point3D
var i *[4]int
//此时2个指针的值为nil 这个时候反向引用是不合法的
符号*可以放在一个指针前 (*pointer),哪么它将得到这个指针指向地址上所存粗的值,这称为反向引用。(*pointer).x 简写 pointer.x
var == *(&var)
const i = 5
lf := &i
lf := 5 //都会报错
指针的使用方法
- 定义指针变量
- 为指针变量赋值
- 访问指针变量中指向地址的值
- 在指针类型签名加上*号来获取指针指向的内容
new() 和 make()的区别
new() 用于值类型的内存分配,并且置为零值
make()只用于切片、字典、以及通道这三种引用数据类型的内存分配和初始化。
new(t)分配类型T的零值并返回地址,也就是指向T的指针
make(T) 返回类型T的值 不是*T
GO语言不能判断变量是分配到栈,还是堆上。位置是有编译器决定的。编译器根据变量的大小和泄漏(逃逸)分析的结果来决定其位置。
//自己决定内存配位位置 go run -gcflags -m main.go # command-line-arguments .\main.go:12:31 m.Alloc / 1024 escapes to heap .\main.go:11:23 &m does not escape
测试 testing
testing包含测试函数,测试辅助代码,被专门来进行单元测试以及自动化测试,打印日志和错误报告。
示例函数则是名称以 example开头的函数,通常保持在 example*test.go文件中
单元测试
测试程序是独立的文件,他必须属于被测试的包,和这个包的其他程序放在一起,并且
文件名满足 *test_.go
。import{ "testing" } func TestLf(t *testing.T){} //TestXxx 固定格式 首字母需要大写
*testIng.T 是给测试用的结构类型,用来管理测试状态,支持格式化测试日志,入t.log,t.error,t.errorf
函数同通知 | 说明 |
---|---|
func(t *T)Fail() | 标记测试函数为失败,然后执行剩下的测试 |
func(t *T)FailNow() | 标记测试韩式为失败并终止执行;文件中比的测试也被略过,继续执行下一个文件 |
func(t *T)log(arrgs …interfacel{}) | args被用默认的格式化并打印到错误日志中 |
func(t *T)Fatal(args …interfacel{}) | 打印错误日志,后终止当前文件测试,执行下一个文件 |
go test常用参数 | 说明 |
---|---|
-cpu | 指定测试的GOMAXPROCS值,默认是GOMAXPROCS当前值 |
-count | 运行单元测试和基准测试n次(默认是1);如果设置了-cpu,则为每一个GOMAXPROCS运行n次,示例函数总运行一次 |
-cover | 启动覆盖率分析 |
-run | 执行功能测试函数,支持正则匹配,可以选择测试函数或者测试文件来仅测试耽搁函数或者单个文件 |
-bench | 执行基准测试函数,支持正则 |
-benchtime | 基准测试最大时间上限 |
-parallel | 运行并执行的最大测试数,默认情况设置为GOMAXPROCS值 |
-v | 展示测试过程信息 |
基准测试 BenchmarkXxx
基础测试命名规范
func BeanchmarkLf(b *testing.B){}
命令
go test-test.bench=*
会运行所有的基准测试
分析并优化go
go test -x -v -test.cpuprofile=pprof.out
//指定文件写入cput 或内存情况报告
用pprof 调试
监控go程序的堆栈,cpu耗时等性能信息,可以通过pprof包老实现。
"net/http/pprof" //中使用 runtime/ppro 封装,在http端口上暴露 "runtime/prof"
web服务器
引入 _“net/http/pprof” 然后访问http://localhost:port/debug/pprof/
package main import ( "fmt" "net/http" _ "net/http/pprof" // 为什么用_ , 在讲解http包时有解释。 ) func myfunc(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hi") } func main() { http.HandleFunc("/", myfunc) http.ListenAndServe(":8080", nil) }
服务进程
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
// 开启pprof
go func() {
log.Println(http.ListenAndServe("localhost:8080", nil))
}()
go hello()
select {}
}
func hello() {
for {
go func() {
fmt.Println("hello word")
}()
time.Sleep(time.Millisecond * 1)
}
}
应用程序
需要使用 runtime/pprof
反射 reflect
reflect
包提供了相关的功能在reflect包中 refect.TypeOf(),分别从类型、值的角度来描述一个GO对象
func TypeOf(i interface{}) Type
type Type interface
func ValueOf(i interface{}) Value
type Value struct
在Go语言的实现中,一个interface类型变量储存2个信息,一个<值,类型>,value,type
方法 | 说明 |
---|---|
type | |
Kind() | 返回一个常量,表示具体类型的底层类型 |
Elem() | 方法返回指针、数组、切片、字典、通道的基础类型,这个方法要慎用,如果用在其他类型上吗会出现 panic |
value | |
Type() | 将返回具体类型所对应的 reflect.type 静态类型 |
Kind() | 将返回一个常量,标识具体类型的底层类型 |
var a int = 50
v := reflect.ValueOf(a) // 返回Value类型对象,值为50
t := reflect.TypeOf(a) // 返回Type类型对象,值为int
fmt.Println(v, t, v.Type(), t.Kind())
var b [5]int = [5]int{5, 6, 7, 8}
fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array int
var Pupil Student
p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
fmt.Println(p.Type()) // 输出:Student
fmt.Println(p.Kind()) // 输出:struct
在Go语言中,类型包括 static type 和 concrete type ,简单说
static type 是你在编码看见的类型(int,string),concrete type 实际具体的类型,runtime系统看见的类型
type()返回的是静态类型,king()返回的是具体类型
通过反射修改原对象
d.CanAddr() 判断它是否可以被取地址
d.CanSet() 判断它是否可以被取地址并可被修改
var a int = 50
v := reflect.ValueOf(a) // 返回Value类型对象,值为50
t := reflect.TypeOf(a) // 返回Type类型对象,值为int
fmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem())
seta := reflect.ValueOf(&a).Elem() // 这样才能让seta保存a的值
fmt.Println(seta, seta.CanSet())
seta.SetInt(1000)
fmt.Println(seta)
lf:=reflect.ValueOf(&name).Elem()
setUser.Field(1).SetString("lf")
反射结构体
package main
import (
"fmt"
"reflect"
)
// 结构体
type ss struct {
int
string
bool
float64
}
func (s ss) Method1(i int) string { return "结构体方法1" }
func (s *ss) Method2(i int) string { return "结构体方法2" }
var (
structValue = ss{ // 结构体
20,
"结构体",
false,
64.0,
}
)
// 复杂类型
var complexTypes = []interface{}{
structValue, &structValue, // 结构体
structValue.Method1, structValue.Method2, // 方法
}
func main() {
// 测试复杂类型
for i := 0; i < len(complexTypes); i++ {
PrintInfo(complexTypes[i])
}
}
func PrintInfo(i interface{}) {
if i == nil {
fmt.Println("--------------------")
fmt.Printf("无效接口值:%v\n", i)
fmt.Println("--------------------")
return
}
v := reflect.ValueOf(i)
PrintValue(v)
}
func PrintValue(v reflect.Value) {
fmt.Println("--------------------")
// ----- 通用方法 -----
fmt.Println("String :", v.String()) // 反射值的字符串形式
fmt.Println("Type :", v.Type()) // 反射值的类型
fmt.Println("Kind :", v.Kind()) // 反射值的类别
fmt.Println("CanAddr :", v.CanAddr()) // 是否可以获取地址
fmt.Println("CanSet :", v.CanSet()) // 是否可以修改
if v.CanAddr() {
fmt.Println("Addr :", v.Addr()) // 获取地址
fmt.Println("UnsafeAddr :", v.UnsafeAddr()) // 获取自由地址
}
// 获取方法数量
fmt.Println("NumMethod :", v.NumMethod())
if v.NumMethod() > 0 {
// 遍历方法
i := 0
for ; i < v.NumMethod()-1; i++ {
fmt.Printf(" ┣ %v\n", v.Method(i).String())
// if i >= 4 { // 只列举 5 个
// fmt.Println(" ┗ ...")
// break
// }
}
fmt.Printf(" ┗ %v\n", v.Method(i).String())
// 通过名称获取方法
fmt.Println("MethodByName :", v.MethodByName("String").String())
}
switch v.Kind() {
// 结构体:
case reflect.Struct:
fmt.Println("=== 结构体 ===")
// 获取字段个数
fmt.Println("NumField :", v.NumField())
if v.NumField() > 0 {
var i int
// 遍历结构体字段
for i = 0; i < v.NumField()-1; i++ {
field := v.Field(i) // 获取结构体字段
fmt.Printf(" ├ %-8v %v\n", field.Type(), field.String())
}
field := v.Field(i) // 获取结构体字段
fmt.Printf(" └ %-8v %v\n", field.Type(), field.String())
// 通过名称查找字段
if v := v.FieldByName("ptr"); v.IsValid() {
fmt.Println("FieldByName(ptr) :", v.Type().Name())
}
// 通过函数查找字段
v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
if v.IsValid() {
fmt.Println("FieldByNameFunc :", v.Type().Name())
}
}
}
}
程序输出:
String : <main.ss Value>
Type : main.ss
Kind : struct
CanAddr : false
CanSet : false
NumMethod : 1
┗ <func(int) string Value>
MethodByName : <invalid Value>
=== 结构体 ===
NumField : 4
├ int <int Value>
├ string 结构体
├ bool <bool Value>
└ float64 <float64 Value>
--------------------
String : <*main.ss Value>
Type : *main.ss
Kind : ptr
CanAddr : false
CanSet : false
NumMethod : 2
┣ <func(int) string Value>
┗ <func(int) string Value>
MethodByName : <invalid Value>
--------------------
String : <func(int) string Value>
Type : func(int) string
Kind : func
CanAddr : false
CanSet : false
NumMethod : 0
--------------------
String : <func(int) string Value>
Type : func(int) string
Kind : func
CanAddr : false
CanSet : false
NumMethod : 0
unsafe
func Alignof(x ArbitraryType) uintptr //返回变量对齐字节数量 func Offsetof(x ArbitraryType) uintptr //返回变量指定属性的偏移量,如果变量是一个struct类型,不能直接将这个struct类型的变量当作参数,这能将struct类型变量的属性当作参数 func Sizeof(x ArbitraryType) uintptr //返回变量在内存中占用的字节数,如果是slice,则不会返回切片在内存中实际占用长度。 type ArbitraryType int type Pointer *ArbitraryType
ArbitraryType是以int 为基础定义的一个新类型,但是Go语言unsafe包中,对ArbitraryType赋予了特殊定义,通常,我们把interface{} 看作任意类型,那么ArbitraryType 这个类型比 interface{}更加随意
Pointer 是ArbitraryType指针类型为基础的新类型,Pointer 是任何指针类型的 父类型
unsafe中,通过ArbitaryType 、Pointer 这两个类型,可以将其他类型都转换,然后通过这三个函数,分别能取长度,偏移量,对齐字节数,就可以在内存地址映射中,来回游走。
指针运算
uintptr 这个基础类型,在Go语言中,字节长度是与int一致,通常Pointer不能参与指针运算,比如在某个指针上加一个便宜量,Pointer是不鞥做这个运算,那么谁可以了。将Pointer类型转换为unitptr类型,做完地址加减运算后,在转换成Pointer类型,通过*操作达到取值、修改的目的。
uintptr 和 unsafe.Pointer 的区别
- unsafe.Pointer 单纯的普通指针类型,用于类型转换不同类型的指针,它可以不参与指针运算;
- 而uintptr是用于指针运算,GC不把uintptr当指针,也就是uintptr无法持有对象,uintptr类型的目标会被回收
- unsafe.Pointer 可以和 普通指针进行互换;
- unsafe.Pointer可以和 uintptr 进行相互转换。
Go语言不支持直接进行指针运算,用起来稍显麻烦
sort 排序
go 语言标准库 sort包中实现了几种基本的排序算法: 插入、快排、堆排序。
使用sort时无需具体考虑具体使用那种排序方式
func insertionSort(data Interface, a, b int) func heapSort(data Interface, a, b int) func quickSort(data Interface, a, b, maxDepth int)
interface 这个是名字,是大写字母I开头
type Interface interface{
// Len 为集合内元素总和
Len() int
//如果index为i的元素小于index为j的元素,则返回true,否则false
Less(i,j int) bool
//Swap 交换索引为 i和j的元素
Swap(i,j int)
}
自定义 sort.interface 排序
具体某个结构体排序, 就需要自己实现interface。需要自己实现sort.interface的接口的三个方法len(),Swap(i,j int),Less(i,j int) 。
// 示例
package main
import (
"fmt"
"sort"
)
type person struct {
Name string
Age int
}
type personSlice []person
func (s personSlice) Len() int { return len(s) }
func (s personSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s personSlice) Less(i, j int) bool { return s[i].Age < s[j].Age }
func main() {
a := personSlice{
{
Name: "AAA",
Age: 55,
},
{
Name: "BBB",
Age: 22,
},
{
Name: "CCC",
Age: 0,
},
{
Name: "DDD",
Age: 22,
},
{
Name: "EEE",
Age: 11,
},
}
sort.Sort(a)
fmt.Println("Sort:", a)
sort.Stable(a)
fmt.Println("Stable:", a)
}
程序输出:
Sort: [{CCC 0} {EEE 11} {BBB 22} {DDD 22} {AAA 55}]
Stable: [{CCC 0} {EEE 11} {BBB 22} {DDD 22} {AAA 55}]
sort.Slice
利用 sort.Slice 函数,而不用提供一个特定的,sort.Interface 的实现,而是Less() 作为一个比较回调函数,可以简单传递给sortSlice进行排序。这种方法一般不建议使用,因为在sort.Slice中使用了反射
type Peak struct{
Name string
Elevation int //in feet
}
func main(){
peaks := []Peak{
{"Aconcagua", 22838},
{"Denali", 20322},
{"Kilimanjaro", 19341},
{"Mount Elbrus", 18510},
{"Mount Everest", 29029},
{"Mount Kosciuszko", 7310},
{"Mount Vinson", 16050},
{"Puncak Jaya", 16024},
}
// does an in-place sort on the peaks slice, with tallest peak first
sort.Slice(peaks, func(i, j int) bool {
return peaks[i].Elevation >= peaks[j].Elevation
})
fmt.Println(peaks)
}
程序输出:
[{Mount Everest 29029} {Aconcagua 22838} {Denali 20322} {Kilimanjaro 19341} {Mount Elbrus 18510} {Mount Vinson 16050} {Puncak Jaya 16024} {Mount Kosciuszko 7310}]
启动外部命令和程序
os标准包,是一个比较重要的包,顾名思义,主要是在服务器上进行系统的基本操作,入文件操作,目录操作,执行命令,信号中断,进程,系统状态等等。在os包下有exec,signal,user三个子包
函数 OS包 | 说明 |
---|---|
Chdir(dir string) error | chdir将当前工作目录更改为dir目录 |
Getwd()(dir string , err error) | 获取当前目录 |
Chmod(name string,mode FileMode) error | 更改文件的权限 |
Chown(name string,uid,gid int)error | 更改文件拥有者owner |
Chtimes(name string,atime time.Time,mtime time.Time)error | |
Clearenv() | 清楚所有环境变量 |
Environ() []string | 返回所有环境变量 |
Exit(code int) | 系统退出,并返回code,其中0标识执行成功并退出 |
文件处理方法
函数 | 说明 |
---|---|
Create(name string)(file *File, error error) | Create采用模式0666创建一个名为name的文件,以存在的文件会被截断为空文件 |
Open(name steing)(file *File,err error) | 打开一个文件用于读取 |
(f *File)stat()(fi FileInfo,err error) | stat返回描述文件f的FileInfo类型值 |
(f *File)Readdir(n int)(fi []FileInfo,err error) | Readdir读取目录f的内容,返回一个有n个成员的[]fileInfo |
(f *File)WriteString(s string)(ret int ,err error) | 向文件写入字符串 |
(f *File)Sync()(err error) | sync递交文件的当前内容进行稳定存储 |
信号处理
一个运行良好的程序在退出(正常、强制、Ctrl+c,kill)时时可以执行一段清理代码,将收尾工作做完后再真正退出。一般采用系统signal来通知系统退出。
Go的系统信号处理主要涉及os包,os.signal包以及syscall包。其中最主要的函数signal包中的Notify函数:
func Notify(c chan <- os.Signal,sig ...os.Signal)
该函数会将进程收到的系统Signal转发给channel c。如果没有穿入sig参数,那么Notify会将系统收到的所有信号转发给channel c。
Notify会根据穿入的os.Signal,监听对应Signal信号,Notify()方法会将接收到对应os.Signal往一个channel c中发送
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
go signalListen()
for {
time.Sleep(10 * time.Second)
}
}
func signalListen() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGUSR2)
for {
s := <-c
//收到信号后的处理,这里a只是输出信号内容,可以做一些更有意思的事
fmt.Println("get signal:", s)
}
}
文件操作 io
func Mkdir(name string, perm FileMode) error
func Chdir(dir string) error
func TempDir() string
func Rename(oldpath, newpath string) error
func Chmod(name string, mode FileMode) error
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name)
return openFileNolog(name, flag, perm)
}
io操作封装如下几种
- io 为I/O primitives 提供的基础接口
- io/loutil 封装一些实用的 i/o函数
- fmt 实现格式化 i/o
- bufio 实现了缓冲 i/o
io包中最主要的两个接口 Reader 和 Writer 接口
内核中的缓冲:
无论进程是否提供缓冲,内核都是提供缓冲的,系统对磁盘的读写都会提供一个缓冲(内核告诉缓冲),将数据写入到块缓冲进行排队,当块缓冲到一定量时,才把数据写入磁盘
进程中的缓冲:
是对输入输出流进行该进,提供一个流缓冲,当调用一个函向磁盘写数据时,先把数据写入缓冲区,当达到某个条件,入流缓冲满了,或刷新流缓冲,这是才会把数据已一次送往内核提供的缓冲中,在经块缓冲写入磁盘
文件读写
file.Read > ioutil > bufio
//文件对象直接读写
func (f *File) Read(b []byte)(n int ,err error)
func (f *File) Write(b []byte)(n int ,err error)
使用 File.Read 读取文件时,可考虑使用 buffer
func main(){
b:=make([]byte,1024)
f,err:=os.Open("./tt.text")
_,err:=f.Read(b)
f.Close()
if err!=nil{
fmt.Println(err)
}
fmt.Println(string[b])
}
ioutil库,没有直接实现Reader和Writer 接口,但是通过内部调用,也可读写文件内容
func ReadAll(r io.Reader) ([]byte, error)
func ReadFile(filename string) ([]byte, error) //os.Open
func WriteFile(filename string, data []byte, perm os.FileMode) error //os.OpenFile
func ReadDir(dirname string) ([]os.FileInfo, error) // os.Open
使用buflo库,这个库实现了I/O的缓冲操作,通过内嵌io.Reader、io.Writer接口,新建了Reader,Writer结构体。同样也是实现了,Reader和Weiter 接口
func (b *Reader) Read(p []byte) (n int, err error)
func (b *Writer) Write(p []byte) (nn int, err error)
ioutil 包
import (
"fmt"
"io/ioutil"
"os"
)
func main(){
fileObj,err:=os.Open("./tt.txt")
defer fileObj.Clase()
Contents,_:=ioutil.ReadFile("./tt.txt");err == nil{
fmt.Println(string(contents))
}
ioutil.WriteFile("./t3.txt", contents, 0666)
}
bufio
// NewReaderSize 将 rd 封装成一个带缓存的 bufio.Reader 对象,缓存大小由 size 指定(如果小于 16 则会被设置为 16)。
func NewReaderSize(rd io.Reader, size int) *Reader
// NewReader 相当于 NewReaderSize(rd, 4096)
func NewReader(rd io.Reader) *Reader
// Peek 返回缓存的一个切片,该切片引用缓存中前 n 个字节的数据。
// 如果 n 大于缓存的总大小,则返回 当前缓存中能读到的字节的数据。
func (b *Reader) Peek(n int) ([]byte, error)
// Read 从 b 中读出数据到 p 中,返回读出的字节数和遇到的错误。
// 如果缓存不为空,则只能读出缓存中的数据,不会从底层 io.Reader
// 中提取数据,如果缓存为空,则:
// 1、len(p) >= 缓存大小,则跳过缓存,直接从底层 io.Reader 中读出到 p 中。
// 2、len(p) < 缓存大小,则先将数据从底层 io.Reader 中读取到缓存中,
// 再从缓存读取到 p 中。
func (b *Reader) Read(p []byte) (n int, err error)
// Buffered 该方法返回从当前缓存中能被读到的字节数。
func (b *Reader) Buffered() int
// Discard 方法跳过后续的 n 个字节的数据,返回跳过的字节数。
func (b *Reader) Discard(n int) (discarded int, err error)
// ReadSlice 在 b 中查找 delim 并返回 delim 及其之前的所有数据。
// 该操作会读出数据,返回的切片是已读出的数据的引用,切片中的数据在下一次
// 读取操作之前是有效的。
// 如果找到 delim,则返回查找结果,err 返回 nil。
// 如果未找到 delim,则:
// 1、缓存不满,则将缓存填满后再次查找。
// 2、缓存是满的,则返回整个缓存,err 返回 ErrBufferFull。
// 如果未找到 delim 且遇到错误(通常是 io.EOF),则返回缓存中的所有数据
// 和遇到的错误。
// 因为返回的数据有可能被下一次的读写操作修改,所以大多数操作应该使用
// ReadBytes 或 ReadString,它们返回的是数据的拷贝。
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
// ReadLine 是一个低水平的行读取原语,大多数情况下,应该使用ReadBytes('\n')
// 或 ReadString('\n'),或者使用一个 Scanner。
// ReadLine 通过调用 ReadSlice 方法实现,返回的也是缓存的切片。
// 用于读取一行数据,不包括行尾标记(\n 或 \r\n)。
// 只要能读出数据,err 就为 nil。如果没有数据可读,则 isPrefix
// 返回 false,err 返回 io.EOF。
// 如果找到行尾标记,则返回查找结果,isPrefix 返回 false。
// 如果未找到行尾标记,则:
// 1、缓存不满,则将缓存填满后再次查找。
// 2、缓存是满的,则返回整个缓存,isPrefix 返回 true。
// 整个数据尾部“有一个换行标记”和“没有换行标记”的读取结果是一样。
// 如果 ReadLine 读取到换行标记,则调用 UnreadByte 撤销的是换行标记,
// 而不是返回的数据。
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
// ReadBytes 功能同 ReadSlice,只不过返回的是缓存的拷贝。
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
// ReadString 功能同 ReadBytes,只不过返回的是字符串。
func (b *Reader) ReadString(delim byte) (line string, err error)
// Reset 将 b 的底层 Reader 重新指定为 r,同时丢弃缓存中的所有数据,
// 复位所有标记和错误信息。 bufio.Reader。
func (b *Reader) Reset(r io.Reader)
fmt 格式化
Scan 和 Pront
f
指定了format
ln
有换行符
scan 包中
F
指定了io.Reader
S
从字符串读取
Scan、Scanf、Scanln 从标准输入os.Stdin读取文本;
Fscan、Fscanf、Fscanln 从指定的io.Reader接口读取文本;
Sscan、Sscanf、Sscanln 从一个参数字符串读取文本。
格式化verb
符号 | 含义 |
---|---|
通用: | |
%v | 值的默认格式表示。当输出结构体时,扩展标志(%+v) 会添加字段名 |
%#v | 值的Go语法表示 |
%T | 值的类型的Go语法表示 |
%% | 百分号 |
%t : true/fasle
符号 整数 | 含义 |
---|---|
%b | 表示为二进制 |
%c | 该值对应 unicode 码值 |
%d | 表示为十进制 |
%o | 表示为八进制 |
%q | 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 |
%x | 表示为十六进制 a-f |
%X | 表示为十六进制 A-F |
%U | 表示为Unicode格式: U+1234,等价于 “U+%04X” |
符号 浮点数、复数 | 含义 |
---|---|
%b | 无小数部分、二进制指数的科计数法,如-1233456p-78;参见 strconv.FormatFloat |
%e | 科学计数法,如-1234.345e+78 |
%E | 科学计数法,如-1234.345E+78 |
%f | 有小数部分但无指数部分,123.456 |
%F | = %f |
%g | 根据实际情况采用 %e 或%f 格式 |
%G | 同上 字母大写 |
符号 ()byte | 含义 |
---|---|
%s | 直接输出字符串或者()byte |
%q | 该值对应的双引号括起来的go语法字符串字面值,必要时采用安全的转移表示 |
%x | 每个字节用两字符十六进制表示 a-f |
%X | 每个字节用两字符十六进制表示A |
指针 %p 表示为十六进制,并加上前导的 0x
宽度通过一个紧跟在百分号后面十进制数指定,如果未指定宽度,则表示值时处必须要之外不作填充。精度通过,宽度后
跟点号的十进制
符号 | 含义 |
---|---|
%f | 默认宽度,默认精度 |
%9f | 宽度9,默认精度 |
%.2f | 默认宽度,进度2 |
%9.2f | 宽度9,精度2 |
%9.0f | 宽度9,精度9 |
整数 宽度和精度都设置输出总长度,采用精度时表示左右对齐并用0填充,而默认默认表示用空格填充
符号 | 含义 |
---|---|
+ | 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义); |
- | 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐); |
# | 切换格式:八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p); 对%q(%#q),如果strconv.CanBackquote返回真会输出反引号括起来的未转义字符串; 对%U(%#U),如果字符是可打印的,会在输出Unicode格式、空格、单引号括起来的Go字面值; |
’ ’ | 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格; |
0 | 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面; |
verb 忽略不支持的旗帜
import (
"fmt"
"os"
)
type User struct {
name string
age int
}
var valF float64 = 32.9983
var valI int = 89
var valS string = "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
var valB bool = true
func main() {
p := User{"John", 28}
fmt.Printf("Printf struct %%v : %v\n", p)
fmt.Printf("Printf struct %%+v : %+v\n", p)
fmt.Printf("Printf struct %%#v : %#v\n", p)
fmt.Printf("Printf struct %%T : %T\n", p)
fmt.Printf("Printf struct %%p : %p\n", &p)
fmt.Printf("Printf float64 %%v : %v\n", valF)
fmt.Printf("Printf float64 %%+v : %+v\n", valF)
fmt.Printf("Printf float64 %%#v : %#v\n", valF)
fmt.Printf("Printf float64 %%T : %T\n", valF)
fmt.Printf("Printf float64 %%f : %f\n", valF)
fmt.Printf("Printf float64 %%4.3f : %4.3f\n", valF)
fmt.Printf("Printf float64 %%8.3f : %8.3f\n", valF)
fmt.Printf("Printf float64 %%-8.3f : %-8.3f\n", valF)
fmt.Printf("Printf float64 %%e : %e\n", valF)
fmt.Printf("Printf float64 %%E : %E\n", valF)
fmt.Printf("Printf int %%v : %v\n", valI)
fmt.Printf("Printf int %%+v : %+v\n", valI)
fmt.Printf("Printf int %%#v : %#v\n", valI)
fmt.Printf("Printf int %%T : %T\n", valI)
fmt.Printf("Printf int %%d : %d\n", valI)
fmt.Printf("Printf int %%8d : %8d\n", valI)
fmt.Printf("Printf int %%-8d : %-8d\n", valI)
fmt.Printf("Printf int %%b : %b\n", valI)
fmt.Printf("Printf int %%c : %c\n", valI)
fmt.Printf("Printf int %%o : %o\n", valI)
fmt.Printf("Printf int %%U : %U\n", valI)
fmt.Printf("Printf int %%q : %q\n", valI)
fmt.Printf("Printf int %%x : %x\n", valI)
fmt.Printf("Printf string %%v : %v\n", valS)
fmt.Printf("Printf string %%+v : %+v\n", valS)
fmt.Printf("Printf string %%#v : %#v\n", valS)
fmt.Printf("Printf string %%T : %T\n", valS)
fmt.Printf("Printf string %%x : %x\n", valS)
fmt.Printf("Printf string %%X : %X\n", valS)
fmt.Printf("Printf string %%s : %s\n", valS)
fmt.Printf("Printf string %%200s : %200s\n", valS)
fmt.Printf("Printf string %%-200s : %-200s\n", valS)
fmt.Printf("Printf string %%q : %q\n", valS)
fmt.Printf("Printf bool %%v : %v\n", valB)
fmt.Printf("Printf bool %%+v : %+v\n", valB)
fmt.Printf("Printf bool %%#v : %#v\n", valB)
fmt.Printf("Printf bool %%T : %T\n", valB)
fmt.Printf("Printf bool %%t : %t\n", valB)
s := fmt.Sprintf("a %s", "string")
fmt.Println(s)
fmt.Fprintf(os.Stderr, "an %s\n", "error")
}
程序输出:
Printf struct %v : {John 28}
Printf struct %+v : {name:John age:28}
Printf struct %#v : main.User{name:"John", age:28}
Printf struct %T : main.User
Printf struct %p : 0xc000048400
Printf float64 %v : 32.9983
Printf float64 %+v : 32.9983
Printf float64 %#v : 32.9983
Printf float64 %T : float64
Printf float64 %f : 32.998300
Printf float64 %4.3f : 32.998
Printf float64 %8.3f : 32.998
Printf float64 %-8.3f : 32.998
Printf float64 %e : 3.299830e+01
Printf float64 %E : 3.299830E+01
Printf int %v : 89
Printf int %+v : 89
Printf int %#v : 89
Printf int %T : int
Printf int %d : 89
Printf int %8d : 89
Printf int %-8d : 89
Printf int %b : 1011001
Printf int %c : Y
Printf int %o : 131
Printf int %U : U+0059
Printf int %q : 'Y'
Printf int %x : 59
Printf string %v : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %+v : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %#v : "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
Printf string %T : string
Printf string %x : 476f20697320616e206f70656e20736f757263652070726f6772616d6d696e67206c616e67756167652074686174206d616b6573206974206561737920746f206275696c642073696d706c652c202072656c6961626c652c2020616e6420656666696369656e7420736f6674776172652e
Printf string %X : 476F20697320616E206F70656E20736F757263652070726F6772616D6D696E67206C616E67756167652074686174206D616B6573206974206561737920746F206275696C642073696D706C652C202072656C6961626C652C2020616E6420656666696369656E7420736F6674776172652E
Printf string %s : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %200s : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %-200s : Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Printf string %q : "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software."
Printf bool %v : true
Printf bool %+v : true
Printf bool %#v : true
Printf bool %T : bool
Printf bool %t : true
a string
an error
log 日志
log包中通过New函数得到一个logger 结构指针,这个函数分别时out、prefix、flag。
var std = New(os.Stderr, "",LastFlags)
func Println(V ...interface{}){
std.Output(2,fmt.Sprintln(v...))
}
logfile, err := os.OpenFile("my.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatalln("fail to create log file!")
}
defer logfile.Close()
l:=log.New(logfile, "", log.LstdFlags)
l.Println("test")
num:=5
l.Println("test %d",num)
socket 网络
Go 自带runtime的跨平台编程语言,Go中暴露给语言使用者Tcp socket api时建立OS原生 TCP socket接口上,所以使用上相对简单
go语言net包封装了。服务端是一个标准的 listen + Accept 的结构,而在客户端go语言使用 net.Diai 或 DialTimeout 进行连接建立
net包中有一个 TCPConn ,这个类型可以用来作为客户端和服务器端交互的通道,他有两个主要的函数
// net 参数是 tcp4 、 tcp6 、 tcp 中的任意一个
func ResolveTCPAddr(net,addr string)(*TCPAddr , os.Error)
//addr 域名 或者ip
TODO 待补充
命令行
写命令行程序时需要对一命令参数进行解析,这是我们可以使用os库。os库可以通过变量Args来获取命令参数, os.Args 返回一个字符串数组,其中第一参数即使执行文件本身。
fmt.Println(os.Args)
// 编译执行后
$ ./cmd -user = "root"
[./cmd -user = root]
这种方式比较麻烦,可以使用flag库代替
flag包
概念
- 命令行参数 : 运行程序提供的参数
- 已定义命令行参数:程序中通过flag.Xxx等这种形式定义了的参数
- 非flag (non-flag) 命令行参数 : flag包不能解析的参数
参数定义
//返回对应类型的指针
var ip = flag.Int("flagname" ,1234 , "消息")
//将 flag 绑定到一个变量上
var flagvar int
flag.IntVar(&flagvar , "flagname" , 1234, "消息")
参数解析
// flag.Parse() 进行解析
s:=f.args[0]
if len(s) == 0 || s[s] != '-' || len(s) == 1 {
return false,nil
}
当遇到单独的一个“-”或不是“-”开始时,会停止解析。比如:./cli – -f 或 ./cli -f
这两种情况,-f都不会被正确解析。像这些参数,我们称之为non-flag参数
//分支程序
if h || H {
flag.Usage()
}
template
fmt 包中,Printf()等方法可以做大输出格式化,当然,对于简单的列子来说足够了,但是我们有时候还是需要复杂的输出格式,甚至需要格式化代码分离开。这时,可以使用 text/template 和 html/template
text/template
模版引擎,将模版和数据进行渲染的输出格式化后的字符程序。
- 创建模版对象
- 加载模版
- 执行渲染模版
package main
import (
"log"
"os"
"text/template"
)
// printf "%6.2f" 表示6位宽度2位精度
const templ = `
{{range .}}----------------------------------------
Name: {{.Name}}
Price: {{.Price | printf "%6.2f"}}
{{end}}`
var report = template.Must(template.New("report").Parse(templ))
type Book struct {
Name string
Price float64
}
func main() {
Data := []Book{ {"《三国演义》", 19.82}, {"《儒林外史》", 99.09}, {"《史记》", 26.89} }
if err := report.Execute(os.Stdout, Data); err != nil {
log.Fatal(err)
}
}
程序输出:
----------------------------------------
Name: 《三国演义》
Price: 19.82
----------------------------------------
Name: 《儒林外史》
Price: 99.09
----------------------------------------
Name: 《史记》
Price: 26.89
通过 tmp.txt
{{range .}}----------------------------------------
Name: {{.Name}}
Price: {{.Price | printf "%6.2f"}}
{{end}}
package main
import (
"log"
"os"
"text/template"
)
var report = template.Must(template.ParseFiles("tmp.txt"))
type Book struct {
Name string
Price float64
}
func main() {
Data := []Book{ {"《三国演义》", 19.82}, {"《儒林外史》", 99.09}, {"《史记》", 26.89} }
if err := report.Execute(os.Stdout, Data); err != nil {
log.Fatal(err)
}
}
模版语法
标签
{{ }}
注释
{{ /* 注释 */ }}
变量
{{ . }}
此标签输出当前对象的值
输出对象字段 或 方法名称为 Admpub的值。
'“Com”: {} 是一个方法并返回一个结构体对象,同样也可以访问其他字段或方法{{.Admpub.comFile}}
{{ .Admpub }}
调用方法
{{$admpub}}
调用方法 Admpub 是参数
{{.Admpub|FuncName1}} -> funcName1(this.Admpub)
条件判断
{{if false/true}} ... {{else if ....}} ... {{end}}
循环
{{range $k ,$v := .Var}} {{$k}} => {{end}} //$value 取外部变量
嵌入子模版
{{template "name"}} //命名 定义内容
{{tmeplate "name" pipeline}}
子模版嵌套
{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}
输出 ONE TWO
局部变量
{{with pipeline}} T1 {{end}}
{{with "output"}}{{printf "%q" .}}{{end}} // . 是"output"
输出字符串
{{"\"output""}}
输出字符串常量
{{`"output"`}}
{{printf "%q" "output"}}
{{"output" | printf "%q"}} // | 左边作为参数
net/http
import(
"fmt"
"net/http"
)
func myfunc(w http.ResponseWriter,r *http.Request){
fmt.Fprintf(w, "hi")
}
func main(){
http.HandleFunc("/",myfunc)
http.ListenAndServe(":8080",nil)
}
主要文件
chlient.go
server.go
request.go
response.go
request
http.NewRequest 构造一个http 请求,可以包括Headers,cookies等
函数 | 说明 |
---|---|
NewRequest | 新建一个请求 |
ReadRequest | 解析一个请求 |
AddCookie | 添加cookie安装RFC 6265 section 5.4的规则,AddCookie不会添加超过一个Cookie头字段 所有的Cookie都写在同一行,用分号分隔 |
Cookie | 返回指定name的cookie |
SetBasicAuth | 利用提供的用户名密码给http基本全新提供具有一定权限的header 当使用http基本授权时,用户名和密码时不加密的 |
FormFile | 返回符合条件的第一个文件 |
FormValue | 返回url 中的value |
ParseForm | 解析查询字符串,body |
ParseMultipartForm | 请求主体做为 multipart/form-data解析 |
PostFormValue | 解析post、put 请求中 body内元素,url元素被忽略 |
ProtoAtLeast | 检测request中使用的http协议是否之少时 参数: major.minor |
Referer | 如果请求汇总有refer 则返回对应的url |
Response
启始行、Headers、Body
函数 | 说明 |
---|---|
ReadResponse | 读一个http请求 |
Location | 重定向 |
ProtoAtLeast | http协议至少是major.minor |
Client
函数 | 说明 |
---|---|
Do | 发送http 请求并返回一个http响应 |
Head | 利用Head方法请求指定url,Head只返回页面的首部 |
http.NewRequest可以灵活的对http Request进行配置,然后再使用http.Client的Do方法发送这个http Request请求。
如果使用 Post或者PostForm ,是不能使用http.NewRequest配置请求的,只有Do方法可以定义http.NewRequest
import(
"fmt"
"compress/gzip"
"io/ioutil"
"net/http"
"strconv"
)
func main(){
//简式声明一个http.Client空结体指针对象
client:=&http.Client{}
//使用http.NewRequest 构建 rquest 请求
request , err := http.NewRequest("GET","http://baidu.com",nil)
if err != nil{
fmt.Println(err)
}
//使用http.Cookie结构体初始化一个cookie建值对
cookie:=&http.Cookie{Name:"userId",Value:strconv.Itoa(12345)}
//使用 request 添加cookie
request.AddCookie(cookie)
//设置request的Header
request.Header.Set("Accept", "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8")
request.Header.Set("Accept-Charset", "GBK, utf-8;q=0.7, *;q=0.3")
request.Header.Set("Accept-Encoding", "gzip, deflate, sdch")
request.Header.Set("Accept-Language", "zh-CN, zh;q=0.8")
request.Header.Set("Cache-Control", "max-age=0")
request.Header.Set("Connection", "keep-alive")
//使用http.Client 来发送request ,这里使用Do
response,err := client.Do(request)
if err!=nil{
fmt.Println(err)
return
}
//程序结束时关闭 response.Body 响应流
defer response.Body.Close()
//接收到http Response 状态值
fmt.Println(response.StatusCode)
if response.StatusCode == 200{
//200 请求成功
//解压压缩包返回信息
body,err := gzip.NewReader(reqsponse.Body)
if err!= nil{
fmt.Println(err)
}
defer body.Close()
r,err:=ioutil.ReadAll(body)
if err!=nil{
fmt.Println(err)
}
...
}
}
server
函数 | 说明 |
---|---|
ListentAndServer | 监听Tcp网络地址srv.Addr然后调用server处理接下来的请求 |
ListenAndServeTLS | 必须提供证书文件和对应的私钥 |
Server | 创建一个新的服务协程 |
SetKeepAlivesEnabled | 函数控制是否 http的keep-alives能够使用 |
NewServeMux | 初始化一个心的ServeMux(多路复用) |
Handle | 将handler注册为指定的模式 |
ServeHTTP | 该函数用于将最近请求的url模式的handler分配给指定的请求 |
自定义处理器 coustom handlers
import(
"log"
"net/http"
"time"
)
type timeHandler struct{
format string
}
func (th *timeHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
tm:=time.Now().Format(th.format)
w.Write([]byte("The time is:"+ tm))
}
func main(){
mux:=http.NewServeMux()
th:=&timeHandler{format: time.RFC1123}
mux.Handle("/time",th)
log.Println("Listening...")
http.ListenAndServe(":3000",mux)
}
NewServeMux 可以创建一个 多路复用。 在代码中 Mux 也是一种handler。吧它当作参数传递给http.ListenAndServe方法,后者会把mux传给server实例。因为指定了handler,因此真个http服务就不再是DefaultServeMux,而是mux,无论是在主持路由还是提供请求服务的时候。
任何有func(http.ResponseWriter, *http.Request)签名函数都能转化为一个HandlerFunc类型。这很有用,因为HandlerFunc,对象内置了ServeHTTP方法,后者可以聪明又方便的调用我们最初提供的函数内容。
context
context
方便在协程之间传递request相关数据、取消协程signal或截止时间等。协程在执行之前,要先知道程序当前执行状态,通常将这些状态封装在一个Context变量中,传递给需要执行的协程。上下文则几乎已经传递与请求同生存周期变量的标准方法。
context包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元外部,通过ctx变量,将过期或撤销这些信号传递给被调用的程序单元。若存在A调用B的API,B再调用C的API,若A调用B取消,那么要取消B调用C,通过在a、b、c的api调用之间传递context,以及判断其状态。
// Context包含过期,取消信号,request值传递等,方法在多个协程中协程安全
type Context interface {
// Done 方法在context被取消或者超时返回一个close的channel
Done() <-chan struct{}
Err() error
// Deadline 返回context超时时间
Deadline() (deadline time.Time, ok bool)
// Value 返回context相关key对应的值
Value(key interface{}) interface{}
}
func Background() Context //返回context数根
context 应用
sync.WaitGroup,它用于线程同步,会等待一组线程集合完成,才会继续向下执行,这对监控所有子协程完成情况有用,但无法控制这个协程
channel来传递消息,一个协程发送chennel信号,另一个协程通过select来得到。两个协程之间通信还行,但是A同B同C同D,就容易出现问题。
使用context
传递是层层传递,根节点能控制子节点。
序列化
encoding/json 包处理系列化和返序列化
type Human struct {
name string `json:"name"` // 姓名
Gender string `json:"s"` // 性别,性别的tag表明在json中为s字段
Age int `json:"Age"` // 年龄
Lesson
}
jsonStr := `{"Age": 18,"name": "Jim" ,"s": "男",
"lessons":["English","History"],"Room":201,"n":null,"b":false}`
var hu Human
if err := json.Unmarshal([]byte(jsonStr), &hu); err == nil {
fmt.Println("\n结构体Human")
fmt.Println(hu)
}
结构体Human
{ 男 18 {[English History]}}
sql
database/sql
关系型数据库访问,sql.DB 通过数据库驱动为我们提供管理底层数据库连接和关闭操作,管理数据库连接池。import ( "database/sql" _ "github.com/go-sql-driver/mysql" )//导入mysql 驱动
package main
import (
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
)
type DbWorker struct {
Dsn string
Db *sql.DB
}
type Cate struct {
cid int
cname string
addtime int
scope int
}
func main() {
dbw := DbWorker{Dsn: "root:123456@tcp(localhost:3306)/mydb?charset=utf8mb4"}
// 支持下面几种DSN写法,具体看MySQL服务端配置,常见为第2种
// user@unix(/path/to/socket)/dbname?charset=utf8
// user:password@tcp(localhost:5555)/dbname?charset=utf8
// user:password@/dbname
// user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
dbtemp, err := sql.Open("mysql", dbw.Dsn)
dbw.Db = dbtemp
if err != nil {
panic(err)
return
}
defer dbw.Db.Close()
// 插入数据测试
dbw.insertData()
// 删除数据测试
dbw.deleteData()
// 修改数据测试
dbw.editData()
// 查询数据测试
dbw.queryData()
// 事务操作测试
dbw.transaction()
}