ywzou

ywzou blog


  • 首页

  • 标签

  • 分类

  • 归档

Go(九) 异常、错误

发表于 2019-01-21 | 分类于 编程语言

一、defer

defer确保在函数调用的时候执行(总是被执行),遵循先进后出的原则,参数在defer语句是计算

1
2
3
4
5
6
7
func main()  {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
return
fmt.Println(4)
}

输出的顺序为 3 2 1

示例:创建一个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func writeFile(fileName string){
file, e := os.Create(fileName)
if nil != e {
log.Println("异常:",e.Error())
return
}
//关闭io
defer file.Close()

//使用缓冲提高效率
writer := bufio.NewWriter(file)
// 保证释放 写入
defer writer.Flush()

fmt.Fprintln(writer,"Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。")
}

二、panic

panic是用来表示非常严重的不可恢复的错误的,向外抛出异常,程序不在往下执行,逐层向上返回,并执行每一层上面的defer;如果没有recover,程序结束运行

panic(“异常信息”)

1
2
3
4
5
6
7
func main()  {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
panic("error....")
fmt.Println(4)
}

输出的顺序为 3 2 1

三、recover

recover仅在defer条用中执行,可以获取到panic的信息,如无法处理,还可以重新panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func tryRecover()  {
//使用 defer 保证该匿名方法总被执行
defer func() {
r := recover()
if err,ok := r.(error); ok{
fmt.Println("recover 异常信息是 ",err)
}else{
panic("不是error类型的错误.")
}
}()

//制造一个异常
//panic("这是一个错误信息...") //不是error类型
panic(errors.New("这是一个错误信息..."))
}

四、自定义error

需要实现builtin包的接口type error interface {Error() string}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
type User struct {
name,pwd string
}

type LoginError struct {
msg string
}

//自定义异常
func (err *LoginError) Error() string {
return err.msg
}

func login(name string,pwd string) (*User,error) {
if name != "go" {
return nil, &LoginError{msg:"登录账号异常..."}
}

if pwd != "123456" {
return nil, &LoginError{msg:"密码错误..."}
}

return &User{name:name,pwd:pwd}, nil
}

func main() {
user, e := login("go", "123456")
if nil != e {
loginError,ok:= e.(*LoginError)
if !ok {
//未知的异常类型 不处理 抛出
panic(e)
}else{
fmt.Println(loginError.Error())
}
}else{
fmt.Println(user)
}
}

Go(八)使用信道遍历二叉树

发表于 2019-01-20 | 分类于 编程语言

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package concurrent

import "fmt"

type TreeNode struct {
value int
leftNode,rightNode *TreeNode
}

//结构体创建 工厂函数
func createNode(value int) *TreeNode {
return &TreeNode{value:value}
}

func (node TreeNode) print() {
fmt.Println(node.value)
}

func (node *TreeNode) traverseFun(f func(*TreeNode)) {
if nil == node {
return
}
f(node)
node.leftNode.traverseFun(f)
node.rightNode.traverseFun(f)
}

func (node *TreeNode) traverse() chan *TreeNode{
c := make(chan *TreeNode)
go func() {
node.traverseFun(func(node *TreeNode) {
c <- node
fmt.Println(node.value)
})
close(c)
}()
return c
}

func ChannelTraverseTree() {
//创建结构体 方式二
root := TreeNode{value:-1}

//创建结构体 方式三
//root := new(treeNode)

//构建树
root.leftNode = &TreeNode{value:1}
root.rightNode = &TreeNode{value:2}

root.leftNode.leftNode = createNode(11)
root.leftNode.rightNode = createNode(12)

root.rightNode.leftNode = createNode(21)
root.rightNode.rightNode = createNode(22)

fmt.Println(root)

c := root.traverse()
maxValue := 0
for node := range c{
if node.value > maxValue {
maxValue = node.value
}
}

fmt.Printf("最大的值是%d \n",maxValue)//22
}

Go(七)Go线程、信道、并发编程

发表于 2019-01-20 | 分类于 编程语言

