一、tips
- 必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。
1.1、关键字:25个
关键字不能用于自定义名字,只能在特定语法结构 中使用。
1.2、预定义名称30个,包括内建的常量、类型和函数
内建常量: 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
这些并不是关键字,可以在定义中重新使用它们。
在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱
若一个名字是在函数内部定义,那么它的就只在函数内部有效。
如果是在函数外部定义, 那么将在当前包的所有文件中都可以访问。
名字的开头字母的大小写决定了名字在包外的可 见性。
如果一个名字是大写字母开头的(必须是在函数外部定义的包级名字;包级函 数名本身也是包级名字),
那么它将是导出的,也就是说可以被外部的包访问,
例如fmt包的 Printf函数就是导出的,可以在fmt包外部访问。
包本身的名字一般总是用小写字母。
局部变量尽可能简短,名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义,一般驼峰式 命名
优先使用大小写 分隔,而不是优先用下划线分隔
二、声明
声明语句定义了程序的各种实体对象以及部分或全部的属性。
Go语言主要有四种类型的声明 语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明
const boilingF = 212.0
func main() {
var f = boilingF
var c = (f - 32) * 5 / 9
其中常量boilingF是在包一级范围声明语句声明的,然后f和c两个变量是在main函数内部声明 的声明语句声明的。
在包一级声明语句声明的名字可在整个包对应的每个源文件中访问,而 不是仅仅在其声明语句所在的源文件中访问。
相比之下,局部声明的名字就只能在函数内部 很小的范围被访问。
2.1 函数的声明
由 func 关键字、函数名、参数列表、返回值列表
以及包含在大括号里的函数体组成
2.2 变量声明
var 变量名字 类型 = 表达式
完整表达式 var s string = "abcd"
变量会在声明时直接初始化。
如果变量没有显 式初始化,则被隐式地赋予其类型的零值(zero value),
数值类型是0,字符串类型是空字 符串""
即:var s string ,使用默认初始化零值机制,被初始化为""
var s, sep string 等价 var s = "" ; var sep = ""
var s = 1 ;省略了类型定义,会被自动初始化为 int类型
2.2.1 一组变量声明
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
var f, err = os.Open(name) // os.Open returns a file and an error
2.3 := 短变量声明(short variable declaration)语句
i := 1
“:=”是一个变量声明语句,而“=”是一个变量赋值操作
只能用在函数内 部,而不能用于包变量
anim := gif.GIF{LoopCount: nframes}
freq := rand.Float64() * 3.0
t := 0.0
f, err := os.Open(name)
不要混淆多个变量的声明和元 组的多重赋值(§2.4.1),后者是将右边各个的表达式值赋值给左边对应位置的各个变量
i, j = j, i // 交换 i 和 j 的值
第一个语句声明了in和err两个变量。
第二个语句只声明了out一个变量, 然后对已经声明的err进行了赋值操作。
in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)
简短变量声明语句中必须至少要声明一个新的变量,下面的代码将不能编译通过:
f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables
2.3.1、var形 式的声明语句
var形 式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初 始值无关紧要的地方。
i := 100 // an int
var boiling float64 = 100 // a float64
var names []string
var err error
var p Point
和var形式声明语句一样,简短变量声明语句也可以用来声明和初始化一组变量:
i, j := 0, 1
2.4、自增语句
i += 1 等价 i = i + 1
2.5、赋值运算符号
注意,这是赋值语句,不是声明变量
s += sep + os.Args[i] 赋值语句;等价s = s + sep + os.Args[i]
2.6、指针
var x int”声明语句声明一个x变量
&x表达式(取x变量的内存地址)将产生一个 指向该整数变量的指针
指针对应的数据类型是 *int ,指针被称之为“指向int类型的指针”。
指针名字为p,那么可以说“p指针指向变量x”,或者说“p指针保存了x变量的内存地址”。
同时 *p 表达式对应p指针指向的变量的值。
一般 *p 表达式读取指针指向的变量的值,这里 为int类型的值,同时因为 *p 对应一个变量,所以该表达式也可以出现在赋值语句的左边,表 示更新指针所指向的变量的值。
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "1"
*p = 2 // equivalent to x = 2
fmt.Println(x) // "2"
对于聚合类型每个成员——比如结构体的每个字段、或者是数组的每个元素——也都是对应 一个变量,因此可以被取地址。
变量有时候被称为可寻址的值。即使变量由表达式临时生成,那么表达式也必须能接受 & 取 地址操作。
任何类型的指针的零值都是nil。如果p指向某个有效变量,那么 p != nil 测试为真。
指针之 间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
任何类型的指针的零值都是nil。如果p指向某个有效变量,那么 p != nil 测试为真。指针之 间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
2.6.1 new
用new创建变量和普通变量声明语句方式创建变量没有什么区别
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"
func newInt() *int {
new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为 别的类型。
func delta(old, new int) int { return new - old }
三、赋值
最简单的赋值语句是将要被赋值的变量放在=的左边,
新值的表达式放在=的右边。
x = 1
*p = true
person.name = "bob"
count[x] = count[x] * scale // 数组、slice或map的元素赋值
count[x] *= scale 等价以上
v := 1
v++ // 等价方式 v = v + 1;v 变成 2
v-- // 等价方式 v = v - 1;v 变成 1
3.1 元组赋值
x, y = y, x
a[i], a[j] = a[j], a[i] 交换两个变量的值
x, y = y, x%y
x, y := 0, 1
x, y = y, x+y
i, j, k = 2, 3, 5
左边变量的数目必须和右边一致。
f, err = os.Open("foo.txt") // function call returns two values
v, ok = m[key] // map lookup map查找
v, ok = x.(T) // type assertion 类型断言
v, ok = <-ch // channel receive 通道接收
map查找(§4.3)、类型断言(§7.10)或通道接收(§8.4.2)出现在赋值语句的右边 时,并不一定是产生两个结果,也可能只产生一个结果
v = m[key] // map查找,失败时返回零值
v = x.(T) // type断言,失败时panic异常
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败)
_, ok = m[key] // map返回2个值
_, ok = mm[""], false // map返回1个值
_ = mm[""] // map返回1个值
3.2、赋值
和变量声明一样,我们可以用下划线空白标识符 _ 来丢弃不需要的值。
_, err = io.Copy(dst, src) // 丢弃字节数
_, ok = x.(T) // 只检测类型,忽略具体值
可赋值性
medals := []string{"gold", "silver", "bronze"}
等价
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
map和chan的元素,虽然不是普通的变量,但是也有类似的隐式赋值行为
不管是隐式还是显式地赋值,在赋值语句左边的变量和右边最终的求到的值必须有相同的数
据类型。更直白地说,只有右边的值对于左边的变量是可赋值的,赋值语句才是允许的。
类型必须完全匹配,nil可以赋值给任何 指针或引用类型的变量。
常量则有更灵活的赋值规则,因为这样可以避免不必要的 显式的类型转换。
对于两个值是否可以用 == 或 != 进行相等比较的能力也和可赋值能力有关系:对于任何类型 的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之亦然。