go语言进阶篇——面向对象(一)

news2024/11/15 14:07:43

什么是面向对象

在我们设计代码时,比如写一个算法题或者写一个问题结局办法时,我们常常会使用面向过程的方式来书写代码,面向过程主要指的是以解决问题为中心,按照一步步具体的步骤来编写代码或者调用函数,他在问题规模小的情况下简洁快速且十分有效。

当我们遇到的问题比较庞大且复杂的时候,面向过程的代码就会变得难以维护与重复使用,这时我们就需要对问题进行抽象,将一类具有鲜明特色的函数抽象为一类对象,我们只需要通过调用对象中的一个方法来处理某一类特定的问题,对象与对象之间用方法来交互,这种编程方法我们称之为面向对象编程。

面向对象主要的特征有三点:封装,继承,多态,它们在面向对象编程中占据着重要的地位,最后,开始今天的学习之前我们要明确一件事情:面向对象只是一种思想,它并不特指某一部分语言

Go语言的面向对象编程设计

前言

相对于传统的如c++,Java等语言而言,Go语言显得优雅且简洁,它没有去沿袭传统面向对象编程得诸多概念,比如类的继承,接口的实现,构造与析构函数等等,也不再有publicprivateprotected等访问修饰符

Go语言的优雅之处,它对面向对象编程的支持是语言类型系统中的天然组成部分,整个类型系统通过接口串联,浑然一体

什么是类型系统

类型系统指的是一个语言的类型体系结构,一个典型类型系统一般包括以下内容:

  1. 基本类型(int,string ,byte,float)
  2. 复合类型(数组,切片,字典,字符串)
  3. 可以指向任意对象的类型(比如Go语言中的空接口)
  4. 值语义与引用语义(值语义指的是数据类型在赋值时会生成副本,彼此之间互不干扰,引用语义主要是多个副本共享一份数据,修改任意一个其他的也会随之修改)
  5. 面向对象,即所有具备面向对象特征(比如成员方法)的类型
  6. 接口

Java VS Go类型系统设计

Java

在Java语言中,存在两种完全独立的类型系统、

  1. 值类型系统,这里主要是基本类型,如int ,float,double等等
  2. Object类型为根的对象类型系统,它可以定义成员变量,成员方法,虚函数,这些一般是引用语义,在堆上分配内存

Java 语言中的 Any 类型就是整个对象类型系统的根 —— java.lang.Object 类型,只有对象类型系统中的实例才可以被 Any 类型引用。值类型想要被 Any 类型引用,需要经过装箱 (boxing)过程,比如 int 类型需要装箱成为 Integer 类型。

另外,在 Java 中,只有对象类型系统中的类型才可以实现接口。

Go

Go语言的大多数类型都是值语义,比如基本类型(int,float,double等等)或者是复合类型(数组,结构体等)

类的定义,初始化以及成员方法

类的定义与初始化

Go语言的面向对象编程与我们熟悉的Java等语言不同,它没有像classimplements,extend之类的关键字以及相应的概念,主要还是依靠i结构体来实现的,比如我们现在像创建一个学生类:

type Student struct{
	id sting
	name string 
	age uint 
	sex string
}

类名为student,并且包括了idnameagesex四个属性,Go语言也不支持构造函数与析构函数

所以我们可以定义全局函数NewStudent来初始化

func NewStudent(id,nmae,sex string.age uint) *Student{
	return &Strudent{id,nmae,sex,age}
}

当然我们也可以初始化指定字段:

func NewStudent1(age int,id,name string) *Student{
	return &Student{
		id:   id,
		name: name,
		age:  age,
	}
}

在 Go 语言中,未进行显式初始化的变量都会被初始化为该类型的零值,例如 bool 类型的零值为 falseint 类型的零值为 0,string 类型的零值为空字符串,float 类型的零值为 0.0

定义成员方法

值方法

