Golang语法

栏目: Go · 发布时间: 6年前

内容简介: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 结尾
  • 测试函数名为 TestXxxTest_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的消息状态。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

UML风格

UML风格

布格勒 / 袁峰 / 2008-12 / 35.00元

《UML风格(第2版)(英汉对照)》给出了一系列有效提高团队生产效率的编程风格的原则,描述了创建简洁、易于理解的UML图的标准和指南,涉及类图、定时图、用例图、组合结构图、顺序图、交互概览图、活动图、对象图、状态图、包图、通信图、部署图和组件图等内容。著名UML专家Scott W.Ambler描述了创建UML图的标准和指南,以帮助建模人员创建简明而易于理解的UML 图形。 《UML风格(第2......一起来看看 《UML风格》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具