1 字符串的使用

package main
​
import (
    "fmt"
    "unicode/utf8"  // Unicode是4个字节、而utf-8是存Unicode的一种格式
)
​
// 字符串:
​
​
func main() {
    // 1 单独获取每个字符串的字节(bytes)
    // 字符串可以按索引取值,但是取出来是数字,需要转成字符串
    s:="hello world"
    for i:=0;i<len(s);i++{
        fmt.Println(string(s[i]))
    }
​
    // 2 len()得到的是bytes个数,而“中国”两个字符都为3个bytes,所以要循环6次,且是乱码的,因为不是按字符来循环的
    s2 := "中国"
    for i := 0; i < len(s2); i++ {
        fmt.Println(s2[i])  // 是每个bytes对应的十进制数大小
        fmt.Println(string(s2[i]))  // 打印每个bytes对应ASCII码的值
    }
    // 2.1把字符串做成rune的切片
    var r []rune=[]rune(s2)  // 强制类型转换,将s2转化为rune类型,rune是int32类型,且len()rune类型得到的是字符的个数
    for i:= 0; i < len(r); i++ {  // 循环两次
        fmt.Printf("%c ",r[i])  // 占位符(%c:字符  %s:字符串  %T:类型  %d:数字  %f:浮点数)
        fmt.Println(r[i])  // 中和国对应的编码数
        fmt.Println(string(r[i]))  // 将对应编码转化为字符
    }
​
​
    // 通过字节切片构建字符串
    byteSlice1 := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}  // 0x是十六进制的数
    byteSlice2 := []byte{97,98}
    str1 := string(byteSlice1)
    str2 := string(byteSlice2)
    fmt.Println(str1,str2)  // Café ab  其中é属于法语字符
​
    // 通过rune切片构建字符串
    runeSlice:=[]rune{20013,22269}
    str := string(runeSlice)
    fmt.Println(str)  // 中国
​
    // byte(uint8,一个字节)和rune(int32,四个字节)
    // 1个字节就是1个byte
    // 1个字符就是1个rune
​
​
    //4  字符串长度
    s4:="a中国"
    fmt.Println(len(s4))  // 统计的是字节长度 a(1byte)+中(3bytes)+国(3bytes)=7bytes
    fmt.Println(utf8.RuneCountInString(s4))// 按字符统计长度:3
​
​
    // 5 字符串不可变
    s5:="hello"
    fmt.Println(s5[1])  // 101
    fmt.Println(string(s5[1]))  // e
    //s5[1]=97  // 报错
    //s5[1]='a'  // 报错
​
    // 引用字符串的切片可以变,但字符串仍不变
    r5:=[]rune(s5)  // 将s5转为切片类型
    r5[0]=97
    r5[1]='a'
    fmt.Println(string(r5))  // aallo
    fmt.Println(s5)  // hello
​
}
​
​


2 指针

