顺序编程

声明:内容来自《Go语言编程》

[TOC]

1 变量

1.1 声明

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
var ( 
    v1 int
    v2 string
) 

1.2 初始化

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

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

1.3 赋值

支持多重赋值功能:

i,j=j,i

1.4 匿名变量

在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量。这个时候就可以使用匿名变量来接收不需要的值。eg:

func GetName() (firstName, lastName, nickName string) { 
    return "May", "Chan", "Chibi Maruko" 
}

_,_,nickName := GetName()

2 常量

Go语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、 浮点型和复数类型)、布尔类型、字符串类型等。

2.1 字面常量

所谓字面常量(literal),是指程序中硬编码的常量。eg:

-12 
3.14159265358979323846 // 浮点类型的常量
3.2+12i // 复数类型的常量
true // 布尔类型的常量
"foo" // 字符串常量

Go语言的字面常量更接近我们自然语言中的常量概念,它是无类型的。只要这个常量在相应类型的值域 范围内,就可以作为该类型的常量,比如上面的常量-12,它可以赋值给int、uint、int32、 int64、float32、float64、complex64、complex128等类型的变量。

2.2 常量的定义

通过const关键字,给字面常量指定一个友好的名字。

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" 
// a = 3, b = 4, c = "foo", 无类型整型和字符串常量

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

常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达 式。

2.3 预定义常量

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

  • iota : iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被 重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式。
const ( // iota被重设为0
    c0 = iota // c0 == 0
    c1 = iota // c1 == 1
    c2 = iota // c2 == 2
)
const (
    a = 1 << iota // a == 1 (iota在每个const开头被重设为0)
    b = 1 << iota // b == 2
    c = 1 << iota // c == 4
)
const (
    u         = iota * 42 // u == 0
    v float64 = iota * 42 // v == 42.0 w = iota * 42 // w == 84
)
const x = iota // x == 0 (因为iota又被重设为0了)
const y = iota // y == 0 (同上)


const ( // iota被重设为0
    c0 = iota // c0 == 0 
    c1        // c1 == 1 
    c2        // c2 == 2 
)
const (
    a = 1 << iota // a == 1 (iota在每个const开头被重设为0) 
    b             // b == 2 
    c             // c == 4 
)

2.4 枚举

在const后跟一对圆括号的方式定义一组常量,这种定义法在Go语言中通常用于定义枚举值。

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

3 类型

  • 内置基础类型

    • 布尔类型:bool
    • 整型: int8、byte、int16、int、uint、uintptr等
    • 浮点类型:float32、float64
    • 复数类型:complex64、complex128
    • 字符串:string
    • 字符类型:rune
    • 错误类型:error
  • 复合类型

    • 指针(pointer)
    • 数组(array)
    • 切片(slice)
    • 字典(map)
    • 通道(chan)
    • 结构体(struct)
    • 接口(interface)

3.1 布尔类型

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

3.2 整型

类型 字节
int8 1
int16 2
int64 8
int 平台相关
uint8 1
uint16 2
uint32 4
uint64 8
uint 平台相关
uintptr 同指针(32位平台为4字节,63位平台下位8字节)
int32 4

整形之间不可以直接值转换,需要做强制类型转换。

var value2 int32
value1:=64
value2=int32(value1)

3.3 浮点型

类型 字节
float32 4
float64 8

3.4 复数类型

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

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

3.5 字符串

字符串不可以被修改,是常量。字符串操作包括:+、len(s)、s[i]等,更多操作,见strings包。

str := "Hello,世界"
for i, ch := range str { 
 fmt.Println(i, ch)//ch的类型为rune 
}

str := "Hello,世界"
n := len(str) 
for i := 0; i < n; i++ { 
 ch := str[i] // 依据下标取字符串中的字符,类型为byte 
 fmt.Println(i, ch) 
} 

3.6 字符类型

类型 备注
byte UTF-8字符串的单个字节的值
rune 代表单个Unicode字符

3.7 数组

数组就是指一系列同一类型数据 的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数 组的长度。

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

需要特别注意的是,在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。数组的长度在定义之后无法再次修改。

3.8 数组切片

与数组相比,数组切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的 值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能

数组切片的数据结构可以抽象为以下3个变量:

  • 一个指向原生数组的指针
  • 数组切片中的元素个数
  • 数组切片已分配的存储空间

数组切片添加了一系 列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复 复制

创建方法有两种:

  • 基于数组
  • 直接创建
// 先定义一个数组
 var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 
 // 基于数组创建一个数组切片
 var mySlice []int = myArray[:5] 
 fmt.Println("Elements of myArray: ") 
 for _, v := range myArray { 
 fmt.Print(v, " ") 
 } 
 fmt.Println("\nElements of mySlice: ") 
 for _, v := range mySlice { 
 fmt.Print(v, " ") 
 } 

//直接创建
//创建一个初始元素个数为5的数组切片,元素初始值为0:
mySlice1 := make([]int, 5) 
//创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:
mySlice2 := make([]int, 5, 10) 
//直接创建并初始化包含5个元素的数组切片:
mySlice3 := []int{1, 2, 3, 4, 5} 
  • cap()函数,返回的是数组切片分配的空间大小

  • len()函数,返回的是数组切片中当前所存储的元素个数。

  • append()函数,继续新增元素。

      mySlice2 := []int{8, 9, 10} 
      myslice := make([]int, 5, 10)
      myslice = append(myslice, 8, 9, 10)
      // 给mySlice后面添加另一个数组切片
      mySlice = append(mySlice, mySlice2...) 
      //上句等价于
      mySlice = append(mySlice, 8, 9, 10) 

    注意:在第二个参数mySlice2后面加了三个点,即一个省略号,如果没有这个省 略号的话,会有编译错误,因为按append()的语义,从第二个参数起的所有参数都是待附加的 元素。因为mySlice中的元素类型为int,所以直接传递mySlice2是行不通的。加上省略号相 当于把mySlice2包含的所有元素打散后传入。

  • copy()函数,将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行 复制。

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个位置

