内容简介:Golang要求强制类型转换,无隐式转换使用const关键字定义常量,const数值可作为各种类型使用。switch后可以没有表达式,case结束后自动break,通过fallthrough不使用自动break。
变量定义
变量定义语法
- 使用var关键字,可放在函数内,也可放在包内
// var + 变量名 + 数据类型(有默认值) var a int var b string = "string" // 通过赋值自动判断类型 var c = true // 集中定义 var ( x = 1 y = 2 )
- 使用:=定义变量,只能在函数内使用
// := 用于赋初值 a, b := 1, 2
内建变量类型(builtin)
- bool, string
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
- byte, rune
- float32, float64, complex64, complex128
强制类型转换
Golang要求强制类型转换,无隐式转换
a, b := 3, 4 var c int c = int(math.Sqrt(float64(a * a + b * b)))
常量
使用const关键字定义常量,const数值可作为各种类型使用。
// 不确定类型 const a = 3 var b float64 b = a // 合并定义 const ( c = 1 d = 2 )
枚举
const ( spring = 0 summer = 1 autumn = 2 winter = 3 ) // iota用在取值为0的const变量,后续变量依次加1 const ( spring = iota summer autumn winter )
变量定义要点
- 变量类型写在变量名之后
- 编译器可以推测变量类型
- Golang没有char,使用rune
- 原生支持复数类型
分支
if
const filename = "abc.txt" contents, err := ioutil.ReadFile(filename) if err != nil { fmt.Println(err) } else { fmt.Println(contents) } // 可以在if条件中赋值,赋值变量的作用域在if语句中 if contents, err := ioutil.ReadFile(filename); err != nil { fmt.Println(err) } else { fmt.Println(contents) }
switch
switch后可以没有表达式,case结束后自动break,通过fallthrough不使用自动break。
func grade(score int) { grade := "" switch { case score < 0: panic(fmt.Sprintf("Wrong score: %d\n", score)) case score < 60: grade = "C" case score < 80: grade = "B" case score < 90: grade = "A" case score < 100: // 自动break,除非出现fallthrough fallthrough case score >= 100: grade = "S" } fmt.Println(grade) }
循环
for条件没有括号,没有while。
func convertToBin(n int) string { result := "" for ; n > 0; n /= 2 { lsb := n % 2 result = strconv.Itoa(lsb) + result } return result } 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()) } } func forever() { // while true for { fmt.Println("forever") } }
函数
- 函数声明的顺序为:关键字,函数名,参数,返回类型,函数体。
- 函数允许有多个返回值,并能在函数体内拿到返回值。
- 函数可以作为另一个函数的参数
- 函数支持可变参数列表
func div(a, b int) (q, r int) { // q = a / b // r = a % b // return return a / b, a % b } // 使用_抛弃返回值 a, _ := div(12, 7) // 使用另一个函数作为参数 func apply(operation func(a, b int), a, b int) { operation(a, b) } // 使用匿名函数 apply(func(a, b int) { fmt.Println(a, b) }, 1, 2) // 可变参数列表 func sum(numbers ...int) int { s := 0 for i := range numbers { s += numbers[i] } return s }
指针
Golang只有值传递一种方式。
// 使用指针操作 func change(pa *int) { *pa++ } a := 5 change(&a) func swap(x, y *int) { *x, *y = *y, *x }
数组
数组长度写在类型之前,[10]int和[20]int是不同的类型。数组是值复制。
var arr1 [5]int arr2 := [3]int{1, 3, 5} arr3 := [...]int{2, 4, 6} var arr4 [4][5]int
遍历数组
for i := 0; i < len(arr3); i++ { fmt.Println(arr3[i]) } for i := range arr3 { fmt.Println(arr3[i]) } // 按下标和值遍历 for i, v := range arr3 { fmt.Println(i, v) }
切片
数组切片相当于数据的一个视图,可以通过切片改变数组的值。
func updateSlice(s []int) { s[0] = 100 } func main() { array := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s1 := array[2:6] s2 := array[2:] s3 := array[:6] s4 := array[:] fmt.Println(s1, s2, s3, s4) // [2 3 4 5] [2 3 4 5 6 7] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7] updateSlice(s2) fmt.Println(s2) // [100 3 4 5 6 7] updateSlice(s3) fmt.Println(s3) // [100 1 100 3 4 5] // 切片扩展 s5 := s3[6:8] fmt.Println(s5) // 添加元素 s6 := append(s5, 8) fmt.Println(s6, array) fmt.Println(array[:8]) // 通过make创建切片 // 长度为10的切片 x := make([]int, 10) // 长度为10,容量为16的切片 y := make([]int, 10, 16) // 拷贝数组元素 copy(x, y) // 截取数组元素 z := append(x[:2], x[3:]...) }
切片底层维护了一个数组,ptr指向切片的首个元素,len表示切片的长度,cap表示底层数组的从ptr到最末的元素数量。切片可以向后扩展,不可向前扩展。切片添加元素如果会超过底层数组的cap,Golang会分配更大的底层数组(原容量*2进行扩展),由于值传递的关系,必须接收append方法的返回值。
map
通过map[K]v的形式定义map。除了slice、map、function的内建类型和struct类型都可以作为map的key。
m := map[string]string{ "name": "wch", "age": "23", } m2 := make(map[string]int) // 无序遍历map for k, v := range m { fmt.Println(k, v) } // map读值 name := m["name"] // 判断key是否存在 grade, exist := m["grade"] // 删除 delete(m, "name")
rune
Golang的rune相当于 java 的char,中文字符在Golang中占3个字节,使用 utf8.RuneCountInString
获取字符数,用 len
获取字节长度,使用 []byte
获取字节。
s := "学习Golang!" fmt.Println(s) // ch是rune类型,中文字符占3个字节 for i, ch := range s { fmt.Printf("(%d %X) ", 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("size= %d, rune=%c\n", size, ch) } for i, ch := range []rune(s) { fmt.Printf("(%d %c)", i, ch) } fmt.Println()
面向对象
Golang仅支持封装,不支持继承和多态,没有class,只有struct,通过struct来定义对象。
type treeNode struct { value int left, right *treeNode } func createNode(value int) *treeNode { return &treeNode{value: value} } func main() { // 通过多种方式声明对象 var root treeNode root = treeNode{value: 0} root.left = &treeNode{} root.right = &treeNode{1, nil, nil} root.right.left = new(treeNode) root.right.right = createNode(3) }
Golang支持显式定义方法接收者,值/指针接收者均可接收值/指针。当需要改变内容或结构过大时需要使用指针接收者,值接收者是Golang特有的
// 指定方法接收者 func (node treeNode) print() { fmt.Println(node.value) } func (node *treeNode) setValue(value int) { node.value = value } func main() { node := treeNode{} node.setValue(100) node.print() pNode := &node pNode.setValue(101) pNode.print() }
封装
Golang的可见性(针对包)使用首字母来控制,首字母大写表示public,首字母小写表示private。每个目录都是一个包,main包包含可执行入口。为结构定义的方法必须放在同一个包内,可以是不同的文件。Golang提供了两种扩展系统类型和已封装类型的方法:
- 定义别名:通过type为已有类型定义一个别名,针对新的结构体进行扩展。
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 len(*q) == 0 }
- 使用组合:定义新的结构体,其中一个成员是需要扩展的类型的指针。
type EnhanceNode struct { node *Node } func (enhanceNode *EnhanceNode) PostOrder() { if nil == enhanceNode || nil == enhanceNode.node { return } left := EnhanceNode{enhanceNode.node.Left} right := EnhanceNode{enhanceNode.node.Right} left.PostOrder() right.PostOrder() enhanceNode.PostOrder() }
GOROOT、GOPATH、go get
GOROOT一般为下载的sdk路径,GOPATH为用户可定义的路径,用户源码和通过go get命令下载的第三方库在GOPATH的src目录下。
gopm是go get的镜像工具,通过 go get -g -v github.com/gpmgo/gopm
下载gopm。
// 下载 gopm get -g -v Golang.org/x/tools/cmd/goimports // 更新 gopm get -g -v -u Golang.org/x/tools/cmd/goimports // 安装到GOPATH的bin目录 go install Golang.org/x/tools/cmd/goimports
接口
接口的实现是隐式的,只要实现接口的方法。
duck typing
“像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子”,意为描述事物的外部行为而非内部结构。严格说 go 属于结构化类型系统,类似duck typing。
接口变量里有什么
- 接口自带指针
- 接口变量同样采用值传递,几乎不需要使用接口的指针
- 指针接收者只能以指针的方式使用,值接收者都可以
- interface{}可以代表任何类型
- 定义接口
type Retriever interface { Get(url string) string } func download(r Retriever) string { return r.Get("http://www.baidu.com") }
- 实现接口
type Retriever struct { UserAgent string Timeout time.Duration } func (r *Retriever) Get(url string) string { resp, err := http.Get(url) if nil != err { panic(err) } result, err := httputil.DumpResponse(resp, true) resp.Body.Close() if nil != err { panic(err) } return string(result) }
- 调用接口
var r Retriever r = &real.Retriever{ UserAgent: "Mozilla/5.0", Timeout: time.Minute, } fmt.Println(download(r)) // Type Assertion if retriever, ok := r.(*real.Retriever); ok { fmt.Printf("%T %v\n", retriever, retriever) }
- 接口组合
type HttpExecute interface { Retriever Poster }
函数式编程
- 函数是一等公民:参数,变量,返回值都可以是函数。
- 高阶函数:函数的参数可以是函数
- 函数->闭包
func adder() func(int) int { sum := 0 return func(i int) int { sum += i return sum } } func main() { // f中不仅是一个函数,还有对变量sum的引用 f := adder() for i := 0; i < 10; i++ { fmt.Println(f(i)) } }
错误处理和资源管理
defer
用于指定在函数结束之前执行,可以用于关闭文件、释放锁等。
func tryDefer() { // 在函数借结束之前执行 defer fmt.Println("execute last...") defer fmt.Println("execute before last...") fmt.Println("execute") }
panic
- 停止当前函数执行
- 一直向上返回,执行每一层的defer
- 如果没有遇见recover,程序退出
recover
- 仅在defer调用中使用
- 获取panic的值
- 如果无法处理,重新panic
func tryRecover() { defer func() { r := recover() if err, ok := r.(error); ok { fmt.Println("error:", err) } else { panic(fmt.Sprintf("unknown error: %v", r)) } }() panic(errors.New("this is an error")) }
测试
测试格式
- 测试文件与待测试代码放在同一目录下
-
测试文件名以
_test.go
结尾 -
测试函数名为
TestXxx
或Test_xxx
的格式,使用其它类型测试,如性能测试则函数名为BenchmarkXxx
或 ```Benchmark_xxx`` 的格式
传统测试
- 传统测试数据与逻辑混在一起
- 传统测试的出错信息不明确
- 传统测试一旦出错测试即全部结束
表格驱动测试
- 测试代码
func calcTriangle(a, b int) int { return int(math.Sqrt(float64(a*a + b*b))) }
-
表格驱动测试
分离了测试数据和测试逻辑,明确了出错信息,可以部分失败。
func TestCalcTriangle(t *testing.T) { tests := []struct{ a, b, c int }{ {3, 4, 5}, {5, 12, 13}, {8, 15, 18}, } for _, tt := range tests { if actual := calcTriangle(tt.a, tt.b); actual != tt.c { t.Errorf("calcTriangle(%d %d); got %d; expected %d", tt.a, tt.b, actual, tt.c) } } }
性能测试
执行b.N次,由Golang决定具体次数,测试完成后在控制台打印执行次数和平均执行时间。
func BenchmarkTriangle(b *testing.B) { x, y, z := 8, 15, 17 for i := 0; i < b.N; i++ { actual := calcTriangle(x, y) if actual != z { b.Errorf("calcTriangle(%d %d); got %d; expected %d", x, y, actual, z) } } }
测试命令行工具
# 测试命令 go test # 代码覆盖率测试 go test -cover # 生成代码测试覆盖率文件 go test -coverprofile c.out # 代码覆盖率测试html go tool cover -html=c.out # 性能测试 go test -bench . # 生成性能报告 go test -bench . -cpuprofile cpu.out
文档
Golang提供 go doc
命令查看由注释组成的文档,通过 godoc -http :6060
命令在本地6060端口生成html文档。在test文件中建立名为 ExampleXxx_xxx
的函数,通过特定格式生成代码实例文档。
func ExampleQueue_Pop() { queue := Queue{} queue.Push(1) fmt.Println(queue.Pop()) // Output: // 1 }
go routine
协程Coroutine
- 轻量级“线程”
- 非抢占式多任务处理,由协程主动交出控制权
- 编译器/解释器/虚拟机层面的多任务
- 多个协程可能在一个或多个线程上运行
goroutine定义
go runtime.Gosched() go run -race xxx.go
channel
相对于通过共享内存来通信,channel通过通信来共享内存。
range
使用select进行调度
select可以用来监听IO操作,可以同时监听多个channel的消息状态。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Swift语法快速入门(一)之 Swift基础语法
- 在ES6中使用扩展语法有什么好处?它与rest语法有什么不同?
- Python 基础语法
- go语法
- JPQL 语言语法
- reStructuredText简明语法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。