# 指针是一种存储变量内存地址(Memory Address)的变量。
​
package main
​
import "fmt"
​
// 指针:存储    变量内存地址  的     变量
​
/*
1 类型前放 *  表示指针类型,这个类型的指针,指向这个类型的指针
2 在变量前加 & 取地址符号,表示取该变量的地址
3 在(指针)变量前加 * 表示反解,把地址解析成具体的值
*/
​
func main() {
​
    // 1 定义指针
    var a = 10             // 定义一个int类型变量
    var p *int             // 定义一个变量,来存a的地址,p可以存放a的地址
    p = &a                 // &为取地址符,变量前+&可以得到变量地址
    fmt.Println("a的地址", p) //0xc0000b2008   程序只要没停,是一样的,只要重新运行,基本就变了
​
    // 2 指针的指针
    // p也是变量----》p也有地址---》定义一个变量,存p的地址
​
    //var p2 **int=&p
    var p2 = &p // 简写,或 p2:=&p
    fmt.Println("p的地址", p2)
    fmt.Printf("p2的类型:%T", p2) // p2的类型:**int
    var p3 ***int = &p2
    //var p3 ***int=&(&(&a))  // 等同于
    fmt.Println("p2的地址", p3)
​
    // 3 把地址反解成值(在指针前加*)
    fmt.Println(*p) // 10 是a的值 p对应的地址的值,也就是a的值
​
    fmt.Println(*p3)   // p3对应的地址的值,也就是p2的值---》p2是指针,对应p的地址,所以输出p的地址
    fmt.Println(**p3)  // 因为*p3是p的地址、也就是p2,所以*p3也是指针(存地址的变量),指针前加*,是反解地址的值,所以**p3可划为*p2,即p2对应地址的值,即p的值,也就是a的地址
    fmt.Println(***p3) // 10 是a的值  ***p3--**p2--*p(p存的a的地址)--a的地址对应的值--10
​
    // 4 指针零值---nil
    s4 := "cx"
    var p4 *string  // 只定义,没有初始化
    fmt.Println(p4) // <nil>  引用类型的零值都是nil
    p4 = &s4
    fmt.Println(p4) // 0xc000042230  s4的地址
​
    // 5 指针是引用类型---》当参数传递
    a5 := 10
    p5 := &a5       // *int--->指向int类型的指针
    test2(p5)       // 函数通过p5,*p5=99
    fmt.Println(a5) // 99,通过指针,可以修改原来的值
​
    // 6 不要向函数传递数组的指针,而应该使用切片
    // 使用切片的好处是,如果数组长度变了,函数不需要重写
    // 如果是数组,需要再写一个函数对应
​
    var a6 = [4]string{"刘清政老师", "彭于晏", "迪丽热巴"}
    fmt.Println(a6) // [刘清政老师 彭于晏 迪丽热巴 ]
    test3(&a6)
    fmt.Println(a6) // [刘亦菲 彭于晏 迪丽热巴 ]  会改变原来的值
​
    // 调用test4,传切片
    test4(a6[:])
    fmt.Println(a6) // [雷锋 彭于晏 迪丽热巴 ]  会改变原来的值
​
    // 7 如果是数组的指针,不需要解引用,直接使用即可,按索引取值即可
    var a7 = [3]int{8, 9, 10}
    var p7 = &a7
    fmt.Println((*p7)[0]) // 8
    fmt.Println(p7[0])    // 8 支持直接按索引取值
    fmt.Println(p7)       // &[8 9 10]
​
    // 8 go不支持指针运算、
    //var  a8 = [3]int{8,9,10}
    //var p8 *[3]int=&a8
    //fmt.Println(p8--)  报错
​
    // 9 指针数组(数组里放了一堆指针)和数组指针(指向数组的指针)
​
    // 数组指针:指向数组的指针
    var a9 = [3]int{7, 8, 9}
    var p9 = &a9 //数组指针
    fmt.Println(p9)  // &[7 8 9]
​
    // 指针数组:数组里的元素为指针类型
    var x, y, z = 7, 8, 9
    var p10 = [3]*int{&x, &y, &z}
    fmt.Println(p10)  // [0xc00000a100 0xc00000a108 0xc00000a110]
​
}
​
func test2(p *int) { // 接收指针类型,指向int类型的指针
    fmt.Println(*p) //反解,p真正指向的值--->10
    // 修改值,不是改p,改p指向的值也就是a的值
    *p = 99
    fmt.Println(*p) //99
}
​
func test3(p *[4]string) {
    fmt.Println((*p)[1]) // 先对a解引用---》数组---》再取值---》彭于晏
    (*p)[0] = "刘亦菲"      // 把数组第0个位置的值改成了刘亦菲
    fmt.Println(*p)
}
​
func test4(s []string) {
    fmt.Println(s[1])
    s[0] = "雷锋"
    fmt.Println(s)
}
​



3 结构体

