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 stingname 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{Animalpet
    }
    

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

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

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

    type Dog struct {AnimalPet
    }
    

    type Dog struct {PetAnimal
    }
    

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

  • 为组合类型设置别名

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

    type Dog struct{pet *Petanimal *Animal 
    }
    

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

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

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

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

package animaltype 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 animaltype Pet struct {Name string
}func (p Pet) GetName() string  {return p.Name
}

然后创建dog.go

package animaltype Dog struct {Animal *AnimalPet Pet
}func (d Dog) FavorFood() string {return "骨头"
}func (d Dog) Call() string {return "汪汪汪"
}

最后是main.go文件

package mainimport ("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 animaltype 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.rhkb.cn/news/255630.html

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

相关文章

C#上位机与三菱PLC的通信05--MC协议之QnA-3E报文解析

1、MC协议回顾 MC是公开协议 ,所有报文格式都是有标准 ,MC协议可以在串口通信,也可以在以太网通信 串口:1C、2C、3C、4C 网口:4E、3E、1E A-1E是三菱PLC通信协议中最早的一种,它是一种基于二进制通信协…

Java 学习和实践笔记(6)

各数据类型所占的空间: byte: 1个字节 short:2个字节 int:4个 long:8个 float:4个 double: 8个 char:1个 boolean:1bit 所有引用数据类型都是4个字节,实际其值是指向该数据类型的地址。 上图中稍特…

百卓Smart管理平台 uploadfile.php 文件上传漏洞(CVE-2024-0939)

免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…

【前端web入门第五天】01 结构伪类选择器与伪元素选择器

文章目录: 1.结构伪类选择器 1.1 nth-child(公式) 2.伪元素选择器 1.结构伪类选择器 作用:根据元素的结构关系查找元素。 选择器说明E:first-child查找第一个E元素E:last-child查找最后一个E元素E:nth-child(N)查找第N个E元素(第一个元素N值为1) 一个列表结构…

Spring基础 - SpringMVC请求流程和案例

Spring基础 - SpringMVC请求流程和案例 什么是MVC 用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理…

服务器解析漏洞及任意文件下载

1.服务器文件解析漏洞 文件解析漏洞,是指Web容器(Apache、nginx、iis等)在解析文件时出现了漏洞,以其他格式执行出脚本格式的效果。从而,黑客可以利用该漏洞实现非法文件的解析。 (1) Apache linux系统中的apache的php配置文件在/etc/apac…

【深蓝学院】移动机器人运动规划--第4章 动力学约束下的运动规划--笔记

0. Outline 1. Introduction 什么是kinodynamic? 运动学(Kinematics)和动力学(Dynamics)都是力学的分支,涉及物体的运动,但它们研究的焦点不同。 运动学专注于描述物体的运动,而…

假期day5

TCP UDP区别 共同点:都是属于传输层的协议 TCP:稳定。面向连接的,有可靠的数据传输服务。传输过程中数据无误,无丢失,无失序,无重复。传输效率低,耗费资源多。数据收发不同步,有沾…

每日五道java面试题之java基础篇(六)

第一题:Java 创建对象有哪⼏种⽅式? Java 中有以下四种创建对象的⽅式: new 创建新对象通过反射机制采⽤ clone 机制通过序列化机制 前两者都需要显式地调⽤构造⽅法。对于 clone 机制,需要注意浅拷⻉和深拷⻉的区别,对于序列化机制需要明…

AJAX——认识URL

1 什么是URL? 统一资源定位符(英语:Uniform Resource Locator,缩写:URL,或称统一资源定位器、定位地址、URL地址)俗称网页地址,简称网址,是因特网上标准的资源的地址&…

Linux网络编程——tcp套接字

文章目录 主要代码关于构造listen监听accepttelnet测试读取信息掉线重连翻译服务器演示 本章Gitee仓库&#xff1a;tcp套接字 主要代码 客户端&#xff1a; #pragma once#include"Log.hpp"#include<iostream> #include<cstring>#include<sys/wait.h…

2024-2-11-复习作业

1> 要求&#xff1a; 源代码&#xff1a; #include <stdio.h> int fun(int n) {if(n0) return 1;return n*fun(n-1); } int main(int argc, char const *argv[]) {/* code */int n;printf("enter n :");scanf("%d",&n);int sfun(n);printf(…

JVM 性能调优 - 常用的垃圾回收器(6)

垃圾收集器 在 JVM(Java虚拟机)中,垃圾收集器(Garbage Collector)是负责自动管理内存的组件。它的主要任务是在程序运行过程中,自动回收不再使用的对象所占用的内存空间,以便为新的对象提供足够的内存。 JVM中的垃圾收集器使用不同的算法和策略来实现垃圾收集过程,以…

【Ubuntu 20.04/22.04 LTS】最新 esp-matter SDK 软件编译环境搭建步骤

仓库链接&#xff1a;esp-matter SDK官方软件说明&#xff1a;ESP Matter Programming Guide官方参考文档&#xff1a;使用 Matter-SDK 快速搭建 Matter 环境 (Linux) 环境要求 Ubuntu 20.04 或 Ubuntu22.04网络环境支持访问 Gihub 在安装 esp-matter SDK 软件编译环境之前&a…

金融行业专题|证券超融合架构转型与场景探索合集(2023版)

更新内容 更新 SmartX 超融合在证券行业的覆盖范围、部署规模与应用场景。新增操作系统信创转型、Nutanix 国产化替代、网络与安全等场景实践。更多超融合金融核心生产业务场景实践&#xff0c;欢迎阅读文末电子书。 在金融行业如火如荼的数字化转型大潮中&#xff0c;传统架…

mysql、mybatis中SORT

SORT排序 根据数据表sys_series中HOT&#xff08;int类型&#xff09;进行升序排列&#xff1a; 原来的数据库中存储&#xff1a; 排序 # 结果是HOT字段为null的所有数据都排在最前面&#xff0c;不为null的数据按升序排列 SELECT * FROM sys_series ORDER BY HOT;# 结果是H…

机器学习10-特征缩放

特征缩放的目的是确保不同特征的数值范围相近&#xff0c;使得模型在训练过程中更加稳定&#xff0c;加速模型收敛&#xff0c;提高模型性能。具体而言&#xff0c;零均值和单位方差的目标有以下几点好处&#xff1a; 1. 均值为零&#xff08;Zero Mean&#xff09;&#xff1a…

Git分支常用指令

目录 1 git branch 2 git branch xx 3 git checkout xx 4 git checkout -b xx 5 git branch -d xx 6 git branch -D xx 7 git merge xx(含快进模式和冲突解决的讲解) 注意git-log: 1 git branch 作用&#xff1a;查看分支 示例&#xff1a; 2 git branch xx 作用&a…

热数据存储在HDFS,冷备数据存储于对象存储中

1.场景分析 生产环境均为腾讯云服务器&#xff0c;日志数据计划存储于HDFS中&#xff0c;由于日志数据较大&#xff08;压缩后1T/天&#xff09;&#xff0c;不断扩充云盘成本消耗大。鉴于对象存储的存储成本较为低廉&#xff0c;但是日常频繁使用会产生流量费用。 鉴于此&…

【数学建模】【2024年】【第40届】【MCM/ICM】【C题 网球运动中的“动量”】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 MCM Problem C: Momentum in Tennis In the 2023 Wimbledon Gentlemen’s final, 20-year-old Spanish rising star Carlos Alcaraz defeated 36-year-old Novak Djokovic. The loss was Djokovic’s first at Wimbledon…