作者:Mayank Gupta
四哥水平有限,如有翻译或理解错误,烦请帮忙指出,感谢!
这篇文章源自 Medium,文章点赞 900+。需要提醒的是 Golang 并不是面向对象语言。虽然 Go 提供了 struct 这种数据类型,并可以定义与之相关联的方法,但这与我们在其他语言中认为的面向对象仍然是截然不同。
原文如下:
这篇文章重点介绍 Golang 的面向对象体系结构。Go 语言没有类,因此支持面向对象模式的唯一方法就是使用 struct。
使用 Go语言创建 struct
struct 可以用来表示包含多个键值对的复杂对象。
我们一起来假设一种场景:我们想用一种数据结构表示公司里的员工。为了表示一名员工,该结构需要包含所有与员工相关数据的键值对。员工实体可以由多个键值对组成,例如 Name,Age,Designation 和 Salary。所有这些键值对属性共同确定了公司的某一名员工。
让我们来创建一个包含基本属性的简单结构体 Employee。
type Employee struct {
Name string
Age int
Designation string
Salary int
}
上面的代码包含如下的构建块:
-
Go 语言中,type 关键字可以用来定义一种新的数据类型;
-
可以将 Employee 作为新的结构名称传递;
-
关键字 struct 提示我们正在创建一种新的 struct 数据类型;
-
可以根据属性类型给属性赋值;
Golang 确保严格的类型安全,因此在声明 struct 的时候需要指定属性的类型。如果给这些属性分配其他类型值时将会导致编译错误。
使用 struct 创建对象
一旦创建了新的可用 struct,我们就可以用一些方法来创建新的对象。接下来,我们将详细研究如何使用已定义的 struct(Employee) 创建对象。创建对象的方法有多种,我们将研究各种可能的情况以及他们的优缺点。
使用逗号分隔符
创建对象最简单的方式是将结构体每一个属性都一一赋值,并且使用逗号分隔。必须按结构体定义的顺序,给每个成员都指定值。
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = Employee{"Mayank", 30, "Developer", 40}
上面的代码,我们通过给所有的成员指定值来创建 struct 对象。创建一个新对象并将其分配给 newEmployee 变量。
上述方法的缺点:
-
创建对象的时候我们需要记住所有结构体成员及其顺序;
-
结构体的所有成员都需要赋值;
传键值对
我们可以通过传键值对的方式避免上述办法的缺点。这种方式在创建对象的时候有两种不同的地方:
-
不需要按结构体成员顺序传值;
-
不需要指定所有的键值对;
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
var otherEmployee = Employee{Designation: "Developer", Name: "Mayank", Age: 40}
通过上述的声明可以看到,我们可以按任意顺序传键值对并且可以忽略一些成员不传值。通过这种方式依然可以创建对象。
未初始化成员的默认值
上面的 case 中,创建 newEmployee 对象的时候,忽略了成员 Salary 没有传值。默认情况下,这些成员会有默认值,默认值取决于变量的类型。例如:
-
int 类型默认值是 0;
-
string 类型默认值是 "";
-
bool 类型默认值是 false;
所以上面的 case 中,因为成员类型是 int,所以 Salary 的值是 0。接下去我们一起看下,如何通过 struct 对象访问成员。
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
// Accessing "Name" Properties...
fmt.Println(newEmployee.Name)
引用对象 vs 值对象
使用上述方法创建对象的另一个重要点是确定对象 newEmployee 是值类型还是引用类型。使用上述声明方法得到的对象是值而非引用。newEmployee 并不指向任何的内存地址。
如果 newEmployee 作为参数传递给其他函数,被调用函数得到的是对象的值。原始对象的值会被拷贝,作为被调函数的参数。因为传递的并非是对象引用,所以在被调用函数里对对象的任何修改不会影响到对象的原始值。让我们通过例子理解下上述场景:
func UpdateEmployee(empDetials Employee) {
empDetails.Name = "Anshul";
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
UpdateEmployee(newEmployee)
fmt.Println(newEmployee.Name)
上面的例子,即使我们在 UpdateEmployee() 函数中修改对象的值,原始对象 newEmployee 的值并不会受影响。因为是值传递而非引用传递。
传对象的引用
上面的 case 中,因为传递的是对象的拷贝值,所以在被调函数中对象的修改不会影响到对象的原始值。为了将对象作为引用传递,我们可以传递对象引用而不是值。
调用函数的时候使用取址符 & 取得对象的地址,被调函数的接收参数修改成引用(指针)类型而非值类型。
func UpdateEmployee(empDetials *Employee) {
empDetails.Name = "Anshul";
}
var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}
UpdateEmployee(&newEmployee)
// The original Object sent to the function Updated...
fmt.Println(newEmployee.Name)
上述例子,调用函数时使用的是对象的引用而不是值,在被调函数中对数据的修改会影响对象的原始值。
这种方法的问题在于,我们必须显示地使用 & 取址符取得对象的地址并将其传递给函数。
使用 new 关键字创建对象
另一种创建结构体对象的方法是使用 new 关键字。使用 new 可以创建 struct 类型的新对象并且返回对象地址。通过 new 关键字返回的对象引用,可以给对象的属性赋新值。默认情况下,属性的默认值是各自对应的类型默认值。例如:
-
int 类型默认值是 0;
-
string 类型默认值是 "";
-
bool 类型默认值是 false;
写一段代码,帮助我们清晰理解下这些概念:
type Employee struct {
Name string
Age int
Designation string
Salary int
}
var newEmployee = new(Employee)
fmt.Println(newEmployee.Name)
上面的 case,我们通过 new 创建了新的对象。当创建对象的时候,每个属性都会有默认值,所以上面代码输出的是空字符。
上面的代码返回的是新对象的地址,newEmployee 变量是一个指针,指向的是 Employee 对象。
给函数传递对象
当使用 new 创建对象时返回的是对象的地址,所以如果你将 new 返回值作为参数传递,实际传递的是对象的引用。在被调函数中,对象的修改会影响到对象的原始值。
func UpdateEmployee(empDetials *Employee) {
empDetails.Name = "Anshul";
}
var newEmployee = new(Employee)
newEmployee.Name = "Mayank"
newEmployee.Age = 30
UpdateEmployee(newEmployee)
fmt.Println(newEmployee.Name)
上面的 case,new 函数返回的是对象的地址,所以作为参数传递时,不用使用取址符 &。
为结构体添加函数
结构体不仅可以定义对象的属性,还可以定义对象的行为。我们可以给结构体添加描述其行为的函数。在 Go 语言里面,将结构体与函数相关联是完全不同的。
我们借下面这个例子来理解下:
type Employee struct {
Name string
Age int
Designation string
Salary int
}
func (emp Employee) ShowDetails() {
fmt.Println("User Name: ", emp.Name)
}
上面的代码,我们将新增一个函数与 Employee 结构体绑定。我们需要将函数显式地绑定到结构体。
一起来看下 Go 语言里如何调用与结构体绑定的函数(译者注:称为方法,注意 Go 语言里面方法与函数的区别)。
type Employee struct {
Name string
Age int
Designation string
Salary int
}
func (emp Employee) ShowDetails() {
fmt.Println("User Name: ", emp.Name)
}
var newEmployee = new(Employee)
newEmployee.Name = "Mayank"
newEmployee.ShowDetails()
阅读完整篇文章,我们发现全篇都是在讲解 struct 的使用方法。那为什么原文还有这么多人点赞?仔细想下,文章对 struct 的讲解倒是非常详细的。不过四哥之前的几篇文章好像都讲到了这些知识点,甚至比这个还丰富,有兴趣的同学可以看看推荐阅读。
推荐阅读:
https://blog.51cto.com/u_15289640/2962414
https://blog.51cto.com/u_15289640/2962578
https://blog.51cto.com/u_15289640/2962597