Go并发理论是 基于CSP(Communication Sequential Process)模型,提倡不要通过共享内存来通通讯,而是通过共享通讯来共享内存

一、线程、协程

goroutine是由 Go 运行时管理的轻量级线程,只需要一个关键字go即可启动一个线程并执行;goroutine在相同的地址空间中运行,因此在访问共享的内存时必须进行同步,可使用sync实现同步。
Go原生的支持并发,这也是Go的一大特点。更像协程coroutine,是非抢占式的多任务处理,由协程主动交出控制权;不是系统层面的多任务,而是编译器、解释器、虚拟机层面的多任务;多个协程可以再一个或多个线程上运行

goroutine可能切换点
1、I/O,select
2、信道channel
3、等待锁
4、函数调用
5、runtime.Gosched()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func threadFun(index int)  {
for {
//Printf是io操作 或有协程测切换 主动交出资源
fmt.Printf("第 %d 个线程....\n",index)
}
}

func RoutineDemo() {
//开启10个线程
for i := 0; i < 10; i ++ {
go threadFun(i)
}
time.Sleep(time.Millisecond) //休眠一毫秒

var arr [5]int
for i := 0; i < 5; i ++ {
go func(i int) {
for {
arr[i] ++
//手动交出协程
runtime.Gosched()
}
}(i)
}
time.Sleep(time.Millisecond) //休眠一毫秒
fmt.Println(arr)//[632 791 755 545 785] 并不是平均的
}

二、信道

信道是带有类型的管道,你可以通过它用信道操作符<-(“箭头”就是数据流的方向)来发送或者接收值。

ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。

和映射与切片一样,信道在使用前必须创建

ch := make(chan int)

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得Go协程可以在没有显式的锁或竞态变量的情况下进行同步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//工作者
func worker(c chan int,id int) {
for {
//从channel接收值
n := <-c;
fmt.Printf("第 %d 个worker 从channel接收数据 %c \n", id, n)
}
}

//创建worker
func createWorker(id int) chan int {
c := make(chan int)
// 去工作接收信息
go worker(c, id)
return c
}

//创建worker 返回的channel只能接收数据 只能向channel发送数据
func createWorkeSend(id int) chan<- int {
c := make(chan int)
// 去工作接收信息
go worker(c, id)
return c
}

//创建worker 返回的channel只能接收receive数据 不能像返回值发送数据
func createWorkeReceive(id int) <-chan int {
c := make(chan int)
// 去工作接收信息
go func() {
//n := <-c;
c <- id
fmt.Printf("第 %d 个worker 从channel接收数据 %c \n", id, <-c)
}()
return c
}

//带缓冲的信道
func bufChannel() {
c := make(chan int,2)
c <- 1
c <- 2
fmt.Println(<-c)

}

func ChannelDemo() {
//创建一个信道
c := make(chan int)
//开启协程接收channel
go func() {
for {
n := <- c
fmt.Println(n)
}
}()

//向channel传递值
c <- 2
c <- 4
time.Sleep(time.Millisecond)

fmt.Println("=====接收channel的函数=====")
var channels [5]chan int
for i:= 0; i<5; i++ {
channels[i] = make(chan int)
go worker(channels[i],i)
}

//向channel发送值
for i:= 0; i<5; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)

fmt.Println("======返回channel的函数======")
var channels2 [5]chan int
for i:= 0; i<5; i++ {
channels2[i] = createWorker(i)
}

for i:= 0; i<5; i++ {
channels[i] <- 'A' + i
}

time.Sleep(time.Millisecond)

fmt.Println("======只能向返回的channel的发送数据 不能向返回channel接收数据======")
var channels3 [5]chan<- int
for i:= 0; i<5; i++ {
channels3[i] = createWorkeSend(i)
}

for i:= 0; i<5; i++ {
channels3[i] <- 'A' + i
//不能取数据 不能从channel取出数据 send-only
//n := <- channels3[i]
}

time.Sleep(time.Millisecond)

