Skip to content

Instantly share code, notes, and snippets.

@dreamer2q
Last active March 29, 2020 08:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dreamer2q/2aec832d82c857046d7d4a96e8eb3dc5 to your computer and use it in GitHub Desktop.
Save dreamer2q/2aec832d82c857046d7d4a96e8eb3dc5 to your computer and use it in GitHub Desktop.
go_summary

前言

这里总结我学习==Go语言==的知识点, 可能比较凌乱, :smile: 因为我学习的时候也是很凌乱的。

Go语言对其它大众语言做了减法,这使得初学者可以很快的上手Go语言。 快速上手的好处就是,初学者很快用Go语言做出一些有用的东西,这得益于Go语言丰富的包支持。 此外,Go语言的一大亮点就是语法简单,易懂。 我非常喜欢这种less is more观念,尽管Go没有明确的面向对象概念,但是Go使用结构体绑定方法来实现面向对象。

这样的好处就是简洁明了,我觉得C++的面向对象就是语法糖,Go语言直接在语法层面展示面向对象的实现方法,减少了学习的负担。 所有Go语言的结构体真是是Go的一大法宝,但除此之外Go更优秀的地方在于它的goroutine。 一个go关键字就很好的诠释了Go语言,可以说,不了解goroutine相当于没有学过Go

本笔记所属内容大多数来源<<Go程序设计实践>>这本书,目录安排也是参考此书。

程序结构

2.3 变量

2.3.4 变量的生命周期

Go由于自动垃圾回收,变量的回收取决于变量是否可以被访问到。 变量存在的位置(堆/栈),不是由变量申明的位置决定(C语言)。

//变量逃逸
var global *int
func f(){
    var x int
    x = 1
    global = &x //x变量逃逸,使用堆空间
}

2.4 赋值

2.4.1 多重赋值

go函数支持多个返回值,变量支持多重赋值。

x,y = y,x //多重赋值,右边表达式先被推演
func gcd(x,y int) int {     //求两个整数的最大公约数
    for y != 0 {
        x,y = y, x%y
    }
    return x
}

表达式较复杂时不建议使用多重赋值

2.4.2 可赋值性

赋值要求:变量和值的类型一样

在C语言中,赋值比较灵活,编译器会做响应的转换,例如将float赋值给double,但是反过来就会存在问题。

Go语言中就比较严格,对于var offset int = x == y就是非法的,因为boolint类型不一样。 此外,Go不支持C语言的三元表达式condition ? a : b;

2.5 类型声明

类型声明即定义一个新的类型(可以理解为命名类型),用于区分使用底层类型而造成的混淆,或者隐藏具体的实现?

任何情况下,运行时的转换不会失败。(这句话没理解)

底层类型决定了它的结构,表达方式操作集合

2.6 包和文件

一个包 => 命名空间

补充 fmt 格式化输出

占位符

  • 一般占位符
符号 说明
%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语言~区别)

3.1 整数

3.2 浮点数

3.3 复数

3.4 布尔值

布尔值无法隐性转换为数值,反过来也不行。

3.5 字符串 !

字符串是不可变的字节序列,这点要与C语言区分。

字符串可以包含任意数据,包括0值字节,习惯上,文本字符被解读为UTF-8编码的的Unicode码点序列。

内置len函数返回字符串的字节数,为文字符号数目,因为一个文字符号可能占多个字节。

字符串值不可改变,意味着两个字符串能安全共同使用同一段底层内存,使得复制任意长度的字符串开销低廉。

3.5.1 字符串字面量

字符串字面量(string literal),形式上是带双引号的字节序列。

转义字符以\开始,常见的转义字符如下

  • \a
  • \b
  • \f 换页符
  • \n 换行符(跳到下一行的同一位置)
  • \r 回车符(返回首行)
  • \t
  • \v
  • \'
  • \"
  • \\
  • \xHH 十六进制
  • \ooo 八进制

原生字符串,使用反引号``,此时转义字符无效,可以展开写成多行,例如正则表达,HTML模板就需要这样的写法。

单引号括起来的字符表示一个rune类型的字符。

3.5.2 Unicode !

需要弄清缘由,程序员不懂不好意思说学过编程。

3.5.3 UTF-8

UTF-8 以字节(一个字节8位)为单位对Unicode码点进行变成编码。

UTF-8 由Go的两位创建者Ken ThompsonRob 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(�)替换它。

3.5.4 字符串和字节slice

3.5.5 字符串和数字的相互转换

3.6 常量

3.6.1 常量生成器 iota

3.6.2 无类型常量

4 复合数据类型

4.1 数组

数组的长度是固定的,同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语言)

4.2 slice

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。

4.2.1 append 函数

添加元素到slice后面,由于append函数添加函数的时候,底层数据可能发送了改变,所有一般将append返回的值赋值给原slice变量,确保slice可用。

4.2.2 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]

4.3 map

map实现-

