- 首页 > it技术 > >
- defer并不延迟函数的参数本身的调用
func trace(s string) string {
fmt.Println("entering:", s)
return s
}func un(s string) {
fmt.Println("leaving:", s)
}func a() {
defer un(trace("a"))
fmt.Println("in a")
}func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}func main() {
b()
}
返回结果为
entering: b
in b
entering: a
in a
leaving: a
leaving: b
这一特性可以用来设计系统状态转移机制。
- new和make以及声明的关系。
首先要明晰值类型和引用类型的区别。go中值类型包括基本数据类型,数组以及结构体;引用类型包括指针,slice,map以及channel几种。值类型变量存储在栈上,变量的内容就是值;引用类型变量存储在堆上,变量的内容为堆区内存的首地址。在利用var声明变量时,值类型会默认分配对应的内存空间以及默认值,引用类型并没有在堆中分配内存,因此变量内容为0x0,此时需要用new或者make分配空间。下面的分析都只针对引用类型,因为值类型的new与声明相比几乎没什么区别。new和它在其他语言如C++中最大的区别是go的new不初始化内存,也就是说,对于new(T),为T类型实例分配了默认值的内存(引用类型默认值都是nil),并返回了T实例的首地址,所以new(T)返回的是*T类型的值,是个指向T实例的指针。make只适用于slice,map和channel,返回的是类型本身。看个例子吧,new大多数情况下没什么用处。 var p *[]int = new([]int)// allocates slice structure;
*p == nil;
rarely useful
var v[]int = make([]int, 100) // the slice v now refers to a new array of 100 ints// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)// Idiomatic:
v := make([]int, 100)
所以,对于值类型与引用类型,请分别使用普通定义方式和make方式,new不推荐。
- go中没有引用传递
go的函数传参都是值传递,如果想达到引用传递的效果需要传递指向变量的指针,但这也是值传递。参数传递时,会把参数值复制一份,由于引用类型的值是指向堆内存的地址,因此复制引用类型变量后,对该复制体的修改仍会影响到堆内存中的实际值,这其实就达到了引用传递的效果。下面是一个简单的测试,结构体属于值类型,但结构体中包含的slice属于引用类型,因此结构体作为参数会复制结构体,对复制结构体的slice修改还是能对原结构体的slice产生影响。 示例:
type SV struct {
s []int
v int
}
func testModify(target SV) {
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
target.s[1] = 2
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
target.v = 10
}
func main() {
ts := make([]int, 2)
ts[0] = 1
target := SV{s: ts}
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
testModify(target)
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
}
结果:
target:main.SV{s:[]int{1, 0}, v:0}, &target:0xc00007e020, target.s:0xc000088010, target.s:[]int{1, 0}
target:main.SV{s:[]int{1, 0}, v:0}, &target:0xc00007e0a0, target.s:0xc000088010, target.s:[]int{1, 0}
target:main.SV{s:[]int{1, 2}, v:0}, &target:0xc00007e0a0, target.s:0xc000088010, target.s:[]int{1, 2}
target:main.SV{s:[]int{1, 2}, v:0}, &target:0xc00007e020, target.s:0xc000088010, target.s:[]int{1, 2}
但是这里也有两个坑。首先,如果源结构体的slice长度设成0,容量设为2,然后在函数中对其进行append操作,如下所示:
type SV struct {
s []int
v int
}
func testModify(target SV) {
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
target.s = append(target.s, 3)
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
target.v = 10
}
func main() {
ts := make([]int, 0, 2)
target := SV{s: ts}
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
testModify(target)
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
}
target:main.SV{s:[]int{}, v:0}, &target:0xc00000c080, target.s:0xc00001c110, target.s:[]int{}
target:main.SV{s:[]int{}, v:0}, &target:0xc00000c100, target.s:0xc00001c110, target.s:[]int{}
target:main.SV{s:[]int{3}, v:0}, &target:0xc00000c100, target.s:0xc00001c110, target.s:[]int{3}
target:main.SV{s:[]int{}, v:0}, &target:0xc00000c080, target.s:0xc00001c110, target.s:[]int{}
可以看到这个结果最后原结构体显示并没有append成功,明明ts的容量是足够append的,这里推测是原来的slice的长度属性没有被改变?但是看地址显示复制的结构体中的slice都是一个地址,如果函数中改变了长度应该会影响原来的结果才对,暂时比较迷惑这个点,希望有大神解答。第二个坑就比较常见了,append超出原slice的容量,导致slice新开内存扩容然后复制原slice的值。
type SV struct {
s []int
v int
}
func testModify(target SV) {
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
target.s = append(target.s, 3)
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
target.v = 10
}
func main() {
ts := make([]int, 2)
target := SV{s: ts}
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
testModify(target)
fmt.Printf("target:%#v, &target:%p, target.s:%p, target.s:%#v\n", target, &target, target.s, target.s)
}
target:main.SV{s:[]int{0, 0}, v:0}, &target:0xc00000c080, target.s:0xc00001c110, target.s:[]int{0, 0}
target:main.SV{s:[]int{0, 0}, v:0}, &target:0xc00000c100, target.s:0xc00001c110, target.s:[]int{0, 0}
target:main.SV{s:[]int{0, 0, 3}, v:0}, &target:0xc00000c100, target.s:0xc000016440, target.s:[]int{0, 0, 3}
target:main.SV{s:[]int{0, 0}, v:0}, &target:0xc00000c080, target.s:0xc00001c110, target.s:[]int{0, 0}
这里可以看到append后的slice内存地址已经变了,所以必然不会影响原slice。
- GO中的数据格式转换,hex string to int
go的strconv包提供了足够的格式转换功能,但是对于hex string类型的负数值,直接使用ParseInt是会出问题的,见下面代码: func main() {
hexStr := "80000000"
num1, err := strconv.ParseInt(hexStr, 16, 32)
fmt.Printf("Original hexString:%s;
Direct parseInt:%d;
err:%s\n", hexStr, num1, err)
num2, err := strconv.ParseUint(hexStr, 16, 32)
realNum := int32(num2)
fmt.Printf("Original hexString:%s;
ParseUint then transform:%d;
err:%s\n", hexStr, realNum, err)
decStr := "-2147483648"
num3, err := strconv.ParseInt(decStr, 10, 32)
fmt.Printf("Original decString:%s;
Direct parseInt:%d;
err:%s\n", decStr, num3, err)
}
Original hexString:80000000;
Direct parseInt:2147483647;
err:strconv.ParseInt: parsing "80000000": value out of range
Original hexString:80000000;
ParseUint then transform:-2147483648;
err:%!s()
Original decString:-2147483649;
Direct parseInt:-2147483648;
err:%!s()
这里hexStr是负数,可以看到直接用ParseInt转是会溢出的,所以要先用ParseUint转uint32,然后显示转换成int32;而如果字符串是十进制形式的,就没这个问题,可以直接转。所以需要研究下ParseInt的源码,如下:
func ParseInt(s string, base int, bitSize int) (i int64, err error) {
const fnParseInt = "ParseInt" // Empty string bad.
if len(s) == 0 {
return 0, syntaxError(fnParseInt, s)
} // Pick off leading sign.
s0 := s
neg := false
if s[0] == '+' {
s = s[1:]
} else if s[0] == '-' {
neg = true
s = s[1:]
} // Convert unsigned and check range.
var un uint64
un, err = ParseUint(s, base, bitSize)
if err != nil && err.(*NumError).Err != ErrRange {
err.(*NumError).Func = fnParseInt
err.(*NumError).Num = s0
return 0, err
} if bitSize == 0 {
bitSize = int(IntSize)
} cutoff := uint64(1 << uint(bitSize-1))
if !neg && un >= cutoff {
return int64(cutoff - 1), rangeError(fnParseInt, s0)
}
if neg && un > cutoff {
return -int64(cutoff), rangeError(fnParseInt, s0)
}
n := int64(un)
if neg {
n = -n
}
return n, nil
}
可以看到,ParseInt在处理正负数时,判断标准仅仅是前面有没有带负号,而对于其他包括二进制、八进制、十六进制,首位最高位为1代表负数的形式,是不支持的。判断完符号,就调用ParseUint进行处理,得到结果会根据符号和bitSize来判断是否溢出,如果溢出就返回能表示的最大值以及错误信息。
- 格式化输出时,%#v的坑
%#v是完整按照golang的语法打印值,但是转义字符的问题,对于如下表示 type T struct {
a int
b float64
c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
&{7 -2.35 abcdef}
&{a:7 b:-2.35 c:abcdef}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
可以看到转义字符不会生效。
-
【Go语言细节摘录】
推荐阅读