面向对象编程 深碍√TFBOYSˉ_ 2024-03-22 08:41 98阅读 0赞 **目录** 面向对象编程 自定义类型 方法 组合 接口 接口变量值的类型 嵌入interface 练习题 实验总结 -------------------- ## 面向对象编程 ## 在讲解 Go 语言面向对象内容之前,需要说明下 Go 语言的代码是以包结构来组织的,且如果标示符(变量名,函数名,自定义类型等)如果以大写字母开头那么这些标示符是可以导出的,可以在任何导入了定义该标示符的包的包中直接使用。Go 语言中的面向对象和 C++,Java 中的面向对象不同,因为 Go 语言不支持继承,Go 语言只支持组合。 #### 自定义类型 #### Go 语言的中结构体 `struct` 与 C++、JAVA 中的类 `class` 相似,但 Go 放弃了传统面向对象的诸多特性,只保留了组合。 * **type** typeName typeSpecification 其中,`typeName` 可以是一个包或者函数内唯一合法的 Go 标示符。`typeSpecification` 可以是任何内置的类型,一个接口或者是一个结构体。所谓结构体,它的字段是由其他类型或者接口组成。例如我们通过结构体定义了一下类型: type ColorPoint struct { color.Color // 匿名字段(嵌入) x, y int // 具名字段(聚合) } 以上代码我们通过结构体自定义了类型 `ColorPoint`,结构体中 `color.Color` 字段是 Color 包的类型 color,这个字段没有名字,所以被称为匿名的,也是嵌入字段。字段 `x`和 `y`是有变量名的,所以被称为具名字段。假如我们创建了类型 `ColorPoint`的一个值 `point`(通过语法:`point := ColorPoint{}` 创建),那么这些字段可以通过 `point.Color`、`point.x`、`point.y` 访问。其他面向对象语言中的"类 (`class`)"、"对象 (`object`)"、"实例 (`instance`)"在 Go 语言中我们完全避开使用。相反的我们使用"类型 (`type`)"和其对应的"值",其中自定义类型的值可以包含方法。 定义了结构体后如何创建并初始化一个对象实例呢?Go 语言支持以下几种方法进行实现: //先定义一个结构体Man type Man struct{ name string age int } //对象创建与初始化 man := new(Man) man := &Man{} man := &Man{"Tom", 18} man := &Man{name: "Tom", age: 18} 为了更加方便的创建对象,我们一般会使用一个全局函数来完成对象的创建,这和传统的“构造函数”类似。 func NewMan(name string, age int) *Man { return &Man{name, age} } ### 方法 ### 方法是作用在自定义类型上的一类特殊函数,通常自定义类型的值会被传递给该函数,该值可能是以指针或者复制值的形式传递。定义方法和定义函数几乎相同,只是需要在**`func`** 关键字和方法名之间必须写上接接受者。例如我们给类型**`Count`**定义了以下方法: type Count int func (count *Count) Increment() { *count++ } // 接受者是一个 `Count` 类型的指针 func (count *Count) Decrement() { *count-- } func (count Count) IsZero() bool { return count == 0 } 以上代码中,我们在内置类型 `int` 的基础上定义了自定义类型 `Count`,然后给该类型添加了 **`Increment()`、`Decrement()`**和**`IsZero()`**方法,其中前两者的接受者为 `Count` 类型的指针,后一个方法接收`Count` 类型的值。 类型的方法集是指可以被该类型的值调用的所有方法的集合。 一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。如果在指针上调用一个接受值的方法,Go 语言会聪明地将该指针解引用。 一个自定义类型值的方法集合则由该类型定义的接收者为值类型的方法组成,但是不包括那些接收者类型为指针的方法。 其实这些限制 Go 语言帮我们解决的非常好,结果就是我们可以在值类型上调用接收者为指针的方法。假如我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这是因为 Go 语言会自动获取值的地址传递给该方法,前提是该值是可寻址的。 在以上定义的类型 `Count` 中,`*Count` 方法集是 `Increment()`, `Decrement()` 和 `IsZero()`,`Count` 的值的方法集是 `IsZero()`。但是因为 `Count` 类型的是可寻址的,所以我们可以使用 `Count` 的值调用全部的方法。 另外如果结构体的字段也有方法,我们也可以直接通过结构体访问字段中的方法。下面让我们练习下,创建源文件 `struct_t.go`,输入以下代码: package main import "fmt" type Count int // 创建自定义类型 Count func (count *Count) Increment() { *count++ } // Count类型的方法 func (count *Count) Decrement() { *count-- } func (count Count) IsZero() bool { return count == 0 } type Part struct { // 基于结构体创建自定义类型 Part stat string Count // 匿名字段 } func (part Part) IsZero() bool { // 覆盖了匿名字段Count的IsZero()方法 return part.Count.IsZero() && part.stat == "" // 调用了匿名字段的方法 } func (part Part) String() string { // 定义String()方法,自定义了格式化指令%v的输出 return fmt.Sprintf("<<%s, %d>>", part.stat, part.Count) } func main() { var i Count = -1 fmt.Printf("Start \"Count\" test:\nOrigin value of count: %d\n", i) i.Increment() fmt.Printf("Value of count after increment: %d\n", i) fmt.Printf("Count is zero t/f? : %t\n\n", i.IsZero()) fmt.Println("Start: \"Part\" test:") part := Part{"232", 0} fmt.Printf("Part: %v\n", part) fmt.Printf("Part is zero t/f? : %t\n", part.IsZero()) fmt.Printf("Count in Part is zero t/f?: %t\n", part.Count.IsZero()) // 尽管覆盖了匿名字段的方法,单还是可以访问 } 以上代码中,我们创建了 `Count` 类型,然后在其基础上又创建了结构体类型 `Part`。我们为 `Count` 类型定义了 3 个方法,并在 `Part` 类型中创建了方法 `IsZero()` 覆盖了其匿名字段 `Count` 中 `IsZero()` 方法。但是我们还是可以二次访问到匿名字段中被覆盖的方法。执行代码,输出如下: $ go run struct_t.go Start "Count" test: Origin value of count: -1 Value of count after increment: 0 Count is zero t/f? : true Start: "Part" test: Part: <<232, 0>> Part is zero t/f? : false Count in Part is zero t/f?: true ### 组合 ### Go 语言虽然抛弃了继承,但是却提供了一个更加方便的组合特性。相对于继承的编译期确定实现,组合的运行态指定实现,更加灵活。下面通过一段代码来了解组合的基本属性以及它与继承的不同之处。 先定义一个结构体 Base, 并为它添加两个方法 `Foo()` 和 `Bar()`: type Base struct{ Name string } func (b *Base) Foo() {...} func (b *Base) Bar() {...} type Seed struct { Base ... } func (s *Seed) Foo() { s.Base.Foo() s.Bar() ... } 上面代码先定义了一个 Base 类,然后定义了一个 Seed 类。Seed 类“继承”了 Base 类的所有成员属性和方法并重写了 `Foo()` 方法。同时在重写 `Foo()` 方法时调用了 Base 类的 `Foo()` 方法和 `Bar()` 方法。需要注意的是,若此时 Seed 的对象通过 `s.Foo()` 调用 `Foo()` 方法时,实际调用的是 Seed 重写过后的 `Foo()` 方法,而不是基类 Base 的 `Foo()` 方法,若想调用 Base 类的 `Foo()` 方法则要使用 `s.Base.Foo`,而调用没有重写的 `Bar()` 方法时,使用 `s.Bar()` 和`s.Base.Bar()` 效果是一样的。 ### 接口 ### 在 Go 中,接口是一组方法签名。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口。它与 oop 非常相似。接口指定类型应具有的方法,类型决定如何实现这些方法。 接口基础 之所以说 Go 语言的面向对象很灵活,很大一部分原因是由于接口的存在。接口是一个自定义类型,它声明了一个或者多个方法签名,任何实现了这些方法的类型都实现这个接口。`infterface{}` 类型是声明了空方法集的接口类型。任何一个值都满足 `interface{}` 类型,也就是说如果一个函数或者方法接收 `interface{}` 类型的参数,那么任意类型的参数都可以传递给该函数。接口是完全抽象的,不能实例化。接口能存储任何实现了该接口的类型。直接看例子吧,创建源文件 `interface_t.go`,输入以下代码: package main import "fmt" type Human struct { // 结构体 name string age int phone string } //Human实现SayHi方法 func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human实现Sing方法 func (h Human) Sing(lyrics string) { fmt.Println("La la la la...", lyrics) } type Student struct { Human //匿名字段 school string loan float32 } type Employee struct { Human //匿名字段 company string money float32 } // Employee重载Human的SayHi方法 func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) } // Interface Men被Human,Student和Employee实现 // 因为这三个类型都实现了这两个方法 type Men interface { SayHi() Sing(lyrics string) } func main() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} Tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} //定义Men类型的变量i var i Men //i能存储Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存储Employee i = Tom fmt.Println("This is Tom, an Employee:") i.SayHi() i.Sing("Born to be wild") //定义了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //这三个都是不同类型的元素,但是他们实现了interface同一个接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x { value.SayHi() } } 以上代码中,接口类型声明的变量能存储任何实现了该接口的类型的值。运行代码,输出如下: go run interface_t.go This is Mike, a Student: Hi, I am Mike you can call me on 222-222-XXX La la la la... November rain This is Tom, an Employee: Hi, I am Tom, I work at Things Ltd.. Call me on 222-444-XXX La la la la... Born to be wild Let's use a slice of Men and see what happens Hi, I am Paul you can call me on 111-222-XXX Hi, I am Sam, I work at Golang Inc.. Call me on 444-222-XXX Hi, I am Mike you can call me on 222-222-XXX ### 接口变量值的类型 ### 我们知道接口类型声明的变量里能存储任何实现了该接口的类型的值。有的时候我们需要知道这个变量里的值的类型,那么需要怎么做呢?可以使用类型断言,或者是 `switch` 类型判断分支。以下的例子 `interface_t1.go` 我们使用了 `switch` 类型判断分支。 package main import ( "fmt" "strconv" ) type Element interface{} type List []Element type Person struct { name string age int } // 实现了fmt.Stringer接口 func (p Person) String() string { return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" } func main() { list := make(List, 3) list[0] = 1 //an int list[1] = "Hello" //a string list[2] = Person{"Dennis", 70} for index, element := range list { switch value := element.(type) { // switch类型判断开关 case int: fmt.Printf("list[%d] is an int and its value is %d\n", index, value) case string: fmt.Printf("list[%d] is a string and its value is %s\n", index, value) case Person: fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) default: fmt.Println("list[%d] is of a different type", index) } } } 运行结果: $ go run interface_t1.go list[0] is an int and its value is 1 list[1] is a string and its value is Hello list[2] is a Person and its value is (name: Dennis - age: 70 years) ### 嵌入interface ### 在前面的课程中我们已经知道在结构体中可以嵌入匿名字段,其实在接口里也可以再嵌入接口。如果一个 `interface1` 作为 `interface2` 的一个嵌入字段,那么 `interface2` 隐式的包含了 `interface1` 里的方法。如下例子中,`Interface2` 包含了 `Interface1` 的所有方法。 type Interface1 interface { Send() Receive() } type Interface2 interface { Interface1 Close() } ### 练习题 ### 先创建一个结构体 `Animal`,再以 `Animal` 为基类创建结构体 `Cat` 和 `Dog`, 这两个子类分别实现接口 `behavior` 的 `eat()` 和 `run()` 方法。 ### 实验总结 ### Go 语言的面向对象课程实验到这里就结束了,首先我们先来回顾一下本实验涉及到的重点知识: * 结构体 * 实例化对象 * 方法 * 组合 * 接口
相关 面向对象编程 介绍 OOPs代表面向对象编程,这是一种编程范式,它使用对象和类来表示现实世界的实体及其行为。 OOP 的主要概念是将数据和处理该数据的函数作为一个单元绑定在一起,这样代 缺乏、安全感/ 2024年03月22日 18:06/ 0 赞/ 82 阅读
相关 面向对象编程 目录 面向对象编程 自定义类型 方法 组合 接口 接口变量值的类型 嵌入interface 练习题 实验总结 -------------------- 深碍√TFBOYSˉ_/ 2024年03月22日 08:41/ 0 赞/ 99 阅读
相关 面向对象编程 1. 面向对象概念 1.1 什么是对象 对象是相关状态和行为的包装,软件对象通常用于对日常生活中找到真实对象进行建模。 对象是理解面向对象技术的关键。看看周围,你 小咪咪/ 2023年01月23日 08:53/ 0 赞/ 65 阅读
相关 面向对象编程 函数的另一种形式 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ib 怼烎@/ 2022年11月13日 06:23/ 0 赞/ 296 阅读
相关 面向对象编程 面向对象编程 > 一切皆对象,面向对象编程特点封装、继承、多态 javascript的面向对象编程与其他的OOP语言不同,因为JS里没有类(class)的概念,什么是类,可 柔光的暖阳◎/ 2022年07月13日 09:49/ 0 赞/ 308 阅读
相关 面向对象编程 面向对象编程 概述: 面向对象本身就是一种方法;一种对现实世界理解和抽象的方法。也是一种更加符合人类的思想习惯的思想,将复杂问题简单化,将我们从执行者变成了指 小咪咪/ 2022年02月17日 05:27/ 0 赞/ 436 阅读
相关 面向对象编程 面向对象分为‘类’和‘实例’ 什么是类?类是对象的类型模版;例如Student,我们可以把它当做学生类型;它不具体代表某个学生; 什么是实例?实例是根据类型模版生成的对象; 矫情吗;*/ 2022年02月02日 05:21/ 0 赞/ 449 阅读
相关 面向对象编程,面向切面编程,面向过程编程 面向过程编程 面向过程编程(POP): 面向过程编程是以过程为中心的编程思想,从而分析解决问题所需要的步骤,然后编写函数来实现这些步骤,再依次调用这 一时失言乱红尘/ 2022年01月23日 07:13/ 0 赞/ 573 阅读
相关 面向对象编程 面向对象编程 面向过程编程:核心是过程,过程是一步一步的。先干啥,后干啥。 对扩展性要求较低的程序例如:系统内核,git,计算器,等等。 优点:逻辑清晰,复杂问 向右看齐/ 2021年11月02日 04:02/ 0 赞/ 566 阅读
还没有评论,来说两句吧...