2.3 常量和变量
在Go语言中,可以将基本的数据类型分为两种:常量和变量。在程序执行过程中,其值不发生变化的数据类型被称为常量,其值可以变化的被称为变量。
2.3.1 常量
在Go语言中,常量和变量的区别如下:
- 常量的意志比较坚定,一旦定义一个值后就永远不会变。
- 变量的立场不坚定,在程序的运行过程中可能会发生变化。
在Go中使用关键字const定义常量,并且经常用大写字母表示常量名。例如,在下面的代码中,定义了一个名为“RICE”的常量:
const PRICE int = 10
通过上述代码,将常量“RICE”的值设置为整数10。并且在整个程序中,整数10这个值是一直固定不变的。
读者需要注意的是,Go语言和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。这样做的好处就是可以避免像C语言中那样含糊不清的声明形式,例如:int* a, b; 。其中只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。
实例2-1:打印输出某学生的考试成绩(源码路径:Go-codes\2\exam.go)
实例文件exam.go的具体实现代码如下所示。
package main
import "fmt"
func main() {
const SCORE int = 100
fmt.Println("你的期末考试成绩为:", SCORE)
}
在上述代码中,首先创建了整型常量SCOR并赋值为100,然后使用函数println()打印输出成绩。执行后会输出:
你的期末考试成绩为:100
在Go语言中有一个特殊常量:iota,这是一个可以被编译器修改的常量。iota在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
2.3.2 变量的声明
在Go语言中,将其值可以改变的量称为变量。一个变量应该有一个名字,并在内存中占据一定的存储单元。必须将变量定义放在变量被使用之前,一般放在函数体的开头部分。
注意:任何一种编程语言都离不开变量,特别是在数据处理程序中。我们甚至无法编写出没有变量参与的程序,即使能编写出来,这样的程序也没有意义。变量之所以如此重要,是因为变量是编程语言中数据的符号标识和载体。变量是内存或寄存器中用一个标识符命名的存储单元,可以用来存储一个特定类型的数据,并且数据的值在程序运行过程中可以进行修改。程序员一旦定义了变量,那么,变量至少可为我们提供两个信息:一是变量的地址,即操作系统为变量在内存中分配的若干内存的首地址;二是变量的值,也就是变量在内存中所分配的那些内存单元中所存放的数据。
在Go程序中,声明变量的语法格式如下。
var name type
- var:是声明变量的关键字;
- name:是变量名;
- type:是变量的类型。
当一个变量被声明之后,系统自动赋予它该类型的零值:int类型的初始值为 0,float类型的初始值为 0.0,bool类型的初始值为 false,string类型的初始值为空字符串,指针类型的初始值为 nil 等。所有的内存在 Go 中都是经过初始化的。
如果大家觉得觉得每行都用 var 声明变量比较烦琐,可以使用如下批量声明变量的方法:
var (
a int
b string
c []float32
d func() bool
e struct {
x int
}
)
在上述声明格式中,在Go程序中还可以使用如下更加简短的变量声明和初始化语法。
名字 := 表达式
需要注意的是,在使用上述简短模式(short variable declaration)有以下限制:
- 声明变量,同时显式初始化。
- 不能提供数据类型。
- 只能用在函数内部。
和使用关键字var声明变量的语句一样,简短变量声明语句也可以用来声明和初始化一组变量。例如:
i, j := 0, 1
下面通过一段代码来演示简短格式变量声明的基本样式。
func main() {
x:=100
a,s:=1, "abc"
}
在上述代码的第3行代码中,使用简短方式同时声明了变量a和s。
注意:因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
2.3.3 变量的初始化
在Go语言中声明变量时,会自动对变量对应的内存区域进行初始化操作。每个变量会初始化其类型的默认值,例如整型和浮点型变量的默认值为 0 和 0.0,字符串变量的默认值为空字符串,等等。当然,我们也可以在变量声明时给变量设置一个初始值。实现变量初始化的语法格式如下:
var 变量名 类型 = 表达式
请看下面的代码,hp 为变量名,类型为 int,hp 的初始值为 100。
var hp int = 100
在上面的代码中,100 和 int 同为 int 类型,int 可以认为是冗余信息,因此可以进一步简化初始化的写法。我们可以将 int省略:
var hp = 100
这样编译器会尝试根据等号右边的表达式推导 hp 变量的类型,等号右边的部分在编译原理里被称做右值(rvalue)。
实例2-2:根据右值推导变量类型完成初始化(源码路径:Go-codes\2\tui.go)
实例文件tui.go的具体实现代码如下所示。
package main
import "fmt"
func main(){
var attack = 40
var defence = 20
var damageRate float32 = 0.17
var damage = float32(attack-defence) * damageRate
fmt.Println(damage)
}
对上述代码的具体说明如下:
- 第 4和 5行:右值为整型,变量attack 和 defence的类型为 int。
- 第 6 行:表达式的右值中使用了 0.17。由于Go语言和C语言一样,编译器会尽量提高精确度,以避免计算中的精度损失。所以这里如果不指定变量 damageRate的类型,Go语言编译器会将 damageRate 类型推导为 float64,我们这里不需要 float64 的精度,所以需要强制指定类型为 float32。
- 第 7 行:将变量attack 和 defence 相减后的数值结果依然为整型,使用 float32() 将结果转换为 float32 类型,再与 float32 类型的 damageRate 相乘后,damage 类型也是 float32 类型。
- 第 8行:打印输出变量damage 的值。
注意:变量damage的右值是一个复杂的表达式,整个过程既有 attack 和 defence 的运算还有强制类型转换。强制类型转换会在后面的章节中介绍。
在Go语言中,还有一种更为精简的var 声明变量方式,例如:
hp := 100
这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型。此处需要注意,由于使用了“:=”而不是赋值的“=”,因此推导声明写法的左值变量必须是没有定义过的变量。如果定义过,将会发生编译错误。如果 hp 已经被声明过,但依然使用“:=”时编译器会报错,代码如下:
//声明 hp 变量
var hp int
//再次声明并赋值
hp := 10
此时编译报错如下:
no new variables on left side of :=
上述报错信息的含义是,在“:=”的左边没有新变量出现,意思就是“:=”的左边变量已经被声明了。
在实际的开发应用中,短变量声明格式比较常见,比如下面的代码:
conn, err := net.Dial("tcp","127.0.0.1:8080")
在上述代码中,net.Dial 提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(err)。如果是标准格式将会变成:
var conn net.Conn
var err error
conn, err = net.Dial("tcp", "127.0.0.1:8080")
因此,短变量声明并初始化的格式在开发中使用比较普遍。
注意:在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错,代码如下:
conn, err := net.Dial("tcp", "127.0.0.1:8080")
conn2, err := net.Dial("tcp", "127.0.0.1:8080")
上面的代码片段,编译器不会报重复定义错误。
实例2-3:计算圆的面积和周长(源码路径:Go-codes\2\area.go)
实例文件area.go的具体实现代码如下所示。
package main
import "fmt"
func main() {
//1:确定出n的取值
const PI = 3.14
//2:接收用户输入的半径
fmt.Println("请输入半径:")
var r float64
fmt.Scan(&r)
//3:计算圆的面积
area := PI * r * r
//4:i算圆的周长
p := 2 * PI * r
//5:输出结果
fmt.Printf("面积是%.2f\n", area)
fmt.Printf("周长是%.2f", p)
}
在上述代码中同时用到了变量和常量的声明和初始化操作,执行后先提示用户输入圆的半径,按下回车键后将打印输出对应的面积和周长:
请输入半径:
8
面积是200.96
周长是50.24
2.3.4 匿名变量
在Go语言中,匿名变量就是没有名字的变量;匿名变量不占用内存空间,不会分配内存,匿名变量与匿名变量之间也不会因为多次声明而无法使用。匿名变量的特点是一个下画线“_”,“_”本身就是一个特殊的标识符,可以像其他标识符那样用于变量的声明或赋值,但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
实例2-4:分别打印输出语文和数学成绩(源码路径:Go-codes\2\ni.go)
实例文件ni.go的具体实现代码如下所示。
package main
import "fmt"
func GetData() (int, int) {
return 100, 88
}
func main(){
a, _ := GetData() //只需要获取第一个返回值,所以将第二个返回值的变量设为下画线(匿名变量)
_, b := GetData() //将第一个返回值的变量设为匿名变量。
fmt.Println("你的语文和数学成绩分别为:",a,b)
}
在上述代码中,GetData()是一个函数,有关函数的详细知识将本书后面的内容中进行讲解。函数GetData() 是一个函数,拥有两个整型返回值,每次调用将会返回100和88这两个数值。执行后会输出:
你的语文和数学成绩分别为: 100 88