Go语言入门02——关键字、基本数据类型、变量与常量

86 阅读10分钟

写在前面:本文的知识点并非完全的顺序结构,一些代码可能涉及后面的知识。但不论如何,你只需要理解代码真正想表达的内容即可,不必过于深究~

关键字

下面列举的是Go语言中的25个关键字或保留字:

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

同时,Go语言还有36个预定义标识符:

Constants:    true  false  iota  nil

Types:        int  int8  int16  int32  int64  
              uint  uint8  uint16  uint32  uint64  uintptr
              float32  float64  complex128  complex64
              bool  byte  rune  string  error

Functions:    make  len  cap  new  append  copy  close  delete
              complex  real  imag
              panic  recover

基本数据类型

整型

整型分为以下两个大类: 按长度分为:int8、int16、int32、int64

对应的无符号整型:uint8、uint16、uint32、uint64

其中,uint8就是我们熟知的byte型(byte == 8bits),int16对应C语言中的short型,int32对应C语言中的int类型, int64对应C语言中的long型。

类型描述
uint8无符号 8位整型 (0 到 255)
uint16无符号 16位整型 (0 到 65535)
uint32无符号 32位整型 (0 到 4294967295)
uint64无符号 64位整型 (0 到 18446744073709551615)
int8有符号 8位整型 (-128 到 127)
int16有符号 16位整型 (-32768 到 32767)
int32有符号 32位整型 (-2147483648 到 2147483647)
int64有符号 64位整型 (-9223372036854775808 到 9223372036854775807)

特殊整型

类型描述
uint32位操作系统上就是uint32,64位操作系统上就是uint64
int32位操作系统上就是int32,64位操作系统上就是int64
uintptr无符号整型,用于存放一个指针

注意:

  • 在使用int和 uint类型时,不能假定它是32位或64位的整型,而是考虑intuint可能在不同平台上的差异。
  • 获取对象的长度的内建len()函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用int来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int和 uint

数字字面量语法(Number literals syntax)

Go1.13版本之后引入了数字字面量语法,以便于用二进制、八进制或十六进制浮点数的格式定义数字,例如:

v := 0b00101101, 代表二进制的 101101,相当于十进制的 45。 v := 0o377,代表八进制的 377,相当于十进制的 255。 v := 0x1p-2,代表十六进制的 1 除以 2²,也就是 0.25。

而且还允许我们用 _ 来分隔数字,比如说: v := 123_456 表示 v 的值等于 123456。

我们可以借助fmt函数来将一个整数以不同进制形式表示:

package main

import "fmt"

func main() {
   //十进制
   var a int = 10
   fmt.Printf("十进制: %d\n", a) // 10
   fmt.Printf("二进制: %b\n", a) // 1010

   //八进制
   var b int = 077
   fmt.Printf("八进制: %o\n", b) // 77

   //十六进制
   var c int = 0xff
   fmt.Printf("十六进制小写: %x\n", c) // ff
   fmt.Printf("十六进制大写: %X\n", c) // FF
}

浮点型

类型描述
float32IEEE-754标准 32位浮点型数,最大范围约为 3.4e38
float64IEEE-754标准 64位浮点型数,最大范围约为 1.8e308

这两种类型的最大值可以使用常量定义,分别是math.MaxFloat32math.MaxFloat64

和C语言类似,打印浮点数的时候可以使用%f来表示:

package main

import (
   "fmt"
   "math"
)

func main() {
   a, b := math.MaxFloat32, math.MaxFloat64
   fmt.Println(a, b)
   c := 3.1415926
   fmt.Printf("c = %f", c)
   fmt.Printf("c = %.3f,保留三位小数", c)
}

复数

类型描述
complex64可表示32 位实数和虚数
complex128可表示64 位实数和虚数

布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有truefalse两个值。

注意:

  1. 布尔类型变量的默认值为false
  2. Go 语言中不允许将整型强制转换为布尔型.
  3. 布尔型无法参与数值运算,也无法与其他类型进行转换。

字符串

Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为""中的内容,可以在Go语言的源码中直接添加非ASCII码字符。同时,关于字符串连接还有一个小trick:和Java类似,在fmt.Println中,我们可以用+来连接字符串:

package main

import "fmt"

func main() {
   s1 := "我不知天上宫阙"
   s2 := "今夕是何年何月"
   fmt.Println(s1 + " " + s2)
}

字符串转义符

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。

转义符含义
\r回车符(返回行首)
\n换行符(直接跳到下一行的同列位置)
\t制表符
\'单引号(不能直接打)
\"双引号(不能直接打)
\\反斜杠(不能直接打)

多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符:

package main

import "fmt"

func main() {
   s1 := `转朱阁
低绮户
照无眠
`
   fmt.Println(s1)
}

反引号间换行将被作为字符串中的换行,但所有的义字符都没有影响,文本将会原样输出。

字符串的常用操作

方法介绍
len(str)求长度
+或fmt.Sprintf拼接字符串
strings.Split分割
strings.contains判断是否包含
strings.HasPrefix,strings.HasSuffix前缀/后缀判断
strings.Index(),strings.LastIndex()子串出现的位置
strings.Join(a[]string, sep string)join操作

