目录
一、项目开发
二、数据类型
一、项目开发
1.Go和Java一样,是编译型的静态语言,但Java的特点“一次编译,到处运行”,是因为Java编译成为.class文件之后,由虚拟机解释成二进制文件执行,Java程序运行在虚拟机上,虚拟机屏蔽了平台差异,同时也带来了限制,Java程序的运行要经过两个步骤,而且必须要运行在JVM上。而Go语言则是直接编译成二进制可执行文件,比较灵活。
2.Go使用Go Modules来管理代码,类似Java中的Maven。Go Modules的项目根目录下默认包含了一个go.mod文件,通过这个文件管理项目依赖,类似Maven中的pom.xml文件。下图为例calc项目结构,使用go build构建项目。
3.Go的程序入口文件为main.go,这个文件必须在main包中。Java中程序入口为类中的main方法,如果一个类中没有main方法,则该类无法独立运行。
4.Go在单元测试需要引入testing包,类似于Java中的JUnit。
5.Go使用包作为基本单位来管理代码,每个 Go 源代码文件的开头都是一个 package 声明,表示该文件中 Go 代码所属的包。包是 Go 语言里最基本的分发单位,也是工程管理中依赖关系的体现。有些类似Java中包+类的作用。
6.在引入 Go Modules 以前,Go 语言会基于 GOPATH 这个系统环境变量配置的路径为根目录(可能有多个),然后依次去对应路径下的 src 目录下根据包名查找对应的文件目录,如果目录存在,则再到该目录下的源文件中查找对应的变量、类属性、函数和成员方法。
在启用 Go Modules 之后,不再依赖 $GOPATH 定位包,而是基于 go.mod 中 module 配置值作为根路径,在该模块路径下,根据包名查找对应目录,如果存在,则继续到该目录下的源文件中查找对应变量、类属性、函数和成员方法。
7.在 Go 语言中,可以通过 import 关键字导入官方提供的包、第三方包、以及自定义的包,导入第三方包时,还需要通过 go get 指令下载才能使用,如果基于 Go Modules 管理项目的话,这个依赖关系会自动维护到 go.mod 中。
二、数据类型
1.Go和Java都是强类型语言,不同的是,Java在声明变量时有严格的格式和类型,而Go则比较随意,通过var来声明变量,也可以使用:=运算符声明并初始化变量(此时可以省略var),虽然看起来和动态语言声明变量类似,但是Go也是强类型语言,底层会自动根据赋值判断对应变量的类型,这个判断过程是在编译期做的,不是运行期,所以Go也是静态语言。
如上图所示,Go对变量的声明,类型放在变量名之后,而且可以省略,Java类型在变量名之前,不可省略。
2.所有的Go语句不需要分号作为结束符,Java语句结束必须加分号。
3.Java和Go的变量命名均遵循驼峰命名法。
4.Go支持多重赋值,例如,交换i,j变量,如果在Java中需要一个中间变量来实现,在Go中只需要i, j = j, i;这一句即可。
5.Go支持匿名变量,可使用_接受不想要的变量,会直接抛弃。
6.Go通过const关键字定义常量,常量类型可以指定也可以省略(由底层自动推导)。Java通过static final组合关键字定义常量,类型不可省略。
7.Go预定义的常量:true、false、iota。iota是Java中没有的,可以被认为是一个可被编译器修改的常量,在每一个 const 关键字出现时被重置为 0,然后在下一个 const 出现之前,每出现一次 iota,其所代表的数字会自动增 1。
8.Java中枚举的关键字是enum,在Go中不支持enum关键字,通过在 const 后跟一对圆括号定义一组常量的方式来实现枚举。
9.Go中的变量和常量不用public和private修饰,通过首字母大小来判断是否包内可见。首字母大写相当于Java中用public修饰包外也可以访问,首字母小写相当于用private修饰,只能包内访问。
10.Go支持的基本数据类型:
- 布尔类型:bool
- 整型:int8、byte、int16、int、uint、uintptr 等
- 浮点类型:float32、float64
- 复数类型:complex64、complex128
- 字符串:string
- 字符类型:rune
- 错误类型:error
(1)Go支持的整型类型非常丰富,其中int、uint、uintptr的位数与平台有关。
(2)Go在运算时无法进行自动类型转换,不同类型的值进行运算会报错。
数据类型之间的互相转化,只需调用要转化的数据类型对应的函数即可。(在有符号与无符号以及高位数字向低位数字转化时,需要注意数字的溢出和截断。)
目前 Go 语言不支持将数值类型转化为布尔型,需要自己根据需求去实现类似的转化。
(3)Go语言中++ 或 -- 只能出现在语句中,不能用于表达式,而且只能放在变量后,不能放在变量前。
(4)Go中float32相当于Java中的float,Go中的float64相当于Java中的double。
(5)Go支持复数类型,复数支持两种类型:complex64(32 位实部和虚部) 和 complex128(64 位实部与虚部),对应的表示示例如下,和数学概念中的复数表示形式一致:
对于一个复数 z = complex(x, y),就可以通过 Go 语言内置函数 real(z) 获得该复数的实部,也就是 x,通过 imag(z) 获得该复数的虚部,也就是 y。
(6)Go中字符串是一种基本数据类型,Java中字符串是引用类型。
Go的字符串默认是通过 UTF-8 编码的字符序列,当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节,比如中文编码通常需要 3 个字节。Java中的字符串底层是指针指向的一个字符数组。
Go和Java一样,字符串可以通过数组下标访问其中的字符,而且不可变值。
Go 语言中字符串默认是 UTF-8 编码的 Unicode 字符序列
在 Go 语言中,可以通过字符串切片实现获取子串的功能:
切片相当于Java中的subString方法,取元素的区间同样是左闭右开。
Go 语言对字符串中的单个字符进行了单独的类型支持,在 Go 语言中支持两种字符类型:
- 一种是 byte,代表 UTF-8 编码中单个字节的值(它也是 uint8 类型的别名,两者是等价的,因为正好占据 1 个字节的内存空间);
- 另一种是 rune,代表单个 Unicode 字符(它也是 uint32 类型的别名,因为正好占据 4 个字节的内存空间。
如果想要将 Unicode 字符编码转化为对应的字符,可以使用 string 函数进行转化。
UTF-8 编码不能这样转化,英文字符没问题,因为一个英文字符就是一个字节,中文字符则会乱码,因为一个中文字符编码需要三个字节,转化单个字节会出现乱码。
(7)Go中整型数据可以通过 Unicode 字符集转化为对应的 UTF-8 编码的字符串:
Unicode 兼容 ASCII 字符集,所以 65 被转化为 A。
此外,还可以将 byte 数组或者 rune 数组转化为字符串,因为字符串底层就是通过这两个基本字符类型构建的:
当然了,byte 是 uint8 的别名,rune 是 uint32 的别名,所以也可以看做是整型数组和字符串之间的转化。
Java中整型数据可以通过String.valueOf()方法转化为字符串。
(8)Go 语言默认不支持将字符串类型强制转化为数值类型,即使字符串中包含数字也不行。如果要实现更强大的基本数据类型与字符串之间的转化,可以使用 Go 官方 strconv 包提供的函数。
Java可以通过Integer.parseInt()方法将包含数字的字符串转化为整型数据。
11.Go支持的复合类型:
- 指针(pointer)
- 数组(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 结构体(struct)
- 接口(interface)
(1)对于数组,Go和Java很类似,都是固定长度的、同一类型的数据集合。Go一些常见的数组声明方法如下图所示:
Go 语言还提供了一个关键字 range用于遍历数组
range 表达式返回两个值,第一个是数组下标索引值,第二个是索引对应数组元素值。
(2)Go中的数组是值类型,切片是引用类型,支持动态的添加新元素。
在 Go 语言中,切片是一个新的数据类型,与数组最大的不同在于,切片的类型字面量中只有元素的类型,没有长度:
切片从底层管理上来看依然使用数组来管理元素,可以看作是对数组做了一层简单的封装。
创建切片的方法主要有三种 —— 基于数组(通过 array[start:end] 这样的方式基于数组生成一个切片)、切片和直接创建(Go 语言提供的内置函数 make() 可以用于灵活地创建切片)。最终切片都是基于数组创建的,切片可以看做是操作数组的指针。
切片底层结构:
切片底层引用了一个数组,由三个部分构成 —— 指针、长度和容量,指针指向数组起始下标,长度对应切片中元素的个数,容量则是切片起始位置到底层数组结尾的位置:
可以通过 append() 函数向切片追加新元素,append() 的第二个参数是一个不定参数,可以是若干个元素,也可以是一个另一个切片。如果追加的元素超过了原切片的容量,底层会自动扩容(默认两倍)。
切片可以通过copy()函数复制,默认按照小切片的个数进行复制。
切片结构体:
两个切片指向同一个数组会存在内存共享问题,解决办法是将其中一个切片扩容到比原容量大,则会自动新建一个底层数组。
排序可以使用sort包。
(3)Go字典类型的声明:
或
或者类似切片,使用Go内置的make方法:
这种方法的初始化可以使用下图所示方法添加键值对:
字典查找返回两个值,找到的键值和是否成功找到的标识
Go在声明字典的键类型时,要求数据类型必须是支持通过 == 或 != 进行判等操作的类型。在Java中Map类型的键值要求必须重写equals()和hashcode()方法。
Go用内置函数 delete()删除容器内的元素,Java用remove()。
(3)Go支持指针,Java不支持,但Java的引用类型类似指针。Java中无指针,所以程序员不能直接操作内存地址,这在一定程度上也保证了安全性。
声明指针:var ptr *int // 声明指针类型
*ptr取指针指向的变量值,&a取变量的地址。
Go规定unsafe.Pointer 是特别定义的一种指针类型,它可以包含任意类型变量的地址(类似 C 语言中的 void 类型指针)。Go 官方文档对这个类型有如下四个描述:
a.任何类型的指针都可以被转化为 unsafe.Pointer;
b.unsafe.Pointer 可以被转化为任何类型的指针;
c.uintptr 可以被转化为 unsafe.Pointer;
d.unsafe.Pointer 可以被转化为 uintptr。
uintprt类型单独列出的原因是:uintptr 是 Go 内置的可用于存储指针的整型,而整型是可以进行数学运算的!因此,将 unsafe.Pointer 转化为 uintptr 类型后,就可以让本不具备运算能力的指针具备了指针运算能力(如:指向前一个元素的指针加上元素偏移量可以得到下一个元素的内存地址)。实际使用时要尽量避免,因为可以绕过 Go 指针的安全限制,实现对指针的动态偏移和计算了,这会导致即使发生数组越界了,也不会报错,而是返回下一个内存地址存储的值,这就破坏了内存安全限制,所以这也是不安全的操作。



