51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Go语言学习(二)

根据大牛陈皓的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++里面的++, --运算

// 指针声明
// 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的代码:

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:%d\n", now, i)
    
    //枚举数组成员
    for i, v := range globalArray {
        fmt.Printf("%d:%d\n", i, v)
    }
    
    for , v := range globalArray {
fmt.Printf("value:%d\\n", v)
}
globalArray2 = \[10\]int{1, 2, 3}
for, v := range globalArray {
        fmt.Printf("globalArray2 value:%d\n", 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-%02d-%02d %02d:%02d:%02d\n", 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:%d\n", 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:%d\n", a, b, c)
    
    //指针
    testa := new(int)
    fmt.Printf("testa:%T\n", 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的解释型语言多些,可以返回多个值,支持闭包、匿名函数、函数延迟执行等特性:

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 %cat 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的代码:

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 init\n")
}
//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. 误用短声明导致变量被覆盖

// 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
}
  1. 误用字符串

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

// 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)
}
  1. 误用defer

  2. 误用map

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

  4. 误用new()函数

  5. 误用指针


呱牛笔记





赞(1)
未经允许不得转载:工具盒子 » Go语言学习(二)