Go语言入门基础(三)——数据类型(基础数据类型)

276 阅读8分钟

阅读本文前,建议已掌握基本C语言语法及规范,本文将更加侧重Go语言和C系列语言的区别以达到快速上手的目的

本文主要参考《Go语言圣经(中文版)》及个人实践撰写,具体可参考: Go语言圣经

本文作为该系列第三篇,将重点介绍数据类型,本篇具体介绍其中之一基础数据类型

CSAPP读书笔记中关于数据类型有以下描述:

计算机的字长决定了虚拟地址空间的最大大小,对于一个字长为n位的机器而言,虚拟地址的范围则为0~2n2^n-1,程序最多可访问2n2^n个字节(每个字节对应一个地址) 为了避免由于依赖“典型”大小和不用编译器设置带来的差错,增强程序的可移植性,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语言不会对数据做隐式的类型转换,所以需要显示手动进行转换,通常有两种转换方式:

  1. 简单类型转换和C语言的显式转换规则相同依据以下标准语句:
valueOfTypeB = typeB(valueOfTypeA)

虽然语法较为简单但是受以下限制:① 只有底层类型相同的数据类型才能相互转换,如int & float,int32 & int,底层类型不同的如int & string 不可相互转换;② 精度丢失问题是数据类型转换的内在问题,和使用的语言无关,高精度转为低精度如float->int往往会丢失精度。

  1. strconv包类型转换是支持跨大类的数据类型转换,本质上是使用外部包函数进行转换(当然也可以手动写转换函数)。具体的包函数使用可查阅官方文档:go doc strconv,下表是常用的strconv函数枚举:
类型转换函数名
string->intstrconv.Atoi(string x)
int->stringstrconv.Itoa(int x)
bool->stringstrconv.FormatBool(bool x)
float->stringstrconv.FormatFloat(float x)
int->stringstrconv.Formatint(int x)
unsigned_int->stringstrconv.FormatUint(Uint x)

Go语言中数据的算术/逻辑/比较运算符和C语言中使用方法和优先级完全相同,若不清楚可参考:Go 语言运算符及优先级表

使用Go语言做算术运算时同样需要考虑数据溢出的问题,(无论是有符号还是无符号型)只要当前位数不足以表达整个运算结果便会导致溢出,底层操作为丢弃高位bit位,有符号数可能会导致符号位颠倒。

浮点

Go语言支持float32float64两种精度的浮点数,运算符合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)
}

运行结果如下,自行对照: image.png

关于其精度,float32提供大约6个十进制位数的精度,float64大约15个,关于其选用《Go语言圣经》中作了如下阐释:

通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大

复数

我们较为熟悉C语言(C++)的复数,即C语言中头文件加入#include <complex.h>后调用库函数,或C++使用complex类即可进行复数运算。具体C/C++复数语法可参考:C/C++中的复数使用方法

Go语言同理,其对应于float32和float64分别提供了complex64complex128。内置三个复数函数,使用如下表:

作用标准语句
复数创建函数:给出实部和虚部,返回创建好的复数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))
}

以下为运行结果,自行对照: image.png

布尔型

布尔型相对于数值型简单得多,因为只有两种值truefalse,使用方法和C语言完全相同(可以使用取非运算符!进行取反、&&||进行逻辑运算,且&&优先级高于 | | )。

和C语言不同的是,Go语言中由于不存在隐式转换规则,故bool型不会自动转为int型中的0和1进行算术运算。但是我们可以手动封装函数itobbtoi进行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 }

运行结果如下,自行对照: image.png

字符串

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)
}

运行结果如下: image.png

关于字符串处理的进阶知识,可以自行查阅以下四个字符串处理包:bytes/strings/strconv/unicode,可参考文献如下:

本篇主要涉及Go语言数据类型中的基础数据类型,包括数据型(整型/浮点型/复数)、布尔型和字符串型,文中部分内容补充介绍了数据类型转换和占位符的相关知识。