// 结构体是用户定义的类型,表示若干个字段(Field)的集合。有时应该把数据整合在一起,而不是让这些数据没有联系。这种情况下可以使用结构体
// 人的类型,有一堆字段:姓名,性别,年龄。。。
​
​
package main
​
import "fmt"
​
// 结构体
​
/*
type 名字 struct{
    字段1 类型
    字段2 类型
}
*/
​
// 定义了一个结构体,用户自定义的类型
type Person1 struct {
    name  string
    age   uint8
    sex   string
    hobby []string
}
​
func main() {
    // 1 结构体定义和使用
    var person Person1
    fmt.Println(person) // { 0  []}  定义没有初始化,有值,结构体是值类型
​
    // 2 定义并初始化
    var person21 Person1 = Person1{} // 没有传值
    hobby2 := make([]string, 3, 4)
    var person22 = Person1{"lqz", 19, "男", nil}       // 有传值,按位置传值
    var person23 = Person1{"lqz", 19, "男", hobby2}     // 有传值,按位置传值,符合顺序,不能少传
    var person24 = Person1{name: "lqz", hobby: hobby2} // 有传值,按关键字,不用符合顺序,可以少传
    fmt.Println(person21, person22, person23, person24)  // { 0  []} {lqz 19 男 []} {lqz 19 男 [  ]} {lqz 0  [  ]}
​
    // 3 结构体初始化后使用
    hobby3:=make([]string,3,4)
    var person3 =Person1{"lqz",19,"男",hobby3}
    // 取出姓名,修改姓名为彭于晏
    fmt.Println(person3.name)
    person3.name="彭于晏"
    fmt.Println(person3.name)
​
    // 加一个篮球爱好(切片已经初始化了,直接用即可,如果没有初始化是nil)
    person3.hobby[0]="篮球"
    //person.hobby[3]="乒乓球" // 报错,越界了,切片只能按长度的下标取值
    person3.hobby=append(person3.hobby,"乒乓球" )  // 用append,在hobby末尾追加  hobby元素为:篮球、""、""、"乒乓球"、
    fmt.Println(len(person3.hobby))   //4
    fmt.Println(cap(person3.hobby))   //4
    fmt.Println(person3)  // {彭于晏 19 男 [篮球   乒乓球]}
​
    // 4 创建匿名结构体--->结构体没有名字---》定义在函数内部,只使用一次--->没有type和名字
    //    需要直接定义并初始化
    p4:=struct{
        name string
        age int
    }{"lqz",19}
    p41:=struct{   // 把数据整合到一起
        name string
        age int
    }{}  // 默认为元素零值
    p41.name="lqz"
    fmt.Println(p4.name)
    fmt.Println(p4)
​
    // 5 结构体零值---》值类型---》空值不为nil---》是结构体每个字段的零值
    // 数字:0
    // 字符串: ""
    // 布尔:  false
    // 数组: [元素的零值,]
    // 结构体: 字段的零值
    //var person Person   // 有零值{ "" 0 "" []}
    //var person Person=Person{name:"lqz",age:19}
    //changePersonName(person) // go 语言参数传递都是copy传递,{ "" 0 "" []}
    //fmt.Println(person)    // 不会影响原来的
​
    // 6 访问结构体字段---》通过.(点) 访问,如果结构体某个字段是引用类型---》引用类型必须要初始化
​
    //7 匿名字段---》字段没有名字--->匿名字段类型不能重复
    type Animal struct {
         string // 字段没有名字,类型名就是字段名
         //sex string
         int   // 字段没有名字
    }
    var animal Animal=Animal{"小鸭子",1}  // 赋初值,按位置
    var animal2 Animal=Animal{string:"小鸡",int:2}  // 赋初值,按关键字,类型名就是字段名
    fmt.Println(animal)
    fmt.Println(animal2)
    // 取字段的值
    fmt.Println(animal.string)
​
    // 8 结构体嵌套--》结构体中套结构体
​
    type Wife struct {
        name string
        height float32
    }
    type Dog struct {
        name string
        age int
        wife Wife  // 类型也可是自己定义的(结构体)
    }
    var dog Dog=Dog{"小奶狗",3,Wife{"小母狗",30.1}}
    fmt.Println(dog)
    // 取出wife的身高
    fmt.Println(dog.wife.height)
​
    // 结构体嵌套---》嵌套匿名结构体
    type Duck struct {
        name string
        age int
        wife struct{
            name string
            age int
        }
    }
​
    var duck Duck=Duck{name:"小鸭子"}
    duck.wife.age=10
    duck.wife.name="小母鸭子"
    fmt.Println(duck)
​
    // 9 结构体嵌套+匿名字段---》字段提升
    type Wife2 struct {
        name string
        height float32
    }
    type Dog2 struct {
        name string
        age int
        Wife2  // 匿名字段,字段没有名字,字段名就是类型名
    }
    var dog2 Dog2=Dog2{name:"小奶狗",age:2,Wife2:Wife2{"小母狗",30.1}}
    // 取出这个小奶狗的wife的身高
    fmt.Println(dog2.Wife2.height)
    fmt.Println(dog2.height) // 字段提升,本来是Wife的字段,被提升到了dog身上
    // name 可以提升吗? 不能---》重复了---》字段重复了,就不能提升
    // 类似于面向对象的继承---》相当于Dog继承了Wife---》子类中,调用父类的属性---》子类对象.属性名
    // Dog重写了name字段---》子类对象.属性名,取到自己的---》优先用自己的,自己没有,用父类的
    // 在子类对象中使用父类的属性  super().属性名----》super()就是咱们这个案例的Wife
​
​
    // 10 导出结构体和字段----》包--->大小写字母开头表示导出---》在其他包可以使用
​
    /*
    entity内的Animal结构体:
        type  Animal struct {
            name string  // 小写开头不能导出
            Age int  // 大写字母开头,表示导出字段---》就可以在外部包使用
        }
    */
​
    //animal :=entity.Animal{Age:8} // 不能按位置了,只能按关键字传参
​
​
    // 11 结构体相等性---->结构体是否能比较,取决于结构体字段---》
    // 如果字段都是可比较的,那结构体可以比较
    // 如果有不可以比较的字段,结构体就不可以比较
    type  Animal3 struct {
        name string
        Age int
    }
    var a1,a2=Animal3{"小狗",2},Animal3{"小狗",1}
    fmt.Println(a1==a2)  // false  值类型,可以比较 ,引用类型只能与nil比
​
    /*
    type  Animal4 struct {
            name *string
            Age int
            hobby []string   // 切片类型不能比较
        }
    s:="小老虎"
    s1:="小老虎"
    var a11,a12 =Animal4{name:&s,Age:8},Animal4{name:&s1,Age:8}
    var a13,a14 =Animal4{},Animal4{}
    fmt.Println(a11==a12)  // 报错
    fmt.Println(a13==a14)  // 报错
    */
​
​
    // 指针类型可以比较
    var number=10
    var number2=10
    var a *int=&number
    var b *int=&number2
    fmt.Println(a==b)  // false  地址不一样
​
    var a111 **int=&a
    var b111 **int=&b
    fmt.Println(a111==b111)  // false  
​
}
​
func changePersonName(p Person1)  {
    p.name="迪丽热巴"
    fmt.Println(p)
}
​



