数据类型的基本介绍

数据类型的基本介绍

Go中的每一种数据都定义了明确的数据类型,在内存中分配了不同大小的内存空间。具体来说,Go中的数据类型可分为基本数据类型和复杂数据类型。如下:

在这里插入图片描述

基本数据类型

整数类型

整数类型

Go中的整数类型如下:

类型有无符号占用存储空间表数范围
int81字节 [ − 2 7 , 2 7 − 1 ] [-2^{7},2^{7}-1] [27,271]
int162字节 [ − 2 15 , 2 15 − 1 ] [-2^{15},2^{15}-1] [215,2151]
int324字节 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [231,2311]
int648字节 [ − 2 63 , 2 63 − 1 ] [-2^{63},2^{63}-1] [263,2631]
uint81字节 [ 0 , 2 8 − 1 ] [0,2^{8}-1] [0,281]
uint162字节 [ 0 , 2 16 − 1 ] [0,2^{16}-1] [0,2161]
uint324字节 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321]
uint648字节 [ 0 , 2 64 − 1 ] [0,2^{64}-1] [0,2641]
int32位系统占用4字节,64位系统占用8字节 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [231,2311] [ − 2 63 , 2 63 − 1 ] [-2^{63},2^{63}-1] [263,2631]
uint32位系统占用4字节,64位系统占用8字节 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321] [ 0 , 2 64 − 1 ] [0,2^{64}-1] [0,2641]
rune等价于int32,占用4字节 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [231,2311]
byte等价于uint8,占用1字节 [ 0 , 2 8 − 1 ] [0,2^{8}-1] [0,281]

使用案例如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 整数类型
	var num int16 = 1024
	fmt.Printf("num value = %d\n", num)               // num value = 1024
	fmt.Printf("num type = %T\n", num)                // num type = int16
	fmt.Printf("num size = %d\n", unsafe.Sizeof(num)) // num size = 2
}

说明一下:

  • 在声明整数类型变量时,如果不指明变量的类型,则系统默认声明为int类型。
  • 整数类型的默认值为0,变量声明后,如果没有给变量赋值,则保留默认值。
  • Sizeof是unsafe包中的一个函数,用于获取数据所占用的字节数。
  • Go中在格式化字符串时,%d表示按十进制的方式进行格式化,%T表示对应变量的类型。
  • rune是Go中表示Unicode字符的类型,byte是Go中表示ASCII字符的类型。

字符类型

字符类型

Go中没有专门的字符类型,一般使用整数类型中的byte来存储单个字符。如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 字符类型
	var ch1 byte = 'a'
	fmt.Printf("ch1 value = %c\n", ch1)               // ch1 value = a
	fmt.Printf("ch1 type = %T\n", ch1)                // ch1 type = uint8
	fmt.Printf("ch1 size = %d\n", unsafe.Sizeof(ch1)) // ch1 size = 1

	var ch2 rune = '龙'
	fmt.Printf("ch2 value = %c\n", ch2)               // ch2 value = 龙
	fmt.Printf("ch2 type = %T\n", ch2)                // ch2 type = int32
	fmt.Printf("ch2 size = %d\n", unsafe.Sizeof(ch2)) // ch2 size = 4
}

说明一下:

  • ASCII字符可以直接用byte类型保存,如果要保存的码值大于255的Unicode字符,则需要考虑使用更大的整数类型来保存,一般使用rune类型保存即可。
  • Go中在格式化字符串时,%c表示按字符的方式进行格式化。
  • byte和rune本质是整数类型,因此字符类型也是可以进行运算操作的。
  • Go使用UTF-8编码作为默认编码,因此可以轻松处理多语言文本和Unicode字符。

浮点数类型

浮点数类型

Go中的浮点数类型如下:

类型占用存储空间表数范围
float324字节 [ − 3.403 E 38 , 3.403 E 38 ] [-3.403E38,3.403E38] [3.403E38,3.403E38]
float648字节 [ − 1.798 E 308 , 1.798 E 308 ] [-1.798E308,1.798E308] [1.798E308,1.798E308]

使用案例如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 浮点数类型
	var num float32 = 3.1415926
	fmt.Printf("num value = %.2f\n", num)             // num value = 3.14
	fmt.Printf("num type = %T\n", num)                // num type = float32
	fmt.Printf("num size = %d\n", unsafe.Sizeof(num)) // num size = 4
}

