Java程序员学习Go指南(二)
摘抄:https://www.luozhiyun.com/archives/211
Go中的结构体
构建结构体
如下:
type AnimalCategory struct {
kingdom string // 界。
phylum string // 门。
class string // 纲。
order string // 目。
family string // 科。
genus string // 属。
species string // 种。
}
func (ac AnimalCategory) String() string {
return fmt.Sprintf("%s%s%s%s%s%s%s",
ac.kingdom, ac.phylum, ac.class, ac.order,
ac.family, ac.genus, ac.species)
}
我们在Go中一般构建一个结构体由上面代码块所示。AnimalCategory结构体中有7个string类型的字段,下边有个名叫String的方法,这个方法其实就是java类中的toString方法。其实这个结构体就是java中的类,结构体中有属性,有方法。
category := AnimalCategory{species: "cat"}
fmt.Printf("The animal category: %s\n", category)
我们在上面的代码块中初始化了一个AnimalCategory类型的值,并把它赋给了变量category,通过调用fmt.Printf方法调用了category实例内的String方法,⽽⽆需 显式地调⽤它的String⽅法。
在结构体中声明一个嵌入字段
因为在Go中是没有继承一说,所以使用了嵌入字段的方式来实现类型之间的组合,实现了方法的重用。
这里继续用到上面的结构体AnimalCategory
type Animal struct {
scientificName string // 学名。
AnimalCategory // 动物基本分类。
}
字段声明AnimalCategory代表了Animal类型的⼀个嵌⼊字段。Go语⾔规范规定,如果⼀个字段 的声明中只有字段的类型名⽽没有字段的名称,那么它就是⼀个嵌⼊字段,也可以被称为匿名字段。嵌⼊字段的类型既是类型也是名称。
如果要像java中引用字段里面的属性,那么可以这么写:
func (a Animal) String() string {
return a.AnimalCategory.String()
}
这里还是和java是一样的,但是接下来要讲的却和java有很大区别
由于我们在AnimalCategory中写了一个String的方法,如果我们没有给Animal写String的方法,那么我们直接打印会得到什么结果?
category := AnimalCategory{species: "cat"}
animal := Animal{
scientificName: "American Shorthair",
AnimalCategory: category,
}
fmt.Printf("The animal: %s\n", animal)
在这里fmt.Printf函数相当于调用animal的String⽅法。在java中只有父类才会做到方法的覆盖,但是在Go中,嵌⼊字段的⽅法集合会被⽆条件地合并进被嵌⼊类型的⽅法集合中。
如果为Animal类型编写⼀个String⽅法,那么会将嵌⼊字段AnimalCategory的String⽅法被“屏蔽”了,从而调用Animal的String方法。
只 要名称相同,⽆论这两个⽅法的签名是否⼀致,被嵌⼊类型的⽅法都会“屏蔽”掉嵌⼊字段的同名⽅法。也就是说不管返回值类型或者方法参数如何,只要名称相同就会屏蔽掉嵌⼊字段的同名⽅法。
指针方法
上面我们的例子其实都是值方法,下面我们举一个指针方法的例子:
func main() {
cat := New("little pig", "American Shorthair", "cat")
cat.SetName("monster") // (&cat).SetName("monster")
fmt.Printf("The cat: %s\n", cat)
cat.SetNameOfCopy("little pig")
fmt.Printf("The cat: %s\n", cat)
}
type Cat struct {
name string // 名字。
scientificName string // 学名。
category string // 动物学基本分类。
}
//构造一个cat实例
func New(name, scientificName, category string) Cat {
return Cat{
name: name,
scientificName: scientificName,
category: category,
}
}
//传指针设置cat名字
func (cat *Cat) SetName(name string) {
cat.name = name
}
//传入值
func (cat Cat) SetNameOfCopy(name string) {
cat.name = name
}
func (cat Cat) String() string {
return fmt.Sprintf("%s (category: %s, name: %q)",
cat.scientificName, cat.category, cat.name)
}
在这个例子中,我们为Cat设置了两个方法,SetName是传指针的方法,SetNameOfCopy是传值的方法。
⽅法SetName的接收者类型是Cat。Cat左边再加个代表的就是Cat类型的指针类型。
我们通过运行上面的例子可以得出,值⽅法的接收者是该⽅法所属的那个类型值的⼀个副本。⽽指针⽅法的接收者,是该⽅法所属的那个基本类型值的指针值的⼀个副本。我们在这样的⽅法内对该副本指向的值进⾏ 修改,却⼀定会体现在原值上。
接口类型
声明
type Pet interface {
SetName(name string)
Name() string
Category() string
}
当数据类型中的方法实现了接口中的所有方法,那么该数据类型就是该接口的实现类型,如下:
type Pet interface {
Name() string
Category() string
SetName(name string)
}
type Dog struct {
name string // 名字。
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog Dog) Name() string {
return dog.name
}
func (dog Dog) Category() string {
return "dog"
}
在这里Dog类型实现了Pet接口。
接口变量赋值
接口变量赋值也涉及了值传递和指针传递的概念。如下:
// 示例1
dog := Dog{"little pig"}
fmt.Printf("The dog's name is %q.\n", dog.Name())
var pet Pet = dog
dog.SetName("monster")
fmt.Printf("The dog's name is %q.\n", dog.Name())
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
fmt.Println()
// 示例2。
dog = Dog{"little pig"}
fmt.Printf("The dog's name is %q.\n", dog.Name())
pet = &dog
dog.SetName("monster")
fmt.Printf("The dog's name is %q.\n", dog.Name())
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
返回
The dog's name is "little pig".
The dog's name is "monster".
This pet is a dog, the name is "little pig".
The dog's name is "little pig".
The dog's name is "monster".
This pet is a dog, the name is "monster".
在示例1中,赋给pet变量的实际上是dog的一个副本,所以当dog设置了name的时候pet的name并没发生改变。
在实例2中,赋给pet变量的是一个指针的副本,所以pet和dog一样发生了编发。
接口之间的组合
可以通过接口间的嵌入实现接口的组合。接⼝类型间的嵌⼊不会涉及⽅法间的“屏蔽”。只要组合的接⼝之间有同名的⽅法就会产⽣冲突,从⽽⽆ 法通过编译,即使同名⽅法的签名彼此不同也会是如此。
type Animal interface {
// ScientificName 用于获取动物的学名。
ScientificName() string
// Category 用于获取动物的基本分类。
Category() string
}
type Named interface {
// Name 用于获取名字。
Name() string
}
type Pet interface {
Animal
Named
}
指针
哪些值是不可寻址的
- 不可变的变量
如果一个变量是不可变的,那么基于它的索引或切⽚的结果值都是不可寻址的,因为即使拿到了这种值的内存地址也改变不了什么。
如:
const num = 123
//_ = &num // 常量不可寻址。
//_ = &(123) // 基本类型值的字面量不可寻址。
var str = "abc"
_ = str
//_ = &(str[0]) // 对字符串变量的索引结果值不可寻址。
//_ = &(str[0:2]) // 对字符串变量的切片结果值不可寻址。
str2 := str[0]
_ = &str2 // 但这样的寻址就是合法的。
- 临时结果
在我们把临时结果值赋给任何变量或常量之前,即使能拿到它的内存地址也是没有任何意义的。所以也是不可寻址的。
我们可以把各种对值字⾯量施加的表达式的求值结果都看做是 临时结果。
如:
* ⽤于获得某个元素的索引表达式。
* ⽤于获得某个切⽚(⽚段)的切⽚表达式。
* ⽤于访问某个字段的选择表达式。
* ⽤于调⽤某个函数或⽅法的调⽤表达式。
* ⽤于转换值的类型的类型转换表达式。
* ⽤于判断值的类型的类型断⾔表达式。
* 向通道发送元素值或从通道那⾥接收元素值的接收表达式。
⼀个需要特别注意的例外是,对切⽚字⾯量的索引结果值是可寻址的。因为不论怎样,每个切⽚值都会持有⼀个底层数组,⽽ 这个底层数组中的每个元素值都是有⼀个确切的内存地址的。
//_ = &(123 + 456) // 算术操作的结果值不可寻址。
//_ = &([3]int{1, 2, 3}[0]) // 对数组字面量的索引结果值不可寻址。
//_ = &([3]int{1, 2, 3}[0:2]) // 对数组字面量的切片结果值不可寻址。
_ = &([]int{1, 2, 3}[0]) // 对切片字面量的索引结果值却是可寻址的。
//_ = &([]int{1, 2, 3}[0:2]) // 对切片字面量的切片结果值不可寻址。
//_ = &(map[int]string{1: "a"}[0]) // 对字典字面量的索引结果值不可寻址。
- 不安全
函数在Go语⾔中是⼀等公⺠,所以我们可以把代表函数或⽅法的字⾯量或标识符赋给某个变量、传给某个函数或者从某个函数传出。
但是,这样的函数和⽅法都是不可寻址的。⼀个原因是函数就是代码,是不可变的。另⼀个原因是,拿到指向⼀段代码的指针是不安全的。
此外,对函数或⽅法的调⽤结果值也是不可寻址的,这是因为它们都属 于临时结果。
如:
//_ = &(func(x, y int) int {
// return x + y
//}) // 字面量代表的函数不可寻址。
//_ = &(fmt.Sprintf) // 标识符代表的函数不可寻址。
//_ = &(fmt.Sprintln("abc")) // 对函数的调用结果值不可寻址。
goroutine协程
在Go语言中,协程是由go函数进行触发的,当程序执⾏到⼀条go语句的时候,Go语⾔ 的运⾏时系统,会先试图从某个存放空闲的G的队列中获取⼀个G(也就是goroutine),它只有在找不到空闲G的情况下才会 去创建⼀个新的G。
故已存在的goroutine总是会被优先复⽤。
在拿到了⼀个空闲的G之后,Go语⾔运⾏时系统会⽤这个G去包装当前的那个go函数(或者说该函数中的那些代码),然后再 把这个G追加到某个存放可运⾏的G的队列中。
在Go语⾔并不会去保证这些goroutine会以怎样的顺序运⾏。所以哪个goroutine先执⾏完、哪个goroutine后执⾏完往往是不可预知的,除⾮我们使⽤了某种Go语⾔提供的⽅式进⾏了⼈为 ⼲预。
所以,怎样让我们启⽤的多个goroutine按照既定的顺序运⾏?
多个goroutine按照既定的顺序运⾏
下面我们先看个例子:
func main() {
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i)
}()
}
}
在下面的代码中,由于Go语言并不会按顺序去执行调度,所以没法知道fmt.Println(i)会在什么时候被打印,也不知道fmt.Println(i)打印的时候i是多少,也有可能main方法执行完了,但是没有一条输出。
所以我们需要进行如下改造:
func main() {
var count uint32
trigger := func(i uint32, fn func()) {
for {
if n := atomic.LoadUint32(&count); n == i {
fn()
atomic.AddUint32(&count, 1)
break
}
time.Sleep(time.Nanosecond)
}
}
for i := uint32(0); i < 10; i++ {
go func(i uint32) {
fn := func() {
fmt.Println(i)
}
trigger(i, fn)
}(i)
}
trigger(10, func() {})
}
我们在for循环中声明了一个fn函数,fn函数里面只是简单的执行打印i的值,然后传入到trigger中。
trigger函数会不断地获取⼀个名叫count的变量的值,并判断该值是否与参数i的值相同。如果相同,那么就⽴即调⽤fn代 表的函数,然后把count变量的值加1,最后显式地退出当前的循环。否则,我们就先让当前的goroutine“睡眠”⼀个纳秒再进 ⼊下⼀个迭代。
因为会有多个线程操作trigger函数,所以使用的count变量是通过原子操作来进行获取值和加一操作。
所以过函数实际执行顺序会根据count的值依次执行,这里实现了一种自旋,未满足条件的时候会不断地进行检查。
最后防止主协程在其他协程没有运行完的时候就关闭,加上一个trigger(10, func() {})
代码。
Java程序员学习Go指南(二)的更多相关文章
- 【Python】Java程序员学习Python(二)— 开发环境搭建
巧妇难为无米之炊,我最爱的还是鸡蛋羹,因为我和鸡蛋羹有段不能说的秘密. 不管学啥,都要有环境,对于程序员来说搭建个开发环境应该不是什么难题.按顺序一步步来就可以,我也只是记录我的安装过程,你也可以滴. ...
- Java程序员学习Go指南(终)
我的博客:https://www.luozhiyun.com/archives/215 context.Context类型 Context类型可以提供一类代表上下文的值.此类值是并发安全的,也就是说它 ...
- Java程序员学习Go指南(三)
转载:https://www.luozhiyun.com/archives/213 人是否会进步以及进步得有多快,依赖的恰恰就是对自我的否定,这包括否定的深刻与否,以及否定自我的频率如何.这其实就是& ...
- Java程序员学习之路
1. Java语言基础 谈到Java语 言基础学习的书籍,大家肯定会推荐Bruce Eckel的<Thinking in Java>.它是一本写的相当深刻的技术书籍,Java语言基础部分基 ...
- 聊聊阿里社招面试,谈谈“野生”Java程序员学习的道路
引言 很尴尬的是,这个类型的文章其实之前笔者就写过,原文章里,笔者自称LZ(也就是楼主,有人说是老子的简写,笔者只想说,这位同学你站出来,保证不打死你,-_-),原文章名称叫做<回答阿里社招面试 ...
- 【Python】Java程序员学习Python(五)— 函数的定义和使用
不想做一个待宰的羔羊!!!!要自己变得强大.... 函数的定义和使用放在最前边还是有原因的,现在语言趋于通用,基本类型基本都是那些,重点还是学习对象的使用方法,而最根本的还是方法的使用,因此优先介绍, ...
- 如何准备阿里社招面试,顺谈 Java 程序员学习中各阶段的建议
引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内容 ...
- 写给自己的Java程序员学习路线图
恩,做开发的工作已经三年多了,说起来实在是惭愧,自己的知识树还像一棵小草一样,工作中使用到了许多的知识和技术,不过系统性不够.根基不牢.并且不够深入!当然,慢慢的我也更加的清楚,我需要学习一些什么样的 ...
- 写给自己的Java程序员学习路线图_转载
如下是我做开发这三年经常使用一些技术和工具,当然这些技术也都是需要加强的(有些是我一直使用的,不过不深入,有些内部的原理等等不是很清楚) 前端部分: 1)HTML:网页的核心语言,构成网页的基础 2) ...
随机推荐
- ArcGIS-PictureMarkerSymbol-向地图添加图片标记
1.基于4.13 版本 <link rel="stylesheet" href="https://js.arcgis.com/4.13/esri/themes/li ...
- ASP.NET WebForm Ajax请求Handler的经验
ajax代码 $.ajax({ type: "GET", url: "/AjaxHandler/GetPluginCode.ashx", data: " ...
- 机器学习 - Python 02
好了,咱们接着上一节的内容,继续学习机器学习中的Python语法部分.这一节算是Python语法的最后一节了.也就是说如果真的看懂了这两节的内容,理论上说就机器学习的领域或者方向,语言已经不是问题了. ...
- resin部署安装
Resin是CAUCHO公司的产品,是一个非常流行的application server,对servlet和JSP提供了良好的支持,性能也比较优良,resin自身也是采用JAVA语法开发,功能近似于t ...
- Kubernetes基本概念和术语之《Pod》
Pod是Kubernetes的最重要也最基本的概念.我们看到每个Pod都有一个特殊的被称为“根容器”的Pause容器对应的镜像属于Kubernetes平台的一部分.除了Pause容器,每个Pod还包含 ...
- $NOIp$提高组历年题目复习
写在前面 一个简略的\(NOIp\)题高组历年题目复习记录.大部分都有单独写题解,但懒得放\(link\)了\(QwQ\).对于想的时候兜了圈子的题打上\(*\). \(NOIp2018\ [4/6] ...
- Ceph14.2.5 RBD块存储的实战配置和详细介绍,不看后悔! -- <3>
Ceph RBD介绍与使用 RBD介绍 RBD即RADOS Block Device的简称,RBD块存储是最稳定且最常用的存储类型.RBD块设备类似磁盘可以被挂载. RBD块设备具有快照.多副本.克隆 ...
- 反射 API基本代码测试
ReflectBean.java package com.xiaojian.basics.reflect; /** * 使用反射的测试类 */ public class ReflectBean { / ...
- JavaScript数据类型 - Symbol
ES5:对象的属性名只能是字符串,当给对象添加新属性时,很容易造成属性名冲突,从而覆盖了原有的属性. ES6:所以ES6中引入了symbol数据类型,他表示独一无二的值,避免了属性名的冲突,此时对象的 ...
- 快速开发架构Spring Boot 从入门到精通 附源码
导读 篇幅较长,干货十足,阅读需花费点时间.珍惜原创,转载请注明出处,谢谢! Spring Boot基础 Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计 ...