7.3 使用结构体
在本章前面讲解了结构体的定义和初始化知识,也讲解了使用结构体成员的知识。在本节的内容中,将进一步讲解在Go程序中使用结构体的知识。
7.3.1 将结构体作为函数的参数
在 Go 语言中,结构体是一种自定义数据类型。它允许您将多个不同的变量组合成一个单一的对象,并且可以作为其他函数的参数传递。在将结构体传递给函数时,可以使用值传递或指针传递。当使用值传递时,函数将接收到结构体的副本,而不是原始结构体本身。这意味着,在函数内部对结构体进行任何更改都不会影响到原始结构体。例如在下面的代码中,调用函数CalculateArea()来计算矩形的面积。
type Rectangle struct {
Width float64
Height float64
}
func CalculateArea(rect Rectangle) float64 {
rect.Width = 20.0 // 修改结构体副本
return rect.Width * rect.Height
}
func main() {
r := Rectangle{Width: 10.0, Height: 20.0}
area := CalculateArea(r)
fmt.Println("Area:", area) // 输出 Area: 200
fmt.Println("Width:", r.Width) // 输出 Width: 10
}
在上述代码中,函数CalculateArea()将结构体副本的宽度修改为 20,而没有更改原始结构体。因此输出结果表明矩形的面积为 200,但宽度仍然为 10。
相反,如果想要在函数CalculateArea()内部修改原始结构体,请使用指针。指针传递允许函数直接访问原始结构体的内存地址。例如下面的演示代码:
func CalculateArea(rect *Rectangle) float64 {
rect.Width = 20.0 // 修改原始结构体
return rect.Width * rect.Height
}
func main() {
r := Rectangle{Width: 10.0, Height: 20.0}
area := CalculateArea(&r)
fmt.Println("Area:", area) // 输出 Area: 400
fmt.Println("Width:", r.Width) // 输出 Width: 20
}
在上述代码中,使用指针将结构体传递给 CalculateArea 函数。在该函数中,我们直接访问了原始结构体的内存地址,并修改了其宽度属性。因此,输出结果表明矩形的面积为 400,宽度也已经被修改为 20。
注意:在使用指针时必须小心,确保不会访问未分配内存或空指针,否则可能导致程序崩溃或出错。
实例7-5:图书展示系统(源码路径:Go-codes\7\can.go)
实例文件can.go的具体实现代码如下所示。
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */
/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.toppr.net"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407
/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.toppr.net"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700
/* 打印 Book1 信息 */
printBook(Book1)
/* 打印 Book2 信息 */
printBook(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.toppr.net
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.toppr.net
Book subject : Python 语言教程
Book book_id : 6495700
7.3.2 结构体数组
在 Go 语言中,结构体数组是一种将多个结构体对象存储在单个数组中的方法。例如可以使用以下代码定义一个结构体数组:
type Person struct {
Name string
Age int
}
var people [3]Person
在上述代码中,首先定义了一个名为 Person 的结构体类型,其中包含一个名为 Name 的字符串和一个名为 Age 的整数。接下来,我们定义了一个名为 people 的长度为 3 的 Person 类型数组。
接下来,我们可以使用以下代码初始化结构体数组:
people := [3]Person{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
在上述代码中,使用结构体字面量的方式初始化了一个名为 people 的 Person 类型数组。每个元素都是一个 Person 类型的结构体,并且包含名字和年龄属性。
访问结构体数组的元素与访问其他类型数组的元素相同。例如在下面的代码中,将输出第二个人的姓名和年龄:
fmt.Println("Name:", people[1].Name) // 输出 Name: Bob
fmt.Println("Age:", people[1].Age) // 输出 Age: 30
由此可见,结构体数组在处理大量相似数据时非常有用,它使得可以轻松地按索引访问结构体对象,并对它们进行排序、过滤和其他常见操作。
实例7-6:排序输出学生信息(源码路径:Go-codes\7\sort.go)
在本实例中,使用结构体数组来表示一组学生,然后对这些学生进行排序并输出他们的姓名和年龄。实例文件sort.go的具体实现代码如下所示。
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
}
func main() {
students := []Student{
{Name: "Alice", Age: 20},
{Name: "Bob", Age: 18},
{Name: "Charlie", Age: 19},
}
fmt.Println("Before sorting:")
for _, student := range students {
fmt.Println(student.Name, student.Age)
}
sort.Slice(students, func(i, j int) bool {
return students[i].Age < students[j].Age
})
fmt.Println("\nAfter sorting:")
for _, student := range students {
fmt.Println(student.Name, student.Age)
}
}
在上述代码中,首先定义了一个名为 Student 的结构体类型,其中包含一个名为 Name 的字符串属性和一个名为 Age 的整数属性。接下来,我们创建了一个名为 students 的 Student 类型切片,其中包含三个学生的姓名和年龄。然后,使用函数sort.Slice()对学生按照年龄进行排序,并输出排序后的结果。执行后会输出:
Before sorting:
Alice 20
Bob 18
Charlie 19
After sorting:
Bob 18
Charlie 19
Alice 20
7.3.3 结构体函数
在 Go 语言中,结构体函数是一种特殊类型的函数,它与结构体类型相关联。结构体函数是 Go 语言中一种强大的特性,它使得可以将行为和数据结合在一起,并更好地封装代码。结构体函数使用接收器(或称为方法接收器)来将结构体类型绑定到函数。
例如在下面的代码中,首先定义了一个名为 Rectangle 的结构体类型,并包含两个浮点数属性,分别表示矩形的宽度和高度。
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
接下来,我们定义了函数Area(),并将其绑定到 Rectangle 类型上。在函数Area()中,我们使用接收器 r 来访问结构体的属性,并计算矩形的面积。接下来可以使用以下方式调用 Area 函数:
r := Rectangle{Width: 10.0, Height: 20.0}
area := r.Area()
fmt.Println("Area:", area)
在上述代码中,创建了一个名为 r 的 Rectangle 类型变量,并设置其宽度为 10,高度为 20。然后,我们调用 Area 函数来计算矩形的面积,并将结果存储在变量 area 中。最后,输出变量 area 的值。
注意:当函数绑定到结构体类型时,接收器可以是值类型或指针类型。如果接收器是值类型,函数将使用结构体的副本,而不是原始结构体本身。如果接收器是指针类型,则函数将使用原始结构体,并且对其进行修改也会影响原始结构体。此外,绑定到结构体类型的函数可以具有任何可选参数和返回值。它们与常规函数的定义方式相同,只是添加了接收器参数。
实例7-7:扑克的洗牌和抽牌(源码路径:Go-codes\7\puke.go)
本实例演示了如何使用结构体函数来实现扑克牌的操作,包括洗牌、抽牌等功能。它可以帮助您更好地理解 Go 语言中结构体函数的实用性。实例文件puke.go的具体实现代码如下所示。
import (
"fmt"
"math/rand"
"time"
)
type Card struct {
Rank string
Suit string
}
type Deck []Card
func (d Deck) Shuffle() {
rand.Seed(time.Now().UnixNano())
for i := range d {
j := rand.Intn(i + 1)
d[i], d[j] = d[j], d[i]
}
}
func (d Deck) Draw() (Deck, Card) {
if len(d) == 0 {
return nil, Card{}
}
return d[1:], d[0]
}
func NewDeck() Deck {
ranks := []string{"Ace", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"}
suits := []string{"Clubs", "Diamonds", "Hearts", "Spades"}
deck := make(Deck, 0, len(ranks)*len(suits))
for _, suit := range suits {
for _, rank := range ranks {
card := Card{Rank: rank, Suit: suit}
deck = append(deck, card)
}
}
return deck
}
func main() {
deck := NewDeck()
fmt.Println("New deck:", deck)
deck.Shuffle()
fmt.Println("\nShuffled deck:", deck)
hand, drawnCard := deck.Draw()
fmt.Println("\nDrawn card:", drawnCard)
fmt.Println("Remaining cards:", hand)
}
对上述代码的具体说明如下:
- 首先定义了一个名为 Card 的结构体类型,表示扑克牌中的一张卡片。
- 然后,定义了一个名为 Deck 的自定义类型,表示包含多张卡片的扑克牌组合。
- 接下来,定义了三个函数作为 Deck 类型的结构体函数:Shuffle()、Draw() 和 NewDeck()。其中,函数Shuffle()使用 Fisher-Yates 算法对扑克牌进行洗牌;函数Draw()从牌堆中抽取一张卡片,并返回更新后的牌堆和所抽取的卡片;函数NewDeck()创建一个新的扑克牌组合,并返回一个 Deck 类型切片。
- 在主函数main()中,使用函数NewDeck()创建一个新的扑克牌组合,并输出其内容。然后,我们调用函数Shuffle()对扑克牌进行洗牌,并将结果输出到控制台。最后,我们调用函数Draw()从洗好的牌堆中抽取一张卡片,并输出结果。
执行后会输出:
New deck: [{Ace Clubs} {1 Clubs} {2 Clubs} {3 Clubs} {4 Clubs} {5 Clubs} {6 Clubs} {7 Clubs} {8 Clubs} {9 Clubs} {10 Clubs} {Jack Clubs} {Queen Clubs} {King Clubs} {Ace Diamonds} {1 Diamonds} {2 Diamonds} {3 Diamonds} {4 Diamonds} {5 Diamonds} {6 Diamonds} {7 Diamonds} {8 Diamonds} {9 Diamonds} {10 Diamonds} {Jack Diamonds} {Queen Diamonds} {King Diamonds} {Ace Hearts} {1 Hearts} {2 Hearts} {3 Hearts} {4 Hearts} {5 Hearts} {6 Hearts} {7 Hearts} {8 Hearts} {9 Hearts} {10 Hearts} {Jack Hearts} {Queen Hearts} {King Hearts} {Ace Spades} {1 Spades} {2 Spades} {3 Spades} {4 Spades} {5 Spades} {6 Spades} {7 Spades} {8 Spades} {9 Spades} {10 Spades} {Jack Spades} {Queen Spades} {King Spades}]
Shuffled deck: [{1 Hearts} {King Spades} {5 Clubs} {10 Clubs} {Ace Hearts} {2 Hearts} {6 Diamonds} {Jack Diamonds} {5 Spades} {Jack Clubs} {10 Hearts} {Jack Spades} {8 Clubs} {7 Hearts} {2 Diamonds} {King Hearts} {3 Clubs} {8 Spades} {1 Diamonds} {5 Hearts} {1 Clubs} {10 Diamonds} {Queen Diamonds} {6 Clubs} {2 Clubs} {4 Hearts} {Ace Diamonds} {8 Diamonds} {Queen Hearts} {4 Spades} {Queen Spades} {5 Diamonds} {9 Diamonds} {Ace Spades} {King Clubs} {1 Spades} {3 Diamonds} {Jack Hearts} {3 Spades} {6 Spades} {7 Spades} {2 Spades} {8 Hearts} {10 Spades} {9 Spades} {9 Hearts} {6 Hearts} {4 Diamonds} {7 Diamonds} {3 Hearts} {7 Clubs} {4 Clubs} {Queen Clubs} {Ace Clubs} {9 Clubs} {King Diamonds}]
Drawn card: {1 Hearts}
Remaining cards: [{King Spades} {5 Clubs} {10 Clubs} {Ace Hearts} {2 Hearts} {6 Diamonds} {Jack Diamonds} {5 Spades} {Jack Clubs} {10 Hearts} {Jack Spades} {8 Clubs} {7 Hearts} {2 Diamonds} {King Hearts} {3 Clubs} {8 Spades} {1 Diamonds} {5 Hearts} {1 Clubs} {10 Diamonds} {Queen Diamonds} {6 Clubs} {2 Clubs} {4 Hearts} {Ace Diamonds} {8 Diamonds} {Queen Hearts} {4 Spades} {Queen Spades} {5 Diamonds} {9 Diamonds} {Ace Spades} {King Clubs} {1 Spades} {3 Diamonds} {Jack Hearts} {3 Spades} {6 Spades} {7 Spades} {2 Spades} {8 Hearts} {10 Spades} {9 Spades} {9 Hearts} {6 Hearts} {4 Diamonds} {7 Diamonds} {3 Hearts} {7 Clubs} {4 Clubs} {Queen Clubs} {Ace Clubs} {9 Clubs} {King Diamonds}]
7.3.4 结构体指针
在 Go 语言中,结构体指针是一种特殊类型的指针,它指向一个结构体变量的内存地址。使用结构体指针可以避免复制结构体变量,并在函数间传递大型结构体时提高效率。例如在下面的代码中,首先定义了一个名为 Person 的结构体类型,其中包含一个名为 Name 的字符串和一个名为 Age 的整数。
type Person struct {
Name string
Age int
}
func main() {
p1 := &Person{Name: "Alice", Age: 25}
fmt.Println("Before:", *p1)
updatePerson(p1)
fmt.Println("After:", *p1)
}
func updatePerson(p *Person) {
p.Age = 30
}
接下来,创建了一个指向 Person 类型变量的指针,并将其赋值给变量 p1。然后,我们输出该结构体变量的内容到控制台,并调用函数updatePerson()来修改年龄属性。在函数updatePerson()中,使用结构体指针参数 p 来访问原始结构体变量,并修改其年龄属性。最后,再次输出更新后的结构体变量内容到控制台。执行结果如下:
Before: {Alice 25}
After: {Alice 30}
由此可以看到,在调用函数updatePerson()后,结构体变量的年龄属性已经被更新,并且在主函数 main()中,它的值也发生了变化。这是因为我们使用了结构体指针来操作结构体变量,而不是复制结构体变量本身。
注意:结构体指针是 Go 语言中一种非常有用的特性,可以提高程序的效率和灵活性。但是需要注意,如果使用不当,结构体指针可能会导致内存泄漏和其他问题。因此,在使用结构体指针时一定要小心谨慎。
7.3.5 函数返回结构体类型的值
在Go语言中,一个函数可以返回一个函数值,这个函数值可以是整型、实型、字符型、指针型等。另外,Go语言函数还可以返回一个结构体类型的值,即函数的类型可以定义为结构体类型。例如下面是一个以下是一个示例代码的演示代码。
type Person struct {
Name string
Age int
}
func getPerson() Person {
return Person{Name: "Alice", Age: 25}
}
func main() {
p := getPerson()
fmt.Println(p)
}
在上述代码中,首先定义了一个名为 Person 的结构体类型,其中包含一个名为 Name 的字符串和一个名为 Age 的整数。然后定义了一个函数getPerson(),该函数返回一个 Person 类型的变量。在函数内部,使用结构体字面量的方式创建并初始化一个 Person 类型的结构体,并将其作为函数的返回值。最后,在主函数main()中调用函数getPerson(),并将返回值存储在变量 p 中。然后,输出变量 p 的内容到控制台。输出结果如下:
{Alice 25}
由此可见,成功地从函数中返回了一个包含两个属性的结构体类型的值,并在主程序中对其进行了操作。这说明了 Go 语言中函数返回结构体类型值的实用性。