11

一文带你入门 Golang

 3 years ago
source link: https://studygolang.com/articles/31848
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

go 语言特点

脚本化的语法,容易上手。

静态类型+编译性,开发、运行效率都有保证

函数式 & 面向对象 两种编程范式,

原生支持并发编程支持,降低开发成本,维护成本,以及更好的兼容性,效率。

劣势:语法糖没有 Python 和 Ruby 多。运行效率不及C,但已赶超C++,Java。第三方库不多,就是轮子少(喜欢造轮子的可以加入golang轮子大军)。

安装

官方: https://golang.org/

国内官方站点: https://go-zh.org/

Linux

http://golang.org/dl/ 下载最新Go语言二进制包

wget https://dl.google.com/go/go1.13.15.linux-amd64.tar.gz

tar -C /usr/local -xzf go1.13.15.linux-amd64.tar.gz

export PATH=$PATH:/usr/local/go/bin

go version

Mac

环境变量配置

GOROOT, GOPATH, GOBIN, PATH, 现在安装的最新golang runtiem都不用配置了环境变量了。

目录结构

go命令

go run

go 代码关键字

break         //退出循环
default     //选择结构默认项(switch、select)
func         //定义函数
interface    //定义接口
select        //channel
case         //选择结构标签
chan         //定义channel
const         //常量
continue     //跳过本次循环
defer         //延迟执行内容(收尾工作)
go         //并发执行
map         //map类型
struct        //定义结构体
else         //选择结构
goto         //跳转语句
package     //包
switch        //选择结构
fallthrough     //??
if         //选择结构
range         //从slice、map等结构中取元素
type        //定义类型
for         //循环
import         //导入包
return         //返回
var        //定义变量

标示符

append bool byte cap close complex complex64 complex128 uint16 copy false float32 float64 imag int int8 int16 uint32 int32 int64 iota len make new nil panic uint64 print println real recover string true uint uint8 uintptr

语言特色

不要求缩进,不要求末尾加分号——;,同一行代码中有多个表达式,需要用 分号 分割。没有使用的变量,包,会导致报错。

每个go源文件开头必须是package开头,定义自己的包

一个目录下,只能有一个包名

一个可执行的文件必须要有 main() 函数

import 引入包

两种引入风格

import "package1"
import "package2"
import (
    "package1"
    pa2 "package2"      // 包别名,别名为 pa2
    . "fmt"
    _ "mysql"
)

. "fmt" 方式引入包的化,使用fmt里面的函数就可直接使用,不用带 fmt 前缀了

如果引入的包不使用,会报错, 或者加个前缀 _ 即可,这样的下划线会把引入的包的init函数执行一下。定义的变量不用,也会报错。

包内初始化函数

定义 包内 初始化函数

func init() {
    
}

只导入这个包部分,并运行init函数,由于导入不全,所以在代码中就不能使用这个包了。

import _ "MyPackage"

数据类型

序号 类型和描述 1 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 2 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 3 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 4 派生类型: 包括: (a) 指针类型(Pointer) (b) 数组类型 (c) 结构化类型(struct) (d) Channel 类型 (e) 函数类型 (f) 切片类型 (g) 接口类型(interface) (h) Map 类型

使用 int 时,根据当前操作系统来的,64位系统对应 int64, 32位操作系统,对应int32.

变量声明

  • 变量声明: var <变量名> [变量类型]
  • 变量赋值: <变量名> = <值,表达式,函数返回>
  • 变量声明赋值:var <变量名> [变量类型] = <值,表达式,函数返回>
  • 变量声明,类型推断,并赋值 <变量名> := <值,表达式,函数返回>
分组声明

var (
    i int
    foo float32
    name string
)

分组批量声明、赋值
var a,b,c,d int = 1,2,3,4
a,b := 1,2

特殊变量 _

变量作用域

var

