Go语言的这些地方都做的还不错:

  • 拥有自动垃圾回收: 不用手动释放内存
  • 一个包系统:

Go 语言的源码复用建立在包(package)基础之上。包通过 package, import, GOPATH 操作完成。

        Go 语言的入口 main() 函数所在的包(package)叫 main,main 包想要引用别的代码,需要import导入.

包需要满足:

  1.  一个目录下的同级文件归属一个包。
  2.  包名可以与其目录不同名。
  3. 包名为 main 的包为应用程序的入口包,其他包不能使用
  4. 包中,通过标识符首字母是否大写,来确定是否可以被导出。首字母大写才可以被导出,视为 public 公共的资源。
  5. 要引用其他包,可以使用 import 关键字,可以单个导入或者批量导入,语法演示:

    // 单个导入
    import "package"
    // 批量导入
    import (
    "package1"
    "package2"
    )
  6. 导入时,可以为包定义别名:
import (
p1 "package1"
p2 "package2"
)
// 使用时
p1.Method()

  7.  import导入时,会从GO的安装目录(也就是GOROOT环境变量设置的目录)和GOPATH环境变量设置的目录中,检索 src/package 来导入包。如果不存在,则导入失败。

     GOROOT,就是GO内置的包所在的位置。

          GOPATH,就是我们自己定义的包的位置。

  8. 可以在源码中,定义 init() 函数。此函数会在包被导入时执行,例如如果是在 main 中导入包,包中存在 init(),那么 init() 中的代码会在 main() 函数执行前执行,用于初始化包所需要的特定资料。

  golang的包管理有很大的问题,这个后续有章节单独介绍。

  • 函数作为一等公民
      支持头等函数(First Class Function)的编程语言,可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。Go 语言支持头等函数的机制
package main

import (
"fmt"
) func main() {
a := func() {
fmt.Println("hello world first class function")
}
a()
fmt.Printf("%T", a)
}

  在上面的程序中,我们将一个函数赋值给了变量 a这是把函数赋值给变量的语法。你如果观察得仔细的话,会发现赋值给 a 的函数没有名称。由于没有名称,这类函数称为匿名函数(Anonymous Function)

输出:

hello world first class function
func()

  调用一个匿名函数,也可以不赋值。如下是上面使用的简化版本:

package main

import (
"fmt"
) func main() {
func() {
fmt.Println("hello world first class function")
}()
}

而且还可以给匿名函数传参数呢:

// mainfile
package main import (
"fmt"
) func main() {
func(n string) {
fmt.Println("Welcome", n)
}("Gophers")
}

输出:

Welcome Gophers

正如我们定义自己的结构体类型一样,我们可以定义自己的函数类型

// mainfile
package main import (
"fmt"
)
type add func(a int, b int) int
func main() {
var a add = func(a int, b int) int {
return a + b
}
s := a(, )
fmt.Println("Sum", s)
}

高阶函数:

  满足下列条件之一的函数

  1. 接收一个或多个函数作为参数
package main

import (
"fmt"
) func simple(a func(a, b int) int) {
fmt.Println(a(, ))
} func main() {
f := func(a, b int) int {
return a + b
}
simple(f)
}

2.  函数返回函数

func simple() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
} func main() {
s := simple()
fmt.Println(s(, ))
}

 下面通过一个例子来看看头灯函数的实际用途:

首先定义一个 student 类型:

type student struct {
firstName string
lastName string
grade string
country string
}

下一步是编写一个 filter 函数。该函数接收一个 students 切片和一个函数作为参数,这个函数会计算一个学生是否满足筛选条件:

func filter(s []student, f func(student) bool) []student {
var r []student
for _, v := range s {
if f(v) == true {
r = append(r, v)
}
}
return r
}

filter 的第二个参数是一个函数。这个函数接收 student 参数,返回一个 bool 值。这个函数计算了某一学生是否满足筛选条件。我们在第 3 行遍历了 student 切片,将每个学生作为参数传递给了函数 f。如果该函数返回 true,就表示该学生通过了筛选条件,接着将该学生添加到了结果切片 r 中。

package main

