Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).
Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.
Go语言的FQA页面:Frequently Asked Questions (FAQ) - The Go Programming Language?
1、封装数据和行为(方法)
Go语言封装数据:
/*type Name struct {}*/package encapsulationtype Employee struct { Id string Name string Age int}
数据的初始化:
package encapsulationimport "testing"type Employee struct {Id stringName stringAge int}func TestCreateEmployeeObj(t *testing.T) {// 方法一e := Employee{"0", "Bob", 20}// 方法二e1 := Employee{Name: "Mike", Age: 30}// 方法三,使用new关键字(返回的是指针,使用.访问数据)e2 := new(Employee) // 返回指针e2.Id = "2"e2.Name = "Rose"e2.Age = 22t.Log(e)t.Log(e1)t.Log(e1.Id)t.Log(e2)t.Logf("e is %T", e)t.Logf("e2 is %T", e2)}
封装行为(方法):
package error_test2import ("errors""testing")func GetFibonacci(n int) ([]int, error) {if n < 2 || n > 100 {// 设置error返回10return nil, errors.New("n's should be in [2,100]")}fibList := []int{1, 1}for i := 2; i < n; i++ {fibList = Append(fibList, fibList[i-2]+fibList[i-1])}return fibList, nil}func TestGetFibonacci(t *testing.T) {if v, err := GetFibonacci(-10); err != nil {t.Error(err)} else {t.Log(v)}}
package encapsulationimport ("fmt""testing""unsafe")type Employee struct {Id stringName stringAge int}// 第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制func (e Employee) String() string {fmt.Printf("func object Address is %xn", unsafe.Pointer(&e.Name))return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)}func TestStructOperations(t *testing.T) {e := Employee{"0", "Bob", 20}e2 := new(Employee) // 返回指针e2.Id = "2"e2.Name = "Rose"e2.Age = 22fmt.Printf("e Address is %xn", unsafe.Pointer(&e.Name))fmt.Printf("e2 Address is %xn", unsafe.Pointer(&e2.Name))t.Log(e.String())t.Log(e2.String())}/*输出的地址不一样,说明是值复制*/
第二种定义方式定义实例对象为指针(go都是值传递,但复制的是指针,指向的是同一块内存)
通常情况下为了避免内存拷贝,使用第二种方式
package encapsulationimport ("fmt""testing""unsafe")type Employee struct {Id stringName stringAge int}// 通常情况下为了避免内存拷贝我们使用第二种定义方式(指针,go都是值传递,但复制的是指针,但指向的是同一个)func (e *Employee) String() string {fmt.Printf("func object Address is %xn", unsafe.Pointer(&e.Name))return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)}func TestStructOperations(t *testing.T) {e := Employee{"0", "Bob", 20}e2 := new(Employee) // 返回指针e2.Id = "2"e2.Name = "Rose"e2.Age = 22fmt.Printf("e Address is %xn", unsafe.Pointer(&e.Name))fmt.Printf("e2 Address is %xn", unsafe.Pointer(&e2.Name))t.Log(e.String())t.Log(e2.String())}/*输出地址一样,说明指向的是同一块内存*/
思考:第二种方式是否协程或线程安全?即:假如修改了结构体里边的元素值,会污染到其他线程或者协程吗?
2、结构体类型转换
属性名、属性类型、属性个数、排列顺序都是类型组成部分
- 只有属性名、属性类型、属性个数、排列顺序都相同的结构体类型才能转换(且是强制转换)
package mAInimport "fmt"func main() {type Person1 struct {name stringage int}type Person2 struct {name stringage int}type Person3 struct {age intname string}type Person4 struct {nm stringage int}type Person5 struct {name stringage string}type Person6 struct {name stringage intgender string}var p1 Person1 = Person1{"lnj", 33}var p2 Person2// 类型名称不一样不能直接赋值(Person1、Person2)//p2 = p1// 虽然类型名称不一样, 但是两个类型中的`属性名称`、`属性类型`、`属性个数`、`排列顺序`都一样,所以可以强制转换p2 = Person2(p1)fmt.Println(p2)// 两个结构体类型中的`属性名称`、`属性类型`、`属性个数`都一样,但是`排列顺序`不一样,所以不能强制转换//var p3 Person3//p3 = Person3(p1)//fmt.Println(p3)// 两个结构体类型中的`属性类型`、`属性个数`、`排列顺序`都一样,但是`属性名称`不一样,所以不能强制转换//var p4 Person4//p4 = Person4(p1)//fmt.Println(p4)// 两个结构体类型中的`属性名称`、`属性个数`、`排列顺序`都一样,但是`属性类型`不一样,所以不能强制转换//var p5 Person5//p5 = Person5(p1)//fmt.Println(p5)// 两个结构体类型中的`属性名称`、`属性类型`、`排列顺序`都一样,但是`属性个数`不一样,所以不能强制转换//var p6 Person6//p6 = Person6(p1)//fmt.Println(p6)}
3、匿名属性
- 没有指定属性名称,只有属性的类型, 我们称之为匿名属性
- 任何有命名的数据类型都可以作为匿名属性(int、float、bool、string、struct等)
package mainimport "fmt"func main() {type Person struct {intfloat32boolstring}// 不指定名称初始化per1 := Person{3, 3.14, false, "lnj"}fmt.Println(per1)// 可以把数据类型作为名字显示初始化per2 := Person{int: 3,float32: 3.14,bool: true,string: "lnj",}fmt.Println(per2)// 可以把数据类型当做属性名称操作结构体per2.int = 666fmt.Println(per2.int) // 666}
- Go语言中最常见的匿名属性是用结构体类型作为匿名属性
package mainimport "fmt"func main() {type Person struct {name stringage int}type Student struct {Person // 匿名属性class string}stu := Student{Person{"lnj", 33},"学前一班",}fmt.Println(stu) // {{lnj 33} 学前一班}}
- 如果结构体作为匿名属性, 想访问匿名属性的属性有两种方式
package mainimport "fmt"func main() {type Person struct {name stringage int}type Student struct {Person // 匿名属性class string}stu := Student{Person{"lnj", 33},"学前一班",}fmt.Println(stu) // {{lnj 33} 学前一班}// 方式一: 先找到匿名属性,再访问匿名属性中的属性stu.Person.name = "zs"fmt.Println(stu) // {{zs 33} 学前一班}// 方式二: 直接访问匿名属性中的属性// 系统会先查找当前结构体有没有名称叫做name的属性// 如果没有会继续查找匿名属性中有没有名称叫做name的属性stu.name = "ww"fmt.Println(stu) // {{ww 33} 学前一班}}
- 注意点: 如果多个匿名属性的属性名称相同,那么不能通过方式二操作,只能通过方式一
package mainimport "fmt"func main() {type Person struct {name stringage int}type Class struct {name stringtime string}type Student struct {Person // 匿名属性Class // 匿名属性}stu := Student{Person{"lnj", 33},Class{"学前一班", "2020-12-12"},}fmt.Println(stu) // {{lnj 33} {学前一班 2020-12-12}}// 编译报错, 系统搞不清楚要找哪个name//stu.name = "zs"stu.Person.name = "zs"stu.Class.name = "小学一年级"fmt.Println(stu) // {{zs 33} {小学一年级 2020-12-12}}}
- 注意点: 只有匿名结构体才支持向上查找
package mainimport "fmt"func main() {type Person struct {name string}type Student struct {per Personage int}var stu Student = Student{Person{"lnj"}, 18}//fmt.Println(stu.name) // 报错fmt.Println(stu.per.name) // 必须通过属性进一步查找fmt.Println(stu.age)}
- 注意点: 如果匿名属性是一个结构体类型, 那么这个结构体类型不能是自己
package mainfunc main() {type Person struct {Person // 错误name string}type Student struct {*Student // 正确, 链表age int} var stu Student = Student{age: 32}var stu2 Student = Student{&stu, 18}fmt.Println(stu)fmt.Println(stu2)}