[摘]Go 语言简介(上)— 语法

作者:陈皓

只要求你对C语言,Unix,Python有少数基础,我相信您会在27分钟左右读完并对Go语言有一部分上马询问的。

图片 1

Hello World

文件名 hello.go
1
2
3
4
5
6
7
package main //声明本文件的package名
 
import "fmt" //import语言的fmt库——用于输出
 
func main() {
    fmt.Println("hello world")
}

 

运行

你能够有两种运市场价格势,

解释执行(实际是编译成a.out再执行)
1
2
$go run hello.go
hello world
编译执行
1
2
3
4
5
6
7
$go build hello.go
 
$ls
hello hello.go
 
$./hello
hello world

自己的package

你能够动用GOPATH环境变量,或是使用相对路径来import你自身的package。

Go的清规戒律是如此的:

1)在import中,你可以动用相对路径,如 ./或 ../ 来引用你的package

2)固然没有动用相对路径,那么,go会去找$GOPATH/src/目录。

使用相对路径
1
2
import "./haoel"  //import当前目录里haoel子目录里的所有的go文件
使用GOPATH路径
1
2
import "haoel"  //import 环境变量 $GOPATH/src/haoel子目录里的所有的go文件

fmt输出格式

fmt包和libc里的那堆使用printf, scanf,fprintf,fscanf
很相似。上面包车型地铁东西对于C程序员不会素不相识。

小心:Println不帮助,Printf才支撑%式的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import "fmt"
import "math"
 
func main() {
    fmt.Println("hello world")
 
    fmt.Printf("%t\n", 1==2)
    fmt.Printf("二进制:%b\n", 255)
    fmt.Printf("八进制:%o\n", 255)
    fmt.Printf("十六进制:%X\n", 255)
    fmt.Printf("十进制:%d\n", 255)
    fmt.Printf("浮点数:%f\n", math.Pi)
    fmt.Printf("字符串:%s\n", "hello world")
}

理所当然,也得以采纳如\n\t\r那样的和C语言一样的控制字符

变量和常量

变量的扬言很像 javascript,使用
var关键字。注意:go是静态类型的语言,上面是代码:

1
2
3
4
5
6
7
8
//声明初始化一个变量
var  x int = 100
var str string = "hello world"</pre>
//声明初始化多个变量
var  i, j, k int = 1, 2, 3
 
//不用指明类型,通过初始化值来推导
var b = true //bool型

还有一种概念变量的艺术(那让自家想到了Pascal语言,但完全不平等)

1
2
x := 100 //等价于 var x int = 100;

常量很简短,使用const关键字:

1
2
const s string = "hello world"
const pi float32 = 3.1415926

数组

直接看代码(注意当中的for语句,和C很一般吧,便是从未括号了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
    var a [5]int
    fmt.Println("array a:", a)
 
    a[1] = 10
    a[3] = 30
    fmt.Println("assign:", a)
 
    fmt.Println("len:", len(a))
 
    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println("init:", b)
 
    var c [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            c[i][j] = i + j
        }
    }
    fmt.Println("2d: ", c)
}

运营结果:

1
2
3
4
5
6
array a: [0 0 0 0 0]
assign: [0 10 0 30 0]
len: 5
init: [1 2 3 4 5]
2d:  [[0 1 2] [1 2 3]]

数组的切片操作

这个很Python了。

1
2
3
4
5
6
7
8
9
10
11
a := [5]int{1, 2, 3, 4, 5}
 
b := a[2:4] // a[2] 和 a[3],但不包括a[4]
fmt.Println(b)
 
b = a[:4] // 从 a[0]到a[4],但不包括a[4]
fmt.Println(b)
 
b = a[2:] // 从 a[2]到a[4],且包括a[2]
fmt.Println(b)

分层循环语句

if语句

留神:if 语句没有圆括号,而必需求有花括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//if 语句
if x % 2 == 0 {
    //...
}
//if - else
if x % 2 == 0 {
    //偶数...
} else {
    //奇数...
}
 
//多分支
if num < 0 {
    //负数
} else if num == 0 {
    //零
} else {
    //正数
}

switch 语句

只顾:switch语句没有break,仍是能够使用逗号case多个值

1
2
3
4
5
6
7
8
9
10
11
12
switch i {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")
    case 4,5,6:
        fmt.Println("four, five, six")
    default:
        fmt.Println("invalid value!")
}

for 语句

前边你已见过了,上面再来看看for的二种样式:(注意:Go语言中一向不while)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//经典的for语句 init; condition; post
for i := 0; i<10; i++{
     fmt.Println(i)
}
 
