不能将brassplus类型的值分配到brass类的实体_GO的类型系统和类型的方法
GO的类型系统和类型的方法
[TOC]
- 1. 类型及其作用
- 2. 用户自定类型(两种实现)
- 3. 类型的方法
- 4. 类型的本质
- 5. 嵌入类型
0 前言
如果说goroutine
和channel
是撑起GO语言实现高并发的基石,那么GO的类型系统
和接口
就是整个GO语言实现的基础。(接口篇见[TODO])
本篇属于入门级别的文章,主要讲解GO中的类型系统和类型的方法。
从GO类型的作用出发,描述GO中创建自定义类型的两种方法,然后详细的讲解了GO中类型的方法,包括GO接收者类型(值类型/引用类型)的选取,使用方式, 和“坍塌原则”,然后回归GO类型的本质,简略的讲解GO的三种类型:内置类型/引用类型/结构类型,最后讲解了一下嵌入类型的作用及实现方式,以及实现细节。
错误难免,还望指正。
1. 类型及其作用
任何语言都有自己的类型系统,任何“值”也都唯一的属于某种类型。
一个“值”只有有自己的类型,才能被编译器所识别,从而正确的为其分配内存和使用。
类型为编译器提供了两种信息:
- 类型的规模,即需要为该种类型的“值”分配多少内存;
- 内存的表示,即这段内存表示的是什么。
举例说明:比如GO中的int32,规模为4Byte,表示的是一个整数值;float64规模为8Byte,表示的是一个浮点数; *int16规模为4Byte或8Byte,即实际机器的位数,表示的是一个指向int16类型的指针(引用)类型。
2. 用户定义类型
除了内置类型,大多数语言也都支持用户自定义类型,GO也不例外。
(1)使用type TYPENAME struct{}
定义类型
GO中可以使用type TYPENAME struct{}
来定义用户自己的类型,且该类型可以如内置类型一样的进行使用,和被编译器识别。
(2)基于已有类型创建
以time
包的Duation
为例,源码为type Duration int64
,通过这样的显式声明,我们就创建了一种新的类型Duration
, 其底层数据结构是int64
。
通过这样的方式进行类型创建是很常见的。
注意:虽然Duration
和int64
的底层数据结构都是int64
,但是这两种类型是完全不一样的,不能够相互比较,更不能相互替换。
举例说明:var d Duration = int64(1000)
是通不过编译的。
3. 类型的方法
C++或者Java中我们经常定义一个类,类中拥有属性和方法。GO不是面向对象的编程语言,不支持这样的类定义。但是GO同样支持为类型添加方法。
GO为类型添加方法是通过接收者
+函数
实现的。其实就相当于将接收者
作为一个隐式的参数传入到了函数当中,从而使得函数能够对该接收者
进行操作。 这里比较简单,不举例了。
GO中的函数传参都是值传递
,只不过当参数是引用的时候,传入的是实参的指针(实际是实参指针的一个副本,即本质仍未值传递),从而能够对实参进行修改。
(一句题外话:说到这里,突然好怀念C++中 const+引用 传参的方式,即避免了大量的拷贝,又避免了对实参的修改。这个特性对性能提升还是很大的,不知道以后GO会不会也加入这个特性。)
在这里对值接收者
和指针接收者
进行一下区分。
1. 当接收者
定义为值类型
的时候:
- 值类型的变量可以调用该方法,但是不能修改
接收者
- 引用类型的变量可以调用该方法,但是也不能修改
接收者
说明:
- 第一点很好理解。
- 对于第二点,当方法定义在
值类型
上,而一个引用类型的变量去调用该方法时,实际上编译器会在背后做个变换,取该引用所指向的实际变量(即*p)
传入方法(函数)当中进行运算,即对于函数来讲,所看到的仍然是值类型,从而不能够对接收者进行修改。
示例程序:
package mainimport "fmt"type Stu struct{name string
}func (p Stu) Name(){fmt.Println(p.name)
}func (p Stu) ChangeName(str string){p.name = str
}func main(){// p为值类型p := Stu{"Lito"}p.ChangeName("Neo") // 值类型不能修改接收者p.Name() // 输出仍然为"Lito"// y为引用类型y := &Stu{"Lito"}y.ChangeName("Neo") // 可以调用定义在值类型上的方法,即运行(*y).ChangeName("Neo"), 但是由于是值传递进去的,因此不能修改接收者y.Name() // 输出仍为"Lito"
}
2. 当接收者
定义为引用类型
的时候:
- 值类型的变量可以调用该方法,而且可以修改
接收者
- 引用类型的变量可以调用该方法,而且可以修改
接收者
说明:
- 第二点很好理解。
- 对于第一点,当方法定义在
引用类型
上,而一个值类型的变量去调用该方法时,实际上编译器会在背后做个变换,取该值类型的引用(即&p)
传入方法(函数)当中进行运算,即对于函数来讲,所看到的将是引用类型,从而能够对接收者进行修改。
示例程序:
package mainimport "fmt"type Stu struct{name string
}func (p *Stu) Name(){fmt.Println(p.name)
}func (p *Stu) ChangeName(str string){p.name = str
}func main() {// p为值类型p := Stu{"Lito"} p.ChangeName("Neo") // 可以调用定义在引用类型上的方法,而且可以修改接收者;即运行(&p).ChangeName("Neo")p.Name() // 输出”Neo“// y为引用类型y := &Stu{"Lito"}y.ChangeName("Neo") // 原生的可以调用定义在引用类型上的方法,而且可以修改接收者y.Name() // 输出”Neo“
}
3. 接收者使用小结
- 无论方法是定义在值类型上还是引用类型上,都可以被值类型的变量或者引用类型的变量访问刀。这背后是GO的语法糖在起作用,即GO会隐式的将变量实际类型转化成 方法需要的类型;
- 定义在引用类型上的方法,才能够对接收者进行修改,而与实际使用的变量类型无关(这是由于上面1中所说的GO的语法糖的隐式类型转换的缘故)。
实际上,当一个方法定义在值类型上时,GO编译器会隐式为其生成一个定义在引用类型上的方法(但是仍然不能修改接收调用者)。
而当方法定义在引用类型上时,却不会自动为值类型添加该方法(因为定义在引用类型上的方法往往会修改接收者,此时把引用类型上的方法定义在值类型上就不合适了)。
我们可以将该原则称之为*”类型方法的坍塌原则“**(自创的,意会),
即值类型上的方法会坍塌到引用类型上。
这一点有什么用呢?在实现接口的时候有用。
值类型和引用类型仍然是两种不同的类型,GO对不同类型的区分还是很严格的。
举例说明:(此处仅举简单的例子说明,详细内容会在另一篇关于接口的总结中说明)
package mainimport "fmt"// 定义Gopher接口,该接口有两个方法
type Gopher interface{Code()Debug()
}// 定义一个新的Worker类型
type Worker struct{Name string
}// > 为值类型Worker绑定Code()方法
func (w Worker) Code(){fmt.Println("Coding is fun.")
}// > 为引用类型Worker绑定Debug()方法
func (w *Worker) Debug(){fmt.Println("Debug is panic.")
}func main() {var p Gopher = Worker{"Lito"} // 错误:因为值类型的Worker没有实现Debug()方法var y Gopher = &Worker{"Lito"} // 正确,值类型上的方法坍塌到了引用类型上,相当于引用类型也实现了Code()方法
}
另外还有一点需要尤其注意,在定义方法的时候选择使用值接收者
还是引用接收者
, 不是由该方法是否修改了调用者(即接收者)来决定,而是由该类型的 本质
来决定
什么是类型的本质?我们在下一小节来展开。
4. 类型的本质
广义上来划分,可以将GO中的类型划分为原始类型
和非原始类型
两类。 --这就是类型的本质
根据类型的特点,又可以划分为:内置类型,引用类型,结构类型
三类。
(1). 内置类型
数值类型,字符串,布尔值
这三大类型是GO语言预先定义并提供的,属于GO的内置类型。
这些类型本质上是原始的类型
,当对这些类型的值做修改的时候,会生成一个新的值。因此,当把这些类型的值传递给方法或者函数时,应该传递一个对应值的副本。
(2). 引用类型
切片,映射,通道,函数,接口
都是GO中的引用类型。严格来说,string
也是引用类型。
对于引用类型来讲,我们创建的引用类型的变量是一个标头值(header)
,
该标头值包含两部分:
- 指向底层数据结构的指针
- 一个用于管理底层数据结构的字段
以最简单的切片slice为例:
type slice struct {array unsafe.Pointer // 指向数组的指针// 下面为进行底层数据管理的字段len int // 切片中元素的数量cap int // array 数组的总容量
}
我们复制(浅拷贝,即 a=b 这种拷贝)一个引用类型的变量时(比如函数传参),实际上复制的是这个标头值。
而这个标头值指向的底层数据结构是同一块,从而实现了底层数据结构的共享。
(3). 结构类型
结构类型可以用来描述一组数据值,这组值的本质即可以是原始的,也可以是非原始的。如 果决定在某些东西需要删除或者添加某个结构类型的值时该结构类型的值不应该被更改,那么需 要遵守之前提到的内置类型和引用类型的规范。
这里说的比较混沌,感兴趣的可以参考《GO语言实战》 5.3节相关的内容。
5. 嵌入类型
嵌入类型不属于GO的三大类型之一,嵌入类型的作用是通过类型嵌入(组合)来扩充父类型
(注:说父类型是不准确的,GO中没有父类子类的概念。 这里的意思是说通过嵌入可以实现C++等语言中继承
所实现的作用)
- 内部类型的标识符(方法)会被提升到外部类型上,成为外部类型的一部分;
- 外部类型可以声明与内部类型同名的标识符(方法)来
覆盖
内部类型的方法;
注意:嵌入的意思是直接将类型嵌入,而不是在结构体内定义一个某个类型的字段。
举例说明:
package mainimport "fmt"type user struct{name stringemail string
}func (u *user) notify(){fmt.Println(u.name, u.email)
}type userinfo struct{address string
}func (i *userinfo) Show(){fmt.Println(i.address)
}type admin struct{user // user作为类型而嵌入admin类型,admin类型拥有且可以直接访问user的方法notify()info userinfo // info和level一样,是作为admin的一个字段,而非userinfo类型的嵌入,因此admin不拥有info的Show()方法level string
}func main() {ad := admin{user: user{"lito", "12@gmail"},level: "top",info: userinfo{address: "shandong"},}ad.name = "neo" // √ 嵌入类型的属性提升ad.user.name = "lito" // √ad.user.notify() // √ad.notify() // √ 嵌入类型的方法提升ad.info.Show() // √// ad.Show() // × userInfo不是嵌入类型,其属性和方法属于info字段,而非提升到admin类型中
}
后记
本文主要阐述了以下几点:
- 为什么需要类型,即类型的作用
- 用户如何实现自定义类型
- 如何为类型定义方法
- 值接收者和引用接收者的区别,使用方法,坍塌原则
- GO的三大类型,引用类型的底层实现
- 嵌入类型的功能,实现方式
不能将brassplus类型的值分配到brass类的实体_GO的类型系统和类型的方法相关推荐
- 不能将brassplus类型的值分配到brass类的实体_Java 基础 - 类与对象
类.对象和引用的关系 类和对象的关系 类是对象的模版,对象是类的一个实例,一个类可以有很多对象 一个Java程序中类名相同的类只能有一个,也就是类型不会重名 一个对象只能根据一个类来创建 引用和类以及 ...
- 不能将X*类型的值分配到X*类型的实体问题的解决方法
不能将X类型的值分配到X类型的实体问题的解决方法 今天在学习链表的过程中,遇到了这样一个问题 1.代码如下: typedef struct {ElemType data;struct LNode *n ...
- getch()与_getch()、不能将const char*类型的值分配到const* 类型的实体
参考1:getch()与_getch() 添加预处理 项目 -> 属性 -> 配置属性 -> C/C++ -> 预处理器 -> 预处理器定义 -> 编辑中添加:_C ...
- Vs调试报错:不能将 “const char *“ 类型的值分配到 “char *“ 类型的实体
设置为否 即可成功编译 参考博文 CSDN博主「骑码学GIS」的原创文章
- 关于C#值类型,引用类型,值传递,引用传递
说到参数传递,必须得弄清值类型和引用类型: (为了容易表达,我暂且命名存放在堆中的内容为堆中对象,存放在栈上的内容为栈中对象.) 值类型存放在栈中,直接访问.如果有:int a=0;int b=a;就 ...
- 3.内存分配、逃逸分析与栈上分配、直接内存和运行时常量池、基本类型的包装类和常量池、TLAB、可达性分析算法(学习笔记)
3.JVM内存分配 3.1.内存分配概述 3.2.内存分配–Eden区域 3.3.内存分配–大对象直接进老年代 3.3.1.背景 3.3.2.解析 3.4.内存分配–长期存活的对象进去老年代 3.5. ...
- ext如何将值存入变量_变量类型之值类型与引用类型
前言 变量类型在我们日常开发中经常接触到,但是js中的变量类型与其他强类型语言不同,由于js是弱类型语言,因此他的变量拷贝在我们实际的日常开发中有很多需要注意的项.而半斤在最近的开发中遇到了很多匪夷所 ...
- C#中的变量类型(值类型、引用类型)
C#中的变量类型: 值类型:值类型直接存储的是变量的值,变量空间在栈上分配,分配速度比较快.给变量赋值时需注意变量类型的取值范围,给变量赋不合理的值会导致编译器报错.布尔类型的变量只有两种可选择的值t ...
- lua学习之类型与值篇
类型与值 lua 是动态类型的语言 在语言中没有类型定义的语法 每个值都携带有它的类型信息 8种基础类型 用 type 可以返回这个值的类型的名称 将一个变量用于不同类型,通常会导致混乱的代码 但合理 ...
最新文章
- ios开发国外视频教程(有翻译)
- 领英Linkedin信息搜集工具InSpy
- SDN,这一年都经历了什么
- hbuilderx制作简单网页_网页制作的基本步骤是怎样的?制作简单网页的具体操作有哪些呢?...
- 问了自己8个问题后,我选择了考博...
- 视频监控系统中的平台服务器,【视频监控主机 网络视频集中管理平台服务器】 - 太平洋安防网...
- leveldb——leveldb入门篇之Linux下编译配置和使用
- 一步一步安装Git控件版本工具
- 宣讲会通知|香港科技大学工学院理学硕士线上直播综合场【MSc】
- Unity游戏建议对话功能 Fungus插件。
- ios java模拟器 2017_Visual Studio 2017(Xamarin)未显示iPhone模拟器列表
- 牛客小白月赛61-C-小喵觅食
- Vue+Element表格动态列+表格分页
- HTML语言中代表网页标题的标签是,html标题标记 在html中,标题标签一共有几级?
- 腾讯START云游戏 0.290.1中文版 (跨终端游戏平台)
- 美国ESS-HIFI音频解码芯片ES9023P
- VMware Fusion安装CentOS 7教程
- VISTA注册ActiveX控件出现 0x80040200的处理方法
- 关于代码选择省份和城市?
- 华为鸿蒙os3.0评测,华为鸿蒙OS威力初显!实测体验比EMUI更好 功耗却更低 惊喜还有很多...