说明一下:

  • 在声明浮点数类型变量时,如果不指明变量的类型,则系统默认声明为float64类型。
  • 浮点数类型的默认值为0,变量声明后,如果没有给变量赋值,则保留默认值。
  • float32为单精度浮点型,float64为双精度浮点型,float64的精度更高。
  • 浮点数在机器中分为三部分存储,分别是符号位、指数位和尾数位。
  • Go中在格式化字符串时,%f表示按浮点数的方式进行格式化,%.2f表示保留两位小数。

浮点数常量的表示形式

Go中浮点数常量有两种表示形式,分别是十进制表示形式和科学计数法表示形式。如下:

// 十进制表示形式
var num1 = 3.1415926
var num2 = .1415926
fmt.Printf("num1 = %.2f\n", num1) // num1 = 3.14
fmt.Printf("num2 = %.2f\n", num2) // num2 = 0.14

// 科学计数法表示形式
var num3 = 3.1415926e2
var num4 = 3.1415926e-2
fmt.Printf("num3 = %f\n", num3) // num3 = 314.159260
fmt.Printf("num4 = %f\n", num4) // num4 = 0.031416

说明一下:

  • 科学计数法表示形式中的E,可以大写也可以小写。
  • aEx表示a乘以10的x次方,aE-x表示a除以10的x次方。

复数类型

复数类型

Go中的复数类型如下:

类型占用存储空间表数范围
complex648字节实部和虚部的范围与float32类型相同
complex12816字节实部和虚部的范围与float64类型相同

使用案例如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 复数类型
	var z complex64 = 1 + 2i
	fmt.Printf("z value = %v\n", z)               // z = (1+2i)
	fmt.Printf("z type = %T\n", z)                // z type = complex64
	fmt.Printf("z size = %d\n", unsafe.Sizeof(z)) // z size = 8
	fmt.Printf("z = %f\n", real(z))               // z的实部 = 1.000000
	fmt.Printf("z = %f\n", imag(z))               // z的虚部 = 2.000000
}

说明一下:

  • 在声明复数类型变量时,如果不指明变量的类型,则系统默认声明为complex128类型。
  • 复数类型的默认值为0,变量声明后,如果没有给变量赋值,则保留默认值。
  • real和imag是Go中的内建函数(无需引入包,可以直接使用),分别用于获取复数的实部和虚部。
  • 除了以ai + b的形式给复数赋值外,也可以使用内建函数complex构建复数,构建时指定实部和虚部。
  • Go中在格式化字符串时,%v表示按值的默认格式进行格式化。

布尔类型

布尔类型

Go中的布尔类型取值为true或false,占用1字节大小。如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 布尔类型
	var flag bool = true
	fmt.Printf("flag value = %t\n", flag)               // flag value = true
	fmt.Printf("flag type = %T\n", flag)                // flag type = bool
	fmt.Printf("flag size = %d\n", unsafe.Sizeof(flag)) // flag size = 1
}

说明一下:

  • 布尔类型的默认值为false,变量声明后,如果没有给变量赋值,则保留默认值。
  • Go中在格式化字符串时,%t表示按布尔值true或false进行格式化。
  • 关系运算符和逻辑运算符都会产生布尔类型的值,条件语句和循环语句的条件部分也都是布尔类型的值。

string类型

string类型

字符串就是一串固定长度的字符连接起来的字符序列,Go中的string是由单个字节连接起来的。string类型底层包含两个字段,一个字段是字符串指针,该指针指向对应的字符串,另一个字段记录着字符串的长度。如下:

在这里插入图片描述

使用案例如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// string类型
	var s string = "Hello World"
	fmt.Printf("s value = %s\n", s)               // s value = Hello World
	fmt.Printf("s type = %T\n", s)                // s type = string
	fmt.Printf("s size = %d\n", unsafe.Sizeof(s)) // s size = 16
}

说明一下:

  • string类型的默认值为"",变量声明后,如果没有给变量赋值,则保留默认值。
  • Go中在格式化字符串时,%s表示按字符串的方式进行格式化。
  • string类型底层包含一个字符串指针和记录字符串长度的变量,因此string类型变量的大小为16字节。

string元素的访问

通过变量名[下标]的方式能够访问string中指定下标的元素。如下:

package main

import (
	"fmt"
)

func main() {
	var s string = "Hello World"
	for i := 0; i < len(s); i++ {
		fmt.Printf("s[%d]: %c\n", i, s[i]) // 访问string的第i个元素
	}
}

说明一下:

  • 访问string元素时,下标的取值范围为 [ 0 , l e n ) [0, len) [0,len),如果超出范围则会报错。
  • len是Go中的内建函数,可以用于获取字符串的长度。
  • Go中的字符串是不可变的,因此在访问string元素时无法将元素修改为其他值。

