根据大牛陈皓的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
误用短声明导致变量被覆盖
// 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
// 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. 误用指针
-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com