Go语言学习(一):简介

主要目标

兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性。
旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势。
在Go语言出现之前,开发者们总是面临非常艰难的抉择,究竟是使用执行速度快但是编译速度并不理想的语言(如:C++),还是使用编译速度较快但执行效率不佳的语言(如:.NET、Java),或者说开发难度较低但执行速度一般的动态语言(python)呢?Go语言在这 3 个条件之间做到了最佳的平衡:快速编译,高效执行,易于开发
Go语言支持交叉编译,比如说你可以在运行 Linux 系统的计算机上开发可以在 Windows 上运行的应用程序。
这是第一门完全支持 UTF-8 的编程语言,这不仅体现在它可以处理使用 UTF-8 编码的字符串,就连它的源码文件格式都是使用的 UTF-8 编码

Go是编译型语言

Go 使用编译器来编译代码。
编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。
要创建并运行 Go 程序,程序员必须执行如下步骤。

  1. 使用文本编辑器创建 Go 程序;
  2. 保存文件;
  3. 编译程序;
  4. 运行编译得到的可执行文件。
    Go 自带了编译器,因此无须单独安装编译器。

Go语言的特性

  1. 自动垃圾回收
    以C++为例:
void foo()
{
    char* p = new char[128]
    ...//对P指向的内存块进行赋值
    func1(p);//使用内存指针

    delete[] p;
}

