翻译进度
17
分块数量
7
参与人数

[奔跑的 Go] 教程十二、深入学习 Go 语言的结构(Struct)

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。

Golang

跟传统的面向对象编程不同, go 没有类对象这样的数据结构。但是 go 语言有可以容纳复杂数据的结构体。

☛ 什么是结构体?

结构体 或结构可以与面向对象编程中的 概念进行比较。 如果您不知道 OOP 是什么,那么你可以将结构视为一个配方,它声明了成分和它们的量。

结构具有相同或不同类型的不同字段。 结构体主要用于需要定义由不同的单个字段( properties )组成的模式类型时。 例如** ross **是一种**员工**(结构),它有** firstName *** * LastName ****工资**** fullTime **状态(架构字段)。

miss201 翻译于 1周前

☛ 声明结构体类型

 结构体 是一种包含数据结构的设计模式。 为了更方便, 我们可以使用 类型别名 来简单的声明一个结构体。可以这样生成一个结构体

type StructName struct{
    field1 fieldType1
    field2 fieldType2
}

在上面的例子中, StructName 是一个结构体, field1 和 field2 分别是 fieldType1 和 fieldType2 类型的字段。

huazi 翻译于 1周前

我们来创建一个上面我们提到的名为Employee(雇员 )的结构体,它包含一些字段。

type Employee struct{
    firstName string
    lastName string
    salary int
    fullTime bool
}

你可以将我们在variables课程中学到的,将相同类型不同字段的定义写在同一行。所以我们可以编写如下程序:

type Employee struct {
    firstName, lastName string
    salary int
    fullTime bool
}
php_go 翻译于 1周前

☛ 创建一个结构体

由于我们创建了一个结构类型 Employee,让我们用它来创建一个 ross 结构体。因为 Employee 是一种类型,所以创建该类型的变量遵循常规方法。

https://play.golang.org/p/c_Gf7YCXBJW

上面程序的输出看起来很奇怪,但是它给出了一个结构的 零值 ,因为我们只定义了结构类型 Employee 的变量 ross ,但是并没有初始化它。结构的零值是其中各自具有零值的字段。因为 string 的零值将是 ""(空字符串被打印了出来但是您无法看到),int 的空值是 0 ,bool 的空值是 false

当我们讨论结构体时,我们指的是包含 Employee 类型的变量。所以 Employee 是 结构体类型 而 ross 是 结构体,结构体是一种内置数据类型。在面向对象编程(OOP)中,我们将 Employee 称之为一个类, ross 称之为一个对象,而 class 是一个关键字。

如梦又似幻 翻译于 6天前

☛ 获取与设置结构体的字段

获取与设置结构体的字段非常简单。当结构体被定义后,使用.)符号,用struct.field的语法来访问结构体字段。

在上面的程序中我们定义了一个拥有4个字段名为ross的结构体。让我们来给ross填充一些信息。例如,要给rossfirstName字段赋值,你需要使用ross.firstName = "ross"

https://play.golang.org/p/Fw_Z6uBCinZ

php_go 翻译于 5天前

☛ 初始化结构体

我们可以在创建结构体的同时将字段赋值,而不是创建结构体后再分别给字段赋值。

https://play.golang.org/p/FGJ0Ja4WM-F

我们使用简写符号:=来创建变量,go会自动推导出结构体类型Employee。我们在salary字段之前初始化了fullTime字段,显然,字段初始化的顺序是可以随意的。

你可以只初始化一部分字段,而让其它的字段保持零值。在下面的例子中,因为薪水的零值是0,所以结构体变量ross语义上为 {ross Bing 0 true}

ross := Employee{
    firstName: "ross",
    lastName:  "Bing",
    fullTime:  true,
}

还有另一种方法来初始化结构体,它没有指明字段名,如下所示。

ross := Employee{"Ross", "Geller", 1200, true}

上面的语法完全有效。在创建结构体时你没有指明字段名的话,你的字段值的顺序需要与结构体声明时定义的字段顺序保持一致。