由于 Go 语言不支持 class 这样的代码块,要为 Go 类定义成员方法,需要在 func 和方法名之间声明方法所属的类型(有的地方将其称之为接收者声明),以 Student 类为例,要为其定义获取 name 值的方法,可以这么做:

func (s Student) GetName(string {

    return	s.name

}

这样一来,我们就可以在初始化 Student 类后,通过 GetName() 方法获取 name 值:

student := NewStudent(1, "学院君", 100)

fmt.Println("Name:", student.GetName())

可以看到,我们通过在函数签名中增加接收者声明的方式定义了函数所归属的类型,这个时候,函数就不再是普通的函数,而是类的成员方法了。

指针方法

在类的成员方法中,可以通过声明的类型变量来访问类的属性和其他方法(Go 语言不支持隐藏的 this 指针,所有的东西都是显式声明)。GetName 是一个只读方法,如果我们要在外部通过 Student 类暴露的方法设置 name 值,可以这么做:

func (s *Student) SetName(name string) {

    s.name = name

}

你可能已经注意到,这里的方法声明和前面 GetXXX 方法声明不太一样,Student 类型设置成了指针类型:

s *Student

这是因为 Go 语言面向对象编程不像 PHP、Java 那样支持隐式的 this 指针,所有的东西都是显式声明的,在 GetXXX 方法中,由于不需要对类的成员变量进行修改,所以不需要传入指针,而 SetXXX 方法需要在函数内部修改成员变量的值,并且该修改要作用到该函数作用域以外,所以需要传入指针类型(结构体是值类型,不是引用类型,所以需要显式传入指针)。

我们可以把接收者类型为指针的成员方法叫做指针方法,把接收者类型为非指针的成员方法叫做值方法,二者的区别在于值方法传入的结构体变量是值类型(类型本身为指针类型除外),因此传入函数内部的是外部传入结构体实例的值拷贝,修改不会作用到外部传入的结构体实例

选择值方法还是指针方法

当我们有如下情形的考量时,需要将类方法定义为指针方法:

  1. 数据一致性:方法需要修改传入的类型实例本身;
  2. 方法执行效率:如果是值方法,在方法调用时一定会产生值拷贝,而大对象拷贝代价很大。

通常我们都会选择定义指针方法。

基于组合来实现类的继承与方法重写

要实现面向对象编程,就必须实现面向对象编程的三大特性:封装、继承和多态。上面我们已经介绍了类的封装,将函数定义为归属某个自定义类型,这就等同于实现了类的成员方法,如果这个自定义类型是基于结构体的,那么结构体的字段可以看做是类的属性。

继承

在go语言中并没有直接的提供有关继承的与凡是线,但是我们可以使用组合的方式来简介实现继承功能。

传统面向对象编程中,显式定义继承关系的弊端有两个:一个是导致类的层级越来越复杂,另一个是影响了类的扩展性,很多软件设计模式的理念就是通过组合来替代继承提高类的扩展性。减下来我们通过几个例子来看一下如何利用组合来实现继承

首先我们可以来定义一个父类Animal

type Animal struct {
    Name string
}

func (a *Animal) Call() string{
    return "动物的叫声是..."
}

func (a *Animal) FavorFood() string{
    return "动物最喜欢的食物是..."
}

func (a *Animal) GetName(name string) string{
    a.Name = name
}

如果我们想到一个子类Dog,可以这么来写:

type Dog struct{
    Animal
    //other things
}

这里,我们在 Dog 结构体类型中,嵌入了 Animal 这个类型,这样一来,我们就可以在 Dog 实例上访问所有 Animal 类型包含的属性和方法,相当于通过组合实现了继承

多态

在go语言中我们可以通过字子类中定义同名方法来覆盖父类方法,比如我们现在重写一下Animal中的方法:

func (d *Dog) Call() string{
    return "汪汪汪"
}

func (d *Dog) FavorFood() string{
    return "骨头"
}

当我们再执行 main 函数时,直接在 Dog 实例上调用 Call 方法或 FavorFood 方法时,调用的就是 Dog 类中定义的方法而不是 Animal 中定义的方法,如果要指定调用Animal里面的函数,就要按照下面的格式:

dog.Animal.Call()

拓展

可以看到,与传统面向对象编程语言的继承机制不同,这种组合的实现方式更加灵活,我们不用考虑单继承还是多继承,你想要继承哪个类型的方法,直接组合进来就好了。接下来我们来介绍一下继承与多态中常出现的一些问题:

  • 多继承同名方法冲突处理

    如果组合中不同类型中包含同名的方法,比如下面这种情况:

    type Dog struct{
        Animal
        pet
    }
    

    如果Animal和pet中有同名方法且类 Dog 没有重写该方法,直接在 Dog 实例上调用的话会报错,除非我们指定了执行哪个父类的函数

  • 调整组合位置会改变内存布局

    另外,我们还可以通过任意调整被组合类型的位置来改变类的内存布局:

    type Dog struct {
        Animal
        Pet
    }
    

    type Dog struct {
       Pet
       Animal
    }
    

    虽然上面两个 Dog 子类的功能一致,但是它们的内存结构不同。

  • 为组合类型设置别名

    前面的示例调用父类方法时都直接引用的是组合类型(父类)的类型字面量,其实,我们还可以像基本类型一样,为其设置别名,方便引用:

    type Dog struct{
        pet *Pet
        animal *Animal 
    }
    

类属性和成员方法可见性设置

在go语言中,无论是变量,函数还是类属性和成员方法,它们的可见性都是以包为维度的,go没有像publicprivateprotected这样的关键字来修饰其可见性。它们的可见性都是根据其首字母的大小写来决定的,如果变量名、属性名、函数名或方法名首字母大写,就可以在包外直接访问这些变量、属性、函数和方法,否则只能在包内访问,因此 Go 语言类属性和成员方法的可见性都是包一级的,而不是类一级的。

接下来我们来演示一个例子:

首先我们来创建一个animal包,然后创建一个animal.go文件:

package animal

type Animal struct {
    Name string
}

func (a Animal) Call() string {
    return "动物的叫声..."
}

func (a Animal) FavorFood() string {
    return "爱吃的食物..."
}

func (a Animal) GetName() string  {
    return a.Name
}

然后再创建一个pet.go

package animal

type Pet struct {
    Name string
}

func (p Pet) GetName() string  {
    return p.Name
}

然后创建dog.go

package animal

type Dog struct {
    Animal *Animal
    Pet Pet
}

func (d Dog) FavorFood() string {
    return "骨头"
}

func (d Dog) Call() string {
    return "汪汪汪"
}

最后是main.go文件

package main

import (
    "fmt"
    . "animal"
)

func main() {
    animal := Animal{Name: "中华田园犬"}
    pet := Pet{Name: "宠物狗"}
    dog := Dog{Animal: &animal, Pet: pet}

    fmt.Println(dog.Animal.GetName())
    fmt.Print(dog.Animal.Call())
    fmt.Println(dog.Call())
    fmt.Print(dog.Animal.FavorFood())
    fmt.Println(dog.FavorFood())
}

这样我们就实现了一个简单的面向对象程序,但是这里文件的变量以及方法名字都是大写,类似于全部都是public(公有的)如果你觉得直接暴露这三个类的所有属性可以被任意修改,不够安全,还可以通过定义构造函数来封装它们的初始化过程,然后把属性名首字母小写进行私有化:

animal.go为例

package animal

type Animal struct {
    name string
}

func NewAnimal(name string) Animal {
    return Animal{name: name}
}

func (a Animal) Call() string {
    return "动物的叫声..."
}

func (a Animal) FavorFood() string {
    return "爱吃的食物..."
}

func (a Animal) GetName() string  {
    return a.name
}

此时运行程序就会:
在这里插入图片描述

总结:

上面我们介绍了go语言的类型系统,并且完成了使用go语言来实现一个简单面向对象封装,继承与多态,大家可以多西靠思考,理解一下go语言的面向对象与常见如c++,Java等语言再面向对象实现上的不同,后面博主将介绍有关于接口在面向对象中的使用,以及有关泛型的使用以及基于泛型来实现我们自己封装的简短的数据结构,大家下篇见!,最后的最后,大家如果喜欢,还请收藏加关注,这样才能不迷路哦!!!
在这里插入图片描述

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

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

相关文章

12.状态模式

文章目录 状态模式总结 状态模式 介绍 状态模式它允许一个对象在其内部状态改变时改变其行为,使对象看起来似乎修改了其类。状态模式的主要目的是将对象的状态封装成不同的类,并将对象的行为委托给当前状态。 组成 Context(环境)&…

【JavaScript】Js中一些数组常用API总结

目录 前言 会改变原数组 push() pop()和shift() unshift() splice() sort() reverse() 不会改变原数组 slice() concat() filter() forEach() toString join(分隔符) 小结 前言 Js中数组是一个重要的数据结构,它相比于字符串有更多的方法…

【RT-DETR有效改进】利用SENetV1重构化网络结构 (ILSVRC冠军得主)

👑欢迎大家订阅本专栏,一起学习RT-DETR👑 一、本文介绍 本文给大家带来的改进机制是SENet(Squeeze-and-Excitation Networks)其是一种通过调整卷积网络中的通道关系来提升性能的网络结构。SENet并不是一个独立的网络模型,而是一个可以和现有的任何一个模型相结合…

Unity SRP 管线【第十讲:SRP/URP 图形API】

Unity 封装的图形API 文章目录 Unity 封装的图形API一、 CommandBuffer 要执行的图形命令列表1. CommandBuffer 属性2. CommandBuffer 常用图形API(方法)(1)设置(2)获取临时纹理 GetTemporaryRT以及释放(3)设置纹理为渲染目标 SetRenderTarget(4)Command…

一键放置柱子护角,你get了吗?

今天写个番外篇,给柱子添加护角。 记得几年前刚开始做BIM的时候,有次做车库导视方案模型,记得好像是鼎伦设计的车库一体化方案,当时柱子护角就给了两种方案,而且基本上每颗柱子上都要放护角,然后甲方竟然要…

MySQL8.0 最新版下载安装,手把手式的教程,小白都能安装成功!!

1、打开MySQL数据库官网的下载地址,根据自己的操作系统下载对应的安装包。我这里是Windows的,操作步骤如下: https://dev.mysql.com/downloads/installer/2、选择不登录下载 3、等待下载完成 4、然后点击下载下来的安装包 5、选择安装类型,Custom自定义安装 6、接…

【从Python基础到深度学习】1. Python PyCharm安装及激活

前言: 为了帮助大家快速入门机器学习-深度学习,从今天起我将用100天的时间将大学本科期间的所学所想分享给大家,和大家共同进步。【从Python基础到深度学习】系列博客中我将从python基础开始通过知识和代码实践结合的方式进行知识的分享和记…

Redis中的缓存穿透、雪崩、击穿的原因以及解决方案(详解)

一、概述 ① 缓存穿透:大量请求根本不存在的key(下文详解) ② 缓存雪崩:redis中大量key集体过期(下文详解) ③ 缓存击穿:redis中一个热点key过期(大量用户访问该热点key,…

【Git】08 多人单分支协作场景

文章目录 一、场景1:不同人修改不同文件1.1 场景描述1.2 场景复现1.2.1 克隆到本地1.2.2 新建分支1.2.3 B修改、提交与推送1.2.4 A修改与提交1.2.5 B再次修改并推送1.2.6 A推送报错 1.3 解决 二、场景2:不同人修改同文件的不同区域2.1 场景描述2.2 场景复…

canvas实现涂鸦画板功能

查看专栏目录 canvas实例应用100专栏,提供canvas的基础知识,高级动画,相关应用扩展等信息。canvas作为html的一部分,是图像图标地图可视化的一个重要的基础,学好了canvas,在其他的一些应用上将会起到非常重…

HarmonyOS SDK 助力新浪新闻打造精致易用的新闻应用

原生智能是HarmonyOS NEXT的核心亮点之一,依托HarmonyOS SDK丰富全面的开放能力,开发者只需通过几行代码,即可快速实现AI功能。新浪新闻作为鸿蒙原生应用开发的先行者之一,从有声资讯入手,基于Speech Kit朗读控件上线听…

大模型为什么会有 tokens 限制?

人是以字数来计算文本长度,大语言模型 (LLM)是以 token 数来计算长度的。LLM 使用 token 把一个句子分解成若干部分。 token 可以是一个单词、一个单词中的一个部分、甚至是一个字符,具体取决于它使用的标记化方法 (tokenization…

基于麻雀优化算法优化XGBoost参数的优化控制策略

目录 一、背景 二、算法流程图 三、附录 一、背景 为提高极端梯度提升(Extreme Gradient Boosting, XGBoost)集成算法在时间预测、信贷风险预测、工件参数预测、故障诊断预测等方面中的准确性,研究者提出了一种改进的麻雀算法(…

相机图像质量研究(7)常见问题总结:光学结构对成像的影响--镜片固化

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结:光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结:光学结构对成…

07-OpenFeign-HTTP压缩优化

gzip是一种数据格式,采用用deflate算法压缩数据;gzip是一种流行的数据压缩算法,应用十分广泛,尤其是在Linux平台。 当GZIP压缩到一个纯文本数据时,效果是非常明显的,大约可以减少70%以上的数据…

QTabWidget和QTabBar控件样式设置(qss)

QTabWidget和QTabBar控件样式设置 1、QTabWidget样式可自定义的有哪些示例:效果图 2、QTabBar样式可自定义的有哪些示例效果图 1、QTabWidget样式可自定义的有哪些 QTabWidget::pane{} 定义tabWidgetFrameQTabWidget::tab-bar{} 定义TabBar的位置QTabWidget::tab{}定…

【最详解】如何进行点云的凹凸缺陷检测(opene3D)(完成度80%)

文章目录 前言实现思路想法1想法2想法3 补充实现想法1想法2代码 想法3代码 总结 前言 读前须知: 首先我们得确保你已经完全知晓相关的基本的数学知识,其中包括用最小二乘法拟合曲二次曲面,以及曲面的曲率详细求解。若还是没弄清楚&#xff0…

《幻兽帕鲁》攻略:0基础入门及游戏基础操作 幻兽帕鲁基础设施 幻兽帕鲁基础攻击力 Mac苹果电脑玩幻兽帕鲁 幻兽帕鲁加班加点

今天就跟大家聊聊《幻兽帕鲁》攻略:0基础入门及游戏基础操作。 如果想在苹果电脑玩《幻兽帕鲁》记得安装CrossOver哦。 以下纯干货: CrossOver正版安装包(免费试用):https://souurl.cn/Y1gDao 一、基础操作 二、界面…

稳压二极管应用电路

稳压二极管比较特殊,基本结构与普通二极管一样,也有一个PN结。由于制造工艺的不同,当这种PN结处于反向击穿状态时,PN结不会损坏(普通二极管的PN结是会损坏),在稳压二极管用来稳定电压时就是利用它的这一击穿特性。 由…

【学网攻】 第(23)节 -- PPP协议

系列文章目录 目录 系列文章目录 文章目录 前言 一、PPP协议是什么? 二、实验 1.引入 实验目的 实验背景你是某公司的网络管理员,现在需要与另一个公司进行通信,需要你配置PPP协议保证双方发送的人是真正的而非黑客 技术原理 实验步骤新建Pack…