目 录CONTENT

文章目录

Go学习系列05-基础数据类型

cplinux98
2022-10-17 / 0 评论 / 0 点赞 / 628 阅读 / 6,578 字 / 正在检测是否收录...

布尔类型

要么是真,要么是假,初始值是假

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位表示尾数

image-20220915222424526

float64

双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数

image-20220915222455823

什么是精度

精度主要取决于尾数部分的位数。

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

image-20220917105308523

转义字符

转义字符就是将原有的字符的意思转换成其他的意思,比如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 版本之前定义内建类型的代码是这样写的:

  1. type byte uint8
  2. type rune int32

而在 Go 1.9 版本之后变为:

  1. type byte = uint8
  2. 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 包不在同一个包中,因此不能为不在一个包中的类型定义方法。

image-20221016105330995

此时就可以使用新建类型来代替类型别名

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进行替代(但是不推荐使用)

0

评论区