需要注意的是,Go中的string是由单个字节连接起来的,string中元素的类型为byte。如果string中含有码值超过255的Unicode字符,那么在以byte为单位访问string元素时就会出现乱码,因为在UTF-8中一个汉字对应3个字节。

rune作为Go中表示Unicode字符的类型,其大小为4字节,因此可以将string转换为rune切片类型,转换后再通过变量名[下标]的方式访问rune切片中的元素,这样就能解决乱码问题。如下:

package main

import (
	"fmt"
)

func main() {
	var s string = "Hello 世界"
	tmp := []rune(s)
	for i := 0; i < len(tmp); i++ {
		fmt.Printf("tmp[%d]: %c\n", i, tmp[i]) // 访问rune的第i个元素
	}
}

字符串的表示形式

Go中有如下两种方式来表示字符串:

  • 双引号表示:系统会识别字符串中的转义字符。
  • 反引号表示:系统不会识别字符串中的转义字符,可以实现防止攻击、输出源代码等效果。

使用案例如下:

package main

import (
	"fmt"
)

func main() {
	var s1 string = "hello\nworld" // 双引号的方式表示字符串
	fmt.Printf("s1 = %s\n", s1)
	var s2 string = `hello\nworld` // 反引号的方式表示字符串
	fmt.Printf("s2 = %s\n", s2)
}

运行代码后可以看到,用双引号方式表示的字符串中的\n被识别为换行符输出,而用反引号方式表示的字符串中的\n被当作普通字符输出。如下:

在这里插入图片描述

字符串拼接

Go中的字符串可以通过+进行拼接。如下:

package main

import (
	"fmt"
)

func main() {
	// 字符串拼接
	var s string = "Hello" + "World"
	fmt.Printf("s = %s\n", s) // s = HelloWorld
}

常量

常量

Go中通过const关键字声明常量,在声明常量时必须给常量一个初始值,多个常量可以同时声明,常量声明后不能修改。如下:

package main

import (
	"fmt"
)

func main() {
	const s1 string = "Hello"               // 声明常量
	const s2 = "World"                      // 声明常量时省略类型
	const s3, s4 string = "Hello", "Golang" // 声明多个同类型常量
	const year, name = 2021, "dragon"       // 声明多个不同类型常量
	fmt.Println(s1, s2)                     // Hello World
	fmt.Println(s3, s4)                     // Hello Golang
	fmt.Println(year, name)                 // 2021 dragon
}

需要注意的是,只有数值类型、string类型和bool类型可以声明为常量,声明常量时可以省略常量的数据类型,这时将会声明为对应的默认类型。如下:

package main

import (
	"fmt"
)

func main() {
	const num1 = 10
	const num2 = 3.14
	const num3 = 1 + 2i
	const ch = 'a'
	const flag = true
	const s = "dragon"
	fmt.Printf("num1 type = %T\n", num1) // num1 type = int
	fmt.Printf("num2 type = %T\n", num2) // num2 type = float64
	fmt.Printf("num3 type = %T\n", num3) // num3 type = complex128
	fmt.Printf("ch type = %T\n", ch)     // ch type = int32
	fmt.Printf("flag type = %T\n", flag) // flag type = bool
	fmt.Printf("s type = %T\n", s)       // s type = string
}

说明一下:

  • 常量表达式的值在编译期计算,而不是在运行期,因此常量表达式中不能包含变量,也不能通过调用函数来给常量进行初始化。

批量声明常量

可以通过下面这种方式批量声明常量,这时除了第一个常量以外,其他常量右边的初始化表达式都可以省略,省略初始化表达式的常量将使用前面常量的初始化表达式进行初始化。如下:

package main

import (
	"fmt"
)

func main() {
	const (
		a = 1
		b
		c = 2
		d
	)
	fmt.Printf("a = %d\n", a) // a = 1
	fmt.Printf("b = %d\n", b) // b = 1
	fmt.Printf("c = %d\n", c) // c = 2
	fmt.Printf("d = %d\n", d) // d = 2
}

批量声明常量时可以使用iota常量生成器进行初始化,初始化时iota将会在第一个常量所在行被置为0,然后在下面每一行依次加一。如下:

package main

import (
	"fmt"
)

func main() {
	const (
		a = iota
		b
		c
		d
	)
	fmt.Printf("a = %d\n", a) // a = 0
	fmt.Printf("b = %d\n", b) // b = 1
	fmt.Printf("c = %d\n", c) // c = 2
	fmt.Printf("d = %d\n", d) // d = 3
}

我们也可以在复杂的常量表达式中使用iota常量生成器,例如下面让每一个常量都是前一个常量的2倍。如下:

package main

