十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
go语言math包里面定义了min/max函数,但是是float64类型的,而并没有整数类型的min/max。
成都创新互联公司服务项目包括昆都仑网站建设、昆都仑网站制作、昆都仑网页制作以及昆都仑网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,昆都仑网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到昆都仑省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
因为go没有重载,这是个大坑。所以math库里min/max函数都只能定义一个,所以官方选择了比较难实现的float64类型。而简单的整形就需要让程序员自己实现了
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
自定义类型
在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:
通过Type关键字的定义,MyInt就是一种新的类型,它具有int的特性。
类型别名
类型别名是Go1.9版本添加的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type
我们之前见过的rune和byte就是类型别名,他们的定义如下:
类型定义和类型别名的区别
类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。
Go语言中通过struct来实现面向对象。
结构体的定义
使用type和struct关键字来定义结构体,具体代码格式如下:
其中:
举个例子,我们定义一个Person(人)结构体,代码如下:
同样类型的字段也可以写在一行,
这样我们就拥有了一个person的自定义类型,它有name、city、age三个字段,分别表示姓名、城市和年龄。这样我们使用这个person结构体就能够很方便的在程序中表示和存储人信息了。
语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型
结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
基本实例化
举个例子:
我们通过.来访问结构体的字段(成员变量),例如p1.name和p1.age等。
匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
创建指针类型结构体
我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:
从打印的结果中我们可以看出p2是一个结构体指针。
需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。
取结构体的地址实例化
使用对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。
p3.name = "七米"其实在底层是(*p3).name = "七米",这是Go语言帮我们实现的语法糖。
结构体初始化
没有初始化的结构体,其成员变量都是对应其类型的零值。
使用键值对初始化
使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。
也可以对结构体指针进行键值对初始化,例如:
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。
使用值的列表初始化
初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
使用这种格式初始化时,需要注意:
结构体内存布局
结构体占用一块连续的内存。
输出:
【进阶知识点】关于Go语言中的内存对齐推荐阅读:在 Go 中恰到好处的内存对齐
面试题
请问下面代码的执行结果是什么?
构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
调用构造函数
方法和接收者
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
方法的定义格式如下:
其中,
举个例子:
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
指针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
调用该方法:
值类型的接收者
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
什么时候应该使用指针类型接收者
任意类型添加方法
在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。
注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。
结构体的匿名字段
匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针。
嵌套匿名结构体
当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
嵌套结构体的字段名冲突
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
结构体的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
结构体与JSON序列化
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。
结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
例如我们为Student结构体的每个字段定义json序列化时使用的Tag:
不是为了与众不同。而是为了更加清晰易懂。
Rob Pike 曾经在 Go 官方博客解释过这个问题(原文地址:),简略翻译如下(水平有限翻译的不对的地方见谅):
引言
Go语言新人常常会很疑惑为什么这门语言的声明语法(declaration syntax)会和传统的C家族语言不同。在这篇博文里,我们会进行一个比较,并做出解答。
C 的语法
首先,先看看 C 的语法。C 采用了一种聪明而不同寻常的声明语法。声明变量时,只需写出一个带有目标变量名的表达式,然后在表达式里指明该表达式本身的类型即可。比如:
int x;
上面的代码声明了 x 变量,并且其类型为 int——即,表达式 x 为 int 类型。一般而言,为了指明新变量的类型,我们得写出一个表达式,其中含有我们要声明的变量,这个表达式运算的结果值属于某种基本类型,我们把这种基本类型写到表达式的左边。所以,下述声明:
int *p;
int a[3];
指明了 p 是一个int类型的指针,因为 *p 的类型为 int。而 a 是一个 int 数组,因为 a[3] 的类型为 int(别管这里出现的索引值,它只是用于指明数组的长度)。
我们接下来看看函数声明的情况。C 的函数声明中关于参数的类型是写在括号外的,像下面这样:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
如前所述,我们可以看到 main 之所以是函数,是因为表达式 main(argc, argv) 返回 int。在现代记法中我们是这么写的:
int main(int argc, char *argv[]) { /* ... */ }
尽管看起来有些不同,但是基本的结构是一样的。
总的来看,当类型比较简单时,C的语法显得很聪明。但是遗憾的是一旦类型开始复杂,C的这套语法很快就能让人迷糊了。著名的例子如函数指针,我们得按下面这样来写:
int (*fp)(int a, int b);
在这儿,fp 之所以是一个指针是因为如果你写出 (*fp)(a, b) 这样的表达式将会调用一个函数,其返回 int 类型的值。如果当 fp 的某个参数本身又是一个函数,情况会怎样呢?
int (*fp)(int (*ff)(int x, int y), int b)
这读起来可就点难了。
当然了,我们声明函数时是可以不写明参数的名称的,因此 main 函数可以声明为:
int main(int, char *[])
回想一下,之前 argv 是下面这样的
char *argv[]
你有没有发现你是从声明的「中间」去掉变量名而后构造出其变量类型的?尽管这不是很明显,但你声明某个 char *[] 类型的变量的时候,竟然需要把名字插入到变量类型的中间。
我们再来看看,如果我们不命名 fp 的参数会怎样:
int (*fp)(int (*)(int, int), int)
这东西难懂的地方可不仅仅是要记得参数名原本是放这中间的
int (*)(int, int)
它更让人混淆的地方还在于甚至可能都搞不清这竟然是个函数指针声明。我们接着看看,如果返回值也是个函数指针类型又会怎么样
int (*(*fp)(int (*)(int, int), int))(int, int)
这已经很难看出是关于 fp 的声明了。
你自己还可以构建出比这更复杂的例子,但这已经足以解释 C 的声明语法引入的某些复杂性了。
还有一点需要指出,由于类型语法和声明语法是一样的,要解析中间带有类型的表达式可能会有些难度。这也就是为什么,C 在做类型转换的时候总是要把类型用括号括起来的原因,像这样
(int)M_PI
Go 的语法
非C家族的语言通常在声明时使用一种不同的类型语法。一般是名字先出现,然后常常跟着一个冒号。按照这样来写,我们上面所举的例子就会变成下面这样:
x: int
p: pointer to int
a: array[3] of int
这样的声明即便有些冗长,当至少是清晰的——你只需从左向右读就行。Go 语言所采用的方案就是以此为基础的,但为了追求简洁性,Go 语言丢掉了冒号并去掉了部分关键词,成了下面这样:
x int
p *int
a [3]int
在 [3]int 和表达式中 a 的用法没有直接的对应关系(我们在下一节会回过头来探讨指针的问题)。至此,你获得了代码清晰性方面的提升,但付出的代价是语法上需要区别对待。
下面我们来考虑函数的问题。虽然在 Go 语言里,main 函数实际上没有参数,但是我们先誊抄一下之前的 main 函数的声明:
func main(argc int, argv *[]byte) int
粗略一看和 C 没什么不同,不过自左向右读的话还不错。
main 函数接受一个 int 和一个指针并返回一个 int。
如果此时把参数名去掉,它还是很清楚——因为参数名总在类型的前面,所以不会引起混淆。
func main(int, *[]byte) int
这种自左向右风格的声明的一个价值在于,当类型变得更复杂时,它依然相对简单。下面是一个函数变量的声明(相当于 C 语言里的函数指针)
f func(func(int,int) int, int) int
或者当它返回一个函数时:
f func(func(int,int) int, int) func(int, int) int
上面的声明读起来还是很清晰,自左向右,而且究竟哪一个变量名是当前被声明的也容易看懂——因为变量名永远在首位。
类型语法和表达式语法带来的差别使得在 Go 语言里调用闭包也变得更简单:
sum := func(a, b int) int { return a+b } (3, 4)
指针
指针有些例外。注意在数组 (array )和切片 (slice) 中,Go 的类型语法把方括号放在了类型的左边,但是在表达式语法中却又把方括号放到了右边:
var a []int
x = a[1]
类似的,Go 的指针沿用了 C 的 * 记法,但是我们写的时候也是声明时 * 在变量名右边,但在表达式中却又得把 * 放到左左边:
var p *int
x = *p
不能写成下面这样
var p *int
x = p*
因为后缀的 * 可能会和乘法运算混淆,也许我们可以改用 Pascal 的 ^ 标记,像这样
var p ^int
x = p^
我们也许还真的应该把 * 像上面这样改成 ^ (当然这么一改 xor 运算的符号也得改),因为在类型和表达式中的 * 前缀确实把好些事儿都搞得有点复杂,举个例子来说,虽然我们可以像下面这样写
[]int("hi")
但在转换时,如果类型是以 * 开头的,就得加上括号:
(*int)(nil)
如果有一天我们愿意放弃用 * 作为指针语法的话,那么上面的括号就可以省略了。
可见,Go 的指针语法是和 C 相似的。但这种相似也意味着我们无法彻底避免在文法中有时为了避免类型和表达式的歧义需要补充括号的情况。
总而言之,尽管存在不足,但我们相信 Go 的类型语法要比 C 的容易懂。特别是当类型比较复杂时。
Go语言也称 Golang,兼具效率、性能、安全、健壮等特性。这套Go语言教程(Golang教程)通俗易懂,深入浅出,既适合没有基础的读者快速入门,也适合工作多年的程序员查阅知识点。
Go 语言
这套教程在讲解一些知识点时,将 Go 语言和其他多种语言进行对比,让掌握其它编程语言的读者能迅速理解 Go 语言的特性。Go语言从底层原生支持并发,无须第三方库、开发者的编程技巧和开发经验就可以轻松搞定。
Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。
Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。
Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。
因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。
此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。Go 是编译型语言,Go 使用编译器来编译代码。编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。要创建并运行 Go 程序,程序员必须执行如下步骤。
使用文本编辑器创建 Go 程序;
保存文件;编译程序;运行编译得到的可执行文件。
这不同于 Python、Ruby 和 JavaScript 等语言,它们不包含编译步骤。Go 自带了编译器,因此无须单独安装编译器。
链乔教育在线旗下学硕创新区块链技术工作站是中国教育部学校规划建设发展中心开展的“智慧学习工场2020-学硕创新工作站 ”唯一获准的“区块链技术专业”试点工作站。专业站立足为学生提供多样化成长路径,推进专业学位研究生产学研结合培养模式改革,构建应用型、复合型人才培养体系。
Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成为现实。Go 团队实施了一个看起来比较稳定的设计草案,并且正以源到源翻译器原型的形式获得关注。本文讲述的是泛型的最新设计,以及如何自己尝试泛型。
例子
FIFO Stack
假设你要创建一个先进先出堆栈。没有泛型,你可能会这样实现:
type Stack []interface{}func (s Stack) Peek() interface{} {
return s[len(s)-1]
}
func (s *Stack) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack) Push(value interface{}) {
*s =
append(*s, value)
}
但是,这里存在一个问题:每当你 Peek 项时,都必须使用类型断言将其从 interface{} 转换为你需要的类型。如果你的堆栈是 *MyObject 的堆栈,则意味着很多 s.Peek().(*MyObject)这样的代码。这不仅让人眼花缭乱,而且还可能引发错误。比如忘记 * 怎么办?或者如果您输入错误的类型怎么办?s.Push(MyObject{})` 可以顺利编译,而且你可能不会发现到自己的错误,直到它影响到你的整个服务为止。
通常,使用 interface{} 是相对危险的。使用更多受限制的类型总是更安全,因为可以在编译时而不是运行时发现问题。
泛型通过允许类型具有类型参数来解决此问题:
type Stack(type T) []Tfunc (s Stack(T)) Peek() T {
return s[len(s)-1]
}
func (s *Stack(T)) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack(T)) Push(value T) {
*s =
append(*s, value)
}
这会向 Stack 添加一个类型参数,从而完全不需要 interface{}。现在,当你使用 Peek() 时,返回的值已经是原始类型,并且没有机会返回错误的值类型。这种方式更安全,更容易使用。(译注:就是看起来更丑陋,^-^)
此外,泛型代码通常更易于编译器优化,从而获得更好的性能(以二进制大小为代价)。如果我们对上面的非泛型代码和泛型代码进行基准测试,我们可以看到区别:
type MyObject struct {
X
int
}
var sink MyObjectfunc BenchmarkGo1(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek().(MyObject)
}
}
func BenchmarkGo2(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek()
}
}
结果:
BenchmarkGo1BenchmarkGo1-16 12837528 87.0 ns/op 48 B/op 2 allocs/opBenchmarkGo2BenchmarkGo2-16 28406479 41.9 ns/op 24 B/op 2 allocs/op
在这种情况下,我们分配更少的内存,同时泛型的速度是非泛型的两倍。
合约(Contracts)
上面的堆栈示例适用于任何类型。但是,在许多情况下,你需要编写仅适用于具有某些特征的类型的代码。例如,你可能希望堆栈要求类型实现 String() 函数
当需要定义一个整形变量a 心里是这样想的:我现在需要一个整形的变量,我要定义它,于是我先写一个int,再思考它的名字 a ,于是就这么写出来了int a 。而不是我写了个变量a,我得给它区分个类型int。2. 在调用一个方法的时候,func(abdfsasdffdg int, bagressdgf string, csdgesredg bool) 那个go函数看的很乱,程序员其实根本就不怎么看参数名字是什么,而只是看需要传入什么类型,注意力只在于int,string,bool这三个,如果如上那么写,反而影响了视线,乱系八糟的。func(int adsfasdfsdaf, string asdfasfasf, bool gwegasgs),这么写我只注意类型,就不受名称影响了。3. IDE自动提示 go本身就是为快而生,定义一个结构变量Rectangle rectangle,当键盘敲下r时候,IDE会自动给出rectangle,直接回车就出来了,反过来就的自己一个字母一个字母敲上去,蛋疼啊4. 至于go给出的解释,当遇到复杂函数时…… 一个项目中能写几个复杂函数,为了去解决这么一点小问题就把优势给牺牲了.