异常情况下会导致最后的“delete[] p;”语句没有被调用,这样就会引发内存泄漏问题。如果这个函数还是被调用的非常频繁,那么该进程执行的时候会发现进程所占用的内存会一直增加,如果泄露的系统资源还会有可能导致系统崩溃。
手动管理内存的另外一个问题:由于指针的到处传递,无法确定何时可以释放该指针指向的内存块比如,代码中某个位置释放了内存,但是其他还有地方在使用指向这块内存的指针,那么这些指针会班车“野指针”或者叫“悬空指针”。对这些指针操作都会有不可预料的后果。尽管现在有许多工具可以在一定程度上辅助开发者但是并不能完全解决问题。
到目前为止,内存泄露的最佳解决方案是在语言级别引入自动垃圾回收算法。
垃圾回收:
所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也会被记录,然后垃圾回收器会对所有已分配的内存进行跟踪监测,一旦发现有些内存,已经不再被任何人使用就阶段性的回收这些没人用的内存。
因为垃圾回收功能的支持,开发者无需担心所指向的对象失效的问题,所以Go语言不需要delete关键字,也不需要free()方法来明确释放内存
2. 更丰富的内置类型
除了几乎所有语言都支持的简单内置类型(整型和浮点型等),Go语言也内置了一些比较新的语言中的内置高级类型(C#和java中的数组和字符串),此外还内置了字典类型(对于其他静态语言通常需要用库的方式支持),此外还有一个新增数据类型“数组切片”。可以认为这是一种可动态增长的数组。
这几种数据结构基本上覆盖了绝大部分的应用场景,而且因为是语言内置特性,开发者根本不用费事去添加依赖的包,这样既减少工作量也让语言看起来尽量简洁。
3. 函数多返回值
目前主流语言中除了python基本都不支持函数的多返回值功能。
比如,我们如果要定义一个函数用于返回用户个人信息,信息包含姓式、名字、中间名和别名。
在不支持多返回值的语言中可以使用以下方式:
专门定义一个结构体用于返回:

struct name
{
    char first_name[20];
    char middle_name[20];
    char last_name[20];
    char nick_name[48];
};
//函数原型
extern name get_name();
//函数调用
name n = get_name();

或者以传出参数的方式返回信息:

extern void get_name(
    /*out*/char* first_name,
    /*out*/char* middle_name,
    /*out*/char* last_name,
    /*out*/char* nick_name);
//分配内存
char first_name[20];
char middle_name[20];
char last_name[20];
char nick_name[48];
//函数调用
get_name(first_name,middle_name,last_name,nick_name);

Go语言支持多返回值功能,这个特性可以让开发者不需要再区分参数列表中哪几个用于输入,哪几个用于输出,也不用为了返回多个值专门定义一个数据结构。

func getName()(first_name,middle_name,last_name,nick_name string){
    return "May","FK","chane","BoBo"
}

因为返回值已经有名字,因此各个返回值也可以用如下的方式来在不同位置进行赋值,从而提供了极大的灵活性。

func getName()(first_name,middle_name,last_name,nick_name string){
    first_name = "May"
    middle_name = "FK"
    last_name = "chane"
    nick_name = "BoBo"
    return
fn, mn, ln, nn := getName()

并不是每个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。如果只对其中几个返回值感兴趣,可以直接使用下划线作为占用符来忽略不关心的值。

_, _, ln, _ := getName()
  1. 错误处理
    Go语言引入defer关键字用于标准的错误处理流程,并且提供内置函数panic、recover完成异常的抛出与捕获。
f, err := os.open(filename)
if err != nil{
    log.Println("Open file failed:", err)
    return
}
defer f.close()
...//关闭已经打开的文件

defer关键字:defer语句的含义是不管程序是否出现异常,均在函数退出时自动执行相关代码,上述例子中不管是否会出现异常,都会确保文件被正确的关闭。
6. 匿名函数和闭包
在Go语言中,所有的函数也是值类型,可以作为参数传递。

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

代码定义了一个名称为f的匿名函数,开发者可以随意对该匿名函数变量进行传递和调用。
7. 类型和接口
前置知识:
类就是拥有相等功能和相同的属性的对象的集合;
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。(不如动物类,当你提到动物类,你并不知道我说的是什么动物,只有看到了具体的动物,你才知道这是什么动物,所以说动物本身并不是一个具体的事物,而是一个抽象的事物。只有真正的猫,狗才是具体的动物,同理我们也可以推理不同的动物,他们的行为习惯应该是不一样的,所以我们不应该在动物类中给出具体体现,而是给出一个声明即可。)
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
接口就是比“抽象类"还“抽象”的“抽象类”, 可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。接口是完全面向规范的。“抽象类所拥有的共性”。接口中只包含方法
Go语言的类型定义非常接近于C语言结构中的结构(struct),甚至直接沿用了struct关键字。
在C++中一般会这样确定接口和类型的关系:

//抽象接口
interface IFly
{
    virtual void Fly() =0;
};
//实现类
class Brid : public IFly
{
public:
    Bird()
    {}
    virtual ~Bird()
    {}
public:
    void Fly()
    {
        //以鸟的方式飞行
    }
};

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

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

type Brid struct {
    ...
}
func (b *Brid) Fly() {
    //以鸟的方式飞行
}

对比一下,Go语言在实现Brid类时完全没有任何IFly的信息。可以在另外的地方定义这个接口:

type IFly interface {
    Fly()
}
func main() {
    var fly IFly = new(Bird)
    fly.Fly()
}

虽然在Brid类型实现的时候,没有声明与接口IFly的关系,但是接口和类型可以直接转换,甚至接口定义都不用在类型定义之前。
9. 并发编程
Go语言引入了goroutine概念。通过在函数调用钱使用关键字go,可以让该函数已goroutine方式执行。goroutine是一种比线程更加轻盈的更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度规模不下于每秒百万次,这样我们可以创建大量的goroutine。
Go语言实现了CSP(通信顺序进程,Communicating Sequential Process)模型来作为goroutine间的推荐通信方式。在CSP模型中,一个并发系统由若干并行运行的顺序进程组成,每个进程不能对其他进程的变量赋值。进程之间只能通过一对通信原语实现协作。Go语言用channel(通道)这个概念来轻巧的实现CSP模型,类型与Unix系统中的管道概念。
此外,由于一个进程内创建的所有goroutine运行在同一个内存地址空间中,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁。sync包提供了完备的读写锁功能。

package main
import "fmt"
func sum(values [] int, resultChan chan int) {
    sum := 0 
    for _,value := range {
        sum += values 
    }
    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)
} 

这是一个并行计算的代码,由2个goroutine进行并行的累加计算,待这两个计算过程都完成后打印计算结果。
10.反射
通过反射你可以获取对象类型的详细信息,并可动态操作对象。

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 = %v\n", i, typeOfT.Field(i).Name, f.Type(),
            f.Interface()
    }
}

结果是:
0: Name string = Sparrow
1: LifeExpectance int = 3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值