import (
	"fmt"
)

func main() {
	const (
		a = 1 << iota
		b
		c
		d
	)
	fmt.Printf("a = %d\n", a) // a = 1
	fmt.Printf("b = %d\n", b) // b = 2
	fmt.Printf("c = %d\n", c) // c = 4
	fmt.Printf("d = %d\n", d) // d = 8
}

类型转换

类型转换

Go中的数据类型不会自动转换,不同类型变量之间赋值或参与运算都需要显式进行类型转换,否则会产生报错。如下:

package main

import "fmt"

func main() {
	var num1 int = 10
	//var num2 float64 = num1 // error:类型不同,无法赋值
	var num2 float64 = 1.2
	// var ret = num1 * num2 // error:类型不同,无法参与运算
	fmt.Printf("num1 = %d, num2 = %.2f\n", num1, num2) // num1 = 10, num2 = 1.20
}

说明一下:

  • Go中全局变量/常量允许声明但不使用,但局部变量/常量声明后必须使用,否则会产生报错。

基本数据类型相互转换

基本数据类型相互转换

Go中通过T(v)的方式将v转换为T类型,其中v是需要转换的变量,T是需要转换为的类型。如下:

package main

import "fmt"

func main() {
	// 基本数据类型相互转换
	var num int = 10
	var num1 float64 = float64(num)
	var num2 int8 = int8(num)
	var num3 uint16 = uint16(num)
	fmt.Printf("num1 = %.2f, num2 = %d, num3 = %d\n", num1, num2, num3)
}

说明一下:

  • 在类型转换过程中,如果待转换的值超出了目标类型的表示范围,则按溢出处理。

基本数据类型与string的转换

基本数据类型转string

要将基本数据类型转换为string类型,可以借助fmt包中的Sprintf函数,该函数的使用方式与Printf函数一样,只不过Sprintf是将格式化后的字符串作为返回值返回。如下:

package main

import "fmt"

func main() {
	// 基本数据类型转string
	var i1 int = 10
	var i2 float32 = 3.14
	var i3 bool = true
	var i4 uint = 20
	var s string = fmt.Sprintf("%d %.2f %t %d", i1, i2, i3, i4)
	fmt.Printf("s = %q\n", s) // s = "10 3.14 true 20"
}

说明一下:

  • Go中在格式化字符串时,%q表示按字符串的方式进行格式化,并会用双引号将格式化后的字符串包裹起来。

除此之外,通过strconv包中的函数,也可以将基本数据类型转换为string类型:

  • FormatInt函数:将int64类型的变量转换为string类型。
  • FormatFloat函数:将float64类型的变量转换为string类型。
  • FormatBool函数:将bool类型的变量转换为string类型。
  • FormatUint函数:将uint64类型的变量转换为string类型。

使用案例如下:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// 基本数据类型转string
	var i1 int = 10
	var i2 float32 = 3.14
	var i3 bool = true
	var i4 uint = 20
	var s1 string = strconv.FormatInt(int64(i1), 10)
	var s2 string = strconv.FormatFloat(float64(i2), 'f', -1, 32)
	var s3 string = strconv.FormatBool(i3)
	var s4 string = strconv.FormatUint(uint64(i4), 10)
	fmt.Printf("s1 = %q\n", s1) // s1 = "10"
	fmt.Printf("s2 = %q\n", s2) // s2 = "3.14"
	fmt.Printf("s3 = %q\n", s3) // s3 = "true"
	fmt.Printf("s4 = %q\n", s4) // s4 = "20"
}

说明一下:

  • 在使用上述函数将基本数据类型转换为string类型时,如果待转的基本数据类型与函数参数的类型不匹配,则需要将其转换为对应的类型传入。
  • FormatInt和FormatUint函数的第二个参数base,表示在转换为string类型时,按照base进制进行转换,范围为2-36,转换结果中会使用小写字母a-z来表示大于10的数字。
  • FormatFloat函数的第二个参数fmt,表示转换后浮点数的表示格式,设置为’f’表示一般的浮点数表示格式;第三个参数prec,表示浮点数的控制精度,设置为-1表示使用最少数量但又必需的数字来表示。第四个参数bitSize,表示待转浮点数的源类型(32表示float32,64表示float64)。
  • Go标准库文档:Go官方标准库API文档Go中文网在线标准库文档

string转基本数据类型

通过strconv包中的函数,也可以将string类型转换为基本数据类型:

  • ParseInt函数:将string类型变量转换为int64类型。
  • ParseFloat函数:将string类型变量转换为float64类型。
  • ParseBool函数:将string类型变量转换为bool类型。
  • ParseUint函数:将string类型变量转换为uint64类型。