作用域可以分为以下四个类型:

  • 内置作用域:不需要自己声明,所有的关键字和内置类型、函数都拥有全局作用域
  • 包级作用域:必須函数外声明,在该包内的所有文件都可以访问
  • 文件级作用域:不需要声明,导入即可。一个文件中通过import导入的包名,只在该文件内可用
  • 局部作用域:在自己的语句块内声明,包括函数,for、if 等语句块,或自定义的 {} 语句块形成的作用域,只在自己的局部作用域内可用

语句块

语句块是由花括弧({})所包含的一系列语句。

在 Go 中还有很多的隐式语句块:

  • 主语句块:包括所有源码,对应内置作用域
  • 包语句块:包括该包中所有的源码(一个包可能会包括一个目录下的多个文件),对应包级作用域
  • 文件语句块:包括该文件中的所有源码,对应文件级作用域
  • for 、if、switch等语句本身也在它自身的隐式语句块中,对应局部作用域

类型转换

  • 不存在隐式转换,必须是显示
  • 类型转换必须是在两种兼容的类型之间
  • <变量名称> [:]= <目标类型>( <需要转换的变量名> )

类型转换精度丢失

类型断言

断言,顾名思义就是果断的去猜测一个未知的事物。在 go 语言中,interface{} 就是这个神秘的未知类型,其断言操作就是用来判断 interface{} 的类型。

var foo interface{} = 22

    f, ok := foo.(int)
    if !ok {
        t.Log("Guess wrong ...")
    }
    t.Logf("The type is : %T", f)

常量

  • 显示 const idenfity [type] = value
  • 隐式 const identify = value () (无类型常量)

变量类型支持: bool, int, float, string

特殊常量 iota

运算

算术运算

运算符 描述 实例 + 相加 A + B 输出结果 30 - 相减 A - B 输出结果 -10 * 相乘 A * B 输出结果 200 / 相除 B / A 输出结果 2 % 求余 B % A 输出结果 0 ++ 自增 A++ 输出结果 11 -- 自减 A-- 输出结果 9

关系运算

运算符 描述 实例 == 检查两个值是否相等,如果相等返回 True 否则返回 False。 (A == B) 为 False != 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 (A != B) 为 True > 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 (A > B) 为 False < 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 (A < B) 为 True >= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 (A >= B) 为 False <= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 (A <= B) 为 True

逻辑运算

运算符 描述 实例 && 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 (A && B) 为 False \ \ 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 (A \ \ B) 为 True ! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !(A && B) 为 True

位运算

运算符 描述 实例 & 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 (A & B) 结果为 12, 二进制为 0000 1100 \ 按位或运算符"\ "是双目运算符。 其功能是参与运算的两数各对应的二进位相或 (A \ B) 结果为 61, 二进制为 0011 1101 ^ 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (A ^ B) 结果为 49, 二进制为 0011 0001 << 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 A << 2 结果为 240 ,二进制为 1111 0000 >> 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 A >> 2 结果为 15 ,二进制为 0000 1111

赋值运算

运算符 描述 实例 = 简单的赋值运算符,将一个表达式的值赋给一个左值 C = A + B 将 A + B 表达式结果赋值给 C += 相加后再赋值 C += A 等于 C = C + A -= 相减后再赋值 C -= A 等于 C = C - A *= 相乘后再赋值 C = A 等于 C = C A /= 相除后再赋值 C /= A 等于 C = C / A %= 求余后再赋值 C %= A 等于 C = C % A <<= 左移后赋值 C <<= 2 等于 C = C << 2 >>= 右移后赋值 C >>= 2 等于 C = C >> 2 &= 按位与后赋值 C &= 2 等于 C = C & 2 ^= 按位异或后赋值 C ^= 2 等于 C = C ^ 2 \ = 按位或后赋值 C \ = 2 等于 C = C \ 2

优先级

优先级 运算符 功能 9 () [] -> . 后缀运算 8 ! *(指针) & ++ -- +(正号) -(负号) 单目运算 7 * / % + - 算术运算,加减乘除 6 << >> 位运算 5 == != < <= > >= 逻辑运算、不等、等 4 & \ ^ 按位 逻辑与、或 3 \ \ && 逻辑或、与 2 = += -= *= 等等 赋值运算 1 , 逗号

