GO语言学习笔记(与Java的比较学习)(七)

结构与方法

结构体定义

结构体定义的一般方式如下:

type identifier struct {field1 type1field2 type2...
}
type T struct {a, b int}

也是合法的语法,它更适用于简单的结构体。

结构体里的字段都有 名字,像 field1、field2 等,如果字段在代码中从来也不会被用到,那么可以命名它为 _。

结构体的字段可以是任何类型,甚至是结构体本身,也可以是函数或者接口。

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)

使用 fmt.Println 打印一个结构体的默认输出可以很好的显示它的内容,类似使用 %v 选项。

就像在面向对象语言所作的那样,可以使用点号符给字段赋值:structname.fieldname = value。同样的,使用点号符可以获取结构体字段的值:structname.fieldname在 Go 语言中这叫 选择器(selector)

初始化一个结构体实例(一个结构体字面量:struct-literal)的更简短和惯用的方式如下:

ms := &struct1{10, 15.5, "Chris"}
// 此时ms的类型是 *struct1

或者:

var ms struct1
ms = struct1{10, 15.5, "Chris"}

混合字面量语法(composite literal syntax)&struct1{a, b, c} 是一种简写,底层仍然会调用 new (),这里值的顺序必须按照字段顺序来写。

结构体的内存布局

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和 Go 语言中的指针很像。

递归结构体

结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)

使用工厂方法创建结构体实例

结构体工厂

Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂” 方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。

type File struct {fd      int     // 文件描述符name    string  // 文件名
}

下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:

func NewFile(fd int, name string) *File {if fd < 0 {return nil}
​return &File{fd, name}
​
}

然后这样调用它:

f := NewFile(10, "./test.txt")

带标签的结构体

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 reflect 能获取它。

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体

可以粗略地将这个和面向对象语言中的继承概念相比较,随后将会看到它被用来模拟类似继承的行为。Go 语言中的继承是通过内嵌或组合来实现的,所以可以说,在 Go 语言中,相比较于继承,组合更受青睐。

package main
​
import "fmt"
​
type innerS struct {in1 intin2 int
}
​
type outerS struct {b      intc      float32int    // anonymous fieldinnerS //anonymous field
}
​
func main() {outer := new(outerS)outer.b = 6outer.c = 7.5outer.int = 60outer.in1 = 5outer.in2 = 10
​fmt.Printf("outer.b is: %d\n", outer.b)fmt.Printf("outer.c is: %f\n", outer.c)fmt.Printf("outer.int is: %d\n", outer.int)fmt.Printf("outer.in1 is: %d\n", outer.in1)fmt.Printf("outer.in2 is: %d\n", outer.in2)
​//使用结构体字面量outer2 := outerS{6, 7.5, 60, innerS{5, 10}}fmt.Println("outer2 is:", outer2)
}

注意:通过类型 outer.int 的名字来获取存储在匿名字段中的数据,于是可以得出一个结论:在一个结构体中对于每一种数据类型只能有一个匿名字段。

内嵌结构体

同样地结构体也是一种数据类型,所以它也可以作为一个匿名字段来使用,如同上面例子中那样。外层结构体通过 outer.in1 直接进入内层结构体的字段,内嵌结构体甚至可以来自其他包。内层结构体被简单的插入或者内嵌进外层结构体。这个简单的 “继承” 机制提供了一种方式,使得可以从另外一个或一些类型继承部分或全部实现。

package main
​
import "fmt"
​
type animal struct {name   stringaction stringage    int
}
​
type cat struct {food stringshot stringanimal
}
​
func main() {cat1 := cat{"fish", "miaomiaomiao", animal{"cat", "jump", 1}}fmt.Printf("the name is %s\n, the age is %d\n, the action is %d\n, the food is %s\n, the shot is %s\n",cat1.name, cat1.age, cat1.action, cat1.food, cat1.shot)cat1.name = "猎鹰"fmt.Printf("the name is %s\n, the age is %d\n, the action is %d\n, the food is %s\n, the shot is %s\n",cat1.name, cat1.age, cat1.action, cat1.food, cat1.shot)
}

命名冲突

当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?

外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式; 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。

方法

方法是什么

在 Go 中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。

接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是一个接口类型。,因为接口是一个抽象定义,但是方法却是具体实现。接收者不能是一个指针类型,但是它可以是任何其他允许类型的指针。

一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。

因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在

