理解go语言编程-初识go语言

所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。

1
2
3
4
5
6
// c语言内存回收的考验
int* p = new int;
p += 10; // 对指针进行了偏移,因此那块内存不再被引用
// ...... 这里可能会发生针对这块int内存的垃圾收集 ......
p -= 10; // 咦,居然又偏移到原来的位置
*p = 10; // 如果有垃圾收集,这里就无法保证可以正常运行了

Go语言还内置了一个对于其他静态类型语言通常用库方式支持的字典类型(map)。可以认为数组切片是一种可动态增长的数组,数组切片的功能与C++标准库中的vector非常类似。

1
2
3
4
5
6
7
8
9
10
11
12
// 多返回值
func getName()(firstName, middleName, lastName, nickName string){ return "May", "M", "Chen", "Babe"
}
// 可以返回空值
func getName()(firstName, middleName, lastName, nickName string){ firstName = "May"
middleName = "M"
lastName = "Chen"
nickName = "Babe"
return
// 调用过程
fn, mn, ln, nn := getName()
}

c语言在实现一个接口之前必须先定义该接口,并且将类型和接口紧密绑定,即接口的修改会影响到所有实现了该接口的类型,而Go语言的接口体系则避免了这类问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 抽象接口 interface IFly {
virtual void Fly()=0; };
// 实现类
class Bird : public IFly {
public:
Bird() {}
virtual ~Bird() {}
public:
void Fly()
{
// 以鸟的方式飞行
}
};

void main() {
IFly* pFly = new Bird();
pFly->Fly();
delete pFly;
}

相比之下的go实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在实现Bird类型时完全没有任何IFly的信息
type Bird struct { ...
}
func (b *Bird) Fly() { // 以鸟的方式飞行
}
// 以在另外一个地方定义这个IFly接口:
type IFly interface {
Fly()
}
// 这两者目前看起来完全没有关系,现在看看我们如何使用它们:
// 虽然Bird类型实现的时候,没有声明与接口IFly的关系,但接口和类型可以直接转换,甚至接口的定义都不用在类型定义之前,这种比较松散的对应关系可以大幅降低因为接 口调整而导致的大量代码调整工作。
func main() {
var fly IFly = new(Bird)
fly.Fly()
}

当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度的规模不下于每秒百万次,这使得我们能够创建大量的goroutine,从而可以很轻松地编写高并发程序,达到我们想要的目的。

Go语言实现了CSP(通信顺序进程,Communicating Sequential Process)模型来作为goroutine间的推荐通信方式。Go语言用channel(通道)这个概念来轻巧地实现了CSP模型。channel的使用方式比较接近Unix系统中的管道(pipe)概念,可以方便地进行跨goroutine的通信。
一个进程内创建的所有goroutine运行在同一个内存地址空间中,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁。Go语言标准库中的sync包提供了完备的读写锁功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// channel实现并行计算
package main
import "fmt"
func sum(values [] int, resultChan chan int) {
sum := 0
for _, value := range values {
sum += value
}
resultChan <- sum // 将计算结果发送到channel中
}
func main() {
values := [] int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
resultChan := make(chan int, 2)
go sum(values[:len(values)/2], resultChan)
go sum(values[len(values)/2:], resultChan)
sum1, sum2 := <-resultChan, <-resultChan // 接收结果
fmt.Println("Result:", sum1, sum2, sum1 + sum2)
}

利用反射功能列出某个类型中所有成员变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
"fmt"
"reflect"
)
type Bird struct {
Name string
LifeExpectance int
}
func (b *Bird) Fly() {
fmt.Println("I am flying...")
}
func main() {
sparrow := &Bird{"Sparrow", 3}
s := reflect.ValueOf(sparrow).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface())
}
}

在Go代码中,可以按Cgo的特定语法混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。

1
2
3
4
5
6
7
8
9
10
11
// 调用c的puts函数
package main
/*
#include <stdio.h> */
import "C"
import "unsafe"
func main() {
cstr := C.CString("Hello, world")
C.puts(cstr)
C.free(unsafe.Pointer(cstr))
}

Go语言的main()函数不能带参数,也不能定义返回值。命令行传入的参数在os.Args变量中保存。如果需要支持命令行开关,可使用flag包。

6g和6l是64位版本的Go编译器和链接器,对应的32位版本工具为8g和8l。Go还有另外一个GCC版本的编译器,名为gccgo

1
2
3
4
$ 6g helloworld.go
$ 6l helloworld.6
$ ./6.out
Hello, world. 你好,世界!

Go命令行工具,分析import语句以了解包的依赖关系,不需要makefile即可编译工程。不用设置什么编译选项,Go语言编译的二进制程序直接支持GDB调试。

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!