【启程Golang之旅】从结构到接口揭秘Go的“面向对象”面纱

欢迎来到Golang的世界!在当今快节奏的软件开发领域,选择一种高效、简洁的编程语言至关重要。而在这方面,Golang(又称Go)无疑是一个备受瞩目的选择。在本文中,带领您探索Golang的世界,一步步地了解这门语言的基础知识和实用技巧。

目录

初识面向对象

方法的引入

封装的引入

继承的引入

接口的引入

多态的引入

断言的引入


初识面向对象

在Go语言中,虽然它并没有像Java或C++那样显式地支持类和继承这样的传统面向对象编程(OOP)特性,但Go语言仍然支持面向对象编程的许多核心概念,如封装、继承(通过组合和接口实现)和多态。以下是Go语言中面向对象使用结构体的简单案例:

package main
import "fmt"
// Student 定义学生的结构体,将学生中的各个属性统一放入结构体中管理
type Student struct {// 变量名字大写外界可以访问到这个属性Name   stringAge    intSchool string
}func main() {// 直接创建// 创建学生结构体的实例、对象、变量;var t1 Studentfmt.Println(t1) // 在未赋值时,结果为 { 0 }// 开始赋值t1.Name = "张三"t1.Age = 18t1.School = "北大"fmt.Println(t1) // {张三 18 北大}// 第二种var t2 Student = Student{"张三", 18, "北大"}fmt.Println(t2) // {张三 18 北大}//第三种:返回的是一个指针var t3 *Student = new(Student)// t3是指针,t3指向的就是地址,应该给这个地址的指向的对象的字段赋值(*t3).Name = "张三"(*t3).Age = 45   // *的作用是根据地址取值fmt.Println(*t3) // {张三 45 }// 为了符合程序员的编程习惯,go提供了简化的赋值方式t3.School = "北大" // go编译器底层对t3.School转化 (*t3).School = "北大"fmt.Println(*t3) // {张三 45 北大}//第四种var t4 *Student = &Student{"张三", 18, "北大"}fmt.Println(*t4) // {张三 18 北大}
}

最终实现的效果如下:

当然我们也可以对结构体之间进行转换,因为结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型):

package main
import "fmt"
type Student struct {Age int
}
type Teacher struct {Age int
}func main() {var a Student = Student{Age: 18}var b Teacher = Teacher{Age: 20}a = Student(b) // 类型转换fmt.Println(a) // {20}fmt.Println(b) // {20}
}

结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转

package main
import "fmt"
type Student struct {Age int
}
type Stu Studentfunc main() {var s1 Student = Student{Age: 1}var s2 Stu = Stu{Age: 10}s1 = Student(s2) // 类型转换fmt.Println(s1) // {10}fmt.Println(s2) // {10}
}

方法的引入

在go语言中,虽然没有类的概念,但是可以通过定义结构体和与结构体关联的方法来实现面向对象的编程,在这种方式下,方法是与特定类型关联的函数,方法是作用在指定的数据类型上,和指定的数据类型绑定,因此自定义类型都可以有方法,而不仅仅是struct,方法的声明和调用的格式如下:

type A struct {Name string
}func (a A) test() {println(a.Name)
}

ok,接下来通过具体的代码示例进行简单的讲解,如下:

package main
import "fmt"
type Person struct {Name string
}func (p Person) test() {p.Name = "李四"fmt.Println(p.Name) // 李四
}
func main() {var p Personp.Name = "张三"p.test()fmt.Println(p.Name) // 张三
}

注意:根据上面的示例代码,我们注意到以下几点

1)test方法中的参数名字随意起

2)结构体person和test方法绑定,调用test方法必须靠指定的类型,person

3)如果其他类型变量调用test方法一定会报错

4)结构体对象传入test方法中,值传递和函数参数传递一致

5)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式:

如果程序员希望在方法中改变结构体变量的值,可以通过结构体指针的方式来处理:

指针和不加指针的区别在于方法对原始变量的影响。使用指针接收者的方法可以直接修改原始变量的值,而不使用指针接收者的方法只能修改方法内部的副本,不会影响原始变量的值:

package main
import "fmt"
type integer int
func (i integer) print() {i = 30fmt.Println("i = ", i) // 30
}func (i *integer) change() {*i = 15fmt.Println("i = ", *i) // 15
}
func main() {var i integer = 10i.print()i.change()fmt.Println(i) // 15
}