//精简的for语句 condition
i := 1
for i<10 {
    fmt.Println(i)
    i++
}
 
//死循环的for语句 相当于for(;;)
i :=1
for {
    if i>10 {
        break
    }
    i++
}

至于分号

从地点的代码大家得以看出代码里从未分号。其实,和C一样,Go的正儿八经的语法使用分号来终止语句。和C分歧的是,那一个分公司由词法分析器在扫描源代码进度中应用简单的规则自动插入分号,因而输入源代码多数时候就不须要分号了

平整是那样的:假若在二个新行前方的末梢三个标志是七个标识符(包含像intfloat64如此那般的单词)、2当中坚的如数值那样的文字、或以下标记中的三个时,会自行插入分号:

break continue fallthrough return ++ -- ) }

一般说来Go程序仅在for循环语句中运用分号,以此来分别初叶化器、条件和增量单元。借使你在一行中写八个语句,也亟需用分号分开。

注意无论任哪天候,你都不应有将二个控制结构((ifforswitchselect)的左大括号放在下一行。假诺那样做,将会在大括号的前沿插入1个分公司,那大概导致出现不想要的结果

map

map在别的语言里只怕叫哈希表或叫dict,下面是和map的连带操作的代码,代码很容易懂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func main(){
    m := make(map[string]int) //使用make创建一个空的map
 
    m["one"] = 1
    m["two"] = 2
    m["three"] = 3
 
    fmt.Println(m) //输出 map[three:3 two:2 one:1] (顺序在运行时可能不一样)
    fmt.Println(len(m)) //输出 3
 
    v := m["two"] //从map里取值
    fmt.Println(v) // 输出 2
 
    delete(m, "two")
    fmt.Println(m) //输出 map[three:3 one:1]
 
    m1 := map[string]int{"one": 1, "two": 2, "three": 3}
    fmt.Println(m1) //输出 map[two:2 three:3 one:1] (顺序在运行时可能不一样)
 
    for key, val := range m1{
        fmt.Printf("%s => %d \n", key, val)
        /*输出:(顺序在运行时可能不一样)
            three => 3
            one => 1
            two => 2*/
    }
}

指针

Go语言一样有指针,看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
var i int = 1
var pInt *int = &i
//输出:i=1     pInt=0xf8400371b0       *pInt=1
fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)
 
*pInt = 2
//输出:i=2     pInt=0xf8400371b0       *pInt=2
fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)
 
i = 3
//输出:i=3     pInt=0xf8400371b0       *pInt=3
fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)

Go具有三个分配内部存款和储蓄器的建制,分别是内建的函数new和make。他们所做的事不相同,所采用到的花色也不相同,这或者引起混淆,但规则却很不难。

内存分配

new
是一个分配内部存款和储蓄器的内建函数,但分化于其他语言中同名的new所作的办事,它只是将内部存储器清零,而不是开端化内部存款和储蓄器。new(T)为一个门类为T的新品类分配了值为零的囤积空间并回到其地址,也正是多个品种为*T的值。用Go的术语来说,正是它回到了3个对准新分配的花色为T的零值的指针

make(T,args)函数的指标与new(T)今非昔比。它仅用于创立切片、map和chan(音讯管道),并赶回类型T(不是*T)的一个被初阶化了的(不是)实例。那种差异的面世是出于那三种档次实质上是对在运用前必须实行初步化的数据结构的引用。例如,切片是三个颇具三项内容的描述符,包蕴针对数据(在3个数组内部)的指针、长度以及体积,在那三项内容被初步化在此以前,切片值为nil。对于切片、映射和信道,make开端化了其里面包车型大巴数据结构并准备了就要采纳的值。如:

下边包车型客车代码分配了2个整型数组,长度为10,体量为100,并重返前拾2个数组的切片

1
make([]int, 10, 100)

以下示例表明了newmake的不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
var p *[]int = new([]int)   // 为切片结构分配内存;*p == nil;很少使用
var v  []int = make([]int, 10) // 切片v现在是对一个新的有10个整数的数组的引用
 
// 不必要地使问题复杂化:
var p *[]int = new([]int)
fmt.Println(p) //输出:&[]
*p = make([]int, 10, 10)
fmt.Println(p) //输出:&[0 0 0 0 0 0 0 0 0 0]
fmt.Println((*p)[2]) //输出: 0
 
// 习惯用法:
v := make([]int, 10)
fmt.Println(v) //输出:[0 0 0 0 0 0 0 0 0 0]

函数

老实说,笔者对Go语言那种扭曲注明变量类型和函数重回值的做法有点缺憾(保持和C一样的不行吗?
呵呵)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
 
