阅读本文前,建议已掌握基本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语言数据类型中的基础数据类型,包括数据型(整型/浮点型/复数)、布尔型和字符串型,文中部分内容补充介绍了数据类型转换和占位符的相关知识。