赋值(Assignment)
变量的值可以通过赋值操作符 = 来更新, v = 10。
x = 1 // 具名变量x *p = true // 指针变量 person.name = "bob" // 结构体struct的字段 count[x] = count[x] * scale // 数组、切片或者map的某个元素
count[x] *= scale这样的缩略形式能省去不少重复的工作,同时数字变量还能通过++递增或者--递减:
v := 1 v++ // same as v = v + 1; v becomes 2 v-- // same as v = v - 1; v becomes 1 again
x, y = y, x a[i], a[j] = a[j], a[i]再比如计算两个数的最大公约数(GCD):
func gcd(x, y int) int { for y != 0 { x, y = y, x%y } return x }或者计算斐波那契数列第N个值:
func fib(n int) int { x, y := 0, 1 for i := 0; i < n; i++ { x, y = y, x+y } return x }
i, j, k = 2, 3, 5
但是如果表达式较为复杂时,应该尽量避免元组赋值,分开赋值的可读性会更好。
一些特定的表达式,例如函数调用有多个返回值,这种情况下 = 左边必须有对应数量的变量来进行赋值:
f, err = os.Open("foo.txt") // 函数调用有两个返回值
很多时候,函数会利用这种多返回值特性来额外返回一个值,这个值会说明函数的调用是否成功(可能是一个error类型变量err,或者bool类型变量ok)。在map中查找某个值,类型断言,从channel中接收值等等都会有两个返回值,其中第二个值就是一个bool类型:
v, ok = m[key] // map lookup v, ok = x.(T) // type assertion v, ok = <-ch // channel receive
Go语言还支持匿名变量 (用过函数式语言的读者应该了解这种机制),我们可以把不想要的值赋给空白标示符(=左边的变量数目和右边的值数目必须相同):
_, err = io.Copy(dst, src) // 只关心Copy的成功与否,不关心具体Copy的字节数,因此丢弃第一个值 _, ok = x.(T) // 只关心类型断言的成功与否,不关心x的具体值,因此丢弃第一个值
2.可赋值性
上面的赋值语句是一种显式赋值,但是某些情况下,会发生隐式赋值:在函数调用中,隐式赋值给函数参数;函数返回时,隐式赋值给return的操作数;还有类似下面的组合类型:
medals := []string{"gold", "silver", "bronze"}这里就是隐式赋值,等价的显式形式是这样的:
medals[0] = "gold" medals[1] = "silver" medals[2] = "bronze"
同样的还有map、channel类型等,都支持这种隐式赋值。
无论是用显式赋值或隐式赋值,只要 = 左右两边有相同的类型即可。
针对不同类型的具体赋值规则会在后续章节详细讲解。对于我们目前已经讨论过的那些类型,规则是很简单的:类型必须准确匹配(因此Go是强类型的静态语言),nil可以被赋值给interface或者其它引用类型。 常量(constant)赋值在类型转换时非常有灵活性,可以避免大多数显式类型转换:
const x = 112 var v float64 = x fmt.Println(v) //output:112两个变量能否用 == 或 != 比较取决于可赋值性,a == b 只有在a = b可行时才能判断。
类型声明
变量的类型定义了变量的一些个性化属性,例如变量占据的内存大小、在内存中的排列,变量内部的组织形式,变量支持的操作,变量的行为method等。
在实际项目中,很多自定义类型都有同样的底层类型,例如:int可以是循环的索引,时间戳,文件描述符或者一个月份;float64类型可以是车辆行驶速度,温度。使用type就可以声明具名类型,这样就可以在底层类型之上构建自己的需要的时间戳,文件描述符等类型。
type name underlying-type具名类型声明一般都发生在package级别,因此该类型是包内可见的,如果类型是导出的(首字母大写),那么就是全局可见的。为了解释类型声明,这里把不同的温度计量单位设置为不同的类型:
package tempconv import "fmt" type Celsius float64 type Fahrenheit float64 const ( AbsoluteZeroC Celsius = -273.15 FreezingC Celsius = 0 BoilingC Celsius = 100 ) func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }上面定义了两个类型Celsius(摄氏度)和Fahrenheit(华氏度),作为温度的两种不同单位。即使两个类型的底层类型是相同的float64,但是它们是完全不同的类型,所以它们彼此之间不能比较,也不能在算术表达式中结合使用,这种规则可以避免错误的使用两种不同的温度单位,因为两个温度单位的类型是不同的,CToF和FToC返回的两个值也是完全不同的。
对于每个类型T,都有一个对应的类型转换T(x),可以把x转换为T类型。当且仅当两个类型具有相同的底层类型时,才能进行类型转换,例如上文中的Celsius和Fahrenheit,或者两个类型都是指针类型,指向的是同一个底层类型。
数值类型之间、string和[]byte之间都可以进行转换,这些转换可能会改变值的表现形式。例如,将浮点数转化为整数会截取掉小树部分;将string转化为[]byte切片会分配内存空间创建string的一份拷贝(内存拷贝往往是性能瓶颈之一)。总之类型转换是编译期完成的,在运行期是不会失败的!
具名类型的底层类型决定了它的结构和表现形式,也决定了它支持的基本操作(可以理解为继承自底层类型),就好像直接使用底层类型一样。因此对于Celsius和Fahrenheit来说,float64支持的算术操作,它们都支持:
fmt.Printf("%g\n", BoilingC - FreezingC) // "100" °C boilingF := CToF(BoilingC) fmt.Printf("%g\n", boilingF - CToF(FreezingC)) // "180" °F fmt.Printf("%g\n", boilingF - FreezingC) // compile error: type mismatch再比如:
type temp int func main() { var x1 temp = 1 var x2 temp = 2 fmt.Println(x1 + x2)//output:3 }
如果两个值有同样的具名类型,那么就可以用比较操作符==和<进行比较;或者两个值,一个是具名类型,一个是具名类型的底层类型,也可以进行比较。但是两个不同的具名类型是不可以直接比较的:
var c Celsius var f Fahrenheit fmt.Println(c == 0) // "true" fmt.Println(f >= 0) // "true" fmt.Println(c == f) // compile error: type mismatch fmt.Println(c == Celsius(f)) // "true"!
我们都知道浮点数是不精确的表达形式,因此两个浮点数之间的比较是需要格外小心的。这里注意最后一个类型转换后浮点数之间的比较,Celsius(f)没有改变f的值,是因为c和f两个都是初始化为0,所以可以放心比较。
如果在项目中有些地方需要重复的去写一个复杂类型时,那么使用具名变量可以带来极大的便利。
我们还可以为具名类型定义特有的行为,这些行为就是Go语言中的类型方法(method),在后续章节我们还会详细讲解。
下面的代码定义了Celsuis类型的一个methond:String,
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
很多自定义类型都会定义一个String method,因为在调用fmt包的函数去打印该类型时,String会控制该类型的打印方式(这里其实是实现了Stringer这个接口,和其它OO语言不同,Go语言的接口实现是隐式的):
c := FToC(212.0) fmt.Println(c.String()) // "100°C" fmt.Printf("%v\n", c) // "100°C"; no need to call String explicitly fmt.Printf("%s\n", c) // "100°C" fmt.Println(c) // "100°C" fmt.Printf("%g\n", c) // "100"; does not call String fmt.Println(float64(c)) // "100"; does not call String
欢迎大家加入Go语言核心技术QQ群894864,里面热心大神很多哦!
有疑问加站长微信联系(非本文作者)