- 2.3 变量
- 2.4 赋值
- 2.5 类型声明
- 2.6 包和文件
- 3.1 整数
- 3.2 浮点数
- 3.3 复数
- 3.4 布尔值
- 3.5 字符串 !
- 3.6 常量
- 4.1 数组
- 4.2 slice
- 4.3 map
- 4.4 结构体
- 4.5 JSON
- 4.6 模板
- 5.6 匿名函数
- WARN: 捕获迭代变量
- 5.7 变长函数
- 5.8 延迟函数 defer
- 5.9 宕机
- 5.10 恢复
- 6.4 方法变量
- 7.1 接口即约定
- 7.2 接口类型
- 7.3 实现接口
- 7.4 使用flag.Value来解析参数
- 7.5 接口值
- 7.6 使用sort.Interface 来排序
- 7.7 http.Handler 接口
- 7.8 error 接口
- 7.9 表达式求值器
- 7.10 类型断言
- 7.11 使用类型断言来识别错误
- 7.12 通过接口类型断言来查询特性
- 7.13 类型分支
- 7.14 基于标记的XML解析
- 12.2 reflect.Type 和 reflect.Value
这里总结我学习==Go语言==的知识点, 可能比较凌乱, :smile: 因为我学习的时候也是很凌乱的。
Go语言对其它大众语言做了减法,这使得初学者可以很快的上手Go语言。
快速上手的好处就是,初学者很快用Go语言做出一些有用的东西,这得益于Go语言丰富的包支持。
此外,Go语言的一大亮点就是语法简单,易懂。
我非常喜欢这种less is more
观念,尽管Go没有明确的面向对象概念,但是Go使用结构体绑定方法来实现面向对象。
这样的好处就是简洁明了,我觉得C++的面向对象就是语法糖
,Go语言直接在语法层面展示面向对象的实现方法,减少了学习的负担。
所有Go语言的结构体真是是Go的一大法宝,但除此之外Go更优秀的地方在于它的goroutine
。
一个go
关键字就很好的诠释了Go语言,可以说,不了解goroutine
相当于没有学过Go
。
本笔记所属内容大多数来源<<Go程序设计实践>>
这本书,目录安排也是参考此书。
Go由于自动垃圾回收,变量的回收取决于变量是否可以被访问到
。
变量存在的位置(堆/栈),不是由变量申明的位置决定(C语言)。
//变量逃逸
var global *int
func f(){
var x int
x = 1
global = &x //x变量逃逸,使用堆空间
}
go函数支持多个返回值,变量支持多重赋值。
x,y = y,x //多重赋值,右边表达式先被推演
func gcd(x,y int) int { //求两个整数的最大公约数
for y != 0 {
x,y = y, x%y
}
return x
}
表达式较复杂时不建议使用多重赋值
赋值要求:变量和值的类型一样
在C语言中,赋值比较灵活,编译器会做响应的转换,例如将float
赋值给double
,但是反过来就会存在问题。
Go语言中就比较严格,对于var offset int = x == y
就是非法的,因为bool
和int
类型不一样。
此外,Go不支持C语言的三元表达式condition ? a : b;
。
类型声明即定义一个新的类型(可以理解为命名类型
),用于区分使用底层类型
而造成的混淆,或者隐藏具体的实现?
任何情况下,运行时的转换不会失败。(这句话没理解)
底层类型
决定了它的结构
,表达方式
和 操作集合
。
一个包 => 命名空间
占位符
- 一般占位符
符号 | 说明 |
---|---|
%v |
相应值的默认格式 |
%+v |
打印结构体,默认格式,会添加字段名 |
%+v |
相应值的Go语法表示 |
%T |
相应值类型的Go语法表示 |
%% |
打印百分号% |
- 布尔占位符
符号 | 说明 |
---|---|
%t |
单词true或者false |
- 整数占位符
符号 | 说明 |
---|---|
%b |
二进制表示 |
%c |
相应Unicode码所表示的字符 |
%d |
十进制表示 |
%o |
八进制表示 |
%q |
单引号围绕的字符值直面量,由Go语言安全转义 |
%x |
十六进制小写 |
%X |
十六进制大小 |
%U |
Unicode格式:U+1234,等同于"U+%04X" |
- 浮点及其复合构成占位符
符号 | 说明 |
---|---|
%b |
无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat 的 'b' 转换格式一致。例如 -123456p-78 |
%e |
科学计数法 |
%E |
同上 |
%f |
有小数点而无指数 |
%g |
根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的 0)输出 |
%G |
根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的 0)输出 |
- 字符串与字节切片占位符
符号 | 说明 |
---|---|
%s |
字符串或切片的无解译字节 |
%q |
双引号围绕的字符串,由 Go 语法安全地转义 |
%x |
十六进制,小写字母,每字节两个字符 |
%X |
十六进制,大写字母,每字节两个字符 |
- 指针
符号 | 说明 |
---|---|
%p |
十六进制表示,前缀 0x |
- 其它标记
符号 | 说明 |
---|---|
+ |
总打印数值的正负号;对于 %q(%+q)保证只输出 ASCII 编码的字符 |
- |
在右侧而非左侧填充空格(左对齐该区域) |
# |
备用格式:对八进制添加前导 0(%#o),对十六进制添加前导 0x(%#x)或 0X(%#X)对 %p(%#p)去掉前导 0x |
数据类型时一个语言的基础,我们写程序大多数都是在和数据打交道,因此掌握好语言的数据类型,学起来才能事半功倍。
- 基础类型(basic type)
- 数字(number)
- 字符串(string)
- 布尔型(boolean)
- 聚合类型(aggregate type)
- 数组(array)
- 结构体(struct)
- 引用类型(reference type)
- 指针(pointer)
- slice
- map
- 函数(function)
- 通道(channel)
- 接口类型(interface type)
- 操作符
&^
按位清除^
一元时,按位取反(与C语言~
区别)
布尔值无法隐性转换为数值,反过来也不行。
字符串是不可变的字节序列,这点要与C语言区分。
字符串可以包含任意数据,包括0值字节,习惯上,文本字符被解读为UTF-8编码的的Unicode码点序列。
内置len
函数返回字符串的字节数,为文字符号数目,因为一个文字符号可能占多个字节。
字符串值不可改变,意味着两个字符串能安全共同使用同一段底层内存,使得复制任意长度的字符串开销低廉。
字符串字面量
(string literal),形式上是带双引号的字节序列。
转义字符以\
开始,常见的转义字符如下
- \a
- \b
- \f 换页符
- \n 换行符(跳到下一行的同一位置)
- \r 回车符(返回首行)
- \t
- \v
- \'
- \"
- \\
- \xHH 十六进制
- \ooo 八进制
原生字符串
,使用反引号``,此时转义字符无效,可以展开写成多行,例如正则表达,HTML模板就需要这样的写法。
单引号括起来的字符表示一个rune
类型的字符。
需要弄清缘由,程序员不懂不好意思说学过编程。
UTF-8
以字节(一个字节8位)为单位对Unicode
码点进行变成编码。
UTF-8 由Go的两位创建者Ken Thompson
和 Rob Pike
发明。(难怪Go的源代码都是UTF-8格式)
一个字符号的编码的首字节的高位指明了后面还有多少个字节。
0xxxxxxx
110xxxxx
10xxxxxx
1110xxxx
10xxxxxx
10xxxxxx
11110xxx
10xxxxxx
10xxxxxx
10xxxxxx
缺点,无法按照下表[i]
来访问第i
个字符。
优点
- 兼容ASCII,自同步
- 前缀编码,从左到右解码
- 不嵌入NUL字节
逐个处理Unicode字符
import "unicode/utf8"
s := "hello,中国"
for i:=0;i<len(s); {
r,size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\n",i,r)
i += size //跳到下一个字符
}
//因为Go原生支持UTF-8,可以用for range 来遍历,同slice遍历一样
for index,value := range {
fmt.Printf("%d\t%c\n",index,value)
}
//统计字符串长度
length := utf8.RuneCountInString(s)
由于string
类型可以存储任意字节,所有会产生不符合utf8
编码的字符出现,此时会产生一个专门的Unicode字符\uFFFD
(�)替换它。
数组的长度是固定的,同C语言,一般很少使用。Slice的长度可以改变,使用更多。
var q [3]int = [3]int{1,2,3} //定义一个int类型数组,有三个元素(1,2,3)
var p = [...]int{4,5,6} //...表示数组长度由编译器推到
var t = [...]int{1:0, 2:10, 10:1} //指定索引可以个性化赋值
c1 := sha256.Sum256([]byte("x"))
c2 := sha256.Sum256([]byte("X"))
fmt.Printf("%x\n%x\n%t\n%T\n",c1,c2,c1==c2,c1)
/*
2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015
false
[32]uint8
*/
数组长度是类型的一部分,因此[3]int
与[4]int
是不同的类型。
如果数组元素类型可以比较,那么可以使用==
来比较两个数组值是否完全相同。
在传递参数时,数组在Go作为值传送,需要发生赋值。(区别与C语言)
slice有三个属性,指针
、长度
和容量
。
len
,cap
分别返回一个slice的长度和容量。
slice操作符s[i:j]
创建了一个新的slice,其中s
可以是数组,指向数组的指针,或者slice。
s[:]
引用了整个数据。通过[i:j]
创建的slice与之前的slice有共同的底层数据。
slice无法进行比较,不能使用==
测试两个slice是否相同。(原因:为了安全)
//将一个slice左移n个元素,使用三次反转。
a := []int{0,1,2,3,4,5}
reverse(s[:2])
reverse(s[2:])
reverse(s)
slice的零值是nil
,意味着使用var i []T
创建的slice不能直接使用,因为它没有对于的底层数据。
可以使用make([]T,len,cap)
来创建一个slice。
添加元素到slice后面,由于append函数添加函数的时候,底层数据可能发送了改变,所有一般将append
返回的值赋值给原slice变量,确保slice可用。
就地修改不涉及到底层数据重新分配内存。
//去除空元素
func nonEmpty(strings []string) []string {
out := strings[:0] //0长度的slice
for _, s := range strings {
if s != "" {
out = append(out,s)
}
}
return out
}
slice可以用来实现stack
//push v
stack = append(stack,v)
//top
top := stack[len(stack)-1]
//pop
stack = stack[:len(stack)-1]
map实现键
-值
map类型为map[K]V
,其中K
是键类型,要求必须可以使用==
比较。V
是值类型,没有任何限制。
delete
可以从map中根据键删除一个元素,键不存在也无妨。
无法获取一个map元素的地址。(原因是后面的操作可能会使地址失效)
使用for k,v := range mapXX
迭代循序是不固定的,这是feature
Go没有提供集合
类型,但是map的key
是唯一的。
Go的结构体和C语言相似。
但是对于一个指向结构体指针的变量,若要引用其结构体成员,Go只需用.
操作符,相当于C语言中的->
。
//二叉树,插入排序
type tree struct {
value int
left,right *tree
}
func Sort(values []int) {
var root *tree
for _, v := range values {
root = add(root,v)
}
appendValues(values[:0],root)
}
func add(t *tree,value int) *tree {
if t == nil {
t = new(tree)
t.value = value
return t
}
if value < t.value {
t.left = add(t.left,v)
}else {
t.right = add(t.right,v)
}
return t
}
func appendValues(values []int,t *tree) []int{
if t != nil {
values = appendValues(values,t.left)
values = append(values,t.value)
values = appendValues(values,t.right)
}
return values
}
结构体类型的值可以通过结构体字面量
来设置。
不同寻常的结构体嵌套机制(继承吗?)
Go允许我们定义不带名称的结构体成员,只需用指定类型(类型必须是命名类型或者指向命名类型的指针)即可;这种结构体成员称作匿名成员
。
虽然这样可以方面我们访问结构体的成员变量但是,结构体字面量就没办法了,必须按部就班。
Go中,组合是面向对象编程方式的核心。
func squares() func() int {
var x int
return func() int {
x++
return x*x
}
}
func main(){
f := square()
fmt.Println(f()) // 1
fmt.Println(f()) // 4
fmt.Println(f()) // 9
fmt.Println(f()) // 16
}
var prereqs = map[string][]string{
"algorithms": {"data structures"},
"calculus": {"linear algebra"},
"compilers": {"data structures", "formal languages", "computer organization"},
"data structures": {"discrete math"},
"discrete math": {"intro to programming"},
"databases": {"data structures"},
"formal languages": {"discrete math"},
"networks": {"operating systems"},
"operating systems": {"data structures", "computer organization"},
"programming language": {"data structures", "computer organization"},
}
func main() {
for i, v := range topoSort1(prereqs) {
fmt.Println(i+1, v)
}
}
func topoSort1(m map[string][]string) []string {
var order []string
seen := make(map[string]bool)
var visitAll func(items []string)
visitAll = func(items []string) {
for _, item := range items {
if !seen[item] {
seen[item] = true
visitAll(m[item])
order = append(order, item)
}
}
}
var keys []string
for key, _ := range m {
keys = append(keys, key)
}
sort.Strings(keys)
visitAll(keys)
return order
}
捕获迭代变量可能会造成意想不到的结果,因为捕获的迭代变量将在下一场迭代更新,因此之前捕获的迭代变量内容已经不是原来的东西了,如果需要当时特定的状态,可以在每次迭代中创建一个变量并用迭代变量赋予初始值,这样确保捕获的变量不会发送改变。或者,可以在声明匿名函数的时候直接指明参数,调用的时候传递参数即可,但只适用值传递的类型。
var rmdirs []func()
for _,d := range tempDirs(){
dir := d //这个是必需的,不能在匿名函数里面捕获d变量。
os.MkdirAll(dir,0755)
rmdirs = append(rmdirs, func(){
os.RemoveAll(dir)
})
}
func sum(vals ...int) int {
total := 0
for _,val := range vals {
total += val
}
return total
}
func main(){
fmt.Println(sum())
fmt.Println(sum(1,2,3,4))
fmt.Println([]int{1,2,3,4}...)
}
延迟函数不限制数量,以倒序执行。
func trace(msg string) func() {
start := time.Now()
log.Printf("Enter: %s",msg)
return func() {
log.Printf("Exit: %s (%s)",msg,time.Since(start))
}
}
func main() {
defer trace("Main")()
time.Sleep(10*time.Second)
}
运行时错误,比如数组越界访问,解引用空指针等,都会发生宕机
宕机
发生时,程序执行终止,goroutine延迟函数会执行,程序异常退出留下一条日志消息。
每一个goroutine都会在宕机的时候显示一个函数调用的栈跟踪
消息。
条件
- 延迟函数内部
- 宕机与延迟函数在同一个函数内
func main(){
defer tryToRecover()
panicMe()
fmt.Println("It should not be run")
}
func tryToRecover() {
r := recover()
if r != nil {
fmt.Println(r)
fmt.Println("Try to recover")
var stack [4096]byte
runtime.Stack(stack[:], false)
fmt.Printf("%s\n", stack)
}else{
fmt.Println("No neeed to recover")
fmt.Println(runtime.Caller(1))
}
}
func panicMe() {
defer tryToRecover()
panic("Panic test")
}
有些情况下是没有恢复动作的,比如内存耗尽。
我们可以将一个特定类型变量的方法赋值给一个变量,这个变量叫做方法变量
(有点类使用匿名函数捕获变量)。
但我们也可以将一个特定类型的方法赋值给一个变量,这叫做方法表达式
。
type Point struct{
X,Y float64
}
func (p *Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X,q.Y-p.Y)
}
p := Point{1,2}
q := Point{4,6}
distanceFromP := p.Distance //方法变量,捕获变量p
distance := Point.Distance //方法表达式,调用的时候需要传入变量p
distanceFromP(q)
distance(p,q)
接口类型是对其它类型行为的概括与抽象。
接口时一种抽象类型
,(隐藏隐藏了数据的布局或者内部结构)仅仅提供一些方法。
//示例,使用接口实现单词统计,Byte计数
type TextCounter struct {
Words int
Blanks int
Symbols int
Length int
}
func (t *TextCounter) Write(p []byte) (int, error) {
s := bufio.NewScanner(bytes.NewBuffer(p))
s.Split(bufio.ScanWords)
w := 0
for s.Scan() {
w++
}
t.Words += w
return len(p), nil
}
func (t TextCounter) String() string {
return fmt.Sprintf("Words %d", t.Words)
}
type CountingWriter struct {
bytesCounter int64
r io.Writer
}
func (c *CountingWriter) Write(p []byte) (int, error) {
rlen, err := c.r.Write(p)
if err == nil {
c.bytesCounter += int64(rlen)
}
return rlen, err
}
func NewCounterWriter(r io.Writer) (io.Writer, *int64) {
c := &CountingWriter{
r: r,
}
return c, &c.bytesCounter
}
func main() {
var counter TextCounter
in := bufio.NewScanner(os.Stdin)
w,pLen := NewCounterWriter(&counter)
for in.Scan() {
fmt.Fprintf(w,in.Text())
}
fmt.Println(counter)
fmt.Println("CountingWriter",*pLen)
}
一个接口类型定义了一套方法,如果一个具体类型要实现改接口,那么必须实现改接口类型定义中的所有方法。
一个io.Writer接口抽象了所有可以写入字节的类型,包括文件,内存缓冲区,网络连接等等。
如果一个类型实现了一个接口要求的所有方法,那么这个类型就实现了这个接口,就可以给赋值给接口。
//声明,*Bytes.Buffer实现了io.Writer接口
var _ io.Writer = (*Bytes.Buffer)(nil)
type Celsius float64
type Fahrenheit float64
type Kelvin float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BoilingC Celsius = 100
)
const (
celsiusSymbol1 string = "℃"
celsiusSymbol2 string = "C"
fahrenheitSymbol1 string = "℉"
fahrenheitSymbol2 string = "F"
kelvinSymbol string = "K"
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
func KToC(k Kelvin) Celsius { return Celsius(k) - AbsoluteZeroC }
func CToK(c Celsius) Kelvin { return Kelvin(c + AbsoluteZeroC) }
func (c Celsius) String() string { return fmt.Sprintf("%g%s", c, celsiusSymbol1) }
func (f Fahrenheit) String() string { return fmt.Sprintf("%g%s", f, fahrenheitSymbol1) }
func (k Kelvin) String() string {
if k >= 0 {
return fmt.Sprintf("%g%s", k, kelvinSymbol)
}
return fmt.Sprintf("Kelvin %g: out of range", k)
}
下面是使用flag包来,来实现自定义解析内容
type celsiusFlag struct {
Celsius
}
func (f *celsiusFlag) Set(s string) error {
var unit string
var value float64
fmt.Sscan(s, "%f%s", &value, &unit)
switch unit {
case celsiusSymbol1, celsiusSymbol2:
f.Celsius = Celsius(value)
case fahrenheitSymbol1, fahrenheitSymbol2:
f.Celsius = FToC(Fahrenheit(value))
case kelvinSymbol:
f.Celsius = KToC(Kelvin(value))
default:
return fmt.Errorf("invalid temperature %q", value)
}
return nil
}
func CelsiusFlag(name string, valude Celsius, usage string) *Celsius {
f := celsiusFlag{Celsius: valude}
flag.CommandLine.Var(&f, name, usage)
return &f.Celsius
}
一个接口类型的值(接口值
)其实由两部分:一个具体类型
和改类型的一个值
二者称为接口的动态类型
和动态值
Go语言是静态类型语言,类型仅仅是一个编译时的概念,所有类型不是一个值。在我们的概念模型中,用类型描述符
来提供每个类型的具体信息,比如它的名字和方法。
对于一个接口值,类型部分就用对应的类型描述符来表述。
接口的零值,就是把它的动态类型和值都设置为nil
。调用一个nil接口的任何方法都会崩溃。
var w io.Writer
w = os.Stdout
w = os.Stdout
,这句设置接口w的类型
为*os.File
,值
为一个指向os.File类型的指针
一般来说,在编译时我们我发知道一个接口值的动态类型会时什么,所有通过接口来做调用必然需要使用动态分发
。编译器必须生产一段代码来从类型描述符拿到名为Write
的方法地址,再间接调用该方法地址。调用的接收者就是接口值的动态值,即os.Stdout
,所以实际效果与直接调用等价。
接口值可以使用==
和!=
操作符来做比较。如果两个接口值都时nil或者二者的动态类型完全一致且二者动态值相等(使用动态类型的==
来做比较),那么两个接口值相等。
所有接口值可以做map
的键,和switch
的操作数。
**!**如果在比较两个接口值时,如果两个接口值的动态类型一致,但对应的动态值时不可比较的(比如slice),那么这个比较会以崩溃的方式失败:
var x interface{} = []int{1,2,3,4}\
fmt.Println(x == x) //失败,[]int 类型无法比较
我们必须小心崩溃的可能性,仅在能确认接口值包含的动态值可以比较时,才比较接口值。
!注意含有空指针的非空接口,做!=nil
会是true
结果
一个原地排序算法需要知道三个信息:序列长度,比较两个元素的含义以及如何交换两个元素
package sort
type Interface interface{
Len() int
Less(i,j int) bool //i,j 是序列元素的下标
Swap(i,j int)
}
学习Go语言的入门,这个必须要了解的,不然怎么分分钟撸一个服务器出来?
我们先看一下 Handler 接口的定义
package http
type Handler interface{
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
我们写一个简单的示例来,通过构建database
来实现http.Handler
接口,从而实现我们简易的服务器。
const (
ListenAddress string = "localhost:8080"
)
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("%.2f$", d) }
type database map[string]dollars
func (d database) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/list":
fmt.Fprintf(w, "<h1>List</h1>\n")
fmt.Fprintf(w,"<ul>")
for k, v := range db {
fmt.Fprintf(w, "<li>%s:%s</li>", k, v)
}
fmt.Fprintf(w,"</ul>")
case "/price":
item := r.URL.Query().Get("item")
price, ok := db[item]
if !ok {
fmt.Fprintf(w, "Item %s not found", item)
return
}
fmt.Fprintf(w,"Item %s is %s", item, price)
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Page not found %s", r.URL)
}
}
var db = database{
"sock": 0.2,
"shoes": 10,
}
func main() {
log.Fatal(http.ListenAndServe(ListenAddress, db))
}
我们虽然可以继续给ServerHTTP添加功能,但是对于一个真时的应用,应当把每部分的逻辑分到独立的函数或方法。
这样如果要处理类似,/image/*.png
的请求时候就方便的很多了。
因此,net/http
包提供了一个请求多发转发器ServeMux
,用来简化URL和处理程序之间的关联,一个ServeMux
把多个http.Handler
组合成单个http.Handler
。
若对于一个更复杂的应用,多个ServeMux
会组合起来,用来处理更复杂的分发需求。
下面是一个使用了ServeMux
的示例,写得不是很好,主要是html.template
中的template如何写比较陌生。
const (
ListenAddress string = "localhost:8080"
)
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("%.2f$", d) }
type database map[string]dollars
var db = database{
"sock": 0.2,
"shoes": 10,
}
var listPage = template.Must(template.New("listPage").Parse(`
<html>
<head><title>List</title></head>
<body>
<h1>List</h1>
<table>
<tr>
<th>Item</th><th>Price</th><th>Operation</th>
</tr>
{{range .Items}}
<tr>
<th>{{.Item}}</th><th>{{.Price}}</th><th><a href="#">Buy</a></th>
</tr>
{{end}}
</table>
</body>
</html>
`))
func (d database) list(w http.ResponseWriter, r *http.Request) {
var page struct{
Items []struct{
Item string
Price string
}
}
for k,v := range d {
page.Items = append(page.Items, struct {
Item string
Price string
}{Item: k, Price: v.String()})
}
listPage.Execute(w,page)
}
func (d database) price(w http.ResponseWriter, r *http.Request) { /* */}
func (d database) add(w http.ResponseWriter, r *http.Request) {/* */}
func main() {
mx := http.NewServeMux()
mx.Handle("/", http.HandlerFunc(db.list))
mx.HandleFunc("/price", db.price)
mx.HandleFunc("/add", db.add)
log.Fatal(http.ListenAndServe(ListenAddress, mx))
}
这里面还会涉及到并发的问题,就是如何保护变量不会被在并发中被错误的写入。 里面的水很深啊。
这恐怕是Go语言中使用最多的接口类型了吧? 写一行代码处理一个错误
//哈哈哈,这是我写得最多的代码
//动不动就来一句,爽歪歪啊
if err != nil {
fmt.Println(err)
}
package errors
func New(text string) error { return &errorString{text} }
type errorString struct { text string}
func (e *errorString) String() string { return e.text }
这是一个完整的errors
,竟然只有4行代码,有点神奇,我本以为package
都会由很多代码的,看了有些简小精悍的包,不需要很多的代码,但确实很是实用。
满足error
接口的是*errorString
指针,这样是为了每次分配的error
实例都互不相等。
fmt.Println(errors.New("EOF") == errors.New("EOF")) //false
见github
仓库
类型断言
是作用在接口值上面的操作(不然干嘛要断言?),写出来类似于x.(T)
类型断言
会检查作为操作数的动态类型是否满足指定的类型断言,若成功则结果就是x
的动态值,类型就是T
类型;若失败,则崩溃。
类型断言
就是用来从它的操作数中把具体类型的值提取出来的操作。
var w io.Writer
w = os.Stdout
f := w.(*os.File) //success, f is os.Stdout
c := w.(*bytes.Buffer) //panic
如果断言类型T
是一个接口类型,那么类型断言检查x的动态类型是否满足T
。如果检查成功,动态值并没有提取出来,结果仍然是一个接口值,接口值的类型和值部分也没有更变,只是结果的类型接口为T
。换句话说,类型断言是一个接口值表达式,从一个接口类型变为拥有另一套方法接口的类型(通常方法数量变多),但保留了接口值中的动态类型和动态值部分。
如果类型断言出现在需要两个结果的赋值表达式,那么类型断言就不会失败,还是多返回一个bool
值指明断言是否成功。
var w io.Writer = os.Stdout
f,ok := w.(*os.File) //success,ok , f is os.Stdout
b,ok := w.(*bytes.Buffer) //panic ,b is nil
func writeString(w io.Writer,s string)(n int, err error){
type stringWriter interface{
WriteString(string) (n int,err error)
}
if sw,ok := w.(stringWriter); ok {
return sw.WriteString(s) //避免了内存复制
}
return w.Write([]byte(s)) //分配了临时内存
}
一个具体类型是否满足stringWriter
接口仅仅由它拥有的方法来决定,而不是这个类型与一个接口类型之间的关系申明。
这依赖一个假定,即WriteString(s)
与Write([]byte(s))
等效。
接口有两种不同的风格。第一种是突出满足这个接口的具体类型之间的相似性,即强调了方法,而不是具体类型,比如,io.Writer。
第二种风格,是利用接口值能够容纳各种具体类型的能力,它把接口作为这些类型的联合(union)来使用,强调满足这个接口的具体类型,而不是这个接口的方法(经常是没有方法的)。
我们把这种风格接口的使用方式称为`可识别联合(discriminated union)
func sqlQuote(x interface{}){
if x == nil {
return "NULL"
}else if _,ok := x.(int); ok {
return fmt.Sprintf("%d",x)
}else if b,ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
}else if s,ok := x.(string); ok {
return sqlQuoteString(s)
}else {
panic(fmt.Sprintf("unexpected type %T : %v",x,x))
}
}
switch x.(type) {
case nil:
case int,uint:
case bool:
case string:
default:
}
上面代码中switch x.(type)
写法简化了一连串的if-else
语句。
拓展语句switch x := x.(type)
,这个新的x
就是断言后的类型。
分支类型不支持使用fallthrough
注意:switch安装顺序(从上到下)执行,遇到符合的case
就进去,不会考虑后面的case
func sqlQuote(x interface{}) string {
switch x := x.(type) {
case nil:
return "NULL"
case int,uint:
return fmt.Sprintf("%d",x)
case bool:
if x {
return "TRUE"
}
return "FALSE"
case string:
return sqlQuoteString(x)
default:
panic(fmt.Sprintf("unexpected type %T: %v",x,x))
}
}
func main() {
dec := xml.NewDecoder(os.Stdin)
var stack []string
for {
tok, err := dec.Token()
if err == io.EOF {
break
} else if err != nil {
fmt.Fprintln(os.Stderr, "xmlselect: %v\n", err)
os.Exit(1)
}
switch tok := tok.(type) {
case xml.StartElement:
stack = append(stack, tok.Name.Local)
case xml.EndElement:
stack = stack[:len(stack)-1]
case xml.CharData:
if containsAll(stack, os.Args[1:]) {
fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok)
}
}
}
}
func containsAll(x, y []string) bool {
for len(y) <= len(x) {
if len(y) == 0 {
return true
}
if x[0] == y[0] {
y = y[1:]
}
x = x[1:]
}
return false
}
在编译时不知道类型,可更新变量、运行时查看值、调用方法以及直接对它们的布局进行操作,这种机制称为反射
。
这个还有好多内容没有补上去啊,😔我太懒了哎