三:数据类型与基础语法

3.1 什么是变量

变量(Variable)是给某个内存地址起的一个名字。我们用变量来存储某个特定类型的值。在 Go 中有多种声明变量的语法。

声明单一变量

声明一个变量的语法为:var name type,例如:

 package main
 import "fmt"
 func main() {
  var age int // variable declaration
  fmt.Println("my age is", age)
 }

语句 var age int 声明了一个类型为 int,名称为 age 的变量。在这里我们没有给它赋任何值。如果一个变量没有被赋予任何值,Go 会自动将这个变量初始化为其类型的 0值,比如这里的 age 将被赋值为 0(译者注:int的0值为0)。运行这个程序,将得到如下输出:

 my age is 0

一个变量可以被赋予其类型的任何值。例如,在上例中,age 可以被赋予任何整型值:

package main
 
import "fmt"
 
func main() {
 var age int // variable declaration
 fmt.Println("my age is ", age)
 age = 29 //assignment
 fmt.Println("my age is", age)
 age = 54 //assignment
 fmt.Println("my new age is", age)
 
 fmt.Printf("%d,%d", age, age)
}
 
声明一个带初值的变量
 在声明变量时可以指定其初始值。
 声明一个带初值的变量的语法为:
var name type = initialvalue,例如:
package main
import "fmt"
func main() {
 var age int = 29 // variable declaration with initial value
 fmt.Println("my age is", age)
}

类型推导

如果声明一个变量时提供了初始值,Go可以根据该初始值来自动推导变量的类型。因此如果声明变量时提供了初始值,就可以不必指定其类型。也就是说,如果声明变量的形式为:var name = initialvalue,Go将根据 initialvalue 自动推导变量 name 的类型。

在下面的例子中,你可以看到声明变量 age 时并没有指定其类型。因为 age 的初值为 29,Go 自动推断其类型为 int。

package main
import "fmt"
func main() {  
    var age = 29 // type will be inferred
    fmt.Println("my age is", age)
}

多变量声明

多个变量可以在一条语句中声明。

多变量声明的语法为:var name1, name2 type = initialvalue1, initialvalue2,例如:

package main
 
import "fmt"
 
func main() {
 var width, height int = 100, 80
 fmt.Println("width is", width, "height is", height)
 
}

如果指定了初始值,则 type 可以省略。

如果不指定 width 和 height 的初值,它们将自动被赋值为 0,也就是说它们将以 0 作为初值。

多不同类型变量声明

有些时候我们需要在一条语句中声明多个不同类型的变量。我们可以使用下面的语法达到此目的:

var (  
      name1 = initialvalue1,
      name2 = initialvalue2
)

如下代码所示:

 var (
  name string = "lisi"
  age  int    = 30
 )
 name = "zhangsan"
 fmt.Println("age is", age, "name is", name)

速记声明

Go 提供了另一种简洁的声明变量的方式。这种方式称为速记声明(shorthand declaratiion)。速记声明使用 := 操作符来声明变量。

速记声明的语法为:name := initialvalue,例如:

 name, age := "naveen", 30 //short hand declaration
 fmt.Println("my name is", name, "age is", age)

速记声明要求在 := 的左边必须至少有一个变量是新声明的。考虑如下程序:

package main
import “fmt"
var x, y int
var (  // 这种因式分解关键字的写法一般用于声明全局变量
    a int
    b bool
)
var c, d int = 1, 2
var e, f = 123, "hello"

//这种不带声明格式的只能在函数体中出现

//g, h := 123, "hello"
func main() {
 a, b := 20, 30 // declare variables a and b
 fmt.Println("a is", a, "b is", b)
 b, c := 40, 50 // b is already declared but c is new
 fmt.Println("b is", b, "c is", c)
 b, c = 80, 90 // assign new values to already declared variables b and c
 fmt.Println("changed b is", b, "c is", c)
}

速记声明只能用在函数中。不能作用于全局变量。

