Go语言学习笔记(会议分享表述用)

GO语言分享

什么是Go语言?

Go,又称golang,是Google于2009发布的一种静态强类型、编译型,并发型,并具有垃圾回收功能的编程语言。
也是google推出的第二门开源性编程语言(第一门是Simple语言,Go语言被评为2016年年度编程语言)。
Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,
而且更加安全、支持并行进程。

目前,Google团队正在试图让GO运行在Android设备上,这也是我想去了解go的原因。

GO语言的意义

现在市面上的编程语言已经非常多,我们可以大致将其分为以下两种类型。

偏性能敏感的编译型(强类型)语言有: C、C++、Java、C#、Delphi和Objective-C等,
偏快速业务开发的动态解析型(弱类型)语言有:PHP、Python、Perl、Ruby、JavaScript和Lua等。

在2000年前的单机时代,C语言是编程之王。随着机器性能的提升、软件规模与复杂度的提高,Java逐步取代了C的位置。尽管看起来Java已经深获人心,但Java编程的体验并未尽如人意。历年来的编程语言排行榜显示,Java语言的市场份额在逐步下跌,并趋近于C语言的水平,显示了这门语言后劲不足。

Go语言官方自称,之所以开发Go语言,是因为“近10年来开发程序之难让我们有点沮丧”。

这一定位暗示了Go语言希望取代C和Java的地位,成为最流行的通用开发语言。
Go希望成为互联网时代的C语言。

那么,互联网时代的C语言需要考虑哪些关键问题呢,GO语言又是如何应对的呢?

并行与分布式支持

多核化和集群化是互联网时代的典型特征。作为一个互联网时代的C语言,必须要让这门语言操作多核计算机与计算机集群如同操作单机一样容易。

GO语言在语言级别支持协程,叫做goroutine。Go语言标准库提供的所有系统调用(syscall)操作,当然也包括所有的同步IO操作,都会让出CPU给其他goroutine,这让事情变得简单(多线程原理)。

我们来对对比以下JAVA和GO,近距离观察两者对“执行体”的支持。
为了简化方便可读,在这里实现java标准库的线程。

java语言

public class MyThread implements Runnable {
    String arg;

    public static void main(String[] args) {
        new Thread(new MyThread("")).start();
    }

     public MyThread(String arg) {
        this.arg = arg;
    }

    @Override
    public void run() {
        //业务代码
    }
}

Go语言实现(代码中没有”;”是因为在编译时系统已经对每行做了添加分号的处理,如果是一行有多个语句,可以在中间添加”;”分割语句)

func run(arg string) {
    //业务代码
}
func main() {

    //直接用go关键字启动并行
    go run("arg")
}

由此可以看出GO语言由于在语言级别支持协程,而java只有在标准库的支持下才能实现,所以在并行和分布式支持上,可以看出GO有很大的优势。

软件工程支持

工程规模不断扩大是产业发展的必然趋势。单机时代语言可以只关心问题本身的解决,而互联网时代的C语还需要考虑软件品质保障和团队协作相关的话题。

简单来讲,软件工程的支持就是一些的规范标准。
其中包括:

代码风格规范
错误处理规范
包管理
契约规范(接口)
单元测试规范
功能开发的流程规范

而GO语言很可能是第一个将代码风格强制统一的语言,例如public修饰的变量必须以大写字母开头,private修饰的变量必须用小写字母开头。这种方案不仅免除了public等关键字,更重要的是统一了命名风格。

另外 GO语言对{ }的书写进行了强制要求,以下风格是正确的:

if expression{
    //业务
}

而下面这个写法就是错误的:

if expression
{
    //业务
}

另外在错误的处理方面,GO语言也采取了别致的标准规范。

//比如说流的操作
err :== is.Open(filename)
if err != nil{
    log.Println("Open file failed:",err)
    return  
}
//关闭已经打开的f文件
defer f.Close()

defer关键字类似 finally 不管程序是否异常,总会执行。
而且GO语言支持多返回值函数,所以大多数函数的最后一个返回值均为error类型。
error 类型只是一个系统内置的Interface 我们可以对其进行个性化。

又如以下这段代码,在java中会需要多个try catch 导致代码可读性极差(这里不再手打…):

conn := ...
defer conn.Close()

stmt := ... 
defer stmt.Close()

rset := ...
defer rset.Close()

//finally 里边的代码

编程哲学的重塑

