2

Golang基础语法【原创】

 2 years ago
source link: http://xwxz.github.io/golang-grammar-01/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文基于GO官网上的例子写就,主要帮助自己在学习go语言的过程中记忆,更多的是自己对其中一些概念的理解。

1.1 Go基础语法

函数声明关键字func,包括函数名、参数列表(参数名 参数类型)、函数返回值,(需要指明返回类型,可以指定返回变量名)。具体格式如下:

func funcName(a , b int) (sum int){
sum = a + b
return sum
}

基本数据类型

int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、 uintptr 、byte(int8)、rune(int32)、float32、float64、complex64、complex128、 string、 bool

int,uint 和 uintptr 类型在32位的系统上一般是32位,而在64位系统上是64位。当你需要使用一个整数类型时,你应该首选 int,仅当有特别的理由才使用定长整数类型或者无符号整数类型。

变量声明采用var关键字,如果声明时指定了初始值,则可以不写变量类型:

var name string = "xxx"

或短变量声明,在函数中, := 简洁赋值语句在明确类型的地方,可以用于替代 var 定义。函数外的每个语句都必须以关键字开始( var 、 func 、等等), := 结构不能使用在函数外。

name := "xxx"

var 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。 var 语句可以定义在包或函数级别。

循环for

Go中只有一种for循环,基本的 for 循环包含三个由分号分开的组成部分:

  • 初始化语句:在第一次循环执行前被执行
  • 循环条件表达式:每轮迭代开始前被求值
  • 后置语句:每轮迭代后被执行

且循环条件不能有圆括号,循环体需要大括号,其语法如下:

sum :=0
for i:=0; i<100; i++ {
sum++
}

如果前置条件、后置条件和分号都去掉,这就退化为C语言中的while循环了,语法如下:

sum := 0;
for sum < 100 {
sum++
}

Go中的if语句同循环一样,不需要圆括号,具体执行语句需要大括号,其语法如下:

if i < 10 {
//todo something
}

if 的便捷语句

跟 for 一样, if 语句可以在条件之前执行一个简单语句。

由这个语句定义的变量的作用域仅在 if 范围之内。包括在 if 的便捷语句定义的变量同样可以在任何对应的 else 块中使用。

package是golang最基本的分发单位和工程管理中依赖关系的体现。每个golang源代码文件开头都拥有一个package声明,表示该golang代码所属的package。

要生成golang可执行程序,必须建立一个名为main的package,并且在该package中必须包含一个名为main()的函数。

在golang工程中,同一个路径下只能存在一个package,一个package可以拆成多个源文件组成

按照惯例,包名与导入路径的最后一个目录一致。

包的导入使用import关键字

import "fmt"
//或者使用打包的导入语句
import (
"fmt"
"math"
)

在 Go 中,首字母大写的名称是被导出的。

在导入包之后,你只能访问包所导出的名字,任何未导出的名字是不能被包外的代码访问的。

Foo 和 FOO 都是被导出的名称。名称 foo 是不会被导出的。

变量在定义时没有明确的初始化时会赋值为零值 。

  1. 数值类型为 0 ,
  2. 布尔类型为 false ,
  3. 字符串为 “” (空字符串)。

表达式 T(v) 将值 v 转换为类型 T 。

一些关于数值的转换例子:

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
//或者,更加简单的形式:
i := 42
f := float64(i)
u := uint(f)

与 C 不同的是 Go 的在不同类型之间的赋值时需要显式转换。

常量的定义与变量类似,只不过使用 const 关键字。

常量可以是字符、字符串、布尔或数字类型的值。

常量不能使用 := 语法定义。

switch

你可能已经知道 switch 语句会长什么样了。

除非以 fallthrough 语句结束,否则分支会自动终止。

switch 的条件从上到下的执行,当匹配成功的时候停止。

没有条件的 switch

没有条件的 switch 同 switch true 一样。

这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。

defer

defer 语句会延迟函数的执行直到上层函数返回。

延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。

defer栈

延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

Go 具有指针。 指针保存了变量的内存地址。

类型 *T 是指向类型 T 的值的指针。其零值是 nil 。& 符号会生成一个指向其作用对象的指针。

var p *int
i := 42
p = &i
  • 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i

这也就是通常所说的“间接引用”或“非直接引用”。

与 C 不同,Go 没有指针运算。

一个结构体( struct )就是一个字段的集合。

package main
import "fmt"
type Vertex struct {
X int
Y int
}

func main() {
fmt.Println(Vertex{1, 2})
}

结构体字段使用点号来访问。

结构体字段可以通过结构体指针来访问。

通过指针间接的访问是透明的。

结构体文法

结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

特殊的前缀 & 返回一个指向结构体的指针。

类型 [n]T 是一个有 n 个类型为 T 的值的数组。

下面表达式定义变量 a 是一个有十个整数的数组。

