Go的研习笔记-day3(以Java的视角学习Go)

Go的语法:

一、文件名、关键字与标识符
Go 的源文件以 .go 为后缀名存储在计算机中,这些文件名均由小写字母组成,如 scanner.go 。如果文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。
一个源文件可以包含任意多行的代码,Go 本身没有对源文件的大小进行限制。
你会发现在 Go 代码中的几乎所有东西都有一个名称或标识符。另外,Go 语言也是区分大小写的,这与 C 家族中的其它语言相同。有效的标识符必须以字母(可以使用任何 UTF-8 编码的字符或 _)开头,然后紧跟着 0 个或多个字符或 Unicode 数字,如:X56、group1、_x23、i、өԑ12
以下是无效的标识符:

  • 1ab(以数字开头)
  • case(Go 语言的关键字)
  • a+b(运算符是不允许的)
    _ 本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
    在编码过程中,你可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。
    下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
Java中经常用的 go使用的Java无的(除goto)
break select
default map
import go
interface struct
case chan
for fallthrough
else range
continue type
package goto
switch defer
return func
if const
var

之所以刻意地将 Go 代码中的关键字保持的这么少,是为了简化在编译过程第一步中的代码解析。和其它语言一样,关键字不能够作标识符使用。
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数在这里插入图片描述
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。
程序的代码通过语句来实现结构化。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分

二、Go程序的基本结构和要素

package main

import "fmt"

