【Golang】Golang基础语法之面向对象:结构体和方法
面向对象——结构
- Go 仅支持封装,不支持继承和多态;继承和多态要做的事情交给接口来完成,即——面向接口编程。
- Go 只有 struct,没有 class。
定义一个最简单的树节点(treeNode)结构,方法如下:
package mainimport "fmt"type treeNode struct {value intleft, right *treeNode
}func main() {var root treeNodefmt.Println(root)
}
输出的结果是:
{0 <nil> <nil>}
如果想赋予 treeNode 初值,则可以按照下述方式来对 treeNode 进行初始化:
var root treeNoderoot = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
需要注意的是,Go 的 struct 使用花括号进行值初始化,而没有构造函数的概念。
注意最后一行的 new 方法,它是 Go 的内置方法,其行为非常类似于 C++ 的 new 关键字,即新划分一个动态内存,并返回类类型对应的指针。
还有一点要注意的是,最后一行的root.right.left
是指针,在 C++ 中,应该使用root.right -> left
来对指针所指对象的成员进行访问,但是在 Golang 当中没有这么严格的要求。可以总结为:无论是地址还是指针,均使用 . 来访问成员。
可以将 struct 定义在 slice 当中,方法也非常的简单,声明一个对应类型的 slice 并使用花括号进行初始化即可:
nodes := []treeNode{{value: 3},{},{6, nil, &root},
} // 注意, 在花括号初始化当中可以省略 treeNode 关键字
fmt.Println(nodes)
输出的结果为:
[{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc0000080d8}] // 最后一个地址因操作设备而异
结构有构造函数吗?
Golang 的 struct 没有构造函数这一说法。
当需要构造函数时,可以构建一个工厂函数来进行替代:
func createNode(value int) *treeNode {// 使用工厂函数来构建结构对象, 返回一个结构对象的指针return &treeNode{value: value}
}
需要注意的是,从 C++ 的角度来看,上述代码是非常典型的错误,因为它试图返回局部变量的地址给外部。
但是在 Golang 当中,上述语句是合法的,这也是 Golang 和 C++ 较大的区别,即 Golang 可以在局部返回变量的地址。
C++ 将变量的内存分配在栈上,当新建动态内存时,需要开发者自己进行资源的调度和销毁。而 Java 将变量的内存分配在堆上,支持垃圾回收机制。而我们不需要关心 Golang 将内存分配在栈上还是堆上,编译器会帮助我们管理。当局部变量的地址被返回时,编译器将内存分配在堆上,否则分配在栈上。
结构有成员函数吗?
Golang 的结构同样没有成员函数,但是 Golang 具有一种函数定义的语法糖,通过下述方式对函数进行定义,则结构对象可以直接以成员访问的形式对函数进行调用,但本质上该函数不是结构的成员函数,这类函数被称作结构的方法:
func (node treeNode) print() {fmt.Println(node.value)
}
关键字 func 后面的圆括号当中的 (node treeNode) 被称作函数的接收者,接收者有两种类型,分别是值接收者和指针接收者。需要指针接收者的原因是,Golang 的函数只有值传递,因此如果希望在结构的方法中对结构的值进行修改,或是在一个实现了较大型功能的函数调用上确保效率,则需要使用指针接收者的形式来对结构的方法进行定义。
首先来看上述值接收者方法的调用:
直接调用:
root.print()
即可完成 root 对应成员 value 的值的打印。
一个典型的指针接收者的例子如下,在该例中我们试图修改 node 的 value:
func (node *treeNode) setValue(value int) {node.value = value
}
之前我们已经提到,无论结构对象此时是值还是指针,访问其成员的方法都是使用 .
。
注意,nil 指针也可以调用方法。在 Go 当中,nil 是一个安全的可以调用结构的方法的指针。
但是在方法的具体实现上,虽然 nil 可以安全地调用方法,但是在方法中不能对 nil 指针指向对象的成员进行修改,因此可以对 setValue 进行修改:
func (node *treeNode) setValue(value int) {if node == nil {fmt.Println("Setting value to nil node. " +"Ignored")return}node.value = value
}
实验如下:
var pRoot *treeNode
pRoot.setValue(200)
pRoot = &root
pRoot.setValue(300)
pRoot.print()
输出为:
Setting value to nil node. Ignored
300
由于 nil 指针仍然可以调用结构的方法,因此可以方便地实现树的中序遍历:
func (node *treeNode) traverse() {if node == nil {return}node.left.traverse() // 不需要判断 left 是否为空指针, 即使是空指针也可以调用node.print()node.right.traverse()
}
- 值接收者是 Go 特有的;
- 值/指针接收者均可接受值/指针。
包和封装
- 在 Go 当中,名字一般使用 CamelCase;
- 首字母大写代表 public;
- 首字母小写代表 private;
此处的 public 和 private 是针对包(package)来说的。
- 每个目录只有一个包;
- main 包 包含可执行入口;
- 为结构定义的方法必须放在同一个包内;
- 可以在包内的不同文件对结构的方法进行定义;
现在我们在目录下新建一个名为 entry 的目录,并将 node.go 当中的 main 函数迁移到 entry 目录下的 entry.go 当中。注意将 entry.go 的 package 改为 main。
然后,修改 node.go 当中的结构成员和结构方法首字母为大写,以代表它们是 public 的。
文件目录的组织方法如下:
entry.go 当中的内容是:
package mainimport "learngo/tree"func main() {var root tree.Noderoot = tree.Node{Value: 3}root.Left = &tree.Node{}root.Right = &tree.Node{5, nil, nil}root.Right.Left = new(tree.Node)root.Left.Right = tree.CreateNode(2)root.Right.Left.SetValue(4)root.Traverse()
}
node.go 当中的内容是:
package treeimport "fmt"type Node struct {Value intLeft, Right *Node
}func (node Node) Print() {fmt.Print(node.Value, " ")
}func (node *Node) SetValue(value int) {if node == nil {fmt.Println("Setting Value to nil node. " +"Ignored")return}node.Value = value
}func CreateNode(value int) *Node {// 使用工厂函数来构建结构对象, 返回一个结构对象的指针return &Node{value, nil, nil}
}func (node *Node) Traverse() {if node == nil {return}node.Left.Traverse()node.Print()node.Right.Traverse()
}
需要注意的是,将结构 TreeNode 修改为 Node,因为结构的名字不宜与包名 tree 前缀相重复。虽然包名与结构名的前缀重复并不会报错,但是这样会降低代码的可读性。
扩展已有的类型
我们已经知道,为结构定义的方法必须放在同一个包内,并且可以放在不同的文件当中。那么如果现在有一个已知类型,它的作者不是我们,但我们想对它进行拓展,此时应该怎么做呢?
由于 Go 语言没有继承,想要扩充系统类型或已知的自定义类型的方法有三种,分别是:
- 定义别名;
- 使用组合;
- 使用内嵌(Embedding);
使用组合
假定我们现在希望对二叉树进行后序遍历,但是 tree 当中仅实现了中序遍历,一个可行的方法是将 Node 结构进行包装,变为 myTreeNode:
/* tree.Node 只实现了中序遍历, 现在我们希望实现后序遍历 */
type myTreeNode struct {node *tree.Node
}
之后,我们对 myTreeNode 通过指针接收者的方式实现后序遍历:
func (myNode *myTreeNode) postOrder() {if myNode.node == nil || myNode.node == nil { // 需要判断 myTreeNode 的成员是否为 nil 指针return}left := myTreeNode{myNode.node.Left} // 注意, 成员初始化应使用花括号而非圆括号left.postOrder()right := myTreeNode{myNode.node.Right}right.postOrder()myNode.node.Print()
}
在 main 函数中的调用:
node := myTreeNode{&root}
node.postOrder()
fmt.Println()
定义别名
我们已经知道,可以使用 slice 来实现 int 类型的队列。
我们可以使用关键字 type 定义 []int 的别名,将其命名为 queue:
package queuetype Queue []int // 目前的 queue 是一个 int 的 slicefunc (q *Queue) Push(v int) {*q = append(*q, v) // q 指向的 slice 被改变了
}func (q *Queue) Pop() int {head := (*q)[0]*q = (*q)[1:]return head
}func (q *Queue) IsEmpty() bool {return len(*q) == 0
}
实验如下:
package mainimport ("fmt""learngo/queue"
)func main() {q := queue.Queue{1}q.Push(2)q.Push(3)fmt.Println(q.Pop())fmt.Println(q.Pop())fmt.Println(q.IsEmpty())fmt.Println(q.Pop())fmt.Println(q.IsEmpty())
}
使用内嵌(Embedding)
使用内嵌进行类型拓展的示例如下:
/*tree.Node 只实现了中序遍历, 现在我们希望实现后序遍历使用内嵌的方式来实现已知类型的拓展
*/
type myTreeNode struct {// 👇 直接省略类型的变量名, 即可完成内嵌// 之前的定义是 node *tree.Node, 使用 node 作为结构的成员变量// 在使用内嵌进行拓展时, 不需要使用 node*tree.Node // Embedding// 👆 语法糖, 节省代码量
}func (myNode *myTreeNode) postOrder() {if myNode.Node == nil || myNode.Node == nil {return}left := myTreeNode{myNode.Left} // 可以注意到, 使用内嵌之后, 可以直接访问 tree.Node 的成员变量以及方法left.postOrder()right := myTreeNode{myNode.Right}right.postOrder()myNode.Print()
}
内嵌看起来与 C++ 当中的继承非常的相似,但是本质上内嵌和继承还是有去别的。需要重点强调的是,Go 没有继承和多态,只有封装。
假设现在我们要使用 myTreeNode 类型实现一个 Traverse,在其它语言当中该行为会被视为重载,在 Go 中,Node 内嵌在了 myTreeNode 当中,而在其它语言当中会被视为继承关系。
IDE 会提示我们“转到阴影方法”:
假设现在的实现为:
func (myNode *myTreeNode) Traverse() {fmt.Println("this method is shadowed")
}
则使用 myTreeNode 对 Traverse 进行调用的结果如下:
this method is shadowed
确保 myTreeNode 调用 Node 的 Traverse() 方法的做法是,显式地调用root.Node.Traverse()
,即显式地对嵌入的方法进行调用。
继承的一个非常有用的性质是,可以将一个基类指针与派生类对象进行关联,但是在 Go 当中是不可行的:
因此内嵌在本质上是一个语法糖,内嵌的类型与结构类型没有关联。
Golang 通过接口来实现类似于继承的做法,即——面向接口编程。
相关文章:

