Go语言编程笔记3:结构体
Go语言的另一大特色是没有类,因此也不会有继承或者多态之类的面向对象特性。这倒是让我相当诧异,因为我接触过的几乎所有的编程语言,如C++\Java\PHP\Python等都是支持面向对象的,一门09年诞生的语言却不支持面向对象,的确相当奇怪。
当然Go语言中并不是没有类似的东西——结构体,只不过在使用上和类有一些差别。
声明和初始化
结构体的声明和初始化并不难:
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
student1 := Student{
name: "Brus Lee",
age: 11,
}
fmt.Println(student1.name, "is", student1.age, "years old")
}
// Brus Lee is 11 years old
需要注意的有这么几个地方:
- 结构体使用关键字
type
和struct
声明,这点和C中的结构体定义类似。 - 结构体声明时其中的结构体变量不需要使用
var
关键字。 - 结构体创建并初始化时的写法类似于Javascript或PHP中的数组或JSON数据的写法,不同的是最后一个键值对必须在结尾使用
,
。
方法
和C不同的是,Go语言中的结构体是可以定义方法的:
package main
import "fmt"
type Student struct {
name string
age int
}
func (self *Student) print() {
fmt.Println(self.name, "is", self.age, "years old.")
}
func main() {
student1 := Student{
name: "Brus Lee",
age: 11,
}
student1.print()
}
// Brus Lee is 11 years old
要关联函数到结构体上,只需要在函数签名前加上(self *Student)
这样的结构体,事实上self
是一个指向当前结构体变量的指针,有点类似于C或C++中对象的this
指针。然后就可以通过结构体变量调用函数,并且在函数中使用结构体指针。
赋值和传参
虽然说加上方法后结构体就很像是类了,但还是有一个显著区别,就是赋值和传参时的表现不同。
我们知道,为了节省内存开销,对象在赋值和传参时都是“引用传递”,实质上就是传递的是指针。但结构体不同,和C中的结构体一样,Go语言中的结构体对应的变量实体,在赋值和传参时都是“值传递”:
package main
import "fmt"
type Student struct {
name string
age int
}
func (self *Student) print() {
fmt.Println(self.name, "is", self.age, "years old.")
}
func change_student(s Student) {
s.age = 1000
}
func main() {
student1 := Student{
name: "Brus Lee",
age: 11,
}
student2 := student1
student2.age = 20
student2.name = "Jack Chen"
student1.print()
student2.print()
change_student(student2)
student2.print()
}
// Brus Lee is 11 years old.
// Jack Chen is 20 years old.
// Jack Chen is 20 years old.
因为是值传递,所以student2
只是student1
的一个“拷贝”,它们本质上是不同的两个结构体,所以自然互不影响,而类似的,在默认情况下传参时候也是“值传递”,接收到的参数实际上也是一个结构体的拷贝,所以修改参数s
并不能对原始结构体产生影响。如果我们要修改原始结构体,则需要使用“引用传递”,或者用C或C++的风格来说就是“传递指针”:
package main
import "fmt"
type Student struct {
name string
age int
}
func (self *Student) print() {
fmt.Println(self.name, "is", self.age, "years old.")
}
func change_student(s *Student) {
s.age = 1000
}
func main() {
student1 := Student{
name: "Brus Lee",
age: 11,
}
student2 := &student1
student2.age = 20
student2.name = "Jack Chen"
student1.print()
student2.print()
change_student(student2)
student2.print()
}
// Jack Chen is 20 years old.
// Jack Chen is 20 years old.
// Jack Chen is 1000 years old.
在这个例子中,student2
是一个student1
的指针,所以两者实际上指向的是同一个结构体变量,修改其中一个就会影响到另一个。而change_student
函数参数也接收的是指针,自然可以修改指针指向的变量内容。
关于为什么Go语言没有将结构体传递和赋值的默认行为设置为引用传递,我猜测大概是为了和C语言中的结构体保持一致性。总之我认为对结构体这种实际上承担了类的用途的复杂变量,尽量使用应用传递来减少内存消耗是一个不错的习惯。
构造函数
遗憾的是,Go语言中的结构体并没有构造函数这种东西,不过我们可以简单的使用一个工厂函数来进行替代:
package main
import "fmt"
type Student struct {
name string
age int
}
func (self *Student) print() {
fmt.Println(self.name, "is", self.age, "years old.")
}
func new_student(name string, age int) *Student {
return &Student{
name: name,
age: age,
}
}
func change_student(s *Student) {
s.age = 1000
}
func main() {
student1 := new_student("Brus Lee", 12)
student2 := new_student("Jack Chen", 20)
student1.print()
student2.print()
}
// Brus Lee is 12 years old.
// Jack Chen is 20 years old.
出于节省不必要的内存开销考虑,这里在new_student
工厂函数中返回了一个Student
的指针,当然也可以直接返回一个结构体,并进行拷贝,并不会影响实际逻辑。
结构体的字段
当然,结构体的自传除了是基本类型外,也可以是结构体,比如我们可以利用结构体构建一个二叉树:
package main
import "fmt"
type Node struct {
left *Node
right *Node
content string
}
func main() {
root := Node{
left: &Node{left: nil, right: nil, content: "left_child"},
right: &Node{left: nil, right: nil, content: "right_child"},
content: "root",
}
fmt.Println(root.content)
fmt.Println(root.left.content)
fmt.Println(root.right.content)
}
// root
// left_child
// right_child
new
虽然Go语言并没有类和面向对象的概念,但有一个和其它语言中new
关键字相似的内置函数new()
,其用途是新建一个指定的结构体变量,并返回其的指针:
package main
import "fmt"
type Student struct {
name string
age int
}
func (self *Student) print() {
fmt.Println(self.name, "is", self.age, "years old.")
}
func main() {
student1 := new(Student)
student2 := new(Student)
student1.name = "Jack Chen"
student1.age = 20
student2.name = "Brus Lee"
student2.age = 15
student1.print()
student2.print()
}
// Jack Chen is 20 years old.
// Brus Lee is 15 years old.
实际上new(Student)
等效于&Student{}
,在Go语言中通过两种方式中的任意一种来创建一个新的结构体都是可以的。蛋一般来说后者可能更常用,因为可以在创建的时候指定结构体中的字段初始化值。