Go語言的面向對象程式設計簡單而幹淨,通過非侵入式接口模型,否定了C/C++ Java C#等傳統面向對象程式設計語言的複雜度的必要性,我們發現在Go中即使簡單的組合也能達到傳統面向對象語言的效果,而且耦合度非常低,按照Go的作者之一也就是C語言的作者之一說的一句話:Go是更好的C語言。
1、Go中任意類型Any即 interface{}類型,也就是空接口,可以指派為任意類型
2、可以為其他類型 内置類型 不包括指針類型添加相應的方法 但是注意的一點是一定要用别名。。進行包裝
記住想要 為類型 添加新的方法 那麼請把類型定義别名,别名和原來的類型就不一樣了成了新類型
在Go中隻能對非本地類型添加方法......也就是int不能添加方法 需要 type Int int 才可以為Int添加方法
package main
import(
"fmt"
)
type Integer int
func (a Integer) Less(b Integer)bool {
return a<b
}
func main(){
var a Integer=1
r:=a.Less(2)
fmt.Println(r)
/////用法2 組合之指針
func (a *Integer) Less(b Integer)bool {
*a-=10
return *a<b
type Int int
func (a *Int) LessIne(b Int)bool{
var aa int=10
a=Integer(aa)
3、go中不存在this指針,通過文法糖顯式傳遞對象
在Go語言中沒有隐藏的this指針”這句話的含義是:
方法施加的目标(也就是“對象” )顯式傳遞,沒有被隐藏起來;
方法施加的目标(也就是“對象” )不需要非得是指針,也不用非得叫this。
4、用type定以後的别名類型就是新類型了 隻有強制轉換才能使用
a=Integer(aa) //不強制轉換會編譯出錯的
5、Go中的值語義和引用
channel map本質是指針 因為複制他們沒有意義 ........數組切片 []type 本質上是值類型 interface接口非常重要
值語義和引用語義的差别在于指派,比如下面的例子:
b = a
b.Modify()
如果b的修改不會影響a的值,那麼此類型屬于值類型。如果會影響a的值,那麼此類型是引用
類型。
Go語言中的大多數類型都基于值語義,包括:
基本類型,如byte、int、bool、float32、float64和string等;
複合類型,如數組(array) 、結構體(struct)和指針(pointer)等。
Go語言中類型的值語義表現得非常徹底。我們之是以這麼說,是因為數組。
如果讀者之前學過C語言,就會知道C語言中的數組比較特别。通過函數傳遞一個數組的時
候基于引用語義, 但是在結構體中定義數組變量的時候基于值語義 (表現在為結構體指派的時候,
該數組會被完整地複制) 。
Go語言中的數組和基本類型沒有差別,是很純粹的值類型,例如:
var a = [3]int{1, 2, 3}
var b = a
b[1]++
fmt.Println(a, b)
該程式的運作結果如下:
[1 2 3] [1 3 3]。
這表明b=a指派語句是數組内容的完整複制。要想表達引用,需要用指針:
var b = &a
fmt.Println(a, *b)
[1 3 3] [1 3 3]
這表明b=&a指派語句是數組内容的引用。變量b的類型不是[3]int,而是*[3]int類型。
Go語言中有4個類型比較特别,看起來像引用類型,如下所示
數組切片:指向數組(array)的一個區間。
map:極其常見的資料結構,提供鍵值查詢能力。
channel:執行體(goroutine)間的通信設施。
接口(interface) :對一組滿足某個契約的類型的抽象。
但是這并不影響我們将Go語言類型看做值語義。下面我們來看看這4個類型。
數組切片本質上是一個區間,你可以大緻将[]T表示為:
type slice struct {
first *T
len int
cap int
因為數組切片内部是指向數組的指針,是以可以改變所指向的數組元素并不奇怪。數組切片
類型本身的指派仍然是值語義。
map本質上是一個字典指針,你可以大緻将map[K]V表示為:
type Map_K_V struct {
// ...
type map[K]V struct {
impl *Map_K_V
基于指針,我們完全可以自定義一個引用類型,如:
type IntegerRef struct {
impl *int
channel和map類似,本質上是一個指針。将它們設計為引用類型而不是統一的值類型的原因
是,完整複制一個channel或map并不是正常需求。
同樣,接口具備引用語義,是因為内部維持了兩個指針,示意為:
type interface struct {
data *void
itab *Itab
接口在Go語言中的地位非常重要。關于接口的内部實作細節,在後面的高階話題中我們再
細細剖析。
7、Go語言中的結構體 組合非繼承
Go語言的結構體(struct)和其他語言的類(class)有同等的地位,但Go語言放棄了包括繼
承在内的大量面向對象特性,隻保留了組合(composition)這個最基礎的特性。
組合甚至不能算面向對象特性,因為在C語言這樣的過程式程式設計語言中,也有結構體,也有
組合。組合隻是形成複合類型的基礎。
上面我們說到,所有的Go語言類型(指針類型除外)都可以有自己的方法。在這個背景下,
Go語言的結構體隻是很普通的複合類型,平淡無奇。例如,我們要定義一個矩形類型:
type Rect struct {
x, y float64
width, height float64
然後我們定義成員方法Area()來計算矩形的面積:
func (r *Rect) Area() float64 {
return r.width * r.height
可以看出, Go語言中結構體的使用方式與C語言并沒有明顯不同。
8、Go中的組合精華 建立結構體指針 和為 結構體擴充成員函數的時候.....傳遞 值類型和指針類型的差別
import "fmt"
type Rect struct{
x,y float64
width,height float64
///如果寫成rct Rect 那麼内部的修改不會影響到 外部結構
///如果寫成rct*Rect那麼内部的修改會影響到外部結構的值 這就是 指針的效果
func (rct *Rect)Area() float64{
rct.width=1000 ///也可以(*rct).width=1000一樣
return rct.width*rct.height
rct:=new(Rect)
//對于結構體指針,...調用方法和值類型一樣直接.唯一的差別是 作為參數傳遞的時候 傳遞的是位址 值可以被修改
//是以進行組合的時候就有兩種選擇
// 可以寫成 var rct*Rect=&Rect{}
//也可以寫成 var rct Rect=Rect{}
//var rct *Rect=new(Rect)
//也可以寫成 var rct Rect=Rect{1,2,3,4}
rct.width=10.0
rct.height=10.0
area:=rct.Area()
fmt.Println(area)
fmt.Println(rct.width)
9、普通的組合繼承 ...........................以及組合指針繼承 以及覆寫 和函數 成員名字沖突
//通過值類型繼承
//Go中繼承屬于匿名組合 .......可以從對象繼承 也可以從指針匿名繼承...
//匿名繼承會去掉包名,,,是以不能同時繼承類名相同的 即使不在同一個包中
type Base struct{
Name string
Age uint8
///為Base結構體組合進去兩個函數
func (pBase*Base) showName(){
fmt.Println("Age",pBase.Name)
func (pBase*Base) showAge(){
fmt.Println("Age",pBase.Age)
//建立Sub結構體
type Sub struct{
//組合Base修改記憶體模型
//匿名組合進Base 對于調用者是不知道的
//即使我們覆寫了 Base的方法 但是我們還是可以通過xxx.Base.xxx()調用基類的方法的
//如果是*Base我們需要在調用處手動添加new Base 否則運作會出錯的
Base
func (pSub*Sub) showName(){
fmt.Println("Before Sub ShowName")
pSub.Base.showName()
fmt.Println("After Sub ShowName")
obj:=new(Sub)
obj.Name="張三"
obj.Age=15
obj.showName()
obj.showAge()
///通過指針類型繼承
*Base
//由于使用指針繼承是以 我們要設定匿名組合模闆的記憶體對象 位址
obj.Base=&Base{}
10、Go語言的可見性 權限是包一級的,包外的不能通路包内的小寫開頭成員......包内無所謂
11、Go的非侵入式接口 和實作
/////Go語言會為每一個成員函數 自動生成對應的函數 比如 func(a *A) 會自動生成 func (a A) .....
///反過來則不行 因為 func (a A)這時候傳遞的是形參 (&a).xx()改變的是 參數 副本 而不是 外部類
///非侵入式接口
////接口 和實作完全分析 減少耦合
///實作方隻負責實作 接口方隻負責封裝自己的借口就行...實作方甚至不知道 有這個接口的存在 這就是 Go的 非侵入式接口的特點
type IFly interface{
fly()
type ISay interface{
say()
type Bird struct{
//由于匿名傳遞進來的是指針類型 所對于接口的指派必須是 指針
func (pBird*Bird) fly(){
fmt.Println("i am a bird, i can fly()!")
//由于匿名傳遞的不是指針類型是值類型 是以接口指派 可以不是指針而是值
func (pBird Bird) say(){
fmt.Println("i am a bird, i can say()!")
birdObj:=Bird{}
var iFly IFly=&birdObj
iFly.fly()
var iSay ISay=birdObj
iSay.say()
13、接口之間是可以互相指派的
實作了相同方法的接口可以互相指派,如果接口B是A非超集,那麼 A可以指派為B
對象不可以被指派為接口 ,繁殖接口可以被指派為實作了 某些方法的對象 或者包含他方法的 接口對象
type IFly1 interface{
type ISay1 interface{
////接口之間的指派
var iFly1 IFly1=iFly
iFly1.fly()
14、Go中的值類型非常的徹底 數組都是值類型
15、關于給類型添加String()方法 相當于 其他語言的toString 用于列印輸出
func (pBird Bird) String() string{
return "aaaaaaaaaa"
fmt.Println(birdObj)
16、接口的組合 就是把多個接口組合到一起......接口中隻有函數沒有屬性
type ISay_Fly interface{
ISay
IFly
17、接口查詢 obj,ok=val.(Interface) 傳回查詢的接口 并且傳回查詢結果
x.(type) 擷取類型 隻能在switch中用
x.(OterTypeInterface) 判斷x是否是指定接口類型 傳回指定接口對象,和查詢結果
在Go語言中,還可以更加直截了當地詢問接口指向的對象執行個體的類型,例如:
var v1 interface{} = ...
switch v := v1.(type) {
case int: // 現在v的類型是int
case string: // 現在v的類型是string
...
就像現實生活中物種多得數不清一樣,語言中的類型也多得數不清,是以類型查詢并不經常
使用。它更多是個補充,需要配合接口查詢使用,例如:
type Stringer interface {
String() string
func Println(args ...interface{}) {
for _, arg := range args {
switch v := v1.(type) {
case int: // 現在v的類型是int
case string: // 現在v的類型是string
default:
if v, ok := arg.(Stringer); ok { // 現在v的類型是Stringer
val := v.String()
// ...
} else {
}
}
}
當然,Go語言标準庫的Println()比這個例子要複雜很多,我們這裡隻摘取其中的關鍵部
分進行分析。對于内置類型,Println()采用窮舉法,将每個類型轉換為字元串進行列印。對
于更一般的情況,首先确定該類型是否實作了String()方法,如果實作了,則用String()方
法将其轉換為字元串進行列印。否則,Println()利用反射功能來周遊對象的所有成員變量進
行列印。
是的,利用反射也可以進行類型查詢,詳情可參閱reflect.TypeOf()方法的相關文檔。此
外,
18、 Any類型 對于匿名結構體指派給任意類型 沒法取出 具體每個匿名結構體的内部屬性 隻能前部列印 通過系統預設的String()函數
var any1 interface{}=1
var any2 interface{}="b"
var any3 interface{}=struct{x ,y string}{"hello,world","aaaaa"}
fmt.Println(any1,any2,any3)
由于Go語言中任何對象執行個體都滿足空接口interface{},是以interface{}看起來像是可
以指向任何對象的Any類型,如下:
var v1 interface{} = 1 // 将int類型指派給interface{}
var v2 interface{} = "abc" // 将string類型指派給interface{}
var v3 interface{} = &v2 // 将*interface{}類型指派給interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
當函數可以接受任意的對象執行個體時,我們會将其聲明為interface{},最典型的例子是标
準庫fmt中PrintXXX系列的函數,例如:
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
總體來說,interface{}類似于COM中的IUnknown,我們剛開始對其一無所知,但可以通
過接口查詢和類型查詢逐漸了解它。