云原生时代崛起的编程语言Go基础实战

科技资讯 投稿 5800 0 评论

云原生时代崛起的编程语言Go基础实战

目录
    概述
    • 定义
    • 使用场景
    • Go 安全
  • 使用须知
      搜索工具
  • Go基础命令
  • 标准库
  • 基础语法
      Effective Go 概览
  • 命名规范
  • 注释
  • 变量
  • 常量(const
  • 控制结构
  • 数据类型
  • 迭代(range)
  • 函数
  • 指针
  • 字符串和符文
  • 结构体(struct
  • 方法
  • 接口(interface
  • 泛型
  • 错误(errors)
  • 恐慌(pinic)
  • 推迟(defer)
  • 恢复(recover)
  • 概述

    定义

    Go 官网文档地址 https://golang.google.cn/doc/

    Golang简称Go,是谷歌开源的编程语言,旨在提供程序员编程效率,易于学习,非常适合团队使用,天然支持高并发,有垃圾收集机制,且自带功能完善健壮的标准库。

    Go语言由于来自全球技术大厂谷歌创造及推动,其生态发展极其迅速,从业界声音看大有可能成为接下来10年内最具统治力的语言,也即是替代Java霸主地位,至于未来是否可以静待结果。至少从目前国内大厂阿里、腾讯、百度、字节的使用趋势极其效应可以看到其迅速扩张的可能,越受开发者喜爱其生态完整就会越好,如果从事企业级开发的伙伴有时间精力建议的话不烦可以开始深入学学Go语言开发。

    使用场景

      云上和网络的应用:如云计算领域、区块链、并发网络编程,有主要的云提供商强大的工具和api生态系统,用Go构建服务很容易。下面列举小部分流行包:
      • cloud.google.com/go
      • aws/client
      • Azure/azure-sdk-for-go
    • Cli命令行接口:cli是纯文本的。云和基础设施应用程序主要基于cli,这样易于自动化和远程功能。使用流行的开放源码包和健壮的标准库,可以使用Go创建快速而优雅的cli。下面列举小部分流行包:
        spf13/cobra
    • spf13/viper
    • urfave/cli
    • delve
    • chzyer/readline
  • Web开发:Go支持快速和可扩展的web应用程序。下面列举小部分流行包:
      net/http
  • html/template
  • flosch/pongo2
  • database/sql
  • elastic/go-elasticsearch
  • 开发运营和网站可靠性工程(云原生运维方向,特别是基于k8s的运维开发):Go拥有快速的构建时间、简洁的语法、自动格式化器和文档生成器,可以同时支持DevOps和SRE。下面列举小部分流行包:
      open-telemetry/opentelemetry-go
  • istio/istio
  • urfave/cli
  • Go 安全

      Go安全策略:解释了如何向Go团队报告Go标准库和子库中的安全问题。
    • Go安全发布:Go版本历史包含了过去安全问题的发布说明。根据发布策略发布了两个最新的Go主要版本的安全补丁。
    • Go漏洞管理:支持帮助开发人员找到可能影响其Go项目的已知公共漏洞。
    • Go Fuzzing:提供一种自动测试,持续地操作程序的输入以发现错误。
    • Go加密:Go加密库是Go标准库和子库中的crypto/…和golang.org/x/crypto/…包,并遵循这些原则开发。

    使用须知

    搜索工具

    Go 开发包搜索网站 https://pkg.go.dev/

    Go基础命令

    # 通过命令行输入go可以查看支持命令
    go
    go help build
    
      go bug:打开Bug报告。
    • go build:用于编译指定的源码文件或代码包以及它们的依赖包。常用
    • go clean:移除当前源码包和关联源码包里面编译生成的文件,即删除掉执行其它命令时产生的一些文件和目录,go 命令会在临时目录中构建对象,因此 go clean 主要关注其他工具或手动调用 go build 留下的对象文件。常用
    • go doc:打印与由其参数(包、const、func、类型、var、方法或结构字段)标识的项目相关联的文档注释。
    • go env:打印Go语言的环境信息。
    • go fix:把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
    • go fmt:go程序格式化,自动对齐、空格等。如果用了IDE这个命令就不需要了。
    • go generate:⽣成由现有⽂件中的指令描述的运⾏命令。这些命令可以运⾏任何进程,但⽬的是创建或更新 Go 源文件。
    • go get:根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。常用
    • go install:用于编译并安装指定的代码包及它们的依赖包。常用
    • go list:列出指定的代码包的信息。
    • go mod
      • go mod init:生成go.mod文件。常用
      • go mod download :下载go.mod文件中指明的所有依赖。
      • go mod tidy:整理现有的依赖。常用
      • go mod graph:查看现有的依赖结构。
      • go mod edit:编辑go.mod文件。
      • go mod vendor :导出项目所有的依赖到vendor目录。
      • go mod verify:校验一个模块是否被篡改过。
      • go mod why:查看为什么需要依赖某模块。
    • go work:跨文件目录操作本地包、多个mod模块包调用、本地测试。
    • go run:可以编译并运行命令源码文件,并把编译后的可执行文件存放到临时工作目录。常用
    • go test:用于以代码包为单位对Go语言编写的程序进行测试。常用
    • go tool:执行go自带的工具。go tool pprof对cpu、内存和协程进行监控;go tool trace跟踪协程的执行过程。
    • go version:打印 Go 可执⾏⽂件的构建信息。
    • go vet:用于检查Go语言源码中静态错误的简单工具。

    标准库

    精心策划的Go项目列表 https://github.com/golang/go/wiki/Projects

    基础语法

    Effective Go 概览

    Go是一门新语言,尽管它借鉴了现有语言的思想,但它具有不同寻常的特性,使有效的Go程序与用其相关语言编写的程序在性质上有所不同。直接将c++或Java程序翻译成Go不太可能产生令人满意的结果。重要的是要先了解它的性质和习惯用法,了解用Go编程的既定约定比如命名、格式、程序结构等等。比如拿分号(Semicolons)来说,像C一样,Go的形式语法使用分号来终止语句,但与C不同的是,这些分号不会出现在源代码中。相反,词法分析器使用一个简单的规则在扫描时自动插入分号,因此输入文本基本上没有分号,也即是在Go语言中不需要采用分号结尾。

    命名规范

      一般命名:推荐驼峰命名方式,如userManger或UserManager;单词缩写默认全大写或全小写,如userID、baiduCDN、id、cdn。
    • 项目名:小写,多个单词建议采用中划线分隔,比如github.com/gin-gonic。
    • 包名:package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写,统一使用单数形式。如domain、main。
    • 文件名:文件命名一律采用小写,见名思义,测试文件以test.go结尾,如stringutil.go,stringutil_test.go。
    • Local变量:保持简短;如索引采用i而不采用index,reader简写r,buffer简写为b;避免冗余,命名不要和上下文重叠,在方法体内变量名count 会比runeCount更简洁,在map的上下文下可以简写:v,ok=m[k]。
    func RuneCount(b []byte int {
        count := 0
        for i := 0; i < len(b; {
            if b[i] < RuneSelf {
                i++
            } else {
                _, n := DecodeRune(b[i:]
                i += n
            }
            count++
        }
        return count
    }
    func Read(b *Buffer, p []byte (n int, err error {
            if b.empty( {
                    b.Reset(
            }
            n = copy(p, b.buf[b.off:]
            b.off += n
            return n, nil
    }
    
      结构体:名词或名词短语,如Account、Book,避免使用Manager这样的。 如果该数据结构需要序列化,如json,则首字母大写,包括里面的字段。
    type Host struct {
        Port string `json:"port"`
        Address string `json:"address"`
    }
    
      接口:接口单个函数的接口名以 er 为后缀,两个函数的接口名可以综合两个函数名,三个以上函数的接口名类似于结构体名。
    type Reader interface {
        Read(p []byte (n int, err error
    }
    type WriteFlusher interface {
        Write([]byte (int, error
        Flush( error
    }
    type Car interface {
        Start( 
        Stop(
        Drive(
    }
    
      方法:动词或动词短语;如果是结构体方法,那么 Receiver 的名称应该缩写,要么都使用值,要么都用指针;一般用一个或两个字母(优先r);对于Receiver命名应该统一,要么都使用值,要么都用指针,如果 Receiver 是指针可统一使用p。
    func (b *Buffer Read(p []byte (n int, err error{
    }
    func (sh serverHandler ServeHTTP(rw ResponseWriter, req *Request{
    }
    func (r Rectangle Size( Point{
    }
    func (f foo method( {
    }
    func (p *foo method( {
    }
    

    Go没对get/set方法特别支持,必要的时候可以自己定义,Go对get有不同建议,如

    // 推荐
    p.FirstName(
    // 不推荐
    p.GetFirstName(
    
      error:自定义error命名通常以 “名称+Error” 作为结构体的名字,变量时会用简写err + 名称。
    type TypeError struct {
    	Errors []string
    }
    ErrShortDst = errors.New("transform: short destination buffer"
    
      常用缩写
    src = source
    srv = server
    arg = argument
    conn = connect, connection
    attr = attribute
    abs = absolute
    min = minimum
    len = length
    auth = authenticate
    buf = buffer
    ctl = control
    ctx = context
    str = string
    msg = message
    fmt = format
    dest = destination
    diff = difference
    orig = original
    recv = receive
    ref = reference
    repo = repository
    util = utility
    

    注释

    Go提供C风格的/* */块注释和c++风格的//行注释。行注释是常态;块注释主要作为包注释出现,但在表达式中或禁用大量代码时很有用。

    变量

    package main
    
    import "fmt"
    
    func main( {
    
        var a = "initial"
        fmt.Println(a
    
        var b, c int = 1, 2
        fmt.Println(b, c
    
        var d = true
        fmt.Println(d
    
        var e int
        fmt.Println(e
    
        f := "apple"
        fmt.Println(f
    }
    

    常量(const

    package mainimport (    "fmt"    "math"const s string = "constant"func main( {    fmt.Println(s    const n = 500000000    const d = 3e20 / n    fmt.Println(d    fmt.Println(int64(d    fmt.Println(math.Sin(n}
    

    控制结构

      循环(For:for是Go唯一的循环结构。
    package mainimport "fmt"func main( {    i := 1    for i <= 3 {        fmt.Println(i        i = i + 1    }    for j := 7; j <= 9; j++ {        fmt.Println(j    }    for {        fmt.Println("loop"        break    }    for n := 0; n <= 5; n++ {        if n%2 == 0 {            continue        }        fmt.Println(n    }}
    
      选择(If/Else:在Go中没有三元if,所以即使对于基本条件,也需要使用完整的if语句。
    package mainimport "fmt"func main( {    if 7%2 == 0 {        fmt.Println("7 is even"    } else {        fmt.Println("7 is odd"    }    if 8%4 == 0 {        fmt.Println("8 is divisible by 4"    }    if num := 9; num < 0 {        fmt.Println(num, "is negative"    } else if num < 10 {        fmt.Println(num, "has 1 digit"    } else {        fmt.Println(num, "has multiple digits"    }}
    
      Switch:跨多个分支表达条件。
    package mainimport (    "fmt"    "time"func main( {    i := 2    fmt.Print("Write ", i, " as "    switch i {    case 1:        fmt.Println("one"    case 2:        fmt.Println("two"    case 3:        fmt.Println("three"    }    switch time.Now(.Weekday( {    case time.Saturday, time.Sunday:        fmt.Println("It's the weekend"    default:        fmt.Println("It's a weekday"    }    t := time.Now(    switch {    case t.Hour( < 12:        fmt.Println("It's before noon"    default:        fmt.Println("It's after noon"    }    whatAmI := func(i interface{} {        switch t := i.(type {        case bool:            fmt.Println("I'm a bool"        case int:            fmt.Println("I'm an int"        default:            fmt.Printf("Don't know type %T\n", t        }    }    whatAmI(true    whatAmI(1    whatAmI("hey"}
    

    数据类型

      数组(Arrays:数组是特定长度的元素的编号序列。单在典型的Go代码中,切片更为常见,后面介绍。
    package mainimport "fmt"func main( {    var a [5]int    fmt.Println("emp:", a    a[4] = 100    fmt.Println("set:", a    fmt.Println("get:", a[4]    fmt.Println("len:", len(a    b := [5]int{1, 2, 3, 4, 5}    fmt.Println("dcl:", b    var twoD [2][3]int    for i := 0; i < 2; i++ {        for j := 0; j < 3; j++ {            twoD[i][j] = i + j        }    }    fmt.Println("2d: ", twoD}
    
      切片(Slices):是Go中的一种重要数据类型,它为序列提供了比数组更强大的接口。
    package mainimport "fmt"func main( {    s := make([]string, 3    fmt.Println("emp:", s    s[0] = "a"    s[1] = "b"    s[2] = "c"    fmt.Println("set:", s    fmt.Println("get:", s[2]    fmt.Println("len:", len(s    s = append(s, "d"    s = append(s, "e", "f"    fmt.Println("apd:", s    c := make([]string, len(s    copy(c, s    fmt.Println("cpy:", c    l := s[2:5]    fmt.Println("sl1:", l    l = s[:5]    fmt.Println("sl2:", l    l = s[2:]    fmt.Println("sl3:", l    t := []string{"g", "h", "i"}    fmt.Println("dcl:", t    twoD := make([][]int, 3    for i := 0; i < 3; i++ {        innerLen := i + 1        twoD[i] = make([]int, innerLen        for j := 0; j < innerLen; j++ {            twoD[i][j] = i + j        }    }    fmt.Println("2d: ", twoD}
    
      映射(Maps):Go内置的关联数据类型,在其他语言中有时称为哈希或字典。
    package mainimport "fmt"func main( {    m := make(map[string]int    m["k1"] = 7    m["k2"] = 13    fmt.Println("map:", m    v1 := m["k1"]    fmt.Println("v1:", v1    v3 := m["k3"]    fmt.Println("v3:", v3    fmt.Println("len:", len(m    delete(m, "k2"    fmt.Println("map:", m    _, prs := m["k2"]    fmt.Println("prs:", prs    n := map[string]int{"foo": 1, "bar": 2}    fmt.Println("map:", n}
    

    迭代(range)

    package mainimport "fmt"func main( {    nums := []int{2, 3, 4}    sum := 0    for _, num := range nums {        sum += num    }    fmt.Println("sum:", sum    for i, num := range nums {        if num == 3 {            fmt.Println("index:", i        }    }    kvs := map[string]string{"a": "apple", "b": "banana"}    for k, v := range kvs {        fmt.Printf("%s -> %s\n", k, v    }    for k := range kvs {        fmt.Println("key:", k    }    for i, c := range "go" {        fmt.Println(i, c    }}
    

    函数

    package main
    
    import "fmt"
    
    // 多个参数
    func plus(a int, b int int {
    
    	return a + b
    }
    // 多个相同类型参数
    func plusPlus(a, b, c int int {
    	return a + b + c
    }
    // 多个返回值
    func vals( (int, int {
    	return 3, 7
    }
    // 可变参数
    func sum(nums ...int {
        fmt.Print(nums, " "
        total := 0
    
        for _, num := range nums {
            total += num
        }
        fmt.Println(total
    }
    // 闭包,Go支持匿名函数,它可以形成闭包。当想要内联定义一个函数而不必命名它时可以使用匿名函数很。
    func intSeq( func( int {
        i := 0
        return func( int {
            i++
            return i
        }
    }
    // 递归函数
    func fact(n int int {
        if n == 0 {
            return 1
        }
        return n * fact(n-1
    }
    
    func main( {
    
    	res := plus(1, 2
    	fmt.Println("1+2 =", res
    
    	res = plusPlus(1, 2, 3
    	fmt.Println("1+2+3 =", res
        
        a, b := vals(
    	fmt.Println(a
    	fmt.Println(b
    
    	_, c := vals(
    	fmt.Println(c
        
        sum(1, 2
        sum(1, 2, 3
    
        nums := []int{1, 2, 3, 4}
        sum(nums...
        
        nextInt := intSeq(
    
        fmt.Println(nextInt(
        fmt.Println(nextInt(
        fmt.Println(nextInt(
    
        newInts := intSeq(
        fmt.Println(newInts(
        
        fmt.Println(fact(7
    
        var fib func(n int int
    
        fib = func(n int int {
            if n < 2 {
                return n
            }
    
            return fib(n-1 + fib(n-2
        }
    
        fmt.Println(fib(7
    }
    

    指针

    package main
    
    import "fmt"
    
    func zeroval(ival int {
        ival = 0
    }
    
    func zeroptr(iptr *int {
        *iptr = 0
    }
    
    func main( {
        i := 1
        fmt.Println("initial:", i
    
        zeroval(i
        fmt.Println("zeroval:", i
    
        zeroptr(&i
        fmt.Println("zeroptr:", i
    
        fmt.Println("pointer:", &i
    }
    

    字符串和符文

    Go字符串是字节的只读切片。该语言和标准库将字符串特殊地视为UTF-8编码文本的容器。在其他语言中,字符串是由“字符”组成的。在go中,字符的概念被称为符文——它是一个表示Unicode码点的整数。

    package main
    
    import (
        "fmt"
        "unicode/utf8"
    
    
    func main( {
    
        const s = "สวัสดี"
    
        fmt.Println("Len:", len(s
    
        for i := 0; i < len(s; i++ {
            fmt.Printf("%x ", s[i]
        }
        fmt.Println(
    
        fmt.Println("Rune count:", utf8.RuneCountInString(s
    
        for idx, runeValue := range s {
            fmt.Printf("%#U starts at %d\n", runeValue, idx
        }
    
        fmt.Println("\nUsing DecodeRuneInString"
        for i, w := 0, 0; i < len(s; i += w {
            runeValue, width := utf8.DecodeRuneInString(s[i:]
            fmt.Printf("%#U starts at %d\n", runeValue, i
            w = width
    
            examineRune(runeValue
        }
    }
    
    func examineRune(r rune {
    
        if r == 't' {
            fmt.Println("found tee"
        } else if r == 'ส' {
            fmt.Println("found so sua"
        }
    }
    

    结构体(struct

    package main
    
    import "fmt"
    
    type person struct {
        name string
        age  int
    }
    
    func newPerson(name string *person {
    
        p := person{name: name}
        p.age = 42
        return &p
    }
    
    func main( {
    
        fmt.Println(person{"Bob", 20}
    
        fmt.Println(person{name: "Alice", age: 30}
    
        fmt.Println(person{name: "Fred"}
    
        fmt.Println(&person{name: "Ann", age: 40}
    
        fmt.Println(newPerson("Jon"
    
        s := person{name: "Sean", age: 50}
        fmt.Println(s.name
    
        sp := &s
        fmt.Println(sp.age
    
        sp.age = 51
        fmt.Println(sp.age
    }
    

    方法

    package mainimport "fmt"type rect struct {	width, height int}func (r *rect area( int {	return r.width * r.height}func (r rect perim( int {	return 2*r.width + 2*r.height}func main( {	r := rect{width: 10, height: 5}	fmt.Println("area: ", r.area(	fmt.Println("perim:", r.perim(	rp := &r	fmt.Println("area: ", rp.area(	fmt.Println("perim:", rp.perim(}
    

    接口(interface

    package mainimport (    "fmt"    "math"type geometry interface {    area( float64    perim( float64}type rect struct {    width, height float64}type circle struct {    radius float64}func (r rect area( float64 {    return r.width * r.height}func (r rect perim( float64 {    return 2*r.width + 2*r.height}func (c circle area( float64 {    return math.Pi * c.radius * c.radius}func (c circle perim( float64 {    return 2 * math.Pi * c.radius}func measure(g geometry {    fmt.Println(g    fmt.Println(g.area(    fmt.Println(g.perim(}func main( {    r := rect{width: 3, height: 4}    c := circle{radius: 5}    measure(r    measure(c}
    
    package mainimport "fmt"type base struct {    num int}func (b base describe( string {    return fmt.Sprintf("base with num=%v", b.num}type container struct {    base    str string}func main( {    co := container{        base: base{            num: 1,        },        str: "some name",    }    fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str    fmt.Println("also num:", co.base.num    fmt.Println("describe:", co.describe(    type describer interface {        describe( string    }    var d describer = co    fmt.Println("describer:", d.describe(}
    

    泛型

    package mainimport "fmt"func MapKeys[K comparable, V any](m map[K]V []K {    r := make([]K, 0, len(m    for k := range m {        r = append(r, k    }    return r}type List[T any] struct {    head, tail *element[T]}type element[T any] struct {    next *element[T]    val  T}func (lst *List[T] Push(v T {    if lst.tail == nil {        lst.head = &element[T]{val: v}        lst.tail = lst.head    } else {        lst.tail.next = &element[T]{val: v}        lst.tail = lst.tail.next    }}func (lst *List[T] GetAll( []T {    var elems []T    for e := lst.head; e != nil; e = e.next {        elems = append(elems, e.val    }    return elems}func main( {    var m = map[int]string{1: "2", 2: "4", 4: "8"}    fmt.Println("keys:", MapKeys(m    _ = MapKeys[int, string](m    lst := List[int]{}    lst.Push(10    lst.Push(13    lst.Push(23    fmt.Println("list:", lst.GetAll(}
    
      集合数据类型:Go 泛型可以方便地定义各种集合数据类型,如栈、队列、链表、二叉树等。下面使用泛型定义栈的例子定义了一个 Stack[T] 类型,表示一个可以存储任意类型 T 的栈。Push 方法用于将一个元素压入栈中,Pop 方法用于弹出栈顶的元素。
    • 算法实现:泛型还可以用于实现各种算法,如排序、查找、字符串匹配等。以下是一个使用泛型实现快速排序的例子:在这个例子中,使用泛型定义 QuickSort 函数,可以对任意类型 T 的切片进行排序。该函数采用经典的快速排序算法实现。

    错误(errors)

    在Go中,习惯上通过显式的单独返回值来传达错误。这与Java和Ruby等语言中使用的异常以及c中有时使用的重载的单个结果/错误值形成对比。Go的方法可以很容易地看到哪些函数返回错误,并使用用于任何其他无错误任务的相同语言结构来处理它们。

    package main
    
    import (
        "errors"
        "fmt"
    
    
    func f1(arg int (int, error {
        if arg == 42 {
    
            return -1, errors.New("can't work with 42"
    
        }
    
        return arg + 3, nil
    }
    
    type argError struct {
        arg  int
        prob string
    }
    
    func (e *argError Error( string {
        return fmt.Sprintf("%d - %s", e.arg, e.prob
    }
    
    func f2(arg int (int, error {
        if arg == 42 {
    
            return -1, &argError{arg, "can't work with it"}
        }
        return arg + 3, nil
    }
    
    func main( {
    
        for _, i := range []int{7, 42} {
            if r, e := f1(i; e != nil {
                fmt.Println("f1 failed:", e
            } else {
                fmt.Println("f1 worked:", r
            }
        }
        for _, i := range []int{7, 42} {
            if r, e := f2(i; e != nil {
                fmt.Println("f2 failed:", e
            } else {
                fmt.Println("f2 worked:", r
            }
        }
    
        _, e := f2(42
        if ae, ok := e.(*argError; ok {
            fmt.Println(ae.arg
            fmt.Println(ae.prob
        }
    }
    

    恐慌(pinic)

    package main
    
    import "os"
    
    func main( {
    
        panic("a problem"
    
        _, err := os.Create("/tmp/file"
        if err != nil {
            panic(err
        }
    }
    

    推迟(defer)

    package main
    
    import (
        "fmt"
        "os"
    
    
    func main( {
    
        f := createFile("d:/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
        }
    }
    

    恢复(recover)

    package main
    
    import "fmt"
    
    func mayPanic( {
        panic("a problem"
    }
    
    func main( {
    
        defer func( {
            if r := recover(; r != nil {
    
                fmt.Println("Recovered. Error:\n", r
            }
        }(
    
        mayPanic(
    
        fmt.Println("After mayPanic("
    }
    
      本人博客网站IT小神 www.itxiaoshen.com

    编程笔记 » 云原生时代崛起的编程语言Go基础实战

    赞同 (27) or 分享 (0)
    游客 发表我的评论   换个身份
    取消评论

    表情
    (0)个小伙伴在吐槽