fmt.Println("======只能向返回channel接收数据 不能像返回的channel发送数据======")
var channels4 [5]<-chan int
for i:= 0; i<5; i++ {
channels4[i] = createWorkeReceive(i)
}

for i:= 0; i<5; i++ {
//不能像channel发送数据 只能接收数据 receive-only
//channels4[i] <- 'A' + i

n := <- channels4[i]
fmt.Println(n)
}

time.Sleep(time.Millisecond)

fmt.Println("======带缓冲的信道======")
bufChannel()
}

带缓冲的信道
信道可以是带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道,仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

ch := make(chan int, 100)

信道可以作为的函数的参数和返回值

func 函数名称(c chan int,其他参数…){函数体}
func 函数名称(参数…) chan int {函数体}

1
2
3
4
5
6
7
//带缓冲的信道
func bufChannel() {
c := make(chan int,2)
c <- 1
c <- 2
fmt.Println(<-c)
}

range 和 close
发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完

v, ok := <-ch

之后 ok 会被设置为 false
循环 for i := range c 会不断从信道接收值,直到它被关闭。

注意

  • 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
  • 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //斐波那契
    func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
    c <- x
    x, y = y, x+y
    }
    //关闭一个信道来表示没有需要发送的值了
    close(c)
    }

    func ChannelDemo() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
    fmt.Println(i)
    }
    }

等待任务结束
等待一个或多个任务的执行可使用sync.WaitGroup来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func doTaskWG(id int,c chan int, wg *sync.WaitGroup)  {
for n := range c{
fmt.Printf("任务接收第 %d 个任务,值为 %c \n", id, n)
wg.Done()//结束
}
}

type taskWG struct {
c chan int
wg *sync.WaitGroup
}

func createTaskWG(id int,wg *sync.WaitGroup) taskWG{
t := taskWG{
c: make(chan int),
wg: wg,
}
go doTaskWG(id, t.c, wg)
return t
}

func WaitGroupDemo() {
var wg sync.WaitGroup
var workers [5]taskWG
for i:=0; i<5; i++ {
workers[i] = createTaskWG(i, &wg);
}

wg.Add(10)//10个任务
for i, worker := range workers {
worker.c <- 'A' + i
}

for i, worker := range workers {
worker.c <- 'a' + i
}
//等待任务结束
wg.Wait()
}

select 语句
select 语句使一个 Go 协程可以等待多个通信操作,select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func genChannel() chan int {
c := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
c <- i
i++
}
}()
return c
}

func SelectDemo() {
var c1, c2 = genChannel(),genChannel()
//非阻塞是的接收值
//三秒结束
tm := time.After(time.Second * 3);
for {
select {
case n := <-c1 :
fmt.Printf("从c1接收到值%d \n",n)
case n := <-c2 :
fmt.Printf("从c2接收到值%d \n",n)
case <-tm:
fmt.Printf("程序执行结束....\n")
return
//default:
// fmt.Printf("没有值\n")
}
}
}

Go(六)面向函数和接口编程

发表于 2019-01-20 | 分类于 编程语言

Go语言仅支持封装,不支持继承和多态;没有类class,用结构体struct,主要是函数式编程和面向接口编程

一、方法

Go中方法就是一类带特殊的 接收者 参数的函数。方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
可将方法理解为是个带接收者参数的函数。

func (接收者) 方法名称(参数) 放回值 {方法体}

值接收者和指针接收者
要改变接收者是的时候必须使用指针接收者
结构过大也考虑使用指针接收者(性能、内存)
一致性:如果有指针接收者,最好都使用指正接收者
值接收者是Go特有特性。用于逻辑运算场景,不改变接收者的值
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用

值接收者

1
2
3
func (node treeNode) print()  {
fmt.Println(node.value)
}

指针接收者

1
2
3
func (node *treeNode) setValue(value int)  {
node.value = value
}

通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//定义一个简单的二叉树结构体
type treeNode struct {
value int
leftNode,rightNode *treeNode
}

//结构体的方法 接收者为值
func (node treeNode) print() {
fmt.Println(node.value)
}