4 方法

# 结构体是传统的面向对象的一部分:属性,缺了方法
​
# 有属性,有方法---》对象
​
​
# 函数和方法(自动传值---》self),方法是特殊的函数
# 方法其实就是一个函数,在 func 这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的


package main
​
import (
    "fmt"
)
​
// 方法
​
// 定义了结构体
type Person struct {
    Name string
    Age  uint8
}
​
​
/*
定义一个方法----》其他语言,方法是绑定给对象的---》go语言中,方法是绑定给结构体的
给这个结构体绑定一个方法----》打印人名的方法
func (person Person)printName()  {  // 对比python  def printName(self):--->接收器就是python中的self
    // 打印人名
    fmt.Println(person.Name)
}
*/
​
func main() {
    //1 定义并使用方法
    /*
    var person Person
    var person2 Person=Person{Name: "彭于晏"}
    person.Name="lqz"
    fmt.Println(person.Name)
    person.printName()  // 方法绑定给Person结构体了,可以通过结构体的对象直接调用方法
    person2.printName()  // 哪个对象来调用,接收器就是哪个对象
    */
​
    // 2 方法和函数的区别:我们已经有函数了还需要方法呢---》方法绑定给结构体对象,使用起来更方便
    /*
    var person Person=Person{Name: "彭于晏",Age: 38}
    // 打印人名:函数与方法的比较:
    PrintName(person)  调用函数(正常调用,有几个值,就要传几个值)
    person.printName()  调用方法(对象.方法,对象自动传入)
    */
​
​
    // 3 有了结构体,有了方法----》面向对象中  类与对象  ---》对象.属性  对象.方法
​
​
    // 4 值接收器和指针接收器
    /*
    使用值类型接收器
    var person Person=Person{Name: "彭于晏",Age: 38}
    person.changName("刘亦菲")
    fmt.Println("修改后的person为:",person)  // 跟原来一样
​
    原因:接收器跟函数传参一样,都是copy传递,传到方法中修改,不会影响原来的
    */
​
    /*
    使用指针类型接收器
    var person Person=Person{Name: "彭于晏",Age: 38}
    person.changName2("刘亦菲")
    fmt.Println("修改后的person为:",person)  // 会被修改
    */
​
    /*
    什么时候使用指针接收器,什么时候使用值接收器
        1 当拷贝一个结构体的代价过于昂贵时,可以使用指针类型接收器
        2 想修改原来的值,就要使用指针类型接收器
    */
​
    //5  匿名字段的方法: 字段提升,方法提升
    /*
    var animal =Animal{"小狗",2,Hobby{1,"玩泥巴"}}
    fmt.Println(animal.hobbyId)  // 字段提升
    animal.printAnimalName()    // animal的方法
    animal.printHobbyName()     // 方法提升了,printHobbyName是Hobby结构体的方法,animal可以直接使用
    面向对象的继承---》Animal继承了Hobby---》Hobby中的方法,animal对象可以直接使用
​
    animal.printName()  // 是Animal的方法
    animal.Hobby.printName()  // 才是Hobby的方法
    animal.Hobby  等同于面向对象中的   super()
    */
​
    // 6 在方法中使用值接收器 与 在函数中使用值参数
    /*
    var dog Dog =Dog{name:"小奶狗",age:3}
    dog.changeName("小野狗") // 调用值接收器的方法
    changeName(dog,"小野狗") // 调用函数,参数是值类型
​
    原来dog会不会受影响?---》都不会,都是值---》不会影响原来的
    */
​
    // 7 方法中使用指针接收器 与 在函数中使用指针参数
    var dog *Dog = &Dog{name: "小奶狗", age: 3}
    var dog2 Dog = Dog{name: "小奶狗", age: 3}
    dog.changeAge(30)  // 指针对象调用
    dog2.changeAge(30)  // 值对象调用
    changeAge(dog, 30)   // 调用函数
    //changeAge(dog2, 30)  报错,必须传入指针类型参数
    /*
    方法的优势:无论是值还是指针都可以调用结构体的方法(无论是值接收器方法,还是指针接收器方法)
    无论是值类型的接收器方法,还是指针类型接收器方法---》都可以用值或指针来调用
    函数不是,函数的形参是什么,就需要传什么
    */
​
​
    // 8 一般情况下,方法用指针类型接收器或者值类型接收器都可以,但是一般把结构体对象做成指针
    /*
    var dog *Dog = &Dog{name: "小奶狗", age: 3}
    dog.changeName("小死狗")  // 虽然使用指针来调用,但是因为是值类型接收器,所以传入的是值,不会影响原来的
    */
​
    //9 在非结构体上使用方法
    var i Myint8=10
    i.add()
    i.add()
    fmt.Println(i)  // 12
}
​
// 案例2
// 函数
func PrintName(person Person) {
    fmt.Println(person.Name)
}
​
// 方法
func (person Person) printName() {
    fmt.Println(person.Name)
}
​
​
// 案例4
// 给结构体绑定一个修改名字的方法
// 值类型接收器:修改不会影响原来的
func (person Person) changName(name string) {
    person.Name = name
    fmt.Println(person) // 看一下有没有改成功,  改成功了 {"刘亦菲",38}
}
​
// 指针类型接收器,修改会影响原来的---》真正的等同于python中的self
func (person *Person) changName2(name string) {
    (*person).Name = name // 但是支持直接 . 取值
    //person.Name=name     // 跟上面等同
    fmt.Println(*person) // 看一下有没有改成功,  改成功了 {"刘亦菲",38}
}
​
// 案例5
type Hobby struct {
    hobbyId int
    hobbyName string
}
type Animal struct {
    name string
    age int
    //hobby Hobby  // 结构体嵌套
    Hobby  // 结构体嵌套+匿名字段
}
​
// 给Hobby结构体绑定方法
//func (hobby Hobby)printHobbyName()  {
func (hobby Hobby)printName()  {
    fmt.Println(hobby.hobbyName)
}
​
// 给Animal绑定一个方法
//func (animal Animal)printAnimalName()  {
func (animal Animal)printName()  {
    fmt.Println(animal.name)
}
​
// 案例6
type Dog struct {
    name string
    age  int
}
​
// 绑定一个方法--->值接收器
func (dog Dog) changeName(name string) {
    // 指针来调的,但是真传入值
    dog.name = name // 修改狗名字
    fmt.Println(dog)
}
​
// 写一个函数,使用值参数
func changeName(dog Dog, name string) {
    dog.name = name // 修改狗名字
}
​
// 案例7
// 绑定一个方法--->指针收器
func (dog *Dog) changeAge(age int) {
    // 无论值来调用,还是指针来调用,传进来的都是指针
​
    dog.age = age
    //(*dog).age=age
    fmt.Println(dog)
}
​
// 写一个函数,使用指针参数
func changeAge(dog *Dog, age int) {
    dog.age = age
    //(*dog).age=age
}
​
// 案例9 在非结构体上使用方法
//如果能给int8 绑定一个add方法,每调用一次,自增1
// var i int8=10
//i.add()
//func (i int8)add()  {  // 不能绑定
//  i=i+1
//}
​
// 可以给类型重命名后绑定方法
type  Myint8 int8   // 给Myint8类型绑定add方法
func (i *Myint8)add()  {
    *i++
}
​
​