计算机软件经历了数十年的发展,形成了面向对象等多种学术流派。GO语言允许程序员使用最合适的编程范式来解决问题。GO语言可以被用作一个纯粹的过程式编程语言,但对面相对象编程也支持的很好。
GO语言反对函数和操作符重载(overload),而C++,Java和C#都允许出现同名函数或者操作符,只要他们的参数列表不同。虽然重载解决了一小部分面向对象编程(OOP)的问题,但同样给这些语言带来了极大的负担。而GO语言有着完全不同的设计哲学,既然函数重载带来了负担,并且这个特性并不对解决任何问题有显著的价值,那么Go就不提供它。
其次Go语言支持类、类成员方法、类的组合,但是反对继承,反对虚函数和虚重载函数。确切的说,Go也提供了“继承”,但是只不过是采用了组合的文法来提供:

`type Foo struct{
    Base
    ...
}
func (foo *Foo) Bar(){
    ...
}
`

另外,GO语言还提供了和其他语言不同的非侵入性interface系统。

侵入式:java

interface IFoo {  
    void Bar();  
}  
class Foo implements IFoo {   
    void Bar(){}  
}

这种必须明确声明自己实现了某个接口的方式我们称为侵入式接口。关于侵入式接口的坏处我们这里就不再详细讨论,看java庞大的继承体系及其繁复的接口类型我们就可以窥之一二了。

非侵入式:go

type IWriter interface {  
    Write(buf [] byte) (n int, err error)  
  }  
type File struct {  
    //something
}   
func (f *File) Write(buf [] byte) (n int, err error) { 
    //something
} 

golang则采取了完全不同的设计理念,在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口.

2.0 go语言的特性

现在看来,作为一款年轻的语言,GO语言的生命力是非常强的,我们来分析下Go语言在想要成为互联网时代的C语言上是如何做的。

    自动垃圾回收
    丰富的内置类型
    多返回值函数
    错误处理
    匿名函数和闭包
    类型和接口
    并发编程
    反射
    语言交互性

自动垃圾回收

这里就要老生长提一下了:
不支持自动辣鸡回收的语言常用以下方式管理资源,比如以下C代码:

void foo(){
    char* p = new char[128];
    ... //对p指向的内存块进行赋值
    func(p);//调用某个函数使用p

    delete[] p;//回收p的资源
}

有时候开发者忘记做delete操作,就会导致内存的泄漏,然后就GG了。
而像是JAVA这样带有自动回收功能的语言就集成了各自的垃圾回收机制(比如java的GC,GC的原理也就类似我上上次分享的缓存方案)。

带有自动垃圾回收功能的语言一般都会通过系统主动的判断之前分配内存的优先级,然后在何时的时候自动进行辣鸡收集工作。

丰富的内置类型

除了几乎所有语言都支持的简单数据类型(比如int,float等)外,还包含了一些高级语言固有的数据类型,比如string和数组。除此之外,GO语言还内置了map(字典)。

另外还有一个新增的数据类型:数组切片(Slice)。我们可以认为数组切片市一中可动态增长的数组。其实就是List…
不过在由于是内置的数据类型,所以在开发时,我们不需要导包,比如以下代码我们就不需要再写:

#include <vector>
#include <map>
#include <...>

其实现在大多数IDE都支持自动导包,并进行包检查。所以说这个特性基本上没什么卵用。

以下是go语言在声明时的常用关键字:

var和const :变量和常量的声明
var varName type  或者 varName : = value
package and import: 导入
func: 用于定义函数和方法
return :用于从函数返回
defer someCode :在函数退出之前执行
go : 用于并行
select 用于选择不同类型的通讯
interface 用于定义接口
struct 用于定义抽象数据类型
break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
chan用于channel通讯
type用于声明自定义类型
map用于声明map类型数据
range用于读取slice、map、channel数据

多返回值函数

目前主流语言中除了Python外基本都不支持函数的多返回值功能。而G语言革命性地在静态开发语言阵营中率先提出了多返回值功能。这个特性可以让开发者从原来用各种比较别扭的方式返回多个返回值的痛苦中解脱出来,既不用在区分参数列表中哪几个用于输入,也不用再只为了多个返回值而专门定义一种数据结构。

func getName()(name1,name2,name3 string){
    return "name1","name12","name3"
}

也可以使用以下方式:

func getName()(name1,name2,name3 string){
    name1 = "name1"
    name2 = "name2"
    name3 = "name3"
    return
}

需要注意的一点是,并不是每个返回值都必须赋值,没有被明确赋值的返回值将保持默认的参数。

下面是调用多返回值函数的示例:

name1,name2,name3 :=getName()

如果并不需要全部的返回值,可以使用占位符避免声明无用的变量:

_,name2,_ :=getName()

这样就只取自己想要的数据情况下避免了内存的开销。

错误处理

这一点我在上面go语言的意义提了一下,这里再说明一下:

Go语言引入了三个关键子用于标准的错误处理流程,这三个关键字分别为defer、panic和recover。在上文中,
我用的是defor来进行处理error,可见其不仅是在写法或者是在代码的可读性方面都有很大的优势。
简单来说,go语言的错误处方式让我们无需仅仅为了程序安全性而添加大量一层嵌套一层的try-catch语句,
这对于代码阅读和维护有极大的好处,因为可以避免在层层的代码嵌套中定位业务代码。