map类型为map[K]V,其中K是键类型,要求必须可以使用==比较。V是值类型,没有任何限制。

delete可以从map中根据键删除一个元素,键不存在也无妨。

无法获取一个map元素的地址。(原因是后面的操作可能会使地址失效)

使用for k,v := range mapXX迭代循序是不固定的,这是feature

Go没有提供集合类型,但是map的key是唯一的。

4.4 结构体

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
}

4.4.1 结构体字面量

结构体类型的值可以通过结构体字面量来设置。

4.4.3 结构体嵌套和匿名成员

不同寻常的结构体嵌套机制(继承吗?)

Go允许我们定义不带名称的结构体成员,只需用指定类型(类型必须是命名类型或者指向命名类型的指针)即可;这种结构体成员称作匿名成员

虽然这样可以方面我们访问结构体的成员变量但是,结构体字面量就没办法了,必须按部就班。

Go中,组合是面向对象编程方式的核心。

4.5 JSON

4.6 模板

函数

5.6 匿名函数

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
}

WARN: 捕获迭代变量

捕获迭代变量可能会造成意想不到的结果,因为捕获的迭代变量将在下一场迭代更新,因此之前捕获的迭代变量内容已经不是原来的东西了,如果需要当时特定的状态,可以在每次迭代中创建一个变量并用迭代变量赋予初始值,这样确保捕获的变量不会发送改变。或者,可以在声明匿名函数的时候直接指明参数,调用的时候传递参数即可,但只适用值传递的类型。

var rmdirs []func()
for _,d := range tempDirs(){
    dir := d    //这个是必需的,不能在匿名函数里面捕获d变量。
    os.MkdirAll(dir,0755)
    rmdirs = append(rmdirs, func(){
        os.RemoveAll(dir)
    })
}

5.7 变长函数

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}...)
}

5.8 延迟函数 defer

延迟函数不限制数量,以倒序执行。

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)
}

5.9 宕机

运行时错误,比如数组越界访问,解引用空指针等,都会发生宕机

宕机发生时,程序执行终止,goroutine延迟函数会执行,程序异常退出留下一条日志消息。 每一个goroutine都会在宕机的时候显示一个函数调用的栈跟踪消息。

5.10 恢复

条件

  • 延迟函数内部
  • 宕机与延迟函数在同一个函数内
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")
}

有些情况下是没有恢复动作的,比如内存耗尽。

方法

6.4 方法变量

我们可以将一个特定类型变量的方法赋值给一个变量,这个变量叫做方法变量(有点类使用匿名函数捕获变量)。 但我们也可以将一个特定类型的方法赋值给一个变量,这叫做方法表达式

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)

接口

接口类型是对其它类型行为的概括与抽象。

7.1 接口即约定

接口时一种抽象类型,(隐藏隐藏了数据的布局或者内部结构)仅仅提供一些方法。

//示例,使用接口实现单词统计,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)
}

7.2 接口类型

一个接口类型定义了一套方法,如果一个具体类型要实现改接口,那么必须实现改接口类型定义中的所有方法。

一个io.Writer接口抽象了所有可以写入字节的类型,包括文件,内存缓冲区,网络连接等等。

7.3 实现接口

如果一个类型实现了一个接口要求的所有方法,那么这个类型就实现了这个接口,就可以给赋值给接口。

//声明,*Bytes.Buffer实现了io.Writer接口
var _ io.Writer = (*Bytes.Buffer)(nil)

7.4 使用flag.Value来解析参数

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
}

7.5 接口值

一个接口类型的值(接口值)其实由两部分:一个具体类型和改类型的一个值 二者称为接口的动态类型动态值

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结果

7.6 使用sort.Interface 来排序

一个原地排序算法需要知道三个信息:序列长度,比较两个元素的含义以及如何交换两个元素

package sort

type Interface interface{
  Len() int
  Less(i,j int) bool //i,j 是序列元素的下标
  Swap(i,j int)
}

7.7 http.Handler 接口

学习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))
}

这里面还会涉及到并发的问题,就是如何保护变量不会被在并发中被错误的写入。 里面的水很深啊。

7.8 error 接口

这恐怕是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

7.9 表达式求值器

github仓库

7.10 类型断言

类型断言是作用在接口值上面的操作(不然干嘛要断言?),写出来类似于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

7.11 使用类型断言来识别错误

7.12 通过接口类型断言来查询特性

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))等效。

7.13 类型分支

接口有两种不同的风格。第一种是突出满足这个接口的具体类型之间的相似性,即强调了方法,而不是具体类型,比如,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))
	}
}

7.14 基于标记的XML解析

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
}

反射

在编译时不知道类型,可更新变量、运行时查看值、调用方法以及直接对它们的布局进行操作,这种机制称为反射

12.2 reflect.Type 和 reflect.Value

@dreamer2q
Copy link
Author

这个还有好多内容没有补上去啊,😔我太懒了哎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment