跟着实例学Go语言(三)
创始人
2024-03-20 03:12:36
0

本教程全面涵盖了Go语言基础的各个方面。一共80个例子,每个例子对应一个语言特性点,非常适合新人快速上手。
教程代码示例来自go by example,文字部分来自本人自己的理解。

本文是教程系列的第三部分,共计20个例子、约1.5万字。

目录

  • 41. Mutexes
  • 42. Stateful Goroutines
  • 43. Sorting
  • 44. Sorting by Functions
  • 45. Panic
  • 46. Defer
  • 47. Recover
  • 48. String Functions
  • 49. String Formatting
  • 50. Text Templates
  • 51. Regular Expressions
  • 52. JSON
  • 53. XML
  • 54. Time
  • 55. Epoch
  • 56. Time Formatting / Parsing
  • 57. Random Numbers
  • 58. Number Parsing
  • 59. URL Parsing
  • 60. SHA256 Hashes

41. Mutexes

Go中处理同步的另一方式是使用互斥锁,这也是许多其他语言会采用的方式,保证加锁的代码块为原子操作。

package mainimport ("fmt""sync"
)// 封装结构,包括:互斥锁和它保护的map计数器
type Container struct {mu       sync.Mutexcounters map[string]int
}func (c *Container) inc(name string) {// 通过互斥锁保证对计数器的修改是原子操作c.mu.Lock()defer c.mu.Unlock()c.counters[name]++
}func main() {c := Container{counters: map[string]int{"a": 0, "b": 0},}var wg sync.WaitGroupdoIncrement := func(name string, n int) {for i := 0; i < n; i++ {c.inc(name)}wg.Done()}wg.Add(3)go doIncrement("a", 10000)go doIncrement("a", 10000)go doIncrement("b", 10000)wg.Wait()fmt.Println(c.counters)
}
$ go run mutexes.go
map[a:20000 b:10000]

42. Stateful Goroutines

比起互斥锁,Go更推荐使用通道通信的方式来实现goroutine之间的同步。以下是一个使用channel通信来实现并发安全的缓存服务的例子。缓存服务从reads和writes两个channel中读取读写命令,并依次执行,从而避免了多个goroutine对缓存数据的竞争。两个channel在这里起到了消息队列的作用。