【Golang】Golang基础语法之面向对象:结构体和方法
面向对象——结构 Go 仅支持封装,不支持继承和多态;继承和多态要做的事情交给接口来完成,即——面向接口编程。Go 只有 struct,没有 class。 定义一个最简单的树节点(treeNode)结构,方法如下&…...

【西门子PLC.博途】——在S71200里写时间设置和读取功能块
之前我们在这篇文章中介绍过如何读取PLC的系统时间。我们来看看在西门子1200里面有什么区别。同时也欢迎关注gzh。 我们在S71200的帮助文档中搜索时间后找到这个数据类型 在博途中他是一个结构体,具体为 然后我们再看看它带的读取和写入时间块 读取时间࿱…...

位运算(一)位运算简单总结
191. 位1的个数 给定一个正整数 n,编写一个函数,获取一个正整数的二进制形式并返回其二进制表达式中 设置位 的个数(也被称为 汉明重量)。 示例 1: 输入:n 11 输出:3 解释:输入的二…...

工厂方法模式的理解和实践
在软件开发中,设计模式是一种经过验证的解决特定问题的通用方案。工厂方法模式(Factory Method Pattern)是创建型设计模式之一,它提供了一种创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推…...

C# 设计模式--观察者模式 (Observer Pattern)
定义 观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。观察者模式的核心在于解耦主题(被观察者)和观察者之间的依赖关系。 …...

