golang数据类型
2020-07-10 22:08:56 0 举报
AI智能生成
Golang the good parts
作者其他创作
大纲/内容
词法元素
token
token是go语言的词汇构成
四类token
标识符 identifiers
关键字 keywords
运算符与标点符号 operators and punctuation
字面量 literals
字面量
值字面量
用于表示值本身的字符序列
基本(值)字面量
布尔
true | false
整数
123
浮点数
3.14
字符
‘中’
字符串
“中国”
复数
1.2e-3i
复合(值)字面量
固定数组
[3]int{1,2,3}
[...]int{1,2,3}
可变数组(切片)
[]int{1,2,3}
字典
map[int]string{1:"a", 2:"b", 3:"c"}
函数
func(x,y int)int{<br> return x+y<br>}<br>
结构体
struct{<br> Name string<br> Age int<br>}{"alice",21}<br>
整数字面量本身是无类型的,不过
赋给某个变量,如:
var num int8=1 (显示类型指定,num是int8)
var num =1(隐式类型,num是int)
有显示的类型转换,如
int16(100), 表达式结果类型是uint16
类型字面量
用于表示(自定义)类型本身的字符序列
示例
type Person interface{<br> Name() string<br> Age() int<br>}<br>
chan Person
*Person
常量
常量的类型可以是基本类型,可被赋予对应的基本(值)字面量
常量声明
const nun=1
分组声明
const( ....<br> ...<br>)<br>
iota
代表连续的、无类型的整数常量
固定步幅赋值
const{<br> a = 1 << iota<br> b<br> c<br>}<br>
以const开始的常量声明语句为单位<br>
从0开始,每赋给一个常量就递增一次
一旦跨越以const开始的常量声明语句就归零
常量表达式
仅以常量作为操作数的表达式
合法的操作数
无类型的布尔常量
无类型的数值常量
无类型的字符串常量
结果
整数+浮点数
浮点数常量
字符+整数
字符常量
整数或浮点数移位
整数常量
比较大小
布尔常量
变量
var 关键字 <br>
var name type 定义单个变量
var name1,name2, name3 type 定义多个变量 <br>
var name1,name2, name3 type = v1, v2, v3 初始化 <br>
var 类型简化 <br>
var name1, name2, name3 = v1, v2, v3 <br>
:= 符号定义
只能在函数内部使用 <br>
经常用于if for switch的初始化语句
不能用于全局变量的声明和定义
全部变量只能用var
多变量声明
name1,name2, name3 := v1, v2, v3 <br>
重复声明
field1, offset := nextField(str, 0)<br>field2, offset := nextField(str, offset) // redeclares offset
只能用于多变量声明,至少有一个是新的non-blank变量
分组声明 <br>
var( ....<br> ...<br>)<br>
类型判断
类型断言表达式
单变量形式
value := em.(T)
有可能引发panic
双变量形式
value, ok := em.(T)
em必须为initerface类型才可以进行类型断言
s := "BrainWu"<br>if v, ok := interface{}(s).(string); ok {<br> fmt.Println(v)<br>}
类型开关
type-switch
type Element interface {}<br>func main() {<br> var e Element = 100<br> switch value := e.(type) {<br> case int:<br> fmt.Println("int", value)<br> case string:<br> fmt.Println("string", value)<br> default:<br> fmt.Println("unknown", value)<br> }<br>} <br>
类型转换
类型转换表达式
valueOfTypeB = typeB(valueOfTypeA) <br>
例子
a := 5.0 //float<br>b := int(a)
转换规则
低精度转换为高精度时是安全的,高精度的值转换为低精度时会丢失精度。<br>例如int32转换为int16,float32转换为int<br>
底层结构相同的两个类型之间可以互转
与字符串有关的转换
strconv包
基本类型
布尔类型
bool
数值类型
int 、uint
有符号和无符号目前都是32位
rune, int8, int16, int32, int64 <br>byte, uint8, uint16, uint32, uint64 <br>
不同类型 不允许相互赋值操作 <br>
float32, float64 <br>
没有float, 默认为float64 <br>
complex64,complex128 <br>
字符串
string
两种形式
反引号字符串
raw string literal
其中字符是未解释的
例子
`中国`
多行字符串用反引号 ` ` <br>
双引号字符串
Interpreted string literals
其中字符是已解释的
string[1:] 切片操作
string与[]byte 互转<br>
字符串转数组 arr := []byte(str)
数组转字符串 str := string(arr)
参见
容器类型
数组 array
变量声明
var name [n]type
type为元素类型, 不同长度的数组视为不同类型 <br>
[...] 可以省略长度 <br>
a := [...]int{12, 78, 50}
初始值为0
var a [3]int //a:[0 0 0]
var name [n][m]type 多维数组声明 <br>
字面值
a := [3]int{1,2,3}
a := [...]int{1,2,3}
长度
len(a)
遍历
for...len
for i := 0; i < len(a); i++ { <br> ...<br> }
for...range
for i, v := range a { <br> ...<br>} <br>
for i := range a { // ignores value<br> ...<br>} <br>
for _, v := range a { // ignores index <br> ...<br>} <br>
for range a { // ignores index and value<br> ...<br>} <br>
array 是值类型
数组间的赋值是值的赋值,而不是指针 <br>
切片 slice
变量声明
var name []type <br>
切片的零值为nil。 对于nil,len和cap函数都将返回0
字面值
s := []byte { 'a', 'b', 'c' } <br>
s := []int{1,2,3}
make
func make([]T, len, cap) []T <br>
s := make([]byte, 5, 5)<br>// s == []byte{0, 0, 0, 0, 0}
s := make([]byte, 5) <br>// len==cap==5
append
func append(s []T, x ...T) []T <br>
容量不足则自动扩展
a := make([]int, 1)<br>// a == []int{0}<br>a = append(a, 1, 2, 3)<br>// a == []int{0, 1, 2, 3}
copy
func copy(dst, src []T) int <br>
切片
s := []int{1,2,3,4,5}<br>s1 := s[2:4] //s1 {3,4}
<br>
遍历
for...len
同array
for...range
同array
slice 属于引用类 <br>
参考资料
字典 map
变量声明
var name map[string]int<br>
字面量
m := map[int]string{1:"a", 2:"b", 3:"c"}
make
name := make( map[string]int ) <br>
设置|修改
m["key"] = 1
读取
v := m["key"]
如果k在m中不存在,则将value类型的零值赋值给v
逗号表达式
if v, ok := m["key"]; ok {<br> fmt.Printf("v:%+v\n", v)<br> } else {<br> fmt.Printf("不存在\n")<br> }
删除
delete(m,"key")
遍历
for k := range m {<br> ...<br>}
for k,v := range m {<br> ...<br>}
for _,v := range m {<br> ...<br>}
map 属于无序的, 每次打印都会不一样 <br>
map 属于引用类型 <br>
容器库
list
heap
ring
抽象数据类型(ADT)
ADT
定义
为类型的属性和可对类型执行的操作提供一个抽象的描述。不受特定的实现和编程语言的约束。<br>这种正式的抽象描述被称为抽象数据类型(Abstract Data Type,ADT)<br>
组成
数据的定义
操作的定义
封装数据和操作。
使用步骤
定义一个数据类型。数据+操作
开发一个实现该ADT的编程接口
编写代码实现这个接口
Go中的ADT
数据与操作的结合,通过method
方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。
使用method,再结合interface,可以实现多态
数据与操作的结合,通过function
将指向数据的指针,作为函数的第一个参数
例子
注:经编译器处理后,两种方式是等价的,方法会被转换成函数
结构体
定义结构体
例子
type Circle struct {<br> x int<br> y int<br> Radius int<br>}
占位(padding)
struct {<br> x, y int<br> u float32<br><font color="#f15a23"><b> _ float32 // padding<br></b></font> A *[]int<br> F func()<br>}
匿名内嵌字段(embedded fields)
struct {<br> T1 // field name is T1<br> *T2 // field name is T2;T2不能是接口不能是指针<br> P.T3 // field name is T3<br> *P.T4 // field name is T4<br> x, y int // field names are x and y<br>}
标记(tag)
struct {<br> x, y float64 "" // an empty tag string is like an absent tag<br> name string "any string is permitted as a tag"<br> _ [4]byte "ceci n'est pas un champ de structure"<br>}
访问控制
导出(exported)
结构名、字段名 首字母大写:包外可见
非导出(non-exported)
结构名、字段名 首字母小写:仅包内可见
通过<font color="#f15a23"><b>字面量</b></font>变量创建
KV形式
c :=Circle {<br> x: 100,<br> y: 100,<br> Radius: 50, // 注意这里的逗号不能少<br> }
c :=Circle {<br> Radius: 50, <br> }<br>// x,y 省略,初始化为对应的零值
顺序形式
c := Circle {100, 100, 50}
通过<b style=""><font color="#f15a23">关键字</font></b>创建变量
var
var c Circle
new
c := new(Circle) <br>// c是一个指针,等同于下面这种形式<br>c := &Circle{}
结构体复值
按值拷贝,浅拷贝。
例子
a := Circle {100, 100, 50}<br>b := a
a 和 b 是两个独立变量,a的值被拷贝到b(按位拷贝)
结构体大小
fmt.Println(unsafe.Sizeof(c))
参考链接
函数
定义
函数定义
func Add(x,y int)int{<br> return x+y<br>}<br>
返回多值 <br>
如果声明了返回变量名, 则返回时可以不带变量名 【不推荐用】
如果有error,通常放在最后
可变参数
func sum(arg ...int){ }
sum(1, 2, 3)
sum([]int{1, 2, 3)...)
支持: sum(slice...) <br>
不支持:sum(array...)
访问控制
函数名首字母大写是public,函数名首字母小写private私有方法<br>
注意事项
不支持默认值参数。
不支持函数重载。
不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数
函数类型
函数是一类对象
定义函数类型
type Op func(int,int) int
显示函数签名
函数类型又叫函数签名
func add(a,b int) int {<br> return a + b<br>}<br>func main() {<br> fmt.Printf("%T\n",add) //打印效果 func(int, int) int<br>}
两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型相同)
函数变量
场景:引用匿名函数
f := func(a,b int){<br> return a+b<br>}<br>
场景:设置回调函数
函数变量经常作为回调函数,以入参传入
func do(f Op,a,b int) int { //定义一个函数,第一个参数是函数类型Op<br> return f(a,b) //函数类型变量可以直接用来进行函数调用<br>}
匿名函数与闭包
核心概念
闭包示意图
闭包是匿名函数与匿名函数所引用环境的组合 。<br>
匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。
匿名函数即函数类型的字面量
匿名函数,一定是出现在闭包里;也就是只能在函数体里面定义一个匿名函数
场景1-返回
return func
func fib() func() int {<br> a, b := 0, 1<br> return func() int {<br> a, b = b, a+b<br> return a<br> }<br>}
场景2-并发
go func
func foo( ){<br> go func( ){<br> }( )<br>}<br>
场景3-延迟
defer func
func foo( ){<br> defer func( ){<br> }( )<br>}<br>
场景4-函数变量
f :=func(){}
func foo( ){<br> f :=func( ){ }<br>}<br>
场景5-函数字面量
func(){}()
func foo( ){<br> func( ){<br> }( )<br>}<br>
错误处理
错误不像异常,因为错误没有什么特别之处,而未处理的异常可能导致程序崩溃. -Rob Pike
错误
错误用error来表示,error是一个接口
type error interface {<br> Error() string<br>}
程序有错误则返回error,无错误则返回nil
多值返回时,将error放最后
惯用法
v,ok = foo()<br>if !ok{<br> return;<br>}<br>bar()
生成error
errors.New("error ...")
fmt.Errorf(format string, a ...interface{})
go内置error很简单,有个开源库提供了强大的错误处理能力
https://github.com/pkg/errors
异常
异常处理机制( panic/recover ) <br>
panic
panic 表示程序遇到严重错误<br>
发生 panic 时,程序的行为 <br>
1)程序正常执行终止,进入 panic 状态.<br>2)从栈顶到栈底倒序执行 defer 函数.<br>3)如果有 defer 函数调用了 recover 函数, 那么 panic 被捕获, panic状态解除, defer 所在函数正常返回(程序进入正常状态).<br>4)否则直到 main 函数,然后异常退出.<br>5)打印栈跟踪信息,之后清理栈.
recover
仅在 defer函数中有效,获取panic 输入值
配合使用
panic 函数的参数 interface{} 类型,可以传给它任何值.<br>对应的处理函数是recover,如果它捕获了panic,那么返回值是传给panic函数的值.<br>如果没捕获panic,那么返回值是nil. 尽量用error接口的值传给panic函数,以备recover函数捕获后分析. <br>
defer func(){<br> if err := recover(); err != nil{<br> }<br>}<br>panic(errors.New("some errors")) <br>
例子
regexp 是Go语言的正则表达式包,正则表达式需要编译后才能使用,而且编译必须是成功的,表示正则表达式可用。
处理为error
func Compile(expr string) (*Regexp, error)
处理为panic
func MustCompile(str string) *Regexp
Must惯用法
Must 开头的函数一般要求正确的输入,如果输入不正确则引发 panic
defer
函数执行到最后时, 会按照逆序执行defer
场景1-释放资源
defer file.close()
场景2-捕获异常
defer func(){<br> if err := recover(); err != nil{<br> }<br>}<br>panic(errors.New("some errors")) <br>
方法
例子
// Receiver of custom defined struct type.<br>type Book struct {<br> pages int<br>}<br><br>func (b Book) Pages() int {<br> return b.pages<br>}<br>func (b *Book) SetPages(pages int) {<br> b.pages = pages<br>}
方法声明
声明形式
func 接收器 方法原型 方法正文
可以给类型T和*T声明一个方法
要求
T必须是定义的类型<br>T的定义必须跟方法定义的在同一个包中<br>T不能是指针类型<br>T不能是interface类型。
这些类型不能声明方法
内置基本类型,例如int, string,因为我们不能在标准库里声明方法;<br>interface类型,但是一个interface可以拥有方法。<br>未定义类型,包括各种未命名的组合类型
类型别名
用type定义类型的别名时,别名类型<font color="#f15a23"><b>不会</b></font>拥有原始类型的方法。
方法原型
方法原型可以视为一个没有func关键字的函数原型
例子
Pages和SetPages的方法原型如下:<br>Pages() int<br>SetPages(pages int)<br>
方法值和方法调用
例子
b := Book{100}
方法值
b.Pages | b.SetPages
方法调用
b.Pages() | b.SetPages(99)
接收器(receiver)
T和*T被称作为他们定义的相应方法的接收器(Receiver). <br>T被称为类型T和*T声明的所有方法的接收器的基础类型<br>
值接收器
T
只要receiver是值类型的,无论是使用值类型的实例还是指针类型的实例,都是拷贝整个底层数据结构的,<br>方法内部访问的和修改的都是实例的副本。所以,如果有修改操作,不会影响外部原始实例。<br>
例子
指针接收器
*T
只要receiver是指针类型的,无论是使用值类型的实例还是指针类型的实例,都是拷贝指针,<br>方法内部访问的和修改的都是原始的实例数据结构。所以,如果有修改操作,会影响外部原始实例。<br>
例子
Go中所有参数都是值传递,接收器参数(T和*T)也是通过拷贝传入的
T 还是 *T ?
如果很难确定方法是否应该使用指针接收器还是值接收器,那么一般选择指针接收器(*T)方式。
一些考虑因素
指针副本太多可能会导致垃圾收集器的工作量增加。<br>如果值接收器类型的大小很大,则接收器参数复制成本可能不可忽略。指针类型都是小型的。<br>
惯用法
在其他语言中接收器一直被明确为this,在go语言中不推荐这么做,一般用一个简单字母表示
方法集
隐式函数
对每一个方法来说,编译器将为它声明一个相应的隐式函数
func (b Book) Pages() int {<br> return b.pages<br>}
方法->函数
func Book.Pages(b Book) int {<br> return b.pages // the body is the same as the Pages method<br>}
func (b *Book) SetPages(pages int) {<br> b.pages = pages<br>}
方法->函数
func (*Book).SetPages(b *Book, pages int) {<br> b.pages = pages // the body is the same as the SetPages method<br>}
隐式方法
对于声明给值接收器类型<font color="#f15a23">T</font>的每一个方法,编译器会隐式的给<font color="#f15a23">*T</font>声明一个有相同名字的方法。
func (b Book) Pages() int {<br> return b.pages<br>}
生成隐式方法,T->*T
func (b *Book) Pages() int {<br> return Book.Pages(*b)<br>}
方法集
每个类型都有一个方法集。<br>方法集是由所有的声明的方法的方法原型组成的。这些方法包含显示声明的和隐式声明的<br>
规则
T的方法集: 由接收器T定义的方法
T=T
*T的方法集: 由接收器T和*T定义的方法
*T=T+*T
例子
Book的方法集
Pages() int
*Book的方法集
<font color="#f15a23"><b>Pages() int //隐式生成<br></b></font>SetPages(pages int)
嵌套struct中的方法集
若类型 S 包含匿名字段 T,则 S 的方法集包含 T 的方法集。<br>若类型 S 包含匿名字段 *T,则 S 的方法集包含 T 和 *T 方法集。<br>不管类型 S 中嵌入的匿名字段是 T 还是*T,*S 方法集总是包含 T 和 *T 方法集。
例子
这些类型的方法集是空的
内置基本类型<br>定义的指针类型<br>基础类型是interface或者指针类型的指针<br>未定义的数组,切片,函数和通道类型
interface类型的方法集定义了它的接口;<br>非interface类型的方法集 决定了 该类型实现了哪些interface<br>
参考链接
链接1
链接2
接口
什么是接口
Go 接口是一组方法的集合,可以理解为抽象的类型。<br>
非侵入性
任何类型,只要实现了该接口中方法集,那么就属于这个类型。<br>无需在该类型上添加显示声明。<br>
定义
type Name interface{<br> //一组方法集合<br>}<br>
例子
多态
多态就是同一个接口,使用不同的实例而执行不同操作
鸭子类型
go没有 implements, extends 这些显式的关键字,go的实现方案是一种鸭子类型:看起来像鸭子, 那么它就是鸭子
泛型
Go可以用空接口和类型断言来实现一种动态泛型(区别于基于模板的静态泛型)
空接口
interface{}
空接口可以指向任何数据对象
例子
类型断言
接口值可有包含各种不同的具体类型值,而类型断言就是用于从接口中动态的区分出各种具体的类型,从而可以使用具体的类型。
参考链接
单变量
y := x.(T)
检查x的动态类型是否是T,其中x必须是接口值。
T是具体类型
类型断言检查x的动态类型是否等于具体类型T。
断言成功
类型断言返回的结果是x的动态值,其类型是T。
断言失败
panic
T是接口类型
类型断言检查x的动态类型是否满足T
断言成功
x的动态值不会被提取,返回值是一个类型为T的接口值
断言失败
panic
无论T是什么类型,如果x是nil接口值,则类型断言失败
双变量
y, ok := x.(T) <br>
断言成功
ok=true,y值同单值形式
断言失败
ok=false,y=nil,不会panic
惯用法
if f, ok := w.(*os.File); ok {<br> // ... use f ...<br>}
类型开关
type switch
例子
switch x.(type){<br>case nil: // 如果x是nil<br>case int, uint: <br>case bool:<br>case string;<br>default: //没有匹配上<br>}<br>//case的顺序是有意义的,因为可能同时满足多个接口,不可以用fallthrough, default的位置无所谓。
例子
提取具体的值
switch x := x.(type) { /* ... */ } <br>//前面的x是一个局部变量,因为switch创建了一个词法域<br>//x的类型就是每个case的类型<br>
值类型与引用类型
值类型
所有基本类型
固定数组 array
结构体
引用类型
可变数组 slice
字典
函数
接口
指针
channel
0 条评论
下一页