Go语言圣经《The Go Programming Language》的语法纪要:
一、命名
共25个关键字:
break
case
chan
const
continue
default
defer
else
fallthrough
for
Go语言只有for循环这一种循环语句,for循环有多种形式。
for initialization; condition; post { // zero or more statements }
for循环三个部分不需括号包围。大括号强制要求,左大括号必须和post语句在同一行。
initialization、condition、post都可以省略。
// a traditional "while" loop for condition { // ... } // a traditional infinite loop for { // ... }
for循环还有一种形式,在某种数据类型的区间(range)上遍历,如字符串或slice等。
func main() { s, sep := "", "" for _, arg := range os.Args[1:] { s += sep + arg sep = " " } fmt.Println(s) }
func
go
goto
if
import
interface
map
package
range
return
select
struct
switch
type
var
内建常量:
true false iota nil
内建类型:
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内建函数:
make
len
cap
new
append
copy
close
delete
complex
real
imag
panic
recover
定义在函数外部的名字,在当前包的所有文件中都可以访问。用首字母的大小写决定包外的可见性。(大写可见)
二、四种类型声明
1. 变量var
举例:
var myName string = "Tom"
var myName string // 声明时未显示赋值,会被赋予零值
零值:数值类型为0,布尔类型为false,字符串类型为空字符串,接口或引用类型(slice/指针/map/chan/函数)为nil,数组或结构体等聚合类型为每个元素或字段都是零值。
var myName = "Tom"
myName := "Tom"
age := 3
p := &age // p被声明为int指针
p := new(int) // *int类型,指向匿名的int变量
上述两种声明方式创建p变量没有什么区别,这一点和C++不同。
在Go语言中,返回函数中局部变量的地址也是安全的。因为一个变量的生命周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。
编译器会自动选择在栈上海市在堆上分配局部变量的存储空间,这个选择并不是由用var还是new声明变量的方式决定。
2. 常量const
const boilingF = 212.0
常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型。
常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都返回常量结果:
len、cap、real、imag、complex和unsafe.Sizeof。
常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量。
type Weekday int const ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday )
周日对应为0,周一为1,依次类推。类似于其它编程语言中的枚举。
其它例子:
type Flags uint const ( FlagUp Flags = 1 << iota // is up FlagBroadcast // supports broadcast access capability FlagLoopback // is a loopback interface FlagPointToPoint // belongs to a point-to-point link FlagMulticast // supports multicast access capability ) const ( _ = 1 << (10 * iota) KiB // 1024 MiB // 1048576 GiB // 1073741824 TiB // 1099511627776 (exceeds 1 << 32) PiB // 1125899906842624 EiB // 1152921504606846976 ZiB // 1180591620717411303424 (exceeds 1 << 64) YiB // 1208925819614629174706176 )
Go语言的常量有个不同寻常之处,虽然一个常量可以有任意一个确定的基础类型(如:int或time.Duration),但是许多常量并没有一个明确的基础类型。编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术计算。
3. 类型type
一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。
type 类型名字 底层类型
type Celsius float64 // 摄氏温度
type Fahrenheit float64 // 华氏温度
4. 函数func
三、基础数据类型
1. 整型
2. 浮点数
3. 复数
4. 布尔型
5. 字符串
一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但通常是用来包含人类可读的文本。
文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列,内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目)。
子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节(不包含j本身)生成一个新字符串。
不过由于字符串的不变性意味着两个字符串共享相同的底层数据是安全的,即一个字符串s和其对应的字符串切片s[7:]的操作也可以安全的共享相同的内存,没有必要分配新内存。不变性使得复制任何长度的字符串代价是低廉的。
标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。
strings:提供诸如字符串查询、替换、比较、截断、拆分和合并等功能。
bytes:也提供strings中类似功能的函数,针对和字符串有相同结构的[]byte类型(字节slice)。
因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制,使用bytes.Buffer类型将会更有效。
strconv:提供布尔型、整型数、浮点数和对应字符串的相互转化,还提供双引号转义相关的转化。
unicode:提供IsDigit、IsLetter、IsUpper和IsLower等类似功能。
举例:
// intsToString is like fmt.Sprint(values) but adds commas. func intsToString(values []int) string { var buf bytes.Buffer buf.WriteByte('[') for i, v := range values { if i > 0 { buf.WriteString(", ") } fmt.Fprintf(&buf, "%d", v) } buf.WriteByte(']') return buf.String() } func main() { fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]" }
整数转字符串:
x := 123 y := fmt.Sprintf("%d", x) fmt.Println(y, strconv.Itoa(x)) // "123 123" fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
对于format,fmt包中的函数更方便(%b、%d、%o、%x)
s := fmt.Sprintf("x=%b", x) // "x=1111011"
字符串解析为整数,可以使用strconv包的Atoi、ParseInt、ParseUint函数
x, err := strconv.Atoi("123") // x is an int y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits
有时候也可以使用fmt.Scanf来解析输入的字符串和数字
四、复合数据类型
1. 数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。内置的len函数将返回数组中元素的个数。
var q [3]int = [3]int{1, 2, 3} var r [3]int = [3]int{1, 2} fmt.Println(r[2]) // "0" q := [...]int{1, 2, 3} fmt.Printf("%T\n", q) // "[3]int"
如果在数组的长度未知出现的是"..."省略号,则表示数组的长度是根据初始化值的个数来计算。
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型,数组的长度必须是常量表达式。
上面的形式是直接提供顺序初始化值的序列,Go中也可以指定一个索引和对应值列表的方式初始化,如下:
type Currency int const ( USD Currency = iota // 美元 EUR // 欧元 GBP // 英镑 RMB // 人民币 ) symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"} fmt.Println(RMB, symbol[RMB]) // "3 ¥" r := [...]int{99: -1} // 定义了一个含有100个元素的数组,最有一个元素为-1,其它为0
Go语言中数组作为函数的参数时,会进行复制,这一个和其它语言(如:C)不同。当然我们可以显示的传入一个数组指针。
func zero(ptr *[32]byte) { *ptr = [32]byte{} }
2. Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice是一个轻量级的数据结构,提供了访问数组子序列元素的功能,而且slice底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,长度对应slice中元素的数目,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
使用内置的make函数可以创建一个指定元素类型、长度和容量的slice。省略容量部分时,默认等于长度。
make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len]
使用内置的append函数可以向slice追加元素
var runes []rune for _, r := range "Hello, 世界" { runes = append(runes, r) // 通常是将append返回的结果直接赋值给输入的slice变量 } fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
3. Map
一个map是一个哈希表的引用。可以通过内置make函数或map字面值的语法创建map。
ages := make(map[string]int) // mapping from strings to ints ages := map[string]int{ "alice": 31, "charlie": 34, }
使用内置的delete函数可以删除元素:
delete(ages, "alice") // remove element ages["alice"]
我们不能对map中的元素进行取址操作,因为map可能随着元素数量的增长而重新分配更大的内存空间,从而导致之前的地址无效。
可以使用range风格的for循环遍历map的元素:
for name, age := range ages { fmt.Printf("%s\t%d\n", name, age) }
判断元素是否存在:
age, ok := ages["bob"] if !ok { /* "bob" is not a key in this map; age == 0. */ }
4. 结构体
type Employee struct { ID int Name string Address string DoB time.Time Position string Salary int ManagerID int } var dilbert Employee dilbert.Salary -= 5000 // demoted, for writing too few lines of code position := &dilbert.Position *position = "Senior " + *position // promoted, for outsourcing to Elbonia var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)" // 相当于(*employeeOfTheMonth).Position += " (proactive team player)"
匿名成员:Go语言的一个特性,只声明一个成员对应的数据类型而不指名成员的名字。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。
type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int } var w Wheel w.X = 8 // equivalent to w.Circle.Point.X = 8 w.Y = 8 // equivalent to w.Circle.Point.Y = 8 w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20
待续……