使用案例如下:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// string转基本数据类型
	var s1 string = "10"
	var s2 string = "3.14"
	var s3 string = "true"
	var s4 string = "20"
	i1, err1 := strconv.ParseInt(s1, 10, 0)
	i2, err2 := strconv.ParseFloat(s2, 32)
	i3, err3 := strconv.ParseBool(s3)
	i4, err4 := strconv.ParseUint(s4, 10, 0)
	fmt.Printf("i1: value = %d, type = %T, err = %v\n", i1, i1, err1)   //i1: value = 10, type = int64, err = <nil>
	fmt.Printf("i2: value = %.2f, type = %T, err = %v\n", i2, i2, err2) //i2: value = 3.14, type = float64, err = <nil>
	fmt.Printf("i3: value = %t, type = %T, err = %v\n", i3, i3, err3)   // i3: value = true, type = bool, err = <nil>
	fmt.Printf("i4: value = %d, type = %T, err = %v\n", i4, i4, err4)   // i4: value = 20, type = uint64, err = <nil>
}

说明一下:

  • 上述函数均有两个返回值,第一个返回值是转换结果,第二个返回值err表示在转换过程中出现的错误,err为nil表示转换成功。
  • ParseInt和ParseUint函数的第二个参数base,表示在转换为基本数据类型时,按照base进制进行转换,范围为2-36,如果base设置为0,则从字符串的前置判断,"0x"表示16进制,"0"表示8进制,否则是10进制;第三个参数bitSize,指定能无溢出接收转换结果的整数类型,0、8、16、32、64,分别代表int/uint、int8/uint8、int16/uint16、int32/uint32、int64/uint64。
  • ParseFloat函数的第二个参数bitSize,指定期望接收的类型,32表示float32(返回值可以不改变精确值的赋值给float32),64表示float64。

int与string类型相互转换

strconv包中提供了专门用于int与string类型相互转换的函数:

  • Atoi函数:将string类型变量转换为int类型。
  • Itoa函数:将int类型变量转换为string类型。

使用案例如下:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// int转string
	var num1 int = 10
	s1 := strconv.Itoa(num1)
	fmt.Printf("s1 value = %q, s1 type = %T\n", s1, s1)  // s1 value = "10", s1 type = string

	// string转int
	var s2 string = "20"
	num2, err := strconv.Atoi(s2)
	fmt.Printf("num2 value = %d, num2 type = %T, err = %v\n", num2, num2, err) // num2 value = 20, num2 type = int, err = <nil>
}

说明一下:

  • Atoi和Itoa函数底层实际就是调用ParseInt和FormatInt函数实现的,在内部设置了按十进制进行转换。

指针类型

指针类型

在类型的前面加上*就是对应类型的指针类型,通过取地址运算符&可以获取变量的地址,通过解引用运算符*可以获取指针指向变量的值。如下:

在这里插入图片描述

使用案例如下:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 指针类型
	var num int = 10
	var ptr *int = &num
	*ptr = 20
	fmt.Printf("num = %d\n", num)                     // num = 20
	fmt.Printf("address = %p\n", ptr)                 // address = 0xc00000e0b8
	fmt.Printf("ptr size = %d\n", unsafe.Sizeof(ptr)) // ptr size = 8
}

说明一下:

  • 由于指针变量存储的是一个地址,因此在32位系统中指针类型变量的大小为4字节,在64位系统中指针类型变量的大小为8字节。

值类型和引用类型

值类型和引用类型

Go中的数据类型可以分为值类型和引用类型:

  • 值类型变量直接存储值,内存通常在栈区分配,而引用类型变量存储的是一个地址,地址对应的空间才真正存储值,内存通常在堆区分配,当没有任何变量引用这个地址时,该地址对应的数据空间将由GC回收。
  • 值类型在赋值或函数传参时会进行数据的拷贝,而引用类型在赋值或函数传参时只会复制引用,不会复制底层数据。
  • 值类型包括基本数据类型、数组和结构体,引用类型包括指针、切片、管道、接口等。

string是值类型

Go中的string类型虽然在赋值和传递时赋值的是引用,但string类型并没有被归为引用类型。原因如下:

  • 在Go语言规范中,引用类型是指那些可以被修改的指针或包含指针的数据结构。string在Go中是不可变的,这使得string具有值类型的特征,因为string在创建后保持不变,即使复制引用也无法修改底层字符串的内容。
  • 将string定义为值类型可以提供更好的性能,由于string是不可变的,复制引用而不是底层数据使得字符串操作更高效,同时无需考虑并发访问时的安全问题。
Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