手把手教你学Go(一)——语言特点

语言特点

像C语言一样足够简单,互联网时代下支持并行和分布式的支持。

  • 自动垃圾回收
    Go语言作为一门新生的开发语言,当然不能忽略内存管理这个问题。又因为Go语言没有C++ 这么“强大”的指针计算功能,因此可以很自然地包含垃圾回收功能。因为垃圾回收功能的支持,开发者无需担心所指向的对象失效的问题,因此Go语言中不需要delete关键字,也不需要free() 方法来明确释放内存。使用Go语言实现,我们就完全不用考虑何时需要释放之前分配的内存的问题,系统会自动帮我们判断,并在合适的时候(比如CPU相对空闲的时候)进行自动垃圾收集工作。
  • 更丰富的内置类型
    除了几乎所有语言都支持的简单内置类型(比如整型和浮点型等)外,Go语言也内置了一 些比较新的语言中内置的高级类型,比如C#和Java中的数组和字符串。除此之外,Go语言还内置 了一个对于其他静态类型语言通常用库方式支持的字典类型(map)。
    另外有一个新增的数据类型:数组切片(Slice)。我们可以认为数组切片是一种可动态增长的数组。这几种数据结构基本上覆盖了绝大部分的应用场景。数组切片的功能与C++标准库中 的vector非常类似。Go语言在语言层面对数组切片的支持,开发者根本不用费事去添加依赖的包,既可以少一些输入工作量,可以让代码看起来尽量简洁。

Go语言设计者对为什么内置map这个问题的回答也颇为简单:既然绝大多数开发者都需要用到这个类型,为什么还非要每个人都写一行import语句来包含一个库?这也是一个典型的实战派观点,与很多其他语言的学 院派气息迥然不同。

  • 函数多返回值
    目前的主流语言中除Python外基本都不支持函数的多返回值功能,不是没有这类需求,可能 是语言设计者没有想好该如何提供这个功能,或者认为这个功能会影响语言的美感。并不是每一个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。如果开发者只对该函数其中的某几个返回值感兴趣的话,也可以直接用下划线作为占位符来 忽略其他不关心的返回值。
  • 错误处理
    Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和 recover。Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。这对于代码的阅读者和维护者来说也是一件很好的事情,因为可以避免在层层的代码嵌套中定位业务代码。
  • 匿名函数和闭包
    在Go语言中,所有的函数也是值类型,可以作为参数传递。Go语言支持常规的匿名函数和 闭包。
  • 类型和接口
    Go语言的类型定义非常接近于C语言中的结构(struct),甚至直接沿用了struct关键字。相比而言,Go语言并没有直接沿袭C++和Java的传统去设计一个超级复杂的类型系统,不支持继承 和重载,而只是支持了最基本的类型组合功能。
    巧妙的是,虽然看起来支持的功能过于简洁,细用起来你却会发现,C++和Java使用那些复 杂的类型系统实现的功能在Go语言中并不会出现无法表现的情况,这反而让人反思其他语言中 引入这些复杂概念的必要性。
    Go语言也不是简单的对面向对象开发语言做减法,它还引入了一个无比强大的“非侵入式” 接口的概念,让开发者从以往对C++和Java开发中的接口管理问题中解脱出来。
  • 并发编程
    Go语言引入了goroutine概念,它使得并发编程变得非常简单。通过使用goroutine而不是裸用 操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来通信,Go语言让并发编程变得更加轻盈和安全。
    通过在函数调用前使用关键字go,我们即可让该函数以goroutine方式执行。goroutine是一种比线程更加轻盈、更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,一颗CPU调度的规模不下于每秒百万次,这使得我们能够创建大量的goroutine,从而可以很轻松地编写高并发程序,达到我们想要的目的。
    另外,由于一个进程内创建的所有goroutine运行在同一个内存地址空间中,因此如果不同的 goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁。Go语言标准库中的sync包提供了完备的读写锁功能。
  • 反射
    反射(reflection)是在Java语言出现后迅速流行起来的一种概念。通过反射,你可以获取对 象类型的详细信息,并可动态操作对象。反射是把双刃剑,功能强大但代码可读性并不理想。若 非必要,我们并不推荐使用反射。
    Go语言的反射实现了反射的大部分功能,但没有像Java语言那样内置类型工厂,故而无法做 到像Java那样通过类型字符串创建对象实例。在Java中,你可以读取配置并根据类型名称创建对 应的类型,这是一种常见的编程手法,但在Go语言中这并不被推荐。
    反射最常见的使用场景是做对象的序列化(serialization,有时候也叫Marshal & Unmarshal)。 例如,Go语言标准库的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量 依赖于反射功能来实现。
  • 语言交互性
    由于Go语言与C语言之间的天生联系,Go语言的设计者们自然不会忽略如何重用现有C模块 的这个问题,这个功能直接被命名为Cgo。Cgo既是语言特性,同时也是一个工具的名称。
    在Go代码中,可以按Cgo的特定语法混合编写C语言代码,然后Cgo工具可以将这些混合的C 代码提取并生成对于C功能的调用包装代码。开发者基本上可以完全忽略这个Go语言和C语言的 边界是如何跨越的。