var a [10]int

数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。

slice

一个 slice 会指向一个序列的值,并且包含了长度信息。

[]T 是一个元素类型为 T 的 slice。

len(s) 返回 slice s 的长度。

slice 可以包含任意的类型,包括另一个 slice。

对 slice 切片

slice 可以重新切片,创建一个新的 slice 值指向相同的数组。

如表达式:s[lo:hi],表示从 lo 到 hi-1 的 slice 元素,含前端,不包含后端。因此 s[lo:lo]是空的,而s[lo:lo+1]有一个元素。

构造 slice

slice 由函数 make 创建。这会分配一个全是零值的数组并且返回一个 slice 指向这个数组:

a := make([]int, 5)  // len(a)=5

为了指定容量,可传递第三个参数到 make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4

slice 的零值是 nil

一个 nil 的 slice 的长度和容量是 0。

向 slice 添加元素

向 slice 的末尾添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append 。 内建函数的文档对 append 有详细介绍。

func append(s []T, vs ...T) []T

append 的第一个参数 s 是一个元素类型为 T 的 slice ,其余类型为 T 的值将会附加到该 slice 的末尾。

append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。

如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。

range

for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。

当使用 for 循环遍历一个 slice 时,每次迭代 range 将返回两个值。 第一个是当前下标(序号),第二个是该下标所对应元素的一个拷贝。

可以通过赋值给 _ 来忽略序号和值。

如果只需要索引值,去掉 “ , value ” 的部分即可。

package main

import "fmt"

func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}

map 映射键到值。

map 在使用之前必须用 make 来创建;

值为 nil 的 map 是空的,并且不能对其赋值。

map 的文法跟结构体文法相似,不过必须有键名。

若顶级类型只是一个类型名,你可以在文法的元素中省略它。

修改 map

在 map m 中插入或修改一个元素:

var m = make(map[string]string)
m["name"] = "Golang"

获得元素:

name := m["name"]

删除元素:

delete(m, "name")

通过双赋值检测某个键存在:

name, ok = m["name"]

如果 name 在 m 中, ok 为 true。否则, ok 为 false,并且 name 是 map 的元素类型的零值。

同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。

函数也是值。他们可以像其他值一样传递,比如,函数值可以作为函数的参数或者返回值。

函数的闭包

Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。

练习:斐波纳契闭包

现在来通过函数做些有趣的事情。

实现一个 fibonacci 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。

package main

import "fmt"

// fibonacci 函数会返回一个返回 int 的函数。
func fibonacci() func() int {
first := 0
second := 0
index :=0

return func() int{
sum := first + second
switch index {
case 0:
first = 0
second = 1
case 1:
first = 0
second = 1
default:
first = second
second = sum
}
index++
return sum
}
}

func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Printf("[%v]=>%v\n",i,f())
}
}

Go 没有类。然而,仍然可以在结构体类型上定义方法。

方法接收者 出现在 func 关键字和方法名之间的参数中。格式为:

func (变量 类型) funcName(param1 type1,param2 type2)(return_param, return_type) {
//xxx
}

你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。

但是,不能对来自其他包的类型或基础类型定义方法。

接收者为指针的方法

方法可以与命名类型或命名类型的指针关联。

package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.Xv.X + v.Yv.Y)
}

func main() {
v := &Vertex{3, 4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。

尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。

当 v 是 Vertex 的时候 Scale 方法没有任何作用。Scale 修改 v。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。

Abs 的工作方式是一样的。只不过,仅仅读取 v。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。

接口类型是由一组方法定义的集合。

接口类型的值可以存放实现这些方法的任何值

type Abser interface {
Abs() float64
}
type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

类型通过实现接口中的方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implement“。

隐式接口解藕了实现接口的包和定义接口的包:互不依赖。

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

Stringers

一个普遍存在的接口是 fmt 包中定义的 Stringer

type Stringer interface {
String() string
}

Stringer 是一个可以用字符串描述自己的类型。fmt包 (还有许多其他包)使用这个来进行输出。

Go 程序使用 error 值来表示错误状态。

与 fmt.Stringer 类似, error 类型是一个内建接口:

type error interface {
Error() string
}

(与 fmt.Stringer 类似,fmt 包在输出时也会试图匹配 error。)

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil, 来进行错误处理。

i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 的 error 表示错误。

package main

import (
"fmt"
"math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string{
if e < 0 {
return fmt.Sprintf("cannot Sqrt negative number:%v",float64(e))
}
return ""
}

func Sqrt(x float64) (float64, error) {
i := ErrNegativeSqrt(x)
if i < 0 {
return -1, i
}else{
return math.Sqrt(x),nil
}
return 0, nil
}

func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}

Readers

io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。

Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。

例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。

package main

import (
"fmt"
"io"
"strings"
)

func main() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK