在go语言当中,存在着很多的内建变量类型,下面来具体说明。
go中存在着以下的数据类型:
- bool
- string
- int、int8、int16、int32、int64
- uint、uint8、uint16、uint32、uint64、uintptr
- byte // uint8 的别名
- rune // int32 的别名 代表一个 Unicode 码
- float32、float64
- complex64、complex128
当一个变量被声明之后,系统就会自动的给这个变量赋予该变量类型的零值:
- int 为 0
- float为0.0
- bool 为false
- string为空字符串
- 指针为nil
每声明一个变量都相当于是在内存中开辟一块内存空间,而每一块内存都相当于是经过初始化的。
go语言当中的整数类型
go语言同时提供了有符号的整数和无符号的整数两种类型。
有符号的整数类型包括:int8、int16、int32和int64四种大小不同的有符号整数类型。
无符号的整数类型包括:uint8、uint16、uint32和uint64 四种无符号的整数类型。
除了上述的八种类型之外,还有两种整数类型为int 和 uint,分别对应特定的CPU平台的机器字大小。实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。
在开发应用中,我们比较常用的是int类型。例如在循环当中的计数器,数组操作,切片的索引值等等。一般情况下,int类型的运算处理速度是最快的。
在上面的数据类型介绍当中,go里面还有着rune类型、byte类型和uinptr类型。
其中,rune类型用来表示Unicode的字符,和int32类型是等价的,通常情况下用来表示一个Unicode码点。这两个名称可以互换使用。
byte和uint8也是等价类型,byte类型一般用于强调数值是一个原始类型的数据而不是一个小的整数。
最后,还有一种无符号的整数类型 uintptr,它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
尽管在某些特定的运行环境下 int、uint 和 uintptr 的大小可能相等,但是它们依然是不同的类型,比如 int 和 int32,虽然 int 类型的大小也可能是 32 bit,但是在需要把 int 类型当做 int32 类型使用的时候必须显示的对类型进行转换,反之亦然。
哪些情况下使用int和uint
程序逻辑对整型范围没有特殊需求。例如,对象的长度使用内建 len() 函数返回,这个长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用 int 来表示。
反之,在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint。
浮点数类型
在go语言当中,提供了两种精度的浮点数:float32和float64。
浮点数的取值范围可以通过math对象的属性去找。
fmt.Printf("float32最大数值是:%v
",math.MaxFloat32)
fmt.Printf("float63最大数值是:%v
",math.MaxFloat64)
输出结果为:
float32最大数值是:3.4028234663852886e+38
float63最大数值是:1.7976931348623157e+308
float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
在使用浮点数来进行操作的时候,需要注意,一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供大约15个十进制数的精度,我们在使用的时候应当优先使用float64类型。因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。
var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1) // "true"!
浮点数在声明的时候,可以只写小数部分或者整数部分。
例如:
const a = .1
const b = 1.
如果操作的浮点数过小或者过大,可以使用科学计数法来书写,通过e或者E来确定指数部分。
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34 // 普朗克常数
复数类型
复数是数学领域的一个概念,一般在开发当中很少用到,但是一旦涉及到科学计算等内容,复数就变得非常有必要了,而这也是作为一门泛用编程语言必备的内容。
在计算机中,复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)。
在go语言当中的复数类型有两种,分别是complex128(64位实数和虚数)和complex64位(32位实数和虚数),在其中complex128是默认的复数类型。
复数的值由三部分组成 RE + IMi,其中 RE 是实数部分,IM 是虚数部分,RE 和 IM 均为 float 类型,而最后的 i 是虚数单位。
声明复数的语法格式如下:
var name complex128 = complex(x, y)
其中,name 为复数的变量名,complex128为复数的类型,complex是go语言当中的内置函数,用于给复数赋值,x、y 分别表示构成该复数的两个 float64 类型的数值,x 为实部,y 为虚部。
当然,上面的声明语句也可以采用简写的形式来完成。
name := complex(x,y)
在go中,提供了real()函数用来获取复数的实部,imag()函数用来获取复数的虚部。
例如:
a := complex(3,2)
fmt.Print(real(a),imag(a)) // 3 2
布尔类型
在go中,bool类型只有两个值,true和false。
如常见的流程控制语句if或者循环语句for都会产生布尔值,包括==
和<
等比较类的操作符也会产生布尔值。
一元操作符!
对应逻辑非操作,因此!true
的值为 false,更复杂一些的写法是(!true==false) ==true
,实际开发中我们应尽量采用比较简洁的布尔表达式,就像用 x 来表示x==true
。
例如:
var aVar = 10
aVar == 5 // false
aVar == 10 // true
aVar != 5 // true
aVar != 10 // false
Go语言对于值之间的比较有非常严格的限制,只有两个相同类型的值才可以进行比较,如果值的类型是接口(interface),那么它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值可以不是常量,但是类型必须和该常量类型相同。如果以上条件都不满足,则必须将其中一个值的类型转换为和另外一个值的类型相同之后才可以进行比较。
需要知道的是,go语言当中的布尔值并不会隐式的转换为数字0和1。
如果有需要将布尔值转换为数值的操作,可以通过封装函数的形式来实现。
func btoi (b bool) int {
if b {
return 1
}
return 0
}
下面是将数值转为布尔值的函数:
func itob(i int) bool {
return i != 0
}
在go语言当中,不允许将整型强制转换为布尔值。
var a bool = true
fmt.Print(int(a) * 2)
上面这段程序将会导致编译错误。错误信息如下:
cannot convert n (type bool) to type int
布尔型无法参与数值运算,也无法与其他类型进行转换。
字符串类型
一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码表上的字符时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。
字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容,更深入地讲,字符串是字节的定长数组。
可以使用双引号""
来定义字符串,字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:
u
或U
:Unicode 字符\
:反斜杠自身
str := "hello,
world" // 换行显示的hello,world
获取字符串长度
可以通过len() 函数获取字符串长度。
str := "hello,world"
fmt.Print(len(str))
在字符串当中存在索引值,可以通过索引值获取字符串指定位置的字符,但是需要注意的是,字符串是不能够被修改的。同时也不能够获取字符串中某个字节的位置。
字符串的索引值是从0开始,最后一个字符的索引为len(str)-1。
需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。因为中文的汉字每个字符要占据三个字节。
拼接字符串
在go中如果想要对字符串进行拼接,可以使用"+"来完成。
例如:
s1 := "hello"
s2 := "world"
s := s1 + s2
fmt.Print(s) // helloworld
需要注意的是,如果拼接的是多行字符串,那么字符串拼接符"+"的位置一定要放在上一行的最后。
s1 := "hello" +
"world"
此时拼接的是多行字符串,go会自动的在行尾补全分号,所以字符串拼接符一定要放在第一行末尾。
在拼接字符串的时候,也可以使用+=
来进行拼接。
s1 := "hello"
s1 += ",world!"
定义多行字符串
在go中最常见的定义字符串的方式就是通过双引号的形式,这种方式被称之为字符串字面量(string literal) ,但是这种方式声明的字符串是没有办法跨行的。
当我们想要在源码中嵌入一个多行字符串的时候,可以使用反引号`来创建一个多行字符串。
例如:
str := `
hello
world
`
长度问题
我们上面说过,想要获得一个字符串的长度,可以通过len()函数来获取。
但是当我们通过len()函数获取一个包含中文的字符串长度时,得到的结果会和单纯包含ascii字符的字符串的长度不同。
例如:
str1 := "hello"
str2 := "你好"
fmt.Print(len(str1),len(str2)) // 5 6
通过上面的示例我们可以非常清楚的看到,一个中文在go的字符串当中占据三个字符,原因在于go当中的字符串都是以utf-8的格式进行保存,所以每个中文就会占据三个字节。
那么如何按照正常的习惯来获取数量呢?
可以通过Go 语言中 UTF-8 包提供的 RuneCountInString() 函数,统计 Uncode 字符数量。
str := "你好"
fmt.Print(utf8.RuneCountInString(str)) // 2
字符串截取
字符串截取操作在开发是比较常见的一种行为。我们可以把字符串当中某一段的字符称之为子串(substring)。
go语言中为我们提供了strings.Index()函数在字符串截取的时候获取指定子串的索引值。
例如:
str1 := "hello,world"
// 获取h 字符的索引值
fmt.Println(strings.Index(str1,"h")) // 0
// 获取, 字符的索引
fmt.Println(strings.Index(str1,",")) // 5
先看下面的示例,简单的截取操作。
str1 := "hello,world"
fmt.Print(str1[0:4]) // hell
如果想要获取world
,我们可以采用下面的形式。
str1 := "hello,world"
fmt.Print(str1[6:]) // world
strings.LastIndex:反向搜索子字符串
字符串的修改操作
在go当中,我们是没有办法直接修改字符串的,如果存在修改字符串的需求,可以通过类似下面的形式进行操作。
// 创建一个字符串
str := "hello,world!"
// 将o换成*
// 首先需要将字符串变为字符串数组
newStr := []byte(str)
// 开启循环
for i:=0; i<len(str);i++ {
if newStr[i] == 'o' {
newStr[i] = '*'
}
}
// 最后在把newStr变为字符串
s2 := string(newStr)
fmt.Println(s2) // hell*,w*rld!
在上面的代码中,首先将字符串变为了可操作的数组,然后通过循环的形式判断字符是否等于o,如果等于就将其变为*。
最后再把数据转换为字符串类型。
通过bytes.Buffer实现字符串拼接
之前我们说道拼接字符串可以使用+
来进行字符串的拼接,在字符串当中,还可以通过bytes.Buffer的形式进行字符串拼接。
先来看下面的示例:
str1 := "hello,"
str2 := "world!"
// 现在将这两个字符串写入到bytes.Buffer当中
// 首先声明字节缓冲
var stringBuilder bytes.Buffer
// 调用WriteString() 函数写入字符串
stringBuilder.WriteString(str1)
stringBuilder.WriteString(str2)
// 写入之后,再来String()函数将缓冲中的字符串输出
fmt.Println(stringBuilder.String()) // hello,world!
bytes.Buffer 是可以缓冲并可以往里面写入各种字节数组的。字符串也是一种字节数组,使用 WriteString() 方法进行写入。
将需要连接的字符串,通过调用 WriteString() 方法,写入 stringBuilder 中,然后再通过 stringBuilder.String() 方法将缓冲转换为字符串。
Go语言的字符类型(byte和rune)
在字符串中,每一个元素都被称之为叫做"字符",在遍历或者单个获取字符串元素时可以获得字符。
在go语言当中,字符有两种,包括:
- uint8类型,或者叫做byte型,代表了ascii码的一个字符
- 另一个是rune类型,代表一个utf-8字符,当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型等价于int32类型。
byte类型是uint8的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = 'A',字符使用单引号括起来。
需要注意,只有字符才能用单引号,字符串必须使用双引号。
在 ASCII 码表中,A 的值是 65,使用 16 进制表示则为 41,所以下面的写法是等效的:
var ch byte = 65 或 var ch byte = 'x41' //(x 总是紧跟着长度为 2 的 16 进制数)
Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):
- 判断是否为字母:unicode.IsLetter(ch)
- 判断是否为数字:unicode.IsDigit(ch)
- 判断是否为空白符号:unicode.IsSpace(ch)
go 语言当中的指针
介绍
go语言为程序开发提供了控制数据结构指针的能力,但是并不能进行指针运算。允许你控制特定集合的数据结构、分配的数量以及内存访问模式。
指针(pointer)在go语言中可以被拆分成下面的两个核心概念:
- 类型指针,允许对这个指针类型的数据进行修改,在go中传递数据可以直接使用指针,而无需拷贝数据,类型指针不能进行偏移和运算。
- 切片,由指向起始元素的的原始指针、元素数量和容量组成。
受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。
切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。
认识指针地址和指针类型
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在32和64位机器上分别占用四个或者八个字节,占用字节的大小与所指向的值的大小无关。
当一个指针被定义后没有分配到任何变量时,默认值为nil,指针变量通常缩写为ptr。
每个变量在运行时都拥有一个地址,这个地址代表变量在内存里的位置。Go语言中使用只需要在变量前添加&
操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:
ptr := &v // v的类型为T
其中v代表被取地址的变量,变量v的地址使用变量ptr进行接收,ptr的类型为*T
,称作T的指针类型,*
代表指针。
例如:
a := 10
b := "hello,world"
fmt.Print(&a,&b)
输出结果为:
0xc000018050 0xc000010230
从指针获取指针指向的值
当使用&
操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*
操作符,也就是指针取值。
// 创建一个字符串
str1 := "hello,world"
// 获取数据的地址
strAddress := &str1
fmt.Println(strAddress) // 获取数据的地址 0xc000064210
fmt.Println(*strAddress) // 根据地址取值 hello,world
使用指针修改值
除了使用指针获取值之外,还可以通过指针修改值。
下面是一个通过指针交换值得函数。
func swap(a, b *int) {
*a,*b = *b,*a
}
func main() {
x,y := 1,2
swap(&x,&y)
fmt.Println(x,y) // 2 1
}
创建指针的另外一种方式 new
Go语言还提供了另外一种方法来创建指针变量,格式如下:
new(类型)
示例:
str := new(string)
*str = "Go语言编程"
fmt.Println(*str) // go语言编程