func main() {
	fmt.Println("hello, world")
}
  • 包的概念、导入与可见性
  • 一个包package可以有多个.go的文件与Java类似一个包下多个.java文件。道理相通。
  • 在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。这一步骤是必须的。
  • 一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包。如果你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母
  • 标准库
    在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。在 Windows 下,标准库的位置在 Go 根目录下的子目录 pkg\windows_386 中;在 Linux 下,标准库在 Go 根目录下的子目录 pkg\linux_amd64 中(如果是安装的是 32 位,则在 linux_386 目录中)。一般情况下,标准包会存放在 $ GOROOT/pkg/$ GOOS_$GOARCH/ 目录下。
    Go 的标准库包含了大量的包(如:fmt 和 os),但是你也可以创建自己的包。
    如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。
    属于同一个包的源文件必须全部被一起编译,一个包即是编译时的一个单元,因此根据惯例,每个目录都只包含一个包。
    如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。
    Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 .o 的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。
    如果 A.go 依赖 B.go,而 B.go 又依赖 C.go:
    编译 C.go, B.go, 然后是 A.go.
    为了编译 A.go, 编译器读取的是 B.o 而不是 C.o.
    这种机制对于编译大型的项目时可以显著地提升编译速度。
    每一段代码只会被编译一次
    一个 Go 程序是通过 import 关键字将一组包链接在一起。
    import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。包名被封闭在半角双引号 “” 中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。
    如果需要多个包,它们可以被分别导入:
    import “fmt”
    import “os”
    或:
    import “fmt”; import “os”
    但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义):
    import (
    “fmt”
    “os”
    )
    它甚至还可以更短的形式,但使用 gofmt 后将会被强制换行:
    import (“fmt”; “os”)
    当你导入多个包时,最好按照字母顺序排列包名,这样做更加清晰易读。
    如果包名不是以 . 或 / 开头,如 “fmt” 或者 “container/list”,则 Go 会在全局文件进行查找;如果包名以 ./ 开头,则 Go 会在相对目录中查找;如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。
    导入包即等同于包含了这个包的所有的代码对象。
    除了符号 _,包中所有代码对象的标识符必须是唯一的,以避免名称冲突。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。
  • 可见性规则
    当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
    (大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)。
    因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。
    假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不可以省略的)。
    因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,例如 pack1.Thing 和 pack2.Thing。
    你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:import fm “fmt”。
    package main
    import fm “fmt” // alias3
    func main() {
    fm.Println(“hello, world”)
    }
  • 注意:清除无用的包,Java中经常由于导入多个未使用的包需要清除加快编译速度以及代码整洁程度。go语言直接会将引入未使用的包以报错信息编译不通过。
  • 包的分级声明和初始化
    你可以在使用 import 导入包之后定义或声明 0 个或多个常量(const)、变量(var)和类型(type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用,然后声明一个或多个函数(func)。
  • 函数
    func 声明方法函数类似js的function或者Java中的自定义方法名称。程序调用如果有init()方法会先执行init方法,再调用main方法。main函数与Java函数main方法一样没有返回值类型。否则报错。函数通过大括号括起来,左大括号必须与方法声明放在同一行(编译器强制规定)。多个参数可以使用逗号分隔开,与Java和js一样。但是区别是类型在后,参数在前相比于Java而言。
    Java的方法select(String a,String b); go的方法select(a string,b string)
    只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。
  • 调试
    Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。程序正常退出的代码为 0 即 Program exited with code 0;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。
  • 注释
    与Java中的一样,以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
    每一个包应该有相关注释,在 package 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。当开发人员需要了解包的一些情况时,自然会用 godoc 来显示包的文档说明,在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。另外,在多段注释之间应以空行分隔加以区分。
    几乎所有全局作用域的类型、常量、变量、函数和被导出的对象都应该有一个合理的注释。如果这种注释(称为文档注释)出现在函数前面,例如函数 Abcd,则要以 “Abcd…” 作为开头。
  • 类型
    变量(或常量)包含数据,这些数据可以有不同的数据类型,简称类型。使用 var 声明的变量的值会自动初始化为该类型的零值。类型定义了某个变量的值的集合与可对其进行操作的集合。
    类型可以是基本类型,如:int、float、bool、string;结构化的(复合的),如:struct、array、slice、map、channel;只描述类型的行为的,如:interface。
    结构化的类型没有真正的值,它使用 nil 作为默认值(在 Objective-C 中是 nil,在 Java 中是 null,在 C 和 C++ 中是NULL或 0)。值得注意的是,Go 语言中不存在类型继承。
    使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体
    使用var方式声明变量
    多个类型需要定义,可以使用因式分解关键字的方式
    type (
    IZ int
    FZ float64
    STR string
    )
    每个值都必须在经过编译后属于某个类型(编译器必须能够推断出所有值的类型),因为 Go 语言是一种静态类型语言。
  • 函数的返回值
这一点与Java不一样,Java中经常以如下方式作为返回
public Object  getReutrnType(){
return new Ojbect();
}

go语言返回值
func FunctionName (a typea, b typeb) typeFunc{
return var
}
而且go语言可以多个返回值
func Atoi(s string) (i int, err error){
return var1, var2
}

go的基本结构以及go程序的执行启动顺序

  • package的声明
  • import其他包的导入
  • 常量const,变量var,类型type的定义和声明
  • init函数(如果存在每个包都会首先执行该函数)
  • main包,则需要定义main函数
  • 定义其余函数或者类型返回的方法,然后按照main函数中先后调用顺序定义相关函数,如果很多函数,则按照字母顺序来排序。
package main

import (
   "fmt"
)

const c = "C"

var v int = 5

type T struct{}

func init() { // initialization of package
}

func main() {
   var a int
   Func1()
   // ...
   fmt.Println(a)
}

func (t T) Method1() {
   //...
}

func Func1() { // exported function Func1
   //...
}

Go 程序的执行(程序启动)顺序如下:
1、按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程:
2、如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
3、然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。
4、在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。

类型转换
类型 B 的值 = 类型 B(类型 A 的值)
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于 Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
valueOfTypeB = typeB(valueOfTypeA)
在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 int16 转换为 int32)。当从一个取值范围较大的转换到取值范围较小的类型时(例如将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失(截断)的情况。当编译器捕捉到非法的类型转换时会引发编译时错误,否则将引发运行时错误。

Go命名规范
与Java这里也有不太相同,go追求的是简洁,干净,可读。所以命名不建议太冗余或者下划线分隔。有必要可以大小写混合。因为其使用包名作为了限定符。

三、常量
常量使用关键字 const 定义,用于存储不会改变的数据。
存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:const identifier [type] = value

在 Go 语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
显式类型定义: const b string = “abc”
隐式类型定义: const b = “abc”
未定义类型的常量会在必要时刻根据上下文来获得相关类型
var n int
f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int
我觉得这点有点类似Java中根据上下文推断是否需要指令重排序进行优化。

常量的值必须是能够在编译时就能够确定的;因为是一门静态语言。
正确的做法:const c1 = 2/3
错误的做法:const c2 = getNumber() // 引发构建错误: getNumber() used as value
因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()。
数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出:

反斜杠 \ 可以在常量表达式中作为多行的连接符使用。
const Ln2= 0.693147180559945309417232121458\
			176568075500134360255254120680009
const Log2E= 1/Ln2 // this is a precise reciprocal
const Billion = 1e9 // float constant
const hardEight = (1 << 100) >> 97

当常量赋值给一个精度过小的数字型变量时,可能会因为无法正确表达常量所代表的数值而导致溢出,这会在编译期间就引发错误。另外,常量也允许使用并行赋值的形式:

const beef, two, c = "eat", 2, "veg"
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6
const (
	Monday, Tuesday, Wednesday = 1, 2, 3
	Thursday, Friday, Saturday = 4, 5, 6
)

常量还可以用作枚举:

const (
	Unknown = 0
	Female = 1
	Male = 2
)
如果使用iota表达的话则可以表达如下
const (
	Unknown = iota
	Female = iota
	Male = iota
)
iota 也可以用在表达式中,如:iota + 50。在每遇到一个新的常量块或单个常量声明时, iota 都会重置为 0( 简单地讲,每遇到一次 const 关键字,iota 就重置为 0 )。

四、变量(除了类型在后声明,其他都类似相同。)
声明变量的一般形式是使用 var 关键字:var identifier type
var str string
你也可以改写成这种形式:
var (
a int
b bool
str string
)
这种因式分解关键字的写法一般用于声明全局变量。
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 和 startDate。
一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。
在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。
尽管变量的标识符必须是唯一的,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。
变量可以编译期间就被赋值,赋值给变量使用运算符等号 =,当然你也可以在运行时对变量进行赋值操作。但是 Go 编译器的智商已经高到可以根据变量的值来自动推断其类型,这有点像 Ruby 和 Python 这类动态语言,只不过它们是在运行时进行推断,而 Go 是在编译时就已经完成推断过程。
全局变量中声明变量 var a int64 =1;如果是局部变量可以改为 a:=1;

值类型和引用类型
内存在计算机中使用一堆箱子来表示,这些箱子被称为 “ 字 ”。根据不同的处理器以及操作系统类型,所有的字都具有 32 位(4 字节)或 64 位(8 字节)的相同长度;所有的字都使用相关的内存地址来进行表示(以十六进制数表示)。所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值
在这里插入图片描述
另外,像数组和结构这些复合类型也是值类型。
当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:
在这里插入图片描述
你可以通过 &i 来获取变量 i 的内存地址,值类型的变量的值存储在栈中。这个时候你类比Java感受到了什么?我们的基本变量类型其实就对应着go语言的值类型。
内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。
更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
在这里插入图片描述
这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个字中。
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。
当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响
在 Go 语言中,指针属于引用类型,其它的引用类型还包括 slices,maps和 channel。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。这样的话你如果类比Java的话会发现这里强调的指针其实类型Java对象,而对象的传递也是引用的传递。Java对象同样保存在堆内存中,以便进行垃圾回收。这里也可以看到是Go语言也充分利用了Java的一些特性比如垃圾回收机制。

打印
函数 Printf 可以在 fmt 包外部使用,这是因为它以大写字母 P 开头,该函数主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数:
func Printf(format string, list of variables to be printed)
函数 fmt.Sprintf 与 Printf 的作用是完全相同的,不过前者将格式化后的字符串以返回值的形式返回给调用者
:=赋值操作符
初始化声明:使用操作符 := 可以高效地创建一个新的变量
注意事项
如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。
如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:
func main() {
var a string = “abc”
fmt.Println(“hello, world”)
}
此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 fmt.Println(“hello, world”, a) 会移除错误。
但是全局变量是允许声明但不使用。
并行 或 同时 赋值
a, b, c := 5, 7, "abc"右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 “abc”。

init函数
变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。
每个源文件都只能包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。

五、 基本类型和运算符
一元运算符只可以用于一个值的操作(作为后缀),而二元运算符则可以和两个值或者操作数结合(作为中缀)。
只有两个类型相同的值才可以和二元运算符结合,另外要注意的是,Go 是强类型语言,因此不会进行隐式转换,任何不同类型之间的转换都必须显式说明Go 不存在像 C 和 Java 那样的运算符重载,表达式的解析顺序是从左至右。

  • 布尔类型bool
    布尔型的值只可以是常量 true 或者 false。
    两个类型相同的值可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。
    当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的值的类型相同的情况下才可以使用。
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true

当不等运算符两边的值是不同的时候会返回 true,否则返回 false。

var aVar = 10
aVar != 5 -> true
aVar != 10 -> false

Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口(interface),它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。
布尔型的常量和变量也可以通过和逻辑运算符(非 !、和 &&、或 ||)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。
逻辑值可以被用于条件结构中的条件语句,以便测试某个条件是否满足。另外,和 &&、或 || 与相等 == 或不等 != 属于二元运算符,而非 ! 属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。
在 Go 语言中,&& 和 || 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(&& 左边的值为 false,|| 左边的值为 true),运算符右边的表达式将不会被执行。利用这个性质,如果你有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧以减少不必要的运算。
而这些运算其实类比Java语言也是相同的。

数字类型

  • 整型int和浮点型float
    Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码(详情参见 二的补码 页面)
    Go 也有基于架构的类型,例如:int、uint 和 uintptr
    这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
    int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。
    uintptr 的长度被设定为足够存放一个指针即可。
    Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。
    与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来。
  • 整数:
    int8(-128 -> 127)
    int16(-32768 -> 32767)
    int32(-2,147,483,648 -> 2,147,483,647)
    int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
  • 无符号整数:
    uint8(0 -> 255)
    uint16(0 -> 65,535)
    uint32(0 -> 4,294,967,295)
    uint64(0 -> 18,446,744,073,709,551,615)
  • 浮点型(IEEE-754 标准):
    float32(± 1e-45 -> ± 3.4 * 1e38)
    float64(± 5 * 1e-324 -> 107 * 1e308)

int 型是计算最快的一种类型。
整型的零值为 0,浮点型的零值为 0.0。
float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算。
你应该尽可能地使用 float64,因为 math 包中所有有关数学运算的函数都会要求接收这个类型。
你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
你可以使用 a := uint64(0) 来同时完成类型转换和赋值操作,这样 a 的类型就是 uint64。

位运算
位运算只能用于整数类型的变量,且需当它们拥有等长位模式时
%b 是用于表示位的格式化标识符。

  • 二元运算符
  • 按位与 &
  1 & 1 -> 1
  1 & 0 -> 0
  0 & 1 -> 0
  0 & 0 -> 0
  • 按位或 |
  1 | 1 -> 1
  1 | 0 -> 1
  0 | 1 -> 1
  0 | 0 -> 0
  • 按位异或 ^
 1 ^ 1 -> 0
  1 ^ 0 -> 1
  0 ^ 1 -> 1
  0 ^ 0 -> 0
  • 位清除 &^:将指定位置上的值设置为 0
  • 一元运算符
  • 按位补足 ^
    该运算符与异或运算符一同使用,即 m^x,对于无符号 x 使用“全部位设置为 1”,对于有符号 x 时使用 m=-1
    ^10 = -01 ^ 10 = -11
  • 位左移 <<:
    用法:bitP << n。
    bitP 的位向左移动 n 位,右侧空白部分使用 0 填充;如果 n 等于 2,则结果是 2 的相应倍数,即 2 的 n 次方。例如:
    1 << 10 // 等于 1 KB
    1 << 20 // 等于 1 MB
    1 << 30 // 等于 1 GB
  • 位右移 >>:
    用法:bitP >> n。
    bitP 的位向右移动 n 位,左侧空白部分使用 0 填充;如果 n 等于 2,则结果是当前值除以 2 的 n 次方。
  • [ ]位左移常见实现存储单位的用例
    使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:
    type ByteSize float64
    const (
    _ = iota // 通过赋值给空白标识符来忽略值
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
    )
    在通讯中使用位左移表示标识的用例
    type BitFlag int
    const (
    Active BitFlag = 1 << iota // 1 << 0 == 1
    Send // 1 << 1 == 2
    Receive // 1 << 2 == 4
    )
    flag := Active | Send // == 3
  • 逻辑运算符
    Go 中拥有以下逻辑运算符:==、!=、<、<=、>、>=。
    它们之所以被称为逻辑运算符是因为它们的运算结果总是为布尔值 bool
  • 算术运算符
    常见可用于整数和浮点数的二元运算符有 +、-、* 和 /。
    (相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符 + 的重载,但 Go 本身不允许开发者进行自定义的运算符重载)
    / 对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2。
    取余运算符只能作用于整数:9 % 4 -> 1。
    整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);
    浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf 表示
  • 随机数
    rand 包实现了伪随机数的生成。
  • 运算符优先级
    二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
    在这里插入图片描述
    通过使用括号来临时提升某个表达式的整体运算优先级。
  • 类型别名
    在 type TZ int 中,TZ 就是 int 类型的新名称(用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据。
  • 字符类型
    byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = ‘A’;字符使用单引号括起来。
    格式化说明符 %c 用于表示字符;当和字符配合使用时,%v 或 %d 会输出用于表示该字符的整数;%U 输出格式为 U+hhhh 的字符串。
    包 unicode 包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):
    判断是否为字母:unicode.IsLetter(ch)
    判断是否为数字:unicode.IsDigit(ch)
    判断是否为空白符号:unicode.IsSpace(ch)

