Go语言学习(二)

2023-05-02 15:23:03 浏览数 (1)

根据大牛陈皓的GO语言简介(上)目录,基本上完成了目录中如下部分的学习:

Hello World

运行

自己的package

fmt输出格式

变量和常量

数组

数组的切片操作

分支循环语句

关于分号

map

指针

内存分配

函数

现在互联网的资源很多,所以对比学习很有必要,可以参考不同的教材Step by Step的学习,每天都有一点收获,而后才能真正的学有所用。

我参考李文周的Go语言教程多些!

变量、常量、指针

声明变量的一般形式是使用 var 关键字:

var name type

其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型。

需要注意的是,Go语言和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。这样做的好处就是可以避免像C语言中那样含糊不清的声明形式,例如:int* a, b; 。其中只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在 Go 中,则可以和轻松地将它们都声明为指针类型:

var a, b *int

Go语言的基本类型有:

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128

当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。

变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 和 startDate 。

什么是指针

指针的值是一个变量的地址. 一个指针指向的值所保存的位置.  

不是所有的值都有地址, 但是所有的变量都有.

使用指针, 可以在无须知道变量名字的情况下, 间接读取或者更改变量值.

指针的限制

1. 不同类型的指针不能互相转化, 例如*int, int32, 以及int64

2. 任何普通指针类型*T和uintptr之间不能互相转化

3. 指针变量不能进行运算, 比如C/C 里面的 , --运算

代码语言:javascript复制
// 指针声明
// var name   * type
// var 变量名 * 类型

var p * int // *int=<nil>


var i = 1
var p * int // 声明指针p
p = &i // 指针指向变量地址(即在指针中存放变量地址): *int=0xc042054080
*p = 10 // 间接变量,设置指针指向变量的值:
fmt.Printf("p=%v, *p=%v, i=%v", p, *p, i) // p=0xc042054080, *p=10, i=10


// 指针判断
var p * int // p=<nil>
if (p == nil) { // 是空指针
    fmt.Printf("p=%v", p) // p=<nil>
}
i := 10
p = &i
if (p != nil) { // 不是空指针
    fmt.Printf("p=%v", p) // p=0xc0420100d0
}

test1/main.go的代码:

代码语言:javascript复制
package main

import (
"calc" //自定义的模块
"container/list"
"fmt"
"runtime"
"time"
)

//声明全局变量,必须使用var,不能使用 := 类型推导
var globalValue int = 9
var globalArray2 [10]int
var globalArray [3]int = [3]int{1, 2, 3}

//类型推导
var name = "Q1mi"
var age = 18

func threadTest(name string) {
    t0 := time.Now()
    fmt.Println(name, " thread start at ", t0)
}

func timeDemo() {
    //短变量声明
    i := 0
    now := time.Now() //获取当前时间
    fmt.Printf("current time:%v, i:%dn", now, i)
    
    //枚举数组成员
    for i, v := range globalArray {
        fmt.Printf("%d:%dn", i, v)
    }
    
    for _, v := range globalArray {
        fmt.Printf("value:%dn", v)
    }
    
    globalArray2 = [10]int{1, 2, 3}
    for _, v := range globalArray {
        fmt.Printf("globalArray2 value:%dn", v)
    }
    
    testslice := globalArray2[:5]
    fmt.Printf("testslice len:")
    fmt.Println(len(testslice))
    for index, value := range testslice {
        fmt.Println(index, value)
    }
    
    year := now.Year()     //年
    month := now.Month()   //月
    day := now.Day()       //日
    hour := now.Hour()     //小时
    minute := now.Minute() //分钟
    second := now.Second() //秒
    fmt.Printf("%d-d-d d:d:dn", year, month, day, hour, minute, second)
}

func testTypeAlias() {
    type Gender int8
    
    const (
        MALE   Gender = 1
        FEMALE Gender = 2
    )
    gender := FEMALE
    switch gender {
        case FEMALE:
        fmt.Println("famale")
        fallthrough
        case MALE:
        fmt.Println("male")
        default:
        fmt.Println("unknown")
    
    }
}

