数据类型
概要
Go 语言中的数据类型主要包括以下几种:
基本数据类型:
如整数类型(int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr)、
浮点数类型(float32, float64)、
复数类型(complex64, complex128)、
布尔类型(bool)、
字符串类型(string)、
字节类型(byte, rune)等。
复合数据类型:
如数组(array)、切片(slice)、字典(map)、结构体(struct)、函数(function)、通道(channel)、接口(interface)等。
特殊数据类型:
如空类型(nil)和错误类型(error)。
跟Java不同的是,所有的基本数据类型
也可以使用对应的指针形式变成引用类型。
引用类型
- 切片
- Map类型
- Channel
- 函数
- 接口
注意
结构体是值复制,结构体指针是引用复制。
数组(array)也是值类型
nil
相信写过Golang的程序员对下面一段代码是非常非常熟悉的了:
1 | if err != nil { |
error
其实一个接口,内置的,我们看下它的定义
1 | type error interface { |
当出现不等于nil的时候,说明出现某些错误了,需要我们对这个错误进行一些处理,而如果等于nil说明运行正常。
那什么是nil呢?查一下词典可以知道,nil的意思是无,或者是零值。零值,zero value。
在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。
这是每种类型对应的零值:
1 | bool -> false |
举个例子,当你定义了一个struct:
1 | type Person struct { |
字符串
1 | package main |
结果
str1: 1234 56789 len: 10
str2: 234 len: 4
str3: 234 len: 3
str4: [1234 56789] len: 2
字符串拼接
1 | var progress = 2 |
指针与值
1 | package main |
结果
&a 变量的地址是: c000012088
a 变量的值是: 20
a 变量的值是: 20
b 变量储存的指针地址: c000012088
*b 变量的值: 20
总结
*
定义指针,指针前加*
则是取变量的值
变量前&
则是取变量的指针
array(数组)长度不变
1 | var arr0 [3]int |
结果
arr0 [0 0 0]
arr1 [1 2 3]
arr2 [0 0 0]
slice(切片)长度可变
与数组相比,切片的长度是不固定的,并且切片是可以进行扩容。
切片对象非常小,是因为它是只有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。这3个字段,就是Go语言操作底层数组的元数据,有了它们,我们就可以任意的操作切片了。
1 | var s1 []int |
创建切片
1 | package test |
结果
len=0 cap=0 slice=[]
len=1 cap=2 slice=[110]
len=0 cap=0 slice=[]
len=0 cap=10 slice=[]
len=0 cap=0 slice=[]
切片删除元素
从开头删除
删除开头的元素可以直接移动数据指针:
1 | a = []int{1, 2, 3} |
也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):
1 | a = []int{1, 2, 3} |
还可以用 copy() 函数来删除开头的元素:
1 | a = []int{1, 2, 3} |
从中间位置删除
对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成:
1 | a = []int{1, 2, 3, ...} |
从尾部删除
1 | a = []int{1, 2, 3} |
删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况,下面来看一个示例。
【示例】删除切片指定位置的元素。
1 | package main |
代码输出结果:
[a b] [d e]
[a b d e]
代码说明如下:
- seq[:index] 表示的就是被删除元素的前半部分,值为 [1 2],seq[index+1:] 表示的是被删除元素的后半部分,值为 [4 5]。
- 使用 append() 函数将两个切片连接起来。
数组和切片
1 | a1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //数组 |
数组、切片和列表
1 | package main |
结果
1 | 1 2 3 4 0 9 |
list
1 | package test |
array、slice和list
数组是类型相同的元素的集合
切片是对现有数组的引用, 比数组更方便灵活, 还可以追加数据
列表是双链表的容器, 可以添加不同类型的数据
- JSON转换不支持list
map
创建的方式
1 | userinfo := map[string]string{"name":"xiaoming"} |
使用make创建
1 | //初始化10个位置 |
使用new创建,返回的是指针
1 | //只声明 userinfo["age"] = "18" 会报错 |
注意:
键不能重复,必须为可哈希的类型(int/bool/float/string/array)
如
1 | v1 := make(map[[2]int]float32) |
interface/struct
1 | package main |
结果
I am Nokia, I can call you!
I am iPhone, I can call you!
结构体(类?)
模仿类
与其它面向对象语言相比,Go 的方法似乎有些晦涩。它并不像Java一样定义类,创建类的实例调用其方法,它其实没有类的概念,只是改造了方法,让方法可以设置可以调用的结构而已。
1 | package main |
关于值接收者和指针接收者,我们会发现changeEmail
,因为我们要操作原结构数据,所以应该传指针才对
但是我们可以不去指针这是因为Go 在编译的时候有一个隐式转换,将其转换为正确的接收者类型。
就像下面这样:
1 | (&muser).changeEmail("183518918@qq.com") |
初始化方式
三种初始化结构体的方式:
1 | //第1种,在Go语言中,可以直接以 var 的方式声明结构体即可完成实例化 |
使用 var u User
会给 t 分配内存,并零值化内存,但是这个时候的 t 的类型是 T
使用 new 关键字时 user := new(User)
,变量 t 则是一个指向 T 的指针
从内存布局上来看,我们就能看出这三种初始化方式的区别:
第1种 使用 var 声明:
第2种 使用 new 初始化:
第3种 使用结构体字面量初始化:
第4种
error
自定义error消息
1 | errors.New("数据库连接失败") |
获取错误信息
1 | fmt.Println(err.Error()) |
循环遍历
基本
1 | package main |
range(切片)
1 | package main |
结果
0 google
1 runoob
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0
range(map)
1 | package main |
结果
India 首都是 新德里
France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
India 首都是 新德里
Switch
Go 语言中的 switch 语句不需要显式地使用 break
关键字来结束每个 case,一旦匹配到一个 case,后续的 case 不会被执行。
1 | package main |
类型转换
[]byte和string
在Go语言中,可以使用[]byte
和string
类型之间进行转换。
这两种类型之间的转换可以通过类型转换或者使用标准库中的函数来完成。
[]byte
到string
的转换:
可以使用string()
函数将[]byte
转换为string
:
1 | byteSlice := []byte{'H', 'e', 'l', 'l', 'o'} |
string
到[]byte
的转换:
可以使用[]byte()
函数将string
转换为[]byte
:
1 | str := "Hello" |
需要注意的是,在Go中,string
是不可变的,而[]byte
是可变的。
因此,将string
转换为[]byte
后,可以修改[]byte
中的内容,但是不能直接修改string
的内容。
示例:
1 | str := "Hello" |
但是,直接修改string
中的内容是不被允许的:
1 | str := "Hello" |
这些方法可以方便地在string
和[]byte
之间进行转换。
interface{}转string
1 | func Interface2String(value interface{}) string { |
或者
1 | import ( |
string、int、float类型相互转换
string转成int:
1 | int, err := strconv.Atoi(string) |
string转成int64:1
2
3
4// 参数1:带转换字符串,
// 参数2:基于几进制,值可以是0,8,16,32,64
// 参数3:要转成哪个int类型:可以是0、8、16、32、64,分别对应 int,int8,int16,int32,int64
int64, err := strconv.ParseInt(string, 10, 64)
string转成float64、float321
2
3
4
5
6
7
8
9
10
11// ParseFloat 将字符串转换为浮点数
// str:要转换的字符串
// bitSize:指定浮点类型(32:float32、64:float64)
// 如果 str 是合法的格式,而且接近一个浮点值,
// 则返回浮点数的四舍五入值(依据 IEEE754 的四舍五入标准)
// 如果 str 不是合法的格式,则返回“语法错误”
// 如果转换结果超出 bitSize 范围,则返回“超出范围”
//到float64
float64,err := strconv.ParseFloat(str,64)
//到float32
float32,err := strconv.ParseFloat(str,32)
int、int64、uint64转其他
int转成string:
1 | string := strconv.Itoa(int) |
int64转成string:1
string := strconv.FormatInt(int64,10)
uint64转成string:1
string := strconv.FormatUint(uint64,10)
int转float321
float := float32(int)
float转其他
float转成string
1 | // FormatFloat 将浮点数 f 转换为字符串值 |
float转int64(会有精度损失)1
2var x float64 = 6.9
y := int64(x)
打印
对象
1 | parasStr := fmt.Sprintf("%+v", paras) |
Print 和 Printf
Print是打印输出到控制台
Printf是格式化字符串后打印输出到控制台
1 | fmt.Print("a", "\n") //输出a |
Print 和 Sprint
功能不同
- Print 将输入参数转换为 string 后, 写入标准输出。也就是程序运行时,我们可以在运行界面看到转换后的 string。
- Sprint 仅完成将输入参数转换为String,不会写入标准输出。
函数返回值不同
Print 的返回值有两个,分别表示写入标准输出的字节数以及写入时是否有错误发生:
func Print(a ...interface{}) (n int, err error)
Sprint 返回转换后的字符串:
func Sprint(a ...interface{}) string
示例
1 | fmt.Print("a", "\n") //输出a |
Print和Println输出字符串
Print输出给定的字符串,如果是数值或字符,则输出对应的十进制表示
Println自动在结尾输出\n,两个数值之间自动加空格,每项之间自动加空格
1 | fmt.Print("a", "\n") //输出a |
Printf格式化输出
1 | // 打印内存地址 |
普通占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%v | 相应值的默认格式 |
fmt.Printf("%v", name) |
{春生} |
%+v | 打印结构体时,会添加字段名 |
fmt.Printf("%+v", people) |
main.Human{Name:"zhangsan"} |
%#v | 相应值的Go语法表示 |
fmt.Printf("%#v",people) |
main.Human{Name:”春生”} |
%T | 相应值的类型的Go语法表示 |
fmt.Printf("%T",people) |
main.Human |
%% | 字面上的百分号 |
fmt.Printf("%%") |
% |
布尔占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%t | true 或 false。 |
fmt.Printf("%t",true) |
true |
整数占位符
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 二进制表示 |
fmt.Printf("%b", 5) |
101 |
%c | 相应Unicode码点所表示的字符 |
fmt.Printf("%c", 0x4E2D) |
中 |
%d | 十进制表示 |
fmt.Printf("%d", 0x12) |
18 |
%o | 八进制表示 |
fmt.Printf("%d", 10) |
12 |
%q | 单引号围绕的字符字面值,由Go语法安全地转义 |
fmt.Printf("%q", 0x4E2D) |
‘中’ |
%x | 十六进制表示,字母形式为小写 a-f | fmt.Printf("%x", 13) |
d |
%X | 十六进制表示,字母形式为大写 A-F | fmt.Printf("%x", 13) |
D |
%U | Unicode格式:U+1234,等同于 “U+%04X” | fmt.Printf("%U", 0x4E2D) |
U+4E2D |
浮点数
占位符 | 说明 | 举例 |
---|---|---|
%e | (=%.6e) 6位小数点 科学计数法,例如 -1234.456e+78 | fmt.Printf(“%e”, 10.2) |
%E | 科学计数法,例如 -1234.456E+78 | fmt.Printf(“%e”, 10.2) |
%f | (=%.6f) 6位小数点 有小数点而无指数,例如 123.456 | fmt.Printf(“%f”, 10.2) |
%g | 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 | fmt.Printf(“%g”, 10.20) |
%G | 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 | fmt.Printf(“%G”, 10.20+2i) |
字符串与字节切片
占位符 | 说明 | 举例 |
---|---|---|
%s | 输出字符串(string类型或[]byte) | fmt.Printf(“%s”, []byte(“oldboy”)) |
%10s | 输出字符串最小宽度为10(右对齐) | fmt.Printf(“%10s”, “oldboy”) |
%-10s | 输出字符串最小宽度为10(左对齐) | fmt.Printf(“%-10s”, “oldboy”) |
%.5s | 输出字符串最大宽度为5 | fmt.Printf(“%.5s”, “oldboy”) |
%5.10s | 输出字符串最小宽度为5,最大宽度为10 | fmt.Printf(“%5.10s”, “oldboy”) |
%-5.10s | 输出字符串最小宽度为5,最大宽度为10(左对齐) | fmt.Printf(“%-5.10s”, “oldboy”) |
%5.3s | 输出字符串宽度为5,如果原字符串宽度大于3,则截断 | fmt.Printf(“%5.3s”, “oldboy”) |
%010s | 如果宽度小于10,就会在字符串前面补零 | fmt.Printf(“%010s”, “oldboy”) |
%q | 双引号围绕的字符串,由Go语法安全地转义 | fmt.Printf(“%q”, “oldboy”) |
%x | 十六进制,小写字母,每字节两个字符 | fmt.Printf(“%x”, “oldboy”) |
%X | 十六进制,大写字母,每字节两个字符 | fmt.Printf(“%X”, “oldboy”) |
指针
占位符 | 说明 | 举例 |
---|---|---|
%p | 十六进制表示,前缀 0x | fmt.Printf(“%p”, &site) |
%#p | 不带前缀 0x | fmt.Printf(“%#p”, &site) |