Golang 基础之一

文件目录

  • GOROOT Go的主程序目录
  • GOPATH Go的第三方组件目录
  • 项目目录

初始化一个项目,名称为 hello

系统环境需要先添加key-value: GO111MODULE=autoGO111MODULE=on

go mod init hello

  • 每一个应用程序都应包含一个main包: package main
  • 目录下的所有文件名可以任意,但 package 名必须与所在目录一直
import "fmt"
 
import (
    "fmt"
    "os"
    "net/http"
    "github.com/gorilla/websocket"
)

编译与执行

go run hello.go
go build hello.go
 
go install
go get

常量与变量

  • 常量用const关键字定义,变量用var关键字定义
  • Go语言不允许出现未被使用的变量
  • 常量类型编译器会自动判断,变量必须显式指定类型
  • 一般使用 fmt 包的 Scanln() 放法从键盘获取输入,也可用 bufio 的 Reader 对象从 os.stdin 读取
const Pi = 3.14159
var a int
var b float

# 不能以 x := nil (无类型)初始化变量,会报错:use of untyped nil,非要使用,写法如下:
var x interface{} = nil

# 普通输入
fmt.Scanln(&a, &b)

# 输入时必须按格式来,包含逗号
fmt.Scanf("%d,%f", &a, &b)

reader := bufio.NewReader(os.stdin)

# 以回车作为读取结束
res := reader.ReadString('\n')
  • 常量批量赋值
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6

const (
    a = 0
    b = 1
)

const (
    a = iota   //iota每遇到一次const自动归零,a=0
    b          //b=1,      iota=1
    c          //c=2,      iota=2
    d = "haha" //d="haha", iota=3
    e          //e="haha", iota=4
    f = 10     //f=10,     iota=5
    g          //g=10,     iota=6
    h = iota   //h=iota=7
    i          //i=iota=8
)

const (
    j = iota   //j=0
)
  • 变量批量定义与赋值
var n int64 = 2
 
var a, b, c = 5, 7, "abc"
 
var (
    a = 15
    b = false
)

a := true
a, b, c := 5, 7, "abc" //多变量一次性定义并赋值 `:=` ,左边需至少有一个变量未被定义

内置函数

名称 说明
close 用于关闭管道
len 返回某个类型的长度或数量(字符串、数组、切片、map 和管道)
cap 返回某个类型的最大容量(只能用于切片和 map)
new 为值类型和用户定义的类型分配内存,返回地址
make 为内置引用类型(slice、map、chan)分配内存,返回值
copy、append 用于复制和连接切片
panic、recover 两者均用于错误处理机制
print、println 底层打印函数
complex、real imag 用于创建和操作复数

数组

  • 在golang中,array和struct都是值类型的,而slice、map、chan是引用类型。所以我们写代码的时候,基本不使用array,而是用slice代替它,对于struct则尽量使用指针,这样避免传递变量时复制数据的时间和空间消耗,也避免了无法修改原数据的情况。

  • 使用 Copy() 函数进行引用类型的值(深)拷贝

  • 定义与赋值

var a = []int
var b = [3]byte{'a', 'b', 99}
c := []string{"abc", "def"}
d := new([6]float32) //值类型可以用new()初始化,需指定长度
  • 数组切片
//start_length 初始长度(一般为0)
//capacity (相关数组)最大长度
slice0 := make([]type, start_length, capacity)

arr := [6]int{1,2,3,4,5,6}
slice1 := arr[2:5]  索引>=2, <5
slice2 := arr[:5]   索引<5

map类型

  • map即键值对类型
  • 要比数组切片慢,但可以动态增加。
  • 未初始化的 map 的值是 nil
  • 可以被for...range循环
  • key 可以是任意可以用 == 或者 != 操作符比较的类型
  • key 也可以是指针和接口类型
  • key 不能是数组、切片和结构体
  • value 可以是任意类型
var map1 map[string]int  //key类型string,值类型int

//或

map1 := make(map[string]float32, lenth) //长度lenth选填
  • map遍历
for key, value := range map1{
    
}
  • map判断key的值是否存在
if a, ok := map1[key1]; ok {
    //a是value
    //如果key1存在则ok == true,否在ok为false
}
  • map删除键值
delete(map1, key1) //键不存在也不会出错

map底层使用了array存储数据,并且没有容量限制,随着map元素的增多,需要创建更大的array来存储数据,那么之前的地址就无效了,因为数据被复制到了新的更大的array中,所以不允许直接修改map中的元素,需要通过指针来修改。

type person struct {
    name   string
    age    byte
    isDead bool
}