【开发语言】层次状态机(HSM)介绍
层次状态机(Hierarchical State Machine, HSM),从基本原理、结构设计、实现方法以及如何结合 Qt 进行具体实现等方面进行分析。 1. 层次状态机的基本原理 层次状态机是一种用于管理复杂系统行为的状态机模型,它通过将状态组织成…...

03-13、SpringCloud Alibaba第十三章,升级篇,服务降级、熔断和限流Sentinel
SpringCloud Alibaba第十三章,升级篇,服务降级、熔断和限流Sentinel 一、Sentinel概述 1、Sentinel是什么 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保…...

【k8s 深入学习之 event 聚合】event count累记聚合(采用 Patch),Message 聚合形成聚合 event(采用Create)
参考 15.深入k8s:Event事件处理及其源码分析 - luozhiyun - 博客园event 模块总览 EventRecorder:是事件生成者,k8s组件通过调用它的方法来生成事件;EventBroadcaster:事件广播器,负责消费EventRecorder产生的事件,然后分发给broadcasterWatcher;broadcasterWatcher:用…...

leetcode104.二叉树的最大深度
给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:3示例 2: 输入:root [1,null,2] 输出…...

蓝桥杯2117砍竹子(简单易懂 包看包会版)
问题描述 这天, 小明在砍竹子, 他面前有 n 棵竹子排成一排, 一开始第 i 棵竹子的 高度为 hi. 他觉得一棵一棵砍太慢了, 决定使用魔法来砍竹子。魔法可以对连续的一 段相同高度的竹子使用, 假设这一段竹子的高度为 H, 那么 用一次魔法可以 把这一段竹子的高度都变为 ⌊H2⌋…...

