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

什么是面向对象

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

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

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

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等语言再面向对象实现上的不同,后面博主将介绍有关于接口在面向对象中的使用,以及有关泛型的使用以及基于泛型来实现我们自己封装的简短的数据结构,大家下篇见!,最后的最后,大家如果喜欢,还请收藏加关注,这样才能不迷路哦!!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

后端创建订单

package com.java1234.entity;import io.jsonwebtoken.Claims;/*** jwt验证信息* author java1234_小锋* site www.java1234.com* company Java知识分享网* create 2019-08-13 上午 10:00*/ public class CheckResult {private int errCode;private boolean success;private Cl…

Linux下的多线程

前面学习了进程、文件等概念,接下里为大家引入线程的概念 多线程 线程是什么?为什么要有线程?线程的优缺点Linux线程操作线程创建线程等待线程终止线程分离 线程间的私有和共享数据理解线程库和线程id深刻理解Linux多线程(重点&a…

2023年全国职业院校技能大赛软件测试赛题第3套

2023年全国职业院校技能大赛 软件测试赛题第3套 赛项名称: 软件测试 英文名称: Software Testing 赛项编号: GZ034 归属产业: 电子与信息大类 …

软件价值10-数字时钟

这是一个数字时钟程序: # importing whole module from tkinter import * from tkinter.ttk import *# importing strftime function to # retrieve systems time from time import strftime# creating tkinter window root Tk() root.title(Clock)# This functio…

Docker Compose实例

目录 一、前提说明 二、简单的Docker容器部署案例 1. Dockerfile 配置 2. docker-compose.yml 配置 3. application.properties 配置 4. pom.xml 配置 5. 上传文件 6. 创建基础Docker镜像 7. docker-compose.yml编排 8. 停止并删除容器编排 一、前提说明 在配置好Do…

【博云2023】乘龙一跃腾云海,侧目抬手摘星河

癸卯渐远,甲辰渐至,预示着被汗水浇灌的种子,必将顶开冻土,迎接阳光。 每逢春节,当亲友彼此问候,博云人总能自豪地说,我们认真地、努力地奋斗,让我们能自信地踏上新的征程。 我们的…

使用Python进行数据的描述性分析,用少量的描述性指标来概括大量的原始数据

在进行数据分析时,当研究者得到的数据量很小时,可以通过直接观察原始数据来获得所有的信息。但是,当得到的数据量很大时,就必须借助各种描述性指标来完成对数据的描述工作。用少量的描述性指标来概括大量的原始数据,对…

用户和文件权限管理

一、用户管理 1、创建用户 [rootmaster ~]# useradd maple [rootmaster ~]# ll /home total 0 drwx------ 2 maple maple 62 Feb 7 20:47 maple drwx------ 2 www www 62 Jan 17 21:05 www [rootmaster ~]# passwd maple Changing password for user maple. New passwor…

Swift Combine 发布者publisher的生命周期 从入门到精通四

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三 1. 发布者和订阅者的生命周期 订阅者和发布者以明确定义的顺序进行通信,因此使得它们具有从开始到结束的生命周期: …

深度学习系列56:使用whisper进行语音转文字

1. openai-whisper 这应该是最快的使用方式了。安装pip install -U openai-whisper,接着安装ffmpeg,随后就可以使用了。模型清单如下: 第一种方式,使用命令行: whisper japanese.wav --language Japanese --model…

12. BI - 可视化在项目蒸汽量预测的过程及应用

本文为 「茶桁的 AI 秘籍 - BI 篇 第 12 篇」 文章目录 工业蒸汽量预测 Hi, 你好。我是茶桁。 我们今天继续来看数据可视化做数据探索,今天我们还是来看相关项目。来看看可视化 EDA 在项目中的应用。 工业蒸汽量预测 接下来这个项目,是在阿里天池上的一…

c++之说_12|模板

关于模板&#xff0c;至少我们要先了解几个概念 一&#xff1a;函数模板 二&#xff1a;类模板 三&#xff1a;模板特化 四&#xff1a;形参参数包 模板覆盖的东西太多 我目前也不了解太多 函数模板 语法 template<typename 类型名,typename 类型名,typename ...多参…

006集——where语句进行属性筛选——arcgis

在arcgis中&#xff0c; dBASE 文件除了 WHERE 语句以外&#xff0c;不支持 其它 SQL 命令。选择窗口如下&#xff1a; 首先&#xff0c;我们了解下什么是where语句。 WHERE语句是SQL语言中使用频率很高的一种语句。它的作用是从数据库表中选择一些特定的记录行来进行操作。WHE…

uniapp小程序端使用计算属性动态绑定style样式踩坑

踩坑点: 使用uniapp编译小程序端动态绑定复杂style使用计算属性方式&#xff0c;return必须返回json字符串格式&#xff0c;不能返回object&#xff0c;否则会不起作用。 代码总览 视图层 逻辑层&#xff08;注意这里是使用的计算属性哈&#xff09; 这里我封装成了一个个性化…

安全名词解析-水坑攻击、鱼叉攻击、钓鲸攻击

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 水坑攻击02 鱼叉攻击03 钓鲸攻击 01 水坑攻击 水坑攻击&#xff08;Watering Hole Attack&#xff09;是一种针对特定目标的网络攻击方式。顾名思义&#xff0c;是在受害者必经之路设置了一个“水坑…

PyTorch 2.2大更新!集成FlashAttention-2,性能提升2倍

【新智元导读】新的一年&#xff0c;PyTorch也迎来了重大更新&#xff0c;PyTorch 2.2集成了FlashAttention-2和AOTInductor等新特性&#xff0c;计算性能翻倍。 新的一年&#xff0c;PyTorch也迎来了重大更新&#xff01; 继去年十月份的PyTorch大会发布了2.1版本之后&#…

Octave实现位置式PID算法

由于Matlab不让用&#xff0c;只能“你不让爷用&#xff0c;爷就用别的”&#xff0c;选择开源的Octave以及scilab进行相关领域的学习。Octave的代码和Matlab几乎是100%相同的&#xff0c;只有一些专用的包的函数&#xff0c;可能有些还没来得及写&#xff0c;或者有些差异。但…

NLP_语言模型的雏形 N-Gram 模型

文章目录 N-Gram 模型1.将给定的文本分割成连续的N个词的组合(N-Gram)2.统计每个N-Gram在文本中出现的次数&#xff0c;也就是词频3.为了得到一个词在给定上下文中出现的概率&#xff0c;我们可以利用条件概率公式计算。具体来讲&#xff0c;就是计算给定前N-1个词时&#xff0…

Redis篇之redis是单线程

一、redis是单线程 Redis是单线程的&#xff0c;但是为什么还那么快&#xff1f;主要原因有下面3点原因&#xff1a; 1. Redis是纯内存操作&#xff0c;执行速度非常快。 2. 采用单线程&#xff0c;避免不必要的上下文切换可竞争条件&#xff0c;多线程还要考虑线程安全问题。 …