func main() {
    runtime.GOMAXPROCS(4)
    
    timeDemo()
    
    //go goroutine
    go threadTest("goroutine test1 ")
    threadTest("test2")
    
    fmt.Println("helloWorld")
    
    var mapAssigned map[string]int = make(map[string]int, 1)
    // mapAssigned = map[string]int{"one": 1, "two": 2}
    mapAssigned["one"] = 3
    mapAssigned["test"] = 34
    
    fmt.Printf("Map literal at "one" is: %d, test:%dn", mapAssigned["one"], mapAssigned["test"])
    
    var listTemp list.List
    listTemp.PushBack("abc")
    listTemp.PushBack("efg")
    for i := listTemp.Front(); i != nil; i = i.Next() {
        fmt.Println(i.Value)
    }
    
    //常量声明
    const pi = 3.1415
    const e = 2.7182
    
    //iota是go语言的常量计数器,只能在常量的表达式中使用
    // const (
    //  a = iota
    //  b
    //  c
    // )
    const (
    a, b = iota   1, iota   2 //1,2
    c, d                      //2,3
    )
    fmt.Printf("a:%d, b:%d, c:%dn", a, b, c)
    
    //指针
    testa := new(int)
    fmt.Printf("testa:%Tn", testa)
    
    fmt.Println(testa)
    
    var testpoint *int = new(int)
    *testpoint = 10
    fmt.Println(*testpoint)
    
    //指针数组
    var testpointerArr [3]*int
    var temp1 []int = make([]int, 10)
    fmt.Printf("v:%p, 1:%d", temp1, temp1[0])
    for _, v := range temp1 {
        fmt.Println(v)
    }
    
    for i := 0; i < 3; i   {
        testpointerArr[i] = &temp1[i]
    }
    // fmt.Printf("testpointerArr:%p, 0:%d", testpointerArr, *testpointerArr[0])
    
    //测试switch和自定义类型
    testTypeAlias()
    
    //测试模块
    fmt.Printf("Calc.Add:%d", calc.Add(1, 2))
}

函数的特点类似JavaScript的解释型语言多些,可以返回多个值,支持闭包、匿名函数、函数延迟执行等特性:

代码语言:javascript复制
package main
import "fmt"
func main(){
    v, e := multi_ret("one")
    fmt.Println(v,e) //输出 1 true
    v, e = multi_ret("four")
    fmt.Println(v,e) //输出 0 false
    //通常的用法(注意分号后有e)
    if v, e = multi_ret("four"); e {
        // 正常返回
    }else{
        // 出错返回
    }
    
    sum(1, 2)
    sum(1, 2, 3)

    //传数组
    nums := []int{1, 2, 3, 4}
    sum(nums...)
}

func multi_ret(key string) (int, bool){
    m := map[string]int{"one": 1, "two": 2, "three": 3}
    var err bool
    var val int
    val, err = m[key]
    return val, err
}

func sum(nums ...int) {
    fmt.Print(nums, " ")  //输出如 [1, 2, 3] 之类的数组
    total := 0
    for _, num := range nums { //要的是值而不是下标
        total  = num
    }
    fmt.Println(total)
}

包、模块和引用

这块可能稍微有点复杂,类似与其他语言中引用自定义模块的头文件,相对来说java文件的包管理会简单些,像Object-C或者C/C 语言可能就复杂一点,不过都只需要指定好搜索路径,#include一个头文件那都是没有问题的;

Go由于有模块的概念,模块的搜索路径都指向了GOPATH的路径下,包括安装三方的模块,也是安装到了GOPATH路径下,所以模块的默认搜索路径也就是GOPATH所在的路径,如果是自定义的模块,则稍显复杂,路径和文件中的代码如下,看看也就懂了;

以test1的包路径为例说明:

└─ test1

├── calc

 │   ├── go.mod

 │   └── main.go

    ├── go.mod

    └── main.go

如下路径下的 go.mod 可以拷贝一个或者使用go mod init命令生成:

liyizhang@bogon test3 % go mod init test3/m

ll@bogon test1 % ls -l

total 16

drwxr-xr-x  4 ll  staff   128 12 17 11:15 calc

-rw-r--r--  1 ll  staff    95 12 17 11:09 go.mod

-rw-r--r--  1 ll  staff  2899 12 17 14:22 main.go

ll@bogon calc �t go.mod 

module test1

go 1.15

replace calc => ./calc

require calc v0.0.0-00010101000000-000000000000 