import (
"fmt"
) type student struct {
firstName string
lastName string
grade string
country string
} func filter(s []student, f func(student) bool) []student {
var r []student
for _, v := range s {
if f(v) == true {
r = append(r, v)
}
}
return r
} func main() {
s1 := student{
firstName: "Naveen",
lastName: "Ramanathan",
grade: "A",
country: "India",
}
s2 := student{
firstName: "Samuel",
lastName: "Johnson",
grade: "B",
country: "USA",
}
s := []student{s1, s2}
f := filter(s, func(s student) bool {
if s.grade == "B" {
return true
}
return false
})
fmt.Println(f)
}

在 main 函数中,我们首先创建了两个学生 s1 和 s2,并将他们添加到了切片 s。现在假设我们想要查询所有成绩为 B 的学生。为了实现这样的功能,我们传递了一个检查学生成绩是否为 B 的函数,如果是,该函数会返回 true。我们把这个函数作为参数传递给了 filter 函数(第 38 行)。上述程序会输出

假设我们想要查找所有来自印度的学生。通过修改传递给 filter 的函数参数,就很容易地实现了。

c := filter(s, func(s student) bool {
if s.country == "India" {
return true
}
return false
})
fmt.Println(c)
  • 词法作用域
  • 系统调用接口: 系统调用是程序向操作系统内核请求服务的过程,通常包含硬件相关的服务(例如访问硬盘),创建新进程等。系统调用提供了一个进程和操作系统之间的接口。
  • 只读的UTF8字符串: golang的字符串是只读的unicode字节序列,Go语言使用UTF-8格式编码Unicode字符,每个字符对应一个rune类型。一旦字符串变量赋值之后,内部的字符就不能修改。

但是Go语言本身只有很少的特性,也不太可能添加太多的特性:

  • 它没有隐式的数值转换,
  • 没有构造函数和析构函数,
  • 没有运算符重载,
  • 没有默认参数,
  • 也没有继承: 

    golang语言中没有继承,但是可以依靠组合来模拟继承和多态。

    但是,这样模拟出来的继承是有局限的,也就是说:在需要多态的时候,需要小心。

package main  

type ST struct{
} func (s *ST)Show(){
println("ST")
} func (s *ST)Show2(){
println("ST:Show2()")
} type ST2 struct{
ST // 注意,这里是ST 而不是 st ST !!!!!!!!
I int
} func (s *ST2)Show(){
println("ST2")
} func main() {
s := ST2{I:}
s.Show() // ST2自己有show( )方法;
s.Show2() // ST2自己没有show2方法
println(s.I)
}

输出:

ST2
ST:Show2()
  • 没有泛型: 但是Golang也有解决方案:

看一下冒泡排序:

package main

import (
"fmt"
) func bubbleSort(array []int) {
for i := ; i < len(array); i++ {
for j := ; j < len(array)-i-; j++ {
if array[j] > array[j+] {
array[j], array[j+] = array[j+], array[j]
}
}
}
} func main() {
a1 := []int{, , , , , , , }
bubbleSort(a1)
fmt.Println(a1)
}

针对上面的排序问题,我们可以分析一下排序的步骤:

    1. 查看切片长度,以用来遍历元素(Len);
    2. 比较切片中的两个元素(Less);
    3. 根据比较的结果决定是否交换元素位置(Swap)。
      到这里,或许你已经明白了,我们可以把上面的函数分解为一个支持任意类型的接口,任何其他类型的数据只要实现了这个接口,就可以用这个接口中的函数来排序了。

定义接口:

type Sortable interface{
Len() int
Less(int, int) bool
Swap(int, int)
}

下面看一下实现:

package main

import (
"fmt"
)

// 定义接口
type Sortable interface {
Len() int
Less(int, int) bool
Swap(int, int)
}

// 对接口编程,实现冒泡排序
func bubbleSort(array Sortable) {
for i := ; i < array.Len(); i++ {
for j := ; j < array.Len()-i-; j++ {
if array.Less(j+, j) {
array.Swap(j, j+)
}
}
}
} //实现接口的整型切片
type IntArr []int

// 实现接口
func (array IntArr) Len() int {
return len(array)
}
// 实现接口
func (array IntArr) Less(i int, j int) bool {
return array[i] < array[j]
}
// 实现接口
func (array IntArr) Swap(i int, j int) {
array[i], array[j] = array[j], array[i]
} //实现接口的字符串,按照长度排序
type StringArr []string

