go是一门很特别的语言,有以下特征:
- 没有“对象”,没有继承多态,没有泛型,没有try/catch
- 有接口,函数式编程,CSP并发模型(goroutine + channel)
- 语法简单
我在网上看到有人说,go语言学习很简单啊,比js还简单。啊?这个比喻也是激励我来学习go的,我想看看多简单!!!
开发环境
废话不多说,赶紧把go开发的环境搭起来才可以愉快的撸代码啊。
1. 安装
gopath 和 goroot
2. 开发工具
开发工具我使用的是goland,开箱即用,舒服。
语法
定义变量
go使用var关键字来声明变量,这个和js一致,哈哈。
package main
import "fmt"
func variableInitialValue() {
// 变量名后面就是变量类型
var a int = 3
var s string = "abc"
fmt.Println(a, s)
}
variableInitialValue() // 3 abc
复制代码
另外,假如你只是定义变量但是没有赋值,go中是有初值的例如
int会是0,string会是空字符串。这一点和js就不一样了。
另外,go是可以自动推断变量类型的。
func variableTypeDeduction() {
var a, b, c = 3, "a", true
fmt.Println(a, b, c)
}
variableTypeDeduction() // 3 a true
复制代码
同时,go也为在函数内声明变量提供了简便方式, 这里重点看一下函数内这三个关键字。
func variableShortCut() {
a, b, c := 3, "a", true
fmt.Println(a, b, c)
}
variableShortCut() // 3 a true
复制代码
上面我们提到注意函数内这三个字,为什么呢?
package main
import "fmt"
var a = 1 //没有问题
b := 2 // 这句话是错的,在函数外不可以这样写
复制代码
我们注意到文件开头都有个
package main,也就是说,go中没有全局变量,只有包内的变量。
另外,同时声明多个变量我们可以这样写:
var (
a = 1
b = 2
c = 3
)
复制代码
内建变量类型
- bool, string
- (u)int,(u)int8,(u)int16,(u)int32,(u)int,uintptr
- byte, rune
- float32, float, complex, complex128
rune就是go中的char类型。byte是8位的,rune是32位的。complex是复数。
强制类型转换
下面我们看一下代码:
func triangle() {
var a, b int = 3, 4
var c int
c = int(math.Sqrt(float(a*a + b*b)))
fmt.Println(c)
}
复制代码
我想象中的写法就是math.Sqrt(a*a + b*b),但是会报错,必须要照上面的写法。这样在大型项目中,会帮助我们规避很多BUG。
常量定义
go中定义常量使用的是const,和ES6中的定义方法一样唉。使用方法和var一样,但不能缩写。
const a = "2"
复制代码
枚举类型
func enums() {
const (
cpp = iota
java
python
golang
js
)
// b, kb, mb, gb, tb, pb
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
fmt.Println(cpp, java, python, js)
fmt.Println(b, kb, mb, gb, tb, pb)
}
enums()
// 0 1 2 4
// 1 1024 1048576 1073741824 1099511627776 11259906842624
复制代码
iota是自增的意思,并且还可以参加运算。
if else
if和for候面的条件都没有括号
package main
import (
"fmt"
"io/ioutil"
)
func main() {
const filename = "a.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
// 以下代码都不到contents
// fmt.Printf("%s\n", contents)
}
复制代码
请注意一下if contents, err := ioutil.ReadFile(filename); err != nil {}这个写法,虽然从可读性上我不怎么认同,但是我看网上有人说是在结束ifelse之后,contents变量就会被自动回收,因为contents写在了if语句的作用域内。
switch
go中switch会自动break,除非使用fallthrough
func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf("Wrong score: %d", score))
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score <= 100:
g = "A"
}
return g
}
复制代码
for
写一个整数转为二进制树的函数
import (
"fmt"
"strconv"
)
func convertToBin(n int) string {
res := ""
for ; n > 0; n/=2 {
lsb := n % 2
res = strconv.Itoa(lsb) + res
}
return res
}
convertToBin(13) // 1101
复制代码
另外。go语言中没有while,直接使用for就可以,我们写一个一行一行读文件的函数。
import (
"fmt"
"os"
"bufio"
)
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
printFile("a.txt")
// lemon
// lemonfe
复制代码
函数定义
上面我们已经写了好多函数了,现在我们来看一个函数返回多个值的例子
func div(a, b int) (q, r int) {
return a + b, a - b
}
q, r := div(1, 2)
t, _ := div(3, 4)
fmt.Println(q, r, t) // 3 -1 7
复制代码
为什么使用了
_呢?因为go中不允许定义了变量而不使用。
多个返回值的使用场景一般都是返回
err。
func div(a, b int) (int, err){
...
}
复制代码
参数是一个函数
package main
import (
"fmt"
"reflect"
"runtime"
)
func add(a, b int) int {
return a + b
}
func apply(op func(int, int) int, a, b int) int {
// 拿到函数的名字
p := reflect.ValueOf(op).Pointer() //拿到函数的指针
opName := runtime.FuncForPC(p).Name()
fmt.Println(opName, a, b)
return op(a, b)
}
func main() {
fmt.Println(apply(add, 3, 4))
}
复制代码
运行结果:
main.add 3 4
7
复制代码
可变参数列表
func sum(nums ...int) int {
s := 0
for i := range nums {
s += nums[i]
}
return s
}
func main() {
fmt.Println(sum(1,2,5,5,7,4))
}
复制代码
运行结果: 24
指针
var a int = 3
var pa *int = &a
*pa = 4
复制代码
- &a 代表变量a的指针
var pa *int = &a声明一个指针,指向a*pa其实就是a的值
我们来实现一个交换两个变量值的函数:
func swap(a, b *int) {
*a, *b = *b, *a
}
func main() {
a, b := 3, 4
swap(&a, &b)
fmt.Println(a, b)
}
复制代码
运行结果:
4 3
复制代码