LCD与lvgl
LCD与lvgl 目录 LCD与lvgl 回顾 LCD 的驱动层讲解 1、LCD 的常见接口 2、我们的 LCD 的参数 3、LCD 的设备树说明 4、LCD 的设备树说明 5、如何移植 LCD 的驱动(重点) LCD 的应用层开发 1:LCD 应用开发->界面开发的方法 2:LVGL 模拟器安装 3:LVGL 工程创建和…...

SpringBoot 赋能:精铸超稳会员制医疗预约系统,夯实就医数据根基
1绪论 1.1开发背景 传统的管理方式都在使用手工记录的方式进行记录,这种方式耗时,而且对于信息量比较大的情况想要快速查找某一信息非常慢,对于会员制医疗预约服务信息的统计获取比较繁琐,随着网络技术的发展,采用电脑…...

android studio 读写文件操作(应用场景二)
android studio版本:2023.3.1 patch2 例程:readtextviewIDsaveandread 本例程是个过渡例程,如果单是实现下图的目的有更简单的方法,但这个方法是下一步工作的基础,所以一定要做。 例程功能:将两个textvi…...

小尺寸低功耗蓝牙模块在光伏清扫机器人上的应用
一、引言 随着可再生能源的迅速发展,光伏发电系统的清洁与维护变得越来越重要。光伏清扫机器人通过自动化技术提高了清洁效率,而蓝牙模组的集成为这些设备提供了更为智能的管理和控制方案。 二、蓝牙模组的功能与实现: 蓝牙模组ANS-BT103M…...

防火墙有什么作用
防火墙的作用:1. 提供网络安全防护;2. 实施访问控制和流量过滤;3. 检测和阻止恶意攻击;4. 保护内部网络免受未经授权的访问;5. 监控网络流量和安全事件;6. 支持虚拟专用网络(VPN)。防…...

MongoDB-BSON 协议与类型
前言: MongoDB 是一个高性能、无模式的 NoSQL 数据库,广泛应用于大数据处理和实时数据存储。作为一个数据库系统,MongoDB 的核心之一就是其使用的 BSON(Binary JSON)格式,它用于存储数据以及在客户端和数据…...

学习threejs,使用VideoTexture实现视频Video更新纹理
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️VideoTexture 视频纹理 二、…...

