Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
定义结构体
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
type struct_variable_type struct {member definitionmember definition...member definition}
一旦定义了结构体类型,它就能用于变量的声明,语法格式如下
variable_name := structure_variable_type {value1, value2...valuen}或variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:
- ID 书籍ID
- Title 标题
- Author 作者
- Subject 学科 ```go package main import “fmt”
type Books struct { title string author string subject string book_id int }
func main() { // 创建一个新的结构体 fmt.Println(Books{“Go 语言”, “www.runoob.com”, “Go 语言教程”, 6495407})
// 也可以使用 key => value 格式 fmt.Println(Books{title: “Go 语言”, author: “www.runoob.com”, subject: “Go 语言教程”, book_id: 6495407})
// 忽略的字段为 0 或 空 fmt.Println(Books{title: “Go 语言”, author: “www.runoob.com”}) }
/ 输出结果为: {Go 语言 www.runoob.com Go 语言教程 6495407} {Go 语言 www.runoob.com Go 语言教程 6495407} {Go 语言 www.runoob.com 0} /
<a name="NhOMl"></a>### 访问结构体成员如果要访问结构体成员,需要使用点号 **.** 操作符,格式为```go结构体.成员名"
结构体类型变量使用 struct 关键字定义,实例如下:
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int}func main() {var Book1 Books // 声明 Book1 为 Books 类型var Book2 Books // 声明 Book2 为 Books 类型// book 1 描述Book1.title = "Go 语言"Book1.author = "www.runoob.com"Book1.subject = "Go 语言教程"Book1.book_id = 6495407// book 2 描述Book2.title = "Python 教程"Book2.author = "www.runoob.com"Book2.subject = "Python 语言教程"Book2.book_id = 6495700// 打印 Book1 信息fmt.Printf( "Book 1 title : %s\n", Book1.title)fmt.Printf( "Book 1 author : %s\n", Book1.author)fmt.Printf( "Book 1 subject : %s\n", Book1.subject)fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)// 打印 Book2 信息fmt.Printf( "Book 2 title : %s\n", Book2.title)fmt.Printf( "Book 2 author : %s\n", Book2.author)fmt.Printf( "Book 2 subject : %s\n", Book2.subject)fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)}/*以上实例执行运行结果为:Book 1 title : Go 语言Book 1 author : www.runoob.comBook 1 subject : Go 语言教程Book 1 book_id : 6495407Book 2 title : Python 教程Book 2 author : www.runoob.comBook 2 subject : Python 语言教程Book 2 book_id : 6495700*/
结构体作为函数参数
你可以像其他数据类型一样将结构体类型作为参数传递给函数。并以以上实例的方式访问结构体变量:
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int}func main() {var Book1 Books // 声明 Book1 为 Books 类型var Book2 Books // 声明 Book2 为 Books 类型// book 1 描述Book1.title = "Go 语言"Book1.author = "www.runoob.com"Book1.subject = "Go 语言教程"Book1.book_id = 6495407// book 2 描述Book2.title = "Python 教程"Book2.author = "www.runoob.com"Book2.subject = "Python 语言教程"Book2.book_id = 6495700printBook(Book1) // 打印 Book1 信息printBook(Book2) // 打印 Book2 信息}func printBook( book Books ) {fmt.Printf( "Book title : %s\n", book.title)fmt.Printf( "Book author : %s\n", book.author)fmt.Printf( "Book subject : %s\n", book.subject)fmt.Printf( "Book book_id : %d\n", book.book_id)}/*Book title : Go 语言Book author : www.runoob.comBook subject : Go 语言教程Book book_id : 6495407Book title : Python 教程Book author : www.runoob.comBook subject : Python 语言教程Book book_id : 6495700*/
结构体指针
你可以定义指向结构体的指针类似于其他指针变量,格式如下:
var struct_pointer *Books
以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:
struct_pointer = &Book1
使用结构体指针访问结构体成员,使用 “.” 操作符:
struct_pointer.title
接下来让我们使用结构体指针重写以上实例,代码如下:
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int}func main() {var Book1 Books // Declare Book1 of type Bookvar Book2 Books // Declare Book2 of type Book// book 1 描述Book1.title = "Go 语言"Book1.author = "www.runoob.com"Book1.subject = "Go 语言教程"Book1.book_id = 6495407// book 2 描述Book2.title = "Python 教程"Book2.author = "www.runoob.com"Book2.subject = "Python 语言教程"Book2.book_id = 6495700printBook(&Book1) // 打印 Book1 信息printBook(&Book2) // 打印 Book2 信息}func printBook( book *Books ) {fmt.Printf( "Book title : %s\n", book.title)fmt.Printf( "Book author : %s\n", book.author)fmt.Printf( "Book subject : %s\n", book.subject)fmt.Printf( "Book book_id : %d\n", book.book_id)}/*Book title : Go 语言Book author : www.runoob.comBook subject : Go 语言教程Book book_id : 6495407Book title : Python 教程Book author : www.runoob.comBook subject : Python 语言教程Book book_id : 6495700*/
结构体是作为参数的值传递
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int}func changeBook(book Books,title string) {book.title = title}func main() {var book1 Booksbook1.title = "book1"book1.author = "zuozhe"book1.book_id = 1changeBook(book1,"title被修改了")fmt.Println(book1)}/*期盼结果:{title被修改了 zuozhe 1}实际结果为:{book1 zuozhe 1}*/
如果想在函数里面改变结果体数据内容,需要传入指针:
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int}func changeBook(book *Books,title string) {book.title = title}func main() {var book1 Booksbook1.title = "book1"book1.author = "zuozhe"book1.book_id = 1changeBook(&book1,"title被修改了")fmt.Println(book1)}/*运行结果:{title被修改了 zuozhe 1}*/
struct 中定义成员变量
struct 类似于 java 中的类,但是不需要通过 getter, setter 来设置访问权限。要访问成员变量,可以:
- 通过 struct变量.成员变量来访问、重新赋值
- 通过 struct指针.成员变量来访问、重新赋值 ```go type Rect struct{ //定义矩形类 x,y float64 //类型只包含属性,并没有方法 width,height float64 }
func (r Rect) getArea() float64{ //为Rect类型绑定Area的方法,Rect为指针引用可以修改传入参数的值 return r.width*r.height //方法归属于类型,不归属于具体的对象,声明该类型的对象即可调用该类型的方法 }
//_ package main import ( “fmt” )
type Rect struct{
x,y float64
width,height int
}
func (r Rect) getArea() int{ return r.widthr.height }
func main() { var rectExample Rect rectExample.x = 10 rectExample.width = 20 rectExample.height = 30 rectExample.x = 40 fmt.Println(rectExample.getArea()) }
<a name="HjgZM"></a>### Golang自定义结构体转map在Golang中,如何将一个结构体转成map? 本文介绍两种方法。第一种是是使用json包解析解码编码。第二种是使用反射,使用反射的效率比较高<a name="fCPr5"></a>#### json包的marshal,unmarshal```gopackage mainimport ("fmt""time""encoding/json"// "reflect"// "strconv"// "strings"// "testing")const timeLayout = "2006-01-02 15:04:05"type User struct {Name string `map:"name,omitempty"` // stringGithub GithubPage `map:"github,dive,omitempty"` // struct diveNoDive StructNoDive `map:"no_dive,omitempty"` // no dive structMyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method}type GithubPage struct {URL string `map:"url"`Star int `map:"star"`}type StructNoDive struct {NoDive int}type Profile struct {Experience string `map:"experience"`Date time.Time `map:"time"`}// its own toMap methodfunc (p Profile) StructToMap() (key string, value interface{}) {return "time", p.Date.Format(timeLayout)}func newUser() User {name := "user"MyGithub := GithubPage{URL: "https://github.com/liangyaopei/structmap",Star: 1,}NoDive := StructNoDive{NoDive: 1}dateStr := "2020-07-21 12:00:00"date, _ := time.Parse(timeLayout, dateStr)profile := Profile{Experience: "my experience",Date: date,}return User{Name: name,Github: MyGithub,NoDive: NoDive,MyProfile: profile,}}func main() {user := newUser()data, _ := json.Marshal(&user)m := make(map[string]interface{})json.Unmarshal(data, &m)fmt.Println(m)}
先将结构体序列化成 [] byte 数组,再从 [] byte 数组序列化成结构体
data, _ := json.Marshal(&user)m := make(map[string]interface{})json.Unmarshal(data, &m)
- 使用简单 劣势
- 效率比较慢
- 不能支持一些定制的键,也不能支持一些定制的方法,例如将struct的域展开等
使用反射
本文实现了使用反射将结构体转成 map 的方法。通过标签(tag)和反射,将上文示例的 newUser() 返回的结果转化成下面的一个map。其中包含struct的域的展开,定制化struct的方法
1.标签识别
使用readTag方法读取域(field)的标签,如果没有标签,使用域的名字。然后读取tag中的选项。目前支持3个选项
- ‘-‘:忽略当前这个域
- omitempty : 当这个域的值为空,忽略这个域
- dive : 递归地遍历这个结构体,将所有字段作为键
如果选中了一个选项,就讲这个域对应的二进制位置为1
const (OptIgnore = "-"OptOmitempty = "omitempty"OptDive = "dive")const (flagIgnore = 1 << iotaflagOmiEmptyflagDive)func readTag(f reflect.StructField, tag string) (string, int) {val, ok := f.Tag.Lookup(tag)fieldTag := ""flag := 0// no tag, use field nameif !ok {return f.Name, flag}opts := strings.Split(val, ",")fieldTag = opts[0]for i := 1; i < len(opts); i++ {switch opts[i] {case OptIgnore:flag |= flagIgnorecase OptOmitempty:flag |= flagOmiEmptycase OptDive:flag |= flagDive}}return fieldTag, flag}
2.结构体的域(field)的遍历
遍历结构体的每一个域(field),判断field的类型(kind)。如果是string,int等的基本类型,直接取值,并且把标签中的值作为key
for i := 0; i < t.NumField(); i++ {...switch fieldValue.Kind() {case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:res[tagVal] = fieldValue.Int()case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:res[tagVal] = fieldValue.Uint()case reflect.Float32, reflect.Float64:res[tagVal] = fieldValue.Float()case reflect.String:res[tagVal] = fieldValue.String()case reflect.Bool:res[tagVal] = fieldValue.Bool()default:}}}
3.内嵌结构体的转换
如果是结构体,先检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,就递归地调用StructToMap方法,然后根据是否展开(dive),来把返回结果写入res的map
for i := 0; i < t.NumField(); i++ {fieldType := t.Field(i)// ignore unexported fieldif fieldType.PkgPath != "" {continue}// read tagtagVal, flag := readTag(fieldType, tag)if flag&flagIgnore != 0 {continue}fieldValue := v.Field(i)if flag&flagOmiEmpty != 0 && fieldValue.IsZero() {continue}// ignore nil pointer in fieldif fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {continue}if fieldValue.Kind() == reflect.Ptr {fieldValue = fieldValue.Elem()}// get kindswitch fieldValue.Kind() {case reflect.Struct:_, ok := fieldValue.Type().MethodByName(methodName)if ok {key, value, err := callFunc(fieldValue, methodName)if err != nil {return nil, err}res[key] = valuecontinue}// recursivedeepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName)if deepErr != nil {return nil, deepErr}if flag&flagDive != 0 {for k, v := range deepRes {res[k] = v}} else {res[tagVal] = deepRes}default:}}...}// call functionfunc callFunc(fv reflect.Value, methodName string) (string, interface{}, error) {methodRes := fv.MethodByName(methodName).Call([]reflect.Value{})if len(methodRes) != methodResNum {return "", nil, fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})", methodName)}if methodRes[0].Kind() != reflect.String {return "", nil, fmt.Errorf("wrong method %s, first output should be string", methodName)}key := methodRes[0].String()return key, methodRes[1], nil}
4.array,slice类型的转换
如果是array,slice类型,类似地,检查有没有实现传入参数的方法,如果实现了,就调用这个方法。如果没有实现,将这个field的tag作为key,域的值作为value
switch fieldValue.Kind() {case reflect.Slice, reflect.Array:_, ok := fieldValue.Type().MethodByName(methodName)if ok {key, value, err := callFunc(fieldValue, methodName)if err != nil {return nil, err}res[key] = valuecontinue}res[tagVal] = fieldValue....}
5.其他类型
对于其他类型,例如内嵌的map,直接将其返回结果的值
switch fieldValue.Kind() {...case reflect.Map:res[tagVal] = fieldValuecase reflect.Chan:res[tagVal] = fieldValuecase reflect.Interface:res[tagVal] = fieldValue.Interface()default:}
完整案例
创建 structmap.go 文件,写入代码
package structmapimport ("fmt""reflect""strings")const (methodResNum = 2)const (OptIgnore = "-"OptOmitempty = "omitempty"OptDive = "dive")const (flagIgnore = 1 << iotaflagOmiEmptyflagDive)// StructToMap convert a golang sturct to a map// key can be specified by tag, LIKE `map:"tag"`.// If there is no tag, struct filed name will be used instead// methodName is the name the field has implemented.// If implemented, it uses the method to get the key and valuefunc StructToMap(s interface{}, tag string, methodName string) (res map[string]interface{}, err error) {v := reflect.ValueOf(s)if v.Kind() == reflect.Ptr && v.IsNil() {return nil, fmt.Errorf("%s is a nil pointer", v.Kind().String())}if v.Kind() == reflect.Ptr {v = v.Elem()}// only accept struct paramif v.Kind() != reflect.Struct {return nil, fmt.Errorf("s is not a struct but %s", v.Kind().String())}t := v.Type()res = make(map[string]interface{})for i := 0; i < t.NumField(); i++ {fieldType := t.Field(i)// ignore unexported fieldif fieldType.PkgPath != "" {continue}// read tagtagVal, flag := readTag(fieldType, tag)if flag&flagIgnore != 0 {continue}fieldValue := v.Field(i)if flag&flagOmiEmpty != 0 && fieldValue.IsZero() {continue}// ignore nil pointer in fieldif fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {continue}if fieldValue.Kind() == reflect.Ptr {fieldValue = fieldValue.Elem()}// get kindswitch fieldValue.Kind() {case reflect.Slice, reflect.Array:_, ok := fieldValue.Type().MethodByName(methodName)if ok {key, value, err := callFunc(fieldValue, methodName)if err != nil {return nil, err}res[key] = valuecontinue}res[tagVal] = fieldValuecase reflect.Struct:_, ok := fieldValue.Type().MethodByName(methodName)if ok {key, value, err := callFunc(fieldValue, methodName)if err != nil {return nil, err}res[key] = valuecontinue}// recursivedeepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName)if deepErr != nil {return nil, deepErr}if flag&flagDive != 0 {for k, v := range deepRes {res[k] = v}} else {res[tagVal] = deepRes}case reflect.Map:res[tagVal] = fieldValuecase reflect.Chan:res[tagVal] = fieldValuecase reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:res[tagVal] = fieldValue.Int()case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:res[tagVal] = fieldValue.Uint()case reflect.Float32, reflect.Float64:res[tagVal] = fieldValue.Float()case reflect.String:res[tagVal] = fieldValue.String()case reflect.Bool:res[tagVal] = fieldValue.Bool()case reflect.Complex64, reflect.Complex128:res[tagVal] = fieldValue.Complex()case reflect.Interface:res[tagVal] = fieldValue.Interface()default:}}return}// readTag read tag with format `json:"name,omitempty"` or `json:"-"`// For now, only supports above formatfunc readTag(f reflect.StructField, tag string) (string, int) {val, ok := f.Tag.Lookup(tag)fieldTag := ""flag := 0// no tag, use field nameif !ok {return f.Name, flag}opts := strings.Split(val, ",")fieldTag = opts[0]for i := 1; i < len(opts); i++ {switch opts[i] {case OptIgnore:flag |= flagIgnorecase OptOmitempty:flag |= flagOmiEmptycase OptDive:flag |= flagDive}}return fieldTag, flag}// call functionfunc callFunc(fv reflect.Value, methodName string) (string, interface{}, error) {methodRes := fv.MethodByName(methodName).Call([]reflect.Value{})if len(methodRes) != methodResNum {return "", nil, fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})", methodName)}if methodRes[0].Kind() != reflect.String {return "", nil, fmt.Errorf("wrong method %s, first output should be string", methodName)}key := methodRes[0].String()return key, methodRes[1], nil}
创建 to_map_test.go 文件,写入代码
package struct_to_map_testimport ("encoding/json""reflect""strconv""strings""testing""time""github.com/liangyaopei/structmap")const timeLayout = "2006-01-02 15:04:05"type Profile struct {Experience string `map:"experience"`Date time.Time `map:"time"`}// its own toMap methodfunc (p Profile) StructToMap() (key string, value interface{}) {return "time", p.Date.Format(timeLayout)}type MySlice []int// its own toMap methodfunc (a MySlice) StructToMap() (string, interface{}) {key := "array"b := strings.Builder{}if len(a) == 0 {return key, b.String()}for i := 0; i < len(a); i++ {b.WriteString(strconv.Itoa(a[i]) + ",")}return key, b.String()}func TestCallMethod(t *testing.T) {arr := MySlice{1, 2, 3}v := reflect.ValueOf(&arr)typ := v.Type()t.Logf("type:%v,value:%v", v.Type(), v)methodName := "StructToMap"method, ok := typ.MethodByName(methodName)t.Logf("method:%v,ok:%v", method, ok)methodRes := v.MethodByName(methodName).Call([]reflect.Value{})key := methodRes[0].String()resValue := methodRes[1].String()t.Logf("key:%s,value:%v", key, resValue)}// alias typetype Gender intconst (male Gender = 1female Gender = 2)// Dive structtype GithubPage struct {URL string `map:"url"`Star int `map:"star"`}type StructNoDive struct {NoDive int}// User is used for demonstrationtype User struct {Name string `map:"name,omitempty"` // stringEmail *string `map:"email_ptr,omitempty"` // pointerMyGender Gender `map:"gender,omitempty"` // type aliasGithub GithubPage `map:"github,dive,omitempty"` // struct diveNoDive StructNoDive `map:"no_dive,omitempty"` // no dive structMyProfile Profile `map:"my_profile,omitempty"` // struct implements its own methodArr []int `map:"arr,omitempty"` // normal sliceMyArr MySlice `map:"my_arr,omitempty"` // slice implements its own method}func newUser() User {name := "user"email := "yaopei.liang@foxmail.com"myGender := maleMyGithub := GithubPage{URL: "https://github.com/liangyaopei",Star: 1,}NoDive := StructNoDive{NoDive: 1}dateStr := "2020-07-21 12:00:00"date, _ := time.Parse(timeLayout, dateStr)profile := Profile{Experience: "my experience",Date: date,}arr := []int{1, 2, 3}myArr := MySlice{11, 12, 13}return User{Name: name,Email: &email,MyGender: myGender,Github: MyGithub,NoDive: NoDive,MyProfile: profile,Arr: arr,MyArr: myArr,}}func TestStructToMap(t *testing.T) {user := newUser()tag := "map"methodName := "StructToMap"res, err := structmap.StructToMap(&user, tag, methodName)if err != nil {t.Errorf("struct to map:%s", err.Error())return}for k, v := range res {t.Logf("k:%v,v:%v", k, v)}}type benchmarkUser struct {Name string `json:"name"`Age int `json:"age"`Address string `json:"address"`Contact string `json:"contact"`}func newBenchmarkUser() benchmarkUser {return benchmarkUser{Name: "name",Age: 18,Address: "github address",Contact: "github contact",}}func BenchmarkStructToMapByJson(b *testing.B) {user := newBenchmarkUser()b.ResetTimer()for i := 0; i < b.N; i++ {data, _ := json.Marshal(&user)m := make(map[string]interface{})json.Unmarshal(data, &m)}}func BenchmarkStructToMapByToMap(b *testing.B) {user := newBenchmarkUser()tag := "json"methodName := ""b.ResetTimer()for i := 0; i < b.N; i++ {structmap.StructToMap(&user, tag, methodName)}}
