布尔类型
要么是真,要么是假,初始值是假
package main
import "fmt"
func booleanType() {
var a bool
fmt.Println("a =", a)
a = true
fmt.Println("a =", a)
}
func main() {
booleanType()
//a = false
//a = true
}
bool类型的fmt
使用fmt打印bool类型时
package main
import "fmt"
func booleanType() {
var a bool
fmt.Println("a =", a)
a = true
fmt.Println("a =", a)
fmt.Printf("a=%t, type is %T", a, a)
}
func main() {
booleanType()
//a = false
//a = true
//a=true, type is bool
}
整数类型
Go语言的数值类型分为以下几种:整数、浮点数、复数,其中每一种都包含了不同大小的数值类型,例如有符号整数包含 int8、int16、int32、int64 等,每种数值类型都决定了对应的大小范围和是否支持正负符号。
Go语言同时提供了有符号和无符号的整数类型,其中包括 int8、int16、int32 和 int64 四种大小截然不同的有符号整数类型,分别对应 8、16、32、64 bit(二进制位)大小的有符号整数,与此对应的是 uint8、uint16、uint32 和 uint64 四种无符号整数类型。
int和uint类型
此外还有两种整数类型 int 和 uint,它们分别对应特定 CPU 平台的字长(机器字大小),其中 int 表示有符号整数,应用最为广泛,uint 表示无符号整数。实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。
大多数情况下,我们只需要 int 一种整型即可,它可以用于循环计数器(for 循环中控制循环次数的变量)、数组和切片的索引,以及任何通用目的的整型运算符,通常 int 类型的处理速度也是最快的。
rune类型
用来表示 Unicode 字符的 rune 类型和 int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样,byte 和 uint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。
uintptr类型
最后,还有一种无符号的整数类型 uintptr,它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
尽管在某些特定的运行环境下 int、uint 和 uintptr 的大小可能相等,但是它们依然是不同的类型,比如 int 和 int32,虽然 int 类型的大小也可能是 32 bit,但是在需要把 int 类型当做 int32 类型使用的时候必须显示的对类型进行转换,反之亦然。
有符号和无符号
有 u 说明是无符号,没有 u 代表有符号。
Go语言中有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1。无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1。例如,int8 类型整数的取值范围是从 -128 到 127,而 uint8 类型整数的取值范围是从 0 到 255。
以 int8 和 uint8 举例,8 代表 8个bit,能表示的数值个数有 2^8 = 256。
uint8 是无符号,能表示的都是正数,0-255,刚好256个数。
int8 是有符号,既可以正数,也可以负数,那怎么办?对半分呗,-128-127,也刚好 256个数。
int8 int16 int32 int64 这几个类型的最后都有一个数值,这表明了它们能表示的数值个数是固定的。
而 int 没有并没有指定它的位数,说明它的大小,是可以变化的,那根据什么变化呢?
- 当你在32位的系统下,int 和 uint 都占用 4个字节,也就是32位。
- 若你在64位的系统下,int 和 uint 都占用 8个字节,也就是64位。
出于这个原因,在某些场景下,你应当避免使用 int 和 uint ,而使用更加精确的 int32 和 int64,比如在二进制传输、读写文件的结构描述(为了保持文件的结构不会受到不同编译目标平台字节长度的影响)
什么时候用int和uint
程序逻辑对整型范围没有特殊需求。例如,对象的长度使用内建 len() 函数返回,这个长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用 int 来表示。
反之,在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint。
整型中的范围
使用math包中的Max接类型可以看到最大值和最小值
fmt.Println(math.MaxInt)
fmt.Println(math.MinInt)
整数类型的fmt
func intType() {
var a int = 10
var floatInt float32 = 3.1415926
var eightBaseInt int = 0o12
fmt.Printf("二进制为:%b\n", a)
fmt.Printf("unicode码值为:%c", a)
fmt.Printf("十进制:%d\n", eightBaseInt)
fmt.Printf("八进制:%o\n", a)
fmt.Printf("十六进制(a-f):%x\n", a)
fmt.Printf("十六进制(A-F):%X\n", a)
fmt.Printf("go语法字符字面值:%q", a)
fmt.Println()
fmt.Printf("Unicode格式:%U\n", a)
fmt.Printf("科学计数法表示:%E\n", floatInt)
fmt.Printf("浮点数表示:%f\n", floatInt)
}
func main() {
intType()
//二进制为:1010
//unicode码值为:
//十进制:10
//八进制:12
//十六进制(a-f):a
//十六进制(A-F):A
//go语法字符字面值:'\n'
//Unicode格式:U+000A
//科学计数法表示:3.141593E+00
//浮点数表示:3.141593
}
浮点类型
浮点数类型的值一般由整数部分、小数点“.”和小数部分组成。
其中,整数部分和小数部分均由10进制表示法表示。不过还有另一种表示方法。那就是在其中加入指数部分。指数部分由“E”或“e”以及一个带正负号的10进制数组成。比如,3.7E-2表示浮点数0.037。又比如,3.7E+1表示浮点数37。
有时候,浮点数类型值的表示也可以被简化。比如,37.0可以被简化为37。又比如,0.037可以被简化为.037。
有一点需要注意,在Go语言里,浮点数的相关部分只能由10进制表示法表示,而不能由8进制表示法或16进制表示法表示。比如,03.7表示的一定是浮点数3.7。
Go语言中提供了两种精度的浮点数 float32 和 float64。
float32
单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数
float64
双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数
什么是精度
精度主要取决于尾数部分的位数。
对于 float32(单精度)来说,表示尾数的为23位,除去全部为0的情况以外,最小为2-23,约等于1.19*10-7,所以float小数部分只能精确到后面6位,加上小数点前的一位,即有效数字为7位。
同理 float64(单精度)的尾数部分为 52位,最小为2-52,约为2.22*10-16,所以精确到小数点后15位,加上小数点前的一位,有效位数为16位。
float32 和 float64 的范围
func floatType() {
fmt.Println("float32最大", math.MaxFloat32)
fmt.Println("float64最大", math.MaxFloat64)
}
func main() {
floatType()
//float32最大 3.4028234663852886e+38
//float64最大 1.7976931348623157e+308
}
float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
数值很大但精度有限
人家虽然能表示的数值很大,但精度位却没有那么大。
- float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度
- float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度
这里的精度是什么意思呢?
比如 10000018这个数,用 float32 的类型来表示的话,由于其有效位是7位,将10000018 表示成科学计数法,就是 1.0000018 * 10^7,能精确到小数点后面6位。
此时用科学计数法表示后,小数点后有7位,刚刚满足我们的精度要求,意思是什么呢?此时你对这个数进行+1或者-1等数学运算,都能保证计算结果是精确的
import "fmt"
var myfloat float32 = 10000018
func main() {
fmt.Println("myfloat: ", myfloat)
fmt.Println("myfloat: ", myfloat+1)
}
// myfloat: 1.0000018e+07
// myfloat: 1.0000019e+07
上面举了一个刚好满足精度要求数据的临界情况,为了做对比,下面也举一个刚好不满足精度要求的例子。只要给这个数值多加一位数就行了。
换成 100000187,同样使用 float32类型,表示成科学计数法,由于精度有限,表示的时候小数点后面7位是准确的,但若是对其进行数学运算,由于第八位无法表示,所以运算后第七位的值,就会变得不精确。
这里我们写个代码来验证一下,按照我们的理解下面 myfloat01 = 100000182 ,对其+5 操作后,应该等于 myfloat02 = 100000187,
import "fmt"
var myfloat01 float32 = 100000182
var myfloat02 float32 = 100000187
func main() {
fmt.Println("myfloat: ", myfloat01)
fmt.Println("myfloat: ", myfloat01+5)
fmt.Println(myfloat02 == myfloat01+5)
}
// myfloat: 1.00000184e+08
// myfloat: 1.0000019e+08
// false
但是由于其类型是 float32,精度不足,导致最后比较的结果是不相等(从小数点后第七位开始不精确)
由于精度的问题,就会出现这种很怪异的现象,myfloat == myfloat +1 会返回 true 。
如果我们要保存一个精度高的数,则应该选用 float64
而且math包中的浮点数运算都是使用float64,所以更推荐使用float64
浮点数运算
浮点数float32和float64不是同一类型,不能混合运算,需要进行转化,但注意,高精度转低精度会丢失精度,所以最好用低精度转为高精度再比较
浮点数比较
十进制的浮点数在比较时,在底层转化为二进制的浮点数时会丢失精度
字符类型
byte类型
byte也是uint8的别名,代表了ASCII码的一个字符
go里面的字符类型是用单引号引起来的单个字符
func main(){
var ch byte
ch = 'a'
fmt.Printf("%c", ch)
}
字符和ascii码的转换
func byteType() {
var ch byte
ch = 'a'
fmt.Printf("ch=%c, ch=%d\n", ch)
var ch2 byte
ch2 = 97
fmt.Printf("ch2=%c, ch2=%d", ch2)
}
func main() {
byteType()
//ch=a, ch=%!d(MISSING)
//ch2=a, ch2=%!d(MISSING)
}
转义字符
转义字符就是将原有的字符的意思转换成其他的意思,比如n是代表字母n,
\n
代表换行,\\n
代表只输出\n
并不换行
rune类型
rune代表一个UTF-8字符,当处理中文、日文或其他复合字符时,则需要使用rune类型,
rune类型是int32的别名
Unicode字符集和UTF8编码
go语言支持Unicode(UTF-8),在内存中使用int来表示,在书写Unicode字符时,需要在16进制数之前加上前缀\u
或\U
,分别代表使用4字节和使用8字节。
判断规则
- 判断是否为字母:unicode.IsLetter(ch)
- 判断是否为数字:unicode.IsDigit(ch)
- 判断是否为空白字符:unicode.IsSpace(ch)
Unicode和ASCII类似都是一种字符集。
字符集为每个字符分配一个唯一的 ID,我们使用到的所有字符在 Unicode 字符集中都有一个唯一的 ID,例如上面例子中的 a 在 Unicode 与 ASCII 中的编码都是 97。汉字“你”在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。
UTF-8是编码规则,将Unicode中字符的ID以某种方式进行编码,UTF-8是一种边长编码规则,从1-4个字节不等
- 0xxxxxx表示文字符号0~127,兼容ASCII字符集
- 从128到0x10ffff表示其他字符
根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用 3 个字节。
广义的 Unicode 指的是一个标准,它定义了字符集及编码规则,即 Unicode 字符集和 UTF-8、UTF-16 编码等
字符串类型
使用双引号引起来的是字符串类型。它的默认值是""
字符串和引号
- 双引号 "
- 会识别转义字符
- 反引号 “`”
- 以字符串的原生形式输出,包括换行和特殊字符
字符串拼接
可以使用"+"号来拼接
var str1 = "hello"
fmt.Println(str1 + str1)
str1 += str1
fmt.Println(str1)
使用函数拼接
- fmt.Sprintf
- strings.Join
- strings.Builder
当有大量的string需要拼接时,使用strings.Builder效率最高
操作字符串
字符串是由多个字符组成,但也有索引的概念,是从0开始的。
func strType() {
var str1 = "hello"
fmt.Println(str1[0])
fmt.Printf("%c", str1[0])
//104
//h
}
- 求长度:len(str)
- 分割字符串:strings.Split
- 判断是否包含:strings.Contains
- 前缀/后缀判断:strings.HasPrefix;strings.HasSuffix
- 子串出现的位置:strings.Index(); strings.LastIndex()
复数类型
go语言中有两种复数类型
类型 | 字节数 | 说明 |
---|---|---|
complex64 | 8 | 64位的复数型,由float32类型的实部和部联合表示 |
complex128 | 16 | 128位的复数型,由float64类型的实部和虚部联合表示 |
可以通过Go语言内置的 complex() 函数构建复数,并通过内置的 real() 和 imag() 函数分别返回复数的实部和虚部
基本类型转换
表达式 T(v) 将值 v 转换为类型 T
T: 就是数据类型,比如 int32,int64,float32 等等
v: 就是需要转换的变量
package main
import "fmt"
func main() {
var i int32 = 100
var n1 float32 = float32(i)
var n2 int8 = int8(i)
var n3 int64 = int64(i)
fmt.Printf("i=%v n1=%v n2=%v n3=%v\n", i, n1, n2, n3)
fmt.Printf("i type is %T\n", i)
fmt.Printf("n1 type is %T\n", n1)
}
//i=100 n1=100 n2=100 n3=100
//i type is int32
//n1 type is float32
转换时的注意事项:
- Go中,数据类型的转换可以是从 ”表示范围小的“ 转换到 ”表示范围大的“
- 被转换的是变量存储的数据(就是里面的数值),变量本身的数据类型没有变化
- 范围大的转换到范围小的数据类型时,会发送数据溢出
基本类型与String类型转换
在开发中,我们经常会将基本数据类型和String来互相转换
fmt.Sprintf
向String类型转换,最常用的就是fmt.Sprintf
func Sprintf(format string, a ...interface{}) string
package main
import "fmt"
func main() {
var num1 int = 90
var num2 float64 = 23.456
var b bool = true
var myChar byte = 'h'
var str string
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%t", b)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%c", myChar)
fmt.Printf("str type %T str=%q\n", str, str)
}
//str type string str="90"
//str type string str="23.456000"
//str type string str="true"
//str type string str="h"
strconv包
使用strconv转换,也可以将其他基本类型转换为String类型
package main
import (
"fmt"
"strconv"
)
func main() {
var num3 int = 99
var num2 float64 = 23.456
var b bool = true
var str string
str = strconv.FormatInt(int64(num3), 10)
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatFloat(num2, 'f', 10, 64)
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatBool(b)
fmt.Printf("str type %T str=%q\n", str, str)
}
//str type string str="99"
//str type string str="23.4560000000"
//str type string str="true"
也可以将String转换成其他类型
package main
import (
"fmt"
"strconv"
)
func main() {
var str string = "true"
var b bool
b, _ = strconv.ParseBool(str)
// strconv.ParseBool返回结果有2个,value和err,err可以使用_忽略
fmt.Printf("b type %T b=%v\n", b, b)
var str2 string = "12345678"
var n1 int64
var n2 int
n1, _ = strconv.ParseInt(str2, 10, 64)
// 如果要转换成其他的int类型例如 int8 int32,需要再次手动转换一下
n2 = int(n1)
fmt.Printf("n1 type %T n1=%v\n", n1, n1)
fmt.Printf("n2 type %T n2=%v\n", n2, n2)
var str3 string = "123.456"
var f1 float64
f1, _ = strconv.ParseFloat(str3, 64)
fmt.Printf("f1 type %T f1=%v\n", f1, f1)
}
//b type bool b=true
//n1 type int64 n1=12345678
//n2 type int n2=12345678
//f1 type float64 f1=123.456
注意:
- 在String类型转换成基本数据类型时,要确保能够转换成有效数据,例如
- 有效:“1234” -> int(1234)
- 无效:“hello” -> int(“hello”)
- 否则Golang会直接转换成0或false
strconv函数
- itoa int类型转换成字符串
- Atoi 字符串类型转换成int
- ParseBool 字符串解析成bool类型
- ParseInt 字符串解析成int类型
- ParseUint 字符串解析成uint类型
- ParseFloat 字符串解析成float64类型
- FormatBool bool转换成字符串类型
- FormatInt int转换成字符串类型
- FormatUint Uint转换成字符串类型
- FormatFloat Float转换成字符串类型
- Append系列将指定类型转换成字符串后追加到一个切片中
type关键字
类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。在 C/C++ 语言中,代码重构升级可以使用宏快速定义一段新的代码,Go语言中没有选择加入宏,而是解决了重构中最麻烦的类型名变更问题。
区分类型别名与类型定义
在 Go 1.9 版本之前定义内建类型的代码是这样写的:
- type byte uint8
- type rune int32
而在 Go 1.9 版本之后变为:
- type byte = uint8
- type rune = int32
这个修改就是配合类型别名而进行的修改。
定义类型别名的写法为:
type TypeAlias = Type
类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
package main
import "fmt"
type NewInt int
type IntAlias = int
func main() {
var a NewInt
fmt.Printf("a type is %T\n", a)
var b IntAlias
fmt.Printf("b type is %T\n", b)
}
//a type is main.NewInt
//b type is int
结果显示 a 的类型是 main.NewInt,表示 main 包下定义的 NewInt 类型,a2 类型是 int,IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。
非本地类型不能定义方法
type MyDuration = time.Duration
func (m MyDuration) EasySet(a string) {
}
func main() {
}
编译器提示:不能在一个非本地的类型 time.Duration 上定义新方法,非本地类型指的就是 time.Duration 不是在 main 包中定义的,而是在 time 包中定义的,与 main 包不在同一个包中,因此不能为不在一个包中的类型定义方法。
此时就可以使用新建类型来代替类型别名
type MyDuration time.Duration
func (m MyDuration) EasySet(a string) {
}
func main() {
}
或者 将 MyDuration 的别名定义放在 time 包中
在结构体成员嵌入时使用别名
package main
import (
"fmt"
"reflect"
"time"
)
// 定义商标结构
type Brand struct {
}
// 为商标结构添加Show方法
func (t Brand) Show() {
}
// 为商标定义一个别名FakeBrand
type FakeBrand = Brand
// 定义车辆的结构
type Vehicle struct {
// 嵌入商标结构和别名结构
FakeBrand
Brand
}
func main() {
// a是车辆的一个实例
var a Vehicle
// 指定调用车辆结构中别名的show方法
a.FakeBrand.Show()
// 取车辆实例的反射对象
ta := reflect.TypeOf(a)
// 遍历实例中的所有成员和类型
for i := 0; i < ta.NumField(); i++ {
f := ta.Field(i)
fmt.Printf("FieldName: %v, FieldType: %v\n", f.Name, f.Type.Name())
}
//FieldName: FakeBrand, FieldType: Brand
//FieldName: Brand, FieldType: Brand
}
这个例子中,FakeBrand 是 Brand 的一个别名,在 Vehicle 中嵌入 FakeBrand 和 Brand 并不意味着嵌入两个 Brand,FakeBrand 的类型会以名字的方式保留在 Vehicle 的成员中。
如果直接使用实例a调用Show方法,会在运行时报错
# command-line-arguments
./Type关键字.go:51:4: ambiguous selector a.Show
因为两个类型都有 Show() 方法,会发生歧义,证明 FakeBrand 的本质确实是 Brand 类型。
fmt格式化输出
go语言中的格式化
格式 | 含义 |
---|---|
%% | 一个%字面量 |
%b | 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数 |
%c | 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 |
%d | 一个十进制数值(基数为10) |
%f | 以标准记数法表示的浮点数或者复数值 |
%o | 一个以八进制表示的数字(基数为8) |
%p | 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示 |
%q | 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字 |
%s | 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符) |
%t | 以true或者false输出的布尔值 |
%T | 使用Go语法输出的值的类型 |
%x | 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示 |
%X | 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示 |
Printf格式化输出
类型 | 占位符 | 输出结果 |
---|---|---|
通用 | v | 值的默认格式 |
%+v | 添加字段名(如结构体) | |
%#v | 相应值的Go语法表示 | |
%T | 相应值的类型的Go语法表示 | |
%% | 字面上的百分号,并非值的占位符 | |
布尔值 | %t | true或false |
整数值 | %b | 二进制表示 |
%c | 相应Unicode码点所表示的字符 | |
%d | 十进制表示 | |
%o | 八进制表示 | |
%q | 单引号围绕的字符字面值,由Go语法安全的转义 | |
%x | 十六进制表示,字母形式为小写a-f | |
%X | 十六进制表示,字母形式为小写A-F | |
%U | Unicode格式:U+1234,等同于"U+%04X" | |
浮点数 | %b | 无小数部分的,指数为二的幂的科学计数法 与 strconv.FormatFloat中的 ‘b’ 转换格式一致。例如 -123456p-78 |
%e | 科学计数法,例如 -1234.456e+78 | |
%E | 科学计数法,例如 -1234.456E+78 | |
%f | 有小数点而无指数,例如 123.456 | |
%g | 根据情况选择%e或%f以产生更紧凑的(无末尾的0)输出 | |
%G | 根据情况选择%E或%f以产生更紧凑的(无末尾的0)输出 | |
字符串和bytes的slice表示 | %s | 字符串或切片的无解译字节 |
%q | 双引号围绕的字符串,由Go语法安全的转义 | |
%x | 十六进制,小写字母,每字节两个字符 | |
%X | 十六进制,大写字母,每字节两个字符 | |
指针 | %p | 十六进制表示,前缀0x |
这里没有 ‘u’ 标记。若整数为无符号类型,他们就会被打印成无符号的。类似地,这里也不需要指定操作数的大小(int8,int64)。
对于%v来说默认的格式是:
bool: %t
int, int8 etc.: %d
uint, uint8 etc.: %d, %x if printed with %#v
float32, complex64, etc: %g
string: %s
chan: %p
pointer: %p
由此可以看出,默认的输出格式可以使用%v进行指定,除非输出其他与默认不同的格式,否则都可以使用%v进行替代(但是不推荐使用)
评论区