// 实现接口
func (array StringArr) Len() int {
return len(array)
}
// 实现接口
func (array StringArr) Less(i int, j int) bool {
return len(array[i]) < len(array[j])
}
// 实现接口
func (array StringArr) Swap(i int, j int) {
array[i], array[j] = array[j], array[i]
} //测试
func main() {
intArray1 := IntArr{, , , , , }
bubbleSort(intArray1)
fmt.Println(intArray1) stringArray1 := StringArr{"hello", "i", "am", "go", "lang"}
bubbleSort(stringArray1)
fmt.Println(stringArray1)
}
  • 没有异常,

  在Golang错误处理中,变量名称err遍布各处。不论Golang项目有多大和多重要,普遍的格式化错误结构如下:

  f, err := os.Open(filename)

    if err != nil {

    // Handle the error here

  }

  根据Golang的约定,每个可能导致错误的函数都将error其作为最后一个返回值,码农有责任在每一步都正确处理它,所以golang代码中随处都是"if err != nil"语句。用条件处理每一个错误令人反感,而且非常不好看。

  golang 中的错误处理的哲学和 C 语言一样,函数通过返回错误类型(error)或者 bool 类型(不需要区分多种错误状态时)表明函数的执行结果,调用检查返回的错误类型值是否是 nil 来判断调用结果。

  "这设计有问题,为啥处处要检查错误",估计每一个刚入Golang的人都会有这样的共识。

  在Go以外的语言中(c++, java,python),你需要将所有内容包装在相同的内容中try...catch.

  Golang中,将他严格规定下来:可能失败的每个函数都应该返回一个error类型作为最后一个值,并且随后对其处理。

     golang 中内置的错误类型 error 是一个接口类型,自定义的错误类型也必须实现为 error 接口,这样调用总是可以通过 Error() 获取到具体的错误信息而不用关心错误的具体类型。标准库的 fmt.Errorf 和 errors.New 可以方便的创建 error 类型的变量。

type error interface {
Error() string
}

  golang 的多返回值语法糖(可以返回多个值),错误值一般作为返回值列表的最后一个,其他返回值是成功执行时需要返回的信息。为了避免错误处理时过深的代码缩进。

if err != nil {
// error handling
} else {
// normal code
}

预定义错误:

func doStuff() error {
if someCondition {
return errors.New("no space left on the device") // errors.NEW - 非常方便的生成新的error
} else {
return errors.New("permission denied")
}
}

  但是上面的方法不好,因为对返回error进行检查时,需要根据字符串比较才能知道错误的类型,下面是一种优雅的方式:

var ErrNoSpaceLeft = errors.New("no space left on the device")
var ErrPermissionDenied = errors.New("permission denied") func doStuff() error {
if someCondition {
return ErrNoSpaceLeft
} else {
return ErrPermissionDenied
}
}

根据下面的方式进行判断:优雅很多

if err == ErrNoSpaceLeft {
// handle this particular error
}

自定义错误:

  HTTP 表示客户端的错误状态码有几十个。如果为每种状态码都预定义相应的错误值,代码会变得很繁琐:

var ErrBadRequest = errors.New("status code 400: bad request")
var ErrUnauthorized = errors.New("status code 401: unauthorized")
// ...

  这种场景下最佳的最法是自定义一种错误类型,并且至少实现 Error() 方法(满足 error 定义):

type HTTPError struct {
Code int
Description string
} func (h *HTTPError) Error() string {
return fmt.Sprintf("status code %d: %s", h.Code, h.Description)
}

判断的的代码如下:

func request() error {
return &HTTPError{, "not found"}
} func main() {
err := request() if err != nil {
// an error occured
if err.(*HTTPError).Code == {
// handle a "not found" error
} else {
// handle a different error
}
} }

panic 、recover、 defer  :   会有专门的章节讲解这个专题

  • 没有宏,   没有define(define有副作用:  没有类型检查, 还有富作用)
  • 没有函数修饰,  大写表示public, 小写表示private
  • 更没有线程局部存储

