1. 包
go是由包来组织的。
程序从main包的main函数开始。
使用引入的包时,以最后一个包名开始:
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))//Println以fmt开始,Intn以rand开始
}
2. 包可见性
在go中一个方法或变量的开头字母大写,则这个方法或变量是可以被其他包引入的。否则不可以。
3. 函数
func swap(x, y string) (string, string) {//go可以返回多个值,参数可以写成x,y string形式
return y, x
}
func split(sum int) (x, y int) { // 命名返回值
x = sum * 4 / 9
y = sum - x
return
}
命名返回值即相当于x和y在函数开始定义,然后直接return返回。--这种用法容易造成歧义,不建议使用
4. 基本类型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 表示一个 Unicode 码点
float32 float64 //浮点型
complex64 complex128 // 复数
int/uint/uintptr在32为系统占32位,在64位系统占64位。--Java的类型和系统无关,所以说Java是系统无关性的语言,而go不是。
当变量没有赋值时,则系统默认赋0值。数字型零值是0,布尔值零值是false,字符串零值是""
5. 类型转换
go里不允许隐式转换,必须显示转换
类型转换的语法--和Java不同
var a int = 123
float64 b = float64(a)
6. for
go里面唯一的循环就是for,go里没有while
for i := 0; i < 10; i++ { // 注意没有小括号,使用分号分隔
sum += i
}
sum := 1
for sum < 1000 { // 当第一和第三个子句没有时,可以省略两个分号
sum += sum
}
7. if
if x < 0 { // 也是没有小括号,大括号是必须的
return sqrt(-x) + "i"
}
if v := math.Pow(x, n); v < lim { // 可以在判断语句之前有一个小的语句,但注意此时的v的作用域只在if语句块内(包括下面的else)
return v
} else {
return v
}
8. switch
var linux = "linux"
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case linux:
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
使用方式和Java几乎一样,但有两点不同:
- 每个case后系统自动加了break
- case中的东西可以不是常量且不一定是整数。
switch i {
case 0:
case f():
}
如果i的值为0,则f就不会执行
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
switch可以没有条件,达到更好看的if-else if-else效果
9. defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
func main() {
fmt.Println("counting")
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
输出
counting done 2 1 0
10. 指针
var p *int
a := 123
p = &a
*p = 234
用法和C++中的几乎一样
11. 结构体
就是字段的集合
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4 // 使用.来访问
p := &v
p.X = 1e9 // 正常是(*p).X,但也可以直接使用指针p.X的方式。
fmt.Println(v.X)
}
初始化一个结构体的方式:
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit -- 这个还是比较新颖的
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
12. 数组
var a [10]int
p := [10]int{1,2,3,4,5,6,7,8,9,10} // 数组的初始化
13. 切片
其实就是数组的一个引用。
var a []int //定义一个切片 此时 a == nil
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] //从下标1开始到下标4,包括1不包含4,总共4-1=3个元素
s[1] = 100 // 此时会修改primes数组的元素
println(primes)
切片并不存储任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
q := []int{2, 3, 5, 7, 11, 13} //初始化一个切片
上述代码的含义就是创建一个长度6的数组,然后用一个切片p来指向它。
var a [10]int
以下切片是等价的:
a[0:10]
a[:10]
a[0:]
a[:]
即可以省略start和end,默认start=0,end=数组长度(最大下标+1)
切片的长度和容量
var a [10]int
var p []int = a[3:5]
len(p) // 长度 5-3=2
cap(p) //容量 3~10 10-3=7
长度就是切片包含的元素个数
容量就是切片对应的数组开始下标到数组结尾的元素个数
使用make来创建切片
a := make([]int, 5) // len(a)=5 此时创建一个长度为5的数组,数组的每个元素为零值,然后返回这个数组的引用(切片)
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 还可以指定长度和容量
切片可以是多维的
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
向切片追加元素,使用内置append函数
func append(s []T, vs ...T) []T // 内置append函数
s = append(s, 2, 3, 4) //注意如果底层数组足够存放,则此函数会修改底层数组的值
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
总结:创建一个切片的三种方法
var a []int = make([]int, 10) //创建即零值
var b []int = []int{1,2} //创建且赋初值
var c []int = p[1:3] //创建一个指向某个数组的引用
14. range
range和for在一起使用,可以遍历切片或映射的每个元素。
for i, v := range pow { //会返回下标和值
fmt.Printf("2**%d = %d\n", i, v)
}
for _, v := range pow { // 只关心value
fmt.Printf(" %d\n", v)
}
for i := range pow { //只关心下标
fmt.Printf("%d\n", i)
}
15. 映射
就是Java中的Map
var m map[string]string // 此时 m == nil
创建map的两种方法:
m = make(map[string]string) //使用make
m["abc"] = "123"
var m = map[string]string{ //创建并初始化
"abc" : "123",
"def" : "456"
}
增加和修改元素都很简单,这里说一下删除和判无
delete(m, key) // 删除m映射中key的元素
elem = m[key] // 如果不存在则返回零值
elem, ok := m[key] //存在ok等于true,否则ok等于false
16. 函数参数值
函数本身其实和变量是一样的,可以当做参数
hypot := func(x, y float64) float64 { //可以把函数使用一个变量表示
return math.Sqrt(x*x + y*y)
}
func compute(fn func(float64, float64) float64) float64 { //参数可以是一个函数
return fn(3, 4)
}
甚至可以将函数当做另一个函数的返回值
func adder() func(int) int {
return func(x int) int {
return x + 1
}
}
a := adder()
println(a(2))
17. 函数闭包
先看一下计算机科学上闭包的含义:
是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
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 < 3; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
0 0
1 -2
3 -6
就是说sum这个变量随着函数返回一起存在,pos和neg分别对应两个函数以及函数一起存在的sum。
所以每次调用pos/neg都会使用之前的sum值。
其实这点有点类似于C++中的static变量--具有记忆性
网友评论