3.9 map

map是一堆键值对的未排序集合。

package main

import "fmt"

// PersonInfo是一个包含个人详细信息的类型
type PersonInfo struct {
    ID      string
    Name    string
    Address string
}

func main() {
    var personDB map[string]PersonInfo
    personDB = make(map[string]PersonInfo)
    // 往这个map里插入几条数据
    personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203,..."}
    personDB["1"] = PersonInfo{"1", "Jack", "Room 101,..."}
    // 从这个map查找键为"1234"的信息
    person, ok := personDB["1234"]
    // ok是一个返回的bool型,返回true表示找到了对应的数据
    if ok {
        fmt.Println("Found person", person.Name, "with ID 1234.")
    } else {
        fmt.Println("Did not find person with ID 1234.")
    }
}
  1. 变量声明

     var myMap map[string] PersonInfo 
  1. 创建

     myMap = make(map[string] PersonInfo) 
     myMap = make(map[string] PersonInfo, 100)//存储能力为100
     //创建并初始化
     myMap = map[string] PersonInfo{ 
      "1234": PersonInfo{"1", "Jack", "Room 101,..."}, 
     } 
  1. 元素赋值

     myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."} 
  1. 元素删除

     delete(myMap, "1234") 
  1. 元素查找

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

4 流程控制

流程控制语句的作用:

  • 选择
  • 循环
  • 跳转

go语言支持的流程控制语句

  • 条件语句
  • 选择语句
  • 循环语句
  • 跳转语句

为了满足更丰富的控制需求,Go语言还添加了如下关键字:breakcontinuefallthrough

4.1 条件语句

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

4.2 选择语句

switch i { 
 case 0: 
 fmt.Printf("0") 
 case 1: 
 fmt.Printf("1") 
 case 2: 
 fallthrough 
 case 3: 
 fmt.Printf("3") 
 case 4, 5, 6: 
 fmt.Printf("4, 5, 6") 
 default: 
 fmt.Printf("Default") 
}

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...的逻辑作用等同。

4.3 循环语句

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

a := []int{1, 2, 3, 4, 5, 6} 
for i, j := 0, len(a) – 1; i < j; i, j = i + 1, j – 1 { 
 a[i], a[j] = a[j], a[i] 
} 
  • 左花括号{必须与for处于同一行。

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

  • Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环,如下例( 本例中,break语句终止的是JLoop标签处的外层循环。):

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

4.4 跳转语句

oto语句的语义非常简单,就是跳转到本函数内的某个标签,如:

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

5 函数

5.1 不定参数

  1. 指定类型的不定参数
func myfunc(args ...int) { 
    for _, arg := range args { 
    fmt.Println(arg) 
    } 
} 

形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。它是一 个语法糖(syntactic sugar)。其本质是数组切片。

  1. 不定类型的不定参数

    如果你希望传任意类型,可以指定类型为 interface{}。用interface{}传递任意类型数据是Go语言的惯例用法。

func Printf(format string, args ...interface{}) { 
 // ... 
} 
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.")
        }
    }
}

5.2 匿名函数与闭包

  1. 匿名函数是指不需要定义函数名的一种函数实现方式。Go语言支持随时在代码里定义匿名函数。匿名函数可以直接赋值给一个变量或者直接执行。

     f := func(x, y int) int { 
      return x + y 
     } 
     func(ch chan int) { 
      ch <- ACK 
     } (reply_chan) // 花括号后直接跟参数列表表示函数调用
  2. Go的匿名函数是一个闭包

    • 基本概念

      闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义

    • 闭包的价值

      闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示 数据还要表示代码。

    • Go中的闭包

      闭包的实现确保只要闭包还被使用,那么 被闭包引用的变量会一直存在。

      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()
      }
      
      // 结果:
      // i, j: 10, 5 
      // i, j: 10, 10 

6 错误处理

6.1 error接口

Go语言引入了一个关于错误处理的标准模式,即error接口。

type error interface { 
 Error() string
} 

处理错误的方式:

if err != nil { 
 // 错误处理
} else { 
 // 使用返回值n 
} 

如何使用自定义的error类型?

  1. 定义一个用于承载错误信息的类型。Go语言中接口的灵活性,你根本不需要从 error接口继承。

     type PathError struct { 
         Op string
         Path string
         Err error 
     } 
  2. 实现Error()方法

     func (e *PathError) Error() string { 
         return e.Op + " " + e.Path + ": " + e.Err.Error() 
     }

6.2 defer

函数在退出前执行defer指定的操作。一个函数中可以存在多个defer语句,因此需要注意的是,defer语句的调用是遵照 先进后出的原则,即最后一个defer语句将最先被执行。

*6.3 panic 、recover

Go语言引入了两个内置函数panic()recover()报告和处理运行时错误和程序中的错误场景

panicrecover相当于python中的try except

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

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

package main

import (
    "fmt"
)

func divide() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("Runtime panic caught: %v\n", err)
        }
    }()

    var i = 1
    var j = 0
    k := i / j
    fmt.Printf("%d / %d = %d\n", i, j, k)
}

func main() {
    divide()
    fmt.Println("divide方法调用完毕,回到main函数")
}
GO语言快速入门 文章被收录于专栏

帮助GO初学者快速入门Golang

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务