5 结构体取代类

# 类是一系列属性和方法的集合
# 结构体是一系列属性的集合
# 结构体+方法=类的概念
​
"""
项目目录:
    go_basic03
        -entity
            -person.go
        -s.go
"""
​

entity/person.go

package entity
​
import "fmt"
​
func init() {
    fmt.Println("我执行了")
}
​
func init() {
    fmt.Println("wowowwowow")
}
​
type Person struct {
    Name  string
    Age   int
    hobby []string // 不给外包使用,隐藏属性
}
​
// 在每个go文件中,都可以写init函数---》不需要触发,会自动执行
​
// 普通函数
func New(name string, age int, hobby []string) Person {
    // 有了它的好处在于,可以校验数据
    // age 不能大于100岁
    if age > 100 {
        panic("年龄不能大于100岁")
    }
    p := Person{Name: name, Age: age, hobby: hobby}
    return p
}
​
// 给结构体绑定一个方法,打印人所有的爱好
func (person *Person) ShowAllHobby() { // 写了个方法---》让外部操作爱好
    if person.hobby == nil {
        fmt.Println("爱好暂时没有")
    } else {
        for _, value := range person.hobby {
            fmt.Print(value, "----")
            fmt.Println()
        }
    }
​
}
​
// 给结构体绑定一个,添加爱好的方法
func (person *Person) AddHobby(s string) {
    if person.hobby == nil {
        person.hobby = make([]string, 1, 1)
    }
    person.hobby = append(person.hobby, s)
​
    // 方式二
    /*
            if person.hobby==nil{
                person.hobby=[]string{s,}
            }else {
                person.hobby=append(person.hobby,s)
            }
    */
}
​


