GO语言学习2-关键字

关键字有25个

break
case
chan
const
continue
default
defer
else
fallthrough
for
func
go
goto
if
import
interface
map
package
range
return
select
struct
switch
type
var

大约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

四种类型的声明语句

var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。

变量

var 变量名字 类型 = 表达式
   其中“类型”或“= 表达式”两个部分可以省略其中的一个。

  • 如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。
  • 如果初始化表达式被省略,那么将用零值初始化该变量。
    • 数值类型变量对应的零值是0,
    • 布尔类型变量对应的零值是false,
    • 字符串类型对应的零值是空字符串,
    • 接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil,
    • 数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值

变量声明

var s string
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

简短变量声明

简短变量声明被广泛用于大部分的局部变量的声明和初始化。
i := 100 // an int
var boiling float64 = 100 // a float64
var names []string
var err error
var p Point

请记住“:=”是一个变量声明语句,而“=‘是一个变量赋值操作。
i, j := 0, 1
i, j = j, i // 交换 i 和 j 的值

f, err := os.Open(name)
简短变量声明左边的变量可能并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了(§2.7),那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了。

简短变量声明语句中必须至少要声明一个新的变量
 

指针

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 != nil测试为真,那么p是指向某个有效变量。

指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
 

在Go语言中,返回函数中局部变量的地址也是安全的。例如下面的代码,调用f函数时创建局部变量v,
在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量。
var p = f()
func f() *int {
v := 1
return &v
}
每次调用f函数都将返回不同的结果:
fmt.Println(f() == f()) // "false"
 

func incr(p *int) int {
*p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!!
return *p
} v
:= 1
incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3)
 

匿名变量:new函数

创建变量的方法是调用用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"

用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要声明一个临时变量的名字外,我们还可以在表达式中使用new(T)。
func newInt() *int {
return new(int)
}

func newInt() *int {
var dummy int
return &dummy
}
p := new(int)
q := new(int)
fmt.Println(p == q) // "false"


当然也可能有特殊情况:如果两个类型都是空的,也就是说类型的大小是0,例如struct{}和 [0]int,有可能有相同的地址(依赖具体的语言实现)(译注:请谨慎使用大小为0的类型,因为如果类型的大小位0好话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看runtime.SetFinalizer函数相关文档)。
 

由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。
func delta(old, new int) int { return new - old }
由于new被定义为int类型的变量名,因此在delta函数内部是无法使用内置的new函数的。
 

变量的生命周期

for t := 0.0; t < cycles*2*math.Pi; t += res {
    x := math.Sin(t)
    y := math.Sin(t*freq + phase)
    img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
        blackIndex)
}

函数的有右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。
for t := 0.0; t < cycles*2*math.Pi; t += res {
    x := math.Sin(t)
    y := math.Sin(t*freq + phase)
    img.SetColorIndex(
        size+int(x*size+0.5), size+int(y*size+0.5),
        blackIndex, // 最后插入的逗号不会导致编译错误,这是Go编译器的一个特性
    ) // 小括弧另起一行缩进,和大括弧的风格保存一致
}

在每次循环的开始会创建临时变量t,然后在每次循环迭代中创建临时变量x和y。
 

编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。
var global *int
func f() {
    var x int
    x = 1
    global = &x
}

func g() {
    y := new(int)
    *y = 1
}

f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。

相反,当g函数返回时,变量*y将是不可达的,也就是说可以马上被回收的。因此,*y并没有从函数g中逃逸,编译器可以选择在栈上分配*y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。

其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。
 

赋值

使用赋值语句可以更新一个变量的值,最简单的赋值语句是将要被赋值的变量放在=的左边
x = 1 // 命令变量的赋值
*p = true // 通过指针间接赋值
person.name = "bob" // 结构体字段赋值
count[x] = count[x] * scale // 数组、slice或map的元素赋值

特定的二元算术运算符和赋值语句的复合操作有一个简洁形式
count[x] *= scale

数值变量也可以支持++递增和--递减语句(译注:自增和自减是语句,而不是表达式,因此x = i++之类的表达式是错误的)
v := 1
v++ // 等价方式 v = v + 1;v 变成 2
v-- // 等价方式 v = v - 1;v 变成 1
 

元组赋值

元组赋值是另一种形式的赋值语句,它允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值。


交换两个变量的值
x, y = y, x
a[i], a[j] = a[j], a[i]
 

有些表达式会产生多个值,比如调用一个有多个返回值的函数。当这样一个函数调用出现在元组赋值右边的表达式中时(译注:右边不能再有其它表达式),左边变量的数目必须和右边一致。
f, err = os.Open("foo.txt") // function call returns two values
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive

译注:map查找(§4.3)、类型断言(§7.10)或通道接收(§8.4.2)出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于值产生一个结果的情形,map查找失败时会返回零值,类型断言失败时会发送运行时panic异常,通道接收失败时会返回零值(阻塞不算是失败)。例如下面的例子:
v = m[key] // map查找,失败时返回零值
v = x.(T) // type断言,失败时panic异常
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败)
_, ok = m[key] // map返回2个值
_, ok = mm[""], false // map返回1个值
_ = mm[""] // map返回1个值

_, err = io.Copy(dst, src) // 丢弃字节数
_, ok = x.(T) // 只检测类型,忽略具体值

和变量声明一样,我们可以用下划线空白标识符_来丢弃不需要的值。
 

可赋值性

medals := []string{"gold", "silver", "bronze"}
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"
不管是隐式还是显式地赋值,在赋值语句左边的变量和右边最终的求到的值必须有相同的数据类型。

更直白地说,只有右边的值对于左边的变量是可赋值的,赋值语句才是允许的。
 

类型

一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。
type 类型名字 底层类型
type Celsius float64 // 摄氏温度
type Fahrenheit float64 // 华氏温度
对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型(译注:如果T是指针类型,可能会需要用小括弧包装T,比如(*int)(0))。
 

 

range

range关键字是Go语言中一个非常有用的迭代array,slice,map, string, channel中元素的内置关键字。

range的使用非常简单,对于遍历array,*array,string它返回两个值分别是数据的索引和值,遍历map时返回的两个值分别是key和value,遍历channel时,则只有一个返回数据。各种类型的返回值参考下表:

range expression 1st Value 2nd Value(optional) notes
array[n]E,*[n]E indexint value E[i]  
slice []E indexint value E[i]  
string abcd indexint rune int 对于stringrange迭代的是Unicode而不是字节,所以返回的值是rune
map map[k]v keyk value v  
channel element none 遍历channel时,则只有一个返回数据

 

你可能感兴趣的:(go)