func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix
  • func (a *denseMatrix) Add(b Matrix) Matrix:这是方法的定义。在 Go 语言中,方法的定义以关键字 func 开始,后面紧跟着接收者(receiver)和方法名称。在这里,a *denseMatrix 是接收者,表示这个方法属于 denseMatrix 类型。接收者的类型是在方法名称之前定义的,用括号括起来。这里的 a 是接收者的名称,*denseMatrix 表示这个方法是针对指向 denseMatrix 类型的指针的。

  • Add(b Matrix):这是方法的签名部分。方法名称是 Add,它接受一个名为 b 的参数,参数的类型是 Matrix。在这里,Matrix 可能是一个接口类型,表示这个方法可以接受任何实现了 Matrix 接口的类型作为参数。

  • Matrix:这是方法的返回类型。在 Go 中,方法可以有一个返回类型,这里的 Matrix 表示这个方法会返回一个 Matrix 类型的结果。

package main
​
import "fmt"
​
type TwoInts struct {a intb int
}
​
func main() {two1 := new(TwoInts)two1.a = 12two1.b = 10
​fmt.Printf("The sum is: %d\n", two1.AddThem())fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))
​two2 := TwoInts{3, 4}fmt.Printf("The sum is: %d\n", two2.AddThem())
}
​
func (tn *TwoInts) AddThem() int {return tn.a + tn.b
}
​
func (tn *TwoInts) AddToParam(param int) int {return tn.a + tn.b + param
}

类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法。试图在 int 类型上定义方法会得到一个编译错误

但是有一个间接的方式:可以先定义该类型(比如:int 或 float)的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。

package main
​
import ("container/list""fmt"
)
​
type CustomList struct {*list.List
}
​
func (p *CustomList) Iter() {for e := p.Front(); e != nil; e = e.Next() {fmt.Println(e.Value)}
}
​
func main() {lst := &CustomList{list.New()}
​// 添加一些元素到列表中lst.PushBack(1)lst.PushBack(2)lst.PushBack(3)
​// 使用自定义的 Iter 方法遍历列表lst.Iter()
}

函数和方法的区别

函数将变量作为参数:Function1(recv)

方法在变量上被调用:recv.Method1()

方法调用类似于Java中对象调用方法,而函数就是当前类中调用方法

方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。

指针或值作为接收者

鉴于性能的原因,recv 最常见的是一个指向 receiver_type 的指针(因为我们不想要一个实例的拷贝,如果按值调用的话就会是这样),特别是在 receiver 类型是结构体时,就更是如此了。

如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。

package main
​
import "fmt"
​
type Person struct {Name stringAge  int
}
​
func (p Person) UpdateName(newName string) {p.Name = newName
}
​
func (p *Person) UpdateAge(newAge int) {p.Age = newAge
}
​
func main() {person1 := Person{Name: "Alice", Age: 30}person1.UpdateName("Bob")fmt.Println(person1.Name) // 输出: Alice,因为在值类型上定义的方法没有修改原始值
​person1.UpdateAge(35)fmt.Println(person1.Age) // 输出: 35,因为在指针类型上定义的方法修改了原始值
}
  1. 在值类型上定义方法

    • 当你在值类型上定义方法时,方法接收的是该值的副本。这意味着在方法内部对接收者的修改不会影响原始值。

    • 这种方式适用于不需要在方法内部修改接收者数据的情况,或者对数据的修改是独立于原始数据的。

  2. 在指针类型上定义方法

    • 当你在指针类型上定义方法时,方法接收的是指向该值的指针。这意味着在方法内部对接收者的修改会影响原始值。

    • 这种方式适用于需要在方法内部修改接收者数据的情况,或者对数据的修改需要影响原始数据的情况。

指针方法和值方法都可以在指针或非指针上被调用

