结构体:
结构体:不同类型数据集合
结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”
先声明一下我们的结构体:
type Person struct {name stringage intsex string
}
定义结构体法1:
var p1 Personfmt.Println(p1)p1.name = "XUPT"p1.sex = "无性别"p1.age = 74
定义结构体法2:
p2 := Person{}p2.age = 244p2.name = "艾美莉卡"p2.sex = "沃尔玛购物袋"fmt.Println(p2) //{艾美莉卡 244 沃尔玛购物袋}
定义结构体法3:
p3 := Person{name: "荷叶饭",sex: "女",age: 18, //加逗号}fmt.Println(p3) //{荷叶饭 18 女}
定义结构体法4:
p4 := Person{"米库",16,"女",}fmt.Println(p4) //{米库 16 女}
结构体为值类型,默认深拷贝(连同对象引用的所有嵌套对象(或指针指向的对象)都进行独立的复制),往往用指针操纵:
fmt.Printf("%T\n", &p4) //*main.Personfmt.Printf("%T\n", p1) //main.Person
当你对一个结构体变量取地址时,返回的是这个结构体在内存中的存储位置,这个“储存位置”就是用指针类型来储存的
定义结构体指针:
var pp1 *Personpp1 = &p1//p1是一个结构体,对p1取地址就是一个指针,所以可以画等号fmt.Println(pp1)fmt.Printf("%p,%T\n", pp1, pp1) //0xc0001040c0,*main.Personfmt.Println(*pp1) //{XUPT 74 无性别}
使用内置函数new(),go中专门用来创建某种类型的指针的函数
pp2 := new(Person)fmt.Printf("%p,%T\n", pp2, pp2) //0xc0000202d0,*main.Person,指针类型(*pp2).name = "zyzy"//也可以省略前面的*号,pp2.name="tzz"(*pp2).age = 22(*pp2).sex = "男"fmt.Println(pp2) //&{zyzy 22 男}
创建匿名结构体,一个没有名称的结构体:
s2 := struct {name stringage int}{name: "无敌高考大王",age: 18,}fmt.Println(s2.name, s2.age)
好处:
简洁性
- 代码简洁:匿名结构体允许我们在定义变量的同时声明其类型,这减少了代码量并提高了可读性。
- 减少命名负担:对于只使用一次的小型结构体,无需费心命名。
- 即时定义:在函数或方法中快速定义新的数据结构,非常适合处理一次性的、结构简单的数据。
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
type Worker struct {/*name stringage int*/stringint //匿名字段
}
定义一个含匿名字段的结构体:
w2 := Worker{"无敌崩坏星穹铁道大王",18,}fmt.Println(w2.string, w2.int) //打印匿名字段结构体
嵌套结构体:
结构体里套着结构体:
type Book struct {bookName stringprice float64
}
type Studentbook struct {name stringage intbook Book
}
定义法1:
ss2 := Studentbook{name: "code",age: 18,book: Book{"西西弗神话",24.9,},}fmt.Println(ss2)
定义法2:
bb1 := Book{}bb1.bookName = "1984"bb1.price = 45.8ss1 := Studentbook{}ss1.name = "奥威尔"ss1.age = 24ss1.book = bb1fmt.Println(bb1)fmt.Println(ss1)
定义法3:
bb3 := Book{bookName: "三体",price: 14.9,}ss3 := Studentbook2{name: "刘慈欣",age: 18,book: &bb3,}fmt.Println(bb3) //{三体 14.9}fmt.Println(ss3)
type关键字
1.类型定义:type 类型名 Type
2.类型别名:type 类型名=Type
var i1 myint //intvar i2 = 100 //intvar str1 mystrstr1 = "嘻嘻"
//main函数外
type myint int
type mystr string
看上去好像myint和int没什么区别?
打印一下他们的数据类型:
fmt.Printf("i1类型==%T\ni2类型==%T\n", i1, i2)//i2!=i1,i1,i2是两种语法类型
fmt.Printf("str1类型==%T\n", str1) //main.mystr
所以他们不是同一种数据类型,不能直接相等哦
我们也可以用type来重命名我们的函数类型:
type myfun func(int, int) string//定义一个新的函数类型,myfun就等价于 func(int,int)string
//照这个类型定义一个函数,等价于 func fun11() func(int, int) string
func fun11() myfun {fun := func(a, b int) string {s := strconv.Itoa(a) + strconv.Itoa(b)return s}return fun
}
/*
上述函数等价于
func fun11() func(int, int) string {fun := func(a, b int) string {s := strconv.Itoa(a) + strconv.Itoa(b)return s}return fun
}*/
调用我们的函数:
res1 := fun11()fmt.Println(res1(10, 20)) //拼接10和20type myint2 = int //给int起了别名,就像define
放弃了大量面向对象的特性,首字母大写可共用,小写为私用
这个类的私用和共用和保护在c++中是这么体现的:
class BaseClass {
public:// 公有成员int publicData;protected:// 保护成员int protectedData;
};class DerivedClass : public BaseClass {
public:// 在派生类中可以访问基类的保护成员void accessProtected() {protectedData = 42; // 正常访问}private:// 派生类有自己的私有成员int derivedPrivate;
};int main() {DerivedClass d;d.publicData = 10; // 可见,因为是公有的d.accessProtected(); // 可见,因为DerivedClass继承了BaseClass// BaseClass的对象尝试访问protectedData是不允许的// BaseClass base;// base.protectedData = 50; // 错误,protectedData仅对DerivedClass可见return 0;
}
如果我们写成go应该是这样的:
package mainimport "fmt"type BaseClass struct {PublicData int //共有字段protectData int //protected,只能在包内私用,未导出的字段
}// 模拟c++的继承,嵌套结构体
type DerivedClass struct {BaseClassderivedPrivate int
}func (d *DerivedClass) AccessProtected() {d.protectData = 42 //访问我们未导出的protected字段fmt.Println("protectData==", d.protectData)
}
func main() {d := DerivedClass{}d.PublicData = 10fmt.Println("d.PublicData==", d.PublicData)d.AccessProtected()base := BaseClass{}base.protectData = 50 // 如果在c++里编译错误:无法访问,因为我们的protected是对于派生类可以被访问//但是在go里,我们不能把protected限定在类里,只能控制它在包内和包外是否可调用fmt.Println(base.protectData) //50fmt.Println("program finished")
}
很显然我们的go在面向对象这方面并不能像c++一样(比如我们的protected类)
oop(面向对象)
面向对象和面向过程的区别:
其实不止操作系统,很多系统的管理模式都是用面向对象的模式管理的
管理者并不在乎你每个人的个体特征。
在公司里,老板让你买咖啡,并且他很急。买咖啡有多种方式,例如点外卖、自己到楼下购买、托正在外面的同事捎来或者给老板花超绝三块钱冲雀巢等等。然后你就问老板说:老板,那我是去点外卖还是自己买还是给你冲速溶咖啡还是......老板根本不在乎你说的什么,他只会告诉你:不管怎样,我只要我的桌子上放有咖啡。
员工只要搞到咖啡就好了,而老板考虑的就多了(勾潮的鸣式还在追我
老板这个命令就叫面向对象(oop)他不在乎事情处理的过程;底层员工要做的就是处理好这些事情的过程。
面向对象就是把拥有复杂过程的事情抽象为一个类,比如在这里我们把自己买咖啡、点外卖、冲咖啡这种事情全部抽象为搞到咖啡(其实很像那些不近人情的上级会说:我不问过程只要结果呃呃)
本质上获得咖啡的途径,就是面向过程(opp)。也就是说,面向对象的最底层的实现方法还是面向过程。只不过在这里,面向过程被我们抽象为一个类。
按C++的STL来举例子:首先我们需要一个工具箱,里面装着我们需要的东西。于是我们先创造了工具箱这个东西;然后我们就要想在我们的工具箱里塞什么东西,比如说我需要一个排序的函数,需要一个求大值、最小值的函数;然后我们再去编写函数里面的内容,这个函数最里面的内容是面向过程,然后外边整体用的是面向对象的思想。
也就是说对特定对象先描述,再组织
————————————————之前写的部分
原文链接:https://blog.csdn.net/Au_ust/article/details/141108377
对象是最基本的单位,例如switch就是一个对象,描述他的有属性(是个程序员)和方法(打炉石传说)
这种类型的人可以被抽象、概括出来,就是一个类,例如上述的像switch一样的人可以被抽象为爱打炉石传说的程序员,像她一样爱打炉石传说的程序员都在这个类里面
面向对象编程具有三大特性:封装、继承、多态。
封装:将对象的属性和方法打包
继承:子类可以继承父类已有的功能
多态:不同的类执行不同的方法有不同的结果
在go语言中其实并不具有一种完备的面向对象的机制,没有什么内联函数,不如c++
但是我们可以使用偷感很重的方式:用接口和方法模拟oop的特性:
1.模拟继承性:is-a(是),一个类继承一个类
子类可以直接访问父类的属性和方法
子类可以新增自己的属性和方法
子类可以重写父类的方法(override,就是将父类已有的方法,重新实现)
type A struct{
field
}
type B struct{
A//匿名字段
//可以在B的里面,在A的基础上修改A的方法属性
}
2.模拟聚合关系 has-a(有),一个类拥有一个类
//这个很好理解
type C struct{field
}
type D struct{c C//聚合关系
}
3.模拟多态
一个对象受到类型的限制,可以定义为父类类型,也可以定义为子类,类似于界门纲目科属种的关系
类型不同,能够访问的字段(功能)不同
go语言通过接口模拟多态,就是一个接口的实现
1.看成实现本身的类型,能够访问实现类中的属性和方法
2.看成是对应接口的类型,只能访问接口中定义的方法
一个很弱智很普通的模拟面向对象的实现:
package mainimport "fmt"func main() {//1.创建父类对象p1 := Personoop{"张三", 18}fmt.Println(p1)fmt.Println(p1.name, p1.age)//2.创建子类对象s1 := Studentoop{Personoop{"李四", 18}, "XUPT"}fmt.Println(s1)s2 := Studentoop{Personoop: Personoop{"王五", 18}, school: "XUPT"}fmt.Println(s2)var s3 Studentoops3.Personoop.name = "老公"s3.Personoop.age = 18s3.school = "CCUT"fmt.Println(s3) //{{老公 18} CCUT}//Personoop在Studentoopz中为匿名结构体字段,被称为提升字段//因此可以如下命名s3.name = "荷叶饭"s3.age = 16s3.school = "我不要上学"fmt.Println(s3) //{{荷叶饭,16} 我不要上学}
}// 1.定义父类
type Personoop struct {name stringage int
}
// 2.定义子类//is-a的关系
type Studentoop struct {Personoop //结构体嵌套模拟继承结构,提升字段,可以直接进行访问school string //子类的新增属性
}
差不多就是这个意思,等下讲接口
方法(method)
方法的本质是一种函数,但是是定义了接受者的函数,这个接受者可以是很多种数据类型,还要结构体类型中的值或指针
所有给定统一类型的方法称为该类型的方法集。比如虾头太刀侠,可以登!龙!,还可以进行气刃大回旋,登龙和气刃大回旋就是虾头太刀侠的方法集
方法的名字可以重复,但是method区别调用者
语法:func(接受者)方法名(参数列表)(返回值列表){}
来写一个样例看看:
type Worker1 struct {name stringage intsex string
}//结构体定义部分
func (w Worker1) work() {fmt.Println(w.name, "在工作")
}//工作方法
func (p *Worker1) rest() {fmt.Println(p.name, "在休息")
}//休息方法func main(){w1 := Worker1{name: "张三", age: 18, sex: "男"}w1.work() //张三 在工作w2 := &Worker1{name: "李四", age: 18, sex: "女"}w2.rest() //李四 在休息}
方法可以重名,我们创建一个新的类和刚刚的类对比:
type Worker1 struct {name stringage intsex string
}
type Cat struct {name stringcolor stringsex string
}func (p *Worker1) printfInfo() {fmt.Println("name:", p.name, "age:", p.age, "sex", p.sex)
}
func (p *Cat) printfInfo() {fmt.Println("name:", p.name, "color:", p.color, "sex:", p.sex)
}func main(){w1 := Worker1{name: "张三", age: 18, sex: "男"}c1 := Cat{name: "艾露猫", color: "浅棕色", sex: "男"}c1.printfInfo() //name: 艾露猫 color: 浅棕色 sex: 男c1.name = "呆猫"c1.printfInfo() //name: 呆猫 color: 浅棕色 sex: 男w1.printfInfo() //name: 张三 age: 18 sex 男//方法可以重名,因为接受者不同}
新增方法和重写方法的区别:
func main(){//创建Personmethod类型p1 := Personmethod{"王二狗", 18}fmt.Println(p1) //{王二狗 18}p1.eat() //父类的方法,吃盖浇面s1 := Studentmethod{Personmethod{"ruby", 18}, "XUPT"}fmt.Println(s1) //{{ruby 18} XUPT}s1.study() //子类的新增方法,学生学习s1.eat() //子类重写的方法,吃炸鸡}
// 继承中的方法
// 定义一个“父类”
type Personmethod struct {name stringage int
}// 定义一个子类
type Studentmethod struct {Personmethodschool string
}// 定义一个父类方法
func (p Personmethod) eat() {fmt.Println("父类的方法,吃盖浇面")
}// 定义一个子类方法
func (p Studentmethod) study() {fmt.Println("子类的新增方法,学生学习")//和子类的类型一样,方法名不一样
}
func (p Studentmethod) eat() {fmt.Println("子类重写的方法,吃炸鸡")//重写是名字一样,类型不一样
}
接口(interface)
接口是对方法的抽象,例如:你会吃东西,说明你肯定是个生物,但是如果你又会吃东西,还会玩元神,那你就是个私宅;所以吃东西可以单独做一个接口,吃东西和玩元神也可以做另一个接口
这样叫通过方法来确定你的类型(鸭子类型)
go语言中,接口类型的实现关系,是非侵入式:只实现,不用在乎是谁实现的
java中要显示定义
eg:
class Mouse implements USB{}
package main
import ("fmt"
)
func main(){//1.创建mouse类型m1 := Mouse{"罗技小红"}fmt.Println(m1)//2.创建FlashDiskf1 := FlashDisk{"闪速64G"}fmt.Println(f1)testIterface(m1)//鼠标就会从testIterface中的USB中找到鼠标对应的start方法testIterface(f1)//同上}
func testIterface(usb USB) { //usb=m1 usb=f1usb.start()usb.end()
}//测试我们接口的函数type USB interface {start()end()
}//USB接口
func (m Mouse) start() {fmt.Println("Mouse.start")
}
func (m Mouse) end() {fmt.Println("Mouse.end")
}//对应的鼠标方法
func (f FlashDisk) start() {fmt.Println("FlashDisk.start")
}
func (f FlashDisk) end() {fmt.Println("FlashDisk.end")
}//对应的FlashDisk方法
也可以把main函数里面写成这样:
var usb USBusb = m1usb.start()usb.end()usb = f1usb.start()usb.end()
可以更明显的看出接口对同类的不同对象方法的抽象
空接口
没有任何方法签名的接口叫空接口
写一个空接口:
package main
import ("fmt"
)
func main() {var a1 A = Dog{"黑犬"}var a2 A = People{"纸包鱼", 30}fmt.Println(a1, a2) //{黑犬} {纸包鱼 30}test1(a1) //{黑犬}test1(a2) //{纸包鱼 30}test2(a1) //空接口 {黑犬}test2(a2) //空接口 {纸包鱼 30}//空接口代表任意类型的使用slice1 := make([]interface{}, 0, 10)slice1 = append(slice1, a1, a2, 100, "abc") //[{黑犬} {纸包鱼 30} 100 abc]fmt.Println(slice1)
}func test1(a A) {fmt.Println(a)
}
func test2(a interface{}) {fmt.Println("空接口", a)
}// 空接口
type A interface {
}
空接口中不限制数据类型
接口嵌套,可以模拟oop的继承特性,也可以通过不同接口看是什么接口类型,可以把实现类型看成接口类型:
package main
import ("fmt"
)
func main() {var dog Dog = Dog{"青色"}dog.test11()dog.test22()dog.test33()fmt.Println("------分隔符------")var aa1 AA = dog //AA接口类型aa1.test11()fmt.Println("------分隔符------")var bb1 BB = dog //BB接口类型bb1.test22()fmt.Println("------分隔符------")var cc1 CC = dog //CC接口类型cc1.test11()cc1.test22()cc1.test33()
}
type AA interface {test11()
}
type BB interface {test22()
}
type CC interface {AABBtest33()
}func (d Dog) test11() {fmt.Println("test11...")
}
func (d Dog) test22() {fmt.Println("test22...")
} //如果test11和test22都能实现,则Dog同时是AA和BB的实现
func (d Dog) test33() {fmt.Println("test33...")
}
接口断言
我们可以通过接口来判断实现类型。那么如果是空接口呢?或者两个类都可以实现同一个接口下的方法呢?比如你会翻滚,烤肠机上的烤肠也会翻滚,那么如何区分你和烤肠呢?
package mainimport ("fmt""math"
)func main() {var t1 Triangle = Triangle{3, 4, 5}fmt.Println(t1.perimeter()) //12fmt.Println(t1.area()) //6var c1 Circle = Circle{5}fmt.Println(c1.perimeter()) //31.41592653589793fmt.Println(c1.area()) //78.53981633974483var s1 Shape //Shape类型,但是没有确定为Triangle类型还是Circle类型s1 = t1 //定义为Triangle类型fmt.Println(s1.perimeter())fmt.Println(s1.area())//fmt.Println(s1.a,s1.b,s1.c),打印不了,因为s1是Shape接口类型testShape(t1) //周长 12 面积 6testShape(c1) //周长 31.41592653589793 面积 78.53981633974483getType(t1) //是三角形,三边为 3 4 5getType(c1) //是圆形半径为 5getType(s1) //是三角形,三边为 3 4 5var t2 *Triangle = &Triangle{3, 4, 5}getType(t2) //ins:*main.Triangle 0xc000058030//s:*main.Triangle 0xc0000260a0,地址不一样,值传递getType1(t2) //三角形结构体指针 3 4 5
}
func getType(s Shape) {//断言,判断接口对象是不是对应的实际类型//1.if分支if ins, ok := s.(Triangle); ok {fmt.Println("是三角形,三边为", ins.a, ins.b, ins.c)} else if ins, ok := s.(Circle); ok {fmt.Println("是圆形半径为", ins.radius)} else if ins, ok := s.(*Triangle); ok {fmt.Printf("ins:%T %p\n", ins, &ins)fmt.Printf("s:%T %p\n", s, &s)} else {fmt.Println("我也不知道")}
}
func getType1(s Shape) {//2.switch分支switch ins := s.(type) {case Triangle:fmt.Println("是三角形,三边为", ins.a, ins.b, ins.c)case Circle:fmt.Println("是圆形半径为", ins.radius)case *Triangle:fmt.Println("三角形结构体指针", ins.a, ins.b, ins.c)}
}
func testShape(s Shape) {fmt.Println("周长", s.perimeter(), "面积", s.area())
}// 1.定义一个接口
type Shape interface {perimeter() float64area() float64
}// 2.定义实现类
type Triangle struct {a, b, c float64
}func (t Triangle) perimeter() float64 {return t.a + t.b + t.c
}
func (t Triangle) area() float64 {p := t.perimeter() / 2s := math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))return s
}type Circle struct {radius float64
}func (c Circle) perimeter() float64 {return math.Pi * c.radius * 2
}
func (c Circle) area() float64 {return math.Pi * c.radius * c.radius
}