如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出,以后定义结构体的话,常定义String()作为输出结构体信息的方法,会自动调用。

方法与函数的区别

1)绑定指定类型

方法:需要绑定指定数据类型;函数:不需要绑定数据类型

2)调用方式不一样

函数的调用方式:函数名(实参列表);方法的调用方式:变量.方法名(实参列表)

对于函数来说,参数类型对应是什么就要传入什么;对于方法来说,接收者为值类型可以传入指针类型,接收者为指针类型,可以传入值类型,示例代码如下:

package main
import "fmt"
type Student struct {Name string
}// 定义方法
func (s Student) test01() {fmt.Println(s.Name) // 张三
}// 定义函数
func method01(s Student) {fmt.Println(s.Name) // 张三
}func main() {// 调用函数var s Student = Student{"张三"}method01(s)// 调用方法s.test01()
}

如果想跨包创建实例的话,和以前的方法一致,如下:

要知道我们跨包访问变量的话,变量的首字母必须大写,对于结构体来说也是一样的,那有没有办法让结构体首字母小写也能跨包呢?这里需要采用工厂模式:

封装的引入

在go语言中,封装是面向对象编程(OOP)的一个重要概念,尽管go语言并没有明确地支持类和继承这样的传统OOP特性,但它仍然提供了封装的能力。封装主要是指将数据(字段)和与这些数据相关的操作(方法)组合在一个结构体(struct)中,并通过控制对结构体的访问权限来保护数据的完整性:

golang中如何实现封装:

1)建议将结构体、字段(属性)的首字母小写(其它包不能使用,类似private,实际开发不小写也可能,因为封装没有那么严格)

2)给结构体所在包提供一个工厂模式的函数,首字母大写 (类似一个构造函数)

3)提供一个首字母大写的set方法(类似其它语言的public),用于对属性判断并赋值

这里我给出如下封装代码:

package testUtils
import "fmt"
type person struct {Name stringage  int // 首字母小写,其他包不能直接访问
}// NewPerson 定义工厂模式的函数,相当于构造器
func NewPerson(name string) *person {return &person{Name: name,}
}// 定义set和get方法,对age字段进行封装,因为在方法中可以加一系列的限制操作,确保被封装字段的安全合理性
func (p *person) SetAge(age int) {if age < 0 || age > 150 {fmt.Println(age, "年龄不合法")}p.age = age
}
func (p *person) GetAge() int {return p.age
}

接下来在main函数中开始调用结构体中的实例,如下:

package main
import ("fmt""testUtils"
)func main() {// 创建person结构体的实例p := testUtils.NewPerson("张三")p.SetAge(20)fmt.Println(p.Name)     // 张三fmt.Println(p.GetAge()) // 20fmt.Println(*p)         // {张三 20}
}

继承的引入

        当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其它的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性,如下:

如下代码给出具体的示例:

package main
import "fmt"
// 定义动物结构体
type Animal struct {Name stringAge  int
}// 给Animal绑定方法
func (a *Animal) Speak() {fmt.Println("动物说话")
}
func (a *Animal) showInfo() {fmt.Println("动物名称:", a.Name, "年龄:", a.Age)
}
// 定义猫结构体
type Cat struct {Animal // 匿名嵌入
}// 对cat绑定特有方法
func (c *Cat) CatSpeak() {fmt.Println("喵喵~")
}
func main() {// 创建Cat结构体示例cat := &Cat{}cat.Animal.Name = "小猫"cat.Animal.Age = 2cat.Animal.Speak()    // 动物说话cat.Animal.showInfo() // 动物名称:小猫 年龄:2cat.CatSpeak()        // 喵喵~
}

注意

1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用:

2)匿名结构体字段访问可以简化:

3)当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访间原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

4)golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是go中保留了。

5)如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分

6)结构体的匿名字段是基本数据类型

7)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

8)嵌入匿名结构体的指针也是可以的

9)结构体的字段可以是结构体类型的(组合模式)

接口的引入

在go语言中,接口(Interface)是一种类型,它定义了一组方法的集合,但没有为这些方法提供实现,任何实现了这些方法的类型都隐式地实现了该接口,无需显式声明。这种特性使得接口在Go中成为实现多态性的主要方式:

package main
import "fmt"
// 接口的定义:定义规则、定义规范、定义某种能力
type SayHello interface {// 声明没有实现的方法sayHello()
}// 接口的实现,定义一个结构体
type Chinese struct{}
type Amerian struct{}// 实现接口的方法
func (person *Chinese) sayHello() {fmt.Println("你好,中国")
}
func (person *Amerian) sayHello() {fmt.Println("Hello, America")
}func main() {// 创建中国人var chinese = Chinese{}// 创建美国人var amerian = Amerian{}// 调用接口的方法chinese.sayHello() // 你好,中国amerian.sayHello() // Hello, America
}

注意

1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。

2)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

3)一个自定义类型可以实现多个接口。

4)一个接口(比如A接口)可以继承多个别的接口(比如B,c接口),这时如果要实现A接口,也必须将B,c接口的方法也全部实现。

5)interface类型默认是一个指针(引l用类型),如果没有对interface初始化就使用,那么会输出nil

6)空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把如何一个变量赋给空接口。

最后总结

1)接口中可以定义一组方法,但不需要实现,不需要方法体。并且接口中不能包含任何变量。到某个自定义类型要使用的时候(实现接口的时候),再根据具体情况把这些方法具体实现出来。

2)实现接口要实现所有的方法才是实现。

3)golang中的接,不需要显式的实现接口,golang中没有implement关键字。

4)接口目的是为了定义规范,具体由别人来实现即可。

多态的引入

在go语言中,多态(Polymorphism)是一个重要的面向对象编程的概念,它指的是不同对象对同一消息做出不同的响应,在go中多态性主要通过接口(Interface)来实现,但go并没有传统意义上的类和继承机制:

在go中,多态性主要体现在以下几个方面:

1)接口作为类型:Go的接口定义了一组方法的集合,任何实现了这些方法的类型都可以被视为该接口类型的实例。这意味着,我们可以将实现了相同接口的不同类型的对象赋值给接口类型的变量,并通过这个接口变量调用接口中定义的方法。由于不同的类型可能会以不同的方式实现这些方法,因此通过接口调用这些方法时就会表现出多态性。

2)隐藏具体实现:通过接口,我们可以隐藏对象的具体类型和实现细节,只关注对象的行为(即接口中定义的方法)。这使得我们可以编写更加灵活和可重用的代码,因为我们可以将任何实现了特定接口的对象传递给函数或方法,而无需关心其具体的类型。

3)动态类型绑定:在运行时,Go会根据接口变量所引用的对象的实际类型来调用相应的方法实现。这种动态类型绑定的特性使得我们可以在不修改代码的情况下,通过替换实现了相同接口的不同对象来改变程序的行为。

package mainimport "fmt"// 定义一个接口
type Shape interface {Area() float64Perimeter() float64
}// 矩形类型实现了Shape接口
type Rectangle struct {width, height float64
}func (r Rectangle) Area() float64 {return r.width * r.height
}func (r Rectangle) Perimeter() float64 {return 2 * (r.width + r.height)
}// 圆形类型实现了Shape接口
type Circle struct {radius float64
}func (c Circle) Area() float64 {return 3.14 * c.radius * c.radius
}func (c Circle) Perimeter() float64 {return 2 * 3.14 * c.radius
}// 一个函数,接受Shape接口类型的参数
func printInfo(s Shape) {fmt.Println("Area:", s.Area())fmt.Println("Perimeter:", s.Perimeter())
}func main() {rect := Rectangle{width: 4, height: 5}circle := Circle{radius: 3}printInfo(rect)   // 调用Rectangle的Area和Perimeter方法printInfo(circle) // 调用Circle的Area和Perimeter方法
}

断言的引入

        在Go语言中,断言(Assertion)通常与接口(Interface)和类型断言(Type Assertion)相关。类型断言用于在运行时检查接口变量中存储的具体类型,并尝试将其转换为该类型。如果接口变量确实包含该类型的值,则断言成功;否则,断言失败并可能导致运行时错误:

非安全断言(也称为显式类型断言):使用类型变量.(类型)的形式进行断言。如果接口变量不包含该类型的值,则会引发运行时错误(panic):

var x interface{} = "hello"  
s := x.(string) // 如果x包含字符串,则s将接收该字符串;否则,panic