一元赋值 这两大运算符是 从右到左 关联,其他都是 从左到右 关联。

注意:优先级 值越大则优先级越高。为了方便理解、记忆,我对没有严格按照优先级制表,只是做了个大概!!

更详细的

代码控制语句

if, else, else if

var number int = 37
    if number += 4; 10 > number {
        fmt.Print("less than 10:", number)
    } else if 10 < number {
        number -= 2
        fmt.Print("greater 10:", number)
    } else {
        
    }

switch, select

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    ia := []interface{}{byte(6), 'a', uint(10), int32(-4), "CC"}
    v := ia[rand.Intn(4)]
    
    // 值 switch
    switch v {
        case 'a' :
            fmt.Println("char: ", v)
        case 10 :
            fmt.Println("uint: ", v)
        case -4 :
            fmt.Println("int: ", v)
        case 0.1 :
            fallthrough
        caes "0.1"
            fmt.Println("float: ", v)
        default :
            fmt.Println("byte: ", v)
    }
    
    // 变量类型 switch
    switch interface{}(v).(type) {
    case string :
        fmt.Printf("Case A.")
    case byte :
        fmt.Printf("Case B.")
    case int :
        fmt.Printf("Case B.")
    default:
        fmt.Println("Unknown!")
    }
}

注意,go语言和其他语言不同的时,每个case代码末尾会自动加上 break 操作, 如果你需要使用 fallthrough 来抵消默认的 break

select 用于管道

for

是的 golangforforforeachfor inwhile 于一体。

do while 表示:golang你这么绕,不优雅

package main

import (
    "fmt"
    "time"
)

func main() {
    map1 := map[int]string{1: "Golang", 2: "Java", 3: "Python", 4: "C"}
    
    n := 1
    for {   // 省略则默认是true
        if n > 3 {
            break;
        }
        fmt.Println("for true map item: ", map1[n])
        time.Sleep(1)
        n++
    }
    
    for i := 1; i < 4; i++ {
        fmt.Println("for i map item: ", map1[i])
    }
    
    for k,v := range map1 {
        fmt.Print(k, ":", v)
    }
}

goto, break, continue

goto 是跳过代码块

package main

import (
    "fmt"
    "time"
)

func main() {
    code:
        fmt.Println("do some thing~")
        time.Sleep(1)
        
    goto code
}

break 跳出并结束循环

continue 跳过当前循环

虽然不能和 PHP 那样 break 2

跳出多层, 单只要有goto就能干很多事了。

golang给 循环 就分配了一个 for,语句跳转语句却整了那么多花样

复合数据

内建方法 make & new

内建方法就是不需要引入包就能用的

make 可以创建 slice、map、chan,返回指针类型

  • slice 是可变长的数组
  • map 是key-map 数据数组
  • chan 是go独有的 管道

一股c编程风格扑面而来, char ptr = (char )malloc(sizeof(char) * 5);

内建方法 new

内存置0,返回传入类型的指针地址

package main
import fmt
import reflect

func main() {
    mSlice := make([]string, 3)
    mSlice[0] = "dog"
    mSlice[1] = "cat"
    mSlice[2] = "pig"
    fmt.Println("animals: ", mSlice)
    
    mMap := make(map[int]string)
    mMap[10] = "dog"
    mMap['2'] = "cat"
    fmt.Println(reflect.TypeOf(mMap))
    fmt.Println("animals :: ", mMap)
    
        
    nMap := new(map[int]string)
    fmt.Println(reflect.TypeOf(nMap))
}

append copy delete

slice可以使用copy,append 函数

delete 是专门用来删除 map

  • append(src, ele) 追加元素
  • copy(dst, src) 把src元素赋值到dst上,
  • delete() 删除元素

例子:

package main
import "fmt"