怎么获取键值对的键的数值?
问: 通过paelData.cardMap.C0002112可以获取到Cooo2112里面的数据,但是有时候接口返回的不是C0002112而是C0002093或者其他值,请问我该怎么写? 后端返回的数据是这样的: cardMap: { C0002112: { name: Item 1, va…...

数据结构排序算法详解
数据结构排序算法详解 1、冒泡排序(Bubble Sort)2、选择排序(Selection Sort)2、插入排序(Insertion Sort) 1、冒泡排序(Bubble Sort) 原理:越小的元素会慢慢“浮”到数…...

在Linux设置postgresql开机自启动,创建一个文件 postgresql-15.service
在Linux设置postgresql开机自启动,创建一个文件 postgresql-15.service 在Linux设置postgresql开机自启动,创建一个文件 postgresql-15.service1. 创建 systemd 服务文件2. 编辑服务文件3. 保存并退出4. 重新加载 systemd 配置5. 启动 PostgreSQL 服务6.…...

【kafka】消息队列的认识,Kafka与RabbitMQ的简单对比
什么是消息队列? 消息队列(Message Queue,简称 MQ)是一个在不同应用程序、系统或服务之间传递数据的机制。 它允许系统间异步地交换信息,而无需直接交互,确保消息的可靠传输。 想象一下,你正在…...

ProjectSend 身份认证绕过漏洞复现(CVE-2024-11680)
0x01 产品描述: ProjectSend 是一个开源文件共享网络应用程序,旨在促进服务器管理员和客户端之间的安全、私密文件传输。它是一款相当流行的应用程序,被更喜欢自托管解决方案而不是 Google Drive 和 Dropbox 等第三方服务的组织使用。0x02 漏洞描述: ProjectSend r1720 之前…...

Android笔记(三十四):onCreate执行Handler.post在onResume后才能执行?
背景 偶然发现一个点,就是在onCreate执行Handler.post在onResume后才执行,以下是测试代码 多次运行的结果一致,为什么execute runnable不是在onCreate和onResume之间执行的呢,带着疑问撸了一遍Activity启动流程 关键源码分析 …...

关闭模组的IP过滤功能
关闭模组的IP过滤功能 关闭模组的IP过滤功能 本脚本用于关闭模组的IP过滤功能,关闭后, 源地址不是终端IP的数据包,也可以被模组转发给网络 关闭模组的IP过滤功能 cat > /usr/bin/ipfilter << "EOF"echo -e "ATCFUN…...

算法分析与设计复习笔记
插入排序 1. void insert_sort(int A[ ],int n) 2. { 3. int a,i,j; 4. for (i1;i<n;i) { 5. a A[ i ]; 6. j i – 1; 7. while (j>0 && A[j]>a) { 8. A[ j…...

vue-amap 高德地图
vue-amap是一套基于Vue 2/vue3和高德地图的地图组件 vue-amap 高德地图2.0版本的对应vue3...

Numpy基础练习
import numpy as np 1.创建一个长度为10的一维全为0的ndarray对象,然后让第5个元素等于1 n np.zeros(10,dtypenp.int32) n[4] 12.创建一个元素从10到49的ndarray对象 n np.arrange(10,50)3.将第2题的所有元素位置反转 n[::-1]使用np.random.random创建一个10*10的ndarray对象…...

一番赏小程序定制开发,打造全新抽赏体验平台
随着盲盒的热潮来袭,作为传统的潮玩方式一番赏也再次受到了大家的关注,市场热度不断上升! 一番赏能够让玩家百分百中奖,商品种类丰富、收藏价值高,拥有各种IP,从而吸引着各个圈子的粉丝玩家,用…...

【前端】将vue的方法挂载到window上供全局使用,也方便跟原生js做交互
【前端】将vue的方法挂载到window上供全局使用,也方便跟原生js做交互 <template><div><el-button click"start">调用方法</el-button></div> </template> <script> // import { JScallbackProc } from ./JScal…...

Oracle查询优化:高效实现仅查询前10条记录的方法与实践
在 Oracle 中,实现仅查询前10条记录的四种方法 1. 使用 ROWNUM 查询 ROWNUM 是 Oracle 中的伪列,用于限制返回的行数。 SELECT * FROM table_name WHERE condition AND ROWNUM < 10;condition:查询条件。ROWNUM < 10:限制…...