golang小程序试验(一)

1. 手动实现append

package main

import (
	"fmt"
)

func Append(slice, data []byte) []byte {
	l := len(slice)
	total_len := len(slice) + len(data)
	if total_len >= cap(slice) {
		total_len = total_len * 2
		newslice := make([]byte, total_len)

		copy(newslice, slice)
		slice = newslice
	}
	for i, k := range data {
		slice[l+i] = k
	}
	return slice
}

func main() {
	slice := []byte{1, 2}
	data := []byte{4, 5, 6}
	sumslice := Append(slice, data)
	fmt.Println(sumslice)
}

再看一遍这个程序的时候,我觉得直接迭代拷贝就可以了:

package main
import(
	"fmt"
)

func SliceAppend(s1, s2 []byte)[]byte{
	s1_len:=len(s1)
	s2_len:=len(s2)
	total_len:=s1_len+s2_len
	s:=make([]byte, total_len)
	
	for k, v:=range s1{
		s[k]=v
	}
	for k, v:=range s2{
		s[s1_len+k]=v
	}
	
	return s
}

func main(){
	s1:=[]byte{1,2,3}
	s2:=[]byte{4,5,6}
	s:=SliceAppend(s1, s2)
	fmt.Println(s)
}

2. 利用channel进行通信

package main

import "fmt"

func main() {
	a := make(chan int)
	b := make(chan int)
	c := make(chan int)

	go func(a, b, c chan int) {
		fmt.Println("A wait")
		select {
		//case b <- 1:
		//fmt.Println("This will never happen")
		case <-a:
			fmt.Println("A knows B exit")
		}
		fmt.Println("A exit")
		c <- 1
	}(a, b, c)

	go func(a, b chan int) {
		fmt.Println("B exit")
		close(b)
		a <- 1
	}(a, b)

	<-c

	fmt.Println("C exit")
}

此例中a会等待b的退出,利用c来协调main与goroutine的运行。

3. golang的闭包函数

利用闭包修改返回值:

package main

import "fmt"

func main() {
	x := 0

	exec(func() { x += 100 })

	fmt.Println(x)
}

func exec(callback func()) {
	callback()
}

此例中,声明一个fun()类型的参数,调用时进行具象化,传递给func()类型的参数,然后进行调用。

利用channel传递闭包函数:

package main

import "fmt"
import "math/rand"

var c chan func()

func main() {
	c = make(chan func())
	go loop()
	x := 0
	y := rand.Int() % 100

	exec(func() {
		for i := 0; i < y; i++ {
			x += 1
		}
	})

	fmt.Println("x= ", x)
	fmt.Println("y= ", y)
}

func loop() {
	for {
		select {
		case callback := <-c:
			callback()
		}
	}
}

func exec(callback func()) {
	c <- callback
}

上例中利用闭包函数作为函数的参数,声明一个func()类型的channel,并将此闭包函数通过channel进行传递,实现了函数同步与回调。

4. golang生成MD5值

任何字符串生成的MD5结果长度都是固定的,利用golang生成一个字符串的MD5值,然后与其它语言生成的MD5值进行比较:

package main

import "fmt"
import "crypto/md5"
import "encoding/hex"
import "bytes"

func main() {
	h := md5.New()

	h.Write([]byte("你好,可以认识一下吗?"))

	x := h.Sum(nil)

	y := make([]byte, 32)
	hex.Encode(y, x)

	fmt.Println(string(y))

	z := []byte("e2656fa718dbdb73b1c9b56b5ee2bcef")
	fmt.Println(bytes.Equal(y, z))
}

5. 带缓冲的channel

Go语言提供了两种channel,带缓冲区和不带缓冲区的。不带缓冲区的channel,发送和接收是同步的,必须接收端接收了消息,发送端才解除阻塞。带缓冲区的channel,在缓冲区满之前,发送和接收是异步的,发送端的发送操作只保证把消息放入缓冲区。channel可以关闭,关闭的目的是让接收端知道不会再有消息从这个channel进入,我们可能会用某个channel的关闭来表示某种状态的终结。当关闭一个带缓冲区的channel时,如果缓冲区中还有消息,接收端在接收完所有消息后,才会被告知channel已不可用。

package main

import "fmt"
import "runtime"

func main() {
	runtime.GOMAXPROCS(4)
	bufferedChannel := make(chan int, 10)
	wait := make(chan int)

	for i := 0; i < 10; i++ {
		bufferedChannel <- i
	}
	close(bufferedChannel)

	go func() {
		for {
			if j, ok := <-bufferedChannel; ok {
				fmt.Println(j)
			} else {
				break
			}
		}
		wait <- 1
	}()

	<-wait
}

结果是所有的值还是按顺序打印,与“在缓冲区满之前发送和接收是异步的”不太相符。

6. golang的指针运算

Go语言的语法上是不支持指针运算的,所有指针都在可控的一个范围内使用,没有C语言的*void然后随意转换指针类型这样的东西。最近在思考Go如何操作共享内存,共享内存就需要把指针转成不同类型或者对指针进行运算再获取数据。对Go语言内置的unsafe模块做了一个实验,发现通过unsafe模块,Go语言一样可以做指针运算,只是比C的方式繁琐一些,但是理解上是一样的。

package main

import "fmt"
import "unsafe"

type Data struct {
	Col1 byte
	Col2 int
	Col3 string
	Col4 int
}

