系统类型
为类型添加方法
Go语言的类型,除了指针之外,都可以组合出自己的方法。
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func Ingeter_Less(a Integer, b Integer) bool {
return a < b
}
func main() {
var a Integer = 1
if a.Less(2) {
fmt.Println(a, "less 2")
}
if Ingeter_Less(a, 2) {
fmt.Println(a, "less 2")
}
}
上述的两种写法是等价的,只是暴露了C++的隐藏this
指针的行为。因此,这是go语言面向对象的最基本的操作。Go语言面向对象操作时,一切都是传值操作,若要改变原来对象的值,则需要传递指针,这与C语言类似,例如:
type Integer int
func (a *Integer) Add(b Integer) { // 注意这里的指针符号
*a += b
}
func main() {
var a Integer = 1
a.Add(3)
fmt.Println(a)
}
值语义和引用语义
值语义可以理解为C语言的赋值操作,等效成复制了原来数据的一个副本。Go语言的基本类型都是值语义,比如int
、bool
、float32
等;复合类型也是值语义,比如数组、指针、结构体等。引用语义就是相当于指针的操作,或者C++的语义,在Go语言中一般借助指针实现。
Go语言的数组是赋值的,这与C语言不同。
var a = [3]int{1, 2, 3}
b := a
那么a
和b
的数据相等,但是b
复制了a
的值,改变b
不会影响a
!!
但是可以使用指针的方式:
func main() {
var a = [3]int{1, 2, 3}
b := &a // 注意取地址
b[0] = 10
fmt.Println(a) // 10 2 3
}
Go语言的4种引用类型:
- 数组切片:指向数组的区间
map
字典:键值查询channel
:执行体间的通信设施- 接口(Interface)
结构体
Go语言的结构体和其他语言的类是同等地位的,Go的结构体组合出不同的方法,完成面向对象的过程。Go语言的面向对象的过程中,只保留了组合的特性,抛弃了继承等的其余的特性。
初始化
常规的方式:
type Rect struct {
x, y float64
width, height float64
}
func (r *Rect) Area() float64 {
return r.width * r.height
}
func main() {
// 4种初始化方式,之一后3种的取地址符号
rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}
}
自定义初始化函数。因为Go语言没有构造函数,我们可以创建一个全局函数完成构造的功能。完整版代码:
package main
import "fmt"
type Rect struct {
x, y float64
width, height float64
}
func (r *Rect) Area() float64 {
return r.width * r.height
}
func NewRect(x, y, width, height float64) (rect *Rect) {
return &Rect{x, y, width, height}
}
func main() {
rect4 := NewRect(0, 0, 100, 200)
fmt.Println(rect4.Area())
}
匿名组合
Go语言的组合文法可以理解成继承,但是这个“继承”的过程是透明的,我们可以直观的看出整个“继承”的结构。匿名组合有值类型和指针类型。因为组合的内部是没有名字的,只有一个结构名,所以匿名。。GoLang的这种方式称为类型嵌入。
值类型
import "fmt"
type Base struct { // 基类
Name string
}
// 给Base组合两个方法
func (base *Base) Foo() {
fmt.Println("Base Foo")
}
func (base *Base) Bar() {
fmt.Println("Base Bar")
}
type Derived struct { // Foo组合一个Base类
Base
}
func (foo *Derived) Bar() { // 扩展自己的Bar方法
foo.Base.Bar() // 调用组合的Base的Bar方法
fmt.Println("Derived Bar")
}
func (foo *Derived) Foo() { // 扩展自己的Foo方法
fmt.Println("Derived Foo")
}
func main() {
derived := &Derived{}
derived.Base.Bar()
derived.Base.Foo()
derived.Bar()
derived.Foo()
}
/*
输出结果
Base Bar
Base Foo
Base Bar
derived Bar
derived Foo
*/
“派生类”的方法没有改写“基类”的方法,因此“基类”的特性就被保留了下来。
注意防止出现死循环的方法,以上述的Base为例:
func (base *Base) Foo() {
base.Foo() // 出现了死循环,该函数永远无法跳出!
}
指针类型
package main
import "fmt"
type Base struct {
Name string
}
func (base *Base) Foo() {
fmt.Println("base Foo")
}
type Derived struct {
*Base
}
func (derived *Derived) Foo() {
fmt.Println("Derived Foo")
}
func main() {
base := Base{"base"}
derived := Derived{&base}
base.Foo()
derived.Foo()
derived.Base.Foo()
}
/*
输出结果:
base Foo
Derived Foo
base Foo
*/
二者的区别
区别在于直接嵌入相当于复制一遍,而指针的就是传入地址,节省空间,对于原来对象的直接使用。
这个例子参考了这个问答
package main
import "fmt"
type Animal struct {
Name string
}
type Person struct {
Animal
}
type pPerson struct {
*Animal
}
func main() {
animal := Animal{Name: "cat"}
person := Person{animal}
pperson := pPerson{&animal}
fmt.Println("Animal:" + animal.Name)
fmt.Println("Person:" + person.Name)
fmt.Println("pPerson:" + pperson.Name)
fmt.Println("------------我是卖萌分割线------------")
animal.Name = "Dog"
fmt.Println("Animal:" + animal.Name)
fmt.Println("Person:" + person.Name)
fmt.Println("pPerson:" + pperson.Name)
// 两者的地址不同,相当于复制了一份
fmt.Println("animal == person? ", animal == person.Animal)
// 两者地址相同
fmt.Println("&animal == pperson.Animal? ", &animal == pperson.Animal)
}
/*
输出结果:
Animal:cat
Person:cat
pPerson:cat
------------我是卖萌分割线------------
Animal:Dog
Person:cat
pPerson:Dog
animal == person? false
&animal == pperson.Animal? true
*/
可见性
接口
本文主要参考了这篇文章
interface
的定义
interface
看成一组方法的集合,interface
本身只是包含了一组方法,但是并不实现这一组方法。Go语言中没有显式地实现接口的关键字(比如Java中的implements
),只要某个对象实现了某个接口中所有的方法,那么这个对象就实现了这个接口。
代码实例:
package main
import "fmt"
type Duck interface {
Sing()
Eat()
}
type YellowDuck struct {
Sex string
}
func (yd YellowDuck) Sing() {
fmt.Println("Yellow duck is singing")
}
func (yd YellowDuck) Eat() {
fmt.Println("Yellow duck eats rice")
}
func (yd YellowDuck) LayEgg() {
fmt.Println("Yellow duck lays an egg")
}
type DonaldDuck struct {
Name string
}
func (dd DonaldDuck) Sing() {
fmt.Println("Donald Duck is singing")
}
func (dd DonaldDuck) Eat() {
fmt.Println("Donald Duck eats sugars")
}
func (dd DonaldDuck) Speak() {
fmt.Println("Donald Duck speaks English")
}
func main() {
yellowDuck := YellowDuck{Sex: "female"}
donaldDuck := DonaldDuck{Name: "Donald DUck"}
yellowDuck.Sing()
donaldDuck.Sing()
}
上述的代码中,我们定义了一个Duck
的接口,有Sing()
和Eat()
两个方法,接口的特性要求不能实现这两个方法。
定义了两个对象,YellowDuck
和DonaldDuck
,这两个对象都各自实现了接口中的方法,因此我们说这两个对象都实现了Duck
接口 。(他们也有自己特有的方法)。
interface
的值
某个interface
类型的变量可以存储实现了该类型接口的任意对象。还是以上述的代码为例子,YellowDuck
和DonaldDuck
都实现了Duck
这个接口,那么Duck
接口类型的变量就可以存储这两个对象。需要注意的是,duck
只能调用Duck
接口声明的方法,不能调用对象特有的方法!
代码实例,只修改主函数部分:
func main() {
yellowDuck := YellowDuck{Sex: "female"}
donaldDuck := DonaldDuck{Name: "Donald Duck"}
var duck Duck
duck = yellowDuck
duck.Sing()
duck.Eat()
// duck.LayEgg() 是错误的,只能调用接口中声明的方法
duck = &donaldDuck
duck.Sing()
duck.Eat()
}
/*
输出结果:
Yellow duck is singing
Yellow duck eats rice
Donald Duck is singing
Donald Duck eats sugars
*/
注意上述代码中,duck = &DonaldDuck
多了一个&
符号。在本例中,这两种方法是等价的,但是如果涉及到的方法需要修改原来对象的值,那么就要用&
的赋值方式了。
代码实例:
package main
type Integer int
func (i *Integer) Add(integer Integer) {
*i += integer
}
func (i Integer) Less(integer Integer) bool {
return i < integer
}
type LessAdder interface {
Add(b Integer)
Less(b Integer) bool
}
func main() {
var a Integer = 1
var lessAdder LessAdder = &a // 在这里必须添加&符号
lessAdder.Add(3)
}
因为这里涉及到了修改原来的数据,因此需要用&
,在一般的 赋值语句中,最好是带着&
符号,以免出错。
接口之间的赋值
如果接口A
的方法是接口B
方法的子集,那么B
可以 直接赋值给A
。
代码实例:
package main
import "fmt"
type F1 interface {
Foo1()
}
type F2 interface {
Foo1()
Foo2()
}
type One struct {
Name string
}
func (one One) Foo1() {
fmt.Println("One Foo1")
}
type Two struct {
Name string
}
func (two Two) Foo1() {
fmt.Println("Two Foo1")
}
func (two Two) Foo2() {
fmt.Println("Two Foo2")
}
func main() {
var f1 F1 = new(One)
var f2 F2 = new(Two)
var f3 F1 = f1
f3 = f2
f3.Foo1()
}
/*
输出结果:
Two Foo1
*/
接口f2
包含了f1
,所以可以直接对f1
的类型赋值f2
接口查询
查询某个对象是否实现了某个接口,该对象必须实现这个接口的所有方法才行!
代码实例,引自这篇博客:
package main
import "fmt"
type IFile interface {
Read()
Write()
}
type IReader interface {
Read()
}
type File struct {
}
func (f *File) Read() {
}
func (f *File) Write() {
}
func main() {
f := new(File)
var f1 IFile = f // ok 因为FIle实现了IFile中的所有方法
var f2 IReader = f1 // ok 因为IFile中包含IReader中所有方法
// var f3 IFile = f2 // error 因为IReader并不能满足IFile(少一个方法)
//
var f3 IReader = new(File) // ok 因为File实现了IReader中所有方法
// var f4 IFile = f3 // error 因为IReader并不能满足IFile 同上..如何解决呢? 要用接口查询
// 接口查询
// 这个if语句检查file1接口指向的对象实例是否实现了IFile接口
// 如果实现了
// 则执行特定的代码。
// 注意:这里强调的是对象实例,也就是new(File)
// File包含IFile里所有的方法
// 所以ok = true
if f5, ok := f3.(IFile); ok {
fmt.Println(f5)
fmt.Println(".............")
}
// 询问接口它指向的对象是否是某个类型
// 这个if语句判断file1接口指向的对象实例是否是*File类型
// 依然ok
if f6, ok := f3.(*File); ok {
fmt.Println(f6)
}
fmt.Println(f1, f2, f3)
if b := 1; true {
fmt.Println(b, "判断1==1;true")
}
if c := 1; false {
fmt.Println(c, "判断1==1;false")
}
}
类型查询
在Go语言中,空的interface
可以存储所有类型的值,所以我们在前面提到,可以使用空的interface
表示形参,以传递任何参数。这种方式也称为类型查询:
package main
import (
"fmt"
)
type IDuck interface {
Speak()
Eat()
}
type DonaldDuck struct { // DonaldDuck类型,实现了IDuck接口
Name string
}
func (dd DonaldDuck) Speak() {
fmt.Println("I'm " + dd.Name)
}
func (dd DonaldDuck) Eat() {
fmt.Println(dd.Name + " is eating")
}
type IBird interface {
Fly()
Eat()
}
type RedBird struct { // RedBird类型,实现了IBird接口
Name string
}
func (rb RedBird) Fly() {
fmt.Println(rb.Name + " can fly")
}
func (rb RedBird) Eat() {
fmt.Println(rb.Name + " is eating")
}
type Dog struct { // Dog类型,但是不会输出
Name string
}
func Foo(args ...interface{}) {
for _, arg := range args {
switch v := arg.(type) {
case int:
fmt.Println("int value: ", v)
case string:
fmt.Println("string value", v)
case float64:
fmt.Println("float64 value:", v)
default: // 非标准类型,我们需要自己判断
if v, ok := arg.(IDuck); ok {
v.Speak()
v.Eat()
} else if v, ok := arg.(IBird); ok {
v.Fly()
v.Eat()
} else {
fmt.Println("No right type !")
}
}
}
}
func main() {
v1 := 1
v2 := "hello world!"
var v3 float64 = 1.1
v4 := DonaldDuck{Name: "Donald Duck"}
v5 := RedBird{Name: "Red Bird"}
v6 := Dog{Name: "Didi"}
Foo(v1, v2, v3, v4, v5, v6)
}
/*
输出结果:
int value: 1
string value hello world!
float64 value: 1.1
I'm Donald Duck
Donald Duck is eating
Red Bird can fly
Red Bird is eating
No right type !
*/
接口组合
不同接口可以组合成一个新的接口,代码实例:
package main
import "fmt"
type IRead interface {
Read()
}
type IWrite interface {
Write()
}
type IReadWrite interface { // 组合接口
IRead
IWrite
}
type IReadWrite1 interface { // 覆盖了两个接口的方法
Read()
Write()
}
type ReadWriter struct{}
func (rw ReadWriter) Read() {
fmt.Println("Reading")
}
func (rw ReadWriter) Write() {
fmt.Println("Writing")
}
func main() {
var rw IReadWrite = ReadWriter{} // 注意是使用了接口的类型
if val, ok := rw.(IReadWrite); ok {
val.Read()
val.Write()
}
if val, ok := rw.(IReadWrite1); ok {
val.Read()
val.Write()
}
}
/*
输出结果:
Reading
Writing
Reading
Writing
*/
有上述代码可以看出,IReadWriter
和IReadWriter1
是等效的接口,两种定义方法都可以。一般我们使用接口组合的方式。
Any
类型
Any
类型就是我们前面提到的空接口interface{}
,它可以是指向任何对象的Any
类型。
var v1 interface{} = 1
var v2 interface{} = 1.1
var v3 interface{} = "Hello world !"
var v4 interface{} = struct{x int}{1}
同时,作为函数的形参表示传递任意类型的参数,前面提到过了,在这里不在赘述。。。