php_go 翻译于 4天前

☛ 匿名结构体

 anonymous struct 就是一个没有显式定义结构体类型别名的结构体。到目前为止,我们创建了Employee结构体类型的别名,可从ross推导。但是在匿名结构体的情况下,我们没有定义任何结构体类型别名,使用相同的语法在行内完成结构体的创建与值的初始化。

https://play.golang.org/p/Np7Y8LuOdql

在上面的程序中,我们在创建名为monica的结构体时没有定义结构休类型别名。
当不想重用结构体类型时,这非常有用。
你可能会有疑惑ross的类型是Employee,那 monica的类型是什么呢。使用fmt.Printf%T语法格式,我们得到以下结果:

struct { firstName string; lastName string; salary int; fullTime bool }

这并没有什么奇怪的。如果我们没有给它定义类型别名,它本身就是这个样子。因此我们知道要避免重复编写上面的结构体类型,我们需要定义别名。

php_go 翻译于 3天前

☛ 结构体指针

我们可以在一条语句中创建指向结构体的指针,而不是创建结构。这省去了创建结构体(变量),然后再将指针指向该变量的步骤。

创建结构体指针的语法如下:

s := &StructType{...}

让我们来创建一个名为ross的结构体指针

https://play.golang.org/p/fph03X-T-bu

在上面的程序中,由于ross是一个指针,我们需要使用*ross来取值,需要使用(*ross).firstName来访问 firstName字段,这样编译器就不会在 (*ross).firstName 和*(ross.firstName)的语义上面搞混淆。

但是go提供了另一种语法来访问字段,例如array指针。我们不必使用*,而是直接使用指针来访问字段。

ross := &Employee{
    firstName: "ross",
    lastName:  "Bing",
    salary:    1200,
    fullTime:  true,
}

fmt.Println("firstName", ross.firstName)
php_go 翻译于 1天前

☛ 匿名字段

你可以定义一个没有任何字段名的结构体类型。如果你只是定义了字段的类型,go将使用类型名来作为字段名。

https://play.golang.org/p/oIIIvnrTWdO

在上面的程序中,我们定义一个名为Data的结构体类型,它有3个字段,名称和类型分别是stringint 与 bool。与其他结构体类型相比,Data结构体类型并没有什么不同。在这个例子中,go仅仅帮我们自动创建了字段名称。你可以在字段的命名中混合一些匿名字段,如下所示。

type Employee struct {
    firstName, lastName string
    salary              int
    bool
}
php_go 翻译于 1天前

☛ Nested struct

A struct field can also be a struct. A nested struct is struct that is a field of another struct. Let’s see an example

https://play.golang.org/p/uGsHK2ztQ5o

As you can see in above example, we have created new struct type Salarywhich defines an employee’s salary. Then we modified Employee struct type with instead of int type, salary field is now a struct of type Salary. When creating ross struct of type Employee, we initialized all fields even salaryfield. We have used short approach of excluding field names while initialize salary struct.

Normally, you would access a field of a struct like struct.field as we have seen. You can access salary struct in the same manner like ross.salarywhich will give your salary struct. You can then access (or update) fields of salary using same approach like ross.salary.<field>. Let’s see that in action.

https://play.golang.org/p/jVs385qTwZo

☛ Promoted fields

As we have seen anonymous fields where a type can actually be a field name, we can also use this approach in our nested structs.

https://play.golang.org/p/wzFIIOZkoqT

Using previous example of nested struct, we removed salary field name and just used Salary struct type to create anonymous fields. Then you can use .approach to get and set nested salary fields.

But cool thing about go is when we use anonymous nested struct, all the nested struct fields are automatically available in parent struct.

https://play.golang.org/p/XKn6-rKvPg9

If a nested anonymous struct has a same field (field name) as in the parent struct, then inner struct field won’t get promoted. Only **non-conflicting** fields will get promoted.

☛ Exported fields

