1.2语言特性3这里先给读者罗列一下Go语言最主要的特性:口自动垃圾回收口更丰富的内置类型口函数多返回值口错误处理口匿名函数和闭包口类型和接口口并发编程口反射口语言交互性1.2.1自动垃圾回收我们可以先看下不支持垃圾回收的语言的资源管理方式,以下为一小段C语言代码:void foo()tchar* p = new char[128];//对p指向的内存块进行赋值func1(p)://使用内存指针delete[] p;1各种非预期的原因,比如由于开发者的疏忽导致最后的delete语句没有被调用,都会引发经典而恼人的内存泄露问题。假如该函数被调用得非常频繁,那么我们观察该进程执行时,会发现该进程所占用的内存会一直疯长,直至占用所有系统内存并导致程序崩溃,而如果泄露的是系统资源的话,那么后果还会更加严重,最终很有可能导致系统崩溃。手动管理内存的另外一个问题就是由于指针的到处传递而无法确定何时可以释放该指针所指向的内存块。假如代码中某个位置释放了内存,而另一些地方还在使用指向这块内存的指针,那么这些指针就变成了所谓的“野指针”(wildpointer)或者“悬空指针”(danglingpointer),对这些指针进行的任何读写操作都会导致不可预料的后果。由于其杰出的效率,C和C++语言在非常长的时间内都作为服务端系统的主要开发语言,比如Apache、Nginx和MySQL等著名的服务器端软件就是用C和C++开发的。然而,内存和资源管理一直是一个让人非常抓狂的难题。服务器的崩溃十有八九就是因为不正确的内存和资源管理导致,更讨厌的是这种内存和资源管理问题即使被发现了,也很难定位到具体的错误地点,导致无数程序员通宵达旦地调试程序。这个问题在多年里被不同人用不同的方式来试图解决,并诞生了一些非常著名的内存检查工具,比如RationalPurify、CompuwareBoundsChecker和英特尔的ParallelInspector等。从设计方法的角度也衍生了类似于内存引用计数之类的方法(通常被称为“智能指针”),后续在Windows平台上标准化的COM出现的一个重要原因就是为了解决内存管理的难题。但是事实证明,这些工具和图灵社区会员soooldier(soooldier@live.com)专享尊重版权
1.2 语言特性 3 1 2 3 4 5 9 6 7 8 2 这里先给读者罗列一下Go语言最主要的特性: 自动垃圾回收 更丰富的内置类型 函数多返回值 错误处理 匿名函数和闭包 类型和接口 并发编程 反射 语言交互性 1.2.1 自动垃圾回收 我们可以先看下不支持垃圾回收的语言的资源管理方式,以下为一小段C语言代码: void foo() { char* p = new char[128]; . // 对p指向的内存块进行赋值 func1(p); // 使用内存指针 delete[] p; } 各种非预期的原因,比如由于开发者的疏忽导致最后的delete语句没有被调用,都会引发 经典而恼人的内存泄露问题。假如该函数被调用得非常频繁,那么我们观察该进程执行时,会发 现该进程所占用的内存会一直疯长,直至占用所有系统内存并导致程序崩溃,而如果泄露的是系 统资源的话,那么后果还会更加严重,最终很有可能导致系统崩溃。 手动管理内存的另外一个问题就是由于指针的到处传递而无法确定何时可以释放该指针所 指向的内存块。假如代码中某个位置释放了内存,而另一些地方还在使用指向这块内存的指针, 那么这些指针就变成了所谓的“野指针”(wild pointer)或者“悬空指针”(dangling pointer),对 这些指针进行的任何读写操作都会导致不可预料的后果。 由于其杰出的效率,C和C++语言在非常长的时间内都作为服务端系统的主要开发语言,比 如Apache、Nginx和MySQL等著名的服务器端软件就是用C和C++开发的。然而,内存和资源管 理一直是一个让人非常抓狂的难题。服务器的崩溃十有八九就是因为不正确的内存和资源管理导 致,更讨厌的是这种内存和资源管理问题即使被发现了,也很难定位到具体的错误地点,导致无 数程序员通宵达旦地调试程序。 这个问题在多年里被不同人用不同的方式来试图解决,并诞生了一些非常著名的内存检查工 具,比如Rational Purify、Compuware BoundsChecker和英特尔的Parallel Inspector等。从设计方法的 角度也衍生了类似于内存引用计数之类的方法(通常被称为“智能指针”),后续在Windows平台 上标准化的COM出现的一个重要原因就是为了解决内存管理的难题。但是事实证明,这些工具和 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
4第1章初识GO语言方法虽然能够在一定程度上辅助开发者,但并没法让开发者避免通宵调试这样又苦又累的工作。到目前为止,内存泄露的最佳解决方案是在语言级别引人自动垃圾回收算法(GarbageCollection,简称GC)。所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。当然,因为需要尽量最小化垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个复杂得多,比如为对象增加年龄属性等,但基本原理都是如此。自动垃圾回收在C/C++社区一直作为一柄双刃剑看待,虽然到C++0x(后命名为C++11)正式发布时,这个呼声颇高的特性总算是被加入了,但按C++之父的说法,由于C++本身过于强大,导致在C++中支持垃圾收集变成了一个困难的工作。假如C++支持垃圾收集,以下的代码片段在运行时就会是一个严峻的考验:int*p=new int;P+=1071对指针进行了偏移,因此那块内存不再被引用I...这里可能会发生针对这块int内存的垃圾极收集...P-=10;/,居然又偏移到原来的位置*p=10;7/如果有垃圾收集,这里就无法保证可以正常运行了微软的C++/CLI算是用一种偏门的方式让C++程序员们有机会品尝一下垃圾回收功能的鲜美味道。在C/C++之后出现的新语言,比如Java和C等,基本上都已经自带自动垃圾回收功能。Go语言作为一门新生的开发语言,当然不能忽略内存管理这个问题。又因为Go语言没有C++这么“强大”的指针计算功能,因此可以很自然地包含垃圾回收功能。因为垃圾回收功能的支持开发者无需担心所指向的对象失效的问题,因此Go语言中不需要delete关键字,也不需要free()方法来明确释放内存。例如,对于以上的这个C语言例子,如果使用Go语言实现,我们就完全不用考虑何时需要释放之前分配的内存的问题,系统会自动帮我们判断,并在合适的时候(比如CPU相对空闲的时候)进行自动垃圾收集工作。1.2.2更丰富的内置类型除了儿乎所有语言都支持的简单内置类型(比如整型和浮点型等)外,Go语言也内置了一些比较新的语言中内置的高级类型,比如C#和Java中的数组和字符串。除此之外,Go语言还内置了一个对于其他静态类型语言通常用库方式支持的字典类型(map)。Go语言设计者对为什么内置map这个问题的回答也颇为简单:既然绝大多数开发者都需要用到这个类型,为什么还非要每个人都写一行import语句来包含一个库?这也是一个典型的实战派观点,与很多其他语言的学院派气息迥然不同。另外有二个新增的数据类型:数组切片(s1ice)。我们可以认为数组切片是一种可动态增长的数组。这几种数据结构基本上覆盖了绝大部分的应用场景。数组切片的功能与C++标准库中的vector非常类似。Go语言在语言层面对数组切片的支持,相比C++开发者有效地消除了反复写以下几行代码的工作量:图灵社区会员soooldier(soooldier@live.com)专享尊重版权
4 第 1 章 初识 Go 语言 方法虽然能够在一定程度上辅助开发者,但并没法让开发者避免通宵调试这样又苦又累的工作。 到目前为止,内存泄露的最佳解决方案是在语言级别引入自动垃圾回收算法(Garbage Collection,简称GC)。所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对 该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现 有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。当然,因为需要尽量最小 化垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个 复杂得多,比如为对象增加年龄属性等,但基本原理都是如此。 自动垃圾回收在C/C++社区一直作为一柄双刃剑看待,虽然到C++0x(后命名为C++11)正 式发布时,这个呼声颇高的特性总算是被加入了,但按C++之父的说法,由于C++本身过于强大, 导致在C++中支持垃圾收集变成了一个困难的工作。假如C++支持垃圾收集,以下的代码片段在 运行时就会是一个严峻的考验: int* p = new int; p += 10; // 对指针进行了偏移,因此那块内存不再被引用 // . 这里可能会发生针对这块int内存的垃圾收集 . p -= 10; // 咦,居然又偏移到原来的位置 *p = 10; // 如果有垃圾收集,这里就无法保证可以正常运行了 微软的C++/CLI算是用一种偏门的方式让C++程序员们有机会品尝一下垃圾回收功能的鲜美 味道。在C/C++之后出现的新语言,比如Java和C#等,基本上都已经自带自动垃圾回收功能。 Go语言作为一门新生的开发语言,当然不能忽略内存管理这个问题。又因为Go语言没有C++ 这么“强大”的指针计算功能,因此可以很自然地包含垃圾回收功能。因为垃圾回收功能的支持, 开发者无需担心所指向的对象失效的问题,因此Go语言中不需要delete关键字,也不需要free() 方法来明确释放内存。例如,对于以上的这个C语言例子,如果使用Go语言实现,我们就完全不 用考虑何时需要释放之前分配的内存的问题,系统会自动帮我们判断,并在合适的时候(比如CPU 相对空闲的时候)进行自动垃圾收集工作。 1.2.2 更丰富的内置类型 除了几乎所有语言都支持的简单内置类型(比如整型和浮点型等)外,Go语言也内置了一 些比较新的语言中内置的高级类型,比如C#和Java中的数组和字符串。除此之外,Go语言还内置 了一个对于其他静态类型语言通常用库方式支持的字典类型(map)。Go语言设计者对为什么内 置map这个问题的回答也颇为简单:既然绝大多数开发者都需要用到这个类型,为什么还非要每 个人都写一行import语句来包含一个库?这也是一个典型的实战派观点,与很多其他语言的学 院派气息迥然不同。 另外有一个新增的数据类型:数组切片(Slice)。我们可以认为数组切片是一种可动态增 长的数组。这几种数据结构基本上覆盖了绝大部分的应用场景。数组切片的功能与C++标准库中 的vector非常类似。Go语言在语言层面对数组切片的支持,相比C++开发者有效地消除了反复 写以下几行代码的工作量: 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
1.2语言特性5#include<vector>#include<map>#include<algorithm>using namespace std;因为是语言内置特性,开发者根本不用费事去添加依赖的包,既可以少一些输入工作量,也可以让代码看起来尽量简洁。1.2.33函数多返回值自前的主流语言中除Pvthon外基本都不支持函数的多返回值功能,不是没有这类需求,可能是语言设计者没有想好该如何提供这个功能,或者认为这个功能会影响语言的美感。比如我们如果要定义一个函数用于返回个人名字信息,而名字信息因为包含多个部分一一姓氏、名字、中间名和别名:在不支持多返回值的语言中我们有以下两种做法:要么专门定义一个结构体用于返回,比如:struct nametchar first_name[20];char middle_name[20];char last_name[20];char nick_name[48];J;/1函数原型extern name get_name();//函数调用name n = get_name() ;或者以传出参数的方式返回多个结果:1/函数原型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语言革命性地在静态开发语言阵营中率先提供了多返回值功能。这个特性让开发者可以从原来用各种比较别扭的方式返回多个值的痛苦中解脱出来,既不用再区分参数列表中哪几个用于输人,哪几个用于输出,也不用再只为了返回多个值而专门定义一个数据结构。图灵社区会员soooldier(soooldier@live.com)专享尊重版权
1.2 语言特性 5 1 2 3 4 5 9 6 7 8 2 #include <vector> #include <map> #include <algorithm> using namespace std; 因为是语言内置特性,开发者根本不用费事去添加依赖的包,既可以少一些输入工作量,也 可以让代码看起来尽量简洁。 1.2.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语言革命性地在静态开发语言阵营中率先提供了多返回值功能。这个特性让开发者可以 从原来用各种比较别扭的方式返回多个值的痛苦中解脱出来,既不用再区分参数列表中哪几个用 于输入,哪几个用于输出,也不用再只为了返回多个值而专门定义一个数据结构。 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
6第1章初识Go语言在Go语言中,上述的例子可以修改为以下的样子:func getName()(firstName, middleName, lastName, nickName string)(return"May","M","Chen","Babe"1因为返回值都已经有名字,因此各个返回值也可以用如下方式来在不同的位置进行赋值,从而提供了极大的灵活性:func getName()(firstName, middleName, lastName, nickName string)(firstName -"May"middleName ="M"lastName ="Chen"nickName ="Babe"return0并不是每一个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。而函数的调用相比C/C++语言要简化很多:fn, mn, In, nn := getName()如果开发者只对该函数其中的某几个返回值感兴趣的话,也可以直接用下划线作为占位符来忽略其他不关心的返回值。下面的调用表示调用者只希望接收1astName的值,这样可以避免声明完全没用的变量:, ,lastName,- := getName()我们会在第2章中详细讲解多重返回值的用法。1.2.4错误处理Go语言引人了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和recover。本书的“序”已经用示例展示了defer关键字的强大之处,在第2章中我们还会详细描述Go语言错误处理机制的独特之处。整体上而言与C++和Java等语言中的异常捕获机制相比,Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。这对于代码的阅读者和维护者来说也是一件很好的事情,因为可以避免在层层的代码嵌套中定位业务代码。2.6节将介绍Go语言中的错误处理机制。1.2.5诺名函数和闭包在Go语言中,所有的函数也是值类型,可以作为参数传递。Go语言支持常规的屠名函数和闭包,比如下列代码就定义了一个名为的匿名函数,开发者可以随意对该匿名函数变量进行传递和调用:f:=func(x,yint) int (return x + y1图灵社区会员soooldier(soooldier@live.com)专享尊重版权
6 第 1 章 初识 Go 语言 在Go语言中,上述的例子可以修改为以下的样子: 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 } 并不是每一个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。而函数的调 用相比C/C++语言要简化很多: fn, mn, ln, nn := getName() 如果开发者只对该函数其中的某几个返回值感兴趣的话,也可以直接用下划线作为占位符来 忽略其他不关心的返回值。下面的调用表示调用者只希望接收lastName的值,这样可以避免声 明完全没用的变量: _, _, lastName, _ := getName() 我们会在第2章中详细讲解多重返回值的用法。 1.2.4 错误处理 Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和 recover。本书的“序”已经用示例展示了defer关键字的强大之处,在第2章中我们还会详细 描述Go语言错误处理机制的独特之处。整体上而言与C++和Java等语言中的异常捕获机制相比, Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量 一层套一层的try-catch语句。这对于代码的阅读者和维护者来说也是一件很好的事情,因为可 以避免在层层的代码嵌套中定位业务代码。2.6节将介绍Go语言中的错误处理机制。 1.2.5 匿名函数和闭包 在Go语言中,所有的函数也是值类型,可以作为参数传递。Go语言支持常规的匿名函数和 闭包,比如下列代码就定义了一个名为f的匿名函数,开发者可以随意对该匿名函数变量进行传 递和调用: f := func(x, y int) int { return x + y } 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
1.2语言特性71.2.6类型和接口Go语言的类型定义非常接近于C语言中的结构(struct),甚至直接沿用了struct关键字。相比而言,Go语言并没有直接沿袭C++和Java的传统去设计一个超级复杂的类型系统,不支持继承和重载,而只是支持了最基本的类型组合功能。巧妙的是,虽然看起来支持的功能过于简洁,细用起来你却会发现,C++和Java使用那些复杂的类型系统实现的功能在Go语言中并不会出现无法表现的情况,这反而让人反思其他语言中引入这些复杂概念的必要性。我们在第3章中将详细描述Go语言的类型系统。Go语言也不是简单的对面向对象开发语言做减法,它还引入了一个无比强大的“非侵人式”接口的概念,让开发者从以往对C++和Java开发中的接口管理问题中解脱出来。在C++中,我们通常会这样来确定接口和类型的关系://抽象接口interface IFlytvirtual void Fly()=0;1;1/实现类classBird:publicIFly(public:Bird()0virtual -Bird()(0public:void Fly()([/以鸟的方式飞行1];void main()LIFly*pFly =new Bird();pFly->Fly();delete prly:1显然,在实现一个接口之前必须先定义该接口,并且将类型和接口紧密绑定,即接口的修改会影响到所有实现了该接口的类型,而Go语言的接口体系则避免了这类问题:type Bird struct (1func(b*Bird)Fly()(以鸟的方式飞行J图灵社区会员soooldier(soooldier@live.com)专享尊重版权
1.2 语言特性 7 1 2 3 4 5 9 6 7 8 2 1.2.6 类型和接口 Go语言的类型定义非常接近于C语言中的结构(struct),甚至直接沿用了struct关键字。相 比而言,Go语言并没有直接沿袭C++和Java的传统去设计一个超级复杂的类型系统,不支持继承 和重载,而只是支持了最基本的类型组合功能。 巧妙的是,虽然看起来支持的功能过于简洁,细用起来你却会发现,C++和Java使用那些复 杂的类型系统实现的功能在Go语言中并不会出现无法表现的情况,这反而让人反思其他语言中 引入这些复杂概念的必要性。我们在第3章中将详细描述Go语言的类型系统。 Go语言也不是简单的对面向对象开发语言做减法,它还引入了一个无比强大的“非侵入式” 接口的概念,让开发者从以往对C++和Java开发中的接口管理问题中解脱出来。在C++中,我们 通常会这样来确定接口和类型的关系: // 抽象接口 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语言的接口体系则避免了这类问题: type Bird struct { . } func (b *Bird) Fly() { // 以鸟的方式飞行 } 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权