go语法

学习go的一些笔记,便于查阅。

数据类型

类型 描述 例子
布尔型 常量 true 或者 false var b bool = true
数字类型 u/int8/16/32/64,uintptr,float32,64,complex64,128
字符串 UTF-8编码
派生 (a) 指针类型(Pointer)
(b) 数组类型
(c) 结构化类型(struct)
(d) Channel 类型
(e) 函数类型
(f) 切片类型
(g) 接口类型(interface)
(h) Map 类型

类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:

type_name(expression)
type_name 为类型,expression 为表达式。

以下实例中将整型转化为浮点型,并计算结果,将结果赋值给浮点型变量:
var sum int = 17
var count int = 5
var mean float32

mean = float32(sum)/float32(count)
fmt.Printf("mean 的值为: %f\n",mean)

变量声明

var identifier type

第一种,指定变量类型,声明后若不赋值,使用默认值。
var v_name v_type
v_name = value
第二种,根据值自行判定变量类型。
var v_name = value
第三种,省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。
只能被用在函数体内
v_name := value
// 例如
var a int = 10
var b = 10
c : = 10
声明多个变量
var x, y int

全局变量就是在函数作用域外定义的变量。
//定义全局变量
var (
a string = "hello"
b string = "world"
c int
d int32
)

var e int = 32

func main() {
fmt.Println("hello world")

fmt.Printf("%s-%s-%f-%d", a, b, c, d)

fmt.Printf("全局变量e=%d", e)
}

Go还定义了三个依赖系统的类型,uintintuintptr。因为在32位系统和64位系统上用来表示这些类型的位数是不一样的。
对于32位系统
uint=uint32
int=int32
uintptr32位的指针

对于64位系统
uint=uint64
int=int64
uintptr64位的指针

常量

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

const identifier [type] = value

显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"

结构体

type person struct {
name string
age int
}

条件语句

if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}

if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}

if 布尔表达式 1 {
/* 在布尔表达式 1 为 true 时执行 */
if 布尔表达式 2 {
/* 在布尔表达式 2 为 true 时执行 */
}
}

switch var1 {
case val1:
...
case val2:
...
default:
...
}

select 语句
select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。

select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}

循环语句

go 语言只有 for 循环一种

for initialization; condition; post
{
// zero or more statements
}
initialization: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。

// a traditional "while" loop
for condition {
// ...
}

// a traditional infinite loop Like a C for(;;)
for {
// ...
// use break or return to exit
}

/* for 循环 */
for a := 0; a < 10; a++ {
fmt.Printf("a 的值为: %d\n", a)
}

for a < b {
a++
fmt.Printf("a 的值为: %d\n", a)
}

如果需要遍历array, slice, string, or map, or reading from a channel
可以使用range
for key, value := range oldMap {
newMap[key] = value
}
如果只需要第一个返回值,就扔掉第二个
for key := range m {
if key.expired() {
delete(m, key)
}
}
如果只需要第二个,那么使用'_'来扔掉第一个返回值
sum := 0
for _, value := range array {
sum += value
}

接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
/* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}

package main

import (
"fmt"
)

type Phone interface {
call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}

func main() {
var phone Phone

phone = new(NokiaPhone)
phone.call()

phone = new(IPhone)
phone.call()

}

在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call()。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法,输出结果如下:
I am Nokia, I can call you!
I am iPhone, I can call you!

空interface

数组

声明使用数组

var variable_name [SIZE] variable_type

var balance [10] float32

初始化数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
根据元素的个数来设置数组的大小
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
以下实例声明了三维的整型数组:
var threedim [5][10][4]int

向函数传递数组

方式一
形参设定数组大小:
void myFunction(param [10]int)
{
...
}

方式二
形参未设定数组大小:
void myFunction(param []int)
{
...
}

func main() {
/* 数组长度为 5 */
var balance = []int {1000, 2, 3, 17, 50}
var avg float32

/* 数组作为参数传递给函数 */
avg = getAverage( balance, 5 ) ;

/* 输出返回的平均值 */
fmt.Printf( "平均值为: %f ", avg );
}
func getAverage(arr []int, size int) float32 {
var i,sum int
var avg float32

for i = 0; i < size;i++ {
sum += arr[i]
}

avg = float32(sum / size)

return avg;
}

指针

定义使用

使用的时候使用.来操作数据。这里的指针更多的是用来区分传参类型,值还是引用。

func main() {
var a int = 10

fmt.Printf("变量的地址: %x\n", &a )
}

var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */

type Vertex struct {
X int
Y int
}

p := Vertex{1, 2}
q := &p
q.X = 10

空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。

func main() {
var ptr *int

fmt.Printf("ptr 的值为 : %x\n", ptr )
}
以上实例输出结果为:
ptr 的值为 : 0

if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */

指针数组

const MAX int = 3
var ptr [MAX]*int;

for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}

指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:
16dac5ec.png

var ptr *int
var pptr **int

a = 3000

/* 指针 ptr 地址 */
ptr = &a

/* 指向指针 ptr 地址 */
pptr = &ptr

切片(Slice)

Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

//定义Slice
var identifier []type
// user make create slice; len 是数组的长度并且也是切片的初始长度
slice1 := make([]type, len)

//也可以指定容量,其中capacity为可选参数。
make([]T, length, capacity)

//直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s :=[] int {1,2,3 }

//初始化切片s,是数组arr的引用, 用法和python类似
s := arr[startIndex:endIndex]

/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)

范围(Range)

Go 语言中 range 关键字用于for循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
返回index和value

在Go的for…range循环中,Go始终使用值拷贝的方式代替被遍历的元素本身,
就是说for…range中那个value,是一个值拷贝,而不是元素本身。

nums := []int{2, 3, 4}

for index, value := range nums {
fmt.Println("index: " + strconv.Itoa(index))
fmt.Println("value: " + strconv.Itoa(value))
}

// 这里不使用index的情况,使用下划线来占位
for _, value := range nums {
fmt.Println("value: " + strconv.Itoa(value))
}

Map(集合)

Map 是一种无序的键值对的集合

创建

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

var countryCapitalMap map[string]string
/* 创建集合 */
countryCapitalMap = make(map[string]string)

/* map 插入 key-value 对,各个国家对应的首都 */
countryCapitalMap["France"] = "Paris"
countryCapitalMap["Italy"] = "Rome"
countryCapitalMap["Japan"] = "Tokyo"
countryCapitalMap["India"] = "New Delhi"

/* 使用 key 输出 map 值 */
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
}

// 遍历map
for k, v := range m1 {
fmt.Println(k, v)
}

/* 查看元素在集合中是否存在 */
captial, ok := countryCapitalMap["United States"]
/* 如果 ok 是 true, 则存在,否则不存在 */
if(ok){
fmt.Println("Capital of United States is", captial)
}else {
fmt.Println("Capital of United States is not present")
}

简化写法
if v, ok := m1["a"]; ok {
fmt.Println(v)
} else {
fmt.Println("Key Not Found")
}

delete

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key

/* 删除元素 */
delete(countryCapitalMap,"France");

Type Alias

用途

type alias这个特性的主要目的是用于已经定义的类型,在package之间的移动时的兼容。
比如我们有一个导出的类型flysnow.org/lib/T1,现在要迁移到另外一个package中, 比如flysnow.org/lib2/T1中。

没有type alias的时候我们这么做,就会导致其他第三方引用旧的package路径的代码,都要统一修改,不然无法使用。

有了type alias就不一样了,类型T1的实现我们可以迁移到lib2下,同时我们在原来的lib下定义一个lib2下T1的别名,这样第三方的引用就可以不用修改,也可以正常使用,只需要兼容一段时间,再彻底的去掉旧的package里的类型兼容,这样就可以渐进式的重构我们的代码,而不是一刀切。

//package:flysnow.org/lib
type T1=lib2.T1

错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:

type error interface {
Error() string
}

我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

import (
"errors"
"fmt"
)

func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}

f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}

异常处理

Go中引入的Exception处理:defer, panic, recover
这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
defer可以多次,这样形成一个defer栈,后defer的语句在函数返回时将先被调用。
panic如果没有recover一般会导致程序挂掉

panic和recover函数在作用层面分别对等throw和catch语句
从设计层面来看,panic和recover函数适用于那些真正的异常(例如整数除0),而throw catch finally机制常常被用来处理一些业务层面的自定义异常。因此在go语言中,panic和recover要慎用。

package main

import (
"fmt"
)

func main() {

fmt.Println("begin test")

defer func() { // 必须要先声明defer,否则不能捕获到panic异常
fmt.Println("before recover")
if err := recover(); err != nil {
fmt.Println(err) // 这里的err其实就是panic传入的内容,55
}
fmt.Println("after recover")
}()
// panicByHand()
panicByCode()
fmt.Println("after panic code")

}

func panicByHand() {
fmt.Println("before panic")
panic("panic by hand")
fmt.Println("after panic")
}

func panicByCode() {
fmt.Println("before panic")
a := []int{3,4}
fmt.Println(a[2]) //should panic here
fmt.Println("after panic")
}

goroutine

由go运行时管理的轻量级的线程。
Goroutines运行在同一个地址空间,所以访问共享内存必须做同步处理。
‘sync’包提供了一些有用的基本类型

package main

import (
"fmt"
"time"
)

func main() {

fmt.Println("begin test")

go say("world")
say("hello")

}

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(1000 * time.Millisecond)
fmt.Println(s)
}
}

// 注意一个问题, go里面一个goroutine panic了, 会导致进程退出, 所以go func()时第一行带上
go func(){
defer func(){
if err:=recover(); err!=nil{
}
}()
}()