byte和rune类型

组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:

var a = '中'
var b = 'x'

Go 语言的字符有以下两种:

  1. uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
  2. rune类型,代表一个 UTF-8字符

当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32

Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。

// 遍历字符串
func traversalString() {
	s := "hello沙河"
	for i := 0; i < len(s); i++ { //byte
		fmt.Printf("%v(%c) ", s[i], s[i])
	}
	fmt.Println()
	for _, r := range s { //rune
		fmt.Printf("%v(%c) ", r, r)
	}
	fmt.Println()
}

输出:

104(h) 101(e) 108(l) 108(l) 111(o) 230(æ) 178(²) 153() 230(æ) 178(²) 179(³) 
104(h) 101(e) 108(l) 108(l) 111(o) 27801(沙) 27827(河) 

因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。

修改字符串

要修改字符串,需要先将其转换成[]rune[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

func changeString() {
	s1 := "big"
	// 强制类型转换
	byteS1 := []byte(s1)
	byteS1[0] = 'p'
	fmt.Println(string(byteS1))

	s2 := "白萝卜"
	runeS2 := []rune(s2)
	runeS2[0] = '红'
	fmt.Println(string(runeS2))
}

类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。

强制类型转换的基本语法如下:

T(表达式)

其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.

package main

import "fmt"

func main() {
   a := 3.1415926
   fmt.Println(a) // 3.1415926
   b := int(a)
   fmt.Println(b) // 3
}

变量

Go语言的变量名由数字、字母与下划线构成。注意:

  • 变量名不能以数字开头

  • 变量名不能是我们开头提到的关键字

  • 匿名变量: 你可能会遇见以“_”作为变量名的情况,“_”就是Go语言中的匿名变量(在Lua等语言中或被称为“哑元变量”)。它的特点如下:

    • 匿名变量可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算;
    • 匿名变量不占用命名空间,不会分配内存;
    • 匿名变量与匿名变量之间不会因为多次声明而无法使用;
    • 和人生一样,有些事情你并不需要最终的结果,只需要接住它然后丢掉——那么,使用“_”就是一个很好的选择。

变量声明与初始化

下面,我将用代码和注释来展示在Go语言中如何声明变量。

声明变量一般用var关键字:

var variable_name type

声明单个变量

package main

import "fmt"

// 第一种赋值方式:指定变量类型。若声明后不赋值,则使用该变量类型下的默认值
var a int = 7
var b bool //默认为false

// 第二种赋值方式:根据值自行判定变量类型
var c = "浮沉匆匆的一瞥"

func main() {
   /* 
   第三种赋值方式:省略关键字var,运用:=。
   注意 :=左边的变量不能是已经声明过的变量
   注意 这种不带声明格式的只能在函数体内出现,不能用来声明全局变量
   */
   d := 7.5

   fmt.Println(a, b, c, d)
}

关于第一行注释提到的“该变量类型下的默认值”: Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil

声明多个变量

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() {
   g, h := 123, "hello"
   fmt.Println(x, y, a, b, c, d, e, f, g, h)
}

值类型和引用类型

值类型

所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向已经存在内存中的值。比如var i = 10

image.png

而当我们用等号为另一个变量赋值的时候,比如j = i,我们实际上是在内存中对i的值进行了拷贝:

image.png

值类型的变量的值存储在栈中,你可以用&i获取变量i的内存地址(内存地址会因为机器、程序的不同而改变)。例如,在我的计算机上var i = 10i的地址为0xc000016098

引用类型

更复杂的数据通常会需要使用多个值,所以一般使用引用类型保存。

一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个值所在的位置。

这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个值中。

同一个引用类型的指针指向的多个值可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些值分散存放在内存中,每个值都指示了下一个值所在的内存地址。

当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。

如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

常量

相对于变量,常量是一个程序运行中不会被修改的恒定值,数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。 常量的声明与变量声明非常类似,只不过把var换成了const,而且声明的时候必须赋值

下面给出一些声明常量的示例:

package main

import "fmt"

func main() {
   const PI float32 = 3.1415
   const MONTH = 12
   const a, b, c = 1, false, "str" //多重赋值

   const (
      d = "GoPher"
      e = false
   )

   const (
      f = 7
      //声明多个常量时,如果省略了值,则默认与上面一行相同
      g
      h
   )

   fmt.Println(PI, MONTH, a, b, c, d, e, f, g, h)
}

iota

iota是go语言的常量计数器,可以认为是一个可以能够被编译器修改的常量,只能在常量表达式中使用。

iota在const出现时将被重置为0,之后const每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。当然也有例外,以下是一些示例:

package main

import "fmt"

func main() {
   const (
      n1 = iota //0
      n2        //1
      n3        //2
      n4        //3
   )

   // 使用_可以跳过一些值
   const (
      n5 = iota //0
      n6        //1
      _
      n7 //3
   )

   //可以中间插队
   const (
      n8  = iota //0
      n9  = 82   //82
      n10 = iota //2
      n11        //3
   )

   const n12 = iota //0

   // 0 1 2 3 0 1 3 0 82 2 3 0
   fmt.Println(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12)
}