//设置是 接收者为指针
func (node *treeNode) setValue(value int) {
node.value = value
}

//遍历二叉树
func (node *treeNode) traverse() {
if nil == node {
return
}
node.print()
node.leftNode.traverse()
node.rightNode.traverse()
}

//结构体创建 工厂函数
func createNode(value int) *treeNode {
return &treeNode{value:value}
}

//结构体创建演示
func StructDemo() {
//创建结构体 方式一
//var root treeNode

//创建结构体 方式二
root := treeNode{value:-1}

//创建结构体 方式三
//root := new(treeNode)

//赋值
root.leftNode = &treeNode{value:1}
root.rightNode = &treeNode{value:2}

root.leftNode.leftNode = createNode(11)
root.leftNode.rightNode = createNode(12)

root.rightNode.leftNode = createNode(21)
root.rightNode.rightNode = createNode(22)

fmt.Println(root)

//调用结构体方法
root.setValue(0)
root.print()
fmt.Println("===== 二叉树遍历 ====")
root.traverse()
}

二、接口

接口类型 interface 是由一组方法签名定义的集合。接口类型的变量可以保存任何实现了这些方法的值。
类型通过实现一个接口的所有方法来实现该接口。不需要专门显式声明,所以就没有“implements”关键字;隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
接口也是值,它们可以像其它值一样传递;接口值可以用作函数的参数或返回值。在内部,接口值可以看做包含值和具体类型的元组(value, type);接口值保存了一个具体底层类型的具体值。接口值调用方法时会执行其底层类型的同名方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//接口interface
type iPerson interface {
say() string
}

//结构体
type Person struct {
name string
}

//实现接口方法
func (p Person) say() string {
return p.name + " say hello!"
}

func describe(i iPerson) {
fmt.Printf("(%v, %T)\n", i, i)
}

func InterDemo() {
var i iPerson
ps := Person{name:"张三"}
i = ps
fmt.Println(i.say())
describe(i)//({张三}, mi.Person)

//隐式实现
var i2 iPerson = Person{name:"李四"}
fmt.Println(i2.say())
describe(i2)//({李四}, mi.Person)
}

空接口
指定了零个方法的接口值被称为 空接口,因为每个类型都至少实现了零个方法,所以空接口可保存任何类型的值;常被用来处理未知类型的值(例如: fmt.Print)

var i interface{}

类型断言
提供了访问接口值底层具体值的方式。

t := i.(T)

该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。若 i 并未保存 T 类型的值,则会panic

t, ok := i.(T)

若 i 保存了一个 T,那么 t 将会是其底层值,而 ok 为 true;否则,ok 将为 false 而 t 将为 T 类型的零值,程序并不会产生panic

类型选择
是一种按顺序从几个类型断言中选择分支的结构,类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类(而非值),它们针对给定接口值所存储的值的类型进行比较;类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type。此选择语句判断接口值 i 保存的值类型是 T 还是 S。在 T 或 S 的情况下,变量 v 会分别按 T 或 S 类型保存 i 拥有的值。在默认(即没有匹配)的情况下,变量 v 与 i 的接口类型和值相同。

switch v := i.(type) {
case T:
// v 的类型为 T
case S:
// v 的类型为 S
default:
// 没有匹配,v 与 i 的类型相同
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//类型选择
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("int类型 值等于%v \n", v)
case string:
fmt.Printf("string类型 值等于%v \n", v)
case iPerson:
fmt.Printf("iPerson类型 值等于%v \n", v)
default:
fmt.Printf("未知的类型%T!\n", v)
}
}

func InterDemo2() {
var i interface{} = "hello"

// 类型断言
s := i.(string)
fmt.Println(s) //hello

s, ok := i.(string)
fmt.Println(s, ok)//hello true

f, ok := i.(int)
fmt.Println(f, ok)//0 false

//f = i.(float64) // 报错(panic)

do(21)
do("hello")
do(true)
do(Person{name:"李四"})
}

Go(五)函数值和闭包

发表于 2019-01-19 | 更新于 2019-01-20 | 分类于 编程语言