func main() {
    p1 := &person{name: "zzy", age: 100}
    p2 := &person{name: "dj", age: 99}
    p3 := &person{name: "px", age: 20}
    people := map[string]*person{
        p1.name: p1,
        p2.name: p2,
        p3.name: p3,
    }
    whoIsDead(people)
    if p3.isDead {
        fmt.Println("who is dead?", p3.name)
    }
}

func whoIsDead(people map[string]*person) {
    for name, _ := range people {
        if people[name].age < 50 {
            people[name].isDead = true
        }
    }
}
  • map无法直接排序,需先取所有的key到数组里,用sort.String()排序,再通过数组映射map。
keys := make([]string, len(map1))
i := 0
for k, _ := range map1 {
    keys[i] = k
    i++
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Printf("Key: %v, Value: %v / ", k, map1[k])
}

字符串

  • 字符必须以单引号包含, 字符串必须用双引号包含
  • 基本定义与拼接
s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 “hello, world!”
  • bytes.Buffer 高效率拼接
var buf bytes.Buffer
buf.WriteString("12312")
buf.WriteString("werwer")
buf.String()

buf := bytes.Buffer{}
b := []byte{97, 98}
c := []byte{99, 100}
buf.Write(b)
buf.Write(c)
buf.Bytes()     //返回 []byte{ 97, 98, 99 100 }
buf.String()    //返回 "abcd"
  • Strings.Builder() 在一次性分配好足够的内存下性能比bytes.Buffer好
func StringBuilder(p []string) string {
    var b strings.Builder
    l:=len(p)
    for i:=0;i<l;i++{
        b.WriteString(p[i])
    }
    return b.String()
}
  • 字符串和字符数组共享内存
    bytes := []byte("I am byte array !") //字符串转字符数组 () 不是 {}
    str := (*string)(unsafe.Pointer(&bytes))
    bytes[0] = 'i'
    fmt.Println(*str)
  • Sprintf输出字符串变量
var s string = "12312sf"
s = fmt.Sprintf("%s %s",s,"123123")
  • strings.Join 处理字符串切片数组效率最好
s := "1231"
strings.Join([]string{s,"ewrwer"},"")
  • append
var s []string
s = append(s,"123123")
strings.Join(s,"")

字符串与数字转换

//string到int
int,err:=strconv.Atoi(string)

//string到int64
int64, err := strconv.ParseInt(string, 10, 64)

//int到string
string:=strconv.Itoa(int)

//int64到string
string:=strconv.FormatInt(int64,10)

遍历含中文的字符串

  • byte 类型以单字节 ASCII 码方式处理字符串,只能存储 ASCII 码
  • rune 类型以四字节 utf-8 方式处理字符串,[]rune("hello 中文") 可实现字符串转字符切片,可存储 Unicode 全字符
func traverseStrings() {
	//方式一:传统方法
	var str string = "hello,world!北京"
	str_cpy := []rune(str) //把str转成切片[]rune(str),可以解决字符串的中文乱码(中文字符是占3个字节)
	for i := 0; i < len(str_cpy); i++ {
		fmt.Printf("%c\n", str_cpy[i])
	}
 
	//方式二:for..range
	str1 := "ABCDEFG上海"
	//用这种方式不会出现乱码
	for index, val := range str1 {
		fmt.Printf("index = %d, val = %c \n", index, val)
	}
}

控制结构

  • 循环

循环可遍历array、slice、map等结构
循环内部是值拷贝,因此无法直接修改原变量,如需修改,则应当循环地址的集合。

for {
}

for i:=1; i<10; i++  {
}

for i := range arr {
}
  • 判断
if val := 10; val > max {
    // do something
}

if condition1 {
} else if condition2 {
}else {
}
  • switch
switch i {
    case 0:
        …………
    case 1:
        …………
    default:
        …………
}

函数

  • 函数参数也必须指定参数类型和返回值类型:
func a(n int) float32 {
    return float32(n)
}
  • 函数可以传变长参数, 参数可被range循环:
func b(n ...int){
    for i := range n{
    }
}
  • 函数可以返回多个值:
func c(n int) (int, float){
    return 1, 3.14
}
d, e := c(1)

自定义类型 struct

  • 相当于定义一个类(名)和类里的属性
type Person struct{
    name string
    age int
}
  • 结构体打tag
type Person struct{
    name string `json:"user_name"`
    age  int    `json:"user_age"`
}
//转成json后会变成
{
    user_name   : "xxx",
    user_age    : "xx"
}
  • 编写类方法
func (p *Person) sayName(){
	println("name is " + p.name)
	return
}
  • 函数内简单实例化一个类
zhangSan := &Person{
    name : "张三",
    age : 20,
}
  • 构造函数实例化一个类
