Go语言解惑(一)变量与指针
go语言语法简洁又实用,表达能力强,编译速度快,是一门值得我们学习的非常不错的工业类语言。这篇文章不会介绍详细的基础知识,只会介绍一些可能不太注意就会有疑惑的地方,例如变量基本类型,指针等。
[TOC]
零值初始化机制
var声明语句可以创建一个特定类型的变量。声明一般语法如下:
1 | var 变量名字 类型 = 表达式 |
其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。
基础数据类型
Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。
- 基础类型: 包括数字,字符串和布尔类型。
- 复合数据类型: 数组和结构体。
- 引用类型: 包括指针,切片(slice), 字典(map), 函数(func), 通道(chan)。
- 接口类型: 描述一系列方法的集合。
基础类型
整型
go语言同时提供了有符号和无符号类型的整数运算。
类型 | 说明 |
---|---|
int8, int16,int32,int64 / uint8, uint16, uint32,uint64 | 不同bit大小的整型数/无符号整型数 |
int / uint | 这两种长度对应特定CPU平台机器字大小,32或64bit, int是应用最广泛的数值类型 |
byte | 是uint8等价的类型,一般用于强调数值是一个原始的数据,也就是字节 |
rune | 是int32等价的类型,通常用于表示一个Unicode码点 |
uintptr | 没有指定具体bit大小但是足以容纳指针,它只在底层编程才需要。 |
浮点数
go语言提供了两种精度的浮点数,float32和float64. 浮点数的范围极值可以在math包找到。
一个float32类型的浮点数可以提供大约6个十进制数的精度, 而float64可以提供15个十进制数的精度,通常优先使用float64类型。
复数类型
Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两
种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实
部和虚部:
1 | var x complex128 = complex(1, 3) |
字符串类型
一个字符串是一个不可变的字节序列(和java一样)。文本字符串通常被解释为采用UTF-8编码的Unicode码点(rune)序列。
内置的len函数可以返回一个字符串的字节数目(不是rune字符数目), 索引操作s[i]返回第i个字节的字节值, 必须满足0 << i << len(s)。第i个字节不一定是字符串的第i个字符,因为对非ascii字符的UTF8编码需要两个或多个字节。
字符串可以用==和< 进行比较, 比较是通过逐个字节比较完成的。
字符串面值
字符串也可以用字符串面值方式编写,只要将一系列字节序列包含在双引号即可:
1 | var str = "hello, \n世界" |
在一个双引号包含的字符串面值中,可以用以反斜杠 \ 开头的转义序列插入任意的数据
1 | \a 响铃 |
也可以通过十六进制或八进制转义在字符串面值包含任意字节。一个十六进制的转义形式是\xhh,其中两个h表示十六进制数字(0-F)。一个八进制转义形式是\ooo, 包含三个八进制的o数字(0-7),但不能超过\377(因为是一个字节长度)。
1 | var str = "hello, \n\x5f\x54\376世界" |
因为go语言原文件按总是用UTF-8编码,并且Go语言的文本字符串也以UTF-8编码的方式处理,因此我们可以将Unicode码点也写到字符串面值中。
使用反引号,没有转义的操作,也就是说你在代码中写的是什么,它就是什么,么有任何改变,唯一可能不一样的是会删除回车,以保证所有的平台都是一样的,所以用于编写正则表达式非常方便,同时也被广泛应用于HTML模板,JSON面值,命令行提示信息等场景。
Unicode与UTF-8
在原始时代,计算机只有ASCII编码,7bit 128个字符,但是现在世界上有那么多文字和字符,ASCII显然是不够的,于是就有了Unicode,它收集了这个世界上所有的符号系统,超过了120000个字符,涵盖超过100中语言。Unicode码点对应Go语言中的rune整数类型(对应int32),这种编码方式叫UTF-32或UCS-4。 每个码点统一大小32bit来表示,但是他会浪费很多空间,例如原来的ascii字符只需要8bit就能表示。于是有了UTF-8。
UTF-8是一个将Unicode码点编码字节为字节序列的变长编码。UTF8编码使用1-4个字节来表示每个Unicode码点,ASCII部分只用1个字节,常用字符部分使用2或3个字节表示。每个符号编码后第一个字节的高端bit位用于表示共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode码点也是采用类似的策略处理。
1 | 0xxxxxxx runes 0-127 (ASCII) |
变长的编码无法直接通过索引来访问第n个字符。
Go语言unicode包提供了诸多处理rune字符相关功能的函数,unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码功能。
另外标准库中有4个包对字符串处理尤为重要: bytes, strings, strconv和unicode包。
- strings包提供了许多如字符串的查询,替换,比较和截断,拆分和合并等功能。
- bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效,它其实类似于java中的StringBuilder和StringBuffer。
- strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相
关的转换。 - unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。
- path和path/filepath包提供了关于文件路径名更一般的函数操作
复合数据类型
本节我们讨论四种类型–数组,slice, map和结构体。数组和结构体有固定大小,slice和map则是动态的数据接口,它们将根据需要动态增长。
数组与切片的区别
数组是一个由固定长度的特定类型元素组成的序列,它的长度是固定的。所以很少使用,和数组对应的类型是Slice(切片),它是可以增长和收缩动态序列。数组和Slice最大的不同就是一个长度固定,一个长度不固定。
数组的长度是数组类型的一个组成部分,数组的长度必须是常量表达式,因为数组长度需要在编译阶段确定。
1 | var a [3]int // 数组,长度是3, 初始化值都为0 |
第二个区别是数组是可以用==比较的,Slice不行, 因为slice的元素是间接引用的。
一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
slice唯一合法的比较操作是和nil比较,例如
1 | var s []int // len(s) == 0, s == nil |
如果测试一个slice是否是空的,用len(s) == 0,而不要是用s == nil.
内置的make函数创建一个指定元素类型,初始长度和初始容量的slice。