Golang简介

logo


Why Golang?

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


特点

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

配置运行环境

下载安装

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

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

Docker–Golang

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

配置

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

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"

编辑器

推荐使用


基本语法

HELLO_WORLD

// 一个文件夹内只能有一个包名
package main

import "fmt"

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

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

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

变量声明

类型在变量的后面

例子

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

PHP


控制结构

// 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 %d\n", char, pos)
}

// switch

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

复合类型

array、slices 和 map

array

// 定长
var arr [10]int
// 长度不同类型不同
var a [10]int
var b [11]int
a = b //报错

// 传值不传址

slice

// 长度可以改变
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

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)

函数

作用域

package main

var a = 6

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

返回格式


// 函数参数 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

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
}

匿名函数

func main() {
	a := func() {
		fmt.Println("你好")
	}
	a()
}

回调

func main() {
	callback("callback", print)
}
func print(s string) {
	fmt.Println(s)
}

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

恐慌和恢复

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的起点为

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)的。

func Add(a, b int) int {
	return a + b
}

func hide(){
    xxxx
}

工程目录

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为例

go get github.com/go-sql-driver/mysql

指针和结构体

指针

func main() {
	var i int
	i = 1
	var p *int
	p = &i

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

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

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

结构体

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

接口

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方式执行

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关键字创建

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中没有数据,程序会被阻塞,只到有值取出。

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的个数才会这样写,当我们不知道个数的时候呢?

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
			}
		}
	}
}

超时机制

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
		}
	}
}

读取文件

方法一

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])
	}
}

方法二

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))
}















Gopher

Gopher