func max(a int, b int) int { //注意参数和返回值是怎么声明的
 
    if a > b {
        return a
    }
    return b
}
 
func main(){
    fmt.Println(max(4, 5))
}

函数重返多少个值

Go中很多Package 都会回来四个值,3个是平常值,2个是错误,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import "fmt"
 
func main(){
    v, e := multi_ret("one")
    fmt.Println(v,e) //输出 1 true
 
    v, e = multi_ret("four")
    fmt.Println(v,e) //输出 0 false
 
    //通常的用法(注意分号后有e)
    if v, e = multi_ret("four"); e {
        // 正常返回
    }else{
        // 出错返回
    }
}
 
func multi_ret(key string) (int, bool){
    m := map[string]int{"one": 1, "two": 2, "three": 3}
 
    var err bool
    var val int
 
    val, err = m[key]
 
    return val, err
}

函数不定参数

事例很清楚了,我就不多说了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func sum(nums ...int) {
    fmt.Print(nums, " "//输出如 [1, 2, 3] 之类的数组
    total := 0
    for _, num := range nums { //要的是值而不是下标
        total += num
    }
    fmt.Println(total)
}
func main() {
    sum(1, 2)
    sum(1, 2, 3)
 
    //传数组
    nums := []int{1, 2, 3, 4}
    sum(nums...)
}

函数闭包

nextNum那一个函数重回了四个匿名函数,那些匿名函数记住了nextNum中i+j的值,并转移了i,j的值,于是形成了一个闭包的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func nextNum() func() int {
    i,j := 1,1
    return func() int {
        var tmp = i+j
        i, j = j, tmp
        return tmp
    }
}
//main函数中是对nextNum的调用,其主要是打出下一个斐波拉契数
func main(){
    nextNumFunc := nextNum()
    for i:=0; i<10; i++ {
        fmt.Println(nextNumFunc())
    }
}

函数的递归

和c基本是千篇一律的

1
2
3
4
5
6
7
8
9
10
func fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * fact(n-1)
}
 
func main() {
    fmt.Println(fact(7))
}

结构体

Go的结构体和C的大多一样,不过在初叶化时有点分裂等,Go补助带名字的开首化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Person struct {
    name string
    age  int
    email string
}
 
func main() {
    //初始化
    person := Person{"Tom", 30, "tom@gmail.com"}
    person = Person{name:"Tom", age: 30, email:"tom@gmail.com"}
 
    fmt.Println(person) //输出 {Tom 30 tom@gmail.com}
 
    pPerson := &person
 
    fmt.Println(pPerson) //输出 &{Tom 30 tom@gmail.com}
 
    pPerson.age = 40
    person.name = "Jerry"
    fmt.Println(person) //输出 {Jerry 40 tom@gmail.com}
}

结构体方法

不多说了,看代码吧。

小心:Go语言中没有public, protected,
private的首要字,所以,设若你想让四个方法可以被别的包访问的话,你供给把那些法子的第①个字母大写。那是一种约定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type rect struct {
    width, height int
}
 
func (r *rect) area() int { //求面积
    return r.width * r.height
}
 
func (r *rect) perimeter() int{ //求周长
    return 2*(r.width + r.height)
}
 
func main() {
    r := rect{width: 10, height: 15}
 
    fmt.Println("面积: ", r.area())
    fmt.Println("周长: ", r.perimeter())
 
    rp := &r
    fmt.Println("面积: ", rp.area())
    fmt.Println("周长: ", rp.perimeter())
}

接口和多态

接口意味着多态,下边是3个经典的例子,不用多说了,本人看代码吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//---------- 接 口 --------//
type shape interface {
    area() float64 //计算面积
    perimeter() float64 //计算周长
}
 
//--------- 长方形 ----------//
type rect struct {
    width, height float64
}
 
func (r *rect) area() float64 { //面积
    return r.width * r.height
}
 
func (r *rect) perimeter() float64 { //周长
    return 2*(r.width + r.height)
}
 
//----------- 圆  形 ----------//
type circle struct {
    radius float64
}
 
func (c *circle) area() float64 { //面积
    return math.Pi * c.radius * c.radius
}
 
func (c *circle) perimeter() float64 { //周长
    return 2 * math.Pi * c.radius
}
 
// ----------- 接口的使用 -----------//
func interface_test() {
    r := rect {width:2.9, height:4.8}
    c := circle {radius:4.3}
 
    s := []shape{&r, &c} //通过指针实现
 
    for _, sh := range s {
        fmt.Println(sh)
        fmt.Println(sh.area())
        fmt.Println(sh.perimeter())
    }
}

错误处理 – Error接口

函数错误再次来到恐怕是C/C++时最令人纠结的东西的,Go的多值重回能够让我们更便于的回来错误,其可以在回来1个平常的重返值之外,还是能够随便地赶回三个详细的失实描述。常常状态下,错误的花色是error,它有3个内建的接口。

1
2
3
type error interface {
    Error() string
}

要么看个示范吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main
 
import "fmt"
import "errors"
 
//自定义的出错结构
type myError struct {
    arg  int
    errMsg string
}
//实现Error接口
func (e *myError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.errMsg)
}
 
//两种出错
func error_test(arg int) (int, error) {
    if arg < 0  {
         return -1, errors.New("Bad Arguments - negtive!")
     }else if arg >256 {
        return -1, &myError{arg, "Bad Arguments - too large!"}
    }
    return arg*arg, nil
}
 
//相关的测试
func main() {
    for _, i := range []int{-1, 4, 1000} {
        if r, e := error_test(i); e != nil {
            fmt.Println("failed:", e)
        } else {
            fmt.Println("success:", r)
        }
    }
}

程序运维后输出:

1
2
3
failed: Bad Arguments - negtive!
success: 16
failed: 1000 - Bad Arguments - too large!

错误处理 – Defer

上面包车型客车主次对于每一个耳熟能详C语言的人的话都不素不相识(有能源走漏的题材),C++使用RAII来缓解那种难点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
 
    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
 
    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

Go语言引入了Defer来确定保证那二个被打开的文本能被关闭。如下所示:(那种消除情势照旧相比优雅的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
 
    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()
 
    return io.Copy(dst, src)
}

Go的defer语句预设三个函数调用(延期的函数),该调用在函数执行defer重临时立即运维。该办法显得不相同健康,但却是处理上述情状很得力,无论函数怎样重临,都必须开始展览能源自由。

我们再来看贰个defer函数的言传身教:

1
2
3
for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

被推迟的函数以往进先出(LIFO)的顺行执行,因而上述代码在回去时将打字与印刷4 3
2 1 0。

由此可见,作者个人觉得defer的函数行为有点蹊跷,笔者未来还从未完全搞掌握。

错误处理 – Panic/Recover

对此不可恢复生机的一无所能,Go提供了2个内建的panic函数,它将开创3个周转时不当并使程序结束(极度强力)。该函数收取3个随意档次(往往是字符串)作为程序去世时要打字与印刷的东西。当编写翻译器在函数的结尾处检查到3个panic时,就会终止开始展览常规的return语句检查。

上边包车型地铁只是是多少个演示。实际的库函数应防止panic。纵然难点得以容忍,最好是让工作继续下去而不是结束整个程序。

1
2
3
4
5
6
7
var user = os.Getenv("USER")
 
func init() {
    if user == "" {
        panic("no value for $USER")
    }
}

当panic被调用时,它将即时停下当前函数的执行并初叶逐级解开函数堆栈,同时运营具有被defer的函数。如若那种解开达到堆栈的下边,程序就离世了。可是,也足以使用内建的recover函数来重新得到Go程的控制权并苏醒日常的进行。
对recover的调用会文告解开堆栈并回到传递到panic的参量。由于仅在解开时期运转的代码处在被defer的函数之内,recover仅在被推迟的函数内部才是可行的。

你能够省略地理解为recover正是用来捕捉Painc的,幸免程序一下子就挂掉了。

上边是三个例程,很简短了,不表达了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func g(i int) {
    if i>1 {
        fmt.Println("Panic!")
        panic(fmt.Sprintf("%v", i))
    }
 
}
 
func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
 
    for i := 0; i < 4; i++ {
        fmt.Println("Calling g with ", i)
        g(i)
        fmt.Println("Returned normally from g.")
     }
}
 
func main() {
    f()
    fmt.Println("Returned normally from f.")
}

运维结果如下:(我们能够看来Painc后的for循环就没有往下进行了,可是main的次第还在往下走)

1
2
3
4
5
6
7
8
Calling g with  0
Returned normally from g.
Calling g with  1
Returned normally from g.
Calling g with  2
Panic!
Recovered in f 2
Returned normally from f.

你习惯那种编制程序情势啊?作者觉着有些好奇。呵呵。

好了,上面是是一Go语言相关的编制程序语法的介绍,作者尚未事无巨细,只是让您打探一下Go语言是长什么的。理所当然,那还没完,请期待下篇——Go语言的性状

(转发本站小说请注解小编和出处 酷壳 –
CoolShell.cn
,请勿用于其它商业用途)