Golang开发新手常犯的50个错误
《50 Shades of Go: Traps,Gotchas,and Common Mistakes for New Golang Devs》 原文地址:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html 一、初级1、不允许左大括号单独一行 2、不允许出现未使用的变量 3、不允许出现未使用的import 解决方法:使用 package main
import (
_ "fmt" // 指定别名为`_`
"log"
"time"
)
var _ = log.Println // 变量名为`_`
func main() {
_ = time.Now
}
4、短的变量声明(Short Variable Declarations)只能在函数内部使用 package main
// myvar := 1 // error
var myvar = 1 // ok
func main() {
}
5、不能使用短变量声明(Short Variable Declarations)重复声明 6、不能使用短变量声明(Short Variable Declarations)这种方式来设置字段值 package main
func main() {
one := 0
// one := 1 //error: Redeclaring Variables Using Short Variable Declarations
// data.result,err := work() //error:Can't Use Short Variable Declarations to Set Field Values
data.result,err = work() //ok
}
7、意外的变量幽灵(Accidental Variable Shadowing) 短变量声明语法,很好用,但是代码块中使用短变更声明与外部相同的变量时, package main
import "fmt"
func main() {
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
// x = 3
x := 2 // 不会影响到外部x变量的值
fmt.Println(x) //prints 2
//x = 5 // 不会影响到外部x变量值
}
fmt.Println(x) //prints 3
}
这种现象称之为 8、不能使用 // var x = nil //error
var x interface{} = nil // OK
_ = x
9、不能直接使用 10、map使用make分配内存时可指定capicity,但是不能对map使用cap函数 11、字符串不允许使用 在golang中,nil只能赋值给指针、channel、func、interface、map或slice类型的变量。 var x string = nil //error
if x == nil { //error
x = "default"
}
//var x string //defaults to "" (zero value)
if x == "" {
x = "default"
}
12、数组用于函数传参时是值复制 注意:方法或函数调用时,传入参数都是值复制(跟赋值一致),除非是map、slice、channel、指针类型这些特殊类型是引用传递。 x := [3]int{1,2,3}
// 数组在函数中传参是值复制
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
// 使用数组指针实现引用传参
func(arr *[3]int) {
(*arr)[0] = 7
fmt.Println(arr) //prints &[7 2 3]
}(&x)
fmt.Println(x) //prints [7 2 3]
13、range关键字返回是键值对,而不是值 x := []string{"a","b","c"}
// for v := range x {
// fmt.Println(v) //prints 0,1,2
// }
for _,v := range x {
fmt.Println(v) //prints a,b,c
}
14、Slice和Array是一维的 Go表面上看起来像多维的,实际上只是一维的。但可以使用原始的一维数组、一维的切片来实现多维。 15、从不存在key的map中取值时,返回的总是”0值” x := map[string] string {"one":"1","two":"2"}
if _,ok := x["two"]; !ok { // 判断是否存在,x[key]始终有返回值
}
16、字符串是不可变 x := "text"
// x[0] = 'T' // error
xtytes := []byte(x)
xbytes[0] = 'T' // ok
fmt.Println(string(xbytes)) //prints Text
17、字符串与 //[]byte:
for i,v := range []byte(str) {
}
18、string的索引操作返回的是byte(或uint8),如想获取字符可使用 19、字符串并不总是UTF8的文本 20、 21、在Slice、Array、Map的多行书写最后的逗号不可省略 x := []int{
1,// 2 //error
3,// ok
}
y := []int {1, 2, 3,} // ok
z := []int {1, 3} // 单行书写,最后一个元素的逗号可省略
22、内置数据结构的操作并不同步,但可把Go提供了并发的特性使用起来:goroutines和channels。 23、使用for range迭代String,是以rune来迭代的。 一个字符,也可以有多个rune组成。需要处理字符,尽量使用 for range总是尝试将字符串解析成utf8的文本,对于它无法解析的字节,它会返回 data := "Axfex02xffx04"
for _,v := range data {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)
fmt.Println()
for _,v := range []byte(data) {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfe 0x2 0xff 0x4 (good)
24、使用for range迭代map时每次迭代的顺序可能不一样,因为map的迭代是随机的。 25、switch的case默认匹配规则不同于其它语言的是,匹配case条件后默认退出,除非使用fallthrough继续匹配;而其它语言是默认继续匹配,除非使用break退出匹配。 26、只有后置自增、后置自减,不存在前置自增、前置自减 27、位运算的非操作是 28、位运算(与、或、异或、取反)优先级高于四则运算(加、减、乘、除、取余),有别于C语言。 29、结构体在序列化时非导出字段(以小写字母开头的字段名)不会被encode,因此在decode时这些非导出字段的值为”0值” 30、程序不等所有goroutine结束就会退出。可通过channel实现主协程(main goroutine)等待所有goroutine完成。 31、对于无缓存区的channel,写入channel的goroutine会阻塞直到被读取,读取channel的goroutine会阻塞直到有数据写入。 32、从一个closed状态的channel读取数据是安全的,可通过返回状态(第二个返回参数)判断是否关闭;而向一个closed状态的channel写数据会导致panic。 33、向一个 package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get first result
fmt.Println("result:",<-ch)
//do other work
time.Sleep(2 * time.Second)
}
34、方法接收者是类型(T),接收者只是原对象的值复制,在方法中修改接收者不会修改原始对象的值;如果方法接收者是指针类型(*T),是对原对象的引用,方法中对其修改当然是原对象修改。 type data struct {
num int
key *string
items map[string]bool
}
func (this *data) pmethod() {
this.num = 7
}
func (this data) vmethod() {
this.num = 8
*this.key = "v.key"
this.items["vmethod"] = true
}
35、log包中的 二、中级1、关闭HTTP的Response.Body 使用defer语句关闭资源时要注意nil值,在defer语句之前要进行nill值处理 以下以http包的使用为例 package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp,err := http.Get("https://api.ipify.org?format=json")
if resp != nil {
defer resp.Body.Close() // ok,即使不读取Body中的数据,即使Body是空的,也要调用close方法
}
//defer resp.Body.Close() // (1)Error:在nil值判断之前使用,resp为nil时defer中的语句执行会引发空引用的panic
if err != nil {
fmt.Println(err)
return
}
//defer resp.Body.Close() // (2)Error:排除了nil隐患,但是出现重定向错误时,仍然需要调用close
body,err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
在Go 1.5之前 _,err = io.Copy(ioutil.Discard,resp.Body) 如果只是读取Body的部分,就很有必要在关闭Body之前做这种手动处理。例如处理json api响应时 2、关闭HTTP连接: (1) 可使用 (2) 添加 package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req,err := http.NewRequest("GET","http://golang.org",nil)
if err != nil {
fmt.Println(err)
return
}
req.Close = true
//or do this:
//req.Header.Add("Connection","close")
resp,err := http.DefaultClient.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
body,err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(string(body)))
}
(3)全局关闭http连接重用。 对于相同http server发送多个请求时,适合保持网络连接; package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
tr := &http.Transport{DisableKeepAlives: true}
client := &http.Client{Transport: tr}
resp,err := client.Get("http://golang.org")
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.StatusCode)
body,err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(string(body)))
}
3、Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意。 var data = []byte(`{"status": 200}`)
var result map[string]interface{}
if err := json.Unmarshal(data,&result); err != nil {
fmt.Println("error:",err)
return
}
//var status = result["status"].(int) //error: panic: interface conversion: interface is float64,not int
var status = uint64(result["status"].(float64)) //ok
fmt.Println("status value:",status)
(1) 使用 var data = []byte(`{"status": 200}`)
var decoder = json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
if err := decoder.Decode(&result); err != nil {
fmt.Println("error:",err)
return
}
var status,_ = result["status"].(json.Number).Int64() //ok
fmt.Println("status value:",status)
var status2 uint64
if err := json.Unmarshal([]byte(result["status"].(json.Number).String()),&status2); err != nil {
fmt.Println("error:",err)
return
}
(2)使用struct结构体映射 var data = []byte(`{"status": 200}`)
var result struct {
Status uint64 `json:"status"`
}
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil {
fmt.Println("error:",err)
return
}
fmt.Printf("result => %+v",result) //prints: result => {Status:200}
(3) 使用struct映射数字为 records := [][]byte{
[]byte(`{"status": 200,"tag":"one"}`),[]byte(`{"status":"ok","tag":"two"}`),}
for idx,record := range records {
var result struct {
StatusCode uint64
StatusName string
Status json.RawMessage `json:"status"`
Tag string `json:"tag"`
}
if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil {
fmt.Println("error:",err)
return
}
var sstatus string
if err := json.Unmarshal(result.Status,&sstatus); err == nil {
result.StatusName = sstatus
}
var nstatus uint64
if err := json.Unmarshal(result.Status,&nstatus); err == nil {
result.StatusCode = nstatus
}
fmt.Printf("[%v] result => %+vn",idx,result)
}
4、Struct、Array、Slice、Map的比较 如果struct结构体的所有字段都能够使用 同样,array只有在它的每个元素能够使用 Go提供了一些用于比较不能直接使用
type data struct {
num int
fp float32
complex complex64
str string
char rune
yes bool
events <-chan string
handler interface{}
ref *byte
raw [10]byte
}
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",v1 == v2) //prints: v1 == v2: true
type data2 struct {
num int //ok
checks [10]func() bool //not comparable
doit func() bool //not comparable
m map[string] string //not comparable
bytes []byte //not comparable
}
v3 := data2{}
v4 := data2{}
fmt.Println("v3 == v4:",v3 == v4) // error
v5 := data2{}
v6 := data2{}
fmt.Println("v5 == v6:",reflect.DeepEqual(v5,v6)) //prints: v5 == v6: true
m1 := map[string]string{"one": "a","two": "b"}
m2 := map[string]string{"two": "b","one": "a"}
fmt.Println("m1 == m2:",reflect.DeepEqual(m1,m2)) //prints: m1 == m2: true
s1 := []int{1, 3}
s2 := []int{1, 3}
fmt.Println("s1 == s2:",reflect.DeepEqual(s1,s2)) //prints: s1 == s2: true
var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",reflect.DeepEqual(b1,b2)) //prints: b1 == b2: false
var b3 []byte = nil
b4 := []byte{}
fmt.Println("b3 == b4:",bytes.Equal(b3,b4)) //prints: b3 == b4: true
var str string = "one"
var in interface{} = "one"
fmt.Println("str == in:",str == in,reflect.DeepEqual(str,in)) //prints: str == in: true true
v1 := []string{"one","two"}
v2 := []interface{}{"one","two"}
fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) //prints: v1 == v2: false (not ok)
data := map[string]interface{}{
"code": 200,"value": []string{"one","two"},}
encoded,_ := json.Marshal(data)
var decoded map[string]interface{}
json.Unmarshal(encoded,&decoded)
fmt.Println("data == decoded:",reflect.DeepEqual(data,decoded)) //prints: data == decoded: false (not ok)
如果要忽略大小写来比较包含文字数据的字节切片(byte slice),
如果要比较用于验证用户数据密钥信息的字节切片时,使用 5、从panic中恢复
package main
import "fmt"
func doRecover() {
fmt.Println("recovered =>",recover()) //prints: recovered => <nil>
}
func main() {
defer func() {
fmt.Println("recovered:",recover()) // ok
}()
defer func() {
doRecover() //panic is not recovered
}()
// recover() //doesn't do anything
panic("not good")
// recover() //won't be executed :)
fmt.Println("ok")
}
6、在slice、array、map的for range子句中修改和引用数据项 使用range获取的数据项是从集合元素的复制过来的,并非引用原始数据,但使用索引能访问原始数据。 data := []int{1,3}
for _,v := range data {
v *= 10 // original item is not changed
}
data2 := []int{1,3}
for i,v := range data2 {
data2[i] *= 10 // change original item
}
// 元素是指针类型就不一样了
data3 := []*struct{num int} {{1},{2},{3}}
for _,v := range data {
v.num *= 10
}
fmt.Println("data:",data) //prints data: [1 2 3]
fmt.Println("data:",data2) //prints data: [10 20 30]
fmt.Println(data3[0],data3[1],data3[2]) //prints &{10} &{20} &{30}
7、Slice中的隐藏数据 从一个slice上再生成一个切片slice,新的slice将直接引用原始slice的那个数组,两个slice对同一数组的操作,会相互影响。 可通过为新切片slice重新分配空间,从slice中copy部分的数据来避免相互之间的影响。 raw := make([]byte,10000)
fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
data := raw[:3]
fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x>
res := make([]byte,3)
copy(res,raw[:3])
fmt.Println(len(res),cap(res),&res[0]) //prints: 3 3 <byte_addr_y>
8、Slice超范围数据覆盖 从已存在的切片slice中继续切片时,新切片的capicity等于原capicity减去新切片之前部分的数量,新切片与原切片都指向同一数组空间。 新生成切片之间capicity区域是重叠的,因此在添加数据时易造成数据覆盖问题。 slice使用append添加的内容时超出capicity时,会重新分配空间。 path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex]
// 解决方法
// dir1 := path[:sepIndex:sepIndex] //full slice expression
dir2 := path[sepIndex+1:]
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB
dir1 = append(dir1,"suffix"...)
path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)
fmt.Println("new path =>",string(path))
9、Slice增加元素重新分配内存导致的怪事 slice在添加元素前,与其它切片共享同一数据区域,修改会相互影响;但添加元素导致内存重新分配之后,不再指向原来的数据区域,修改元素,不再影响其它切片。 s1 := []int{1,3}
fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]
s2 := s1[1:]
fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]
for i := range s2 { s2[i] += 20 }
//still referencing the same array
fmt.Println(s1) //prints [1 22 23]
fmt.Println(s2) //prints [22 23]
s2 = append(s2,4)
for i := range s2 { s2[i] += 10 }
//s1 is now "stale"
fmt.Println(s1) //prints [1 22 23]
fmt.Println(s2) //prints [32 33 14]
10、类型重定义与方法继承 从一个已存在的(non-interface)非接口类型重新定义一个新类型时,不会继承原类型的任何方法。 但是从一个已存在接口重新定义一个新接口时,新接口会继承原接口所有方法。 11、从”for switch”和”for select”代码块中跳出。 无label的break只会跳出最内层的
12、在for语句的闭包中使用迭代变量会有问题 在for迭代过程中,迭代变量会一直保留,只是每次迭代值不一样。 如果闭包要取所在迭代变量的值,就需要for中定义一个变量来保存所在迭代的值,或者通过闭包函数传参。 package main
import (
"fmt"
"time"
)
func forState1(){
data := []string{"one","two","three"}
for _,v := range data {
go func() {
fmt.Println(v)
}()
}
time.Sleep(3 * time.Second) //goroutines print: three,three,three
for _,v := range data {
vcopy := v // 使用临时变量
go func() {
fmt.Println(vcopy)
}()
}
time.Sleep(3 * time.Second) //goroutines print: one,two,v := range data {
go func(in string) {
fmt.Println(in)
}(v)
}
time.Sleep(3 * time.Second) //goroutines print: one,three
}
func main() {
forState1()
}
再看一个坑埋得比较深的例子。 package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{{"one"},{"two"},{"three"}}
for _,v := range data {
// 解决办法:添加如下语句
// v := v
go v.print()
}
time.Sleep(3 * time.Second) //goroutines print: three,three
data2 := []*field{{"one"},{"three"}} // 注意data2是指针数组
for _,v := range data2 {
go v.print() // go执行是函数,函数执行之前,函数的接受对象已经传过来
}
time.Sleep(3 * time.Second) //goroutines print: one,three
}
13、
要特别注意的是, type field struct{
num int
}
func(t *field) print(n int){
fmt.println(t.num,n)
}
func main() {
var i int = 1
defer fmt.Println("result2 =>",func() int { return i * 2 }())
i++
v := field{1}
defer v.print(func() int { return i * 2 }())
v = field{2}
i++
// prints:
// 2 4
// result => 2 (not ok if you expected 4)
}
14、 for _,target := range targets {
f,err := os.Open(target)
if err != nil {
fmt.Println("bad target:",target,"error:",err) //prints error: too many open files
break
}
defer f.Close() //will not be closed at the end of this code block,but closed at end of this function
// 解决方法1:不用defer
// f.Close()
// 解决方法2:将for中的语句添加到匿名函数中执行。
// func () {
// }()
}
15、失败的类型断言
var data interface{} = "great"
if data,ok := data.(int); ok {
fmt.Println("[is an int] value =>",data)
} else {
fmt.Println("[not an int] value =>",data) //prints: [not an int] value => 0 (not "great")
}
if res,res)
} else {
fmt.Println("[not an int] value =>",data) //prints: [not an int] value => great (as expected)
}
16、阻塞的goroutine与资源泄漏 func First(query string,replicas ...Search) Result {
c := make(chan Result)
// 解决1:使用缓冲的channel: c := make(chan Result,len(replicas))
searchReplica := func(i int) { c <- replicas[i](query) }
// 解决2:使用select-default,防止阻塞
// searchReplica := func(i int) {
// select {
// case c <- replicas[i](query):
// default:
// }
// }
// 解决3:使用特殊的channel来中断原有工作
// done := make(chan struct{})
// defer close(done)
// searchReplica := func(i int) {
// select {
// case c <- replicas[i](query):
// case <- done:
// }
// }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
三、高级1、用值实例上调用接收者为指针的方法 对于可寻址(addressable)的值变量(而不是指针),可以直接调用接受对象为指针类型的方法。 但是,并不是所有变量都是可寻址的,像Map的元素就是不可寻址的。 package main
import "fmt"
type data struct {
name string
}
func (p *data) print() {
fmt.Println("name:",p.name)
}
type printer interface {
print()
}
func main() {
d1 := data{"one"}
d1.print() //ok
// var in printer = data{"two"} //error
var in printer = &data{"two"}
in.print()
m := map[string]data {"x":data{"three"}}
//m["x"].print() //error
d2 = m["x"]
d2.print() // ok
}
2、更新map值的字段 如果map的值类型是结构体类型,那么不能更新从map中取出的结构体的字段值。 package main
type data struct {
name string
}
func main() {
m := map[string]data {"x":{"one"}}
//m["x"].name = "two" //error
r := m["x"]
r.name = "two"
m["x"] = r
fmt.Println(s) // prints: map[x:{two}]
mp := map[string]*data {"x": {"one"}}
mp["x"].name = "two" // ok
s := []data{{"one"}}
s[0].name = "two" // ok
fmt.Println(s) // prints: [{two}]
}
3、 在golang中,nil只能赋值给指针、channel、func、interface、map或slice类型的变量。
声明变量 因此, var data *byte
var in interface{}
fmt.Println(data,data == nil) //prints: <nil> true
fmt.Println(in,in == nil) //prints: <nil> true
in = data
fmt.Println(in,in == nil) //prints: <nil> false
//'data' is 'nil',but 'in' is not 'nil'
doit := func(arg int) interface{} {
var result *struct{} = nil
if(arg > 0) {
result = &struct{}{}
}
return result
}
if res := doit(-1); res != nil {
fmt.Println("good result:",res) //prints: good result: <nil>
//'res' is not 'nil',but its value is 'nil'
}
doit = func(arg int) interface{} {
var result *struct{} = nil
if(arg > 0) {
result = &struct{}{}
} else {
return nil //return an explicit 'nil'
}
return result
}
if res := doit(-1); res != nil {
fmt.Println("good result:",res)
} else {
fmt.Println("bad result (res is nil)") //here as expected
}
4、变量内存的分配 在C++中使用new操作符总是在heap上分配变量。Go编译器使用 要想知道变量内存分配的位置,可以在 5、GOMAXPROCS、Concurrency和Parallelism Go 1.4及以下版本每个操作系统线程只使用一个执行上下文execution context)。这意味着每个时间片,只有一个goroutine执行。 注意,GOMAXPROCS并不代表Go运行时能够使用的CPU数量,它是一个小256的数值,可以设置比实际的CPU数量更大的数字。 fmt.Println(runtime.GOMAXPROCS(-1)) //prints: X (1 on play.golang.org)
fmt.Println(runtime.NumCPU()) //prints: X (1 on play.golang.org)
runtime.GOMAXPROCS(20)
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 20
runtime.GOMAXPROCS(300)
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 256
6、读写操作排序 Go可能会对一些操作排序,但它保证在goroutine的所有行为保持不变。 package main
import (
"runtime"
"time"
)
var _ = runtime.GOMAXPROCS(3)
var a,b int
func u1() {
a = 1
b = 2
}
func u2() {
a = 3
b = 4
}
func p() {
println(a)
println(b)
}
func main() {
go u1()
go u2()
go p()
time.Sleep(1 * time.Second)
// 多次执行可显示以下以几种打印结果
// 1 2
// 3 4
// 0 2 (奇怪吗?)
// 0 0
// 1 4 (奇怪吗?)
}
看到上面显示的02,14这样的值了吗?这没什么奇怪的。以02这样的输出结果,goroutine的执行应该是这样的 ------------------------------------- p函数 | u1函数 | u2函数 | -------------------------------------
println(a) | | |
| a = 1 | |
| b = 2 | |
println(b) | | |
| | a = 3 |
| | b = 4 | -------------------------------------
(从上向下为时间执行顺序)
7、优先调度 有一些比较流氓的goroutine会阻止其它goroutine的执行。 scheduler会在GC、go语句、阻塞channel的操作、阻塞系统调用、lock操作等语句执行之后立即执行。 package main
import (
"fmt"
"runtime"
)
func main() {
done := false
go func(){
done = true
}()
for !done {
// ...
//runtime.Gosched() // 让scheduler执行调度,让出执行时间片
}
fmt.Println("done!")
}
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |