Golang简介

2022-12-28 12:21:07 浏览数 (1)

Golang简介

Why Golang?

我发现我花了四年时间锤炼自己用 C 语言构建系统的能力,试图找到一个规范,可以更好的编写软件。结果发现只是对 Go 的模仿。缺乏语言层面的支持,只能是一个拙劣的模仿。 --- 吴云洋(云风的 BLOG)

特点

  • 并行
  • 快速
  • UTF-8
  • 跨平台

配置运行环境

下载安装

官网下载地址 https://golang.org/dl/

下载文件并执行安装,Linux系统只需要解压即可!

Docker--Golang

tips 命令行godoc -http=:8081 可以查看离线文档

配置

指定GOPATH为将要工作的目录,然后将bin添加到PATH中,输入命令go env查看

代码语言:javascript复制
GOARCH="amd64"   ---架构
GOBIN=""         ---编译好的文件
GOEXE=""  
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"  --系统
GOPATH="/Users/gaozhimin/work"  --工作目录
GORACE=""
GOROOT="/usr/local/go"     --go的安装目录
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/qy/y1gwdk9x425f4nn60278ncp00000gn/T/go-build689949734=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang  "
CGO_ENABLED="1"

编辑器

推荐使用

  • vscode
  • sublime3
  • atom
  • liteide

基本语法

HELLO_WORLD

代码语言:javascript复制
// 一个文件夹内只能有一个包名
package main

import "fmt"

func main() {
    // P在⬇️此处大写,注意
    fmt.Println("Hello world!")
    // 普通字符使用双引号
}

Golang的双引号和反引号都可用于表示一个常量字符串,不同在于:

  • 双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)
  • 反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式

变量声明

类型在变量的后面

例子

代码语言:javascript复制
var a int
var b bool
// := 等价于 var 后赋值
c := 5

var (
    e int
    f bool
)
g := `Linkin
    Park`

const x = 42

const (
    y = 1
    b = ""
)

基本类型

  • int,Runes(注:Rune 是int 的别名)
  • int8 ,int16 ,int32 ,int64
  • byte ,uint8 ,uint16 ,uint32 ,uint64 (注:byte是uint8 的别名)
  • float32 ,float64 (没有float 类型)
  • bool
  • string
  • complex128,complex64

保留关键字

break case chan const continue default func defer go else goto fallthrough if for import interface map package range return select struct switch type var

对比PHP

控制结构

代码语言:javascript复制
// if
if err := Chmod(0664); err != nil {
    // 注意err的作用于
    fmt.Printf(err)
    return err
}

// for
sum := 0
for i := 0; i < 10; i   {
    sum  = i
}

//循环中的break和continue

for{
    // 如果不加break,就是一个完美的死循环
    break
}

for pos, char := range "abc" {
    fmt.Printf("character '%c' starts at byte position %dn", char, pos)
}

// switch

var i int
switch i {
case 0:
case 1:
    f()
default:
    g() //当i不等于0或1时调用
}

复合类型

array、slices 和 map

array

代码语言:javascript复制
// 定长
var arr [10]int
// 长度不同类型不同
var a [10]int
var b [11]int
a = b //报错

// 传值不传址

slice

代码语言:javascript复制
// 长度可以改变
sl := make([]int, 10)
// 传地址
s1 := []int{1, 2, 3}
s2 := s1
fmt.Println(s2)
s1[0] = 0
fmt.Println(s2)
// [1 2 3]
// [0 2 3]
s3 := append(s3, 4)
fmt.Println(s3)

map

≈ python.map

代码语言:javascript复制
newMap := map[int]string{
    0: "Sun", 1: "Mon",
    2: "Tue", 3: "Wed",
    4: "Thu", 5: "Fri",
    6: "Sat",
}
// 查找键值是否存在
if v, ok := newMap[7]; ok {
    fmt.Println(v)
} else {
    fmt.Println("Key Not Found")
}
// Key Not Found
delete(newMap, 5)
fmt.Println(newMap)

函数

作用域

代码语言:javascript复制
package main

var a = 6

func main() {
    p()
    q()
    p()
}
func p() {
    println(a)
}
func q() {
    a := 5
    // a = 5
    println(a)
}

返回格式

代码语言:javascript复制
// 函数参数 ip,类型为string
// 函数返回,conn和error
func connect(ip string) (conn *conn, err error){
    ...
    ...
    
    return
}