补充2:Golang 一些特性的更多相关文章

  1. flipt 一个基于golang 的特性工具开发类库

    以前介绍过一个Flagr 的基于golang 的特性功能开发类库(技术雷达推荐),今天看到一个类似也很不错的方案flipt 参考架构 包含的特性 快速,使用golang 编写,同时进行了性能优化 运行 ...

  2. 补充 3:Golang 一些特性

    1 丰富的内置类型 2 函数多返回值 3 Go的错误处理 :   Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer. panic和 recover 4 在Go语言中,所有的 ...

  3. Go语言基础之1--标识符、关键字、变量和常量、数据类型、Go的基本程序结构、Golang的特性

    一.前言 当我们项目较为简单时,我们在src目录下新建一个该项目目录,里面存放源码文件即可,见下图: 当我们一个项目较为复杂时,我们可以在src目录下新建一个该项目目录,在针对该项目不同模块创建不同目 ...

  4. Golang channel 特性

    最近在项目中遇到了 Go channel 的一些问题,在此记录下 close channel 的一些特性. 关闭channel ch := make(chan bool) close(ch) clos ...

  5. golang语言特性

    1. 垃圾回收 a. 内存⾃动回收,再也不需要开发⼈员管理内存 b. 开发人员专注业务实现,降低了心智负担 c. 只需要new分配内存,不需要释放   2. 天然并发 a. 从语⾔层面⽀持并发,⾮常简 ...

  6. Go golang语言特性

    一.垃圾回收 1.内存自动回收. 2.只需要创建,不需要释放 二.天然并发: 1.语言层支持并发,对比python,少了GIL锁. 2.goroute,轻量级线程. 3.基于CSP模型实现 三.cha ...

  7. Golang 语言特性

    1. 函数 与c 语言不同,go 语言中,函数的参数和返回值都由栈来存储. 传值:函数调用时会复制参数,被调用方和调用方持有两份不相关的两份数据 传引用:函数调用时会传递参数指针,被调用方和调用方持有 ...

  8. C# 6 的新特性~

    原文地址 Mads Torgersen,微软 C# 程序管理者,它发布了一个视频,描述即将到来的下一个 C# 版本--C# 6.在 C# 6 的新特性之间,Mads 主要提到了 getter-only ...

  9. 4 个技巧学习 Golang

    到达 Golang 大陆:一位资深开发者之旅. 2014 年夏天…… IBM:“我们需要你弄清楚这个 Docker.” 我:“没问题.” IBM:“那就开始吧.” 我:“好的.”(内心声音):”Doc ...

随机推荐

  1. java读取大容量excel之一

    最近在用poi读取大容量excel,发现只要是excel文件大于2M左右,便会出现OOM(out of memory),经过查询得知,原来poi读取excel的原理是如下: org.apache.po ...

  2. JavaScript权威指南——词法结构(4)

    标识符和保留字 1.标识符 标识符就是一个名字.在JavaScript中,标识符用来给变量.属性.函数和参数进行命名,或者用做某些循环语句中的跳转位置的标记. //变量 var identifier ...

  3. SWIFT Enumeration(2)

    之前记录了Swift Enumeration(1),这篇算是它的延续吧,继续说下Enumeration,看以下定义 enum TrainStatus { case OnTime case Delay( ...

  4. A+B for Input-Output Practice (IV)

    A+B for Input-Output Practice (IV) Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768  ...

  5. 小程序引入多个e-charts

    小程序引入e-charts图表 这里是狗尾草第一次发表掘金文章,日后望各位大佬多多支持~ 前言:运营助手,见名知意,没有图表数据的展示,看上去是有多空白.因此,俺们UI做了很好的交互,一个页面来了4个 ...

  6. test20180919 区间最大值

    题意 分析 我们将所有修改操作的左右端点都拿出来混合着排序. 然后扫描线一样扫描每个端点,维护一个堆储存当前最大值,然后就可以把这些修改操作分成O(m) 个不相交的区间,各自贡献独立. 复杂度为\(O ...

  7. day 2 Linux Shell笔记

    ------------------------------------------------------------------- -------------------------------- ...

  8. Hadoop操作前准备工作

    摘要:本文介绍Hadoop操作前的准备工作. 关键词:Hadoop  Linux   JDK  WinSCP 俗语说,“磨刀不误砍柴工”.Hadoop操作前的准备工作可以加快Hadoop的操作与应用. ...

  9. (考研)散列表和hashcode和hashmap

    package tt; import java.util.HashMap; import java.util.Map; public class a0 { public static void mai ...

  10. PHP mongodb 的使用

    mongodb 不用过多的介绍了,NOSQL的一种,是一个面向文档的数据库,以其方便灵活的数据结构,对于开发者来说是比较友好的,同时查询的速度也是比较快的,现在好多网站 开始使用mongodb ,具体 ...