美文网首页
golang结构体拷贝

golang结构体拷贝

作者: EasyNetCN | 来源:发表于2020-07-15 18:09 被阅读0次

实现了类似Spring中BeanUtils中的对象复制功能,支持过滤字段,支持时间和字符字段的转换

package utility

import (
    "fmt"
    "reflect"
    "time"
)

const (
    DATE_PATTERN      = "2006-01-02"
    DATE_TIME_PATTERN = "2006-01-02 15:04:05"
)

type BeanUpdateLog struct {
    Changed    bool                `json:"changed"`
    UpdateLogs []BeanUpdateLogItem `json:"updateLogs"`
}

type BeanUpdateLogItem struct {
    PropertyName     string      `json:"propertyName"`
    OldProperyValue  interface{} `json:"oldProperyValue"`
    NewPropertyValue interface{} `json:"newPropertyValue"`
}

func Copy(source interface{}, dest interface{}, ignoreFields ...string) ([]string, error) {
    destType := reflect.TypeOf(dest)
    destValue := reflect.ValueOf(dest)
    sourceType := reflect.TypeOf(source)
    sourceValue := reflect.ValueOf(source)

    if destType.Kind() != reflect.Ptr {
        return make([]string, 0), fmt.Errorf("a must be a struct pointer")
    }

    destValue = reflect.ValueOf(destValue.Interface())

    m := SliceToMap(ignoreFields)
    fileds := make([]string, 0)

    for i := 0; i < sourceValue.NumField(); i++ {
        name := sourceType.Field(i).Name

        if _, ok := m[name]; !ok {
            fileds = append(fileds, name)
        }
    }

    if len(fileds) == 0 {
        return make([]string, 0), nil
    }

    modified := make([]string, 0, len(fileds))

    for i := 0; i < len(fileds); i++ {
        name := fileds[i]
        sourceFieldValue := sourceValue.FieldByName(name)
        destFieldValue := destValue.Elem().FieldByName(name)

        if destFieldValue.IsValid() {
            sourceFieldValueTypeStr := sourceFieldValue.Type().String()
            destFieldValueTypeStr := destFieldValue.Type().String()

            if (sourceFieldValueTypeStr == "time.Time" ||
                sourceFieldValueTypeStr == "*time.Time") &&
                (destFieldValueTypeStr == "string" ||
                    destFieldValueTypeStr == "*string") {
                timeStr := ""

                if sourceFieldValueTypeStr == "time.Time" {
                    t := (sourceFieldValue.Interface()).(time.Time)

                    if !t.IsZero() {
                        timeStr = t.Format(DATE_TIME_PATTERN)
                    }

                } else if sourceFieldValueTypeStr == "*time.Time" {
                    timePtr := (sourceFieldValue.Interface()).(*time.Time)

                    if timePtr != nil && !timePtr.IsZero() {
                        timeStr = (*timePtr).Format(DATE_TIME_PATTERN)
                    }
                }

                if destFieldValueTypeStr == "string" &&
                    reflect.DeepEqual(destFieldValue.Interface(), timeStr) == false {

                    destFieldValue.Set(reflect.ValueOf(timeStr))

                    modified = append(modified, name)
                } else if destFieldValueTypeStr == "*string" {
                    strPtr := (destFieldValue.Interface()).(*string)

                    if strPtr != nil && reflect.DeepEqual(*strPtr, timeStr) == false {
                        destFieldValue.Set(reflect.ValueOf(&timeStr))

                        modified = append(modified, name)
                    } else {
                        destFieldValue.Set(reflect.ValueOf(&timeStr))

                        modified = append(modified, name)
                    }
                }
            } else if (sourceFieldValueTypeStr == "string" || sourceFieldValueTypeStr == "*string") &&
                (destFieldValueTypeStr == "time.Time" || destFieldValueTypeStr == "*time.Time") {
                timeStr := ""

                if sourceFieldValueTypeStr == "string" {
                    timeStr = (sourceFieldValue.Interface()).(string)
                } else if sourceFieldValueTypeStr == "*string" {
                    timePtr := (sourceFieldValue.Interface()).(*string)

                    if timePtr != nil {
                        timeStr = *timePtr
                    }
                }

                if t, err := time.ParseInLocation(DATE_TIME_PATTERN, timeStr, time.Local); err == nil {
                    if destFieldValueTypeStr == "time.Time" && reflect.DeepEqual(t, destFieldValue.Interface()) == false {
                        destFieldValue.Set(reflect.ValueOf(t))

                        modified = append(modified, name)
                    } else if destFieldValueTypeStr == "*time.Time" {
                        timePtr := (destFieldValue.Interface()).(*time.Time)

                        if timePtr != nil && reflect.DeepEqual((*timePtr).Format(DATE_TIME_PATTERN), timeStr) == false {
                            destFieldValue.Set(reflect.ValueOf(&t))

                            modified = append(modified, name)
                        } else if timePtr == nil {
                            destFieldValue.Set(reflect.ValueOf(&t))

                            modified = append(modified, name)
                        }
                    }

                }
            } else if sourceFieldValueTypeStr[0] == '*' &&
                sourceFieldValueTypeStr[1:] == destFieldValueTypeStr &&
                !sourceFieldValue.IsNil() && !reflect.DeepEqual(sourceFieldValue.Elem().Interface(), destFieldValue.Interface()) {
                destFieldValue.Set(reflect.ValueOf(sourceFieldValue.Elem().Interface()))

                modified = append(modified, name)
            } else if destFieldValueTypeStr[0] == '*' &&
                destFieldValueTypeStr[1:] == sourceFieldValueTypeStr &&
                ((!destFieldValue.IsZero() && !reflect.DeepEqual(sourceFieldValue.Interface(), destFieldValue.Elem().Interface())) || (destFieldValue.IsNil())) {
                switch sourceFieldValue.Kind() {
                case reflect.Bool:
                    v := sourceFieldValue.Interface().(bool)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Int:
                    v := sourceFieldValue.Interface().(int)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Int8:
                    v := sourceFieldValue.Interface().(int8)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Int16:
                    v := sourceFieldValue.Interface().(int16)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Int32:
                    v := sourceFieldValue.Interface().(int32)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Int64:
                    v := sourceFieldValue.Interface().(int64)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Uint:
                    v := sourceFieldValue.Interface().(uint)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Uint8:
                    v := sourceFieldValue.Interface().(uint8)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Uint16:
                    v := sourceFieldValue.Interface().(uint16)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Uint32:
                    v := sourceFieldValue.Interface().(uint32)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Uint64:
                    v := sourceFieldValue.Interface().(uint64)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Float32:
                    v := sourceFieldValue.Interface().(float32)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.Float64:
                    v := sourceFieldValue.Interface().(float64)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                case reflect.String:
                    v := sourceFieldValue.Interface().(string)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    modified = append(modified, name)
                }

            } else if destFieldValue.Kind() == sourceFieldValue.Kind() &&
                reflect.DeepEqual(destFieldValue.Interface(), sourceFieldValue.Interface()) == false {
                destFieldValue.Set(sourceFieldValue)

                modified = append(modified, name)
            }
        }

    }

    return modified, nil
}