一个变量不能被赋予与其类型不同的值。

全局变量是允许声明但不使用。函数内部变量声明不使用会报错。

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

3.2 数据类型

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

下面是 Go 支持的基本类型:

bool
Numeric Types 
 int8, int16, int32, int64, int
 uint8,uint16,uin32,uint64, uint
 float32, float64
 complex64, complex128
 byte
 rune
string

派生类型:

 (a) 指针类型(Pointer)
 (b) 数组类型
 (c) 结构化类型(struct)
 (d) Channel 类型
 (e) 函数类型
 (f) 切片类型
 (g) 接口类型(interface)
 (h) Map 类型

下面我们依次介绍每一种类型:

bool

布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

package main
import "fmt"
func main() {
 var a bool = true
 b := false
 fmt.Println("a:", a, "b:", b)
 c := a && b
 fmt.Println("c:", c)
 d := a || b
 fmt.Println("d:", d)
}

这里 a 被赋值为 true,b 被赋值为 false。

c 被赋值为 a && b。与操作符(&&)仅在 a 与 b 都为 true 时才返回 true,因此在这里 c 被赋值为 false。

或操作符(||)在 a 与 b 中至少有一个为 true 时返回 true。在这里因为 a 为 true,因此 d 也被赋值为 true。

有符号整型

int8:表示8位有符号整数 
size:8 bits 
range:-128 ~ 127
 
int16:表示16位有符号整数 
size:16 bits 
range:-32768 ~ 32767
 
int32: 表示32位有符号整数 
size: 32 bits 
range: -2147483648 ~ 2147483647
 
int64: 表示64位有符号整数 
size: 64 bits 
range: -9223372036854775808 ~ 9223372036854775807
 
int: 根据底层平台(underlying platform)不同,表示32或64位整数。在实际编程中,除非对大小有明确的要求,否则一般应该使用 int 表示整数。 
size: 在32位系统下 32 bits,在64位系统下 64 bits 
range: 在32位系统下 -2147483648 ~ 2147483647,在64位系统下 -9223372036854775808 ~ 9223372036854775807

变量的类型可以在 Printf() 函数中通过 %T 格式化指示符(format specifier)来打印。Go的 unsafe 包中提供了一个名为 Sizeof 的方法,该方法接收一个变量并返回它的大小(byte数)。因为使用 unsafe 包可能会带来移植性问题,因此我们需要小心地使用它,但就本教程的目的而言,我们可以使用它。

下面的程序打印变量 a 和变量 b 的类型和大小。使用 %T格式化指示符打印类型,使用 %d 格式化指示符打印大小。

package main
 
import "fmt"
import "unsafe"
func main() {
 var a int = 89
 b := 95
 fmt.Println("value of is", a, "and b is", b)
 fmt.Printf("type of a is %T size of a is %d", a, unsafe.Sizeof(a)) //type and size of a
 fmt.Printf("\ntype of b is %T size of b is %d", b, unsafe.Sizeof(b))
}
输出结果为: 
value of is 89 and b is 95
type of a is int size of a is 8
type of b is int size of b is 8

从上面的输出我们可以推断 a 和 b 是 int 类型,它们的大小为 64 bits (8bytes)。如果在32位系统上运行上面的程序那么输出会变得不同。在32位系统上,a 和 b 占 32 bits(4bytes)。

无符号整型

uint8: 表示8位无符号整型 
size: 8 bits 
range: 0 ~ 255
 
uint16: 表示16位无符号整型 
size: 16 bits 
range: 0 ~ 65535
 
uint32: 表示32位无符号整型 
size: 32 bits 
range: 0 ~ 4294967295
 
uint64: 表示64位无符号整型 
size: 64 bits 
range: 0 ~ 18446744073709551615
 
uint : 根据底层平台不同表示32或64位无符号整型 
size : 32位系统下是32 bits,64位系统下64 bits 
range :32位系统下 0 ~ 4294967295,64位系统下 0 ~ 18446744073709551615