As we seen in packages lesson, any variable or type which starts from capital letter is exported from that package. In case of struct, we made sure that all our structs used in this lesson are exported, hence they start with capital letter viz. EmployeeSalaryData etc. But the really cool thing about struct is, we can also control which fields of a struct are exported. This follows the same uppercase letter approach.

type Employee struct {
    FirstName, LastName string
    salary int
    fullTime bool
}

In above struct type EmployeeFirstName and LastName are only two fields which are exported.

Let’s create a simple package organization with package name org. We can create a file WORKSPACE/src/org/employee.go and place following code

// employee.go
package org

type Employee struct {
    FirstName, LastName string
    salary              int
    fullTime            bool
}

In main package, we can import Employee struct like below.

// main.go
package main

import (
    "fmt"
    "org"
)

func main() {
    ross := org.Employee{
        FirstName: "Ross",
        LastName:  "Geller",
        salary:    1200,
    }

    fmt.Println(ross)
}

Above program won’t compile and compiler will throw below error.

unknown field 'salary' in struct literal of type org.Employee

This happens because salary field is not exported. We need to use org.Employee as struct type as Employee type came from org package. We could also create type alias in main package to make it simpler.

// main.go
package main

import (
    "fmt"
    "org"
)

type Employee org.Employee

func main() {
    ross := Employee{
        FirstName: "Ross",
        LastName:  "Geller",
    }

    fmt.Println(ross)
}

Above program yields below result

{Ross Geller 0 false}

Looks weird? Because we did not expect values of salary and fullTimefields. When we import any struct from another package, we get struct as is, just that we don’t have any control over them. This is useful when you want to protect some fields but still make them useful for an outsider.

What will happen in case of nested struct?

  • A nested struct must also be declared with uppercase letter, so that other packages can import it.
  • Nested struct fields starting with uppercase letter are exported.
  • If nested struct is anonymous, then it’s fields starting with uppercase letter will be available as promoted fields.

☛ Function fields

If you remember function as type and function as value topics from functions lesson, you can pretty much guess that struct fields can also be functions.

So let’s create a simple struct function field which returns full name of an employee.

https://play.golang.org/p/U73YGQcWkwa

In above program, we have defined struct type Employee which has two string fields and one function field. Just for simplicity, we have created a function type alias FullNameType to function type which takes two strings and return one string. While creating a struct e, we need to make sure field FullNamefollows the function type syntax. In above case, we assigned it with an anonymous function.

Then we simply executed function e.FullName with two string arguments e.FirstName and e.LastName.

In case you are wondering, why we need to pass properties of e (viz. e.FirstName and e.LastName) to e.FullName as FullName field belongs to the same struct e, then you need to see methods lesson (upcoming).

☛ struct comparison

Two structs are comparable if they belong to same type and have same field values.

https://play.golang.org/p/AFkN-AxDSk5

Above program prints true as both ross and rossCopy belongs to same struct type Employee and have same set of values.

However, if a struct has field type which is not comparable, for example mapwhich is not comparable, then the struct won’t be comparable. For example, if Employee struct type had leaves field of type map, we could not do above comparison.

type Employee struct {
    firstName, lastName string
    salary              int
    leaves              map[string]int
}

☛ struct field meta-data

struct give one more ability to add meta-data to the fields. Usually it is used to provide transformation info on how a struct field is encoded to or decoded from another format (or stored/retrieved from a database), but you can use it to store whatever meta-info you want to, either intended for another package or for your own use.

This meta information is defined by string literal (read *strings* lesson) like below

type Employee struct {
    firstName string `json:"firstName"`
    lastName  string `json:"lastName"`
    salary    int    `json: "salary"`
    fullTime  int    `json: "fullTime"`
}

In above struct, we are using struct type Employee for JSON encoding/decoding purpose. You will learn more struct meta-data in upcoming lessons like jsonvalidation and others.

本文章首发在 LearnKu.com 网站上。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/rungo/structures-in-g...

译文地址:https://learnku.com/golang/t/29489

参与译者:7
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!