第2章 顺序编程
2.1 变量
变量声明
变量声明的类型信息放在变量之后,放在数组的中括号之后,作返回值类型放在方法名之后。
不需要使用分号作为结束符。
//字串和哈希表都是内置类型,都小写
varv1int
varv2string
varv3[10]int
varv4[]int//数组切片
v4=make([]string,10)
varv5stuct{
fint
}
varv6*int
varv7map[string]int//map,key为string类型,value为int类型
varv8func(aint)int
Go语言是个静态类型语言,但以上写法看上去却像动态语言。
var只用来声明,直接不值不用写var。
varv1=1//编译器报错
go语言变量赋值不使用也报错。
变量初始化
注意类型推演符左侧的就是不应该是已被声明过的。
varv1int=10
varv2=10//不指明类型也可以变量推演
varv3:=10//使用类型推演符:=
==总结==:没声明的必须类型推演,只声明没指定类型的可以类型推演,已声明类型的不能类型推演。
不用类型推演:=,用变量必须声明var,可以不写类型。
交换2个变量的值直接用多重赋值
i,j=j,i
匿名变量
可以使用多重返回和匿名变量的写法,使代码更优雅。
funcmain() {
_,_,nickName:=GetName()
}
//多个同样类型的变量只声明一次就可以
funcGetName() (firstName,lastName,nickNamestring) {
return"May","Chan","Maruko"
}
字面常量
Go语言的字面常量是无类型的,只要值在某类型的值域范围内,就可以赋值(不用再写类型转换了,简化代码)。如-12可以赋给int、unit、int32、float64等。
-12
3.14159//浮点型常量
3.2+12i//复数常量
true//布尔型常量
"foo"//字串常量
常量定义
常量也可以限定类型,但不是必须的。
constzerofloat64=3.14159
constzero=3.14159// 无类型常量
const{
zeroint64=0
zero=0//无类型常量
}
==常量赋值是编译期行为==,常量定义的右侧值也可以是一个编译期运行的常量表达式。
constmask=1<<3
//os.GetEnv("HOME")是运行期返回结果,所以下行代码编译错误
constHome=os.GetEnv("HOME")
预定义常量
go语言有3个预定义常量:true、false、iota。
每const关键字出现时,iota被重置为0,每出现一次iota其值增1。
const{
c0=iota
c1=iota
c2=iota
}
两个const赋值表达式是一样的,则可以省略后一个(==方便之处在于枚举型赋值,但可能带来意外赋值==)。这种写法多用于枚举型。
const{
a=1<
b//b == 2
c//c == 4
}
枚举
go语言并不支持enum关键字。枚举使用const后跟一对==圆括号==定义一组常量。
const(
Sunday=iota
Monay
TuesDay
numberOfDays//这个常量没有导出
)
go语言中大写字母的常量在包外可见,相当于访问限制符。
2.3 类型
go语言内置以下基础类型(7个)
bool
byte int unit int8 int16 uint uintptr等。(整形一般用int unit就可以了,以免移植困难)
float32 float64
complex64 complex128
string
字符类型rune
错误类型error
go语言的复合类型(7个)
指针pointer
数组array
切片slice
字典map
通道chan
结构体struct
接口interface
布尔型
varv1bool=true
布尔型不接受其它类型的赋值,也不支持强制类型转换。
整形
int和int32是两种不同的类型,编译器不会自动转换。
value1:=64//自动推导为
varvalue2int32
value2=value1//编译器报错
go语言不会隐式的类型转换,不同类型的整型不能比较和赋值,如int8和int。但可以直接与字面值常量比较(无类型)。(没有隐式类型转换的烦恼)
不同类型赋值要强制类型转换
value2=int32(value1)//强制转换用静态方法
variint32
varjint64
i,j=1,2
ifi==j{}//编译器报错
ifi==1||j==2{}//编译通过
==取反运算符是^x==,这与C语言不同。
浮点型
varfvalue1
fvalue1:=12.0//加小数点,推导为浮点型。
float32等价于C语言中的float型,float64等价于C语言中的double型(浮点型自动推演的的类型)。
用==比较浮点型不稳定,使用以下方案:
import"math"
funcIsEqual(f1,f2,pfloat64){
returnmath.Fdim(f1,f2)
}
复数
复数表示
varvaluecomplex64
value1=3.2+12i
value2:=complex(3.2,12)
real(value1)//取实部
imag(value1)//取虚部
字串
varstrstring
str="hello"
ch:=str[0]
fmt.Printf("the length of \"%s\" is %d \n",str,len(str))
fmt.Printf("the first character of \"%s\" is %c"str,ch)
字串不能在初始化后修改
str:="hello"
str[0]"X"//编译错误
字串编码转换go只支持utf-8和unicode。其它可用iconv库转换。
字串操作:
x+y//字串连接
len(s)//取字串长度
s[i]//取字符
//两种字串遍历方式
str:="hello"
n:=len(str)
fori,ch:=rangestr{
fmt.Println(i,ch)
}
fori:=0;i
ch:=str[i]
fmt.Println(i,ch)//可以直接打印两个字符
}
字符
两种字符类型,byte表示UTF-8字串的单个字节值,rune代表单个Unicode字符。
数组
数组声明方法:
[32]byte
[2*N]struct{x,yint}//复杂类型数组
[1000]*float指针类型数组
[3][5]int//二维数组
[2][2][2]float64//等同于[2]([2]([2]float64))
varfVar[2][2]float64=[2][2]float64{{1,2},{1,2}}
数组可以是多维的,一旦定义长度后就不能修改。
go语言是值类型,在赋值和参数传递时会进行一次复制动作。因而在函数体中无法修改数组的内容,函数内操作的只是所传入数组的一个副本。
数组切片
数组在长度定义后无法修改,数组是值类型,这些无法满足开发者需求。
数组切片的数据结构可以抽象为以下3个变量:(类似于ArrayList)
指向原生数组的指针
数组切片的元素个数
数组切片已分配的存储空间
vararray1[10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
varslice1[]int=array1[:5]//使用数组的片段创建切片
slice2=make([]int,5)//创建元素个数为5的数组切片
slice3=make([]int,10)//创建元素个数5的数组切片,预留10个存储空间
slice4:=[]int{1,2,3,4,5}//直接创建5个元素的数组切片
len(slice1)
cap(slice1)//返回容量大小
这里书中有误:==make方法返回值==赋变量 ,无需类型推演。
==让编译器自行统计元素个数==:
b := [...]string{"Penn", "Teller"}
切片能够区分元素个数和容量。为避免扩容开销,容量由开发人员指定。
slice1:=append(slice1,8,9,10)//向切片末尾添加元素
slice1:=append(slice1,slice2)//添加切片
使用切片创建切片:
newSlice:=oldSlice[:3]//选择范围超过原切片,则补上0
切片复制:
slice1:=[]int{1,2,3,4,5}
slice2:=[]int(5,4,3)
copy(slice2,slice1)//只复制slice1的前3个元素到slice2中
copy(slice1,slice2)//复制slice2的元素到slice1的前3个位置
切片复制的原则是==不改变原切片长度==。
map
varmap1map[int]string
map1=make(map[int]string)
map1[1]="jack"
map1[2]="bill"
str,ok=map1[1]//查找键为1的数据
map的多返回值判断是否查找成功,使代码更简洁。
map初始化时可以设置容量:
map1=make(map[int]string,100)
添加元素如同元素赋值。
删除元素:
delete(map1,1)
struct(引自博客)
//结构体初始化
b := person{
name: "wyff",
age: 300,
}
2.4 流程控制
选择语句支持switch、case和select。循环语句支持for和rage。跳转语句支持goto。
if语句要求
不需要括号()包条件
无论几条语句,{}是必须的
左花括号必须与if/else在同一行
有返回值函数中,不允许将"最终"return放在if...else中。
if语句也可以在条件表达式前加赋值语句。
switch选择语句
switchi{
case0:
fmt.Print("0")
case1:
fmt.Print("1")
case2:
fmt.Print("2")
default:
fmt.Print("default")
}
switch后无表达式的写法:
varNum
switch{
case0
fmt.Print("0-3")
case4
fmt.Print("4-6")
}
//case不再是匹配项,而是表达式。
要求:
左花括号必须与switch处于同一行
条件表达式==不限制不整形或常量==,也可以不写
单个case可以出现多结果选项??
==不需要break来明确退出一个case==,只有加fallthrough关键字才继续执行下一个case。
switch后无表达式,与if...else逻辑等同。
循环语句
无限循还直接写,而不需要for(;;)和do{}while(1)
for{
}
条件表达式支持多重赋值:
foti,j=0,len(arr);i
a[i],a[j]=a[j],a[i]
}
支持continue break循环控制,还可以决定跳出哪个循还。
forj:=0;j<5;j++{
fori:=0;i<5;i++{
ifi>3{
breakJLoop
}
fmt.
}
}
JLoop://循环标签
==注意循还标签有冒号==。Java使用bool标志来实现。
跳转语句
HERE:
ifi<10{
gotoHERE
}
2.5 函数
相邻的类型相同的参数,可以只写一个类型声明
函数的返回值的变量需要命名,只有一个可以不命名
funcAdd(a,bint)(resint,errerror){
err=errors.New("have errors")
return
}
forAdd(a,bint)int{}//单个返回值
func(a*Integer)Add(i,jint)int{returnx+y}//类方法的写法,是否加*后面讲
函数和变量的小写仅在包内可见,大写才能被其它包使用。相当于访问限制符。
多返回值
匿名函数和闭包
匿名函数可以直接赋值给一个变量或直接执行。
f:=func(x,yint)int{
returnx+y
}
f:=func(x,yint)int{
returnx+y
}(1,2)//后面带参数表示直接执行
匿名函数是一个闭包。
闭包定义:包含自由变量(未绑定到对象)的代码块。要执行的代码块为自由变量提供绑定的计算环境(作用域)。
闭包的价值:用作函数对象或匿名函数,==支持闭包的语言将函数存储到变量作为参数传递给其它函数==。
闭包是函数式编程中很重要的概念。
funcmain() {
varjint=5
a:=func() (func()){
variint=10
returnfunc() {fmt.Printf("i,j:%d,%d",i,j)}
}()
a()
j*=2
a()
}
//闭包内的i值被隔离,不会被修改。
//fmt.Print()用法如java,fmt.Printf()用法如C。
错误处理
typeerrorinterface{
Error()string
}
将error作为多返回值的最后一个,用于函数的返回错误。
错误处理:
iferr!=nil{}
defer关键字
提前写关闭文件、连接的语句。java用finally,C++用包一个类在析构函数中关闭。
funcCopyFile(dst,srcstring) (wint64,errerror) {
srcFile,err:=os.Open(src)
iferr!=nil{
return
}
defersrcFile.Close()
dstFile,err:=os.Create(dst)
iferr!=nil{
return
}
deferdstFile.Close()
returnio.Copy(dstFile,srcFile)
}
panic()和recover()方法
错误处理流程:
当一个函数调用panic()函数时,正常的函数执行流程立即终止。
但使用defer关键字推迟执行语句正常展开执行。
函数返回到调用函数,逐层执行panic流程,直至所属的goroutine中所有执行的函数被终止。
报告错误信息,包括panic()函数传入的参数。
panic()的形参接收任意类型的数据。
recover()函数终止错误处理流程:如果没有在没有发生异常的goroutine中明确地调用恢复过程(使用recover()),则goroutine所属的进程==打印异常信息==后直接退出。