六、字符串
字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。UTF-8 是被广泛使用的编码格式,是文本文件的标准编码,其它包括 XML 和 JSON 在内,也都使用该编码。由于该编码对占用字节长度的不定性,Go 中的字符串里面的字符也可能根据需要占用 1 至 4 个字节,这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码
字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组。

  • Go 支持以下 2 种形式的字面值:
  • 解释字符串:
    该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
    \n:换行符
    \r:回车符
    \t:tab 键
    \u 或 \U:Unicode 字符
    \:反斜杠自身
  • 非解释字符串:
    该类字符串使用反引号括起来,支持换行,例如:
    This is a raw string \n 中的 \n\ 会被原样输出。

和 C/C++不一样,Go 中的字符串是根据长度限定,而非特殊字符\0。
string 类型的零值为长度为零的字符串,即空字符串 “”。
一般的比较运算符(==、!=、<、<=、>=、>)通过在内存中按字节比较来实现字符串的对比。你可以通过函数 len() 来获取字符串所占的字节长度,例如:len(str)
对纯 ASCII 码的字符串,字符串 str 的第 1 个字节:str[0],最后 1 个字节:str[len(str)-1]

== 注意事项 获取字符串中某个字节的地址的行为是非法的,例如:&str[i]==

字符串拼接符 +
str := "Beginning of the string " +
“second part of the string”
s := “hel” + “lo,”
s += “world!”
fmt.Println(s) //输出 “hello, world!”