package mainimport ("fmt""math/rand""sync/atomic""time"
)type readOp struct {key  intresp chan int
}
type writeOp struct {key  intval  intresp chan bool
}func main() {var readOps uint64var writeOps uint64reads := make(chan readOp)writes := make(chan writeOp)go func() {var state = make(map[int]int)for {//  缓存服务从两个channel取命令,串行执行,每次一条select {case read := <-reads:read.resp <- state[read.key]case write := <-writes:state[write.key] = write.valwrite.resp <- true}}}()for r := 0; r < 100; r++ {go func() {for {read := readOp{key:  rand.Intn(5),resp: make(chan int)}reads <- read<-read.resp// 接收到返回后将计数加1atomic.AddUint64(&readOps, 1)time.Sleep(time.Millisecond)}}()}for w := 0; w < 10; w++ {go func() {for {write := writeOp{key:  rand.Intn(5),val:  rand.Intn(100),resp: make(chan bool)}writes <- write<-write.respatomic.AddUint64(&writeOps, 1)time.Sleep(time.Millisecond)}}()}time.Sleep(time.Second)// 用LoadUint64保证读取计数为原子操作readOpsFinal := atomic.LoadUint64(&readOps)fmt.Println("readOps:", readOpsFinal)writeOpsFinal := atomic.LoadUint64(&writeOps)fmt.Println("writeOps:", writeOpsFinal)
}
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177

43. Sorting

Go内置的排序函数支持对不同数据类型slice的排序。

package mainimport ("fmt""sort"
)func main() {// 排序string和int要用不同的函数strs := []string{"c", "a", "b"}sort.Strings(strs)fmt.Println("Strings:", strs)ints := []int{7, 2, 4}sort.Ints(ints)fmt.Println("Ints:   ", ints)// 判断是否已排序s := sort.IntsAreSorted(ints)fmt.Println("Sorted: ", s)
}
$ go run sorting.go
Strings: [a b c]
Ints:    [2 4 7]
Sorted:  true

44. Sorting by Functions

除了按自然序排序,我们也可以通过函数自定义排序规则,对任意类型的结构体进行排序。下面例子是自定义byLength类型的结构体,然后对其中的string按长度进行排序。

package mainimport ("fmt""sort"
)// 新类型是string slice的别名
type byLength []string// 实现了排序所需的三个方法:Len、Swap、Less
// byLength实际就是string slice,是引用类型,因此不用传指针
func (s byLength) Len() int {return len(s)
}
func (s byLength) Swap(i, j int) {s[i], s[j] = s[j], s[i]
}
func (s byLength) Less(i, j int) bool {return len(s[i]) < len(s[j])
}func main() {fruits := []string{"peach", "banana", "kiwi"}// byLength实现排序接口后,可执行被Sort调用sort.Sort(byLength(fruits))fmt.Println(fruits)
}
$ go run sorting-by-functions.go 
[kiwi peach banana]

45. Panic

panic用于表示预期外的严重错误,遇到这种错误时应该第一时间失败,中止程序运行。对于一般的错误处理,Go推荐尽量使用error而非panic。

package mainimport "os"func main() {panic("a problem")_, err := os.Create("/tmp/file")if err != nil {// 当遇到error不知道怎么处理时,可以包装成panicpanic(err)}
}
$ go run panic.go
panic: a problem// 程序会panic的那行直接退出,退出码不再是0
goroutine 1 [running]:
main.main()/.../panic.go:12 +0x47
...
exit status 2

46. Defer

defer用于将某段代码推迟到当前函数的最后执行,一般用于清理操作。它类似于Java中的finally,但不同的是,遇到panic后不保证defer的语句一定能执行,除非使用后面讲到的recover恢复。

package mainimport ("fmt""os"
)func main() {f := createFile("/tmp/defer.txt")// 将关闭文件的逻辑推迟到写完后defer closeFile(f)writeFile(f)
}func createFile(p string) *os.File {fmt.Println("creating")f, err := os.Create(p)if err != nil {panic(err)}return f
}func writeFile(f *os.File) {fmt.Println("writing")fmt.Fprintln(f, "data")}func closeFile(f *os.File) {fmt.Println("closing")err := f.Close()if err != nil {fmt.Fprintf(os.Stderr, "error: %v\n", err)os.Exit(1)}
}
$ go run defer.go
creating
writing
closing

47. Recover

Go提供了从panic中恢复的机制。类似于Java中的catch exception,可定义恢复后执行的操作。恢复通过recover函数实现,recover必须在defer函数中调用,在panic后会自动激活。可用这种方式确保defer函数一定得到执行。

package mainimport "fmt"func mayPanic() {panic("a problem")
}func main() {defer func() {// recover必须在defer函数中调用if r := recover(); r != nil {fmt.Println("Recovered. Error:\n", r)}}()mayPanic()fmt.Println("After mayPanic()")
}
$ go run recover.go
Recovered. Error:a problem

48. String Functions

Go内置的strings包提供了很多有用的string处理函数,如Contains(是否包含)、Join(连接)、Split(切分),具体参见如下示例。

package mainimport ("fmt"s "strings"
)var p = fmt.Printlnfunc main() {p("Contains:  ", s.Contains("test", "es"))p("Count:     ", s.Count("test", "t"))p("HasPrefix: ", s.HasPrefix("test", "te"))p("HasSuffix: ", s.HasSuffix("test", "st"))p("Index:     ", s.Index("test", "e"))p("Join:      ", s.Join([]string{"a", "b"}, "-"))p("Repeat:    ", s.Repeat("a", 5))p("Replace:   ", s.Replace("foo", "o", "0", -1))p("Replace:   ", s.Replace("foo", "o", "0", 1))p("Split:     ", s.Split("a-b-c-d-e", "-"))p("ToLower:   ", s.ToLower("TEST"))p("ToUpper:   ", s.ToUpper("test"))
}
$ go run string-functions.go
Contains:   true
Count:      2
HasPrefix:  true
HasSuffix:  true
Index:      1
Join:       a-b
Repeat:     aaaaa
Replace:    f00
Replace:    f0o
Split:      [a b c d e]
ToLower:    test
ToUpper:    TEST

49. String Formatting

Go通过Printf提供了方便的格式化功能,例如%v(结构的值)、%d(数字)、%s(字符串),具体参见如下示例。

package mainimport ("fmt""os"
)type point struct {x, y int
}func main() {p := point{1, 2}fmt.Printf("struct1: %v\n", p)fmt.Printf("struct2: %+v\n", p)fmt.Printf("struct3: %#v\n", p)fmt.Printf("type: %T\n", p)fmt.Printf("bool: %t\n", true)fmt.Printf("int: %d\n", 123)fmt.Printf("bin: %b\n", 14)fmt.Printf("char: %c\n", 33)fmt.Printf("hex: %x\n", 456)fmt.Printf("float1: %f\n", 78.9)fmt.Printf("float2: %e\n", 123400000.0)fmt.Printf("float3: %E\n", 123400000.0)fmt.Printf("str1: %s\n", "\"string\"")fmt.Printf("str2: %q\n", "\"string\"")fmt.Printf("str3: %x\n", "hex this")fmt.Printf("pointer: %p\n", &p)fmt.Printf("width1: |%6d|%6d|\n", 12, 345)fmt.Printf("width2: |%6.2f|%6.2f|\n", 1.2, 3.45)fmt.Printf("width3: |%-6.2f|%-6.2f|\n", 1.2, 3.45)fmt.Printf("width4: |%6s|%6s|\n", "foo", "b")fmt.Printf("width5: |%-6s|%-6s|\n", "foo", "b")s := fmt.Sprintf("sprintf: a %s", "string")fmt.Println(s)fmt.Fprintf(os.Stderr, "io: an %s\n", "error")
}
$ go run string-formatting.go
struct1: {1 2}
struct2: {x:1 y:2}
struct3: main.point{x:1, y:2}
type: main.point
bool: true
int: 123
bin: 1110
char: !
hex: 1c8
float1: 78.900000
float2: 1.234000e+08
float3: 1.234000E+08
str1: "string"
str2: "\"string\""
str3: 6865782074686973
pointer: 0xc0000ba000
width1: |    12|   345|
width2: |  1.20|  3.45|
width3: |1.20  |3.45  |
width4: |   foo|     b|
width5: |foo   |b     |
sprintf: a string
io: an error

50. Text Templates

Go提供了使用模板动态生成字符串或者HTML的功能。基于给定的模板,提供参数动态替换其中的占位符,可以有助于高效生成我们想要的文字。

package mainimport ("os""text/template"
)func main() {t1 := template.New("t1")t1, err := t1.Parse("Value is {{.}}\n")if err != nil {panic(err)}// Must是上面包装err成panic的简便写法t1 = template.Must(t1.Parse("Value: {{.}}\n"))t1.Execute(os.Stdout, "some text")t1.Execute(os.Stdout, 5)t1.Execute(os.Stdout, []string{"Go","Rust","C++","C#",})Create := func(name, t string) *template.Template {return template.Must(template.New(name).Parse(t))}// 支持用字段名替换t2 := Create("t2", "Name: {{.Name}}\n")t2.Execute(os.Stdout, struct {Name string}{"Jane Doe"})t2.Execute(os.Stdout, map[string]string{"Name": "Mickey Mouse",})// 支持基于条件判断的替换t3 := Create("t3","{{if . -}} yes {{else -}} no {{end}}\n")t3.Execute(os.Stdout, "not empty")t3.Execute(os.Stdout, "")// 支持基于遍历的替换t4 := Create("t4","Range: {{range .}}{{.}} {{end}}\n")t4.Execute(os.Stdout,[]string{"Go","Rust","C++","C#",})
}
$ go run templates.go 
Value: some text
Value: 5
Value: [Go Rust C++ C#]
Name: Jane Doe
Name: Mickey Mouse
yes 
no 
Range: Go Rust C++ C# 

51. Regular Expressions

像许多其他语言一样,Go也对正则表达式这一通用功能有良好支持。

package mainimport ("bytes""fmt""regexp"
)func main() {// 直接判断正则表达式和文字是否匹配match, _ := regexp.MatchString("p([a-z]+)ch", "peach")fmt.Println(match)// 解析正则表达式。注意对于特殊符号,无需像Java一样用反斜杠转义r, _ := regexp.Compile("p([a-z]+)ch")// 再用解析好的正则,判断文字是否匹配fmt.Println(r.MatchString("peach"))fmt.Println(r.FindString("peach punch"))fmt.Println("idx:", r.FindStringIndex("peach punch"))fmt.Println(r.FindStringSubmatch("peach punch"))fmt.Println(r.FindStringSubmatchIndex("peach punch"))fmt.Println(r.FindAllString("peach punch pinch", -1))fmt.Println("all:", r.FindAllStringSubmatchIndex("peach punch pinch", -1))fmt.Println(r.FindAllString("peach punch pinch", 2))fmt.Println(r.Match([]byte("peach")))// 强制解析正则,失败则会panicr = regexp.MustCompile("p([a-z]+)ch")fmt.Println("regexp:", r)fmt.Println(r.ReplaceAllString("a peach", ""))in := []byte("a peach")out := r.ReplaceAllFunc(in, bytes.ToUpper)fmt.Println(string(out))
}
$ go run regular-expressions.go
true
true
peach
idx: [0 5]
[peach ea]
[0 5 1 3]
[peach punch pinch]
all: [[0 5 1 3] [6 11 7 9] [12 17 13 15]]
[peach punch]
true
regexp: p([a-z]+)ch
a 
a PEACH

52. JSON

Go语言对JSON提供了内置的支持,包括Marshal(编码)、Unmarshal(解码)。

package mainimport ("encoding/json""fmt""os"
)type response1 struct {Page   intFruits []string
}type response2 struct {Page   int      `json:"page"`Fruits []string `json:"fruits"`
}func main() {bolB, _ := json.Marshal(true)fmt.Println(string(bolB))intB, _ := json.Marshal(1)fmt.Println(string(intB))fltB, _ := json.Marshal(2.34)fmt.Println(string(fltB))strB, _ := json.Marshal("gopher")fmt.Println(string(strB))slcD := []string{"apple", "peach", "pear"}slcB, _ := json.Marshal(slcD)fmt.Println(string(slcB))mapD := map[string]int{"apple": 5, "lettuce": 7}mapB, _ := json.Marshal(mapD)fmt.Println(string(mapB))res1D := &response1{Page:   1,Fruits: []string{"apple", "peach", "pear"}}res1B, _ := json.Marshal(res1D)fmt.Println(string(res1B))res2D := &response2{Page:   1,Fruits: []string{"apple", "peach", "pear"}}res2B, _ := json.Marshal(res2D)fmt.Println(string(res2B))byt := []byte(`{"num":6.13,"strs":["a","b"]}`)var dat map[string]interface{}if err := json.Unmarshal(byt, &dat); err != nil {panic(err)}fmt.Println(dat)num := dat["num"].(float64)fmt.Println(num)strs := dat["strs"].([]interface{})str1 := strs[0].(string)fmt.Println(str1)str := `{"page": 1, "fruits": ["apple", "peach"]}`res := response2{}json.Unmarshal([]byte(str), &res)fmt.Println(res)fmt.Println(res.Fruits[0])enc := json.NewEncoder(os.Stdout)d := map[string]int{"apple": 5, "lettuce": 7}enc.Encode(d)
}
$ go run json.go
true
1
2.34
"gopher"
["apple","peach","pear"]
{"apple":5,"lettuce":7}
{"Page":1,"Fruits":["apple","peach","pear"]}
{"page":1,"fruits":["apple","peach","pear"]}
map[num:6.13 strs:[a b]]
6.13
a
{1 [apple peach]}
apple
{"apple":5,"lettuce":7}

53. XML

Go语言对XML提供了内置的支持,包括Marshal(编码)、Unmarshal(解码)。

package mainimport ("encoding/xml""fmt"
)type Plant struct {XMLName xml.Name `xml:"plant"`Id      int      `xml:"id,attr"`Name    string   `xml:"name"`Origin  []string `xml:"origin"`
}func (p Plant) String() string {return fmt.Sprintf("Plant id=%v, name=%v, origin=%v",p.Id, p.Name, p.Origin)
}func main() {coffee := &Plant{Id: 27, Name: "Coffee"}coffee.Origin = []string{"Ethiopia", "Brazil"}out, _ := xml.MarshalIndent(coffee, " ", "  ")fmt.Println(string(out))fmt.Println(xml.Header + string(out))var p Plantif err := xml.Unmarshal(out, &p); err != nil {panic(err)}fmt.Println(p)tomato := &Plant{Id: 81, Name: "Tomato"}tomato.Origin = []string{"Mexico", "California"}type Nesting struct {XMLName xml.Name `xml:"nesting"`Plants  []*Plant `xml:"parent>child>plant"`}nesting := &Nesting{}nesting.Plants = []*Plant{coffee, tomato}out, _ = xml.MarshalIndent(nesting, " ", "  ")fmt.Println(string(out))
}
$ go run xml.goCoffeeEthiopiaBrazil
CoffeeEthiopiaBrazil
Plant id=27, name=Coffee, origin=[Ethiopia Brazil]CoffeeEthiopiaBrazilTomatoMexicoCalifornia

54. Time

下面例子展示了Go的time包对时间的操作。

package mainimport ("fmt""time"
)func main() {p := fmt.Println// 获取当前时间now := time.Now()p(now)// 创建一个指定的时间then := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)p(then)// 打印出时间的某项属性p(then.Year())p(then.Month())p(then.Day())p(then.Hour())p(then.Minute())p(then.Second())p(then.Nanosecond())p(then.Location())p(then.Weekday())p(then.Before(now))p(then.After(now))p(then.Equal(now))// 注意:两个时间计算差值,返回的是durationdiff := now.Sub(then)p(diff)p(diff.Hours())p(diff.Minutes())p(diff.Seconds())p(diff.Nanoseconds())p(then.Add(diff))p(then.Add(-diff))
}
$ go run time.go
2012-10-31 15:50:13.793654 +0000 UTC
2009-11-17 20:34:58.651387237 +0000 UTC
2009
November
17
20
34
58
651387237
UTC
Tuesday
true
false
false
25891h15m15.142266763s
25891.25420618521
1.5534752523711128e+06
9.320851514226677e+07
93208515142266763
2012-10-31 15:50:13.793654 +0000 UTC
2006-12-05 01:19:43.509120474 +0000 UTC

55. Epoch

Go也提供了基于时间得到时间戳,即从1970-1-1 0点到指定时间经历的秒、毫秒、纳秒数。

package mainimport ("fmt""time"
)func main() {now := time.Now()fmt.Println(now)// 将时间转成基于秒、毫秒、纳秒的时间戳fmt.Println(now.Unix())fmt.Println(now.UnixMilli())fmt.Println(now.UnixNano())// 可以从时间戳反转成时间fmt.Println(time.Unix(now.Unix(), 0))fmt.Println(time.Unix(0, now.UnixNano()))
}
$ go run epoch.go 
2012-10-31 16:13:58.292387 +0000 UTC
1351700038
1351700038292
1351700038292387000
2012-10-31 16:13:58 +0000 UTC
2012-10-31 16:13:58.292387 +0000 UTC

56. Time Formatting / Parsing

下面例子展示了日期格式化和解析的方法。

package mainimport ("fmt""time"
)func main() {p := fmt.Println// 使用RFC3339标准格式化t := time.Now()p(t.Format(time.RFC3339))// 使用RFC3339标准解析t1, e := time.Parse(time.RFC3339,"2012-11-01T22:08:41+00:00")p(t1)// 格式化可以使用样例作为参数p(t.Format("3:04PM"))p(t.Format("Mon Jan _2 15:04:05 2006"))p(t.Format("2006-01-02T15:04:05.999999-07:00"))form := "3 04 PM"t2, e := time.Parse(form, "8 41 PM")p(t2)fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",t.Year(), t.Month(), t.Day(),t.Hour(), t.Minute(), t.Second())ansic := "Mon Jan _2 15:04:05 2006"_, e = time.Parse(ansic, "8:41PM")p(e)
}
$ go run time-formatting-parsing.go 
2014-04-15T18:00:15-07:00
2012-11-01 22:08:41 +0000 +0000
6:00PM
Tue Apr 15 18:00:15 2014
2014-04-15T18:00:15.161182-07:00
0000-01-01 20:41:00 +0000 UTC
2014-04-15T18:00:15-00:00
parsing time "8:41PM" as "Mon Jan _2 15:04:05 2006": ...

57. Random Numbers

下面的例子展示了随机数的使用。

package mainimport ("fmt""math/rand""time"
)func main() {// 随机生成[0, 100)范围内的整数fmt.Print(rand.Intn(100), ",")fmt.Print(rand.Intn(100))fmt.Println()// 随机生成[0, 1)范围内的float64小数fmt.Println(rand.Float64())fmt.Print((rand.Float64()*5)+5, ",")fmt.Print((rand.Float64() * 5) + 5)fmt.Println()// 随机函数默认以当前时间戳为种子,你也可以显式为它指定种子s1 := rand.NewSource(time.Now().UnixNano())r1 := rand.New(s1)fmt.Print(r1.Intn(100), ",")fmt.Print(r1.Intn(100))fmt.Println()// 用同一种子生成的随机数序列必定相同s2 := rand.NewSource(42)r2 := rand.New(s2)fmt.Print(r2.Intn(100), ",")fmt.Print(r2.Intn(100))fmt.Println()s3 := rand.NewSource(42)r3 := rand.New(s3)fmt.Print(r3.Intn(100), ",")fmt.Print(r3.Intn(100))
}
$ go run random-numbers.go
81,87
0.6645600532184904
7.123187485356329,8.434115364335547
0,28
5,87
5,87

58. Number Parsing

下面例子展示了如何从string解析出数字。

package mainimport ("fmt""strconv"
)func main() {// 解析64位精度的floatf, _ := strconv.ParseFloat("1.234", 64)fmt.Println(f)// 解析64位整数,0代表从string自动推断数字的进制,此处为10进制i, _ := strconv.ParseInt("123", 0, 64)fmt.Println(i)d, _ := strconv.ParseInt("0x1c8", 0, 64)fmt.Println(d)u, _ := strconv.ParseUint("789", 0, 64)fmt.Println(u)// Atoi是解析10进制整数的简便写法k, _ := strconv.Atoi("135")fmt.Println(k)_, e := strconv.Atoi("wat")fmt.Println(e)
}
$ go run number-parsing.go 
1.234
123
456
789
135
strconv.ParseInt: parsing "wat": invalid syntax

59. URL Parsing

下面例子展示了从一个URL解析出用户名、密码、端口、文件路径等信息的方法。

package mainimport ("fmt""net""net/url"
)func main() {s := "postgres://user:pass@host.com:5432/path?k=v#f"u, err := url.Parse(s)if err != nil {panic(err)}fmt.Println(u.Scheme)fmt.Println(u.User)fmt.Println(u.User.Username())p, _ := u.User.Password()fmt.Println(p)fmt.Println(u.Host)host, port, _ := net.SplitHostPort(u.Host)fmt.Println(host)fmt.Println(port)fmt.Println(u.Path)fmt.Println(u.Fragment)fmt.Println(u.RawQuery)m, _ := url.ParseQuery(u.RawQuery)fmt.Println(m)fmt.Println(m["k"][0])
}
$ go run url-parsing.go 
postgres
user:pass
user
pass
host.com:5432
host.com
5432
/path
f
k=v
map[k:[v]]
v

60. SHA256 Hashes

SHA256哈希经常被用来对文件或文字做签名。例如TLS/SSL就使用它计算证书签名。

package mainimport ("crypto/sha256""fmt"
)func main() {s := "sha256 this string"h := sha256.New()h.Write([]byte(s))// 计算SHA256哈希bs := h.Sum(nil)fmt.Println(s)fmt.Printf("%x\n", bs)
}
$ go run sha256-hashes.go
sha256 this string
1af1dfa857bf1d8814fe1af8983c18080019922e557f15a8a...

相关内容

热门资讯

汽车油箱结构是什么(汽车油箱结... 本篇文章极速百科给大家谈谈汽车油箱结构是什么,以及汽车油箱结构原理图解对应的知识点,希望对各位有所帮...
美国2年期国债收益率上涨15个... 原标题:美国2年期国债收益率上涨15个基点 美国2年期国债收益率上涨15个基...
嵌入式 ADC使用手册完整版 ... 嵌入式 ADC使用手册完整版 (188977万字)💜&#...
重大消息战皇大厅开挂是真的吗... 您好:战皇大厅这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
盘点十款牵手跑胡子为什么一直... 您好:牵手跑胡子这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游...
senator香烟多少一盒(s... 今天给各位分享senator香烟多少一盒的知识,其中也会对sevebstars香烟进行解释,如果能碰...
终于懂了新荣耀斗牛真的有挂吗... 您好:新荣耀斗牛这款游戏可以开挂,确实是有挂的,需要了解加客服微信8435338】很多玩家在这款游戏...
盘点十款明星麻将到底有没有挂... 您好:明星麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【5848499】很多玩家在这款游戏...
总结文章“新道游棋牌有透视挂吗... 您好:新道游棋牌这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7682267】很多玩家在这款游...
终于懂了手机麻将到底有没有挂... 您好:手机麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...