目录
一、行为的定义和实现
1、Go语言是面向对象语言吗?
官方给的解释是:Yes or no (即是也不是),因为面向对象中有个很重要的概念是继承,但是 Go 语言中是不支持继承的,Go 的接口采用了 Duck type 的方式,这和其它主流编程语言是完全不一样的,用起来也更加方便。
2、封装数据和行为
a、结构体定义
//和 c 一样,用关键字 struct 定义结构体,然后把数据成员定义在花括号中
type Employee struct {
Id string
Name string
Age int
}
3、实例的创建和初始化
package encapsulation
import "testing"
//和 c 一样,用关键字 struct 定义结构体,然后把数据成员定义在花括号中
type Employee struct {
Id int
Name string
Age int
}
//创建一个 Employee 对象
func TestCreateEmployeeObj(t *testing.T) {
//方案1
emp1 := Employee {1, "Bob", 24}
//方案2:方案2和方案1的差别就是指定每个 filed 的名字在给值
emp2 := Employee{Id: 2, Name: "Mike", Age: 25}
//方案3,用 new 关键字创建一个指向实例的指针
emp3 := new (Employee) //注意:方案3返回的是一个指针,相当于 emp3 := &Employee{}
//然后给每个成员赋值
emp3.Id = 3
//与其它主要编程语言的差异,通过实例的指针访问成员不需要使用"->", 用"."就可以
emp3.Name = "Jane"
emp3.Age = 18
//方案1和方案2创建的都是 Employee 结构体类型
t.Logf("emp1's type is: %T", emp1) //emp1's type is: encapsulation.Employee
//方案1和方案2,如果加上了取址符后,就和方案3是一样的效果了
t.Logf("emp2 add after & type is: %T", &emp2) //emp2 add after & type is: *encapsulation.Employee
//方案3创建的是 Employee 结构体的指针类型
t.Logf("emp3's type is: %T", emp3) //emp3's type is: *encapsulation.Employee
//通过实例的指针访问成员依旧不需要使用"->", 用"."就可以
t.Logf("emp3.Name = %s", emp3.Name) //emp3.Name = Jane
}
4、行为方法的创建和定义
a、方案1,实例对应方法被调用时,实例的成员会进行值复制
//直接对 Employee 这个结构体类型定义行为,它的行为是一个方法,
//它和普通方法的差别在于,会在方法名前面有一个(emp Employee)类型的声明,
//之所有有这么一小段声明,为了方便我们使用这个实例来访问我们封装的数据
func (emp Employee) employeeBehavior() string {
return fmt.Sprintf("Id:%d - Name:%s - Age:%d", emp.Id, emp.Name, emp.Age)
}
b、方案2,采用指针的方案,实例成员不会进行值复制(推荐)
//通常情况下为了避免内存拷贝,我们使用这种指针的方式
func (emp *Employee) empBehavior() string {
return fmt.Sprintf("Id:%d - Name:%s - Age:%d", emp.Id, emp.Name, emp.Age)
}
c、对比对两种行为方法的不同
package encapsulation
import (
"fmt"
"testing"
"unsafe"
)
//和 c 一样,用关键字 struct 定义结构体,然后把数据成员定义在花括号中
type Employee struct {
Id int
Name string
Age int
}
//直接对 Employee 这个结构体类型定义行为,它的行为是一个方法,
//它和普通方法的差别在于,会在方法名前面有一个(emp Employee)类型的声明,
//之所有有这么一小段声明,为了方便我们使用这个实例来访问我们封装的数据
func (emp Employee) emp1Behavior() string {
fmt.Printf("After run emp1Behavior(), emp.Id's address is: %x\n",
unsafe.Pointer(&emp.Id))
//把 Employee 的成员进行格式化输出
return fmt.Sprintf("Id:%d,Name:%s,Age:%d", emp.Id, emp.Name, emp.Age)
}
//通常情况下为了避免内存拷贝,我们使用这种指针的方式
func (emp *Employee) emp2Behavior() string {
fmt.Printf("After run emp2Behavior(), emp.Id's address is: %x\n",
unsafe.Pointer(&emp.Id))
//把 Employee 的成员进行格式化输出
return fmt.Sprintf("Id:%d,Name:%s,Age:%d", emp.Id, emp.Name, emp.Age)
}
//操作结构体,调用结构体的行为方法,并比较两种行为方法定义方式的区别
func TestStructOperations(t *testing.T) {
emp1 := Employee{Id:4, Name:"Rose", Age:18}
fmt.Printf("Before run emp1Behavior(), emp.Id's address is: %x\n",
unsafe.Pointer(&emp1.Id))
//可以直接调用实例的行为方法
t.Log(emp1.emp1Behavior()) //Id:4,Name:Rose,Age:18
//输出结果:
//Before run emp1Behavior(), emp.Id's address is: c00000c1a0
//After run emp1Behavior(), emp.Id's address is: c00000c1c0
//我们会发现,emp.Id 的地址在行为方法调用前后,内存地址发生了变化,
//说明这种写法会发生内存拷贝,有更大的内存开销
emp2 := &Employee{Id:4, Name:"Rose", Age:18}
fmt.Printf("Before run emp2Behavior(), emp.Id's address is: %x\n",
unsafe.Pointer(&emp2.Id))
//可以直接调用实例的行为方法
t.Log(emp2.emp2Behavior()) //Id:4,Name:Rose,Age:18
//输出结果:
//Before run emp2Behavior(), emp.Id's address is: c00000c220
//After run emp2Behavior(), emp.Id's address is: c00000c220
//我们会发现,emp.Id 的地址在行为方法调用前后,内存地址是同一个,
//说明这种写法不会发生内存拷贝,更值得推荐
}
注:这篇博文是我学习中的总结,如有转载请注明出处:
https://blog.csdn.net/DaiChuanrong/article/details/118071519
上一篇:Go 函数
下一篇:Go面向对象-接口