这里也和Java相通,唯一优化点Java可以通过StringBuilder或者StringBuffer类拼接

strings和strconv包
作为一种基本数据结构,每种语言都有一些对于字符串的预定义处理函数。Go 中使用 strings 包来完成对字符串的主要操作。这样类似Java中的string的自己带的方法

  • HasPrefix 判断字符串 s 是否以 prefix 开头:
    strings.HasPrefix(s, prefix string) bool
  • HasSuffix 判断字符串 s 是否以 suffix 结尾:
    strings.HasSuffix(s, suffix string) bool
  • Contains 判断字符串 s 是否包含 substr:
    strings.Contains(s, substr string) bool
  • 判断子字符串或字符在父字符串中出现的位置(索引)
    Index 返回字符串 str 在字符串 s 中的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
    strings.Index(s, str string) int
    LastIndex 返回字符串 str 在字符串 s 中最后出现位置的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
    strings.LastIndex(s, str string) int
    如果需要查询非 ASCII 编码的字符在父字符串中的位置,建议使用以下函数来对字符进行定位:
    strings.IndexRune(s string, r rune) int
  • 字符串替换
    Replace 用于将字符串 str 中的前 n 个字符串 old 替换为字符串 new,并返回一个新的字符串,如果 n = -1 则替换所有字符串 old 为字符串 new:
    strings.Replace(str, old, new, n) string
  • 统计字符串出现次数
    Count 用于计算字符串 str 在字符串 s 中出现的非重叠次数:
    strings.Count(s, str string) int
  • 重复字符串
    Repeat 用于重复 count 次字符串 s 并返回一个新的字符串:
    strings.Repeat(s, count int) string
  • 修改字符串大小写
    ToLower 将字符串中的 Unicode 字符全部转换为相应的小写字符:
    strings.ToLower(s) string
    ToUpper 将字符串中的 Unicode 字符全部转换为相应的大写字符:
    strings.ToUpper(s) string
  • 修剪字符串
    你可以使用 strings.TrimSpace(s) 来剔除字符串开头和结尾的空白符号;如果你想要剔除指定字符,则可以使用 strings.Trim(s, “cut”) 来将开头和结尾的 cut 去除掉。该函数的第二个参数可以包含任何字符,如果你只想剔除开头或者结尾的字符串,则可以使用 TrimLeft 或者 TrimRight 来实现。
  • 分割字符串
    strings.Fields(s) 将会利用 1 个或多个空白符号来作为动态长度的分隔符将字符串分割成若干小块,并返回一个 slice,如果字符串只包含空白符号,则返回一个长度为 0 的 slice。
    strings.Split(s, sep) 用于自定义分割符号来对指定字符串进行分割,同样返回 slice。因为这 2 个函数都会返回 slice,所以习惯使用 for-range 循环来对其进行处理
  • 拼接 slice 到字符串
    Join 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串:
    strings.Join(sl []string, sep string) string
