GO语言基础篇(八)- 赋值操作

1,141 阅读5分钟

这是我参与8月更文挑战的第 8 天,活动详情查看: 8月更文挑战

赋值

赋值语句用来更新变量所指的值,它最简单的形式是由赋值符号=,以及符号左边的变量和右边的表达式组成

x = 1//有名称的变量
*p = true //间接变量
person.name = "bob" //结构体成员
count[x] = count[x] * scale //数组或slice或map的元素

每一个算数和二进制位操作符有一个对应的赋值操作符,比如上边最后那个语句可以重写成

count[x] *= scale

它避免了在表达式中重复变量本身。数字变量也可以通过++和—语句进行递增和递减

v := 1
v++ //等同于v = v+1
v-- //等同于v = v-1

多重赋值

另一种赋值方式是多重赋值,它允许几个变量一次性被赋值。当变量同时出现在赋值符两侧的时候,这种形式特别有用,例如,当交换两个变量的值时:

x, y = y, x
a[i], a[j] = a[j], a[i]

或者计算两个整数的最大公约数
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 = 1, 2, 3

但是如果表达式比较复杂,则应该避免使用这种多重赋值,一系列独立的语句更易读一些。比如有些类型的表达式可能会产生多个值(比如调用了返回多个值的函数)。当在一个赋值语句中使用这样的调用时,左边的变量个数需要和函数的返回值一样多

f, err = os.Open("foo.txt")//函数调用返回两个值

通常函数使用额外的返回值来指示一些错误情况,例如通过os.Open()返回的error类型,或者一个通常叫ok的bool类型变量

v, ok = m[key] //map查询
v, ok = x.(T)//类型断言
v, ok = <=ch//通道接收

像声明变量一样,可以将不需要的值赋给空标识符
_, err = io.Copy(dst, src)
_, ok  = x.(T)

可赋值性

赋值语句是显式的赋值,但是程序中很多地方的赋值是隐式的赋值,比如一个函数调用隐式的将参数的值赋给对应参数的变量;一个return语句隐式的将return操作数赋值给结果变量。复合类型的字面量表达式,比如slice

medals := []string{"gold", "silver", "bronze"}

隐式的给每一个元素赋值,它可以写成这样
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"

不管是隐式赋值还是显式赋值,如果左边的(变量)和右边的(值)类型声明相同,它就是合法的。赋值只有在值对于变量类型是可赋值的时候才合法

可赋值性根据类型不同,有着不同的规则。类型必须精准匹配,nil可以被赋值给任何接口变量或引用类型。常量有更灵活的可赋值性规则来规避显示的转换(后边会有文章整理这块)

两个值使用==和≠进行比较与可赋值性相关:任何比较中,第一个操作数对于第二个操作数的类型必须是可赋值的,或者可以反过来赋值

类型声明

变量或表达式的类型定义这些值应有的特性,比如大小(多少位或多少个元素等)、在内部如何表达、可以对其进行何种操作

任何程序中,都有一些变量使用相同的表示方式,但是含义相差非常大。比如,int类型可以用于表示循环的索引、时间戳、文件描述符或月份。float64类型可以表示速度或精确到几位小数的温度。string类型可以表示密码或颜色的名字

type声明一种新的命名类型它和某个已有的类型使用同样的底层类型。命名类型提供了一种方式来区分底层类型的不同或者不兼容使用,这样他们就不会在无意中混用

type name underlying-type

类型声明通常出现在包级别,此时该命名的类型在整个包中可见。如果希望可以跨包被使用,name的开头字母大写即可

下边的例子中,是把不同的计量单位的温度值,转换成不同类型

package tempconv

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,但是他们是不同的类型,所以他们不能使用算数表达式进行比较和合并

区分这些类型,可以防止无意间合并不同计量单位的温度值。从float64转换为Celsius或Fahrenheit需要显式类型转换。Celsius(t)、Fahrenheit(t)是类型转换,不是函数调用

对于每一个类型T,都有一个对应的类型转换操作T(x),将值x的类型转换成类型T。类型转换不改变类型值的表达式,仅改变类型

命名类型的底层类型决定了它的结构和表达式,以及它支持的内部操作集合,这些内部操作与直接使用底层类型的情况相同。也就是说,上边的Celsius和Fahrenheit类型,可以使用和float64相同的算数操作

fmt.Printf("%g\n", BoilingC - FreezingC) // 100
boilingF := CToF(BoilingC)
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // 180
fmt.Printf("%g\n", boilingF-FreezingC)//编译错误:类型不匹配

命名类型提供了概念上的便利,避免一次次重复的写复杂的类型。当底层类型是像float64这样的简单类型时,好处不明显,但是用在结构体类型上,优势就明显多了

参考

《Go程序设计语言》—-艾伦 A. A. 多诺万

《Go语言学习笔记》—-雨痕