参考书:Go语言编程
第二章 顺序编程
1 变量声明使用关键字var ,如下
var v1 int
var v2 string
var v3 [10]int //数组
var v4 []int //数组切片
var v5 struct {
f int
}
var v6 *int //指针
var v7 map[string]int // map类型,key为string类型,value为int类型
var v8 func(a int) int
2 var可以将若干个同样需要声明的同种类型的变量放在一起,如下
var (
v1 int
v2 string
)
3 变量初始化
var v1 int = 10
var v2 = 10 // 编译器根据赋值自动定义变量类型
v2 := 10
4 用“:=”初始化变量时,左侧的变量必须是未声明过的变量,否则会导致编译错误。只能在函数体内出现
5 Go语言支持多重赋值,如下
i , j = j , i
6 Go语言支持匿名变量为了避免不会因为返回多个值的函数来定义一堆不需要的变量,如下
func GetName( ) ( firstName , lastName , nickName string) {
return "May" , "Chan" , "Chibi"
}
_ , _ , nickName := GetName( )
7 常量定义
const Pi float64 = 3.1415926535
const zero = 0.0
const (
size int64 = 1024
eof = -1
)
const u , v float32 = 0 , 3
const a , b , c = 3, 5.0 , "foo" // a = 3 , b = 5.0 , c = "foo"
8 常量定义的右值也可以是一个在编译期运算的常量表达式,如下
const mask = 1 << 3 // ok
const Home = os.GetEnv("HOME") //error , 因为os.GetEnv()只有在运行期才能知道返回结果,在编译期并不能确定
9 Go语言预定义了这些常量:true ,false和iota
10 iota在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1,如下
const ( // iota重置为0
c0 = iota // c0 = 0
c1 = iota // c1 = 1
c2 = iota // c2 = 2
)
const x = iota // x = 0
const ( // iota重置为0
c0 = iota // c0 = 0
c1 // c1 = 1
c2 // c2 = 2
)
补充:const中每新增一行常量声明将使
iota
计数一次(iota可理解为const语句块中的行索引)const ( n1 = iota // 0 n2 // 1 n3 // 2 n4 // 3 )
const ( n1 = iota // 0 n2 // 1 _ // 2 n4 // 3 )
const ( n1 = iota // 0 n2 // 1 h2 = 100 // 100 h3 // 100 n4 = iota // 4 n5 // 5 )
const ( a, b = iota + 1, iota + 2 //1,2 c, d //2,3 e, f //3,4 )
11 Go语言不支持enum关键字,Go常用const来实现枚举,如下
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
12 Go的基础类型,布尔类型(bool) ,整型(int8 , byte , int16 , int , uint , uintptr等) , 浮点类型(float32 , float64),复数类型(complex64 , complex128),字符串(string),字符类型(rune),错误类型(error),指针(pointer),数组(array),切片(slice),字典(map),通道(chan),结构体(struct),接口(interface)
13 布尔类型不能接受其他类型的赋值,也不支持自动或强制类型转换,如下
var b bool
b = true
c := (1 == 2)
b = 1 // 编译错误
b = bool(1) // 编译错误
14 整型
- int8 一个字节 -128 ~ 127
- uint8 一个字节 0 ~ 255
- int16 二个字节 -32768 ~ 32767
- uint16 二个字节 0 ~ 65535
- int32 四个字节 -2147483648 ~ 2147483647
- uint32 四个字节 0 ~ 4294967295
- int64 八个字节
- uint64
- int 平台相关,在32位平台下为4字节,64位平台下为8字节
- uint 平台相关,在32位平台下为4字节,64位平台下为8字节
- uintptr 平台相关,在32位平台下为4字节,64位平台下为8字节
注:int和int32在Go语言里是被认为两种不同的类型,编译器是不会帮助做类型转换的
var value1 int32
value2 := 64 // value2被自动推导为int类型
value1 = value2 // cannot use value2 (type int) as type int32 in assignment
value1 = int32(value2) // ok
15 两个不同类型的整型数不能直接比较,比如int8类型的数和int类型的数不能直接比较
16 Go语言定义了两个类型float32和float64,没有float类型。float32相当于C语言的float,float64相当于C语言的double。
fvalue := 12.0 // 默认推导为float64
17 因为浮点数不是精确的表达方式,直接用==来判断两个浮点数是否相等会有风险
18 复数
var value1 complex64
value1 = 3.2+12i
value2 := 1 + 3i
value3 := complex(3.2 , 12) // value3 := 3.2 + 12i
19 字符串,字符串的内容可以用类似于数组下标的方式获取。字符串是不可变类型
var str string
str = "Hello world"
ch := str[0]
20 字符串操作
x + y 字符串拼接 "Hello" + "123" // "Hello123"
len(s) 字符串长度 len("hello") // 5
s[i] 取字符 "Hello"[0] // 'H'
21 字符串遍历,Go语言支持两种方式遍历字符串,如下
- 字节数组的方式遍历
str := "Hello,world"
n := len(str)
for i := 0 ; i < n ; i++ {
ch := str[i] // ch的类型为byte
fmt.Println(i , ch)
}
- 以Unicode字符遍历
str := "Hello,world"
for i , ch := range str {
fmt.Println(i , ch) // ch的类型为rune
}
注:Go语言支持两种字符类型,一个是byte(uint8的别名),代表UTF-8字符串的单个字节的值,另一个是rune,代表单个Unicode字符。
22 数组的声明方式
- [32]byte
- [2*N] struct { x , y int32 } // 复杂类型数组
- [1000] *float64
- [3][5] int
- [2][2][2] float64
数组长度在定义后就不可更改,在声明时长度可以是常数也可以是变量。长度可以通过len( )函数获取
23 访问数组除了下标访问,可以使用关键字range,range会返回2个值,元素的数组下标和元素的值
for i , v := range array {
fmt.Println("Array element[" , i , "] = " , v)
}
24 在Go语言中数组是一个值类型,不是引用类型,在赋值或参数传递时都会生成一个副本,修改不会改变原始数组。
func main(){
array := [5]int{1,2,3,4,5}
array2 := array
array2[2] = 4
modify(array)
fmt.Println("In main(), array values: ",array)
fmt.Println("In main(), array2 values: ",array2)
}
func modify(array [5]int){
array[0] = 10
fmt.Println("In modify(), array values: ",array)
}
输出结果
In modify(), array values: [10 2 3 4 5]
In main(), array values: [1 2 3 4 5]
In main(), array2 values: [1 2 4 4 5]
24.1 补充:数组切片看似是一个指向数组的指针,实际上是有自己的数据结构,可以抽象为以下3个变量
- 一个指向原生数组的指针
- 数组切片中的元素个数
- 数组切片已分配的存储空间
25 创建数组切片的方法主要有两种 --- 基于数组和直接创建
- 基于数组创建
var myArray [10]int = [10]int {1,2,3,4,5,6,7,8,9,10}
var mySlice []int = myArray[:5]
- 使用内置函数make( )创建数组切片
mySlice1 := make([]int , 5) // 创建元素个数为5,元素值都为0的数组切片
mySlice2 := make([]int , 5 , 10) // 创建元素个数为5,元素值都为0,实际预留10个元素的存储空间。len(mySlice2)是5
mySlice3 := []int { 1 , 2 , 3 , 4 , 5 }
26 数组切片较数组多了一个存储能力(capacity),即元素个数和分配的空间可以是两个不同的值。
mySlice := make([]int , 5 , 10)
fmt.Println("len(mySlice): " , len(mySlice)) // 5,即元素个数
fmt.Println("cap(mySlice): " , cap(mySlice)) // 10 即数组切片分配的空间大小
27 数组切片新增元素可以使用append( ),如下
- mySlice = append(mySlice , 1 , 2 , 3) // 在切片尾新增3个元素1,2,3
- mySlice2 := []int { 8 , 7 , 9 }
mySlice = append(mySlice , mySlice2 . . . ) // 后面必须有省略号,表示将mySlice2的所有元素都传入
28 基于数组切片创建数组切片
oldSlice := []int { 1 , 2 , 3 , 4 , 5 }
newSlice := oldSlice[ : 3]
补充一点,切片和数组创建的一个区别
func f(k [3]int){ // 要求参数是元素个数为3的数组
fmt.Printf("%v\n",k)
}
func main(){
a := [...]int{1,2,3} // 元素个数是3的数组
b := []int{1,2,3}
f(a) // ok
f(b) // error
}
29 数组切片内容复制,copy( src , des ) 函数将des的元素赋值到src中,同时函数返回一个复制个数值。如果两个数组切片个数不一样,按个数小的个数进行复制,如下
slice1 := []int { 1 , 2 , 3 , 4 , 5 }
slice2 := []int { 5 , 4 , 3 }
copy(slice2 , slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1 , slice2) // 只会复制slice2的3个元素到slice1的前3个位置
30 map
type PersonInfo struct {
ID string
Name string
Address string
}
func main(){
var personDB map[string]PersonInfo
personDB = make(map[string]PersonInfo)
personDB["12345"] = PersonInfo{"12345","Tom","Room123"}
personDB["1"] = PersonInfo{"1","Tom","Room1"}
person,ok := personDB["12345"] // 找到了ok值为true,否则为false
if ok {
fmt.Println("Found person ",person.Name," with ID 1234.")
}else {
fmt.Println("Did not find person with ID 1234")
}
}
myMap := make(map[string] int) // 创建map
myMap := make(map[string] int , 100) // 创建一个初始存储能力为100的map
myMap := map[string] int { // 创建并初始化
"abc":123 ,
"bcd":333 ,
}
delete( myMap , "abc") // 删除某个key值的元素
31 map查找
value , ok := myMap["abc"] //查找会返回两个值,ok表示是否有这个元素
if ok {
// toDo
}
32 Go语言支持如下几种流程控制语句,还有break,continue和fallthrough
- 条件语句,关键字为if ,else 和else if
- 选择语句,关键字为switch,case和select
- 循环语句,关键字为for和range
- 跳转语句,关键字为goto
33 if语句,如下
if a < 5 { // 条件语句不用加小括号(),大括号{ }一定要有
return 0
} else {
return 1
}
34 switch语句,如下
i := 1
switch i { // 左大括号{一定要和switch同一行
case 0:
fmt.Println(0) // 不需要用break来声明退出case
case 1:
fmt.Println(1)
case 2:
fallthrough // fallthrough表示继续执行紧跟的下一个case
case 3:
fmt.Println(3)
case 4, 5, 6: // 单个case中可以出现多个结果选项
fmt.Println("4,5,6")
default:
fmt.Println("default")
}
switch{ // 可以不设定switch之后的条件表达式,只写一个switch
case 0<=i && i < 1: // 条件表达式可以不只限制为常量或整数
fmt.Println(0)
case 1<=i && i < 2:
fmt.Println(1)
}
35 Go语言循环语句只支持for,不支持while和do-while
for i := 0 ; i < 10 ; i ++ { // 条件语句不用加小括号( )
fmt.Println(i)
}
sum := 0
for { // 无限循环
sum ++
if sum > 100 {
break
}
}
a := []int{1,2,3,4,5,6,7}
for i , j := 0 , len(a) - 1 ; i < j ; i , j = i+1 , j-1 {
a[i] , a[j] = a[j] , a[i]
}
补充:Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量,即如下所示
for i:=1,j:=2;i<n;i++,j++ { // error, 不支持多个赋值语句
}
for i,j :=1,2;i<n;i,j=i+1,j+1 { // ok,支持平行赋值
}
36 Go语言的for循环支持continue和break来控制循环
37 跳转语句
func myfunc( ) {
i := 0
HERE :
fmt.Println(i)
i++
if i < 10 {
goto HERE
}
}
38 函数定义
func Add(a int , b int) (ret int , err error)
func Add(a , b int) int
39 函数调用,先导入该函数所在的包,调用即可,如下
import "mymath" // 假设Add被放在一个叫mymath的包中
c := mymath.Add(1 , 2)
40 Go语言有这样的规则,小写字母开头的函数只能在本包内可见,即为private,大写字母开头的函数才能被其他包使用,即public
41 不定参数,. . . type实际就是一个切片,即[ ] type
func myfunc( args . . . int ) { // 该函数可以接受不定数量的参数,并且这些参数类型都是int
for _ , arg := range args {
fmt.Println(arg)
}
}
myfunc(2 , 3) // ok
myfunc(1, 2, 3) // ok
42 不定参数的传递
func myfunc( args . . . int) {
myfunc3(args . . . ) // 不定参数的传递必须在后面加上省略号
myfunc3(args[1 : ] . . . ) // 不定参数的传递必须在后面加上省略号
}
43 任意类型的不定参数,使用interface{ }
func Printf(format string , args . . . interface{ })
44 函数的一个实例
func MyPrintf(args ...interface{}){
for _,arg:= range args{
switch arg.(type){
case int:
fmt.Println(arg," is an int value")
case string:
fmt.Println(arg, " is a string value")
case int64:
fmt.Println(arg, " is an int64 value")
default:
fmt.Println(arg, " is an unknown type")
}
}
}
func main(){
var v1 int = 1
var v2 int64 = 234
var v3 string = "hello"
var v4 float32 = 1.234
MyPrintf(v1,v2,v3,v4)
}
输出结果:
1 is an int value
234 is an int64 value
hello is a string value
1.234 is an unknown type
45 匿名函数由一个不带函数名的函数声明和函数体组成,可以直接赋值给一个变量或直接执行
f := func (a , b int , z float64 ) bool { // 没有函数名,直接给变量f赋值
return a * b < int(z)
}
func ( ch chan int ) {
ch <- ACK
} (reply_chan) // 函数后带括号即直接执行,将reply_chan值传入函数中
补充:46 闭包,Go的匿名函数是一个闭包
- 基本概念:闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)
- 闭包的价值:可以作为函数对象或匿名函数
在如下的闭包例子中,变量a指向的闭包函数引用了局部变量i和j,i的值被隔离了,在闭包外不能被修改。只有内部的匿名函数才能访问变量i,保证了i的安全性。
func main(){
var j int = 5
a := func()func(){ // 这个函数返回一个函数
var i int = 10
return func(){
fmt.Printf("i,j: %d , %d\n",i,j) // 引用了这个j变量,即外面j被修改,这里的j就引用了新值
}
}() // 加上这个括号表示,返回的函数是会执行。
a()
j *= 2
a()
}
47 error其实只是一个接口,定义如下
type error interface {
Error() string
}
48 一个函数可以有多个defer语句。defer语句调用顺序为先进后出,最后一个defer语句先执行
补充:Go语言引入了两个内置函数panic( )和recover( )以报告和处理运行时错误和程序中的错误场景:
func panic(interface{})
func recover() interface{}
- panic( ): 当函数执行过程中调用panic( )函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程。
- recover( ): 用于终止错误处理流程。一般情况下,recover( )应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。
49 当函数执行过程中调用panic( ),正常的函数执行流程将立即终止。recover( )用于终止错误处理流程
补充:Go语言标准库提供了用于快速解析命令行参数的flag包。如下所示
var infile *string = flag.String("i","infile","File contains values for sorting")
var outfile *string = flag.String("o","outfile","File to receive sorted values")
func main(){
flag.Parse()
if infile != nil {
fmt.Println("infile=",*infile," , outfile=",*outfile)
}
}
$ ./test1 -i "hello" -o "ok"
infile= hello , outfile= ok