一、函数值

函数也是值。它们可以像其它值一样传递,函数值可以用作函数的参数或返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//计算 传入一个需要两个int类型的参数并返回值为int的函数  没有返回值
func computeFn(x int, y int ,fn func(int, int) int) {
fmt.Println(fn(x,y))
}

//传入一个需要两个int类型的参数并返回值为int的函数 放回一个int类型值
func computeFn2(x int, y int ,fn func(int, int) int) int {
return fn(x,y);
}

//定义一个 需要两个int类型的参数并返回值为int的函数类型
type computeParam func(int, int) int

func computeFnSim(x int, y int ,fn computeParam) {
fmt.Println(fn(x,y))
}

//定义一个需要连个int类型参数 且放回值为一个不需要参数且返回值为int的函数
func computeFn3(x int, y int) func() int{
return func() int {
return x * y
}
}

func Compute() {
addFn := func(x int, y int) int {
return x + y
}

computeFn(5,3,addFn) //8
computeFnSim(11,3,addFn) //14

subFn := func(x int, y int) int {
return x - y
}
fmt.Println(computeFn2(5,3,subFn)) //2

f :=computeFn3(5,3);
fmt.Println(f()) //15
}

二、闭包

Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被“绑定”在了这些变量上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// i到n的和
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
//定义一个函数类型 需要一个int类型参数 返回一个int类型值和一个函数 递归
type IAdder func(int) (int,IAdder)

//相对标准的函数是编程 不能有过程变量
func adder2(base int) IAdder {
return func(v int) (int, IAdder) {
return base + v , adder2(base + v)
}
}

//斐波纳契
func fibonacci() func() int {
a,b := 0,1
return func() int {
a,b = b, a+b
return a
}
}

func main() {
pos := adder()
for i := 0; i < 10; i++ {
fmt.Printf("1+...+%d = %d \n",i,pos(i))
}

fmt.Println("========")
pos2 := adder2(0)
var sum int
for i := 0; i < 10; i++ {
sum,pos2 = pos2(i);
fmt.Printf("0+...+%d = %d \n",i,sum)
}

f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Printf("%d \t",f())
}
}

Go(四)指针和结构体数组、切片、Map

发表于 2019-01-19 | 更新于 2019-01-20 | 分类于 编程语言

一、数组

类型 [n]T 表示拥有 n 个 T 类型的值的数组。
表达式

var a [10]int
会将变量 a 声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组(切片)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func ArrayDemo()  {
//声明一个长度为2的数组
var arr [2]string
arr[0] = "Go"
arr[1] = "Language"
fmt.Println(arr) //[Go Language]
fmt.Println(arr[0],arr[1]) //Go Language

//遍历
length := len(arr) //获取长度
for i := 0; i < length; i++ {
fmt.Printf("遍历 index[%d], value = %s \n", i, arr[i])
}

//声明并赋值
arr2 := [3]int{3, 7, 8}
fmt.Println(arr2) //[3 7 8]

//遍历
for v := range arr2{
fmt.Printf("range 遍历 value = %d \n", v)
}

//声明一个不定长度的数组
arr3 := [...]int{3, 7, 8, 5, 6}
fmt.Println(arr3) //[3 7 8]
}

二、切片

数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T 表示一个元素类型为 T 的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:

a[low : high]

它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:

a[1:4]