浮点类型

 float32:32位浮点型
 float64:64位浮点型

下面的程序演示了整型和浮点数类型:

 a, b := 5.67, 8.97
 fmt.Printf("type of a %T b %T\n", a, b)
 sum := a + b
 diff := a - b
 fmt.Println("sum", sum, "diff", diff)
 
 no1, no2 := 56, 89
 fmt.Println("sum", no1+no2, "diff", no1-no2)

复数类型

 complex64:实部和虚部都是 float32 
 complex128:实部和虚部都是 float64

通过内置函数 complex 来构造一个包含实部和虚部的复数。它的原型为:

 func complex(r, i FloatType) ComplexType1

它接收一个实部和一个虚部为参数并返回一个复数类型。实部和虚部应该为同一类型(float32 或 float64)。如果实部和虚部都是 float32,该函数返回一个类型为 complex64 的复数。如果实部和虚部都是 float64,该函数返回一个类型为 complex128 的复数。

复数也可以通过简短声明语法来创建:

 com := complex(3, 5)
 
 c1 := complex(5, 7)
 c2 := 8 + 27i
 cadd := c1 + c2
 fmt.Println("sum:", cadd)
 cmul := c1 * c2
 fmt.Println("product:", cmul)

其他数字类型

byte 是 uint8 的别称
rune 是 int32 的别称
 var b byte = 0xff
 fmt.Println(b)
 
 var r rune = 30
 fmt.Println(r)
 

字符串类型

在Go中字符串(String)是 byte 的集合。如果你觉得这个定义没有任何意义也没关系。我们可以暂且假定一个字符串就是一串字符的集合。

之后我们会单独讲解字符串类型。

让我们写一个程序来了解字符串:

 first := "Naveen"
 last := "Rocus"
 name := first + " " + last
 fmt.Println("My name is", name)

类型转换

Go是强类型的语言,没有隐式的类型提升和转换。让我们通过一个例子说明:

package main
 
import (  
    "fmt"
)
 
func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum := i + j //int + float64 not allowed
    fmt.Println(sum)
}

为了修复这个错误,我们应该将 i 和 j 转换为同样的类型,在这里让我们将 j 转换为 int。通过 T(v)可以将 v 的值转换为 T 类型 。

package main
import (  
    "fmt"
)
func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum := i + int(j) //j is converted to int
    fmt.Println(sum)
}

在赋值时情况也是如此,将一个变量赋值给另一个类型不同的变量时必须显式转型.

3.3 常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

 const identifier [type] = value
 const PI float32 = 3.14
 fmt.Print(PI)

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

显式类型定义: const b string = "abc"

隐式类型定义: const b = “abc”’

关键字 const 修饰的名字为常量,不能被重新赋予任何值。

常量的值必须在编译期间确定。因此不能将函数的返回值赋给常量,因为函数调用发生在运行期。

func main() {
 var a = math.Sqrt(4) //allowed
 const b = math.Sqrt(4) //not allowed
 fmt.Println(a, b)
}

const的值必须在编译期间确定,所以这段代码编译不通过。

字符串常量

