根据大牛陈皓的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 。
什么是指针
指针的值是一个变量的地址. 一个指针指向的值所保存的位置.
不是所有的值都有地址, 但是所有的变量都有.
使用指针, 可以在无须知道变量名字的情况下, 间接读取或者更改变量值.
指针的限制
-
不同类型的指针不能互相转化, 例如*int, int32, 以及int64
-
任何普通指针类型*T和uintptr之间不能互相转化
-
指针变量不能进行运算, 比如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
}
- 误用字符串
当对一个字符串进行频繁的操作时,请记住在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)
}
-
误用defer
-
误用map
-
何时使用new()和make()函数
-
误用new()函数
-
误用指针