Go 语言的那些坑
panic 中可以传任何值,不仅仅可以传 string
package main
import "fmt"
func main(){
defer func(){
if r := recover();r != nil{
fmt.Println(r)
}
}()
panic([]int{12312})
}
输出:
[12312]
数组切片 slice 的容量问题带来的 bug
请看下列代码:
import (
"fmt"
)
func main(){
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(slice, 50)
newSlice[1] += 1
fmt.Println(slice)
}
请问输出什么?
答案是:
[10 21]
如果稍作修改,将以上 newSlice 改为扩容三次,newSlice := append(append(append(slice, 50), 100), 150)如下:
import (
"fmt"
)
func main(){
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(append(append(slice, 50), 100), 150)
newSlice[1] += 1
fmt.Println(slice)
fmt.Println(newSlice)
}
输出为:
[10 20]
这特么是什么鬼?
这就要从 Golang 切片的扩容说起了;切片的扩容,就是当切片添加元素时,切片容量不够了,就会扩容,扩容的大小遵循下面的原则:(如果切片的容量小于 1024 个元素,那么扩容的时候 slice 的 cap 就翻番,乘以 2;一旦元素个数超过 1024 个元素,增长因子就变成 1.25,即每次增加原来容量的四分之一。)如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组(这就是产生 bug 的原因);如果扩容之后,超过了原数组的容量,那么,Go 就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。
建议尽量避免 bug 的产生。
for range 的那些坑
go只提供了一种循环方式,即for循环,在使用时可以像c那样使用,也可以通过for range方式遍历容器类型如数组、切片和映射。但是在使用for range时,如果使用不当,就会出现一些问题,导致程序运行行为不如预期。
情形 . 一
> for range + 闭包 代码如下: (闭包相关可以看我前面一篇博客)
package main
import (
"fmt"
"time"
)
func main() {
str := []string{"I","am","Sergey"}
for _,v := range str{
go func() {
fmt.Println(v)
}()
}
time.Sleep(3 * time.Second)
}
输出结果:
Sergey
Sergey
Sergey
程序设计的原意是遍历字符串切片并将其打印出来,可是输出结果却只输出了切片的最后一个元素.
原因是 : 闭包里引用了不作为参数传递进去的值,都是引用传递…也就是说,println(v) 其实是引用了v的地址然后解引用,将值打印出来..等到这个goroutine执行println(v)的时候,v所指向的值已经是”Sergey”
如果要正确打印,在定义闭包的时候要定义一个参数,将v作为参数传递进去.修改后代码如下:
package main
import (
"fmt"
"time"
)
func main() {
str := []string{"I","am","Sergey"}
for _,v := range str{
go func(v string) {
fmt.Println(v)
}(v)
}
time.Sleep(3 * time.Second)
}
此外,输出结果不一定是 i am Sergey,因为goroutine执行顺序有go runtime调度器决定
情形 . 二
> 操作map,原理和情形一类似 代码如下:
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index , value := range slice {
myMap[index] = &value
}
prtMap(myMap)
}
func prtMap(myMap map[int]*int) {
for key, value := range myMap {
fmt.Printf("map[%v]=%v\n", key, *value)
}
}
输出结果:
map[0]=3
map[1]=3
map[2]=3
map[3]=3
原因解释:但是由输出可以知道,映射的值都相同且都是3。其实可以猜测映射的值都是同一个地址,遍历到切片的最后一个元素3时,将3写入了该地址,所以导致映射所有值都相同。其实真实原因也是如此,因为for range创建了每个元素的副本,而不是直接返回每个元素的引用,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误,在迭代时,返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以值的地址总是相同的,导致结果不如预期。
做一下小小的修改就能得到我们的预期结果:
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index , value := range slice {
v := value
myMap[index] = &v
}
prtMap(myMap)
}
func prtMap(myMap map[int]*int) {
for key, value := range myMap {
fmt.Printf("map[%v]=%v\n", key, *value)
}
}
用 for range 来遍历数组或者 map 的时候,每次遍历仅执行 struct 值的拷贝, 被遍历的指针是不变的
在 Go 的 for…range 循环中,Go 始终使用值拷贝的方式代替被遍历的元素本身,
简单来说,就是 for…range 中那个 value,是一个值拷贝,而不是元素本身。这样一来,当我们期望用 & 获取元素的地址时,实际上只是取到了 value 这个临时变量的地址,而非 list 中真正被遍历到的某个元素的地址。而在整个 for…range 循环中,value 这个临时变量会被重复使用,所以,在上面的例子中,list2 被填充了三个相同的地址,其实都是临时变量 value 的地址。而在最后一次循环中,value 被赋值为{c}。因此,list2 输出的时候显示出了三个 &{c}。
for range 创建了每个元素的副本,而不是直接返回每个元素的引用,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误,在迭代时,返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以值的地址总是相同的,导致结果不如预期
import "fmt"
type student struct{
Name string
Age int
}
func main(){
var stus []student
stus = []student{
{Name:"one", Age: 18},
{Name:"two", Age: 19},
}
data := make(map[int]*student)
for i, v := range stus{
data[i] = &v // 应该改为:data[i] = &stus[i]
// 方式2
// elem:=v
// data[i] = &elem
}
for i, v := range data{
fmt.Printf("key=%d, value=%v \n", i,v)
}
}
所以,结果输出为:
key=0, value=&{two 19}
key=1, value=&{two 19}
不管运行顺序如何,当参数为函数的时候,要先计算参数的值
func main(){
a := 1
defer print(function(a))
a = 2;
}
func function(num int) int{
return num
}
func print(num int){
fmt.Println(num) // 1
}
注意是 struct 的函数,还是 * struct 的函数
import "fmt"
type people interface {
speak()
}
type student struct{
name string
age int
}
func (stu *student) speak(){
fmt.Println("I am a student, I am", stu.age)
}
func main(){
var p people
p = student{name:"RyuGou", age:12} // 应该改为 p = &student{name:"RyuGou", age:12}
p.speak()
}
输出:
cannot use student literal (type student) as type people in assignment:
student does not implement people (speak method has pointer receiver)
make(chan int) 和 make(chan int, 1)是不一样的
make(chan int) == make(chan int,0) 不带缓冲
chan 一旦被写入数据后,当前 goruntine 就会被阻塞,直到有人接收才可以(即 “ <- ch”),如果没人接收,它就会一直阻塞着。而如果 chan 带一个缓冲,就会把数据放到缓冲区中,直到缓冲区满了,才会阻塞
import "fmt"
func main(){
ch := make(chan int) // 改为 ch := make(chan int, 1) 就好了
ch <- 1 // 没有接收者就会阻塞
fmt.Println("success")
//[output]: fatal error: all goroutines are asleep - deadlock!
}
golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。
select 的代码形式和 switch 非常相似, 不过 select 的 case 里的操作语句只能是”IO 操作”(不仅仅是取值<-channel,赋值 channel<- 也可以), select 会一直等待等到某个 case 语句完成,也就是等到成功从 channel 中读到数据。 则 select 语句结束
import "fmt"
func main(){
ch := make(chan int, 1)
ch <- 1
select {
case msg :=<-ch:
fmt.Println(msg)
default:
fmt.Println("default")
}
fmt.Println("success")
}
输出:
1
success
default 可以判断 chan 是否已经满了
import "fmt"
func main(){
ch := make(chan int, 1)
select {
case msg :=<-ch:
fmt.Println(msg)
default:
fmt.Println("default")
}
fmt.Println("success")
}
输出:
default
success
此时因为 ch 中没有写入数据,为空,所以 case 不会读取成功。 则 select 执行 default 语句。
Go 语言中不存在未初始化的变量
并不是使用 new 就一定会在堆上分配内存
编译器会自动选择在栈上还是在堆上分配存储空间,但可能令人惊讶的是,这个选择并不是由用 var 还是 new 声明变量的方式决定的。
请看例子:
var global *int
func f() {
var x int x=1
global = &x
}
func g() {
y := new(int)
*y = 1
}
f()函数中的 x 就是在堆上分配内存,而 g()函数中的 y 就是分配在栈上。
init 函数在同一个文件中可以包含多个
在同一个包文件中,可以包含有多个 init 函数,多个 init 函数的执行顺序和定义顺序一致。
Golang 中没有“对象”
package main
import (
"fmt"
)
type test struct {
name string
}
func (t *test) getName(){
fmt.Println("hello world")
}
func main() {
var t *test
t = nil
t.getName()
}
能正常输出吗?会报错吗?
输出为:
hello world
可以正常输出。Go 本质上不是面向对象的语言,Go 中是不存在 object 的含义的,Go 语言书籍中的对象也和 Java、PHP 中的对象有区别,不是真正的”对象”,是 Go 中 struct 的实体。
调用 getName 方法,在 Go 中还可以转换,转换为:Type.method(t Type, arguments)
所以,以上代码 main 函数中还可以写成:
func main() {
(*test).getName(nil)
}
Go 中的指针 * 符号的含义
& 的意思大家都明白的,取地址,假如你想获得一个变量的地址,只需在变量前加上 & 即可。
例如:
a := 1
b := &a
现在,我拿到 a 的地址了,但是我想取得 a 指针指向的值,该如何操作呢?用 * 号,*b 即可。
- 的意思是对指针取值。
下面对 a 的值加一
a := 1
b := &a
*b++
和 & 可以相互抵消,同时注意,& 可以抵消,但是 & 不可以;所以 a 和 *&a 是一样的,和 &&*&a 也是一样的。
os.Args 获取命令行指令参数,应该从数组的 1 坐标开始
os.Args 的第一个元素,os.Args[0], 是命令本身的名字
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args[0])
}
以上代码,经过 go build 之后,打包成一个可执行文件 main,然后运行指令./main 123
输出:./main
map 引用不存在的 key,不报错
请问下面的例子输出什么,会报错吗?
import (
"fmt"
)
func main(){
newMap := make(map[string]int)
fmt.Println(newMap["a"])
}
答案是:
0
不报错。不同于 PHP,Golang 的 map 和 Java 的 HashMap 类似,Java 引用不存在的会返回 null,而 Golang 会返回初始值
map 使用 range 遍历顺序问题,并不是录入的顺序,而是随机顺序
import (
"fmt"
)
func main(){
newMap := make(map[int]int)
for i := 0; i < 10; i++{
newMap[i] = i
}
for key, value := range newMap{
fmt.Printf("key is %d, value is %d\n", key, value)
}
}
输出:
key is 1, value is 1
key is 3, value is 3
key is 5, value is 5
key is 7, value is 7
key is 9, value is 9
key is 0, value is 0
key is 2, value is 2
key is 4, value is 4
key is 6, value is 6
key is 8, value is 8
是杂乱无章的顺序。map 的遍历顺序不固定,这种设计是有意为之的,能为能防止程序依赖特定遍历顺序。
channel 作为函数参数传递,可以声明为只取 (<- chan) 或者只发送(chan <-)
一个函数在将 channel 作为一个类型的参数来声明的时候,可以将 channl 声明为只可以取值 (<- chan) 或者只可以发送值(chan <-),不特殊说明,则既可以取值,也可以发送值。
例如:只可以发送值
func setData(ch chan <- string){
//TODO
}
如果在以上函数中存在<-ch 则会编译不通过。
如下是只可以取值:
func setData(ch <- chan string){
//TODO
}
如果以上函数中存在 ch<- 则在编译期会报错
###Golang中函数被看做是值,但是函数值不能比较,也不能作为map的key
请问以下代码能编译通过吗?
import (
"fmt"
)
func main(){
array := make(map[int]func ()int) //函数作为map的值
array[func()int{ return 10}()] = func()int{
return 12
}
fmt.Println(array) // map[10:0x4e6d00]
}
答案:
可以正常编译通过。
稍作改动,改为如下的情况,还能编译通过吗?
import (
"fmt"
)
func main(){
array := make(map[func ()int]int) //编译报错
array[func()int{return 12}] = 10
fmt.Println(array)
}
答案:
不能编译通过。
在Go语言中,函数被看做是第一类值:(first-class values):函数和其他值一样,可以被赋值,可以传递给函数,可以从函数返回。也可以被当做是一种“函数类型”。例如:有函数func square(n int) int { return n * n },那么就可以赋值f := square,而且还可以fmt.Println(f(3))(将打印出“9”)。
Go语言函数有两点很特别:
函数值类型不能作为map的key
函数值之间不可以比较,函数值只可以和nil作比较,函数类型的零值是nil
匿名函数作用域陷阱
请看下列代码输出什么?
import (
"fmt"
)
func main(){
var msgs []func()
array := []string{
"1", "2", "3", "4",
}
for _, e := range array{
msgs = append(msgs, func(){
fmt.Println(e) //e作为临时变量,每次循环都被复用
})
}
for _, v := range msgs{
v()
}
}
答案:
4
4
4
4
在上述代码中,匿名函数中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。
想要输出1、2、3、4需要改为:
import (
"fmt"
)
func main(){
var msgs []func()
array := []string{
"1", "2", "3", "4",
}
for _, e := range array{
elem := e
msgs = append(msgs, func(){
fmt.Println(elem)
})
}
for _, v := range msgs{
v()
}
}
其实就加了条elem := e看似多余,其实不,这样一来,每次循环后每个匿名函数中保存的就都是当时局部变量elem的值,这样的局部变量定义了4个,每次循环生成一个。
数组[3]int 和 [4]int 不算同一个类型
请看一下代码,请问输出true还是false
import (
"fmt"
"reflect"
)
func main(){
arrayA := [...]int{1, 2, 3}
arrayB := [...]int{1, 2, 3, 4}
fmt.Println(reflect.TypeOf(arrayA) == reflect.TypeOf(arrayB))
}
答案是:
false
数组长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。
数组还可以指定一个索引和对应值的方式来初始化。
例如:
import (
"fmt"
)
func main(){
arrayA := [...]int{0:1, 2:1, 3:4}
fmt.Println(arrayA)
}
会输出:
[1 0 1 4]
有点像PHP数组的感觉,但是又不一样:arrayA的长度是多少呢?
import (
"fmt"
)
func main(){
arrayA := [...]int{0:1, 2:1, 3:4}
fmt.Println(len(arrayA))
}
答案是:
4
没错,定义了一个数组长度为4的数组,指定索引的数组长度和最后一个索引的数值相关,例如:r := [...]int{99:-1}就定义了一个含有100个元素的数组r,最后一个元素输出化为-1,其他的元素都是用0初始化。
不能对map中的某个元素进行取地址&操作
a := &ages["bob"] // compile error: cannot take address of map element
map中的元素不是一个变量,不能对map的元素进行取地址操作,禁止对map进行取地址操作的原因可能是map随着元素的增加map可能会重新分配内存空间,这样会导致原来的地址无效
当map为nil的时候,不能添加值
func main() {
var sampleMap map[string]int
sampleMap["test"] = 1
fmt.Println(sampleMap)
}
输出报错:
panic: assignment to entry in nil map
必须使用make或者将map初始化之后,才可以添加元素。
以上代码可以改为:
func main() {
var sampleMap map[string]int
sampleMap = map[string]int {
"test1":1,
}
sampleMap["test"] = 1
fmt.Println(sampleMap)
}
可以正确输出:
map[test1:1 test:1]
&dilbert.Position和(&dilbert).Position是不同的
&dilbert.Position相当于&(dilbert.Position)而非(&dilbert).Position
请看例子:
请问输出什么?
func main(){
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
var dilbert Employee
dilbert.Position = "123"
position := &dilbert.Position
fmt.Println(position)
}
输出:
0xc42006c220
输出的是内存地址
修改一下,把&dilbert.Position改为(&dilbert).Position
func main(){
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
var dilbert Employee
dilbert.Position = "123"
position := &dilbert.Position
fmt.Println(position)
}
输出:
123
Go语言中函数返回的是值的时候,不能赋值
请看下面例子:
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
func EmployeeByID(id int) Employee {
return Employee{ID:id}
}
func main(){
EmployeeByID(1).Salary = 0
}
请问能编译通过吗?
运行,输出报错:cannot assign to EmployeeByID(1).Salary
在本例子中,函数EmployeeById(id int)返回的是值类型的,它的取值EmployeeByID(1).Salary也是一个值类型;值类型是什么概念?值类型就是和赋值语句var a = 1或var a = hello world等号=右边的1、Hello world是一个概念,他是不能够被赋值的,只有变量能够被赋值。
修改程序如下:
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
func EmployeeByID(id int) Employee {
return Employee{ID:id}
}
func main(){
var a = EmployeeByID(1)
a.Salary = 0
}
这就可以编译通过了
在声明方法时,如果一个类型名称本身就是一个指针的话,不允许出现在方法的接收器中
请看下面的例子,请问会编译通过吗?
import (
"fmt"
)
type littleGirl struct{
Name string
Age int
}
type girl *littleGirl
func(this girl) changeName(name string){
this.Name = name
}
func main(){
littleGirl := girl{Name:"Rose", Age:1}
girl.changeName("yoyo")
fmt.Println(littleGirl)
}
答案:
不能编译通过,会提示“invalid receiver type girl(girl is a pointer type)”
Go语言中规定,只有类型(Type)和指向他们的指针(*Type)才是可能会出现在接收器声明里的两种接收器,为了避免歧义,明确规定,如果一个类型名本身就是一个指针的话,是不允许出现在接收器中的。
函数允许nil指针作为参数,也允许用nil作为方法的接收器
请看下面的例子,请问能编译通过吗?
import (
"fmt"
)
type littleGirl struct{
Name string
Age int
}
func(this littleGirl) changeName(name string){
fmt.Println(name)
}
func main(){
little := littleGirl{Name:"Rose", Age:1}
little = nil
little.changeName("yoyo")
fmt.Println(little)
}
答案:
不能编译通过,显示"cannot use nil as type littleGirl in assignment"
Go语言中,允许方法用nil指针作为其接收器,也允许函数将nil指针作为参数。而上述代码中的littleGirl不是指针类型,改为*littleGirl,然后变量little赋值为&littleGirl{Name:"Rose", Age:1}就可以编译通过了。
并且,nil对于对象来说是合法的零值的时候,比如map或者slice,也可以编译通过并正常运行。
Golang的时间格式化
不同于PHP的date("Y-m-d H:i:s", time()),Golang的格式化奇葩的很,不能使用诸如Y-m-d H:i:s的东西,而是使用2006-01-02 15:04:05这个时间的格式,请记住这个时间,据说这是Golang的诞生时间。
time := time.Now()
time.Format("20060102") //相当于Ymd
time.Format("2006-01-02")//相当于Y-m-d
time.Format("2006-01-02 15:04:05")//相当于Y-m-d H:i:s
time.Format("2006-01-02 00:00:00")//相当于Y-m-d 00:00:00
不要对Go并发函数的执行时机做任何假设
请看下列的列子:
import (
"fmt"
"runtime"
"time"
)
func main(){
names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
for _, name := range names{
go func(){
fmt.Println(name)
}()
}
runtime.GOMAXPROCS(1)
runtime.Gosched()
}
请问输出什么?
答案:
annei
annei
annei
annei
annei
为什么呢?是不是有点诧异?
输出的都是“annei”,而“annei”又是“names”的最后一个元素,那么也就是说程序打印出了最后一个元素的值,而name对于匿名函数来讲又是一个外部的值。因此,我们可以做一个推断:虽然每次循环都启用了一个协程,但是这些协程都是引用了外部的变量,当协程创建完毕,再执行打印动作的时候,name的值已经不知道变为啥了,因为主函数协程也在跑,大家并行,但是在此由于names数组长度太小,当协程创建完毕后,主函数循环早已结束,所以,打印出来的都是遍历的names最后的那一个元素“annei”。
如何证实以上的推断呢?
其实很简单,每次循环结束后,停顿一段时间,等待协程打印当前的name便可。
import (
"fmt"
"runtime"
"time"
)
func main(){
names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
for _, name := range names{
go func(){
fmt.Println(name)
}()
time.Sleep(time.Second)
}
runtime.GOMAXPROCS(1)
runtime.Gosched()
}
打印结果:
lily
yoyo
cersei
rose
annei
以上我们得出一个结论,不要对“go函数”的执行时机做任何的假设,除非你确实能做出让这种假设成为绝对事实的保证。
假设T类型的方法上接收器既有T类型的,又有*T指针类型的,那么就不可以在不能寻址的T值上调用*T接收器的方法
请看代码,试问能正常编译通过吗?
import (
"fmt"
)
type Lili struct{
Name string
}
func (Lili *Lili) fmtPointer(){
fmt.Println("poniter")
}
func (Lili Lili) fmtReference(){
fmt.Println("reference")
}
func main(){
li := Lili{}
li.fmtPointer()
}
答案:
能正常编译通过,并输出"poniter"
感觉有点诧异,请接着看以下的代码,试问能编译通过?
import (
"fmt"
)
type Lili struct{
Name string
}
func (Lili *Lili) fmtPointer(){
fmt.Println("poniter")
}
func (Lili Lili) fmtReference(){
fmt.Println("reference")
}
func main(){
Lili{}.fmtPointer()
}
答案:
不能编译通过。
“cannot call pointer method on Lili literal”
“cannot take the address of Lili literal”
是不是有点奇怪?这是为什么呢?其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是*Lili,因此可以调用*Lili的方法。
一个包含nil指针的接口不是nil接口
请看下列代码,试问返回什么
import (
"bytes"
"fmt"
"io"
)
const debug = true
func main(){
var buf *bytes.Buffer
if debug{
buf = new(bytes.Buffer)
}
f(buf)
}
func f(out io.Writer){
if out != nil{
fmt.Println("surprise!")
}
}
答案是输出:surprise。
ok,让我们吧debug开关关掉,及debug的值变为false。那么输出什么呢?是不是什么都不输出?
import (
"bytes"
"fmt"
"io"
)
const debug = false
func main(){
var buf *bytes.Buffer
if debug{
buf = new(bytes.Buffer)
}
f(buf)
}
func f(out io.Writer){
if out != nil{
fmt.Println("surprise!")
}
}
答案是:依然输出surprise。
这是为什么呢?
这就牵扯到一个概念了,是关于接口值的。概念上讲一个接口的值分为两部分:一部分是类型,一部分是类型对应的值,他们分别叫:动态类型和动态值。类型系统是针对编译型语言的,类型是编译期的概念,因此类型不是一个值。
在上述代码中,给f函数的out参数赋了一个*bytes.Buffer的空指针,所以out的动态值是nil。然而它的动态类型是bytes.Buffer,意思是:“A non-nil interface containing a nil pointer”,所以“out!=nil”的结果依然是true。
但是,对于直接的bytes.Buffer
类型的判空不会出现此问题。
import (
"bytes"
"fmt"
)
func main(){
var buf *bytes.Buffer
if buf == nil{
fmt.Println("right")
}
}
还是输出: right
只有 接口指针 传入函数的接口参数时,才会出现以上的坑。
修改起来也很方便,把*bytes.Buffer改为bytes.Buffer就好了。
import (
"bytes"
"fmt"
"io"
)
const debug = false
func main(){
var buf bytes.Buffer //原来是var buf *bytes.Buffer
if debug{
buf = new(bytes.Buffer)
}
f(buf)
}
func f(out io.Writer){
if out != nil{
fmt.Println("surprise!")
}
}
将map转化为json字符串的时候,json字符串中的顺序和map赋值顺序无关
请看下列代码,请问输出什么?若为json字符串,则json字符串中key的顺序是什么?
func main() {
params := make(map[string]string)
params["id"] = "1"
params["id1"] = "3"
params["controller"] = "sections"
data, _ := json.Marshal(params)
fmt.Println(string(data))
}
答案:输出{"controller":"sections","id":"1","id1":"3"}
利用Golang自带的json转换包转换,会将map中key的顺序改为字母顺序,而不是map的赋值顺序。map这个结构哪怕利用for range遍历的时候,其中的key也是无序的,可以理解为map就是个无序的结构,和php中的array要区分开来
Json反序列化数字到interface{}类型的值中,默认解析为float64类型
请看以下程序,程序想要输出json数据中整型id加上3的值,请问程序会报错吗?
func main(){
jsonStr := `{"id":1058,"name":"RyuGou"}`
var jsonData map[string]interface{}
json.Unmarshal([]byte(jsonStr), &jsonData)
sum := jsonData["id"].(int) + 3
fmt.Println(sum)
}
```
答案是会报错,输出结果为:
panic: interface conversion: interface {} is float64, not int
使用 Golang 解析 JSON 格式数据时,若以 interface{} 接收数据,则会按照下列规则进行解析:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
应该改为:
```go
func main(){
jsonStr := `{"id":1058,"name":"RyuGou"}`
var jsonData map[string]interface{}
json.Unmarshal([]byte(jsonStr), &jsonData)
sum := int(jsonData["id"].(float64)) + 3
fmt.Println(sum)
}
- 即使在有多个变量、且有的变量存在有的变量不存在、且这些变量共同赋值的情况下,也不可以使用:=来给全局变量赋值
:=往往是用来声明局部变量的,在多个变量赋值且有的值存在的情况下,:=也可以用来赋值使用,例如:
msgStr := "hello wolrd"
msgStr, err := "hello", errors.New("xxx")//err并不存在
但是,假如全局变量也使用类似的方式赋值,就会出现问题,请看下列代码,试问能编译通过吗?
var varTest string
func test(){
varTest, err := function()
fmt.Println(err.Error())
}
func function()(string, error){
return "hello world", errors.New("error")
}
func main(){
test()
}
答案是:通不过。输出:
varTest declared and not used
但是如果改成如下代码,就可以通过:
var varTest string
func test(){
err := errors.New("error")
varTest, err = function()
fmt.Println(err.Error())
}
func function()(string, error){
return "hello world", errors.New("error")
}
func main(){
test()
}
输出:
error
这是什么原因呢?
答案其实很简单,在test方法中,如果使用varTest, err := function()这种方式的话,相当于在函数中又定义了一个和全局变量varTest名字相同的局部变量,而这个局部变量又没有使用,所以会编译不通过。