Go的顺序编程

变量

变量声明

Go语言引入了关键字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
var (
v1 int
v2 string
)//避免重复var关键字

变量初始化

对于声明变量时需要进行初始化的场景, var关键字可以保留,但不再是必要的元素,这里Go语言也引入了另一个C和C++中没有的符号(冒号和等号的组合:=),用于明确表达同时进行变量声明和初始化的工作。

var v1 int = 10 // 正确的使用方式1
var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型
v3 := 10 // 正确的使用方式3,编译器可以自动推导出v3的类型

出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误

变量赋值

var v10 int
v10 = 123

Go语言中提供了C/C++程序员期盼多年的多重赋值功能:

i, j = j, i//交换i,j变量

匿名变量

_, _, nickName := GetName()

常量

Go的常量定义可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它与字面常量一样,是无类型常量。

const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 无类型浮点常量
const (
size int64 = 1024
eof = -1 // 无类型整型常量
)
const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值
const a, b, c = 3, 4, "foo"
const mask = 1 << 3//可以是一个在编译期运算的常量表达式

Go语言预定义了这些常量: true、 false和iota。

iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。

const ( // iota被重设为0
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)

Go语言并不支持众多其他语言明确支持的enum关键字。同Go语言的其他符号(symbol)一样,以大写字母开头的常量在包外可见。
常规的枚举表示法:

const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays // 这个常量没有导出
)

类型

Go语言内置以下这些基础类型:
 布尔类型: bool。
 整型: int8、 byte、 int16、 int、 uint、 uintptr等。
 浮点类型: float32、 float64
 复数类型: complex64、 complex128。
 字符串: string。
 字符类型: rune。
 错误类型: error。
此外, Go语言也支持以下这些复合类型:
 指针(pointer)
 数组(array)
 切片(slice)
 字典(map)
 通道(chan)
 结构体(struct)
 接口(interface)

布尔类型

布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换

var b bool
b = 1 // 编译错误
b = bool(1) // 编译错误

整型

int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动做类型转换

var value2 int32
value1 := 64 // value1将会被自动推导为int类型
value2 = value1 // 编译错误
value2 = int32(value1) // 编译通过

Go语言支持的数值运算、比较运算、位运算与大部分语言一致。

浮点型

Go语言定义了两个类型float32和float64,其中float32等价于C语言的float类型,float64等价于C语言的double类型。

//类型被自动推导的fvalue2,需要注意的是其类型将被自动设为float64
fvalue2 := 12.0 // 如果不加小数点, fvalue2会被推导为整型而不是浮点型

复数类型

复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示
虚部(imag)

var value1 complex64 // 由2个float32构成的复数类型
value1 = 3.2 + 12i
value2 := 3.2 + 12i // value2是complex128类型
value3 := complex(3.2, 12) // value3结果同 value2

对于一个复数z = complex(x, y),就可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(z)获得该复数的虚部,也就是y。

字符串

在Go语言中,字符串也是一种基本类型。

var str string // 声明一个字符串变量
str = "Hello world" // 字符串赋值

字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始
化后被修改:

str := "Hello world" // 字符串也支持声明时进行初始化的做法
str[0] = 'X' // 编译错误

在Go语言中支持两个字符类型,一个是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。

数组

[32]byte // 长度为32的数组,每个元素为一个字节
[2*N] struct { x, y int32 } // 复杂类型数组
[1000]*float64 // 指针数组
[3][5]int // 二维数组

在Go语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内置常量,可以用Go语言的内置函数len()来获取。

需要特别注意的是,在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

数组切片

Go语言提供了数组切片(slice)来弥补数组的不足。

数组切片的数据结构可以抽象为以下3个变量:
 一个指向原生数组的指针;
 数组切片中的元素个数;
 数组切片已分配的存储空间。
基于数组,数组切片添加了一系列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复复制

创建数组切片

  • 基于数组
// 先定义一个数组
var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 基于数组创建一个数组切片
var mySlice []int = myArray[:5]

Go语言支持用myArray[first:last]这样的方式来基于数组生成一个数组切片:
基于myArray的所有元素创建数组切片:
mySlice = myArray[:]
基于myArray的前5个元素创建数组切片:
mySlice = myArray[:5]
基于从第5个元素开始的所有元素创建数组切片:
mySlice = myArray[5:]

  • 直接创建
    Go语言提供的内置函数make()可以用于灵活地创建数组切片。
    创建一个初始元素个数为5的数组切片,元素初始值为0:
    mySlice1 := make([]int, 5)
    创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:
    mySlice2 := make([]int, 5, 10)
    直接创建并初始化包含5个元素的数组切片:
    mySlice3 := []int{1, 2, 3, 4, 5}

元素遍历

  • 下标+len()
for i := 0; i <len(mySlice); i++ {
fmt.Println("mySlice[", i, "] =", mySlice[i])
}
  • range
for i, v := range mySlice {
fmt.Println("mySlice[", i, "] =", v)
}

动态增减元素

数组切片支持Go语言内置的cap()函数和len()函数,cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数。

mySlice := make([]int, 5, 10)
fmt.Println("len(mySlice):", len(mySlice))//5
fmt.Println("cap(mySlice):", cap(mySlice))//10
mySlice = append(mySlice, 1, 2, 3)
/*函数append()的第二个参数其实是一个不定参数,我们可以按自己
需求添加若干个元素,甚至直接将一个数组切片追加到另一个数组切片的末尾:*/
mySlice2 := []int{8, 9, 10}
// 给mySlice后面添加另一个数组切片,
mySlice = append(mySlice, mySlice2...)
/*
第二个参数mySlice2后面加了三个点,即一个省略号,如果没有这个省
略号的话,会有编译错误,因为按append()的语义,从第二个参数起的所有参数都是待附加的
元素。因为mySlice中的元素类型为int,所以直接传递mySlice2是行不通的。加上省略号相
当于把mySlice2包含的所有元素打散后传入。
mySlice = append(mySlice, 8, 9, 10)
*/

基于数组切片创建数组切片

/*oldSlicef元素范围甚至可以超过所包含的元素个数,比如newSlice
可以基于oldSlice的前6个元素创建,虽然oldSlice只包含5个元素。只要这个选择的范围不超
过oldSlice存储能力(即cap()返回的值),那么这个创建程序就是合法的。 newSlice中超出
oldSlice元素的部分都会填上0。*/
oldSlice := []int{1, 2, 3, 4, 5}
newSlice := oldSlice[:3] // 基于oldSlice的前3个元素构建新数组切片

内容复制

/*如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行
复制。*/
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个位置

map

变量声明
var myMap map[string] PersonInfo
创建
myMap = make(map[string] PersonInfo)
myMap = make(map[string] PersonInfo, 100)//初始存储能力为100的map
元素赋值
myMap[“1234”] = PersonInfo{“1”, “Jack”, “Room 101,…”}
元素删除
delete(myMap, “1234”)
如果“1234”这个键不存在,那么这个调用将什么都不发生,也不会有什么副作用。但是如果传入的map变量的值是nil,该调用将导致程序抛出异常(panic)。
元素查找

value, ok := myMap["1234"]
if ok { // 找到了
// 处理找到的value
}

流程控制

条件语句

if a < 5 {
	return 0
} else {
	return 1
}

注:
 条件语句不需要使用括号将条件包含起来();
 无论语句体内有几条语句,花括号{}都是必须存在的;
 左花括号{必须与if或者else处于同一行;
 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
 在有返回值的函数中,不允许将“最终的” return语句包含在if…else…结构中,否则会编译失败:function ends without a return statement。

选择语句

switch {
	case 0 <= Num && Num <= 3:
		fmt.Printf("0-3")
	case 4 <= Num && Num <= 6:
		fmt.Printf("4-6")
	case 7 <= Num && Num <= 9:
		fmt.Printf("7-9")
}

 左花括号{必须与switch处于同一行;
 条件表达式不限制为常量或者整数;
 单个case中,可以出现多个结果选项;
 与C语言等规则相反, Go语言不需要用break来明确退出一个case;
 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;
 可以 不设定switch之 后的条 件表达式, 在此种情况 下,整个switch结构与 多个if…else…的逻辑作用等同。

循环语句

Go语言中的循环语句只支持for关键字

sum := 0
for i := 0; i < 10; i++ {
	sum += i
}

for后面的条件表达式不需要用圆括号()包含起来

sum := 0
for {
	sum++
	if sum > 100 {
		break
	}
}

 左花括号{必须与for处于同一行。
 Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是, Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。

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循环同样支持continue和break来控制循环,但是它提供了一个更高级的
break,可以选择中断哪一个循环,如下例:

for j := 0; j < 5; j++ {
	for i := 0; i < 10; i++ {
	if i > 5 {
		break JLoop
	}
	fmt.Println(i)
	}
}
JLoop:
// ...

跳转语句

func myfunc() {
	i := 0
	HERE:
	fmt.Println(i)
	i++
	if i < 10 {
		goto HERE
	}
}

函数

函数定义

func Add(a int, b int) (ret int, err error) {

}

相同类型的变量:

func Add(a, b int)(ret int, err error) {
// ...
}

单一返回值:

func Add(a, b int) int {
	// ...
}

函数调用

Go语言中函数名字的大小写不仅仅是风格,更直接体现了该函数的可见性,这一点尤其需要注意。因此需要先牢记这样的规则:小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。

import "mymath"// 假设Add被放在一个叫mymath的包中
// ...
c := mymath.Add(1, 2)

不定参数

类型

func myfunc(args ...int) {
	for _, arg := range args {
	fmt.Println(arg)
	}
}

形如…type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。从内部实现机理上来说,类型…type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数。

传参

func myfunc(args ...int) {
	// 按原样传递
	myfunc3(args...)
	// 传递片段,实际上任意的int slice都可以传进去
	myfunc3(args[1:]...)
}

任意类型的不定参数
如果你希望传任意类型,可以指定类型为interface{}。使用interface{}仍然是类型安全的.

func Printf(format string, args ...interface{}) {
	// ...
}

匿名函数与闭包

匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式,在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。

f := func(x, y int) int {
	return x + y
}
func(ch chan int) {
	ch <- ACK
} (reply_chan) // 花括号后直接跟参数列表表示函数调用

闭包

  • 基本概念
    闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者
    任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含
    在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环
    境(作用域)。
  • 闭包的价值
    闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示
    数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到
    变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。
package main
import (
"fmt"
)
func main() {
	var j int = 5
	a := func()(func()) {
	var i int = 10
	return func() {
		fmt.Printf("i, j: %d, %d\n", i, j)
	}
	}()
	a()
	j *= 2
	a()
}

错误处理

error接口

Go语言引入了一个关于错误处理的标准模式,即error接口,对于大多数函数,如果要返回错误,大致上都可以定义为如下模式:

func Foo(param int)(n int, err error) {
	// ...
}

处理:

n, err := Foo(0)
if err != nil {
// 错误处理
} else {
// 使用返回值n
}

自定义错误

  1. 定义错误信息类型
type PathError struct {
	Op string
	Path string
	Err error
}
  1. 实现Error()方法
func (e *PathError) Error() string {
	return e.Op + " " + e.Path + ": " + e.Err.Error()
}
  1. 使用
func Stat(name string) (fi FileInfo, err error) {
	var stat syscall.Stat_t
	err = syscall.Stat(name, &stat)
	if err != nil {
		return nil, &PathError{"stat", name, err}//错误包装
	}
	return fileInfoFromStat(&stat, name), nil
}

defer

efer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。

func CopyFile(dst, src string) (w int64, err error) {
	srcFile, err := os.Open(src)
	if err != nil {
	return
}
defer srcFile.Close()
	dstFile, err := os.Create(dstName)
	if err != nil {
	return
}
defer dstFile.Close()
	return io.Copy(dstFile, srcFile)
}

这段代码可以运行,但存在’安全隐患’。如果调用dst, err := os.Create(dstName)失败,则函数会执行return退出运行。但之前创建的src(文件句柄)没有被释放。
修改如下:

func CopyFile(dstName, srcName string) (written int64, err error) {
	src, err := os.Open(srcName)
	if err != nil {
	return
	}
	defer src.Close()
	
	dst, err := os.Create(dstName)
	if err != nil {
		return
	}
	defer dst.Close()
	
	return io.Copy(dst, src)
}

以下是defer三条使用规则:

  1. 当defer被声明时,其参数就会被实时解析
  2. defer执行顺序为先进后出
  3. defer可以读取有名返回值

panic()和recover()

func panic(interface{})
func recover() interface{}

当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中
之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致
逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报
告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程。

recover()函数用于终止错误处理流程。一般情况下, recover()应该在一个使用defer
关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复
过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值