一天搞懂Go语言(1)——程序结构和基本数据类型
程序结构
基础部分仅仅列举和其他语言不一样的地方(C语言为例)。
声明
Go语言有四个主要声明:var、const、type、func,类似于C语言中的变量,常量,结构体和函数。
package main //表明文件属于哪个包 import "fmt" const boilingF = 212.0 //包级别声明,类似于全局变量 func main() { var f = bologna //局部变量 var c = (f-32)*5/9 fmt.Printf("boiling point = %gF or %gC\n",f,c) }
变量使用
定义
通用形式:var name type = expression,type和expression可省略一个。类型省略,由expression决定;表达式省略初始值对应零值。Go语言不存在未初始化的变量。
var b,f,s=true,2.3,"four" //多类型声明
短变量形式:name:=expression,类型由expression决定,仅仅初始化一个新的变量才能用,同样可以声明多个变量如: i,j:=0,1 仅仅对可读性有帮助的时候才用这种形式。
指针使用
指针的概念和访问符号和C语言一样,只是定义时候不一样而已。
定义:*type
指针类型的零值是nil。
new函数
用法:new(T),创建一个T类型变量,初始化,并返回其地址。
go语言里new不是一个关键字,可以重定义为另外的其他类型。如果使用new作为自定义变量名字,那么这个变量作用域内new函数就不能用了。
变量生命周期
go语言变量的生命周期和C语言的略微有点不一样,C语言变量的生命周期几乎可以从它的作用域判断出来,而go语言变量的生命周期时通过它是否可达来确定的。
我们知道变量生命周期可以根据它所分配的空间来定。但是go语言编译器选择使用堆或栈上的空间不是基于使用var或new关键字的。看个例子:
var global *int func f(){ var x int x = 1 global = &x } func g(){ y:=new(int) *y=1 }
按照C语言习惯,局部变量使用的是栈空间,但这里x一定使用堆空间,因为它在f函数返回之后还可以通过global访问,这种情况我们称x从f逃逸。g函数返回后,y就不可访问了,可被回收,即便使用new,编译器也会在栈上分配*y。
垃圾回收对于写出正确程序有巨大帮助,但是变量的生命周期是写出高效程序所必须的。记住它在性能优化时候是有好处的,因为每一次变量逃逸都需要一次额外的内存分配过程。
赋值
go语言支持多重赋值。在实际更新变量前,右边所有的表达式被推演(即被计算出来),当变量同时出现在赋值符两侧时候特别有用。
交换两个变量的值 x,y=y,x
但是独立语句更易读。
因为go语言函数支持多返回值,所以可以将不需要的值赋给空标识符 _,err = x.(T)
类型声明
用法:type name underlying-type
相当于C++的typedef关键字。
包和文件
包的作用和其他语言中的库或模块作用类似,用于支持模块化、封装、编译隔离和重用。gopl.io/ch1/helloworld包文件存储在目录$GOPATH/src/gopl.io/ch1/helloworld中。
每一个包给他的声明提供独立的命名空间。
包让我们可以通过控制变量在包外面的可见性或导出情况来隐藏信息;管理标识符是否对外可见规则:导出标识符以大写字母开头。
导出的包级别的声明在同一个包的所有文件中可见,因为包级别的常量名字以大写字母开头,可以使用packag.name来访问。
package声明前面紧挨着文档注释对整个包进行描述。
导入
go语言中每一个包通过import来导入。语言的规范没有定义从哪来以及含义,这依赖于工具来解释。导入声明可以给导入的包一个短名字,用来在整个文件中引用包的名字。
如果导入一个没有被引用的包,就会触发错误!!如果需要一个空导入,我们可以使用一个替代名字“_”来消除“未使用的导入”错误。
包的初始化从初始化包级别变量开始,在依赖已解析完毕的情况下,根据依赖的顺序进行。
如果导入两个名字一样的包,需要指定一个替代名字来避免冲突,重命名导入在没有冲突时也是非常有用的,每个导入声明从当前包向导入包建立一个依赖,如果这些依赖形成一个循环,go build工具会报错。
var a = b+c //最后初始化a var b = f() //然后初始化b var c = 1 //首先初始化 func f() int { return c+1 }
如果包由多个文件组成,初始化按照编译器收到的文件顺序进行:go工具会在调用编译器前将.go文件进行排序。
go语言数据分为四大类:基础类型(basic type)、聚合类型(aggregate type)、引用类型(reference type)、接口类型(interface type)。
基础类型
go语言的基本数据类型有数字,字符串和布尔型。对于数字类型,常见操作以及运算符go都支持,不在介绍。需要注意的是在位运算符中:^ 如果作为二元运算符表示异或;如果作为一元前缀运算符,表示按位取反。运算符&^表示按位清除,比如z=x&^y中,若y的某位是1,则z对应0,否则等于x对应位。
类型不等的变量之间不支持隐式转换。
fmt.Printf("%d %[1]o %#[1]x",123) //fmt两个小技巧 副词[1]表示重复使用第一个操作数;副词#表示输出前缀0 0x等
整型
关键字:int8 int16 int32 int64,相应无符号加上u。此外还有两种类型int,uint,大小与原生有符号和无符号相同。
rune类型是int32类型的同义词,当然byte类型是uint8的同义词。虽说相同,但也需要进行强转。
uintptr大小并不明确,但足以完整存放指针,仅仅用于底层编程。
浮点数
float32盒float64。math包给出了浮点数的极限,比如常量math.MaxFloat32。除了大量常见的数学函数,math还可以判断NaN,使用math.IsNaN函数,还可以用math.NaN当作信号值,因为与NaN比较总不成立。
复数
go具备两种大小复数:complex64和complex128,二者分别有float32和float64构成。
var x complex128 = complex(1,2) real(x) //提取实部 imag(x) //提取虚部
x:=1+2i //如果数字后面紧跟i,它就变成了一个虚数
cmplx.Sqrt(-1) //math/cmplx包提供了复数运算所需的库函数
布尔值
概念不在介绍。
bool无法隐式转换成0或1,如果转换操作经常用,可以写一个函数。
字符串
概念不在介绍,但要注意字符串是不可变的字节序列。
len(s)可以返回字符串字节数,字符串的第i个字节不一定是第i个字符。
s[i:j] //生成字串,省略i,j则相当于从头开始和一直到末尾,和python的切片操作一样 s[0] = 'L' //编译错误 //尽管我们可以通过+运算符连接一个字符串,但不允许修改原有的值
不可变意味着两个字符串能安全的共用同一段底层内存,使得复制任何长度字符串的开销都很低廉。比如赋值和生成字串的操作都没有分配内存。我们知道在C中字符串是一个地址常量,指向字符串首地址。这里我们可以想象成,字符串的赋值是赋的首地址和长度,首地址可以用来共用内存,长度可以控制访问范围。
字符串和字节slice
4个标准包对字符串操作特别重要:bytes、strings、strcov、unicode。
- strings:提供了很多函数,用于搜索、替换、比较、修整、切分与连接字符串,Tolower、ToUpper等。
- bytes:用于操作字节slice([]byte类型),由于字符串不可变,因此按照增量方式构建字符串会导致多次内存分配和赋值。这种情况使用bytes.Buffer类型会更高效。
- strconv:此包主要用于转换。
- unicode:此包由判别文字符号值特性的函数如:IsDigit、IsLetter、IsUpper和Islower
path包和path/filapath包提供用来操作文件路径等具有层次结构名字的函数,path包处理以'/'分段的路径字符串,path/filapath包根据宿主平台的规则处理文件名。
下面看一个例子,接受整数字符串,从右侧开始每3位插入一个逗号,“12345”-->“12,345”
func cmma(s string) string{ n:=len(s) if n<3 { return s } return comma(s[:n-3])+","+s[n-3:] }
字符串可以和字节slice相互转换,字节有种C语言中char类型的味道
s:="abc" b:=[]byte(s) s2:=string(b)
概念上,[]bytes转换操作会分配新的字节数组,拷贝赋值生成一个引用,指向整个数组。具备优化功能的编译器在某些情况下可能会避免分配内存和复制内容,但一般而言,复制有必要确保s的字节保持不变(即使b的字节在转换后发生改变)。反之,为保证s2不变,string(b)也会产生一份副本。
bytes包为高效处理字节提供了Buffer类型,起初为空,大小随着各种类型的数据写入(byte,string,[]byte)而增长,类似于动态数组。
func intsToString(values []int) string{ var buf bytes.Buffer buf.WriteByte('[') for i,v:=range values{ if i >0 { buf.WriteString(", ") } fmt.Fprintf(&buf,"%d",v) } buf.WriteByte(']') return buf.string() }
字符串和数字的相互转换
strconv包来完成。
//整数转字符串,两种方法 x:=123 y:=fmt.Sprintf("%d",x) fmt.Println(y,strconv.Itoa(x)) //字符串转整数 x, err:=strconv.Atoi("123") y, err:=strconv.ParseInt("123",10,64) //10进制,最长64位相当于转换成int64
常量
const pi =3.1415 //单个定义,type一般省略 const( e = 2.7182 pi = 3.1415 ) //多个定义 //若同时声明一组常量,除第一项之外,其他项在等号右侧的表达式都可以省略,这意味着会复用前面一项的表达式及其类型 const( a = 1 b c = 2 d )
常量生成器iota
隐式赋值,itoa从0开始取值,逐项加1。
type Weekday int const( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday ) //更复杂的表示 const( _ = 1<<(10*iota) KiB //1024 MiB //1048576 GiB )
无类型常量
虽然常量可以是任何基本数据类型,但是许多常量并不从属某一具体类型。编译器将这些丛属类型待定的常量表示成某些值,这些值比基本类型的数字精度更高。
只有常量才可以是无类型的,并且出现等号右边时候可以隐式转换。例如 var f float = 2,整数常量2隐式转换float。如果没有显示指定类型,则隐式转换成变量的默认类型。