func main() {
    mSlice := make([]string, 3)
    mSlice[0] = "dog"
    mSlice[1] = "cat"
    mSlice[2] = "pig"
    fmt.Println("animals: ", mSlice)

    // append(mSlice, "id-3")   // 这样写会导致报错: append(mSlice, "id-3") evaluated but not used
    mSlice = append(mSlice, "id-3")
    fmt.Println("animals update:", mSlice)
    fmt.Println("animals len :", len(mSlice))
    fmt.Println("animals cap:", cap(mSlice))
    
    // newSlice := make([]string)      // 这样写导致报错:missing len argument to make([]string)
    // newSlice := make([]string, 2)       // 这样写会导致数据丢失2个,不会自动扩容
    newSlice := make([]string, 3)       // 不要多次定义初始化:no new variables on left side of :=
    copy(mSlice, newSlice)          // 这样反向copy,会导致前面的几个数组元素被置为空
    // copy(newSlice, mSlice)
    fmt.Println("animals dst:", mSlice)
    fmt.Println("animals copy:", newSlice)
    
    delete(mMap, 50)
    fmt.Println(mMap)
}

panic & recover

异常处理

panic() 抛出异常

recover() 获取异常

报错会导致程序代码中断,不会再执行后续操作

例子:

package main

import "fmt"
import "errors"

func panicFunc() {
    defer func() {
        // recover()
        message := recover()    // 声明了message 变量就需要使用哦,不然报错
        fmt.Println("panice msg: ", message)
        
        switch message.(type) {
            case string:
            case error:
                fmt.Println("panice error msg: ", message)
            default:
        }
    }()
    // panic("报错啦")
    panic(errors.New("I am error."))
}

func main() {

    panicFunc()
}

len & cap & close

len可以计算 string, array, slice, map, chan

cap 可以计算 slice, map, chan

len()
cap()
close()

当声明一个数组时,go会预先分配一部分空间给当前数组,获取实际空间占用大小,使用 cap()

不用像PHP那样,strlen(), count(), length 傻傻分不清楚了。

例子:

package main

import "fmt"

func main() {

    mSlice := make([]string, 3)
    mSlice[0] = "dog"
    mSlice[1] = "cat"
    mSlice[2] = "pig"
    fmt.Println("animals: ", mSlice)

    fmt.Println("animals update:", mSlice)
    fmt.Println("animals len :", len(mSlice))
    fmt.Println("animals cap:", cap(mSlice))

    mChan := make(chan int, 1)
    close(mChan)
    mChan <- 1      // 会导致报错: panic: send on closed channel
}

defer

定一个当前方法关闭时,运行的代码, 压栈设计,先声明的后执行。

结构体

package main

import "fmt"

type Dog struct {
    ID int
    Name string
    Age int32
}

func main() {
    
    var dog Dog
    dog.ID = 1
    dog.Name = "haha"
    dog.Age = 3
    fmt.Println("print Dog Struct", dog)

    dog2 := Dog{ID:2, Name:"san", Age:4}
    fmt.Println("print Dog 2 Struct", dog2)
    
    dog3 := new(Dog)
    dog3.ID = 3
    dog3.Name = "Tom"
    dog3.Age = 5
    fmt.Println("print Dog 3 Struct", dog)
}

输出

print Dog Struct {1 haha 3}
print Dog 2 Struct {2 san 4}
print Dog 3 Struct &{3 Tom 5}

属性 & 函数

接口

/* define an interface */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   ...
   method_namen [return_type]
}

/* define a struct */
type struct_name struct {
   /* variables */
}

/* implement interface methods*/
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* method implementation */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* method implementation */
}

并发

指针

json

需要引入包 encoding/json , 两个函数分别是 json.Marshal() , json.Unmarshal() .

注意,最后一个是英文字母小写的 L ,不是 1

json 序列化

package main

import "fmt"
import "encoding/json"

type ServerInfo struct {
    SerName string
    SerIp   string
    SerPort uint16
}

func main() {
    server := new(ServerInfo)
    server.SerName = "http-nginx"
    server.SerIp = "127.0.0.1"
    server.SerPort = 8080
    re,err := json.Marshal(server)
    if nil != err {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("struct json bytes: ", re)
        fmt.Println("struct json string: ", string(re))
    }
    
    mServer := make(map[string]interface{})
    mServer["serverName"] = "apache2-http"
    mServer["serIp"] = "192.168.30.133"
    mServer["serPort"] = "3033"
    mRe,err := json.Marshal(mServer)
    if nil != err {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("map json string: ", string(mRe))
    }
}