package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "The quick brown fox jumps over the lazy dog"
	sl := strings.Fields(str)
	fmt.Printf("Splitted in slice: %v\n", sl)
	for _, val := range sl {
		fmt.Printf("%s - ", val)
	}
	fmt.Println()
	str2 := "GO1|The ABC of Go|25"
	sl2 := strings.Split(str2, "|")
	fmt.Printf("Splitted in slice: %v\n", sl2)
	for _, val := range sl2 {
		fmt.Printf("%s - ", val)
	}
	fmt.Println()
	str3 := strings.Join(sl2,";")
	fmt.Printf("sl2 joined by ;: %s\n", str3)
}
输出结果:
Splitted in slice: [The quick brown fox jumps over the lazy dog]
The - quick - brown - fox - jumps - over - the - lazy - dog -
Splitted in slice: [GO1 The ABC of Go 25]
GO1 - The ABC of Go - 25 -
sl2 joined by ;: GO1;The ABC of Go;25
  • 从字符串中读取内容
    函数 strings.NewReader(str) 用于生成一个 Reader 并读取字符串中的内容,然后返回指向该 Reader 的指针,从其它类型读取内容的函数还有:
    Read() 从 []byte 中读取内容。
    ReadByte() 和 ReadRune() 从字符串中读取下一个 byte 或者 rune。
  • 字符串与其它类型的转换
    与字符串相关的类型转换都是通过 strconv 包实现的。
    该包包含了一些变量用于获取程序运行的操作系统平台下 int 类型所占的位数,如:strconv.IntSize。
    任何类型 T 转换为字符串总是成功的。
    针对从数字类型转换到字符串,Go 提供了以下函数:
    strconv.Itoa(i int) string 返回数字 i 所表示的字符串类型的十进制数。
    strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string 将 64 位浮点型的数字转换为字符串,其中 fmt 表示格式(其值可以是 ‘b’、‘e’、‘f’ 或 ‘g’),prec 表示精度,bitSize 则使用 32 表示 float32,用 64 表示 float64。
    将字符串转换为其它类型 tp 并不总是可能的,可能会在运行时抛出错误 parsing “…”: invalid argument。
    针对从字符串类型转换为数字类型,Go 提供了以下函数:
    strconv.Atoi(s string) (i int, err error) 将字符串转换为 int 型。
    strconv.ParseFloat(s string, bitSize int) (f float64, err error) 将字符串转换为 float64 型。
    利用多返回值的特性,这些函数会返回 2 个值,第 1 个是转换后的结果(如果转换成功),第 2 个是可能出现的错误,因此,我们一般使用以下形式来进行从字符串到其它类型的转换:
    val, err = strconv.Atoi(s)