s.go 使用

package main
​
import (
    "basic03/entity"
    "fmt"
)
​
// 结构体取代类
func main() {
​
    var person1 entity.Person = entity.Person{Name: "cx", Age: 19}
    person1.AddHobby("篮球")
    person1.ShowAllHobby()
​
    //new 的方式---》建议使用这种
    //类实例化,有个构造方法,__init__
    var person2 entity.Person = entity.New("cx", 99, []string{"篮球"})
    fmt.Println(person2)
    person2.ShowAllHobby()
​
    //init 在包导入的时候,就会执行-->一个包下,可以有多个init,不需要调用,会自动触发
    //包内部的多个init会依次执行
​
    var person3 entity.Person
    fmt.Println(person3)
}
​


6 接口基础

# 在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定
​
# 接口规范了子类的行为
    类中如果有鸭子走路的方法,鸭子说话的方法---》无论你是什么类,你们都是鸭子这种类型
  
 
  
    class Dog():    # dog 就是鸭子这一类
       def speak(self):
        pass
        def run(self):
        pass
    
    class Chicken(): # Chicken就是鸭子这一类
       def speak(self):
        pass
        def run(self):
         pass
    
    # dog和chicke就是一类,就是鸭子这个类
    # 不需要有父类约束,子类中只要有 run和speak,你们就是鸭子类
    
    # 鸭子类型真正的解释:不需要有个父类约束,只要子类中有,子类就是鸭子这个类
    # go 语言也是鸭子类型
    
    # java:中要属于同一个类,必须显示的继承某一个父类
    
    # 接口用来约束子类的行为
    
    
    
    # 如何定义接口:在go中,接口是一系列方法的集合(没有具体实现)