ll@bogon test1 % cd calc

ll@bogon calc % ls -l

total 16

-rw-r--r--  1 ll  staff   23 12 17 11:09 go.mod

-rw-r--r--  1 ll  staff  841 12 17 11:17 main.go 

ll@bogon calc %% cat go.mod

module calc

go 1.15

main.go的代码:

代码语言:javascript复制
package calc

import "fmt"

type person struct { // 首字母小写,外部包不可见,只能在当前包内使用
    name string
}

//Student 首字母大写外部包可见,可在其他包中使用
type Student struct {
    Name  string //可在包外访问的方法
    class string //仅限包内访问的字段
}

//Payer 首字母大写外部包可见,可在其他包中使用
type Payer interface {
init() //仅限包内访问的方法
Pay()  //可在包外访问的方法
}

//Mode 首字母大写外部包可见,可在其他包中使用
const Mode = 1

func init() {
    fmt.Printf("calc module initn")
}

//Add 首字母大写,外部包可见,可在其他包中使用
func Add(a int, b int) int {
    return a   b
}

//Mul 首字母大写,外部包可见,可在其他包中使用
func Mul(a int, b int) int {
    return a * b
}

陈皓的文章提到:你可以使用GOPATH环境变量,或是使用相对路径来import你自己的package。

Go的规约是这样的:

1)在import中,你可以使用相对路径,如 ./或 ../ 来引用你的package

//使用相对路径 import "./haoel"  //import当前目录里haoel子目录里的所有的go文件

2)如果没有使用相对路径,那么,go会去找$GOPATH/src/目录。

运行

如果使用Visual Studio的IDE,则可能忽略了下面的步骤,不过敲敲命令也能知道IDE点Run的时候到底做了些撒。

#解释执行(实际是编译成a.out再执行)

$go run hello.go

hello world

#编译执行

$go build hello.go

$ls

hello hello.go

$./hello

hello world

并发编程

看似简单,第一次这个例子也没跑成功,原因是我将 go func(10)放到了函数最后了,整个程序还没来得及运行go里面的goroutine,程序就结束了,所以通常需要在main方法的结尾之前进行一个等待或者延时处理;

其他

参考其他模块的时候,可能需要安装github的三方模块,比方安装gin,需要设置GOProxy才能正常安装:

安装gin

GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/gin-gonic/gin

安装govendor

GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u  github.com/kardianos/govendor

注意:无论任何时候,你都不应该将一个控制结构((if、for、switch或select)的左大括号放在下一行。如果这样做,将会在大括号的前方插入一个分号,这可能导致出现不想要的结果。

常见的错误与陷阱

来源:https://github.com/qq1060656096/go-tutorials/blob/master/basic/12/README.md

  1. 误用短声明导致变量被覆盖
代码语言:javascript复制
// go run examples/demo1/short_var.go
// if中语句中声明的name,age覆盖了if外声明的name,age变量
package main
import "fmt"
func main()  {
	var name , age = "张三", 18
	if name, age := "李四", 10; age > 10 {
		fmt.Println("if name=%s, age=%d", name, age)
	} else {
		fmt.Println("else name=%s, age=%d", name, age)// else name=%s, age=%d 李四 10
	}
	fmt.Println("name=%s, age=%d", name, age)// name=%s, age=%d 张三 18
}

2. 误用字符串

当对一个字符串进行频繁的操作时,请记住在go语言中字符串是不可变得。 使用 拼接字符串会导致拼接后的新字符串和之前字符串不同,导致需要分配新的存储空间存放新字符串 从而导致大量的内存分配和拷贝。频繁操作字符串建议用bytes.Buffer

代码语言:javascript复制
// go run examples/demo2/string.go
package main
import (
	"fmt"
)
func main()  {
	var s = "test"
	for i := 0; i < 10; i  {
		// 字符串不可变,由于s字符串和si字符串拼接后字符串不在相同,
		// 导致需要分配新的存储空间存放新字符串,从而导致大量的内存分配和拷贝。
		si := fmt.Sprintf(" %d", i)
		s = s   si
	}
	fmt.Println(s)
}

3. 误用defer

4. 误用map

5. 何时使用new()和make()函数

6. 误用new()函数

7. 误用指针

0 人点赞