八、时间和日期
time 包为我们提供了一个数据类型 time.Time(作为值使用)以及显示和测量时间和日期的功能函数。
当前时间可以使用 time.Now() 获取,或者使用 t.Day()、t.Minute() 等等来获取时间的一部分;你甚至可以自定义时间格式化字符串,例如: fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) 将会输出 21.07.2011。
Duration 类型表示两个连续时刻所相差的纳秒数,类型为 int64。Location 类型映射某个时区的时间,UTC 表示通用协调世界时间。
包中的一个预定义函数 func (t Time) Format(layout string) string 可以根据一个格式化字符串来将一个时间 t 转换为相应格式的字符串,你可以使用一些预定义的格式,如:time.ANSIC 或 time.RFC822。
一般的格式化设计是通过对于一个标准时间的格式化描述来展现的,这听起来很奇怪,但看下面这个例子你就会一目了然:
fmt.Println(t.Format(“02 Jan 2006 15:04”))

九、指针
程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b0820 或 0xf84001d7f0。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址
下面的代码片段可能输出 An integer: 5, its location in memory: 0x6b0820(这个值随着你每次运行程序而变化)
var i1 = 5
fmt.Printf(“An integer: %d, it’s location in memory: %p\n”, i1, &i1)
这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 i1:此处使用 *int 表示。如果我们想调用指针 intP,我们可以这样声明它:
var intP *int
然后使用 intP = &i1 是合法的,此时 intP 指向 i1。
(指针的格式化标识符为 %p)
intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变量 i1。
一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
一个指针变量通常缩写为 ptr。
注意事项
在书写表达式类似 var p type 时,切记在 * 号和指针名称间留有一个空格,因为 - var ptype 是语法正确的,但是在更复杂的表达式中,它容易被误认为是一个乘法表达式!
符号 * 可以放在一个指针前,如 *intP,那么它将得到这个指针指向地址上所存储的值;这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。
对于任何一个变量 var, 如下表达式都是正确的:var == *(&var)。
c = *p++ 在 Go 语言的代码中是不合法的。
指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。
另一方面(虽然不太可能),由于一个指针导致的间接引用(一个进程执行了另一个地址),指针的过度频繁使用也会导致性能下降。
指针也可以指向另一个指针,并且可以进行任意深度的嵌套,导致你可以有多级的间接引用,但在大多数情况这会使你的代码结构不清晰。

原文链接:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.9.md

发布了213 篇原创文章 · 获赞 258 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/wolf_love666/article/details/97776577