概述
基于学习效率的考虑,我打算不再详细进行解释,所有举例以及笔记都将默认阅读者有一定的编程语言基础。因此,我不保证按基本流程书写和内容完整。
更多的,我将记录的是与其他编程语言的一些异同点
参考
变量
声明
在Go中,变量的定义有下列几种方式
var a string = "G" // 显式指定变量类型并赋值 var b = "G" // 自动推断类型 类似于Python,Ruby,JavaScript这类动态语言 c := "O" // 简短声明 一般用于声明局部变量
显而易见,var a
这种语法是不正确的,因为编译器并没有任何可以用于自动判断的依据。
变量的类型也可以在运行时实现自动推断,例如:
var ( home = os.Getenv("HOME") user = os.Getenv("USER") goRoot = os.Getenv("GOROOT") )
这种因式分解关键字的写法一般用于声明全局变量
Q:Go在声明变量时将变量的类型放在变量的名称之后,为什么?
A:首先,它是为了避免像C语言中那样含糊不清的声明形式,例如:
int* a, b
; 在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在Go中则可以这样声明:var a, b *int
其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。
变量作用域
尽管在Go中变量的标识符同样要求唯一,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。
那么,练习是必须的:推断以下程序的输出,并解释你的答案,然后编译并执行它们
练习1 local_scope.go
package main var a = "G" func main() { n() m() n() } func n() { print(a) } func m() { a := "O" print(a) }
练习2 global_scope.go
package main var a = "G" func main() { n() m() n() } func n() { print(a) } func m() { a := "O" print(a) }
练习3 function_calls_function.go
package main var a string func main() { a = "G" print(a) f1() } func f1() { a := "O" print(a) f2() } func f2() { print(a) }
简短形式,:= 赋值操作符
这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 :=
可以高效地创建一个新的变量,称之为初始化声明。
注意事项
如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如: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 declared and not used
。
此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 fmt.Println("hello, world", a)
会移除错误。
但是全局变量是允许声明但不使用。
其他的简短形式:
同一类型的多个变量可以声明在同一行,如:
var a, b, c int
(这是将类型写在标识符后面的一个重要原因)
多变量可以在同一行进行赋值(像极了Python),如:
a, b, c = 5, 7, "abc"
这被称为 并行 或 同时 赋值。
考虑一下,在C中我们如何交换变量?那么再看看Go如何交换: a, b = b, a
空白标识符 _
,_
实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值,如值 5
在:_, b = 5, 7
中被抛弃。
init 函数
类比Python中Class的__init__
函数,在Go中init函数先于main函数运行,可被用作初始化变量或是程序运行的校验
基本类型和运算符
布尔类型 bool
与C类似,不提要
数字类型
(与Python有一些共通之处)
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码
Go 也有基于架构的类型,例如:int、uint 和 uintptr
- int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)
- uintptr 的长度被设定为足够存放一个指针即可
Go 语言中没有 float 类型,(Go 语言中只有 float32 和 float64)没有 double 类型
一些注意点
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
Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):
package main func main() { var a int var b int32 a = 15 b = a + a // 编译错误 b = b + 5 // 因为 5 是常量,所以可以通过编译 }
如果你尝试编译该程序,则将得到编译错误 cannot use a + a (type int) as type int32 in assignment
。
同样地,int16 也不能够被隐式转换为 int32。
下面这个程序展示了通过显式转换来避免这个问题
package main import "fmt" func main() { var n int16 = 34 var m int32 // compiler error: cannot use n (type int16) as type int32 in assignment //m = n m = int32(n) fmt.Printf("32 bit int is: %d\n", m) fmt.Printf("16 bit int is: %d\n", n) }
输出:
32 bit int is: 34 16 bit int is: 34
复数
Go 拥有以下复数类型:
complex64 (32 位实数和虚数) complex128 (64 位实数和虚数)
复数使用 re+imI
来表示,其中 re
代表实数部分,im
代表虚数部分,I 代表根号负 1
示例:
var c1 complex64 = 5 + 10i fmt.Printf("The value is: %v", c1) // 输出: 5 + 10i
如果 re
和 im
的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:
c = complex(re, im)
函数 real(c)
和 imag(c)
可以分别获得相应的实数和虚数部分。
在使用格式化说明符时,可以使用 %v
来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f
。
复数支持和其它数字类型一样的运算。当你使用等号 ==
或者不等号 !=
对复数进行比较运算时,注意对精确度的把握。cmath
包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。
算术运算符
带有 ++
和 --
的只能作为语句,而非表达式,因此 n = i++
这种写法是无效的,其它像 f(i++)
或者 a[i]=b[i++]
这些可以用于 C、C++ 和 Java 中的写法在 Go 中也是不允许的。
浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf
表示。
运算时 溢出 不会产生错误,Go 会简单地将超出位数抛弃。如果你需要范围无限大的整数或者有理数(意味着只被限制于计算机内存),你可以使用标准库中的 big
包,该包提供了类似 big.Int
和 big.Rat
这样的类型
类型别名
给某个类型起另一个名字(用于简化名称或解决名称冲突)
在 type TZ int
中,TZ 就是 int 类型的新名称(这里用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据
实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法(后续提要);TZ 可以自定义一个方法用来输出更加人性化的时区信息。
字符类型
除类似C语言char操作语法,Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune
也是 Go 当中的一个类型,并且是 int32
的别名。
在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u
或者 \U
。
因为 Unicode 至少占用 2 个字节,所以我们使用 int16
或者 int
类型来表示。如果需要使用到 4 字节,则会加上 \U
前缀;前缀 \u
则总是紧跟着长度为 4 的 16 进制数,前缀 \U
紧跟着长度为 8 的 16 进制数。
var ch int = '\u0041' var ch2 int = '\u03B2' var ch3 int = '\U00101234' fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
包 unicode
包含了一些针对测试字符的非常有用的函数(其中 ch
代表字符):
- 判断是否为字母:
unicode.IsLetter(ch)
- 判断是否为数字:
unicode.IsDigit(ch)
- 判断是否为空白符号:
unicode.IsSpace(ch)
这些函数返回一个布尔值。包 utf8
拥有更多与 rune 类型相关的函数。
发表回复