package main
​
import ("fmt"
)
​
type List []int
​
func (l List) Len() int        { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
​
func main() {// 值var lst Listlst.Append(1)fmt.Printf("%v (len: %d)", lst, lst.Len()) // [1] (len: 1)
​// 指针plst := new(List)plst.Append(2)fmt.Printf("%v (len: %d)", plst, plst.Len()) // &[2] (len: 1)
}

方法和未导出字段

使用getter和setter方法获取未导出参数:

package Person
​
type Person struct {firstName  stringsecondName string
}
​
func (p *Person) FirstName() string {return p.firstName
}
func (p *Person) SetFirstName(name string) {p.firstName = name
}
package main
​
import ("fmt""goProjects/Person"
)
​
func main() {p := new(Person.Person)oldName := p.FirstName()fmt.Printf("the old first name is %s\n", oldName)p.SetFirstName("Ye")newName := p.FirstName()fmt.Printf("the new first name is %s", newName)
}

类型的 String() 方法和格式化描述符

当定义了一个有很多方法的类型时,十之八九你会使用 String() 方法来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出。如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print() 和 fmt.Println() 也会自动使用 String() 方法。

package main
​
import ("fmt""strconv"
)
​
type TwoInts struct {a intb int
}
​
func main() {two1 := new(TwoInts)two1.a = 12two1.b = 10fmt.Printf("two1 is: %v\n", two1)fmt.Println("two1 is:", two1)fmt.Printf("two1 is: %T\n", two1)fmt.Printf("two1 is: %#v\n", two1)
}
​
func (tn *TwoInts) String() string {return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"
}

垃圾回收和 SetFinalizer

Go 开发者不需要写代码来释放程序中不再使用的变量和结构占用的内存,在 Go 运行时中有一个独立的进程,即垃圾收集器(GC),会处理这些事情,它搜索不再使用的变量然后释放它们的内存。可以通过 runtime 包访问 GC 进程。

通过调用 runtime.GC() 函数可以显式的触发 GC,但这只在某些罕见的场景下才有用,比如当内存资源不足时调用 runtime.GC(),它会在此函数执行的点上立即释放一大片内存,此时程序可能会有短时的性能下降(因为 GC 进程在执行)。

学习参考资料:

《Go 入门指南》 | Go 技术论坛 (learnku.com)

Go 语言之旅

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

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

相关文章

linux服务器连接数

一、要查看 Linux 服务器的最大连接数 我们需要关注两个值&#xff1a;文件描述符限制&#xff08;file descriptor limit&#xff09;和内核参数 "net.core.somaxconn"。 1. 查看文件描述符限制&#xff08;file descriptor limit&#xff09; 文件描述符限制决定了…

System Verilog学习笔记(十)——任务和函数

System Verilog学习笔记&#xff08;十&#xff09;——任务和函数 函数&#xff08;function&#xff09;和任务&#xff08;task&#xff09;可以提高代码的复用性和整洁度。他们的目的在于将大型的过程块切分为更小的片段&#xff0c;而便于阅读和代码维护。 区别 functi…

物联网边缘计算云边协同

文章目录 一、物联网云边协同1.IoT云边协同设计2.物联网平台设计3.物联网平台实现 二、部署环境1.节点配置2.版本信息 三、IoT云边协同部署1.部署Kubernetes集群2.部署KubeEdge3.部署ThingsBoard集群4.部署Node-RED边缘网关4.1.边缘网关功能4.2.部署EMQX4.2.部署Node-RED 5.配置…

(正规api接口代发布权限)短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态

短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态源头开发&#xff08;本篇禁止抄袭复刻&#xff09; 一、短视频矩阵系统开发者架构 云罗短视频矩阵系统saas化系统&#xff0c;开发层将在CAP原则基础上使用分布式架构,对此网站的整体架构采用了基于B/S三层架构模式…

React 模态框的设计(五)主体设计

弹窗的主体设计没什么特别&#xff0c;就是把细分化后的各个功能封装在一个个的小组件内&#xff0c;然后再整合。这样逻辑就分开了&#xff0c;不乱。 弹窗容器 这个容器是弹窗主体的根组件(不含遮罩)&#xff0c;要能根据主题的变化能做出相应的改变。还要记录渲染后的主体…

java016学习记录

Java Web 使用 Java 开发语言完成 Web 后端的项目开发 基于 Web 的应用&#xff1a;网站、APP、小程序 Java 不是做前端的&#xff0c;做后端 Tomcat、Servlet Tomcat 的作用让 Java Web 程序可以正常运行&#xff0c;被浏览器访问 浏览器默认不能访问电脑上的资源&#…

Django Web架构:全面掌握Django模型字段(下)

Django Web架构 全面掌握Django模型字段&#xff08;下&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article…

C++之vector

1、vector概念 功能&#xff1a;vector的功能和数组非常相似&#xff0c;可以看成是一个升级版的数组。 vector和普通数组的区别&#xff1a; &#xff08;1&#xff09;数组是静态的&#xff0c;长度不可改变&#xff0c;而vector可以动态扩展&#xff0c;增加长度。…

协议和序列化反序列化

“协议”和序列化反序列化 “协议”的概念&#xff1a; “协议”本身是一种约定俗成的东西&#xff0c;由通讯双方必须共同遵从的一组约定&#xff0c;因此我们一定要将这种约定用计算机语言表达出来&#xff0c;此时双方计算机才能识别约定的相关内容 我们把这个规矩叫做“…

机器学习|决策树

左图的点是一种线性不可分的情况&#xff0c;无法拿一条直线去将进行分开。 每一个节点都代表一个决策&#xff0c;从而导致节点的分流。 最终的目标肯定是要达到分类。 但取得目标的过程是有所谓的好坏。 而这个好坏用熵/信息增益来衡量。 熵是一种用于反映系统混乱程度的物理…

思科网络设备监控

思科是 IT 行业的先驱之一&#xff0c;提供从交换机到刀片服务器的各种设备&#xff0c;以满足中小企业和企业的各种 IT 管理需求。管理充满思科的 IT 车间涉及许多管理挑战&#xff0c;例如监控可用性和性能、管理配置更改、存档防火墙日志、排除带宽问题等等&#xff0c;这需…

Makefile从入门到项目编译实战(学习笔记)

1.make和makefile介绍 1. make make 是一个应用程序&#xff0c;位于 /usr/bin/make 目录下&#xff0c;make 有如下的功能&#xff1a; &#xff08;1&#xff09;解析源程序之间的依赖关系 &#xff08;2&#xff09;根据依赖关系自动维护编译工作 &#xff08;3&#xff09…

Jmeter插件PerfMon Metrics Collector安装使用及报错解决

Jmeter作为一个轻量级的性能测试工具&#xff0c;开源、小巧、灵活的特性使其越来越受到测试人员喜爱。在实际的项目中&#xff0c;特别是跨地区项目&#xff0c;排除合作方指定要求使用Loadrunner作为性能测试工具外&#xff0c;Jmeter会是首选。 本篇文章&#xff0c;就着重…

Python 全栈系列226 GlobalBuffer

说明 为了简化开发程序&#xff0c;特别是需要依赖全局数据的程序&#xff0c;例如&#xff1a;分布式任务需要知道当前可处理的任务&#xff1b;定时程序需要依据某个约束性全局变量。一个附带的好处是会大量减少对数据库产生的请求。 GlobalBuffer的代价并不高&#xff1a;…

基于C++的http通信记录,使用httplib,Windows环境,vscode,mingw

使用了 httplib库&#xff0c;链接&#xff0c;使用该库只需要包含头文件即可&#xff0c;另外在Windows环境下&#xff0c;使用mingw构建项目&#xff0c;需要在编译时链接网络编程库&#xff0c;task.json如下&#xff1a; {"version": "2.0.0","t…

【MATLAB源码-第149期】基于MATLAB的2ASK,2FSK,2PSK,2DPSK等相干解调仿真,输出各节点波形。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 2ASK&#xff08;二进制幅移键控&#xff09;、2FSK&#xff08;二进制频移键控&#xff09;、2PSK&#xff08;二进制相移键控&#xff09;和2DPSK&#xff08;二进制差分相移键控&#xff09;是数字调制技术中的基本调制方…

汽车三元催化器的废品项目详解,三元催化再生项目的回收技术教学

一、教程描述 这是一个收废品项目&#xff0c;就收那些别人不懂的&#xff0c;三元催化器的附加值高&#xff0c;只要掌握技术&#xff0c;怎么玩都行的&#xff0c;只是要放得下你的面子。三元催化器&#xff0c;是安装在汽车排气系统中最重要的机外净化装置&#xff0c;它可…

MATLAB环境基于全局和局部多特征融合的红外图像分割主动轮廓模型

红外图像是一种热图像&#xff0c;不受光照影响&#xff0c;在光照条件差、有烟雾遮挡的环境中&#xff0c;可以辅助或代替可见光成像在各个领域中应用&#xff0c;同时作为热成像&#xff0c;对景物的热辐射敏感&#xff0c;在安全监测、质量检验等领域具有优势。在以下几个方…

Apache Doris Sink Connector部署指南

在当今数据驱动的时代&#xff0c;如何高效、准确地处理和分析大数据成为了各行各业面临的共同挑战。Apache Doris&#xff0c;作为一个基于 MPP 架构的高性能、实时的分析型数据库&#xff0c;为大规模数据分析提供了强大的支持。 在当今数据驱动的时代&#xff0c;如何高效、…

CSS的弹性布局

CSS 的弹性布局 前言 前端中为了实现页面的布局效果&#xff0c;采用的一个技术手段&#xff0c;它在前端开发的技术场景是非常广泛的 实现上述区域的页面相关的布局效果&#xff0c;就可以使用弹性布局来完成 弹性布局(flex布局) flex 是 flexible box 的缩写&#xff0c;…