[TOC]
变量声明(使用var关键字)
变量(Variable)的功能是存储用户的数据,不同的逻辑有不同的对象类型,也就有不同的变量类型
Go语言使用var关键字进行变量的声明
1 | var a int |
- 第一行,声明一个整形类型的变量,用来保存整数数值
- 第二行,声明一个字符串类型的变量
- 第三行,声明一个32位浮点切片类型的变量,浮点切片表示由多个浮点类型组成的数据结构
- 第四行,声明一个返回值为bool类型的函数变量,这种形式一般用于回调函数,即将函数以变量的形式保存下来,在需要的时候重新调用这个函数
- 声明一个结构体类型的变量,这个结构体拥有一个整形的x字段
标准格式声明
1 | var 变量名 变量类型 |
变量的声明是以var关键字开头,要声明的变量名放中间,将其类型放在后面,行尾无需分号
批量格式声明
1 | var ( |
使用var和括号,可以将一组变量定义放在一起
变量初始化
Go语言在声明变量时,自动对变量对应的内存区域进行初始化操作。每个变量会初始化其类型的默认值,例如:
- 整型和浮点型变量的默认值为 0
- 字符串变量的默认值为空字符串
- 布尔型变量默认为 bool
- 切片、函数、指针变量的默认为 nil
在声明变量的时候也可以进行赋初始值
变量初始化标准格式
1 | var 变量名 类型 = 表达式 |
编译器推导类型
在标准格式的基础上,可以省略部分变量类型,编译器会尝试根据等号右边的表达式推导变量的类型
1 | var name = "zhimma" |
等号右边的部分在编译原理里被称做右值(rvalue)
短变量声明并初始化
var 的变量声明还有一种精简写法
1 | name := "zhimma" |
左值变量必须是没有定义过的变量
在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错,代码如下:
1 | conn, err := net.Dial("tcp", "127.0.0.1:8080") |
多个变量同时赋值
1 | var a int = 100 |
多重赋值时,变量的左值和右值按从左到右的顺序赋值
多重赋值在 Go 语言的错误处理和函数返回值中会大量地使用。
匿名变量
在使用多重赋值时,如果不需要在左值中接收变量,可以使用匿名变量(anonymous variable)
匿名变量的表现是一个下划线_
,使用匿名变量时,只需要在变量声明的地方使用下画线替换即可
1 | func GetData() (int, int) { |
匿名变量不占用命名空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。
匿名变量:
- 可以理解为一种占位符。
- 本身这种变量不会进行空间分配,也不会占用一个变量的名字。
- 在
for range
可以对 key 使用匿名变量,也可以对 value 使用匿名变量。
Go语言类型
Go语言 中有丰富的数据类型,除了基本的整型、浮点型、布尔型、字符串外,还有切片、结构体、函数、map、通道(channel)等。Go 语言的基本类型和其他语言大同小异,切片类型有着指针的便利性,但比指针更为安全,很多高级语言都配有切片进行安全和高效率的内存操作。
整数类型
整型分为以下两个大类:
- 按长度分为:int8、int16、int32、int64
- 还有对应的无符号整型:uint8、uint16、uint32、uint64
其中,uint8 就是我们熟知的 byte 型,int16 对应C语言中的 short 型,int64 对应C语言中的 long 型。
浮点类型(小数类型)
Go语言
支持两种浮点型数:float32 和 float64。这两种浮点型数据格式遵循 IEEE 754 标准:
- float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32。
- float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64。
打印浮点数时,可以使用 fmt 包配合动词%f
,代码如下:
1 | package main |
bool类型
布尔型数据只有 true(真)和 false(假)两个值,布尔型无法参与数值运算,也无法与其他类型进行转换
字符串
字符串在Go语言中以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。
字符串转义符
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
转移符 | 含 义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
' | 单引号 |
" | 双引号 |
\ | 反斜杠 |
定义多行字符串
在源码中,将字符串的值以双引号书写的方式是字符串的常见表达方式,被称为字符串字面量(string literal)
这种双引号字面量不能跨行。如果需要在源码中嵌入一个多行字符串时,就必须使用**`**字符,代码如下:
1 | const str = `第一行 |
在**`**间的所有代码均不会被编译器识别,而只是作为字符串的一部分
字符类型(byte和rune)
字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符
Go语言的字符有以下两种:
- uint8类型,也叫byte型,代表了ASCLL码中的一个字符
- rune类型,代表一个UTF-8字符。当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型实际是一个 int32。
使用fmt.Printf
中的%T
动词可以输出、变量的实际类型,使用这个方法可以查看 byte 和 rune 的本来类型
1 | var a byte = 'a' |
可以发现,byte 类型的 a 变量,实际类型是 uint8,其值为 ‘a’,对应的 ASCII 编码为 97
rune 类型的 b 变量的实际类型是 int32,对应的 Unicode 码就是 20320
Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。
Go数据类型转换
Go语言使用类型前置加括号的方式进行数据类型转换,一般格式如下:
1 | T(表达式) |
T代表要转换的类型。表达式包括变量、复杂算子和函数返回值等
类型转换时,需要考虑两种类型的关系和范围,是否会发生数值截断等
常量
相对于变量,常量是恒定不变的值,例如圆周率。
可以在编译时,对常量表达式进行计算求值,并在运行期使用该计算结果,计算结果无法被修改。常量表示起来非常简单,如下面的代码:
1 | const pi = 4.14159 |
常量的声明和变量声明非常类似,只是把 var 换成了 const。
多个变量可以一起声明,类似的,常量也是可以多个一起声明的,如下面的代码:
1 | const ( |
常量因为在编译期确定,所以可以用于数组声明,如下面的代码:
1 | const size = 4 |
模拟枚举(const和iota模拟枚举)
Go语言现阶段没有枚举,可以使用 const 常量配合 iota 模拟枚举,请看下面的代码:
1 | type Weapon int |
枚举类型其实本质是一个 int 一样。当然,某些情况下,如果需要 int32 和 int64 的枚举,也是可以的。
Go语言type关键字(类型别名)
类型定义
1 | type byte uint8 |
类型别名
类型别名的写法为:
1 | type TypeAlias = Type |
类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
1 | package main |
代码说明如下:
- 第 8 行,将 NewInt 定义为 int 类型,这是常见定义类型的方法,通过 type 关键字的定义,NewInt 会形成一种新的类型。NewInt 本身依然具备int的特性。
- 第 11 行,将 IntAlias 设置为 int 的一个别名,使用 IntAlias 与 int 等效。
- 第 15 行,将 a 声明为 NewInt 类型,此时若打印,则 a 的值为 0。
- 第 16 行,使用
%T
格式化参数,显示 a 变量本身的类型。 - 第 18 行,将 b 声明为 IntAlias 类型,此时打印 b 的值为 0。
- 第 19 行,显示 b 变量的类型。
结果显示a的类型是 main.NewInt,表示 main 包下定义的 NewInt 类型。b 类型是 int。IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。
Go语言指针
指针(pointer)概念在Go语言中被拆分为两个核心概念:
- 类型指针,运行对这个指针类型的数据进行修改,传递数据使用指针,而无需拷贝数据。类型指针不能进行偏移和运算
- 切片,由指向起始元素的原始指针、元素数量和容量组成
受益于这样的约束和拆分,Go 语言的指针类型变量拥有指针的高效访问,但又不会发生指针偏移,从而避免非法修改关键性数据问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。
切片比原始指针具备更强大的特性,更为安全。切片发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。
要明白指针,需要知道几个概念:指针地址、指针类型和指针取值,下面将展开细说。
指针地址和指针类型
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置
Go 语言中使用&
作符放在变量前面对变量进行“取地址”操作。
1 | ptr := &v // v的类型为T |
其中v代表被取地址的变量,被取地址的v使用ptr变量进行接收,ptr的类型就为*T
,称做 T 的指针类型,*
代表指针。
1 | package main |
代码说明:
- 第 8 行,声明整型 cat 变量。
- 第 9 行,声明字符串 str 变量。
- 第 10 行,使用 fmt.Printf 的动词
%p
输出 cat 和 str 变量取地址后的指针值,指针值带有0x
的十六进制前缀。
输出值在每次运行是不同的,代表 cat 和 str 两个变量在运行时的地址。
提示:变量、指针和地址三者的关系是:每个变量都拥有地址,指针的值就是地址
指针取值
在对普通变量使用&
操作符获取地址获得这个变量的指针后,可以对指针使用*
操作,也就是指针取值
1 | package main |
取地址操作符&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性
如下:
- 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
使用指针修改值
通过指针不仅可以取值,也可以修改值。
前面已经使用多重赋值的方法进行数值交换,使用指针同样可以进行数值交换,代码如下:
1 | package main |
*
操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指向的变量
*
操作符的根本意义就是操作指针指向的变量,当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。
创建指针的另一种方法——new() 函数
Go 语言还提供了另外一种方法来创建指针变量,格式如下:
1 | new(T) // T代表类型 |
一般这样写:
1 | str := new(string) |
new() 函数可以创建一个对应类型的指针,创建过程会分配内存。被创建的指针指向的值为默认值。