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页面:FQA - Is Go an object-oriented language?
Go语言封装数据:
/*
type Name struct {}
*/
package encapsulation
type Employee struct {
Id string
Name string
Age int
}
数据的初始化:
package encapsulation
import "testing"
type Employee struct {
Id string
Name string
Age 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 = 22
t.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_test2
import (
"errors"
"testing"
)
func GetFibonacci(n int) ([]int, error) {
if n < 2 || n > 100 {
// 设置error返回10
return 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 encapsulation
import (
"fmt"
"testing"
"unsafe"
)
type Employee struct {
Id string
Name string
Age int
}
// 第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
func (e Employee) String() string {
fmt.Printf("func object Address is %x\n", 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 = 22
fmt.Printf("e Address is %x\n", unsafe.Pointer(&e.Name))
fmt.Printf("e2 Address is %x\n", unsafe.Pointer(&e2.Name))
t.Log(e.String())
t.Log(e2.String())
}
/*
输出的地址不一样,说明是值复制
*/
第二种定义方式定义实例对象为指针(go都是值传递,但复制的是指针,指向的是同一块内存)
通常情况下为了避免内存拷贝,使用第二种方式
package encapsulation
import (
"fmt"
"testing"
"unsafe"
)
type Employee struct {
Id string
Name string
Age int
}
// 通常情况下为了避免内存拷贝我们使用第二种定义方式(指针,go都是值传递,但复制的是指针,但指向的是同一个)
func (e *Employee) String() string {
fmt.Printf("func object Address is %x\n", 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 = 22
fmt.Printf("e Address is %x\n", unsafe.Pointer(&e.Name))
fmt.Printf("e2 Address is %x\n", unsafe.Pointer(&e2.Name))
t.Log(e.String())
t.Log(e2.String())
}
/*
输出地址一样,说明指向的是同一块内存
*/
思考:第二种方式是否协程或线程安全?即:假如修改了结构体里边的元素值,会污染到其他线程或者协程吗?
package main
import "fmt"
func main() {
type Person1 struct {
name string
age int
}
type Person2 struct {
name string
age int
}
type Person3 struct {
age int
name string
}
type Person4 struct {
nm string
age int
}
type Person5 struct {
name string
age string
}
type Person6 struct {
name string
age int
gender 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)
}
有命名的数据类型都可以作为匿名属性(int、float、bool、string、struct等)package main
import "fmt"
func main() {
type Person struct {
int
float32
bool
string
}
// 不指定名称初始化
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 = 666
fmt.Println(per2.int) // 666
}
结构体类型作为匿名属性package main
import "fmt"
func main() {
type Person struct {
name string
age int
}
type Student struct {
Person // 匿名属性
class string
}
stu := Student{
Person{"lnj", 33},
"学前一班",
}
fmt.Println(stu) // {{lnj 33} 学前一班}
}
package main
import "fmt"
func main() {
type Person struct {
name string
age 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 main
import "fmt"
func main() {
type Person struct {
name string
age int
}
type Class struct {
name string
time 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 main
import "fmt"
func main() {
type Person struct {
name string
}
type Student struct {
per Person
age int
}
var stu Student = Student{Person{"lnj"}, 18}
//fmt.Println(stu.name) // 报错
fmt.Println(stu.per.name) // 必须通过属性进一步查找
fmt.Println(stu.age)
}
package main
func 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)
}