阅读本文前,建议已掌握基本C语言语法及规范,本文将更加侧重Go语言和C系列语言的区别以达到快速上手的目的
本文主要参考《Go语言圣经(中文版)》及个人实践撰写,具体可参考: Go语言圣经
本文作为该系列第三篇,将重点介绍数据类型,本篇具体介绍其中之一基础数据类型
CSAPP读书笔记中关于数据类型有以下描述:
计算机的字长决定了虚拟地址空间的最大大小,对于一个字长为n位的机器而言,虚拟地址的范围则为0~-1,程序最多可访问个字节(每个字节对应一个地址) 为了避免由于依赖“典型”大小和不用编译器设置带来的差错,增强程序的可移植性,ISO C99引入了一类数据大小固定的数据类型,例如int32_t和int64_t
也就是说,从底层来看所有的数据都是由比特构成,数据类型的引入是为了更加规范(规整)地使用内存空间、提高空间利用率的同时提高程序的可移植性。
而Go语言由于需要兼顾硬件特性和程序设计,内置了多种数据类型,按照Go语言圣经中的标准可以划分为四类:基础类型/复合类型/引用类型/接口类型。本篇将介绍基础数据类型及其运算,具体分为数据型/布尔型/字符串。其他类型将在后续的章节中介绍。
数据型
最常用的基础数据类型为数据型,具体有整型、浮点型和复数。
整型
Go语言的整型类似于C语言,提供有符号和无符号的整数运算,具体来说是int8/int16/int32/int64四种有符号数(无符号数同样对应不同长度的四种)。同时还有一些同样大小,但是含义不同的等价数据类型(或称别名):
byte等价于int8,但是区别在于前者更强调8位数码,而后者更强调作为一个整数整体。rune等价于int32,rune是一个特定类型的Unicode字符,用于表示一个Unicode码点(官方定义为 rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.即区分字符值和整数值的Unicode码点)uintptr等价于无符号整数类型,无固定大小,用于存放一个指针。
所有数据类型中,即使有个别大小相同,仍然属于不同的数据类型,需要显式的类型转换操作(包括int和int32)。不同于C语言,Go语言不会对数据做隐式的类型转换,所以需要显示手动进行转换,通常有两种转换方式:
简单类型转换和C语言的显式转换规则相同依据以下标准语句:
valueOfTypeB = typeB(valueOfTypeA)
虽然语法较为简单但是受以下限制:① 只有底层类型相同的数据类型才能相互转换,如int & float,int32 & int,底层类型不同的如int & string 不可相互转换;② 精度丢失问题是数据类型转换的内在问题,和使用的语言无关,高精度转为低精度如float->int往往会丢失精度。
strconv包类型转换是支持跨大类的数据类型转换,本质上是使用外部包函数进行转换(当然也可以手动写转换函数)。具体的包函数使用可查阅官方文档:go doc strconv,下表是常用的strconv函数枚举:
| 类型转换 | 函数名 |
|---|---|
| string->int | strconv.Atoi(string x) |
| int->string | strconv.Itoa(int x) |
| bool->string | strconv.FormatBool(bool x) |
| float->string | strconv.FormatFloat(float x) |
| int->string | strconv.Formatint(int x) |
| unsigned_int->string | strconv.FormatUint(Uint x) |
Go语言中数据的算术/逻辑/比较运算符和C语言中使用方法和优先级完全相同,若不清楚可参考:Go 语言运算符及优先级表
使用Go语言做算术运算时同样需要考虑数据溢出的问题,(无论是有符号还是无符号型)只要当前位数不足以表达整个运算结果便会导致溢出,底层操作为丢弃高位bit位,有符号数可能会导致符号位颠倒。
浮点
Go语言支持float32和float64两种精度的浮点数,运算符合IEEE 754规范。
由math包可以获得浮点数的范围:math.MaxFloat32≈3.4e38,math.MaxFloat64≈1.8e308。
浮点数较为重要的知识点为其在Printf函数中的占位符,具体格式见下表:
| 占位符 | 作用 |
|---|---|
| %f | 有小数点而无指数 |
| %g %G | 紧凑型(保证精度,省去末尾0),根据情况选择有无指数 |
| %e %E | 指数型 |
下面是一段占位符使用的示例代码:
package main
import "fmt"
func main() {
fl1 := 2.200000
fl2 := 2333.2333e78
fmt.Println("********************************Test 1")
fmt.Printf("%%f format: %f\n", fl1)
fmt.Printf("%%f format: %.3f\n", fl1)
fmt.Printf("%%g format: %g\n", fl1)
fmt.Printf("%%e format: %e\n", fl1)
fmt.Printf("%%e format: %E\n", fl1)
fmt.Println("********************************Test 2")
fmt.Printf("%%f format: %f\n", fl2)
fmt.Printf("%%e format: %e\n", fl2)
}
运行结果如下,自行对照:
关于其精度,float32提供大约6个十进制位数的精度,float64大约15个,关于其选用《Go语言圣经》中作了如下阐释:
通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大
复数
我们较为熟悉C语言(C++)的复数,即C语言中头文件加入#include <complex.h>后调用库函数,或C++使用complex类即可进行复数运算。具体C/C++复数语法可参考:C/C++中的复数使用方法
Go语言同理,其对应于float32和float64分别提供了complex64和complex128。内置三个复数函数,使用如下表:
| 作用 | 标准语句 |
|---|---|
| 复数创建函数:给出实部和虚部,返回创建好的复数 | complex(实部,虚部) |
| 返回一个复数的实部 | real(复数) |
| 返回一个复数的虚部 | imag(复数) |
以下为一段复数运算的示例代码:
package main
import "fmt"
func main() {
//创建复数
cpx1 := complex(3, 4)
cpx2 := complex(-1, -2)
fmt.Println(cpx1, cpx2)
fmt.Println(cpx1 * cpx2)
//实部
fmt.Println(real(cpx1), real(cpx2))
//虚部
fmt.Println(imag(cpx1), imag(cpx2))
}
以下为运行结果,自行对照:
布尔型
布尔型相对于数值型简单得多,因为只有两种值true和false,使用方法和C语言完全相同(可以使用取非运算符!进行取反、&&和||进行逻辑运算,且&&优先级高于 | | )。
和C语言不同的是,Go语言中由于不存在隐式转换规则,故bool型不会自动转为int型中的0和1进行算术运算。但是我们可以手动封装函数itob和btoi进行bool和int的数据类型转换:(无库函数)
func itob(i int) bool { return i != 0 }
func btoi(b bool) int {
if b {
return 1
}
return 0
}
以下是一段示例代码:
package main
import "fmt"
func main() {
b1 := false
b2 := true
i1 := 1
i2 := 0
fmt.Println(b1, b2)
fmt.Println(btoi(b1), btoi(b2))
fmt.Println(itob(i1), itob(i2))
fmt.Println((!b2 == b1) == b2)
}
func btoi(b bool) int {
if b {
return 1
}
return 0
}
func itob(i int) bool { return i != 0 }
运行结果如下,自行对照:
字符串
Go语言字符串操作同样可以类比于C语言——使用索引s[i]返回数值。
以下是字符串最基本的操作:(假设一个字符串变量s)
| 操作内容 | 代码实现 |
|---|---|
| 获得字符串的字节数 | len(s) |
| 获得索引对象字符(i≥0) | s[i] |
| 获取索引范围在 [i,j) 的子串 | s[i:j] |
| 获取索引范围在 [0,j) 的子串 | s[ :j] |
| 获取索引范围在 [i,结尾) 的子串 | s[i: ] |
| 字符串拼接 | s1+s2 |
Go语言中字符串具有不可修改性,即不能改变字符串的字节序列,以下的代码将会导致编译错误:
s[0] = 'L' // compile error: cannot assign to s[0]
但是可以改变字符串变量的值,原理为给变量分配一个新的字符串值,所以相同效果下,这行代码是正确的:
s = "L" + s[1:]
以下是对字符串不可修改性的实践代码:
package main
import "fmt"
func main() {
str1 := "Hello world"
fmt.Println(str1)
str1 = "h" + str1[1:]
fmt.Println(str1)
}
运行结果如下:
关于字符串处理的进阶知识,可以自行查阅以下四个字符串处理包:bytes/strings/strconv/unicode,可参考文献如下:
本篇主要涉及Go语言数据类型中的基础数据类型,包括数据型(整型/浮点型/复数)、布尔型和字符串型,文中部分内容补充介绍了数据类型转换和占位符的相关知识。