Go快速入门
整理一些Go最基本的语法,旨在快速入门。
最简单的hello world
package main import "fmt" func main() {
fmt.Println("Hello, World!")
}
几点说明:
1、package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
2、import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。在导入包之后,你只能访问包所导出的名字,任何未导出的名字是不能被包外的代码访问的。在 Go 中,首字母大写的名称是被导出的。
package main import (
"fmt"
"math"
) func main() {
fmt.Println(math.pi)
}
比如这个例子,应该是Pi而不是pi,因为pi是不导出的,所以会报如下错误:
tmp/sandbox507287813/main.go:9:14: cannot refer to unexported name math.pi
tmp/sandbox507287813/main.go:9:14: undefined: math.pi
3、当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
4、在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。
关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
int
,uint
和 uintptr
类型在32位的系统上一般是32位,而在64位系统上是64位。当你需要使用一个整数类型时,你应该首选 int
,仅当有特别的理由才使用定长整数类型或者无符号整数类型。
变量声明
1、指定变量类型,声明后若不赋值,使用默认值。
var v_name v_type
v_name = value
2、根据值自行判定变量类型。
var v_name = value
3、省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。
v_name := value
4、多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3 //这种并行赋值也被用于当一个函数返回多个返回值时 var vname1, vname2, vname3 = v1, v2, v3 //和python很像,不需要显示声明类型,自动推断 vname1, vname2, vname3 := v1, v2, v3 //出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误 // 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
5、如果你声明了一个局部变量却没有在相同的代码块中使用它,会得到编译错误;但是全局变量是允许声明但不使用。
(1)看一个综合的例子
package main var x, y int
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
) var c, d int = 1, 2
var e, f = 123, "hello" //这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello" func main(){
var aa int = 10
var bb = 10
cc := 10
g, h := 123, "hello"
var ptr * int = &aa
println(x, y, a, b, c, d, e, f, g, h, aa, bb, cc, ptr)
}
运行结果:
0 0 0 false 1 2 123 hello 123 hello 10 10 10 0x7f2746856f08
(2)再看一个例子:
package main import (
"fmt"
"math/cmplx"
) var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
) func main() {
const f = "%T(%v)\n"
fmt.Printf(f, ToBe, ToBe)
fmt.Printf(f, MaxInt, MaxInt)
fmt.Printf(f, z, z)
}
运行结果:
bool(false)
uint64(18446744073709551615)
complex128((2+3i))
常量
1、常量的定义格式:
const identifier [type] = value
2、多个相同类型的声明可以简写为:
const c_name1, c_name2 = value1, value2
3、常量不能使用 :=
语法定义。
4、常量还可以用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
5、常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过。下面来看一个例子:
package main import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
) func main(){
const aa, bb, cc = 1, "hello", 8.8 println(a, b, c, aa, bb, cc)
}
运行结果:
abc 3 16 1 hello +8.800000e+000
关于上面的Sizeof,字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。
特殊常量iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。iota 可以被用作枚举值。
package main import "fmt" func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
运行结果:
0 1 2 ha ha 100 100 7 8
再看一个例子:
package main import "fmt"
const (
i=1<<iota
j=3<<iota
k
l
) func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}
运行结果:
i= 1
j= 6
k= 12
l= 24
iota表示从0开始自动加1,所以i=1<<0,j=3<<1(<<表示左移的意思),即:i=1,j=6,这没问题,关键在k和l,从输出结果看,k=3<<2,l=3<<3。
条件语句
1、if... else ...和C语言类似,Go 的 if
语句也不要求用 ( )
将条件括起来,同时, { }
还是必须有的。另外还需要特别注意else不能新起一行写,否则会报错:syntax error: unexpected semicolon or newline before else
2、if的便捷语句
if
语句可以在条件之前执行一个简单语句。由这个语句定义的变量的作用域仅在 if
范围之内,如果有else的话,else也是具有相同作用域的,可以使用到 if 中的变量
package main import (
"fmt"
"math"
) func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
} func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
运行结果:
9 20
3、switch语句和C语言类似,除非以 fallthrough
语句结束,否则条件从上到下的执行,当匹配成功的时候停止。switch还可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。
package main import "fmt" func main() {
/* 定义局部变量 */
var grade string = "B"
var marks int = 90 switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
} switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" );
}
fmt.Printf("你的等级是 %s\n", grade );
}
运行结果:
优秀!
你的等级是 A
4、type-switch用来判断某个 interface 变量中实际存储的变量类型。
基本语法如下:
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
例子:
package main import "fmt" func main() {
var x interface{} = "go" switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
}
运行结果:
x 是 bool 或 string 型
5、select 语句,后面再补充
defer
defer 语句会延迟函数的执行直到上层函数返回。延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。
package main import "fmt" func main() {
fmt.Println("counting") for i := 0; i < 3; i++ {
defer fmt.Println(i)
} fmt.Println("done")
}
运行结果:
counting
done
2
1
0
循环语句
1、Go 只有一种循环结构——for循环。for语句和C语言中类似;不像 C,Java,或者 Javascript 等其他语言,for
语句的三个组成部分 并不需要用括号括起来,但循环体必须用 { }
括起来。
for init; condition; post { }
另外,for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
例子:
package main import "fmt" func main() { var b int = 3
var a int numbers := [6]int{1, 2, 3, 5} /* for 循环 */
for a := 0; a < 2; a++ {
fmt.Printf("a 的值为: %d\n", a)
} for a < b {
a++
fmt.Printf("a 的值为: %d\n", a)
} for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}
运行结果:
a 的值为: 0
a 的值为: 1
a 的值为: 1
a 的值为: 2
a 的值为: 3
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0
2、循环初始化语句和后置语句都是可选的。基于此可以省略分号:C 的 while
在 Go 中叫做 for
。
package main import "fmt" func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
运行结果:
1024
3、用for true来表示无限循环
for true {
fmt.Printf("这是无限循环。\n");
}
也可以直接省略循环条件来构成无限循环
函数
func function_name( [parameter list] ) [return_types] {
函数体
}
1、函数定义解析:
- func:函数由 func 开始声明
- function_name:函数名称,函数名和参数列表一起构成了函数签名。
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 函数体:函数定义的代码集合。
例子:
package main import "fmt" func main() {
var a int = 1
var b int = 2
fmt.Println(sum(a, b)) var aa string = "hello"
var bb string = "hi"
aa, bb = swap(aa, bb) fmt.Println(aa, bb) var aaa = 200
change(&aaa)
fmt.Println(aaa)
} func sum(a int, b int) int {
return a + b
} func swap(aa, bb string) (string, string) {
return bb, aa
} func change(aaa *int) {
* aaa += 100
}
运行结果:
3
hi hello
300
2、函数的命令返回值
Go 的返回值可以被命名,并且就像在函数体开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的 return
语句返回各个返回变量的当前值。这种用法被称作“裸”返回。直接返回语句仅应当用在短函数中。在长的函数中它们会影响代码的可读性。
package main import "fmt" func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
} func main() {
fmt.Println(split(17))
}
运行结果:
7 10
3、函数值
函数也是值。他们可以像其他值一样传递,比如,函数值可以作为函数的参数或者返回值。
package main import (
"fmt"
"math"
) func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
} func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12)) fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
运行结果:
13
5
81
4、闭包
(1)Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量,代码如下:
package main import "fmt" func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
} func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence() /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber()) /* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
运行结果:
1
2
3
1
2
(2)Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder
返回一个闭包。每个返回的闭包都被绑定到其各自的 sum
变量上。
package main import "fmt" func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
} func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
运行结果:
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
方法
1、Go 没有类。然而,仍然可以在结构体类型上定义方法。方法接收者 出现在 func
关键字和方法名之间的参数中。一个方法就是一个包含了接收者的函数,接收者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
例子1:
package main import (
"fmt"
) /* 定义函数 */
type Circle struct {
radius float64
} func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("Area of Circle(c1) = ", c1.getArea())
} //该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
运行结果:
Area of Circle(c1) = 314
例子2:
package main import (
"fmt"
"math"
) type Vertex struct {
X, Y float64
} func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
} func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
运行结果:
5
2、可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。但是,不能对来自其他包的类型或基础类型定义方法。
package main import (
"fmt"
"math"
) type MyFloat float64 func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
} func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
运行结果:
1.4142135623730951
3、接收者为指针和非指针对比
有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
指针的例子:
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.X*v.X + v.Y*v.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())
}
运行结果:
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25
Scale
方法使用 Vertex
代替 *Vertex
作为接收者。当 v
是 Vertex
的时候 Scale
方法没有任何作用。Scale
修改 v
。当 v
是一个值(非指针),方法看到的是 Vertex
的副本,并且无法修改原始值。
非指针的例子:
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.X*v.X + v.Y*v.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())
}
运行结果:
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:3 Y:4}, Abs: 5
数组
1、声明方式
指定元素个数
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
不指定元素个数
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
2、遍历
package main import "fmt" func main() {
var array = [3]int{11, 22, 33} for y := range array {
fmt.Printf("第 %d 位的值 = %d\n", y, array[y])
} for j, x := range array {
fmt.Printf("第 %d 位的值 = %d\n", j, x)
} var i int;
for i = 0; i < 3; i++ {
fmt.Printf("第 %d 位的值 = %d\n", i, array[i])
}
}
运行结果:
第 0 位的值 = 11
第 1 位的值 = 22
第 2 位的值 = 33
第 0 位的值 = 11
第 1 位的值 = 22
第 2 位的值 = 33
第 0 位的值 = 11
第 1 位的值 = 22
第 2 位的值 = 33
结构体
1、结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体有中一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}
例子:
package main import "fmt" type Books struct {
title string
author string
subject string
book_id int
} func main() {
Book1 := Books{"book1", "a", "aa", 111} var Book2 Books
Book2.title = "book2"
Book2.author = "b"
Book2.subject = "bb"
Book2.book_id = 222 printBook(&Book1)
printBook(&Book2)
}
func printBook( book *Books ) {
fmt.Printf( "Book title : %s\n", book.title);
fmt.Printf( "Book author : %s\n", book.author);
fmt.Printf( "Book subject : %s\n", book.subject);
fmt.Printf( "Book book_id : %d\n", book.book_id);
}
运行结果:
Book title : book1
Book author : a
Book subject : aa
Book book_id : 111
Book title : book2
Book author : b
Book subject : bb
Book book_id : 222
2、结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。使用 Name:
语法可以仅列出部分字段。(字段名的顺序无关。)特殊的前缀 &
返回一个指向结构体的指针。
例子:
package main import "fmt" type Vertex struct {
X, Y int
} var (
v1 = Vertex{1, 2} // 类型为 Vertex
v2 = Vertex{X: 1} // Y:0 被省略
v3 = Vertex{} // X:0 和 Y:0
p = &Vertex{1, 2} // 类型为 *Vertex
) func main() {
fmt.Println(v1, p, v2, v3)
}
运行结果:
{1 2} &{1 2} {1 0} {0 0}
切片
可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。或使用make()函数来创建切片:
var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len)
也可以指定容量,其中capacity为可选参数。
make([]T, length, capacity)
例子:
package main import "fmt" func main() {
var numbers []int
printSlice(numbers) numbers = append(numbers, 1)
printSlice(numbers) numbers = append(numbers, 2, 3, 4, 5, 6, 7)
printSlice(numbers) /* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4]) /* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3]) /* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:]) number1 := make([]int, 0, 5)
printSlice(number1) /* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2) var number3 []int
copy(number3, numbers[1:4])
printSlice(number3) number4 := make([]int, len(numbers[1:4]), len(numbers[1:4]))
copy(number4, numbers[1:4])
printSlice(number4)
} func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
运行结果:
len=0 cap=0 slice=[]
len=1 cap=1 slice=[1]
len=7 cap=7 slice=[1 2 3 4 5 6 7]
numbers[1:4] == [2 3 4]
numbers[:3] == [1 2 3]
numbers[4:] == [5 6 7]
len=0 cap=5 slice=[]
len=2 cap=7 slice=[1 2]
len=0 cap=0 slice=[]
len=3 cap=3 slice=[2 3 4]
在使用copy的时候,需要注意目标切片的len必须要足够容纳下源切片,而不仅仅是cap,否则是无法完成复制的。
map
map 是无序的,我们无法决定它的返回顺序,这是因为 map 是使用 hash 表来实现的。
可以使用内建函数 make 也可以使用 map 关键字来定义 map:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type /* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
package main import "fmt" func main() {
countryCapitalMap := map[string]string{"china" : "Bei Jing"}
for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
} countryCapitalMap = make(map[string]string) countryCapitalMap["France"] = "Paris"
countryCapitalMap["Italy"] = "Rome"
countryCapitalMap["Japan"] = "Tokyo"
countryCapitalMap["India"] = "New Delhi" for country := range countryCapitalMap {
fmt.Println("Capital of",country,"is",countryCapitalMap[country])
} captial1, ok1 := countryCapitalMap["France"]
if(ok1){
fmt.Println("Capital of France is", captial1)
}else {
fmt.Println("Capital of France is not present")
} delete(countryCapitalMap,"France");
captial2, ok2 := countryCapitalMap["France"]
if(ok2){
fmt.Println("Capital of France is", captial2)
}else {
fmt.Println("Capital of France is not present")
}
}
运行结果:
Capital of china is Bei Jing
Capital of France is Paris
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of India is New Delhi
Capital of France is Paris
Capital of France is not present
range
Go 语言中 range 关键字用于for循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
当使用 for
循环遍历一个 slice 时,每次迭代 range
将返回两个值。 第一个是当前下标(序号),第二个是该下标所对应元素的一个拷贝。
对于第一个值:在数组和切片中它返回元素的索引值,在集合中返回 key-value 对的 key 值;可以通过赋值给 _
来忽略序号和值。
package main
import "fmt"
func main() {
//这是我们使用range去求一个slice的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range也可以用在map的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "ab" {
fmt.Println(i, c)
}
}
运行结果:
sum: 9
index: 1
a -> apple
b -> banana
0 97
1 98
interface
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
} /* 定义结构体 */
type struct_name struct {
/* variables */
} /* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
来看一个例子:
package main import (
"fmt"
) type Phone interface {
call()
} type NokiaPhone struct {
} func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
} type IPhone struct {
} func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
} func main() {
var phone Phone phone = new(NokiaPhone)
phone.call() phone = new(IPhone)
phone.call() }
在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call()。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法,运行结果如下:
I am Nokia, I can call you!
I am iPhone, I can call you!
错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
可以在编码中通过实现 error 接口类型来生成错误信息。函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息。
例子:
package main import (
"fmt"
"math"
"errors"
) func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
} else {
return math.Sqrt(f), nil
}
} func main() { result, err:= Sqrt(-1) if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
} }
运行结果:
math: square root of negative number
再来看一个更复杂的例子:
package main import (
"fmt"
) // 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
} // 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
} // 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
} } func main() { // 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当被除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
} }
运行结果:
100/10 = 10
errorMsg is:
Cannot proceed, the divider is zero.
dividee: 100
divider: 0
只是非常非常浅显的入门而已。最后,语法没有高亮要我感到非常蛋疼……
Update:2017-12-03结合官方指南重新补充了一些内容
本文参考自:
http://www.runoob.com/go/go-tutorial.html
https://tour.go-zh.org/welcome/1
windows下的IDE可以参考:
http://www.runoob.com/go/go-ide.html
另外,推荐一个在线运行Go代码的网站:
http://www.dooccn.com/go/
Go快速入门的更多相关文章
- Web Api 入门实战 (快速入门+工具使用+不依赖IIS)
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...
- SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)
SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...
- 前端开发小白必学技能—非关系数据库又像关系数据库的MongoDB快速入门命令(2)
今天给大家道个歉,没有及时更新MongoDB快速入门的下篇,最近有点小忙,在此向博友们致歉.下面我将简单地说一下mongdb的一些基本命令以及我们日常开发过程中的一些问题.mongodb可以为我们提供 ...
- 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- Mybatis框架 的快速入门
MyBatis 简介 什么是 MyBatis? MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结果 ...
- grunt快速入门
快速入门 Grunt和 Grunt 插件是通过 npm 安装并管理的,npm是 Node.js 的包管理器. Grunt 0.4.x 必须配合Node.js >= 0.8.0版本使用.:奇数版本 ...
- 【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- 【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- Vue.js 快速入门
什么是Vue.js vue是法语中视图的意思,Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API.作者是尤雨溪,写下这篇文章时vue.js版本为1.0.7 准备 我推荐 ...
随机推荐
- 网络上的等待事件 —— SQL*Net message from client/dblink
SQL*Net message from client SQL> select event#,name,parameter1,parameter2,parameter3 from v$event ...
- iOS:CYLTabBarController的具体使用实例:实现新浪微博的主流框架
使用CocoaPods或者手动集成将CYLTabBarController这个第三方框架导入项目后,截图如下: 在AppDelegate.m类中实现的代码如下: // AppDelegate.m // ...
- iOS: performXXX的几种方法总结:
performXXX的用法: 视图切换: ※根据segue标识符切换视图 performSegueWithIdentifier:(NSString *) identifier sender:(id) ...
- 深入理解Java中为什么内部类可以访问外部类的成员
内部类简介 虽然Java是一门相对比较简单的编程语言,但是对于初学者, 还是有很多东西感觉云里雾里, 理解的不是很清晰.内部类就是一个经常让初学者感到迷惑的特性. 即使现在我自认为Java学的不错了, ...
- SQL盲注攻击的简单介绍
1 简介 1.1 普通SQL注入技术概述 目前没有对SQL注入技术的标准定义,微软中国技术中心从2个方面进行了描述[1]: (1) 脚本注入式的攻击 (2) 恶意用户输 ...
- WAF实现扫描器识别
目前安全测试的软件越来越多,也越来越强大,越来越多的人成为[黑客],今天在网上看到一个文章说拦截wvs的扫描,勾起了我写这篇文章的欲望. 因为公司的三大业务之一就有一个云waf,每天拦截的日志里面 ...
- libev客户端
#include <ev.h> #include <stdio.h> #include <netinet/in.h> #include <stdlib.h&g ...
- android sdk下载SDK Platform失败记录
在使用android sdk manager下载的时候会遇到 下载完毕后,你可能会出现如下图一样的错误,就算重复尝试多次依然无法正常安装 Downloading SDK Platform Androi ...
- "com.android.ide.s.ProcessException:Process 'cand 'C:\Program Files\Java\jdk1.8.0_60\bin\java.exe'' finished with non-zero exit value 2"
使用Android Studio 出现该问题: "com.android.ide.common.process.ProcessException: org.gradle.process.in ...
- iOS 使用腾讯地图显示用户位置注意事项
1. 向 target中info 加入 NSLocationWhenInUseUsageDescription,string 类型.值是描写叙述为什么须要用户位置,这句话会出如今 提示用户是否同意a ...