内容详细
1 指针
// python js java 都没有指针的概念
// c c++ go 有指针的概念,go的指针会比c指针简单很多
// 变量---》有类型---》存其他变量的内存地址
指针是一种存储变量内存地址的变量
package main
import "fmt"
// 指针
// & 放在变量前,表示取该变量的地址
// * 放在类型前,表示该类型的指针
// * 放在变量(指针变量)前,表示解引用
func main() {
// 1 指针定义
//var i =10 //定义int类型变量
//
//var p =&i // 取i的地址,赋值给变量 p
//fmt.Println(p)
// 2 指针变量的类型
//var i uint=10 //定义int类型变量
//var p *uint=&i // *int表示 int类型的指针
//fmt.Println(p)
// 3 解引用
//var i uint=10 //定义int类型变量
//var p *uint=&i // *int表示 int类型的指针
//fmt.Println(*p) //解引用
// 4 指针变量的指针
//var i int=10
//var p *int=&i
//var x **int=&p
//var y ***int=&x
//fmt.Println(p)
//fmt.Println(x)
//fmt.Println(y)
//fmt.Println(*y)
//fmt.Println(**y)
//fmt.Println(***y)
// 5 指针类型的零值---》引用类型零值是nil
//var p *string
//fmt.Println(p) // <nil> 必须要 初始化才能用--》PyObject *
//6 向函数传递指针参数-->修改会影响原来的
//var i int=10
////var p *int=&i
//testP(&i) // 函数参数传递都是copy传递 0xc00001a080
//fmt.Println("--",i) //11
//7 不要向函数传递数组的指针,而应该使用切片
//var a [3]int=[3]int{7,8,9}
var a =[4]int{7,8,9}
//var p =&a
//fmt.Println(p) //应该是个地址,但是是:&[7 8 9],为了好看
////test2(p)
//test3(p)
//fmt.Println(a) // a变了!
test4(a[:])
fmt.Println(a)
// 8 指针不支持运算---》c支持运算
//
}
func testP(a *int) {
fmt.Println(a) // 地址
fmt.Println(*a)//10
*a++
fmt.Println(*a) //11
}
func test2(a *[3]int) {
fmt.Println(a)
(*a)[0]=999
fmt.Println(a)
}
func test3(a *[4]int) {
fmt.Println(a)
(*a)[0]=999
fmt.Println(a)
}
func test4(a []int) {
fmt.Println(a)
a[0]=999
fmt.Println(a)
}
2 结构体
// go语言中没有类的概念,class,但是能够使用类的所有特性,继承封装多态。。。。跟咱们之前学的面向对象的语言,语法上有差距
//结构体: c 和go中有结构体概念
结构体是用户定义的【类型】(类),表示若干个字段的集合(属性,name,age...)
package main
import (
"fmt"
"go_day05_demo/entity"
)
// 结构体
//type关键字 结构体名字 struct{
// 字段1 字段类型
// 字段2 字段类型
//}
//1 定义结构体 定义一个 人 这种类型,姓名,性别,年龄 字段
type Person struct { // 如果小写,在其他包使用不到Person结构体
Name string // 字段大写,表示导出,如果小写,表示隐藏,在其他包中取不到
age uint8
Sex string
}
func main() {
// 2 使用结构体---》结构体是值类型-->有默认零值---》如果当函数参数传递,在函数中修改,不会影响原来的
//var p Person // 没有初始化,有零值,零值是每个字段的零值
//fmt.Println(p.age) // 有默认零值,字段的零值
//fmt.Println(p) //{ 0 }
// 3 定义并初始化
// 3.1 按位置初始化,按照顺序,有几个字段,必须传几个字段
//var p Person=Person{"lqz",18,"男"}
//fmt.Println(p)
// 3.2 按关键字初始化,不用按顺序,字段不用都传
//var p Person=Person{Sex:"男",age:19}
//fmt.Println(p)
// 4 字段的取值赋值 通过 .
//var p Person=Person{Sex:"男",age:19}
//fmt.Println(p)
//fmt.Println(p.Name)
//p.Name="xx"
//fmt.Println(p.Name)
// 5 结构体零值 不初始化有默认零值,零值是 各个字段的零值
//var p Person
//fmt.Println(p)
// 6 匿名结构体---》结构体没有名字和type关键字--->一般定义在函数内部--->只使用一次
// 好处是:多个变量可以包装到一个变量中
//a:= struct {
// age int
// name string
//}{19,"彭于晏"}
//fmt.Println(a.name)
//fmt.Println(a.age)
//fmt.Println(a)
// 7 结构体指针
//var p =&Person{"lqz",18,"男"}
//var p *Person=&Person{"lqz",18,"男"}
//// 7.1 结构体指针,访问字段
////(*p).Name="pyy"
//p.Name="pyy" // 可以简写成 跟直接用结构体一样了,
//fmt.Println(p) // &{ pyy 19 男}
// 8 结构体当函数参数传递
//var p =Person{"lqz",18,"男"}
//test5(p)
//fmt.Println("----",p) // p 不受影响
// 8 结构体指针当函数参数传递
//var p =&Person{"lqz",18,"男"}
//test6(p) //指针传入,地址copy传入,改了就影响原来
//fmt.Println("----",p) // p 受影响
// 9 匿名字段 ---》字段没有名字--->跟结构体是否匿名没有必然联系
// 有什么用---》很有用,做字段提升--->实现面向对象的继承
//type Animal struct {
// string // 字段没有名字,但是不能出现两个同种类型的字段 ,字段名就是类型名
// int
//}
////var a =Animal{"小鸭子",1}
//var a =Animal{string:"小鸭子",int:1}
//fmt.Println(a)
// 10 结构体嵌套--->结构体中套结构体
//type Animal struct {
// Name string
// age int
//}
//type Dog struct {
// //Name string
// //age int
// // 简写成--->结构体嵌套
// //animal Animal
// Animal // 匿名字段,Animal字段匿名了,字段名就是类型名,而且不用写name和age了
// // 狗比Animal多的字段
// Speak bool
//}
////var dog =Dog{Animal{"二哈",3},true}
//var dog =Dog{Animal:Animal{"二哈",3},Speak:true}
//// 面向对象继承,1 少写代码 2 子类对象.属性 如果子类没有,直接使用父类的
//fmt.Println(dog.Name) // dog的name本来是Animal的,使用了匿名字段,字段提升了
//// 如果Animal不是匿名字段,不能提升
//fmt.Println(dog.Animal.Name) // 指名道姓用 dog.Animal 相当于 super()
//// Dog中有Name,Animal 用了匿名字段,Name冲突了,会优先使用Dog自己的
// 11 导出结构体 和 字段
//var p =entity.Person{"lyf",37} // age 小写,外包包不能用
var p =entity.Person{Name:"lyf"} // age 小写,外包包不能用
fmt.Println(p)
// 12 结构体是值类型。
//如果它的每一个字段都是可比较的,则该结构体也是可比较的。
//如果两个结构体变量的对应字段相等,则这两个变量也是相等的
//var p1=Person{"lqz",19,"男"}
//var p2=Person{Name: "lqz",age: 19,Sex: "男"}
//p2.age=20
//if p1==p2{
// fmt.Println("p1 和p2 相等")
//}
// 如果结构体包含不可比较的字段(有引用类型),则结构体变量也不可比较
//type Cat struct {
// Name string
// age int
// wife []string // 可以有很多异性猫
//}
//var c1 =Cat{"小野豹",3,[]string{"女朋友1号","女朋友2号"}}
//var c2 =Cat{"小野豹",3,[]string{"女朋友1号","女朋友2号"}}
//if c1==c2 {
//
//}
}
func test5(p Person) {
fmt.Println(p)
p.Name="刘亦菲"
fmt.Println(p)
}
func test6(p *Person) {
fmt.Println(p)
p.Name="刘亦菲"
//(*p).Name="刘亦菲"
fmt.Println(p)
}
3 方法
// 什么是函数,什么是方法? 绑定给对象或者类的函数,由对象或者类来调用,可以自动传值
// 方法
方法其实就是一个函数,在 func 这个关键字和方法名中间加入了一个特殊的接收器类型。
接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的
package main
import "fmt"
// 方法---》结构
/*
func (接收器) 方法名(参数1 参数类型,参数2 参数类型)(返回值){
内容
}
*/
// 1 定义方法
type Dog struct {
Name string
Age int
}
// 给结构体绑定一个方法---》结构体有了属性,有了方法---》就是类的概念
// 这个接收器就是python中的 self,只不过写的位置不太一样
func (self Dog)Speak() {
fmt.Println("我是狗狗:",self.Name,"我说话了")
}
// 值类型接收器
func (dog Dog)changeName(name string) {
dog.Name=name
fmt.Println("方法中改了名字,",dog)
}
// 指针类型接收器
func (dog *Dog)changeAge(age int) {
dog.Age=age
fmt.Println("方法中改了年龄,",dog)
}
func main() {
// 2 使用方法 通过 . 调用
//var dog =Dog{"二哈",2}
//dog.Speak() // 自动把dog传到方法中
// 3 有了函数,为什么还要方法?
// 函数有几个值就要传几个,不能自动传值
// 方法绑定给结构体,拿到结构体对象直接调用即可,不需要在传参数
//var dog =Dog{"二哈",2}
//dog.Speak() // 自动把dog传到方法中
//Speak(dog)
// 4 值接收器和指针接收器
// 值类型接收器-->修改不会影响原来的
//var dog =Dog{"二哈",2}
//dog.changeName("牧羊犬")
//fmt.Println(dog) // 这个名字改了还是没改?
// 指针类型接收器
//var dog =Dog{"二哈",2}
//dog.changeAge(99)
//fmt.Println(dog) // 这个年龄改了
//5 无论是值类型接收器还是指针类型接收器 都可以值来调或者指针来调
// 值来调用
//var dog =Dog{"二哈",2}
//dog.changeName("牧羊犬")
//dog.changeAge(99)
//fmt.Println(dog)
// 指针来调用
var dog =&Dog{"二哈",2}
dog.changeName("牧羊犬")
dog.changeAge(99)
fmt.Println(dog)
// 1 无论是值还是指针,都可以调用值接收器的方法或者指针接收器的方法
// 2 无论值还是指针,只有指针接收器的方法,修改才会影响原来的
}
func Speak(self Dog) {
fmt.Println("我是狗狗:",self.Name,"我说话了")
}
4 接口
// python中鸭子类型就是接口的概念
// 一般语言来讲,接口规定了子类中有哪些方法(接口没有实现,只定义),子类自己去实现,接口是同一种类型
假设定义了一个鸭子接口,有run和speak方法
普通肉鸭,实现接口---》python 继承鸭子这个类,重写run和speak方法,它可以还有自己的方法
唐老鸭,实现接口---》python 继承鸭子这个类,重写run和speak方法,它可以还有自己的方法
普通肉鸭和唐老鸭都属于鸭子这种类型 ---》非鸭子类型的语言
// 鸭子类型的解释
对于python来讲,不需要定义出鸭子接口,不需要继承鸭子类,只要普通肉鸭中有run和speak方法
只要唐老鸭中有run和speak方法,我就认为我们是同一种类,鸭子这个类
// 不要显示的声明接口,继承接口,只要 多个不同类中有同样的方法,我们就可以把这些类归纳为一种类型---->python 特性,语言层面不强求,所有的权限都交给程序员
// 鸭子类型:带来了问题---》正因为不强求,子类如果不小心把方法名写错了,它就不是同一种类型,程序不会报错,运行到那才会报错
// 领导告诉你,你去写一个类,里面有speak和run方法,写好了,告诉我,我来用
-你不小心把speak ---》spaek
-等你领导真正调用的时候,报错了
-实际编码中,鸭子类型好用(少写代码),但是好多情况下不用,而要强制你必须实现run和speak,为了代码的健壮性,违背了鸭子类型,drf源码中,认证类,继承认证的基类,重写xx方法,正常情况不需要继承,直接写就行了
-1 通过抛异常的方式,如果没有重写方法,抛异常了
-2 abcstract装饰器修饰,子类必须实现,不实现,直接报错
以上都违背了鸭子类型的概念,但是确保了代码的健壮性
// python程序员,初级和高级的程序员写的代码差距特别特别大,初级的写的,根本没法维护
// go,java程序员,好多东西都是限制死的,必须这样写,确保了,即便新手,写的代码也没有大的问题
// 接口
在面向对象的领域里,接口一般这样定义:【接口定义一个对象的行为】。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定