func CopyAndLogProperties(source interface{}, dest interface{}, ignoreFields ...string) (BeanUpdateLog, error) {
    beanUpdateLogs := &BeanUpdateLog{UpdateLogs: make([]BeanUpdateLogItem, 0)}
    destType := reflect.TypeOf(dest)
    destValue := reflect.ValueOf(dest)
    sourceType := reflect.TypeOf(source)
    sourceValue := reflect.ValueOf(source)

    if destType.Kind() != reflect.Ptr {
        return *beanUpdateLogs, fmt.Errorf("a must be a struct pointer")
    }

    destValue = reflect.ValueOf(destValue.Interface())

    m := SliceToMap(ignoreFields)
    fileds := make([]string, 0)

    for i := 0; i < sourceValue.NumField(); i++ {
        name := sourceType.Field(i).Name

        if _, ok := m[name]; !ok {
            fileds = append(fileds, name)
        }
    }

    if len(fileds) == 0 {
        return *beanUpdateLogs, nil
    }

    for i := 0; i < len(fileds); i++ {
        name := fileds[i]
        sourceFieldValue := sourceValue.FieldByName(name)

        destFieldValue := destValue.Elem().FieldByName(name)

        if destFieldValue.IsValid() {
            lname := StringFirstLower(name)
            destFieldValueTypeStr := destFieldValue.Type().String()
            sourceFieldValueTypeStr := sourceFieldValue.Type().String()

            if (sourceFieldValueTypeStr == "time.Time" ||
                sourceFieldValueTypeStr == "*time.Time") &&
                (destFieldValueTypeStr == "string" ||
                    destFieldValueTypeStr == "*string") {
                timeStr := ""

                if sourceFieldValueTypeStr == "time.Time" {
                    t := (sourceFieldValue.Interface()).(time.Time)

                    if !t.IsZero() {
                        timeStr = t.Format(DATE_TIME_PATTERN)
                    }

                } else if sourceFieldValueTypeStr == "*time.Time" {
                    timePtr := (sourceFieldValue.Interface()).(*time.Time)

                    if timePtr != nil && !timePtr.IsZero() {
                        timeStr = (*timePtr).Format(DATE_TIME_PATTERN)
                    }
                }

                if destFieldValueTypeStr == "string" &&
                    reflect.DeepEqual(destFieldValue.Interface(), timeStr) == false {
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: timeStr}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    destFieldValue.Set(reflect.ValueOf(timeStr))
                } else if destFieldValueTypeStr == "*string" {
                    strPtr := (destFieldValue.Interface()).(*string)

                    if strPtr != nil && reflect.DeepEqual(*strPtr, timeStr) == false {
                        beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: &timeStr}
                        beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                        destFieldValue.Set(reflect.ValueOf(&timeStr))
                    } else {
                        beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: &timeStr}
                        beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                        destFieldValue.Set(reflect.ValueOf(&timeStr))
                    }
                }
            } else if (sourceFieldValueTypeStr == "string" || sourceFieldValueTypeStr == "*string") &&
                (destFieldValueTypeStr == "time.Time" || destFieldValueTypeStr == "*time.Time") {
                timeStr := ""

                if sourceFieldValueTypeStr == "string" {
                    timeStr = (sourceFieldValue.Interface()).(string)
                } else if sourceFieldValueTypeStr == "*string" {
                    timePtr := (sourceFieldValue.Interface()).(*string)

                    if timePtr != nil {
                        timeStr = *timePtr
                    }
                }

                if t, err := time.ParseInLocation(DATE_TIME_PATTERN, timeStr, time.Local); err == nil {
                    if destFieldValueTypeStr == "time.Time" && reflect.DeepEqual(t, destFieldValue.Interface()) == false {
                        beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: t}
                        beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                        destFieldValue.Set(reflect.ValueOf(t))
                    } else if destFieldValueTypeStr == "*time.Time" {
                        timePtr := (destFieldValue.Interface()).(*time.Time)

                        if timePtr != nil && reflect.DeepEqual((*timePtr).Format(DATE_TIME_PATTERN), timeStr) == false {
                            beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: &t}
                            beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                            destFieldValue.Set(reflect.ValueOf(&t))
                        } else if timePtr == nil {
                            beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: &timeStr}
                            beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                            destFieldValue.Set(reflect.ValueOf(&t))
                        }
                    }

                }
            } else if sourceFieldValueTypeStr[0] == '*' &&
                sourceFieldValueTypeStr[1:] == destFieldValueTypeStr &&
                !sourceFieldValue.IsNil() && !reflect.DeepEqual(sourceFieldValue.Elem().Interface(), destFieldValue.Interface()) {

                beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                destFieldValue.Set(reflect.ValueOf(sourceFieldValue.Elem().Interface()))
            } else if destFieldValueTypeStr[0] == '*' &&
                destFieldValueTypeStr[1:] == sourceFieldValueTypeStr &&
                ((!destFieldValue.IsZero() && !reflect.DeepEqual(sourceFieldValue.Interface(), destFieldValue.Elem().Interface())) || (destFieldValue.IsNil())) {
                switch sourceFieldValue.Kind() {
                case reflect.Bool:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(bool)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Int:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(int)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Int8:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(int8)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Int16:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(int16)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Int32:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(int32)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Int64:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(int64)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Uint:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(uint)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Uint8:
                    v := sourceFieldValue.Interface().(uint8)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)
                case reflect.Uint16:
                    v := sourceFieldValue.Interface().(uint16)
                    destFieldValue.Set(reflect.ValueOf(&v))

                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)
                case reflect.Uint32:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(uint32)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Uint64:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(uint64)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Float32:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(float32)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.Float64:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(float64)
                    destFieldValue.Set(reflect.ValueOf(&v))
                case reflect.String:
                    beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                    beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                    v := sourceFieldValue.Interface().(string)
                    destFieldValue.Set(reflect.ValueOf(&v))
                }

            } else if destFieldValue.Kind() == sourceFieldValue.Kind() &&
                reflect.DeepEqual(destFieldValue.Interface(), sourceFieldValue.Interface()) == false {
                beanUpdateLogItem := &BeanUpdateLogItem{PropertyName: lname, OldProperyValue: destFieldValue.Interface(), NewPropertyValue: sourceFieldValue.Interface()}
                beanUpdateLogs.UpdateLogs = append(beanUpdateLogs.UpdateLogs, *beanUpdateLogItem)

                destFieldValue.Set(sourceFieldValue)
            }
        }

    }

    beanUpdateLogs.Changed = len(beanUpdateLogs.UpdateLogs) > 0

    return *beanUpdateLogs, nil
}