func main() {
	var v Data

	fmt.Println(unsafe.Sizeof(v))

	fmt.Println("----")

	fmt.Println(unsafe.Alignof(v.Col1))
	fmt.Println(unsafe.Alignof(v.Col2))
	fmt.Println(unsafe.Alignof(v.Col3))
	fmt.Println(unsafe.Alignof(v.Col4))

	fmt.Println("----")

	fmt.Println(unsafe.Offsetof(v.Col1))
	fmt.Println(unsafe.Offsetof(v.Col2))
	fmt.Println(unsafe.Offsetof(v.Col3))
	fmt.Println(unsafe.Offsetof(v.Col4))

	fmt.Println("----")

	v.Col1 = 98
	v.Col2 = 77
	v.Col3 = "1234567890abcdef"
	v.Col4 = 23

	fmt.Println(unsafe.Sizeof(v))

	fmt.Println("----")

	x := unsafe.Pointer(&v)

	fmt.Println(*(*byte)(x))
	fmt.Println(*(*int)(unsafe.Pointer(uintptr(x) + unsafe.Offsetof(v.Col2))))
	fmt.Println(*(*string)(unsafe.Pointer(uintptr(x) + unsafe.Offsetof(v.Col3))))
	fmt.Println(*(*int)(unsafe.Pointer(uintptr(x) + unsafe.Offsetof(v.Col4))))
}

unsafe模块的文档中提到几条转换规则,理解了以后就很容易做指针运算了:

  1. A pointer value of any type can be converted to a Pointer.
  2. A Pointer can be converted to a pointer value of any type.
  3. A uintptr can be converted to a Pointer.
  4. A Pointer can be converted to a uintptr

对于unsafe模块还需要看一下。

7. golang关闭channel

在一些场景下需要同时让多个goroutine一起退出,最低限度需要几个channel来做这件事情呢?下面是一则关闭频道的实验,实验中运行两个goruntine阻塞在channel c的消息等待,然后在主函数中关闭channel c,从实验结果可以看出两个goruntine都正确的从阻塞的位置继续运行下去。所以结论是,只需要通过关闭一个公共的channel就可以让多个goruntine一起退出。

package main
import "fmt"

func main(){
    a:=make(chan int)
    b:=make(chan int)
    c:=make(chan int)

    go func(){
        <-c
        fmt.Println("A")
        a<-1
    }

    go func(){
        <-c
        fmt.Println("B")
        b<-1  
    }

    close(c)
    <-a
    <-b
    fmt.Println("End.")
}

8. 实现自定义的ReadFull函数

package main

import (
	//"errors"
	"fmt"
	"io"
)

type UpString string

func (us UpString) Read(p []byte) (n int, e error) {
	i, lus, lp := 0, len(us), len(p)
	fmt.Println(lus, lp)
	for ; i < lus && i < lp; i++ {
		if us[i] >= 'a' && us[i] <= 'z' {
			p[i] = us[i] + 'A' - 'a'
		} else {
			p[i] = us[i]
		}
	}

	switch i {
	case lus:
		return lus, nil
	case lp:
		return lp, io.EOF
	default:
		return i, io.EOF
	}
}

func main() {
	us := UpString("asdfASDFQWEV234VASDswf$%#@SDAFasdf")
	p := make([]byte, 68)
	n, err := io.ReadFull(us, p)
	fmt.Printf("%s\n", p)
	fmt.Println(n, err)
}

将读取缓冲设置为字符串的2倍长度,会读取两次字符串,直到读满为止。

9. golang接口与多态

package main
import (
	"fmt"
	"math"
)

//---------- 接 口 --------//
type shape interface {
	area() float64 //计算面积
	perimeter() float64 //计算周长
}

//--------- 长方形 ----------//
type rect struct {
	width, height float64
}

func (r *rect) area() float64 { //面积
	return r.width * r.height
}

func (r *rect) perimeter() float64 { //周长
	return 2*(r.width + r.height)
}

//----------- 圆  形 ----------//
type circle struct {
	radius float64
}

func (c *circle) area() float64 { //面积
	return math.Pi * c.radius * c.radius
}

func (c *circle) perimeter() float64 { //周长
	return 2 * math.Pi * c.radius
}

// ----------- 接口的使用 -----------//
func interface_test() {
	r := rect {width:2.9, height:4.8}
	c := circle {radius:4.3}

	s := []shape{&r, &c} //通过指针实现

	for _, sh := range s {
		fmt.Println(sh)
		fmt.Println(sh.area())
		fmt.Println(sh.perimeter())
	}
}

func main(){
	interface_test()
}

10. defer、panic示例

package main
import (
	"fmt"
)

func g(i int) {
	if i>1 {
		fmt.Println("Panic!")
		panic(fmt.Sprintf("%v %s", i, "panic."))
	}
}

func f() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()

	for i := 0; i < 4; i++ {
		fmt.Println("Calling g with ", i)
		g(i)
		fmt.Println("Returned normally from g.")
	}
}

func main() {
	f()
	fmt.Println("Returned normally from f.")
}
可以人为的引入panic,然后在defer中利用recover来处理panic信息。从以上程序可以看出,panic发生后,原函数停止向下执行,利用recover可以处理panic产生的信息;处理完后继续执行main()函数中下面的内容。

本文来自:开源中国博客

感谢作者:壬癸甲乙

查看原文:golang小程序试验(一)

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。