package main
​
import (
    "fmt"
)
​
// 接口
​
// 1 接口的定义和实现-------开始
// 1 定义接口--->一系列方法的集合
type Duck interface {
    speak(s string) // 方法----》func (p Person)speak()(){}--->speak()()
    run()
}
​
// 2定义结构体
//定义唐老鸭结构体
type TDuck struct {
    name string
    age  int
    wife map[string]string
}
​
//定义普通鸭结构体
type PDuck struct {
    name string
    age  int
}
​
// 3 实现接口--->实现接口中的所有方法,就叫实现该接口---》如果没有实现所有方法,就不叫实现该接口---》鸭子类型
// 唐老鸭,实现speak和run
func (t TDuck) speak(s string) {
    fmt.Println("我是唐老鸭,我的名字是:", t.name, "我说:", s)
}
func (t TDuck) run() {
    fmt.Println("我是唐老鸭,我的名字是:", t.name, "我走路像人走路")
}
​
// 普通鸭子,实现speak和run
func (t PDuck) speak(s string) {
    fmt.Println("我是普通鸭子,我的名字是:", t.name, "我说:", s)
}
func (t PDuck) run() {
    fmt.Println("我是普通鸭子,我的名字是:", t.name, "我走路歪歪扭扭")
}
​
// 1 接口的定义和实现-------结束
​
func main() {
    // 接口也是一种类型,定义一个Duck接口类型的变量
    var tduck TDuck = TDuck{"唐老鸭", 7, map[string]string{"name": "母老鸭", "height": "40.4"}}
    fmt.Println(tduck)
​
    var pduck PDuck = PDuck{"肉鸭1号", 1}
    var duck Duck // 既然tduck和pduck都是实现了Duck接口,所有他们可以赋值给Duck类型
    duck = pduck  // 将pduck赋值给Duck类型
    fmt.Println(duck)
    // 只能使用pDuck类型中方对应Duck类型的run和speak方法,不能取到pduck的属性
    duck.run()
​
    // 如果没有实现Duck接口,能赋值给Duck接口类型吗?不能、因为没实现接口对应方法,相当于类型不一样,两者不能赋值
​
}
​

慢慢消化兄弟们~