func connect(ip string) (*conn, error){
    conn := xxxx
    err := xxx
    
    return conn, err
}

// 接参数
con, err = connect("127.0.0.1")
if err != nil{
    panic("error")
}

// 忽略参数
_, err = connect("127.0.0.1")
if err != nil{
    panic("error")
}

Defer

代码语言:javascript复制
func downloadFile(filepath string, url string) (err error) {

    // Create the file
    out, err := os.Create(filepath)
    if err != nil {
        return err
    }
    defer out.Close()

    // Get the data
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // Writer the body to file
    _, err = io.Copy(out, resp.Body)
    if err != nil {
        return err
    }

    return
}

匿名函数

代码语言:javascript复制
func main() {
    a := func() {
        fmt.Println("你好")
    }
    a()
}

回调

代码语言:javascript复制
func main() {
    callback("callback", print)
}
func print(s string) {
    fmt.Println(s)
}

func callback(s string, f func(string)) {
    f(s)
}

恐慌和恢复

代码语言:javascript复制
func main() {
    if throwsPanic(p) {
        fmt.Println("Panic Happened")
    }
}

//可能出现恐慌
func p() {
    panic("failed")
}

func throwsPanic(f func()) (b bool) {
    // defer在发生恐慌后能够执行,使用recover能捕捉到panic
    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()
    f()
    return
}

包是函数和数据的集合。用 package 关键字定义一个包。每个文件都必须以package开头!

包名

简洁明了,无下划线,无大小写混合,分隔符表示目录层级关系。

import的起点为

代码语言:javascript复制
import (
    "io"
    "net/http"
    "os"
    
    "github.com/laogao/tools/lib"
)

net/http net/http 实现了 HTTP 请求、响应和 URL 的解析,并且提供了可扩展的 HTTP 服 务和基本的 HTTP 客户端。

flag flag 包实现了命令行解析。

os os 包提供了与平台无关的操作系统功能接口。其设计是 Unix 形式的。

io 这个包提供了原始的 I/O 操作界面。它主要的任务是对 os 包这样的原始的 I/O 进 行封装,增加一些其他相关,使其具有抽象功能用在公共的接口上。

fmt 包 fmt 实现了格式化的 I/O 函数,这与 C 的 printf 和 scanf 类似。格式化短语 派生于 C 。

首字母大小写

名称以大写字母起始的是可导出(exported)的。

代码语言:javascript复制
func Add(a, b int) int {
    return a   b
}

func hide(){
    xxxx
}

工程目录

代码语言:javascript复制
goWorkSpace     // goWorkSpace为GOPATH目录
  -- bin
     -- myApp1  // 编译生成
     -- myApp2  // 编译生成
     -- myApp3  // 编译生成
  -- pkg
  -- src
     -- common 1
     -- common 2
     -- common utils ...
     -- myApp1     // project1
        -- models
        -- controllers
        -- others
        -- main.go 
     -- myApp2     // project2
        -- models
        -- controllers
        -- others
        -- main.go 
     -- myApp3     // project3
        -- models
        -- controllers
        -- others
        -- main.go 

获取第三方Go包

以https://github.com/go-sql-driver/mysql为例

代码语言:javascript复制
go get github.com/go-sql-driver/mysql

指针和结构体

指针

代码语言:javascript复制
func main() {
    var i int
    i = 1
    var p *int
    p = &i

    fmt.Printf("i=%d;p=%d;*p=%dn", i, p, *p)

    *p = 2
    fmt.Printf("i=%d;p=%d;*p=%dn", i, p, *p)

    i = 3
    fmt.Printf("i=%d;p=%d;*p=%dn", i, p, *p)
}

结构体

代码语言:javascript复制
type foo int

func main() {
    var a foo
    a = 1
    fmt.Println(a)

    t1 := T{"t1"}

    fmt.Println("M1调用前:", t1.Name)
    t1.M1()
    fmt.Println("M1调用后:", t1.Name)

    fmt.Println("M2调用前:", t1.Name)
    t1.M2()
    fmt.Println("M2调用后:", t1.Name)
}

//想要使用结构体,可以在其类型上添加函数,也可以直接做参数传入函数中

type T struct {
    Name string
}

func (t T) M1() {
    t.Name = "name1"
}

func (t *T) M2() {
    t.Name = "name2"
}

传值与传指针

当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。

传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。

Go语言中string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)

要访问指针 p 指向的结构体中某个元素 x,不需要显式地使用 * 运算,可以直接 p.x