func StructToMap(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)
    m := make(map[string]interface{})

    for i := 0; i < t.NumField(); i++ {
        m[t.Field(i).Name] = v.Field(i).Interface()
    }

    return m
}

func SliceToMap(s []string) map[string]string {
    m := make(map[string]string, 0)

    if s != nil {
        for _, v := range s {
            m[v] = v
        }
    }

    return m
}

func StringFirstLower(str string) string {
    if isLower := 'a' <= str[0] && str[0] <= 'z'; isLower {
        return str
    }

    newstr := make([]byte, 0, len(str))

    for i := 0; i < len(str); i++ {
        c := str[i]

        if i == 0 {
            c += 'a' - 'A'

            newstr = append(newstr, c)
        } else {
            newstr = append(newstr, c)
        }
    }

    return BytesToString(newstr)
}

func BytesToString(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

相关文章

  • golang结构体拷贝

    实现了类似Spring中BeanUtils中的对象复制功能,支持过滤字段,支持时间和字符字段的转换

  • Go 面向对象编程

    struct 实例化方式 结构体语法糖 结构体是值类型 结构体 深拷贝和浅拷贝 即值传递和引用传递 匿名结构体 结...

  • Golang之情非得已的DeepCopy

    续日今天问我:Golang里面如果做结构体的深拷贝呢?他说他的excelize库被网友爆出一个Bug:改变shee...

  • Learn Golang In Day 9

    Learn Golang In Day 9 简介 结构体是自定义的数据结构 定义结构体 package main ...

  • golang小知识

    golang这门语言中没有深拷贝。浅拷贝是拷贝值以及值中直接包含的东西。深拷贝是连同结构一块拷贝。 通道是将gor...

  • golang 结构体

     一个结构体类型可以包含若干个字段,也可以不包含任何字段。空结构体可以关联上一些方法,从而看成是函数的特殊版本。 ...

  • Golang:结构体

    结构体 结构体(struct)是用户自定义的类型,代表若干字段的集合。有时将多个数据看做一个整体要比单独使用这些数...

  • Golang结构体

    C++中结构体(struct)和类(class)的主要区别有: struct没有权限控制字段 struct没有方法...

  • golang结构体

    定义 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合 结构体的定义 结构体定义需要使用 type 和 ...

  • Golang 结构体

    继承

网友评论

      本文标题:golang结构体拷贝

      本文链接:https://www.haomeiwen.com/subject/eoujhktx.html