安全断言(也称为类型选择):使用类型变量, ok := 类型变量.(类型)的形式进行断言。如果接口变量包含该类型的值,则ok为true,并且该值会被赋予相应的变量;如果接口变量不包含该类型的值,则ok为false,并且不会引发运行时错误:

var x interface{} = "hello"  
if s, ok := x.(string); ok {  // 如果x是字符串,则s将接收该字符串,并且ok为true  fmt.Println(s)  
} else {  // 如果x不是字符串,则不会执行这里的代码  fmt.Println("x is not a string")  
}

在Go中,断言通常用于处理空接口interface{}类型的变量,因为空接口可以存储任何类型的值。通过使用断言,我们可以将空接口变量转换为具体的类型,以便我们可以调用该类型的特定方法或访问其字段。

断言在Go的并发编程和接口交互中特别有用,特别是当你不知道一个接口变量具体包含什么类型的值时。通过断言,你可以编写更加灵活和健壮的代码,能够处理不同类型的值,并在运行时进行类型检查。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/20769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ICPC训练赛补题集

ICPC训练赛补题集 文章目录 ICPC训练赛补题集D - Fast and Fat (负重越野)I-路径规划G. Inscryption(邪恶铭刻)NEW Houses雪中楼(西安交通大学)L.BracketGenerationE - Checksum D - Fast and Fat (负重越野) 原题链接&#xff1a;原题链接 题意&#xff1a;体重大的背体重小的…

【面试题-004】ArrayList 和 LinkList区别

ArrayList 和 LinkedList 都是 Java 中常用的动态数组实现&#xff0c;都实现了 List 接口&#xff0c;但它们在内部数据结构和性能方面有所不同&#xff1a; 内部数据结构&#xff1a; ArrayList 是基于动态数组的数据结构&#xff0c;它允许快速随机访问。数组的大小在创建时…

用java实现客服聊天+网络爬虫下载音乐(java网络编程,io,多线程)

一 灵感&#xff1a; 在2022年的暑假&#xff0c;也就是我即将迈进高三的那个暑假&#xff0c;我并没有察觉自己应该要学习了&#xff0c;还是和过往的暑假一样玩着王者荣耀&#xff0c;凌晨2点睡觉&#xff0c;中午12点起床。我依稀记得这种状态一直持续到8月19。然而离开学还…

【MySQL】Linux安装MySQL

一、center OS环境准备 为了在Linux系统中查看MySQL5.8与8.0版本的区别 我们要准备两个虚拟机&#xff0c;需要的软件&#xff1a;VMware和CentOS7 因为博主之前在学习redis的时候已经安装过一个虚拟机了&#xff0c;所以我就直接克隆了一个CentOS2.0 修改mac地址&#xff0…

STM32作业实现(二)串口控制led

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

深入理解Java中的List集合:解析实例、优化技巧与最佳实践

一&#xff1a;List 集合的基础 1.1 什么是 List 集合&#xff1f; List 集合是 Java 集合框架中的一种有序、可重复的数据结构&#xff0c;它继承自Collection 接口&#xff0c;允许存储多个元素。 与数组不同&#xff0c;List 集合的大小是动态可变的&#xff0c;可以根据…

通过指针变量访问整型变量

有两个与指针变量有关的运算符&#xff1a; (1)&&#xff1a;取地址运算符。 (2)*&#xff1a;指针运算符&#xff08;或称间接访问运算符&#xff09;。 例如&#xff1a;&a为变量a的地址&#xff0c;*p为指针变量p所指向的存储单元。 编写程序&#xff1a; 运行结果…

【面试经典150题】移除元素

目录 一.移除元素 一.移除元素 这题的思路非常清晰&#xff0c;可以使用迭代器遍历找到需要删除的元素使用erase删除即可&#xff1a; #include<iostream> #include<vector>using namespace std;class Solution { public:int removeElement(vector<int>&…

设计模式(六)结构型模式---桥接模式

文章目录 桥接模式简介结构优点UML图具体实现UML图代码实现 桥接模式简介 桥接模式是一种将抽象与实现分离&#xff0c;使它们独立变化。然后利用组合关系来代替继承关系&#xff0c;大大的降低了抽象和实现的耦合度的设计模式。实际使用&#xff1a; JDBC源码分析&#xff0c…

Xcode下载安装