字符串常量最简单的常量,通过了解字符串常量可以更好的理解常量的概念。在Go中任何用双引号(")括起来的值都是字符串常量,比如 "Hello World","Sam" 都是字符串常量。字符串常量是什么类型呢?答案是 无类型(untyped)。

像 "Hello World" 这样的字符串没有任何类型。

有没有办法创建一个有类型(typed)的常量?下面的代码创建了一个有类型常量。

const typedhello string = "Hello World" 

上面的代码中, typedhello 是一个字符串类型的常量。

Go是强类型语言。在赋值时混合使用类型是不允许的。让我们通过以下代码说明这是什么意思。

func main() {
 var defaultName = "Sam" //allowed
 type myString string
 var customName myString = "Sam" //allowed
 customName = defaultName        //not allowed
}

在上面的代码中,我们首先创建了一个变量 defaultName 并且赋值为常量 "Sam"。常量 "Sam" 的默认类型为 string,因此赋值之后,defaultName 的类型亦为 string。

下一行我们创建了一个新的类型 myString,它是 string 的别名。

(可以通过 type NewType Type 的语法来创建一个新的类型。类似 C 语言的 typedef 。)

接着我们创建了一个名为 customName 的 myString 类型的变量,并将常量 "Sam" 赋给它。因为常量 "Sam" 是无类型的所以可以将它赋值给任何字符串变量。因此这个赋值是合法的,customName 的类型是 myString。

现在我们有两个变量:string 类型的 defaultName 和 myString 类型的 customName。尽管我们知道 myString 是 string 的一个别名,但是Go的强类型机制不允许将一个类型的变量赋值给另一个类型的变量。因此, customName = defaultName 这个赋值是不允许的,编译器会报错:main.go:10: cannot use defaultName (type string) as type myString in assignment

布尔常量

布尔常量与字符串常量(在概念上)没有区别。布尔常量只包含两个值:true 和 false

数值常量

数值常量(Numeric constants)包括整数,浮点数以及复数常量。数值常量有一些微妙之处。

package main
 
import (
 "fmt"
)
 
func main() {
 fmt.Println("Hello, playground")
 const a = 5
 var intVar int = a
 var int32Var int32 = a
 var float64Var float64 = a
 var complex64Var complex64 = a
 fmt.Println("intVar", intVar, "\nint32Var", int32Var, "\nfloat64Var", float64Var, "\ncomplex64Var", complex64Var)
}

在这个程序中,a 的值是 5 并且 a 在语法上是泛化的(它既可以表示浮点数 5.0,也可以表示整数 5,甚至可以表示没有虚部的复数 5 + 0i),因此 a 可以赋值给任何与之类型兼容的变量。像 a 这种数值常量的默认类型可以想象成是通过上下文动态生成的。var intVar int = a 要求 a 是一个 int,那么 a 就变成一个 int 常量。var complex64Var complex64 = a 要求 a 是一个 complex64,那么 a 就变成一个 complex64 常量。这很容易理解)

数值表达式

数值常量可以在表达式中自由的混合和匹配,仅当将它们赋值给变量或者在代码中明确需要类型的时候,才需要他们的类型。

package main
import (  
    "fmt"
)
func main() {  
    var a = 5.9/8
    fmt.Printf("a's type %T value %v",a, a)
}

在上面的程序中,语法上 5.9 是一个浮点数,8 是一个整数。因为它们都是数值常量,因此 5.9/8 是合法的。相除的结果为 0.7375,是一个浮点数。因此变量 a 的类型为浮点型。上面的程序输出为:a's type float64 value 0.7375。

常量高级知识点:

常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:

package main
 
import "unsafe"
const (
    a = "abc"
    b = len(a)
    c = unsafe.Sizeof(a)
)
 
func main(){
    println(a, b, c)
}

字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。

iota
 iota,特殊常量,可以认为是一个可以被编译器修改的常量。

在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。

iota 可以被用作枚举值:

const (
 a = iota
 b = iota
 c = iota
)
 fmt.Println(a, b, c)
 第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
    a = iota
    b
    c
)
 
 
下面的输出结果为:
const (
  a = 0
  b = 4
  c = iota
  d
 )
 fmt.Println(a, b, c, d)

分析下面的例子,观察输出结果:

package main
import "fmt"
func main() {
 const (
  a = iota //0
  b        //1
  c        //2
  d = "ha" //独立值,iota += 1
  e        //"ha"   iota += 1
  f = 100  //iota +=1
  g        //100  iota +=1
  h = iota //7,恢复计数
  i        //8
 )
 fmt.Println(a, b, c, d, e, f, g, h, i)
}

以上实例运行结果为:

0 1 2 ha ha 100 100 7 8