切片拥有 长度len 和 容量cap。切片的长度就是它所包含的元素个数,容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。长度和容量可通过表达式 len(s) 和 cap(s) 来获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
func SliceDemo()  {
fmt.Println("切片 slice ...")
//声明一个切片
arr := []int{1, 2, 3, 4, 5, 6}
fmt.Println(arr)//[1 2 3 4 5 6]

// 切片遍历
for i, v := range arr {
fmt.Printf("index = %d, value = %d\n", i, v)
}

// 切片遍历 下标或值赋予 '_' 来忽略它
for _, v := range arr {
fmt.Printf("value = %d\n", v)
}

// 获取切片
var slice []int = arr[:] //全部元素
fmt.Println(slice)//[1 2 3 4 5 6]

slice = arr[:4] // [0,4)元素
fmt.Println(slice)//[1 2 3 4]

slice = arr[3:] // [3:5] 元素
fmt.Println(slice) //[4 5 6]

slice = arr[1:4] // 索引 (1,4] 元素
fmt.Println(slice) //[2 3 4]

//声明一个slice
var slice2 []int

//像slice其添加值
for i := 0; i < 10; i++ {
printSlice(slice2)
slice2 = append(slice2, i*2);
}

//声明一个长度len为3 类型为int的 slice
slice3 := make([]int, 3)
printSlice(slice3)

//声明一个长度len为3 cap为8 类型为int的 slice
slice4 := make([]int, 3, 8)
printSlice(slice4)

//将slice copy到slice3
copy(slice3, slice)
fmt.Println(slice3)//[2 3 4]

//删除下标为2的值
slice3 = deleteElement(slice3,2)
fmt.Println(slice3)//[2 4]

//修改下标为1的值
updateElement(slice3,1, 100)
fmt.Println(slice3)//[2 100]
}

// 修改slice的值
func updateElement(s []int, index int, val int){
s[index] = val
}

//删除元素 s 要删除的原始数据 index为要删除的索引下标
func deleteElement(s []int, index int) []int {
low := index - 1
return append(s[:low],s[index:]...)
}

//注意 这个类型 []int 并不是数组 而是 切片slice
// len 长度
// cap slice的内部机制 可理解为预留长度 容量
func printSlice(s []int) {
fmt.Printf("value = %v , len = %d , cap = %d \n" , s, len(s), cap(s))
}

三、Map

映射将键映射到值,键不能为空。零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。
在映射 m 中插入或修改元素:

m[key] = elem

获取元素:

elem = m[key]

删除元素:

delete(m, key)

通过双赋值检测某个键是否存在:

elem, ok = m[key]

若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,那么 elem 是该映射元素类型的零值。
同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
注 :若 elem 或 ok 还未声明,你可以使用短变量声明:

elem, ok := m[key]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
type User struct {
name string
age int
}

func MapDemo() {
//声明一个映射 键类型为string 值类型为int
var m = map[string]int{
"age": 20,
"height" : 180,
}

fmt.Println(m)//map[age:20 height:180]
fmt.Println(m["age"], m["height"], m["width"])//20 180 0

//遍历
for k,v := range m {
fmt.Printf("key = %s , val = % d \n" ,k , v)
}

m2 := map[string]User{
"A": {name:"张三", age: 20},
"B": {name:"李四", age: 80},
}
fmt.Println(m2)//map[A:{张三 20} B:{李四 80}]

//获取元素
elem := m2["A"]
fmt.Println(elem)//{张三 20}
fmt.Println(elem.age,elem.name)//20 张三

//有值才取出
if elem,ok := m2["C"]; ok {
fmt.Println(elem)
}

//修改值
m2["A"] = User{name:"王麻子",age:34}
fmt.Println(m2)//map[A:{王麻子 34} B:{李四 80}]

//删除元素
delete(m2,"A")
fmt.Println(m2)//map[B:{李四 80}]
}

Go(三)指针和结构体

发表于 2019-01-19 | 更新于 2019-01-20 | 分类于 编程语言

一、指针

Go拥有指针。指针保存了值的内存地址。类型 *T 是指向 T 类型值的指针,其零值为 nil; Go 没有指针运算

var p *int

& 操作符会生成一个指向其操作数的指针。

i := 42
p = &i

  • 操作符表示指针指向的底层值。这也就是通常所说的“间接引用”或“重定向”。

    fmt.Println(p) // 通过指针 p 读取 i p = 21 // 通过指针 p 设置 i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func PointerDemo()  {
//声明两个变量
i, j := 42, 2701

p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值 42

*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值 21

p = &j // 指向 j 2701
*p = *p / 37 // 通过指针对 j 进行除法运算 2701/37
fmt.Println(j) // 查看 i 的值 73
}