接口

代码语言:javascript复制
package main

import (
    "fmt"
)

type apple struct {
}

type pineapple struct {
}

func (a apple) eat() {
    fmt.Println("I'm an apple,I can be eaten")
}

func (p *pineapple) eat() {
    fmt.Println("I'm a pineapple,I can be eaten")
}

type eatable interface {
    eat()
}

func main() {
    apple := apple{}
    pineapple := new(pineapple)
    eat(apple)
    eat(pineapple)
}

func eat(sth eatable) {
    sth.eat()
    switch sth.(type) {
    case *pineapple:
        fmt.Println("Got a pineapple")
    case apple:
        fmt.Println("Got a apple")
    }
}

GC

设想你正在编写一个长期运行的后台服务,就让它是一个 web 应用服务或者某些更复杂的东西。通常来说,在整个运行周期都会需要分配内存。了解如何处理这些内存是必要的。

通常,每 2 分钟会执行一次垃圾收集。如果某个片段持续 5 分钟都没有被使用,回收器会将其释放。

因此,如果你认为内存使用会降低,那么 7 分钟之后再去确认吧。

更多关于GC优化,可以浏览Go’s march to low-latency GC


并发

goroutine

goroutine不是线程、协程、进程等等,传递了不准确的含义。goroutine 有简单的模型:它是与其他 goroutine 并行执行的, 有着相同地址空间的函数。它是轻量的,仅比分配栈空间多一点点。 而初始时栈是很小的,所以它们也是廉价的,并且随着需要在堆空间上分 配(和释放)。

使用go关键字让一个函数以goroutine方式执行

代码语言:javascript复制
func echo(s string) {
    fmt.Println(s)
}

func main() {
    echo("hi")
    go echo("hi")
    
    // time.Sleep(5 * time.Second)
}

//猜测一下回打印出什么?为什么?

channel

如果不考虑go出去的函数,那么程序就会执行完毕并退出,goroutine也会随之停止,需要引入一个通讯机制,他就是channel。

channel ≈ pipe

channel必须使用make关键字创建

代码语言:javascript复制
c1 := make(chan int)

// channel可以有长度
c2 := make(chan string, 100)

//channel的使用方法

c1 <- 1 //把int 1 放入 c1
<-c1 //尝试从c1取值
a := <-c1 //取值并赋变量a

结合

在goroutine中我们把一个值写入chan,然后在主进程中尝试从chan中读取数据,如果此时chan中没有数据,程序会被阻塞,只到有值取出。

代码语言:javascript复制
var c chan int

func ready(w string, sec int) {
    time.Sleep(time.Duration(sec) * time.Second)
    fmt.Println(w, "is ready!")
    c <- 1
}

func main() {
    c = make(chan int)
    go ready("Tea", 2)
    go ready("Coffee", 1)
    fmt.Println("I'm waiting, but not too long")
    <-c
    <-c
}

上面的例子是已经提前知道go出去的goroutine的个数才会这样写,当我们不知道个数的时候呢?

代码语言:javascript复制
func main() {
    c = make(chan int)
    go ready("Tea", 2)
    go ready("Coffee", 1)
    i := 0
    
    //跳转标记L
L:
    for {
        select {
        case <-c:

            i  
            if i > 1 {
                break L
            }
        }
    }
}

超时机制

代码语言:javascript复制
func main() {
    c = make(chan string)
    timeout = make(chan bool)
    go ready("Tea", 6)
    go ready("Coffee", 1)
    go func() {
        time.Sleep(time.Second * 3) // 5 秒超时
        timeout <- true
    }()
    fmt.Println("I'm waiting, but not too long")

L:
    for {
        select {
        case done := <-c:
            fmt.Println(done, "is ready!")
        case <-timeout:
            fmt.Println("timeout")
            break L
        }
    }
}

读取文件

方法一

代码语言:javascript复制
func main() {
    buf := make([]byte, 1024)
    f, _ := os.Open("/etc/passwd")
    defer f.Close()
    for {
        n, _ := f.Read(buf)
        if n == 0 {
            break
        }
        os.Stdout.Write(buf[:n])
    }
}

方法二

代码语言:javascript复制
func main() {
    fi, err := os.Open("/etc/passwd")
    if err != nil {
        panic(err)
    }
    defer fi.Close()
    fd, err := ioutil.ReadAll(fi)
    fmt.Println(string(fd))
}

0 人点赞