1.Xcode可用版本判断&#xff1a; 2.Xcode下载安装&#xff1a; 方案1:AppStore 下载更新 若方案1失败则 方案2:指定版本Xcode包下载解压安装 苹果下载 3.Xcode命令行工具插件安装 xcode-select --install 备注&#xff1a; xcode_x.x.x.xip(压缩包存在时效性(使用前24h/…

Mac专用投屏工具:AirServer 7 for Mac 激活版下载

AirServer 7 是一款在 Windows 和 macOS 平台上运行的强大的屏幕镜像和屏幕录制软件。它能够将 iOS 设备、Mac 以及其他 AirPlay、Google Cast 和 Miracast 兼容设备的屏幕镜像到电脑上&#xff0c;并支持高质量的录制功能。总的来说&#xff0c;AirServer 7 是一款功能全面的屏…

Mybatis编写SQL

文章目录 一、用注解编写1.1 增普通增加获取自增ID 1.2 删和改1.3 查单表查询多表查询 二、用xml编写2.1 使用xml的流程2.2 增普通增加获取自增ID 2.3 删 和 改2.4 查 三、#{} 和 ${}3.1 #{} 、${}3.1 预编译 SQL 、即时编译SQL 两种写法是可以同时存在的 一、用注解编写 1.1 …

mediasoup基础概览

提示&#xff1a;本文为之前mediasoup基础介绍的优化 mediasoup基础概览 架构&#xff1a;2.特性&#xff1a;优点缺点 3.mediasoup常见类介绍js部分c 4.mediasoup类图5.业务类图 Mediasoup 是一个构建在现代 Web 技术之上的实时通信&#xff08;RTC&#xff09;解决方案&#…

快速排序详讲(两种方法)

目录 原理 实现方式 正常实现 理由 先从右到左&#xff0c;在从左到右 先从左到右&#xff0c;先从右到左 挖坑法 效率 优化 测试 代码 原理 快速排序是将最左侧的数字当作关键数字&#xff0c;将关键数字放在对应位置&#xff0c;且关键数字左侧均大于它&#xff…

模拟集成电路(6)----单级放大器(共源共栅级 Cascode Stage)

模拟集成电路(6)----单级放大器&#xff08;共源共栅级 Cascode Stage&#xff09; 大信号分析 对M1 V x ≥ V i n − V T H 1 V x V B − V G S 2 V B ≥ V i n − V T H 1 V G S 2 V_{x}\geq V_{in}-V_{TH1}\quad V_{x}V_{B}-V_{GS2}\\V_{B}\geq V_{in}-V_{TH1}V_{GS2} Vx…

每日一题《leetcode-- LCR 025.两数相加||》

https://leetcode.cn/problems/lMSNwu/ 分别把给定的两个链表翻转&#xff0c;然后从头开始相加。 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ //反转链表 struct ListNode* reverselist(struct ListNode*h…

进程——linux

目录 冯诺依曼体系结构&#xff08;计算机组成原理与体系结构&#xff09; 关于冯诺依曼&#xff0c;必须强调几点&#xff1a; 操作系统(Operator System) 概念 设计OS的目的 定位 如何理解 "管理" 总结 系统调用和库函数概念 承上启下 一、进程 基本概念…

盒模型及其应用(溢出、外边距塌陷)

一、盒模型 盒模型详解-CSDN博客 CSS学习笔记3&#xff1a;CSS三大特性、盒子模型-CSDN博客 1.盒模型组成 &#xff08;1&#xff09;padding padding和border都会撑大盒子&#xff0c;margin不会. 如果没有指定盒子的width/height&#xff0c;那么padding不会撑大盒子。 &am…

详解动态库和静态库

文章目录 前言静态库静态库制作静态库的生成发布静态库使用静态库安装静态库卸载静态库 动态库动态库的制作动态库的生成动态库的发布使用动态库 动态库VS静态库 前言 在C、C中我们使用过标准库&#xff0c;比如在使用strerror、vector、string等时&#xff0c;都只是调用了这…

【对算法期中卷子的解析和反思】

一、程序阅读并回答问题&#xff08;共30分&#xff09; #include<cstdio>#include<cstring>#include<iostream>using namespace std;char chess[10][10];int sign[10];int n, k, ans;void dfs(int x, int k) { if (k 0){ans;return; } if (xk-1 >…