1.聽变量 2.聽命名 3.聽常量 4.聽基本类型 5.聽引用类型 6.聽类型转换 7.聽自定义类型
变量
Go语言有两种方式定义变量:
var聽关键字 :=聽短变量声明符
var关键字
var聽x聽int聽聽聽//自动初始化为0 var聽y聽=聽false聽聽聽//自动推断为bool类型
和C语言不同,类型被放在变量名之后,并且在运行时,为了避免出现不可预测行为,内存分配器会初始化变量为二进制零值。 如果显示初始化变量的值,可以省略变量类型,由编译器推断。
Go语言一次可以定义多个变量,并可以对其初始化成不同的类型,比如
var聽x,y聽int聽聽聽聽聽聽聽聽聽聽聽聽聽//相同类型多个变量 var聽a,聽s聽=聽10010,聽"hello聽go"聽聽聽聽//不同类型多个变量
按照Go语言的编码规范,建议以组的方式整理多行变量定义,比如:
var聽( 聽聽聽聽x,y聽int 聽聽聽聽a,s聽=聽10010,聽"hello聽go" 聽聽聽聽)
短变量声明符
在介绍词法的章节中已经看到过这种用法,比如:
聽聽聽聽x聽:=聽10010 聽聽聽聽a聽,聽s聽:=聽10010,聽"hello聽go"
这种方式很简单,但日常开发中新手经常犯下的一个错误,比如:
var聽x聽=聽10010 func聽main(){聽聽聽聽 聽聽聽聽println(&x,聽x) 聽聽聽聽... 聽聽聽聽x聽:=聽"hello聽go"聽//对全局变量重新定义并初始化 聽聽聽聽println(&x,聽x) }
可想而知最终酿成的后果;为了正确使用这种简单的短变量声明方式,请遵循如下限制:
聽聽1.聽定义变量的同时,显示初始化 聽聽2.聽不能提供数据类型 聽聽3.聽只能在函数体内部使用 聽聽4.聽函数体内不能使用短变量重复声明 聽聽5.聽不能使用短变量声明这种方式来设置字段值 聽聽6.聽不能使用nil初始化一个未指定类型的变量
思考下面的代码片段,输出结果是什么?
func聽test(){ 聽聽聽聽x聽:=聽1 聽聽聽聽fmt.Println(x) 聽聽聽聽{聽聽聽 聽聽聽聽聽聽聽聽fmt.Println(x) 聽聽聽聽聽聽聽聽x聽=聽3聽 聽聽聽聽聽聽聽聽x聽:=聽2 聽聽聽聽聽聽聽聽fmt.Println(x) 聽聽聽聽聽聽聽聽x聽=聽5 聽聽聽聽} 聽聽聽聽fmt.Println(x) }
如何检查你的代码中是否存在这样的声明?
# go tool vet -shadow yourfile.go
Go编译器会将未使用的局部变量当作错误,比如:
func聽main()聽{ 聽聽聽聽x聽:=聽1 聽聽聽聽var聽a聽int聽聽聽聽聽聽聽//a聽declared聽and聽not聽used 聽聽聽聽fmt.Println(x) }
那么思考一下下面的代码,会不会报错?
func聽main()聽{ 聽聽聽聽x聽:=聽1 聽聽聽聽const聽a聽=聽10010聽聽聽//聽??? 聽聽聽聽fmt.Println(x) }
所以要记住函数体中存在未使用的常量,不会引发编译器报错;
常量
常量表示运行时恒定不变的值,由const关键字声明,常量值必须是在编译期可确定的字符、字符串、数字或布尔值。 声明常量的同时可以指定其常量类型,或者由Go编译器通过初始化值推断,如果显示指定其常量类型,那么就必须保证常量左右值类型一致。 必要时要做类型转换。并且右值不能超出常量类型的取值范围,否则会造成溢出错误。比如:
const( 聽聽聽聽x聽,聽y聽=聽100,聽-10010 聽聽聽聽b聽byte聽=聽byte(x)聽聽聽聽//类型不一致需要做类型转换 聽聽聽聽c聽uint8聽=聽uint8(y)聽//溢出 聽聽聽聽)
思考:修改一下上面的代码,只进行声明而没有对其初始化和指定类型,结果会怎样?
const( 聽聽聽聽x聽=聽100 聽聽聽聽y 聽聽聽聽s聽string聽=聽"hello聽go"聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽 聽聽聽聽c ) fmt.Printf("%T聽,聽%v\n",y,y) fmt.Printf("%T聽,聽%v\n",c,c)
结论证明: 在常量组中如果不指定常量的类型和初始化值,则与上一行非空常量的右值相同
常量值可以是Go编译器能计算出结果的表达式,如unsafe.Sizeof, len,cap,iota等。
枚举
Go语言没有明确上的定义枚举enum类型,不过可以通过iota关键字来实现一组递增常量的定义,比如:
const聽( 聽聽聽聽_聽=聽iota 聽聽聽聽KB聽=聽1聽<<聽(10聽*聽iota)聽聽聽//聽1<<(10聽*聽1) 聽聽聽聽MB聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽//聽1<<(10聽*聽2) 聽聽聽聽GB聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽//聽1<<(10聽*聽3) 聽聽聽聽)
多常量定义中也可以使用iota,它们各自单独计算,确保常量组中每行常量的列数量相等即可,比如:
const( 聽聽聽聽a聽,b聽=聽iota,iota聽*10聽聽聽聽//0聽,聽0聽*聽10 聽聽聽聽c聽,d聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽//1聽,聽1聽*聽10 聽聽聽聽)
如果中断iota自增,需要显示恢复,且后续的常量值将按行序递增,比如:
const( 聽聽聽聽a聽=聽iota 聽聽聽聽b聽聽聽聽聽聽聽聽聽聽聽//b聽=聽??? 聽聽聽聽c聽=聽100 聽聽聽聽d聽聽聽聽聽聽聽聽聽聽聽//d聽=聽??? 聽聽聽聽e聽=聽iota聽聽聽聽//c聽=聽??? 聽聽聽聽f聽聽聽聽聽聽聽聽聽聽聽//f聽=聽???)
自增常量可以指定常量类型,甚至可以自定义类型,比如:
const( 聽聽聽聽f聽float聽=聽iota聽//指定常量类型)type聽color聽byteconst( 聽聽聽聽black聽color聽=聽iota聽聽//自定义类型 聽聽聽聽red 聽聽聽聽blue )
常量VS变量
常量是“只读”属性 变量在运行期分配内存,而常量是在编译期间直接展开,作为指令数据使用 数字常量不会分配存储空间,不能像变量那样可以进行内存寻址
比如:
const聽x聽=聽10聽const聽y聽byte聽=聽x聽聽聽聽//聽相当于const聽y聽byte聽=聽100const聽a聽int聽=聽10聽聽聽聽//显示指定常量类型,编译器做强类型检查const聽b聽byte聽=聽a聽聽//聽cannot聽use聽a聽(type聽int)聽as聽type聽byte聽in聽const聽initializer
基本类型
引用类型
特指slice,map和channel这三种预定义类型,他们具有比数字、数组等更复杂的存储结构; 引用类型的创建必须是用make函数来创建,因为除了分配内存之外,还有一系列属性需要初始化,比如指针, 长度,甚至包括哈希分布,数据队列等。 而内置函数new只是按照指定类型长度分配零值内存,返回指针。比如:
p聽:=聽new(map[string]int)聽//new函数返回指针 m聽:=聽*p m["go"]聽=聽1聽聽聽聽聽//panic:聽assignment聽to聽entry聽in聽nil聽map(运行时)
自定义类型
使用关键字type可以定义用户自定义类型,包括基于基本数据类型创建,或者结构体和函数等类型,比如:
type聽flags聽byteconst聽( 聽聽聽聽exec聽flags聽=聽1聽<<聽iota 聽聽聽聽write聽 聽聽聽聽read )
与var、const类似,多个type定义也可以合并成组,比如:
type聽( 聽聽聽聽聽聽聽聽user聽struct{ 聽聽聽聽聽聽聽聽聽聽聽聽name聽string 聽聽聽聽聽聽聽聽聽聽聽聽age聽int 聽聽聽聽聽聽聽聽} 聽聽聽聽聽聽聽聽f聽func(user) 聽聽聽聽)聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽//自定义一组类型 聽聽聽聽u聽:=聽user{"Tom",20}聽聽聽聽var聽say聽f聽=聽func(s聽user){ 聽聽聽聽聽聽聽聽fmt.Println(s.name,聽s.age) 聽聽聽聽} 聽聽聽聽say(u)
自定义类型与基础类型相比,它们具有相同的数据结构,但它们不存在任何关系,除操作符外,自定义类型不会继承基础类型的其它信息,它们属于完全不同的两种类型, 即它们之间不能做隐式转换,不能视为别名甚至不能用于比较表达式。比如:
聽聽聽聽type( 聽聽聽聽聽聽聽聽data聽int 聽聽聽聽)聽聽聽聽var聽d聽data聽=聽10 聽聽聽聽var聽x聽int聽=聽d聽聽聽聽聽聽聽//cannot聽use聽d聽(type聽data)聽as聽type聽int聽in聽assignment 聽聽聽聽println(x聽==聽d)聽//invalid聽operation:聽x聽==聽d聽(mismatched聽types聽int聽and聽data)
未命名类型
与有明确标志符的bool, int, string等类型相比,数组、切片、字典、通道等类型与具体元素类型或长度等属性有关,所以称作 未命名类型(unnamed type)。当然可以用type为其提供具体名称,将其改变为命名类型(named type)。
具有相同声明的未命名类型视作同一类型,比如:
1.聽具有相同基类型的指针 2.聽具有相同元素类型和长度的数组(array) 3.聽具有相同元素类型的切片(slice) 4.聽具有相同键值类型的字典(map) 5.聽具有相同数据类型及操作方向的通道(channel) 6.聽具有相同字段序列(字段名、字段类型、标签以及字段顺序)的结构体(struct) 7.聽具有相同签名(参数类型,参数顺序和返回值列表,不包括参数名)的函数(func) 8.聽具有相同方法集(方法名、方法签名、不包括顺序)的接口(interface)
结构体的tag经常被初忽视,它也属于结构体类型组成的一部分,而不仅仅是元数据描述。
类型转换
类型转换在日常开发中是不可避免的问题,Go语言属于强类型语言,除常量、别名以及未命名类型之外,其它不同类型之间的转换都需要显示类型转换;好处是我们至少能知道语句及表达式的确切含义,比如:
a聽:=聽10聽聽聽聽聽聽聽聽聽//int b聽:=聽byte(a)聽聽聽聽//byte c聽:=聽a聽+聽int(b)聽//确保类型一致性 d聽:=聽b聽+聽byte(a)//确保类型一致性
如果类型转换的目标是指针、单向通道、没有返回值的函数类型,那么为了避免语法歧义带来的类型转换错误,必须使用括号,比如:
x聽:=聽100 p聽:=聽*int(&x)聽//cannot聽convert聽&x聽(type聽*int)聽to聽type聽int 聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽//invalid聽indirect聽of聽int(&x)聽(type聽int) pc聽:=聽<-chan聽int(c)聽//invalid聽operation:聽pc聽<-聽3聽(send聽to聽non-chan聽type聽int)
正确做法如下:
p聽:=聽(*int)(&x)聽//(*int)(&x) (<-chan聽int)(c)聽//单向通道 (func())聽(f)聽聽聽聽聽//无返回值的函数 (func()int)聽(f)聽//有返回值的函数,但是也可以不用括号:func()int(f),但是为了影响程序可读性,建议加上括号
本文出自 “博学于文,约之于礼” 博客,转载请与作者联系!
有疑问加站长微信联系(非本文作者)