【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.…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...
