3、Golang基础--字符串使用、指针、结构体、方法、结构体取代Python类、接口基础

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

}

posted @ 2022-02-26 22:45  简爱cx  阅读(85)  评论(0编辑  收藏  举报