匿名函数和闭包

在Go语言中,所有的函数也是值类型,可以作为参数传递。Go语言支持常规的匿名函数和闭包,比如下面的代码就定义了一个名为f的匿名函数,开发者可以随意的对匿名函数变量就行传递和调用:

f := func(x,y int)int{

return x + y

}

类型和接口

Go语言的类型定义非常接近c语言中的结构(struct),甚至还沿用了struct关键字。相比而言,Go语言并没有直接沿袭C++和java的传统去设计一个超级复杂的类型系统,不支持集成和重载,而只是支持了最基本的类型组合功能。

以下为C++的侵入性接口实现,该种类型接口的修改会影响所有实现该接口的类型。

//抽象接口
interface IFly{
    Virtual void Fly() = 0
};

//实现类
class Bird : public IFly{
    public:
        Bird(){ 

        }
        Virtual -Brid(){ 

        }
    public:
        void Fly(){
            //鸟>>>>飞
        }
}

void mian(){
    IFly* pFly = new Brid()
    pFly->Fly()
    delete pFly;
}

让我们接结合Go的非侵入式接口看如下代码:

type Bird struct{
    //定义鸟的属性
}

func (b *Bird) Fly(){
    //以鸟的形式飞行
}

//定义飞翔的接口
type IFly interface{
    Fly()
}

func main(){
    //新建对象并调用接口里的方法
    var fly Ifly = new(Bird)
    fly.Fly()
}

并发编程

这个我们在上文中已经讲过。这里详细的解释一下。
Go语言引入了goroutine(携程,比线程更加轻盈、更省资源)的概念,它使编程变得非常简单。铜鼓哦使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是共享内存来通信,Go语言让并发编程变得更加轻盈和安全。

我们直接看例子怎么用的:(这个涉及到协程间数据共享)
package main
import fmt

//求和方法,我们不用关心内部实现
func sum(values [] int, resultChan chan int){
    sum := 0
    for _,value :=rang 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)
    go sum(values[len(values)/2],resultChan)

    sum1,sum2 := <-resultChan, <-resultChan //接收结果

    fmt.prinrln("Result:", sum1, sum2, sum1 + sum2)
}

反射

反射(reflection)是在java语言出现后迅速流行起来的一种概念。通过反射,我们可以获取对象类型的详细信息,并可动态操作对象。反射是把双刃剑,功能强大但代码可读性不理想。

我们用go语言通过反射列出某个类型中所有成员变量的值:

`package main

//引包
import (
    "fmt"
    "reflect"
)

//定义鸟类
type Bird struct{
    Name string
    Age int
}

//定义飞翔函数
func (b *Brid)Fly(){
    fmt.Println("---执行fly方法---")
}

func main(){
    //新建一个鸟类麻雀
    sparrow := &Bird{"sparrow",3} 

    //通过反射得到麻雀的属性和属性的数据类型
    s := reflect.ValueOf(sparrow).Elem()        
    typeOfT := s.Type()

    //来个for循环  i< 属性的数量
    for i := 0; i< sNumField(); i++{

        f := s. Field(i)

        //"%d: %s %s = %v/n" 是输出类型
        fmt.Printf("%d: %s %s = %v/n", i , typeOfT.Field(i).Name, f.Type(), fiIeterface())
    }
}

程序最后的输出结果为:
0: Name string = sparrow
1: Age int = 3

当然这里只是一个例子,go的反射还有很多种方式,而我只会这种。

语言交互性

由于Go语言是用c编写的,说以go和c有天生的联系,Go语言的设计者们自然不会忽略如何重用现有C模块这个问题,这个功能直接被命名为Cgo。Cgo既是语言特性,同事也是一个工具的名称。

在go代码中,可以按Cgo的特定愈发混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。我们基本上可以完全忽略这个Go语言和C语言的边界是如何跨越的。

与java的JNI不用,Cgo的用法非常简单,比如以下代码就可以实现在Go中调用C语言标准库的puts函数。

package main

//#include <studio.h>

import "C"
import "unsafe"

func main(){

    //定义在C的String
    cstr := C.CString("Hello,world")

    C.puts(cstr)
    C.free(unsafe.pointer(cstr))
}

对于Go语言,学到这里,我忽然有种从入门到放弃的想法。

就目前我对于Go的理解,感觉Go远不如想象或者人们口头相传的那样强大,不过想来应该是自己学艺未精,理解未至深处。

不管怎样,Go作为一门新兴的语言,了解下还是蛮好的。

猜你喜欢

转载自blog.csdn.net/qq564045867/article/details/62882275