2
或者
3.14
或者
helloworld
.这样的值在Go中被称为常量。常量也可以通过根据常量构建的表达式来创建,例如
2+3
或者
2*3
或者
“go"+"lang"
等。
有些语言支持常量,而有些语言不支持常量,有些语言虽然支持常量,但是定义常量的方式不同,意义也不同。但是在Go中,常数只是一个简单而不变的值。
数字常量有很多种,整数,浮点数,符文,有符号,无符号,虚数,复数 - 因此我们从更简单的常量形式开始:字符串。字符串常量易于理解,并提供了一个较小的空间来探索 Go 中常量的类型问题.
字符串常量在双引号之间包含一些文本. (Go 也有原始字符串文字,用反引号 ````括起来,但出于讨论目的,它们具有相同的属性.) 这是一个字符串常量:
"Hello, World"
通常当我们看到这个字符串常量时,我们会异口同声说这是字符串类型,但是其实并不是。这是一个无类型的字符串常量,它是一个尚未具有固定类型的字符串常量。即使我们将它赋值一个常量变量,那么它也是一个无类型的字符串常量:
const hello1 = "hello world"
声明后,hello1
也是一个无类型的字符串常量。一个没有类型的常量只是一个值,尚未给定类型的常量将迫使它遵守严格的规则,以防止合并不同类型的值.
如果我们要获取一个带类型的常量,那么我们在声明变量的时候可以显示带上类型的名称:
const hello2 string = "hello world"
我们也可以自定义一个字符串类型:
type MyString string
让我们声明一个自定义类型的变量:
var a MyString
a = hello2 //会报错
a = hello1 //可以正常赋值
有人可能会觉得奇怪,为啥hello2
无法赋值给变量a
,但是hello1
却可以正常赋值,其实这就得回到我们一开始所说的。一个字符串常量在赋值给一个定义好类型的变量之前,本身是无类型字符串常量,也就是说此时它的类型是不确定的。我们在声明hello1
字符串常量变量时,并没有指定类型,因此它只是给字符串常量取了名,却未指定类型。回到我们的例子,由于hello1
依然是无类型字符串常量,所以它的类型只有在被赋值给类型为MyString
的那一刻才确定,而hello2
之前已经被确定为string类型
,因此此时它是string类型
,与MyString
为不同的类型,因此赋值会失败。
如果希望变量hello2也能正常赋值,那么我们需要进行显示的类型转换:
a = MyString(hello2)
因为不同于类型常量, 无类型常量没有类型. 将它们分配给与字符串兼容的任何类型的变量都可以正常工作.
var b int
b = hello1 //error
这些无类型的字符串常量当然是字符串,因此它们只能在允许使用字符串的地方使用,但它们是没有 类型的string.上面的例子会报错,因为常量字符串与整数无法兼容,所以虽然满足之前的条件,但是依然会产生编译错误。
我们在编程的过程中经常会用到两种变量赋值形式:
s := "hello,world"
var s = "hello, world"
我们在赋值时没有指定变量的具体类型,但是Go却能指定这些值的具体类型。这是为啥?其实虽然无类型字符串常量没有具体的类型,但是它们本身却带有默认类型。
当我们需要它们的具体类型时,会通过它们的默认类型来进行转换。所以字符串常量的默认类型为string,当我们通过短声明赋值或者类型推导来赋值时,go会将它们的默认类型转换为具体类型。
s := "hello,world"
var s = "hello, world"
var s string = "hello, world"
以上三种赋值形式是一样的。
思考无类型常量的一种方法是,它们生活在一种理想的值空间中,该空间的约束性比Go的完整类型系统要小。但是要对它们执行任何操作,我们需要将它们分配给变量,并且在这种情况下variable(不是常量本身) 需要一个类型,并且常量可以告诉变量应该具有什么类型。在此示例中,str成为类型为 string的值,因为未类型化的字符串常量为其声明提供了默认类型string.
考虑下面一个例子
fmt.Printf("%s", "hello world")
该函数的函数签名为:
func Printf(format string, a ...interface{
}) (n int, err error)
也就是说它的参数 (在格式字符串之后) 是接口值。当使用无类型常量调用 fmt.Printf 时会发生以下情况:创建接口值以作为参数传递,并且为该参数存储的具体类型是常量的默认类型。此过程类似于我们之前使用无类型的字符串常量声明初始化值时看到的过程.
您可以在本示例中看到结果,该示例使用格式%v打印值,并使用%T打印要传递给 fmt.Printf 函数的值的类型:
const name = "hello world"
const name2 string = "helo world"
type MyString string
func main() {
var a MyString
a = name
fmt.Printf("%T %v\n", name, name)
fmt.Printf("%T %v\n", name2, name2)
fmt.Printf("%T %v\n", a, a)
}
输出结果
string hello world
string helo world
main.MyString hello world
总而言之,类型化常量遵循Go中类型化值的所有规则。另一方面,未类型化的常量不会以相同的方式携带Go类型,并且可以更自由地混合和匹配。但是,它确实具有默认类型,该默认类型仅在没有其他类型信息可用时才公开.
无类型常量的默认类型也可以通过语法来确定。对于字符串常量,唯一可能的隐式类型是 string
. 对于 数字常量 , 隐式类型的种类更多。整数常量默认为int
, 浮点常量float64
, 符文常量rune(int32 的别名)
以及虚数常量到complex128
. 这是我们的规范打印语句,用于重复显示默认类型:
fmt.Printf("%T %v.", 0, 0)
fmt.Printf("%T %v.", 0.0, 0.0)
fmt.Printf("%T %v.", 'x', 'x')
fmt.Printf("%T %v.", 0i, 0i)
/*
输出
int 0
float64 0
int32 120
complex128 (0+0i)
*/
字符串常量的规则同样适用于布尔值常量。
值 true
和 false
是无类型的布尔常量,可以分配给任何布尔变量,但是一旦指定类型,就不能将布尔变量混合使用:
type MyBool bool
const b = true
var b1 MyBool = b //正常赋值
var b2 MyBool = true //正常赋值
const b bool = true
var b3 MyBool = b //无法正常赋值
大部分情况下,浮点数常量和布尔常量以及字符串常量的规则差不多。但是由于浮点数还需要划分为两种不同的类型float32
和float64
类型,浮点常量的默认类型为float64
.
type MyFloat64 float64
const Zero = 0.0
const TypedZero float64 = 0.0
var mf MyFloat64
mf = 0.0 // 这样可以
mf = Zero // 这样也可以
mf = TypedZero // 这样就不行了
fmt.Println(mf)
var f32 float32
f32 = 0.0
f32 = Zero // 这样是可以的: Zero 是无类型的
f32 = TypedZero // 这就不行了: TypedZero 类型是 float64 而不是 float32.
fmt.Println(f32)
数字常数存在于任意精度的数字空间中。它们只是常规数字。但是,当将它们分配给变量时,该值必须能够适合目标。我们可以声明一个非常大的常量:
const Huge = 1e1000 //error
//constant 1e+1000 overflows float64
因为这里的常量值以及超过了float64
最大值,因此无法编译成功。但是我们可以将这个值与其他的常量值进行计算来获取新的值:
fmt.Println(Huge / 1e999) // 10
在go的math包中提供了Pi常量值:
Pi = 3.14159265358979323846264338327950288419716939937510582097494459
当该值分配给变量时,某些精度将丢失。分配将创建最接近于高精度值的 float64
(或 float32
) 值:
fmt.Println(math.Pi) // 3.141592653589793
我们发现打印出来的值与实际值相比少了后面的很多位,这是因为当我们把它传入函数时,也进行了一次赋值操作,函数会定义形参,因此它的精度接近于float64。
var pi = math.Pi
fmt.Println(reflect.ValueOf(pi).Kind()) // float64
复数的行为很像浮点数
type MyComplex128 complex128
const I = (0.0 + 1.0i)
const TypedI complex128 = (0.0 + 1.0i)
var mc MyComplex128
mc = (0.0 + 1.0i) // 这样可以
mc = I // 这样也可以
mc = TypedI // 这样就不行了
fmt.Println(mc)
复数的默认类型为 complex128, 它是由两个 float64 值组成的较大精度版本.
为了使示例清晰易懂,我们写出了完整的表达式 (0.0+1.0i), 但是此值可以缩短为 0.0+1.0i, 1.0i 甚至是 1i.
我们知道,在 Go 中数字常数只是一个数字。如果该数字是没有虚部的复数,即实数,该怎么办?代码如下:
```go
const Two = 2.0 + 0i
```
这是一个无类型的复数常量。即使没有虚部,表达式的 语法 仍将其定义为默认类型 complex128
. 因此,如果使用它声明变量,则默认类型为 complex128
. 代码片段如下
s := Two
fmt.Printf("%T: %v.", s, s)
会打印输出 complex128 (2+0i). 但是从数字上讲,可以将 Two 存储在标量浮点数中,即 float64 或 float32, 而不会丢失信息。因此,我们可以在初始化或分配中将 Two 分配给 float64, 而不会出现问题:
var f float64
var g float64 = Two
f = Two
fmt.Println(f, "and", g)
输出为 2
和 2
. 即使 Two
是一个复数常量,也可以将其分配给标量浮点变量。这样的常数可以 “交叉” 类型的能力将被证明是有用的.
整数依然遵照上面的规则,但是整数的话分为有符号整数和无符号整数两种大类,然后可以继续划分为unit8, int8等.
type MyInt int
const Three = 3
const TypedThree int = 3
var mi MyInt
mi = 3 // 这样可以
mi = Three // 这样也可以
mi = TypedThree // 这样就不行了
fmt.Println(mi)
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
uintptr
uint8 的别名 byte
int32 的别名 rune
这是初始化 uint
的几种不同方法;所有都是等效的,但是所有都必须明确提及类型,以使结果不带符号.
var u uint = 17
var u = uint(17)
u := uint(17)
与浮点值部分中提到的范围问题类似,并非所有整数值都可以适合所有整数类型。可能会出现两个问题:该值可能太大,或者可能是分配给无符号整数类型的负值。例如,int8 的范围是 -128 到 127, 因此永远不能将超出该范围的常量分配给 int8 类型的变量:
var i8 int8 = 128 // 错误: too large.
类似地,uint8
也称为 byte
, 范围为 0 到 255, 因此无法将大或负常量分配给 uint8
:
var u8 uint8 = -1 // 错误: negative value.
这种类型检查可以发现错误
type Char byte
var c Char = '世' // 错误: '世' has value 0x4e16, too large.
如果编译器抱怨您使用常量,则可能是真正的错误.
const MaxUint uint = -1 // 错误: negative value
但这是非法的,因为 -1 不能用无符号变量表示;-1
不在无符号值的范围内。出于同样的原因,转换也无济于事:
const MaxUint uint = uint(-1) // 错误: negative value
var u uint
var v = -1
u = uint(v)
以上的例子不会报错,但这仅仅是因为 v
是一个变量;如果我们将 v
设为一个常数,甚至是一个无类型的常数,我们都会回到禁区:
var u uint
const v = -1
u = uint(v) // 错误: negative value
Go 中无类型常量的概念意味着所有数字常量,无论是整数,浮点数,复数,甚至是字符值,都生活在一种统一的空间中。当我们将它们带入变量,赋值和运算的计算世界时,实际类型才有意义。但是只要我们停留在数字常数的世界中,我们就可以随意混合和匹配值。所有这些常量的数值为 1:
1
1.000
1e3-99.0*10-9
'.01'
'.0001'
'b' - 'a
1.0+3i-3.0i
因此,尽管它们具有不同的隐式默认类型,但以无类型常量的形式编写,但可以将它们分配给任何整数类型的变量:
var f float32 = 1
var i int = 1.000
var u uint32 = 1e3 - 99.0*10.0 - 9
var c float64 = '.01'
var p uintptr = '.0001'
var r complex64 = 'b' - 'a'
var b byte = 1.0 + 3i - 3.0i
fmt.Println(f, i, u, c, p, r, b)
甚至可以这样写:
var f = 'a' * 1.5
fmt.Println(f)
结果是 145.5, 除了证明一点之外,没什么实际意义.
但是,这些规则的真正意义在于灵活性。这种灵活性意味着,尽管在 Go 中在同一表达式中混合使用浮点数和整数变量甚至 int 和 int32 变量都是非法的,但对于写:
sqrt2 := math.Sqrt(2)
或者
const millisecond = time.Second/1e3
或者
bigBufferWithHeader := make([]byte, 512+1e6)
都可以获得符合您期望的结果.
因为在 Go 中,数字常量可以按您期望的那样工作:就像数字一样.