二、结构体

结构体(struct)就是一个字段的集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func PointDemo()  {
//声明一个结构体
var point = Point{2,3}
fmt.Println(point) //{2 3}

//声明一个结构体
var point2 = Point{}
fmt.Println(point2) //{0 0}

//结构体字段使用.来访问
point2.x = 10 //给x赋值
point2.y = 5
fmt.Println(point2, point2.x, point2.y) //{10 5} 10 5
}

三、结构体指针

结构体字段可以通过结构体指针来访问。
如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func StructPointer() {
fmt.Println("结构体指针...")

//声明结构体
point := Point{2,3}
fmt.Println(point) //{2 3}

p := &point //指针 取得point内存
p.x = 1024
fmt.Println(point) //{1024 3}

point3 := &Point{1, 2}
fmt.Println(point3) //&{1 2}
}

Go(二)流程控制

发表于 2019-01-19 | 更新于 2019-01-20 | 分类于 编程语言

一、for

在Go中只有一种循环结构:for 循环。
基本的 for 循环由三部分组成,它们用分号隔开:
1、初始化语句:在第一次迭代前执行
2、条件表达式:在每次迭代前求值
3、后置语句:在每次迭代的结尾执行
初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。

一旦条件表达式的布尔值为 false,循环迭代就会终止。

注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//基础语法
func loop(step int) {
sum := 0
for i:= 0; i< step; i++ {
sum += i
}
fmt.Println(sum)
}

//初始化语句和后置语句是可选的。
//将loop() 可简化为
func loop2(step int) {
sum := 1
for ; sum < step; {
sum += sum
}
fmt.Println(sum)
}

//for 是 Go 中的 “while”
func while(step int) {
sum := 1
for sum < step {
sum += sum
}
fmt.Println(sum)
}

//死循环
func unlimitedLoop() {
for {
fmt.Printf("无限循环 step = %d \n",step)
}
}

二、if…else…

Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。

1
2
3
4
5
6
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}

if 的简短语句
if 语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if 之内。

1
2
3
4
5
6
7
8
9
func pow(x, n, lim float64) float64 {
//计算x的n次幂 如果结果小于lim 则返回结果,否则返回lim
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
return lim
}

三、switch

switch是编写一连串 if...else... 语句的简便方法。它运行第一个值等于条件表达式的case语句。
Go的switch语句类似于C、C++、Java、JavaScript 和 PHP 中的,不过Go只运行选定的case,而非之后所有的case。 实际上,Go 自动提供了在这些语言中每个case后面所需的break语句。 除非以fallthrough语句结束,否则分支会自动终止。 Go的另一点重要的不同在于switch 的case无需为常量,且取值不必为整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func grade(gd string)  {
switch gd {
case "A":
fmt.Println("优秀")
case "B":
fmt.Println("良好")
case "C":
fmt.Println("中等")
case "D":
fmt.Println("及格")
case "E":
fmt.Println("差")
default:
fmt.Println("未知")
}
}

没有条件的switch

1
2
3
4
5
6
7
8
9
10
11
12
func unCondition()  {
//当前时间
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("现在是早上")
case t.Hour() < 18:
fmt.Println("现在是下午")
default:
fmt.Println("现在是晚上")
}
}

四、defer

defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
推迟的函数调用会被压入一个栈中(先进后出)。当外层函数返回时,被推迟的函数会按照先进后出的顺序调用。

1
2
3
4
5
6
7
func deferFun()  {
fmt.Println("方法开始")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("方法结束")
}

Go(一)语言教程-基础

发表于 2019-01-19 | 更新于 2019-01-20 | 分类于 编程语言

一、包

每个Go程序都是由包构成的。程序从main包开始运行。包名与导入路径的最后一个元素一致。例如,”math/rand” 包中的源码均以 package rand 语句开始。
1、import包的导入

包的导入使用import <package>

2、用圆括号组合了导入

import (‘fmt’,’math’)

1
2
3
4
5
6
7
8
9
10
11
12
13
//报名
package main

