概述
类型检查:编译时
运行环境:编译成机器代码直接运行
编程范式:面向接口,函数式编程,并发编程
Go语言并发编程
采用CSP(Communication Sequential Process)模式
不需要锁,不需要callback
并发编程vs并行计算
变量定义
使用var定义变量
- var a,b,c, bool
- var s1,s2 string = “hello”, “world”
- 可放在函数内,或直接放在包内
- 使用var()集中定义变量
- 让编译器自动决定类型
var a, b, i, s1, s2 = true, false, 3, “hello”, “world”
使用:=定义变量
- a, b, i, s1, s2 := true, false, 3, “hello”, “world”
- 只能在函数内使用
内置变量类型:
- bool, string;
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr;
- byte, rune
- float32, float64, complex64, complex128.
其中(u)int的位数由操作系统决定;
uintptr是指针;
byte和rune是int的别名,byte是8位rune是32位;
complex64的实部和虚部都是float32,complex128的实部和虚部都是float64。
go没有隐式类型转换,都要做显示的强制转换。
常量
定义:
const filename = “abc.txt”
const数值可作为各种类型使用(无需强制类型转换)
const a,b = 3,4
var c int = int(math.Sqrt(a * a + b * b))
枚举
const组用作枚举
iota关键字用作初值,后续枚举值自动递增,使用_可以占一个数,如下例中,a b c d值分别为0 2 3 4
const(
a = iota
_
b
c
d
也可以使用公式,如下:
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
要点
- 变量类型写在变量名之后
- 编译器可推测变量类型
- 没有char,只有rune
- 原生支持复数类型
条件语句
- if条件里可以赋
- if条件里赋值的变量作用域为if这个语句块
- switch语句可以没有表达式,在case中给出条件就行
- switch语句不需要break,默认自动break,不break的时候使用fallthrough;
- panic相当于报错,进入条件后调用panic,程序中断运行
func grade(score int) string {
g := ""
switch {
case score < 0 || score >100:
panic(fmt.Sprintf("wrong score: %d", score))
case sore < 60:
g = "F"
case sore < 70:
g = "C"
case sore < 90:
g = "B"
case sore < 100:
g = "A"
}
return g
}
循环语句
for的条件里不需要括号
for的条件里可以省略初始条件,结束条件,递增表达式
go语言没有while
函数
函数返回值类型放在后面
函数可以作为参数传递
没有默认参数,可选参数,有变长参数
函数可以返回多个值
可以为返回的多个值起名字,但最好用于简单函数;起不起名字对调用者来说没区别
func div(a, b int) (int, int) {
return a / b, a % b
}
func div2(a, b int) (q, r int) {
return a / b, a % b
}
func div3(a, b int) (q, r int) {
q = a / b
r = a % b
return
}
一般来说,函数的第二个返回值用来返回error,以便调用者处理
func eval2(a, b int, op string) (int, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
return a / b, nil
default:
return 0, fmt.Errorf("Invalid operation: %s", op)
}
}
函数可以作为函数的入参
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling Function %s with args(%d, %d)\n", opName, a, b)
return op(a, b)
}
只有值传递一种方式,即将参数进行拷贝传入函数
指针不能运算
数组
定义:
var arr1 [5]int
var arr2 := [3]int{3, 4, 5}
var arr3 := [...]int{1, 2, 3, 4, 5, 6} // 缺省数组长度,由编译器确定
var grid [4][[5]int
遍历:
len、range、省略变量可以用_代替
numbers := [6]int {1, 2, 3, 4, 5, 6}
for i := 0; i < len(numbers); i++ {
fmt.Println(numbers[i])
}
maxi := -1
maxValue := -1
for i,v := range numbers {
if v > maxValue {
maxi, maxValue = i, v
}
}
fmt.Println(maxi, maxValue)
sum := 0
for _,v := range numbers {
sum += v
}
fmt.Println(sum)
for i := range numbers {
fmt.Println(i)
}
- 数组是值类型,arr [5]int,arr [10]int是不同的类型
- 函数传入数组,是对数组做了拷贝 func f(arr [10]int)
- 可以使用指针,在函数内对入参数组做更改
func printArray(arr *[6]int) {
arr[1] = 100
for _, v := range arr {
fmt.Println(v)
}
}
func main() {
numbers := [6]int {1, 2, 3, 4, 5, 6}
printArray(&numbers)
fmt.Println(numbers)
}
- 一般不直接使用数组,使用slice
slice
slice本身没有数据,是对底层array的一个view
slice的内部:
ptr,指向slice开头的元素,
len,说明slice的长度,方括号取值时只能取到length里面的值,下标超出就报错,
cap(capacity:容量),从ptr开始到结束的整个array的长度;
扩展时,只要不超过capacity就可以扩展,只能向后扩展;
cap(s):slice的容量, len(s):slice的长度
向slice添加元素:
添加元素时如果超过cap,系统会重新分配更大的底层数组
由于是值传递,必须有参数接收append的返回值
s = append(s, val)
slice操作
var s []int /* 此处定义slice的初值为nil */
for i := 0; i < 10; i++ {
s = append(s, i*2+1)
}
s1 := []int{2, 4, 6, 8}
s2 := make([]int, 16)
s3 := make([]int, 10, 32) /* slice长度、容量 */
fmt.Println("Copying slice")
copy(s2, s1)
printSlice(s2)
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Poping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println("Poping from back")
back := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, back)
printSlice(s2)
Map
Map的操作
- 创建:make(map[string]int) 复合map:map[string]map[sring]int
- 获取元素:m[key] key不存在时,获得Value类型的初始值
- 用value, ok = m[key]来判断是否存在key
- 用delete删除一个key
- 使用range遍历key,或者遍历key,value对 (不保证遍历顺序,如需顺序,需手动对key排序)
- 使用map获取长度
map的底层实现是hash表
m := map[string]string{
"name": "xyz",
"sex": "famale",
"high": "167cm",
"weight": "53kg",
}
mm := map[string]map[string]string {
"xx": {"name": "xyz",},
}
m2 := make(map[string]int) /* m2 == empty map */
var m3 map[string]int
fmt.Println("Traversing Map")
for k, v := range m {
fmt.Println(k, v)
}
if namey, flagy := m["namey"]; flagy {
fmt.Println("namey value", namey)
} else {
fmt.Println("key does not exist")
}
fmt.Println("Deleting Element")
delete(m, "name")
namez, flagz := m["name"]
fmt.Println(namez, flagz)
map的key
- map使用哈希表,必须可以比较相等
- 除了slice,map,function的内建类型都可以作为key
- Struct类型不包含上述字段,也可以作为key
字符和字符串
- rune类型是go的字符类型,其实就是int32的别名
- 使用range遍历pos,rune对,
(将带中文字符的string强转为[]rune,可以获得连续索引的中文字符; - 这是又开辟了一块空间,存放解码后的字符;)
- 使用utf8.RuneCountInString获得字符数量
- 使用len获得字节长度
- 使用[]byte获得字节
package main
import (
"unicode/utf8"
"fmt"
)
func main() {
s := "kitty咘咘和臭臭" // UTF-8
fmt.Println(s)
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
for i, ch := range s {
fmt.Printf("(%d %c)", i, ch)
}
fmt.Println()
fmt.Println("Rune count in string:",
utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
}
打印结果
kitty咘咘和臭臭
6B 69 74 74 79 E5 92 98 E5 92 98 E5 92 8C E8 87 AD E8 87 AD
(0 k)(1 i)(2 t)(3 t)(4 y)(5 咘)(8 咘)(11 和)(14 臭)(17 臭)
Rune count in string: 10
k i t t y 咘 咘 和 臭 臭
(0 k)(1 i)(2 t)(3 t)(4 y)(5 咘)(6 咘)(7 和)(8 臭)(9 臭)
其他字符串操作
- Fields, Split, Join
- Contains, Index
- ToLower, ToUpper
- Trim, TrimRight, TrimLeft
面向对象
仅支持封装,不支持继承和多态
没有class,仅有struct
不论是地址还是结构本身,一律使用.来访问成员
没有构造函数,使用自定义工厂函数,函数返回的是局部变量地址
go语言的编译系统会视具体情况将内存分配在栈上或者堆上,当堆上的变量不再使用时,便会启用垃圾回收,回收这块内存,程序员不需要关心。
方法
type treeNode struct {
value int
left, right *treeNode
}
/* 显示定义和命名结构体方法 */
func (node treeNode) print() {
fmt.Print(node.value, " ")
}
/* 只有使用指针才可以改变结构内容 */
func (node *treeNode) setValue(value int) {
node.value = value
}
/* 中序遍历 */
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
func createTreeNode(value int) *treeNode {
return &treeNode{value:value}
}
func main() {
var root treeNode
root = treeNode{value:1}
root.left = &treeNode{}
root.right = &treeNode{11, nil, nil}
root.left.left = new(treeNode)
root.right.right = createTreeNode(111)
nodes := []treeNode{
{value:1},
{},
{11, nil, &root},
}
fmt.Println(root)
fmt.Println(nodes)
root.print()
root.right.right.print()
root.left.left.setValue(100)
root.left.left.print()
/* nil也可以访问指针接收者 */
var pRoot *treeNode
pRoot.setValue(100)
pRoot = &root
pRoot.setValue(1111)
pRoot.print()
}
{1 0xc042060400 0xc042060420}
[{1 <nil> <nil>} {0 <nil> <nil>} {11 <nil> 0xc0420603e0}]
值接收者 vs 指针接受者
- 要改变内容必须使用指针接收者
- 结构过大也考虑使用指针接受者
- 一致性:如有指针接收者,最好都是指针接受者
值接收者是go语言特有
值/指针接受者均可接收值/指针
封装
- 名字一般使用CamelCase
- 首字母大写:public
- 首字母小写:private
包
- 每个目录一个包
- main包包含可执行入口
- 如果一个目录下有main函数,那这个目录下只能有一个main包
- 为结构定义的方法必须放在同一个包内
- 方法可以放在不同的文件中(同一个包名)
扩展已有类型
- 定义别名
- 使用组合
type myTreeNode struct {
node *tree.Node
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left}
right := myTreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
myNode.node.Print()
}
func main() {
var root tree.Node
root = tree.Node{Value:1}
root.Left = &tree.Node{}
root.Left.Left = new(tree.Node)
fmt.Print("\nMy own post-order traversal: ")
myRoot := myTreeNode{&root}
myRoot.postOrder()
fmt.Println()
}
package queue
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return 0 == len(*q)
}
gopath以及目录结构
go get获取第三方库
使用gopm来获取无法下载的包
go get -v github.com/gpmgo/gopm
gopm get -g -v golang.org/x/tools/cmd/goimports
go bulid golang.org/x/tools/cmd/goimports
go install golang.org/x/tools/cmd/goimports
Gopath下包含src pkg bin;文件在src中,pkg是编译衍生物,编译生成的可执行文件在bin中
接口
go语言的duck typing同时完成多种功能
同时具有python,c++的duck typing的灵活性
又具有java的类型检查
接口的定义
接口由使用者定义
接口的实现是隐式的
只要struct实现接口声明的方法,就可以传递给接口参数使用
接口变量
- 接口变量:实现者的类型 + 实现者的值/指针
- 一般不会定义接口类型的指针变量
- 指针接收者实现只能以指针方式使用;值接收者都可以
查看接口变量
- 表示任何类型:interface{}
package queue
type Queue []interface{}
func (q *Queue) Push(v interface{}) {
*q = append(*q, v)
//*q = append(*q, v.(int))
}
//func (q *Queue) Pop() int {
func (q *Queue) Pop() interface{} {
head := (*q)[0]
*q = (*q)[1:]
return head
//return head.(int)
}
func (q *Queue) IsEmpty() interface{} {
return 0 == len(*q)
}
package main
import (
"fmt"
"mypractice.com/practice/queue"
)
func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
q.Push("abc")
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
}
- Type Assertion
- Type Switch
.(type)只能配合switch case使用,用来判断变量类型;还可以使用.(bufio.Reader)
使用者有最大的自由去定义组合接口,而不用管实现者到底是在一个还是多个接口中分别实现的功能
type Retriever struct {
Contents string
}
func (r *Retriever) String() string {
return fmt.Sprintf(
"Retriever: {Contents=%s}", r.Contents)
}
func (r *Retriever) Get(url string) string {
return r.Contents
}
func (r *Retriever) Post(url string,
form map[string]string) string {
r.Contents = form["contents"]
return "OK"
}
type Retriever interface {
Get(url string) string
}
type Poster interface {
Post(url string,
form map[string]string) string
}
func download(r Retriever) string {
return r.Get("https://www.csdn.net")
}
func post(poster Poster) {
poster.Post("https://www.csdn.net",
map[string]string {
"name": "qifangyuan",
"blog": "Blog",
})
}
//组合接口
type RetrieverPoster interface {
Retriever
Poster
}
func session(s RetrieverPoster) string{
s.Post("fake https://www.csdn.net",
map[string]string {
"contents": "another test interface",
})
/*s.Post("https://www.csdn.net",
map[string]string {
"name": "qqq",
"blog": "Blog",
})*/
return s.Get("https://www.csdn.net")
}
func inspect(r Retriever) {
fmt.Println("Inspecting", r)
fmt.Printf(" > Type:%T Value:%v\n", r, r)
fmt.Print(" > Type switch: ")
// .(type)只能配合switch case使用,用来判断变量类型
// 还可以使用.(bufio.Reader)
switch v := r.(type) {
case *mock.Retriever:
fmt.Println("Contents:", v.Contents)
case *real.Retriever:
fmt.Println("UserAgent:", v.UserAgent)
}
fmt.Println()
}
func main() {
var r Retriever
mockRetriever := mock.Retriever{
Contents:"test interface"}
r = &mockRetriever
inspect(r)
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
inspect(r)
realRetriever := r.(*real.Retriever)
fmt.Println(realRetriever.UserAgent)
if mockRetriever, ok := r.(*mock.Retriever); ok {
fmt.Println(mockRetriever.Contents)
}else {
fmt.Println("not a mock retriever")
}
fmt.Println(
"Try a session with mockRetriever")
fmt.Println(session(&mockRetriever))
}
常用的系统接口:
Stringer
Reader/Writer
函数式编程
参数、变量、返回值都可以是函数
高阶函数
函数->闭包
应用举例:
斐波那契额
函数接口
二叉树遍历
go语言闭包的应用:
更为自然,不需要修饰如何访问自由变量
没有lambda表达式,但是有匿名函数
资源管理与出错处理
defer调用
- 确保调用在函数结束时发生
- 参数在defer语句时计算
- defer列表为先进后出
使用时机: - Open/Close
- Lock/Unlock
- PrintHeader/PrintFooter
错误处理
主要是防止程序出错中断,一般是直接返回
- 可以处理预期内的错误
- 也可以实现error接口
func writeFile(filename string) {
file, err := os.OpenFile(filename,
os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Printf("%s, %s, %s\n",
pathError.Op,
pathError.Path,
pathError.Err)
}
return
}
}
使用函数式编程,将错误返回给外层错误处理函数统一处理
综合示例:
- defer + panic + recover
- Type Assertion
- 函数式编程的应用
package filelisting
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func HandleFilelist(writer http.ResponseWriter,
request *http.Request) error {
path := request.URL.Path[len("/list/"):]
fmt.Println(path)
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
/****************************************************************/
package main
import (
"log"
"net/http"
"os"
"mypractice.com/practice/errhandling/filelistingserver/filelisting"
)
type apphandler func(writer http.ResponseWriter,
request *http.Request) error
func errWrapper(
handler apphandler) func(
http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter,
request *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()
err := handler(writer, request)
if err != nil {
log.Printf("Error occurred "+
"handling request: %s",
err.Error())
if userErr, ok := err.(userError); ok {
http.Error(writer,
userErr.Message(),
http.StatusBadRequest)
return
}
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer,
http.StatusText(code), code)
}
}
}
func main() {
http.HandleFunc("/list/",
errWrapper(filelisting.HandleFilelist))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
panic
- 停止当前函数执行
- 一直向上返回,执行每一层的defer
- 如果没有遇见recover,程序退出
recover
- 仅在defer调用中使用
- 获取panic的值
- 如果无法处理,可重新panic
测试
表格驱动测试
- 分离的测试数据和测试逻辑
- 明确的出错信息
- 可以部分失败
- go语言的语法更易实践表格驱动测试
测试用例文件命名:*_test
可以进入文件所在目录,命令行使用命令:go test . 运行测试文件
testing.T
package main
import "testing"
func TestSubstr(t *testing.T) {
tests := []struct {
s string
ans int
}{
// Normal cases
{"abcabcbb", 3},
{"pwwkew", 3},
// Edge cases
{"", 0},
{"b", 1},
{"bbbbbbbbb", 1},
{"abcabcabcd", 4},
// Chinese support
{"哇哈哈哈哇", 6},
{"一二三二一", 3},
{"黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花", 8},
}
for _, tt := range tests {
actual := lengthOfNonRepeatingSubStr(tt.s)
if actual != tt.ans {
t.Errorf("got %d for input %s; "+
"expected %d",
actual, tt.s, tt.ans)
}
}
}
代码覆盖率和性能测试
覆盖率
IDEA里按钮
命令行:go test -coverprofile=c.out
go tool cover 查询用法
go tool cover -html=c.out
性能测试 testing.B
func BenchmarkSubstr(b *testing.B) {
s := "黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花"
for i := 0; i < 13; i++ {
s = s + s
}
b.Logf("len(s) = %d", len(s))
ans := 8
/*除去测试数据准备时间,只计算实际代码运行时间*/
b.ResetTimer()
for i := 0; i < b.N; i++ {
actual := lengthOfNonRepeatingSubStr(s)
if actual != ans {
b.Errorf("got %d for input %s; "+
"expected %d",
actual, s, ans)
}
}
}
IDEA里运行BenchmarkSubstr
或者命令行里用:go test -bench .
使用pprof进行性能调优
go test -bench . -cpuprofile cpu.out
go tool pprof cpu.out
交互式命令行:
(pprof)web (需要先安装Graphviz)
(pprof)quit
生成文档和示例代码
- 用注释写文档
- 在测试中加入Example
- 使用go doc/godoc来查看/生成文档
godoc -http :6060 在网页里看注释生成的文档
go语言的爬虫库/框架
- henrylee2cn/pholcus
- gocrawl
- colly
- hu17889/go_spider
有疑问加站长微信联系(非本文作者)