指針pointer
pointer 存儲了一個變量內存的地址
package main
import "fmt"
func main(){
a:=1
var p1 *int = &a
println(p1)
var p2 *int //nil 空指針
fmt.Println(p2)
println(p2)
}
指針的賦值 , 指針指向的內容 也可以用 C語言方式進行改變
同樣 *int **int ***int …
數組指針: 指向數組的指針
指針數組: 存放指針的數組
函數指針: 指向函數的指針 ,函數名就是函數指針
指針函數: 返回指針的函數 eg:
func fun1()* [4]int //返回一個數組指針
結構體:聚合類型
Go 語言拋棄了 class 和 繼承 , 只保留了strict 和 組合
創建 :type name struct{ };
p2 := name{ };
pr := Person{ name :”老王“ , age: 40, sex: ‘woman’ }
結構體 :值類型的數據:深拷貝
怎麼淺拷貝呢? 可以用指針進行賦值就可以了
new 函數 :開闢內存空間 ,返回值是一個指針
new() 在 go語言中是 函數 ,專門創建內存,返回指針的 函數
make 、new 區別
make 用於 內建類型(slice , map, channel)的內存分配 , new用於各種內存的分配;
new(T) 函數 分配了T類型的 零值進行內存填充, 並返回其地址
而 內建函數make(T,args) 創建一個 make(T,args)返回一個非零的T類型, 而不是 *T類型。
從本質上講: 導致 make 和 new兩個函數 結果不同的原因 是因爲創建之後的初始化
p2 := new(int)
p2 是一個指針 指向 0
匿名結構體: 相當於內部類(意義不大, 沒必要用)
結構體中的成員 也稱爲字段
如果在 struct 中 採用 匿名字段 ,其實是用類型名進行字段命名的 ,因此含有匿名字段的 結構體, 不能含有相同類型的字段。
結構體嵌套 要注意: 賦值的時候 注意 結構體是值類型(深拷貝)
那麼 我們應該看需要定義 類型指針 / 類型 對象, 防止沒必要的內存創建/ 無意修改
Go 語言可以模擬的 OOP思想,但Go語言不是 面向對象的語言
GO語言 :沒有 面向對象的 三大特性,, 因爲 Go語言不含 class 對象 , 但是我們可以用 struct 進行模擬
一 : 匿名結構體 模擬實現 繼承
不定義成定義成 匿名結構體 不就成組合了嗎?我感覺這樣,到時候不就成一個對象了嗎?對吧? 暫時先這樣理解試試吧 ? 但是好像跟C中組合不太一樣
package main
type Person struct{
name string
age int
}
type Student struct{
Person // 匿名結構體 模擬實現 繼承
school string
}
func main(){
s1 := new(Student)
s1.name = "海綿寶寶"
s1.age = 18
s1.school = "北京大學"
println(s1.name," ", s1.age," ", s1.school)
s2 := Student{Person{"派大星",17}, "XPU"}
println(s2.name," ",s2.age," ", s2.school)//' ' 不能表示空格代表32ASCII
s3 := Student{Person:Person{name:"擎天柱",age:14}, school:"地球"}
println(s3.name," ",s3.age," ", s3.school)
s4 := Student{Person{"大黃蜂",13}, "泰斯坦"}
println(s4.name," ",s4.age," ",s4.school)
}
匿名對象的 字段 稱爲提升字段 , 可以通過對象直接訪問,並修改
方法 vs 函數
方法: 包含了調用者的 函數稱爲方法:簡單說 包含對象的函數稱爲方法
方法 和 函數的 定義非常相似: 方法 func關鍵字後、方法名前,加上 接收器的類型 ,方法必須通過接受者來調用,誰調用誰就是接收者, 方法的接收者可以在方法的內部 進行訪問
爲什要有方法啊??
方法和函數的意義不一樣 :
1 方法 必須有調用者,是某個類別的功能; 函數 只是一段代碼 ,隨時可以調用
2 方法名可以相同, 只要接收者(調用者)不同就可以, 而 函數名不能衝突
3 方法可以模擬實現 多態(真不愧一個爹搞出來的)
Go 模擬 method 繼承
如果一個 匿名字段(匿名結構體)實現了一個method方法,那麼包含這個匿名字段的結構體也可以調用該匿名字段的方法
同時 外層struct的方法,匿名字段/內層字段 struct 是可以對其進行重寫的(C++叫重定義, JAVA 叫重寫)
內層字段 類似於 C++的內部類 ,內部字段可以訪問外層函數/方法
方法 = 調用者 + 函數
interface 接口
在面向對象世界中: 接口定義對象的行爲 , 表示對象能做什麼,實現行爲的細節是針對對象的 (感覺像多態)
在Go 語言中: 接口 interface 是一組方法簽名,類型爲接口中的所有方法提供定義 就是在 實現接口, 與OOP思想很相似。 接口指定了 類型應該具有的方法,而類型 決定了如何去實現這些方法。
總之:
Go語言 接口:定義了具有共性的一組方法,任何其他類型只要實現了這些方法 就是實現了這個接口。
接口定義了一組方法: 如果某個對象實現了這個接口的所有方法,那麼此對象就實現了該接口。
來看看 具體實現: 真的是多態的變通啊哈哈哈 ~
來看我實現的代碼
package main
//USB 接口
type USB interface{
start()// 接口定義一組具有共性的方法
end()
}
// 鼠標
type Mouse struct {
name string
}
//U盤
type FlashDisk struct {
name string
}
//Mouse 的實現接口
func (m Mouse)start(){
println("鼠標就緒,可以使用,點點點")
}
func (m Mouse)end(){
println("鼠標結束工作 ,退出啦")
}
//U盤 實現接口
func(f FlashDisk)start(){
println("閃迪準備就緒,可以開始傳輸數據了...")
}
func(f FlashDisk)end(){
println("傳輸數據結束,U盤拔出")
}
//test測試函數 : 普通函數,只進行測試(完美轉發)
func testInterfae(usb USB){
usb.start()
usb.end()
}
func main(){
m1 := Mouse{"鼠標"}
println(m1.name)
m1.start()
m1.end()
f1 :=FlashDisk{"閃迪"}
println(f1.name)
f1.start()
f1.end()
//testInterfae(m1)
//testInterfae(f1)
}
原理就是這麼個原理: 但我寫的代碼的耦合性太高了 ,比如: 名字應該通過對象傳遞,而非直接在方法內定義。
需要注意的是 : 1 由接口實現類型對象賦值 得到的接口對象,不能訪問原有的“一些”字段。
2 接口可以向上訪問, 但是 不能向下訪問(e g: 接口 不可以訪問對象的name 等 ,但 對象卻可以訪問接口的方法)
接口的類型
接口的語法:
1 如果一個函數 接收接口類型作爲參數,那麼可以接收該接口任意實現類型的對象作爲參數,甚至可以將不同實現類對象放在接口數組裏。
2 定義一個對象爲接口類型對象, 那麼可以用任意實現類的對象給其賦值
鴨子類型
長的像鴨子, 會游泳 ,會嘎嘎叫 ~
針對動態語言來說 ,是一種類型推斷策略,更關注對象如何被使用。、,並不是類型本身。
雖然Go 語言是弱類型語言,採用非侵入式的方式實現接口。(非侵入式就是 : 接口的 聲明 和 實現 分離)
,提高代碼的服用,減少代碼的耦合。 、
空接口: 可以當作泛型使用,非常方便
還可以配合 map 使用 map[srting] interface{}
package main
import "fmt"
type A interface {
}
type Cat struct{
name string
}
type Dog struct{
name string
}
//空接口 接收任意類型數據
func test1(a A){
fmt.Println(a)
//println(a)可能會打印成地址
}
func test2(a interface{}){
fmt.Println(a)
}
func main(){
var c1 A = Cat{"小老虎"}
c := Cat{"小貓咪"}
d := Dog{"王二狗"}
var A = 4
var B = 3.14
test1(c1)
test1(c)
test1(d)
test1(A)
test1(B)
test2(c1)
test2(c)
test2(d)
test2(3.14)
test2(6)
m1 := make(map[string]interface{})
m1["name"] ="隔壁老王"
m1["age"] =18
m1["sex"] ="man"
fmt.Println(m1)
}
接口允許繼承 ,更允許多繼承
接口的繼承 用嵌套來模擬實現
嵌套的接口 想使用 必須實現所有 被嵌套的接口內的方法
接口的斷言
Go語言的類型斷言 是對接口實行的操作
package main
import (
"fmt"
"math"
)
type Circle struct{
path float64
}
type Square struct{
path int
}
type AA interface {
Area()
CLong()
}
func (s Square)Area(){
fmt.Println("Square Area is:",s.path * s.path)
}
func (s Square)CLong(){
fmt.Println("Square Long is:",s.path * 4)
}
func (c Circle)Area(){
fmt.Println("Circle Area is:",c.path * c.path * math.Pi)
}
func (c Circle)CLong(){
fmt.Println("Circle long is:",c.path * 2 * math.Pi)
}
func getAssert(a AA){
if ret,ok :=a.(Circle); ok{
fmt.Println("it is Circle, path is",ret.path)
}else if ret, ok := a.(Square);ok{
fmt.Println("it is Square, path is ",ret.path)
}else if ret,ok :=a.(*Circle); ok{
fmt.Println("it is Circle, path is",(*ret).path)
}else if ret,ok :=a.(*Square); ok{
fmt.Println("it is Square, path is ",ret.path)
}else{
fmt.Println("i don't know what type a is")
}
}
func testAssert2(a AA){
switch ret := a.(type){ //a.(type) 只能用在 switch 語句中
case Circle:
fmt.Println("Circle",ret.path)
case Square:
fmt.Println("Square",ret.path)
case * Circle:
fmt.Println("Circle",ret.path)
case * Square:
fmt.Println("Square",ret.path)
default:
fmt.Println("i don't what type it is")
}
}
func testAssert(a AA){
getAssert(a)
a.Area()
a.CLong()
}
func main(){
c := Circle{4}
s := Square{4}
d := &Circle{3}
e := &Square{3}
testAssert2(e)
testAssert(d)
testAssert(c)
testAssert(s)
testAssert2(e)
testAssert2(d)
testAssert2(c)
testAssert2(s)
}
type
type 可以自定義一種類型 , 比如將 int 定義爲 myint
但在語法上,myint類型 和int類型 不相同 ,一次不能相互賦值。 但可以前值類型轉換後進行賦值。
給類型區別名 : type newname = typename
例如 type myInt = int 那麼 myInt 就是 int 的別名
來看一個 別名的不同表現吧 :
package main
import "fmt"
type people struct{
name string
}
type person = people
type puiple struct {
person
people
}
func(p person) show1(){
fmt.Println(p.name)
}
func (p people) show2(){
fmt.Println(p.name)
}
func testShow(a puiple){
fmt.Println(a.people.name)
fmt.Println(a.person.name)
}
func main(){
var s puiple
s.person.name ="小明"
s.people.name ="明明"
println(s.person.name)
println(s.people.name)
// testShow(s)
}
/*
type myInt int
type INT = int
func main(){
var a int =4
var a1 myInt = 5
// a = a1// 不能隱式類型轉換 ,但是可以強制類型轉換
a = int (a1)
println(a)
var cc INT = 10
fmt.Printf("%T\n",cc)
}
*/
雖然 person是people 的別名, 但看起來好像是給一個對象開了兩份空間來進行存儲的 , 我們來進行驗證一下
果然: 是兩份內存(其實也很好理解: people/person 結構體中都單獨又一個 name字段 , 而puiple結構體,含一個person結構體, 也含有一個 people 結構體, 因此 是兩份內存)