//导入包
import (
"fmt"
"math"
)

//程序入口 注意main方法必须在main包中
func main() {
fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}

二、函数、变量、常量

1、函数
函数可以没有参数或接受多个参数。当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。

func 函数名称([参数名 参数类型, …]) [(返回值类型,…)] {函数体…}

2、变量var
var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。

var 名称,… 类型

3、常量
常量的声明与变量类似,只不过是使用const关键字。可以是字符、字符串、布尔值或数值。不能用:=语法声明。

const 名称,… 类型

4、类型装换

表达式 T(v) 将值 v 转换为类型 T。
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

注意 :
在Go中要导出函数或变量需要函数或变量名称的首字母大写,首字母大写代表是Public,若要私有方法首字母小写即可。

示例 basics/fun.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package basics

//加法 返回 x + y
func Add(x int,y int) int {
return x + y
}

//减法 返回 x - y
func Sub(x int,y int) int {
return x - y
}

//乘法 返回 x * y
func Multi(x int,y int) int {
return x * y
}

//除法 返回 x / y
func Div(x int,y int) int {
return x / y
}

//函数作为参数
func Customize(x, y int, cust func(int ,int) int) int{
return cust(x,y)
}

//多个返回值
func Swap(x, y int) (int, int){
return Div(x,y), x % y
}

//多个返回值 命名
func Swap2(x, y int) (a int, b int){
a = Div(x,y)
b = x % y
return
}

调用 main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"Chapter1/basics"
"fmt"
"math"
)

//x % y
// x y 均是 int类型
func mol(x int,y int) int {
return x % y
}

//声明变量 全局变量不能使用 := 声明
var name = "GoLang";
var c, python, java bool;

func main() {
var index = 20;

fmt.Println(index)
fmt.Println(name,c,python,java)

fmt.Printf("9 的平方根是 %g \n", math.Sqrt(9))

//声明变量
x := 10
y := 3
f := mol

fmt.Printf("x + y = %d \n",basics.Add(x,y))
fmt.Printf("x - y = %d \n",basics.Sub(x,y))
fmt.Printf("x * y = %d \n",basics.Multi(x,y))
fmt.Printf("x / y = %d \n",basics.Div(x,y))
fmt.Printf("x 余 y = %d \n",basics.Customize(x,y,f))

a, b := basics.Swap(x, y)
fmt.Printf("a = %d ,b = %d \n",a , b)
}

Flutter常见问题

发表于 2019-01-10 | 更新于 2019-01-12

一、运行flutter run命令,结果卡在了Initializing gradle...

1、方法一

1.自己手动下载gradle。首先用Android studio打开项目,并运行,然后看断一下网或者改一下代理,看看命令行报错的信息
2.打开 https://services.gradle.org/distributions/ ,下载相应版本的gradle,具体版本就是看第一步你报错那会的提示的版本
3.切换路径/Users/你的用户名/.gradle/wrapper/dists,此时你应该能看到预创建好的项目,点进去是一个很长字符串的文件夹,再点进去,把下载好 的gradle文件解压该目录下
4.重新运行flutter run。

2、方法二

1、打开你项目路径/android/gradlew[.bat]
2、修改

1
2
#CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH=你的gradle路径/lib/gradle-launcher-4.10.2.jar

3、修改

1
2
#exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.launcher.GradleMain "$@"

二、使用国内镜像

“Resolving dependencies…”报错,无法正常运行
1.首先打开/Users/你的用户名/.gradle/下的”gradle.properties”文件,检查你是否开启过代理,有的话把代理注释掉
2.打开你项目下的build.gradle文件,路径是你的项目/android/,修改buildscript和allprojects的repositories属性。把下载源替换成阿里的源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
buildscript {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}

dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}

allprojects {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
}

3.打开你flutter SDK目录下的flutter.gradle,与第二步一样,编辑buildscript下的repositories属性。
4.重新运行flutter run。

12

ywzou

ywzou space

12 日志
1 分类
4 标签
GitHub
© 2019 ywzou
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Gemini v6.7.0