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接口类型吗?不能、因为没实现接口对应方法,相当于类型不一样,两者不能赋值
}
慢慢消化兄弟们~