Go接口
- 接口嵌入接口,保持深度在0或1为最佳。
- 接口中直接定义的方法数量10个之内最佳
参考
Rune
- Java的char类型是
UTF-16
的code unit
,也就是两个字节,字符串是UTF-16 code unit
的序列,因此每个字符都是定长的,要想获得某个位置字符,很容易计算出它的字节在字符串中的位置。 - Go语言使用
UTF-8
作为字符串的内部编码,因此对于大部分字符串都是ascii字符的情况下,占用的内存空间就会大大减少,但是带来的问题是,从字符串的字节slice中查找第n个字符比较麻烦,因为不能直接的计算出来。Go中用rune表示字符。 - rune单引号定义,它包含单一的一个字符。
var r rune = '文'
fmt.Printf("%#U\n", r)
//通过range可以遍历一个字符串中所有的rune:
const nihongo = "one world世界大同"
for index, runeValue := range nihongo {
fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}
//因为字符串是以UTF-8编码的,通过输出可以看到ascii字母只用一个字节,而这几个中文汉字每个汉字用了3个字节。
//要想获得字符串中包含几个字符(rune),下面的方法是不对的,它返回的是字符处内部的字slice的长度((9 + 4*3 =21):
const str = "one world世界大同"
fmt.Println(len(str))
常量变量
- 用变量赋值给常量是不允许的
var vs = "hello xiaoxiao"
const s = vs //错误!!
- 声明常量时,可以不指定类型,在需要类型时,常量可以隐式转换为指定类型,前提是可以转换
var v1 int = i
var v2 float32 = i
var v3 complex64 = i
- 变量的声明不能用简写方法赋值给一个结构体的字段
type MyFile struct {
var F *os.File
}
func main() {
var mf MyFile
//错误!!!
mf.F, err := os.Open("dive-into-go.pdf")
fmt.Println(f, err)
}
静态类型和动态类型
- 静态类型:变量声明时的声明类型,在变量声明,new方法创建对象时或结构体的元素的类型定义,参数类型等。
- 接口类型的变量还有一个动态类型,它是运行时赋值给这个变量的具体的值的类型。
var x interface{} // x 为零值 nil,静态类型为 interface{}
var v *T // v 为零值 nil, 静态类型为 *T
x = 42 // x 的值为 42,动态类型为int, 静态类型为interface{}
x = v // x 的值为 (*T)(nil), 动态类型为 *T, 静态类型为 *T
命名类型和未命名类型
- 未命名类型的一个重要属性就是用同样类型的未命名类型声明的变量拥有相同的类型,而两个不同的命名类型,即使底层的类型相同,它们的类型也是不同的。
// x1 x2 类型相同
// y y2 类型不同
var x struct{ I int }
type Foo struct{ I int }
var y Foo
var x2 struct{ I int }
type Bar struct{ I int }
var y2 Bar
- 命名类型可以定义自己的函数,未命名类型不行
指针
- 我们不能直接移动指针,但是我们可以通过曲折的方法操作:·
x := [...]int{1, 2, 3, 4, 5}
p := &x[0]
//p = p + 1
index2Pointer := unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(x[0]))
p = (*int)(index2Pointer) //x[1]
fmt.Printf("%d\n", *p) //2
- 这个例子中我们成功地将指针移动到第二个索引处,虽然它和 &x[1]的功能是一样的,但却表明我们可以根据偏移量计算指针。
- 这种方法更多的应用到struct的字段值的读取中,一些序列化的库通过它来读取struct字段的值。
- unsafe.Pointer定义如下:
type ArbitraryType int // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType
Pointer代表指向任意类型的指针,它有四个独有的操作:
- 任意类型的指针可以被转换成一个 Pointer对象.
- 相反一个Pointer也可以转换成任意类型的指针.
- 一个uintptr可以转换成一个Pointer.
- 相反一个Pointer可以转换成uintptr.
switch
- switch 表达式的前面可以有简单的表达式,它在switch表达式计算之前执行。
switch x := f(); x>0 {
case true:
.....
}
select
- select语句从一组send操作和receive操作中选择一个执行。
- 它类似switch但是只用来对channel进行操作。
- channel操作数如果是一个表达式,那么表达式只会被计算一次
- 如果有多个case可以被执行,只有一个case会被选择执行。选择算法是伪随机算法。
- 如果没有case可以执行,并且有一个default case,则这个default会被选择执行, 如果没有default, select会被阻塞直到有一个case可以被执行。
- select一次只有一个case会执行,所以很多情况下我们把它放入到一个 for循环中。
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
print("received ", i1, " from c1\n")
case c2 <- i2:
print("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
print("received ", i3, " from c3\n")
} else {
print("c3 is closed\n")
}
case a[f()] = <-c4:
// same as:
// case t := <-c4
// a[f()] = t
default:
print("no communication\n")
}
for { // send random sequence of bits to c
select {
case c <- 0: // note: no statement, no fallthrough, no folding of cases
case c <- 1:
}
}
select {} // block forever
- select 语句也经常加入超时的case:
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
//执行超时case
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
//执行C2
c2 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c2 <- "result 2"
}()
select {
case res := <-c2:
fmt.Println(res)
case <-time.After(time.Second * 3):
fmt.Println("timeout 2")
}