func NewPerson(name string, age int) *Person{
	if age <= 0 {
		panic("error age")
	}
	return &Person{
		name:name,
		age:age,
	}
}
  • 调用实例方法
zhangSan = NewPerson("张三", 12)
zhangSan.sayName()
  • go没有static关键字,也没有静态方法或属性

管道与异步

  • 无缓冲区的管道读和写都会阻塞。
  • 有缓冲区的管道已满再写入,或已空再读取,才会阻塞。
  • 管道是不同线程之间数据通信的通道,原型为队列。
  • 关键字 go 用来异步执行函数。

创建管道:

// 无缓冲管道(双向管道)
ch1 := make(chan int)
// 有缓冲(容量)管道(双向管道)
ch2 := make(chan int, 10)
// 只读管道(单向管道)
ch3 := make(<- chan int)
// 只写管道(单向管道)
ch4 := make(chan <- int)
// 定义函数只写管道参数,可传入双向管道,但函数内只能写不能读
func writePipe(ch chan <- int){}
  • 写入数据到管道
ch1 <- 1
  • 从管道取出一个数据
b, ok := <- ch1
//ok 为 true 表示通道正常,为 false 表示通道已关闭
  • 关闭管道
defer close(ch1)
  • 遍历管道数据
  • 通过显式的比较管道取出的状态值是否为false判断管道已关闭,也可通过放入——取出一个约定的值判断
// 无阻塞方式遍历,适用于有缓冲管道
for {
    if value, ok := <- ch1; ok == true {
        println(value)
    }
}
// 或
for value := range ch1 {
    println(value)
}
  • 一个函数内不允许同时出现对同一管道的读和写,否则会出现 deadlock 死锁,因为读一个空的管道,或写一个满的管道,程序都会阻塞,无法继续执行下去,必须用关键字 go 开启新的线程处理读或写。
  • 一个无缓冲管道也可以实现数据同步传递
package main
import "time"

func aa(ch chan int){
	for{
		if value, ok := <- ch; ok == true {
			println(value)
		}
	}
}
func main() {
	ch1 := make(chan int)
	go aa(ch1)
	go func(ch chan int){
		for i:=0;i<10;i++  {
			ch <- i
		}
	}(ch1)
	time.Sleep(1 * time.Second)
}
  • 主函数不会等待异步函数结束才退出,使用 WaitGroup 正确退出
var wg sync.WaitGroup

func aa() {
    ………………
    wg.Done()
    return
}

func main() {
	wg.Add(1)
	go aa()
	wg.Wait()
}
  • select 切换协程
func suck(ch1, ch2 chan int) {
    for {
        select {
        case v := <-ch1:
            fmt.Printf("Received on channel 1: %d\n", v)
        case v := <-ch2:
            fmt.Printf("Received on channel 2: %d\n", v)
        }
    }
}
  • select 与 sync 综合示例
package main

import (
	"strconv"
	"sync"
)

var wg sync.WaitGroup

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	ch3 := make(chan int)
	wg.Add(4)
	go foo1(ch1, 10)
	go foo1(ch2, 20)
	go foo2(ch1, ch2, ch3)
	go foo3(ch3)
	wg.Wait()
}

func foo1(ch chan int, n int) {
	i := 1
	for ; i <= n; i++ {
		ch <- i
	}
	ch <- -1
	close(ch)
	wg.Done()
}

func foo2(ch1, ch2, ch3 chan int) {
	close1 := false
	close2 := false
	for {
		select {
		case v, ok := <-ch1:
			if ok && v > 0 {
				ch3 <- v
			} else if v == -1 {
				println("ch1 Closed")
				close1 = true
			}
		case v, ok := <-ch2:
			if ok && v > 0 {
				ch3 <- v
			} else if v == -1 {
				println("ch2 Closed")
				close2 = true
			}
		}
		if close1 && close2 {
			break
		}
	}
	close(ch3)
	wg.Done()
}

// 有返回值的函数,在 goroutine 中返回值会被抛弃
func foo3(ch chan int) int {
	count := 0
	for {
		if v, ok := <-ch; ok == true {
			count++
			println("ch3: " + strconv.Itoa(v))
		} else if ok == false {
			break
		}
	}
	println("Count: " + strconv.Itoa(count))
	wg.Done()
	return count
}


接口概念 interface

  • 实现一个功能的函数集合
  • 当某个功能方法需要以实现了某个接口的实例为参数时使用
  • 接口可以被多种类实现

空接口

  • 一般用Any命名 : type Any interface
  • 可以给一个空接口类型的变量 var val interface { } 赋任何类型的值。
posted @ 2020-03-24 21:50  Mr_kw  阅读(200)  评论(0)    收藏  举报