输出

struct json bytes:  [123 34 83 101 114 78 97 109 101 34 58 34 104 116 116 112 45 110 103 105 110 120 34 44 34 83 101 114 73 112 34 58 34 49 48 46 49 48 48 46 49 55 46 50 55 58 51 48 48 48 49 34 44 34 83 101 114 80 111 114 116 34 58 56 48 56 48 125]
struct json string:  {"SerName":"http-nginx","SerIp":"10.100.17.27:30001","SerPort":8080}
map json string:  {"serIp":"192.168.30.133","serPort":"3033","serverName":"apache2-http"}

ps: 我也不知道 10.100.17.27:30001 是怎么回事

json 反序列化

可以使用 tag 来做 mapping,

package main

import "fmt"
import "encoding/json"

type ServerInfo struct {
    SerName string  `json:"name"`
    SerIp   string  `json:"ip"`
    SerPort uint16  `json:"port"`
}

func main() {
    // jsonStr := "{\"SerName\":\"http-nginx\",\"SerIp\":\"10.100.17.27:30001\",\"SerPort\":8080}"   \\ 双引号注意转义
    jsonStr := "{\"name\":\"http-nginx\",\"ip\":\"10.100.17.27:30001\",\"port\":8080}"
    
    sServer := new(ServerInfo)
    jsonBytes := []byte(jsonStr)
    uerr := json.Unmarshal(jsonBytes, &sServer)
    if nil != uerr {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("uns struct: ", sServer)
    }
    
    jsonStr3 := `{"serIp":"192.168.30.133","serPort":"3033","serverName":"apache2-http"}`   \\ 使用键盘1旁边的 ` 符号包裹双引号就不用转义了
    
    uSer := make(map[string]interface{})
    uErr := json.Unmarshal([]byte(jsonStr3), &uSer)
    if nil != uErr {
        fmt.Println("error: ", uErr.Error())
    } else {
        fmt.Println("unmar map: ", uSer)
    }
}

输出

uns struct:  &{http-nginx 10.100.17.27:30001 8080}
unmar map:  map[serIp:192.168.30.133 serPort:3033 serverName:apache2-http]

tag

tag 这个东东把,就是json的别名,感觉这个功能是go的特色,与 encoding/json 包紧密结合。

为什么会有这个东西,我估计是这个和 go命名规则 有关,go命名规则,要求public的变量开头要大写,小写开头的变量是private的,所以,json中的变量就会影响一个接口体变量的访问权限,为了不像java那样复杂,提供了方便的tag功能。

package main

import "fmt"
import "encoding/json"

type ServerInfo struct {
    SerName string  `json:"name"`
    SerIp   string  `json:"ip"`
    SerPort uint16  `json:"port"`
}

func main() {
    
    server := new(ServerInfo)
    server.SerName = "http-nginx"
    server.SerIp = "127.0.0.1"
    server.SerPort = 8080
    re,err := json.Marshal(server)
    if nil != err {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("struct json string: ", string(re))
    }
}

输出

struct json string:  {"name":"http-nginx","ip":"10.100.17.27:30001","port":8080}
map json strin

go 特色语法

_

  • _ 变量

这就好比是Linux 里的 /dev/null , 由于go语言要求声明的变量必须被使用,返回的变量必须被接收,那么真有个变量没用但必须要接受怎么办呢,就把返回的参数给他。例如:

package main
import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
    for _, v := range pow {
        fmt.Printf("value is %d\n", v)
    }
}

这里我们只要值,不要key的信息,返回的key不能不收不是,但我也不像把它输出出来,就让 _ 来接收好了。

  • _ 包

引入包, 并不直接使用这个包,运行时执行一次它的 init() 函数,

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jinzhu/gorm"
)

参考

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK