变量
声明
Go语言的变量声明需要类型后置
var v1 int
var v2 string
var v3 [10]int // 数组
var v4 []int // 数组切片
初始化和赋值
var v1 int = 10 // 显式说明,同时赋值
var v2 = 10 // 自动推导
v3 := 10 // 自动推导
变量声明和赋值是不同的
var v10 int
v10 = 123
多重赋值的功能:
i, j = j, i // 相当于交换变量的值
匿名变量
使用下划线即可,类比于python
func GetName() (firstName, lastName, nickName string) {
return "May", "Chen", "bilibili"
}
_, _, nickName := getName() // 下划线作为匿名变量
常量
字值值
编译期间就一直而且不可改变的值,可以是数值类型、布尔类型、字符串类型等。
常量的const
定义
const Pi float64 = 3.1415926
const zero = 0.0 // 无类型的
const (
size int64 = 1024
eof = -1
)
const u, v float32 = 0, 3 // 多重赋值
const a, b, c = 3, 4, "foo" // a==3 b==4 c=="foo"
常量在编译期间就会确定了,可以使无类型的。
itoa
是自动增长的,每遇到const
后就重设为0
const ( // itoa重设为0
c0 = itoa // c0 == 0
c1 = itoa // c1 == 1
c2 = itoa // c2 == 2
)
const (
a = 1 << itoa // 1
b = 1 << itoa // 2
c = 1 << itoa // 4
)
如果两个const
的赋值语句的表达式是一样的,那么可以省略后一个表达式,上述的可以改写为:
const ( // itoa重设为0
c0 = itoa // c0 == 0
c1 // c1 == 1
c2 // c2 == 2
)
const (
a = 1 << itoa // 1
b = 1 // 2
c = 1 // 4
)
枚举
枚举和上面的组内定义的const
一致,Go中没有enum
关键字,小写字母开头在包外不可见。枚举是没有类型的。
const (
Sun = itoa
Mon
Tue
Web
Thu
Fri
Sat
numberOfDays // 没导出
)
类型
内置类型
包括整型、浮点型、布尔型、字符串等,布尔型无法进行类型转换,其余的可以参考有关资料。新增了一个复数类型complex64
可以参考文档。。。字符串在初始化后,无法更改内容。
数组
声明方法,与变量类似,都是元素类型后置:
[32]byte // 长度为32字节的数组
[2*N]struct{x, y, int32} // 结构体数组
[1000]*float64 // 指针数组
[3][5]int // 二位数组 3*5
实际的使用方式,给数组特定的名称:
var arr1 [10]int // 声明
var arr2 = [5]int{1, 2, 3, 4, 5} // 声明并初始化
访问方式:
for i := 0; i<len(array); ++i{ // 下标遍历方式
}
for i, v := range array{ // 容器方式,i表示下标,v表示元素
// 对v的所有操作不影响array的值,只是原来元素的副本
}
与C/C++不同,数组作为函数的参数传递时,是按值传递的,必须使用数组切片才能传入原来的数组!
func foo(array [10]int) {
// 无法对原来的数组进行更改,只是原来数据的副本
}
数组可以直接进行赋值操作,但是也是赋值的副本,这与C、C++有区别:
a := [3]int{1, 2, 3}
b := a
b
和a
数组有相同的数值,但是b
不是a
的引用或者地址,b
是复制的a
的值,更改b
的不会影响a
的值。除非是使用切片。
数组切片
数组切片可以理解为指向数组的指针,同时拥有自己的数据结构。类似于C++的std::vector
,内部存储的还是原来数组的元素,如果对切片进行更改,就是更改原来数组的元素。
基于数组创建:
var array = [5]int{1, 2, 3, 4, 5}
mySlice := array[:] // 切取所有元素
mySlice1 := array[:4] // 切取前4个
mySlice2 := array[1:] // 切取后4个
mySlice3 := array[1:3]// 切取数据2 3
直接创建:
mySlice1 := make([]int, 5) // 元素个数为5的切片,初始值为0
mySlice2 := make([]int, 5, 10) // 初始元素个数为5,初始值为0,预留10个元素的存储空间
mySlice3 := []int{1, 2, 3, 4, 5} // 直接创建
在直接创建的过程中,其实使用一个匿名的数组被创建出来的。。。。。
遍历方法与数组一致。。。
数组切片动态的增减元素时,可以使用append
方法
mySlice := []int{8, 9, 10}
mySlice1 = append(mySlice, 1, 2, 3)
mySlice2 = append(mySlice, mySlice1...) // ...表示把元素打散后传入
使用copy()
函数进行内容复制。
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制前3个
copy(slice1, slice2) // 只会复制3个
map
是一个内置的数据类型。。
使用方式:
type PersonInfo struct{
ID string
Name string
Address string
}
var myMap map[string] PersonInfo // 声明
myMap = make(map[string] PersonInfo) // 创建,空的没有元素
myMap1 := make(map[string] PersonInfo, 100) // 指定了初始化的容量
myMap2 := map[string]PersonInfo{ // 带有初始值的初始化
"1234": PersonInfo{"1", "Jack", "Room 101, ..."}
}
delete(myMap, "1234") // 删除键值是1234的元素
value, ok := myMap["1234"] // 查找元素
if ok { // 找到了
// 处理找到的元素
}
流程控制
switch
每个case
最后不用添加break
关键字,自动跳出的。如果想要接着执行下一个case
,则需要使用fallthrough
关键字。一个case
可以匹配多个条件。
switch i {
case 1:
case 2:
fallthrough
case 3, 4, 5:
default:
}
循环
基本类似C/C++,主要区别是在for
上:
无限循环:
for{
// do smoething
}
支持多重赋值:
for i, j := 0, 100; i < j; i, j = i + 1, j - 1 {
// do something
}
支持goto
语句,与C语言一致。
for j := 0; j < 5; j++ {
for i := 0; i < 10; i++ {
if i > 5 {
goto JLoop
}
}
}
JLoop:
函数
函数定义的一般格式:
func func_name (arguements) (return values){
// func body
}
参数和返回值都是类型后置的。Go语言函数的参数都是按值传递的,要想改变原来的值,需要传递指针,这与C语言一样。
包外调用的函数的首字母必须大写才可见,小写只有包内可见。。
支持不定参数,类比于C++的 initializer_list
,但是这里的更高级,可以支持多类型。不定参数必须放在参数的最后才可以,...type
本质上是数组切片!
固定类型的不定参数:
func myfunc(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
任意类型的不定参数,需要类型的指定判断。
func MyPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
// do something
case string:
// do something
default:
// do something
}
}
}
函数可以返回多个值,函数执行不带任何参数的return语句时,会返回对应的返回值变量的值。
Go语言支持匿名函数
f := func(a, b int, z float32) bool {
return a*b < int(z)
}() // 匿名函数返回值,最后带有()表示直接调用
错误处理
error
接口
Go语言有一个标准的错误处理魔术,error
的接口定义如下:
type error interface {
Error() string
}
在写函数的时候,一般把error
作为最后一个返回值,一般的错误处理方法是:
func Foo(param int) (n int, err error) {
// function body
}
// 处理错误的方式
n, err := Foo(0)
if err!=nil{
// deal with error
}else{
// use return value n
}
具体实践应用留作后期补充
defer
类似于C++中析构函数的,实际上适用于处理需要释放的资源。
unc CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
在函数中,先defer
声明的,后释放,类似于构造函数的顺序。如果需要释放的资源过于繁杂,可以使用匿名函数的方式:
defer func() {
// 清理工作
}
panic
和recover
两者结合报告和处理运行时的错误,作用有点像try
和catch
。当出现panic
的时候,正常的函数流程结束,但是defer
会继续执行。panic
会沿着defer
向上传递,直到遇见recover
后者整个程序流程终止。panic
可以传入任何参数,recover
可以截获panic
传入的参数,并进行异常处理。
package main
import "fmt"
func main() {
defer func() { // 必须要先声明defer,否则不能捕获到panic异常
fmt.Println("c")
if err := recover(); err != nil {
fmt.Println(err) // 这里的err其实就是panic传入的内容,55
// 实际的工作流程中,在这里进行异常代码的处理
}
fmt.Println("d")
}() // 末尾的括号表示defer发生时,立刻进行调用匿名函数
f() // 测试函数
}
func f() {
fmt.Println("a")
panic(55) // 假设这里出现了异常
// 因为调用了panic,因此下面的都不执行,程序中断。
fmt.Println("b")
fmt.Println("f")
}
/*
输出结果:
a
c
55
d
*/
panic
和recover
就是异常处理,但是会影响程序的性能。因此,如果可以使用error
的方式简单处理,就不用这种方法。。