4. Golang的基础数据类型¶
我们将从最基本的类型开始,Go语言的基本类型部分跟C语言很类似,熟习C语言的朋友们应该不会陌生
4.1. 整型¶
整型分为以下两个大类:
按长度分为:
int8、int16、int32、int64对应的无符号整型:
uint8、uint16、uint32、uint64
其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型
类型名 |
字节宽度 |
取值范围 |
|---|---|---|
int |
与平台有关 |
32 位系统 4 字节,64 位系统 8 字节 有符号整型 |
uint |
与平台有关 |
32 位 系统 4 字节,64 位 系统 8 字节 无符号整形 |
int8 |
1 字节 |
用 8 位表示的有符号整型 取值范围为:[-128, 127] |
int16 |
2 字节 |
用 16 位表示的有符号整型 取值范围为:[-32768,32767] |
int32 |
4 字节 |
用 32 位表示的有符号整型,取值范围为:[-2147483648,2147483647] |
int64 |
8 字节 |
用 64 位表示的有符号整型,取值范围为:[-9223372036854775808,9223372036854775807] |
uint8 |
1 字节 |
用 8 位表示的无符号整型,取值范围为:[0,255] |
uint16 |
2 字节 |
用 16 位表示的无符号整型,取值范围为:[0,65535] |
uint32 |
4 字节 |
用 32 位表示的无符号整型,取值范围为:[0,4294967295] |
uint64 |
8 字节 |
用 64 位表示的无符号整型,取值范围为:[0,18446744073709551615] |
uintptr |
与平台有关 |
32 位系统 4 字节,64 位系统 8 字节指针值的无符号整型 |
重要
在使用int和 uint类型时,不能假定它是32位或64位的整型,而是考虑int和uint可能在不同平台上的差异。
获取对象的长度的内建len()函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用int来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int和 uint。
4.1.1. 数字字面量语法(Number literals syntax)¶
Go1.13版本之后引入了数字字面量语法,这样便于开发者以二进制、八进制或十六进制浮点数的格式定义数字,例如:
v := 0b00101101, 代表二进制的 101101,相当于十进制的 45v := 0o377,代表八进制的 377,相当于十进制的 255v := 0x1p-2,代表十六进制的 1 除以 2²,也就是 0.25。而且还允许我们用
_来分隔数字,比如说:v := 123_456表示 v 的值等于 123456。
我们可以借助fmt函数来将一个整数以不同进制形式展示。
package main
import "fmt"
func main(){
// 十进制
var a int = 10
fmt.Printf("%d \n", a) // 10
fmt.Printf("%b \n", a) // 1010 占位符%b表示二进制
// 八进制 以0开头
var b int = 077
fmt.Printf("%o \n", b) // 77
// 十六进制 以0x开头
var c int = 0xff
fmt.Printf("%x \n", c) // ff
fmt.Printf("%X \n", c) // FF
}
10
1010
77
ff
FF
4.1.2. 思考,数值溢出¶
package main
import "fmt"
func main() {
var myInt int8 = 127
fmt.Printf("数字:%d 类型:%T 值:%v\n", myInt, myInt, myInt)
myInt += 1
fmt.Printf("数字:%d 类型:%T 值:%v\n", myInt, myInt, myInt)
}
4.2. 浮点型¶
Go语言支持两种浮点型数:float32和float64。
这两种浮点型数据格式遵循IEEE 754标准:
float32的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32float64的浮点数的最大范围约为1.8e308,可以使用一个常量定义:math.MaxFloat64
类型名 |
字节宽度 |
取值范围 |
|---|---|---|
float32 |
4字节 |
IEEE-754 32位浮点型数 |
float64 |
8字节 |
IEEE-754 64位浮点型数 |
打印浮点数时,可以使用fmt包配合动词%f,代码如下:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi)
fmt.Printf("%.2f\n", math.Pi)
}
3.141593
3.14
4.3. 复数¶
类型名 |
字节宽度 |
取值范围 |
|---|---|---|
complex64 |
4字节 |
32 位实数和虚数 |
complex128 |
8字节 |
64 位实数和虚数 |
package main
import "fmt"
func main() {
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)
}
(1+2i)
(2+3i)
小技巧
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。
4.4. 布尔型¶
Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值。
注意:
布尔类型变量的默认值为
false。Go 语言中不允许将整型强制转换为布尔型.
布尔型无法参与数值运算,也无法与其他类型进行转换。
b1 := true
var b2 bool // 默认为false
fmt.Printf("b1 >>> %T\nb2 >>> %T\nb2 value: %v\n", b1, b2, b2)
4.5. 字符串¶
Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为双引号(“)中的内容,可以在Go语言的源码中直接添加非ASCII码字符,例如:
s1 := "hello"
s2 := "你好"
4.5.1. 字符串转义符¶
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
转义符 |
含义 |
|---|---|
|
回车符(返回行首) |
|
换行符(直接跳到下一行的同列位置) |
|
制表符 |
|
单引号 |
|
双引号 |
|
反斜杠 |
举个例子,我们要打印一个Windows平台下的一个文件路径:
package main
import "fmt"
func main() {
str1 := "\"c:\\Code\\lesson1\\go.exe\""
str2 := `\"c:\\Code\\lesson1\\go.exe\"` // ``符号内的字符串强制不转义!
fmt.Println("str1:", str1)
fmt.Println("str2:", str2)
}
str1: "c:\Code\lesson1\go.exe"
str2: \"c:\\Code\\lesson1\\go.exe\"
小技巧
反引号```` 内定义的字符串输出的时候不转义
4.5.2. 多行字符串¶
Go语言中要定义一个多行字符串时,就必须使用反引号字符:
package main
import "fmt"
func main() {
s2 := `
第一行
第二行
第三行
`
fmt.Printf("多行字符串如下:%s", s2)
}
多行字符串如下:
第一行
第二行
第三行
反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
4.5.3. 字符串的常用操作¶
方法 |
介绍 |
|---|---|
len(str) |
求长度 |
+或fmt.Sprintf |
拼接字符串 |
strings.Split |
分割 |
strings.contains |
判断是否包含 |
strings.HasPrefix,strings.HasSuffix |
前缀/后缀判断 |
strings.Index(),strings.LastIndex() |
子串出现的位置 |
strings.Join(a[]string, sep string) |
join操作 |
4.5.4. 计算字符串长度¶
比较两个字符串长度并返回最大值与最小值
//Compare two string lengths and return the maximum and minimum values
func Len4Str(str1, str2 string) (max, min int) {
l1, l2 := len(str1), len(str2)
if l1>l2 {
max, min = l1, l2
} else {
max, min = l2, l1
}
return
}
4.5.5. 字符串拼接¶
4.5.5.1. 加号连接 +¶
//Concatenate strings by "+"
func concateStrings(strArgs ...string) string {
ret := ""
for _, str := range strArgs {
ret += str
}
return ret
}
//Concatenate strings via Sprintf
func concateStrings2(strArgs ...string) string {
ret := ""
for _, str := range strArgs {
ret = fmt.Sprintf("%s%s",ret,str)
}
return ret
}
4.5.5.2. strings.Join¶
- func Join(str []string, sep string) string
This function concatenates all the elements present in the slice of string into a single string. This function is available in string package.
参数说明
参数 |
说明 |
备注 |
|---|---|---|
str |
待分割的字符串可迭代对象 |
字符串切片类型的参数 |
sep |
分隔符 |
字符串类型的参数 |
返回值
返回一个字符串
//Concatenate strings with join
func concateStrings3(strArgs ...string) string {
return strings.Join(strArgs, "")
}
4.5.5.3. strings.Builder¶
//Concatenate strings with Builder
func concateStrings4(strArgs ...string) string {
strB := strings.Builder{}
for _, str := range strArgs {
strB.WriteString(str)
}
fmt.Println(strB.String())
return strB.String()
}
4.5.6. 字符串切割¶
4.5.6.1. Split¶
- func strings.Split(str, sep string) []string
This function splits a string into all substrings separated by the given separator and returns a slice which contains these substrings.
参数说明
参数 |
说明 |
备注 |
|---|---|---|
str |
待分割的字符串 |
字符串类型的参数 |
sep |
分隔符 |
字符串类型的参数 |
返回值
返回一个字符串切片。
// Go program to illustrate how to split a string
package main
import (
"fmt"
"strings"
)
// Main function
func main() {
// Creating and initializing the strings
str1 := "Welcome, to the, online portal, of GeeksforGeeks"
str2 := "My dog name is Dollar"
str3 := "I like to play Ludo"
// Displaying strings
fmt.Println("String 1: ", str1)
fmt.Println("String 2: ", str2)
fmt.Println("String 3: ", str3)
// Splitting the given strings
// Using Split() function
res1 := strings.Split(str1, ",")
res2 := strings.Split(str2, "")
res3 := strings.Split(str3, "!")
res4 := strings.Split("", "GeeksforGeeks, geeks")
// Displaying the result
fmt.Printf("\nResult 1: %#v\n", res1)
fmt.Printf("Result 2: %#v\n", res2)
fmt.Printf("Result 3: %#v\n", res3)
fmt.Printf("Result 4: %#v\n", res4)
}
String 1: Welcome, to the, online portal, of GeeksforGeeks
String 2: My dog name is Dollar
String 3: I like to play Ludo
Result 1: []string{"Welcome", " to the", " online portal", " of GeeksforGeeks"}
Result 2: []string{"M", "y", " ", "d", "o", "g", " ", "n", "a", "m", "e", " ", "i", "s", " ", "D", "o", "l", "l", "a", "r"}
Result 3: []string{"I like to play Ludo"}
Result 4: []string{""}
4.5.6.2. SplitAfter¶
- func SplitAfter(str, sep string) []string
This function splits a string into all substrings after each instance of the given separator and returns a slice which contains these substrings.
参数说明
参数 |
说明 |
备注 |
|---|---|---|
str |
待分割的字符串 |
字符串类型的参数 |
sep |
分隔符 |
字符串类型的参数 |
返回值
返回一个字符串切片。
// Go program to illustrate how to split a string
package main
import (
"fmt"
"strings"
)
// Main function
func main() {
// Creating and initializing the strings
str1 := "Welcome, to the, online portal, of GeeksforGeeks"
str2 := "My dog name is Dollar"
str3 := "I like to play Ludo"
// Displaying strings
fmt.Println("String 1: ", str1)
fmt.Println("String 2: ", str2)
fmt.Println("String 3: ", str3)
// Splitting the given strings
// Using SplitAfter() function
res1 := strings.SplitAfter(str1, ",")
res2 := strings.SplitAfter(str2, "")
res3 := strings.SplitAfter(str3, "!")
res4 := strings.SplitAfter("", "GeeksforGeeks, geeks")
// Displaying the result
fmt.Printf("\nResult 1: %#v\n", res1)
fmt.Printf("Result 2: %#v\n", res2)
fmt.Printf("Result 3: %#v\n", res3)
fmt.Printf("Result 4: %#v\n", res4)
}
String 1: Welcome, to the, online portal, of GeeksforGeeks
String 2: My dog name is Dollar
String 3: I like to play Ludo
Result 1: []string{"Welcome,", " to the,", " online portal,", " of GeeksforGeeks"}
Result 2: []string{"M", "y", " ", "d", "o", "g", " ", "n", "a", "m", "e", " ", "i", "s", " ", "D", "o", "l", "l", "a", "r"}
Result 3: []string{"I like to play Ludo"}
Result 4: []string{""}
4.5.6.3. SplitAfterN¶
- func SplitAfterN(str, sep string, m int) []string
This function splits a string into all substrings after each instance of the given separator and returns a slice which contains these substrings.
小技巧
str是字符串,sep是分隔符,m 用于查找要返回的子字符串数。
以str为分隔符,将s切分成多个子串,结果中包含str本身。
如果str为空,则将s切分成Unicode字符列表。如果s 中没有str子串,则将整个s作为 []string 的第一个元素返回。
参数n表示最多切分出几个子串,超出的部分将不再切分。
如果n为0,则返回 nil;如果 n 小于 0,则不限制切分个数,全部切分
参数说明
参数 |
说明 |
备注 |
|---|---|---|
str |
待分割的字符串 |
字符串类型的参数 |
sep |
分隔符 |
字符串类型的参数 |
m |
分割次数 |
int类型的参数 |
返回值
返回一个字符串切片。
// Go program to illustrate how to split a string
package main
import (
"fmt"
"strings"
)
// Main function
func main() {
// Creating and initializing the strings
str1 := "Welcome, to the, online portal, of GeeksforGeeks"
str2 := "My dog name is Dollar"
str3 := "I like to play Ludo"
// Displaying strings
fmt.Println("String 1: ", str1)
fmt.Println("String 2: ", str2)
fmt.Println("String 3: ", str3)
// Splitting the given strings
// Using SplitAfterN() function
res1 := strings.SplitAfterN(str1, ",", 2)
res2 := strings.SplitAfterN(str2, "", 4)
res3 := strings.SplitAfterN(str3, "!", 1)
res4 := strings.SplitAfterN("", "GeeksforGeeks, geeks", 3)
// Displaying the result
fmt.Printf("\nResult 1: %#v\n", res1)
fmt.Printf("Result 2: %#v\n", res2)
fmt.Printf("Result 3: %#v\n", res3)
fmt.Printf("Result 4: %#v\n", res4)
}
String 1: Welcome, to the, online portal, of GeeksforGeeks
String 2: My dog name is Dollar
String 3: I like to play Ludo
Result 1: []string{"Welcome,", " to the, online portal, of GeeksforGeeks"}
Result 2: []string{"M", "y", " ", "dog name is Dollar"}
Result 3: []string{"I like to play Ludo"}
Result 4: []string{""}
4.6. 字符类型¶
组成字符串的每个元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号'包裹起来,如:
var a = '中' // rune
var b = 'x' // byte
Go 语言的字符有以下两种:
uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。rune类型,代表一个UTF-8字符。
当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。
Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。
func traversalString() {
s := "你好,李焕英!abc123"
for i := 0; i < len(s); i++ {
fmt.Printf("%T>>%v(%c)", s[i], s[i], s[i])
}
fmt.Println()
for _, r := range s {
fmt.Printf("%T>>%v(%c)", r, r, r)
}
fmt.Println()
}
uint8>>228(ä)uint8>>189(½)uint8>>160( )uint8>>229(å)uint8>>165(¥)uint8>>189(½)ui
nt8>>239(ï)uint8>>188(¼)uint8>>......
int32>>20320(你)int32>>22909(好)int32>>65292(,)int32>>26446(李)int32>>28949(焕)
int32>>33521(英)int32>>65281(!)int32>>97(a)int32>>98(b)int32>>99(c)int32>>49(1)
int32>>50(2)int32>>51(3)
警告
因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。
字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示UTF8字符,一个rune字符由一个或多个byte组成。
思考一下,rune 为什么是int32而不是uint16
Go 使用大量有符号值,不仅用于 runes,还用于数组索引、Read/Write字节计数等,这是因为uint 在任何语言中都会表现得令人困惑,除非你针对溢出进行算术保护(例如,如果var a, b uint = 1, 2,a-b > 0和a-b > 1000000:
int 在日常生活中表现得更像数字,这是使用它们的一个令人信服的理由,并且没有同样令人信服的理由不使用它们。
4.6.1. 练习¶
使用三种方式输出一个变量的地址
package main
import (
"fmt"
"unsafe"
)
//使用三种方式输出一个变量的地址
func multiOutput() {
var n byte = '1'
// method 1
var point unsafe.Pointer = unsafe.Pointer(&n)
//method 2
var p uintptr = uintptr(point)
// method 3
var ptr *byte = &n
fmt.Printf("%p\n", ptr)
fmt.Printf("%x\n", p)
fmt.Printf("%p\n", point)
}
func main() {
multiOutput()
}
0xc000014088
c000014088
0xc000014088
输入一个uint32类型的数字,打印出对应的二进制表示格式
package main
import (
"fmt"
"math"
"strings"
)
//Given a number of type uint32, print its binary representation
func BinaryFMT(num uint32) string {
strB, caliper := strings.Builder{}, uint32(math.Pow(2, 31))
for i := 0; i < 32; i++ {
if 0 == (num & caliper) {
strB.WriteString("0")
} else {
strB.WriteString("1")
}
caliper = caliper >> 1
}
fmt.Println(strB.String())
return strB.String()
}
func main() {
BinaryFMT(12)
}
00000000000000000000000000001100
进阶用法
package BinaryFormat
import (
"fmt"
"strings"
)
//Prints a string of binary expressions for a number of type INT32
func BinaryFormat(n int32) string {
// Consider the plus and minus signs
unsignNum := uint32(n)
strBuilder := strings.Builder{}
caliper := (^uint32(0) >> 1) + 1
for caliper > 0 {
if 0 == caliper&unsignNum {
strBuilder.WriteString("0")
} else {
strBuilder.WriteString("1")
}
caliper >>= 1
}
return strBuilder.String()
}
/*
注意:此函数输出的二进制表达形式为原码形式,实际的加减运算逻辑都是在补码的形势下进行计算的
*/
4.6.2. 修改字符串¶
要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。
func changeString() {
//修改uint8
s1 := "big" //string类型
byteS1 := []byte(s1)
byteS1[0] = 'p'
fmt.Printf("%s\n", string(byteS1))
//修改rune
s2 := "白萝卜"
runeS2 := []rune(s2)
runeS2[0] = '红'
fmt.Printf("%s\n", string(runeS2))
}
4.7. 自定义类型¶
type signal uint8
type ms map[string]string
type add func(a, b int) int
type user struct {name string; age int8}
type User struct {
name string
age int8
}
4.8. 强制类型转换¶
byte和int类型可以相互转换
float和int可以相互转换,但小数位会丢失
bool和int不能相互转换
不同长度的int和float之间可以相互转换
string可以转换为[]byte和[]rune类型,byte和rune也可以转换为字符串
低精度向高精度转换没问题,高精度像低精度转换会丢失位数
无符号像有符号转换,最高位就是了符号位