并发和分布式

多核和集群化是互联网时代的典型特征。语言需要哪些特性来应对这些特征呢?
并发执行的“执行体”,“执行体”是一个抽象的概念,如操作系统自己掌管的进程(process)、进程内的线程(thread)、进程内的协程(coroutine)。
在多数语言层面并不直接支持协程,而是通过库的方式支持,仅仅提供协程的创建、销毁与切换等能力。如果在协程中调用一个同步的IO操作,如网络通信 、本地文件读写,都会阻塞其他的并发协程,从而无法达到真正协程的预期。
Go语言在语言级别 支持协程,goroutine。Go语言标准库提供的所有系统调用(syscall)操作,当然也包括IO操作,都会让出CPU给其他goroutine。

执行体之间的通信

  • 执行体之间的互斥和同步
    多数语言提供线程间的互斥与同步支持,对于协程是没有的
  • 执行体之间的消息传递
    有两种方式,一是共享内存,而是消息传递。Go采用了消息队列的方式,即通道(channel),两个goroutine之间通过channel进行交互。

软件工程

  • Go是将代码风格强制统一的语言,如:Go语言要求public的变量必须以大写字母开头,private变量则以小写字母开头。不仅免去关键字也统一了风格。
  • Go对{}怎么写也有强制。
//正确写法
if expression { ...
}
//错误写法
if expression 
{
... 
}
  • Go首创错误处理规范
f, err := os.Open(filename) 
if err != nil {
	log.Println("Open file failed:", err)
	return 
}
defer f.Close()
... // 操作已经打开的f文件

defer语句的含义是不管程序是否出现异常,均在函数退出时自动执行相关代码。
Go支持返回多个值,大多数函数最后一个 返回值会是error类型,在错误情况下,放回详细信息。有了error类型,程序出现错误的逻辑看起来就相当统一。

//Java语言
Connection conn = ...;
try {
	Statement stmt = ...; 
	try {
		ResultSet rset = ...; 
		try {
			... // 正常代码 
		} finally { 
			rset.close();
		}
	} finally { 
		stmt.close();
	}
} finally { 
	conn.close();
}
//Go语言
conn := ...
defer conn.Close() 
stmt := ...
defer stmt.Close()
rset := ...
defer rset.Close()

变革

Go是变革派,C语言是纯过程的,Java是激进的面向对象主义,不能容忍体系里面孤立的函数。Go没有否定任何一方,而是用批判的眼光进行吸收,极力维持语言特性的简洁,力求小而精。Go语言反对函数重载(解决了小部分OOP问题,但是给语言带来了极大的负担),同时Go支持类、类成员方法、类的组合 ,但是反对继承、反对虚函数和虚函数的重载。确切说,Go提供继承,只不过是通过组合提供:
Go放弃了构造函数和析构函数。但是Go提供interface,而且是非侵入性的。

class Foo implements IFoo { // Java文法 ...
}
class Foo : public IFoo { // C++文法 ...
}

type Foo struct { // Go 文法 ...
}
var foo IFoo = new(Foo)

只要Foo实现了IFoo的所有方法,就实现了该接口,可以赋值。另外两个接口如果拥有相同的方法列表,那么他们就是等同的,可以互相赋值。

//第一个接口:
package one
type ReadWriter interface {
	Read(buf [] byte) (n int, err error) 
	Write(buf [] byte) (n int, err error)
}
第二个接口:
package two
type IStream interface {
	Write(buf [] byte) (n int, err error) 
	Read(buf [] byte) (n int, err error)
}

在Go语言中这两个接口是一样的,因为实现了任何一个的方法就是实现了另外一个。
所以在Go语言中为了引入另一个包的接口而导入这个包的做法是不给推荐的。引入一个包,就意味着更多的耦合。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值