归档 分类 标签 关于 RSS Feed
清风徐来
Michael's Blog
Go 非零基础一文入门

请输入图片描述

其实为了方便使用其他编程语言的开发者快速理解Go的一些特性。

指针和new

package main

import "fmt"

func main() {
	/*
	new关键字用于创建具有某些数据类型的指针变量
	*/
	//创建一个字符串类型的指针Pointer string
	name := new(string)
	//打印指针地址,输出类似0xc0000a0030
	fmt.Println(name)

	//给该指针指向的内存写入数据
	//name是指针变量,
	//给指针变量前加*就是取消应用,直接访问其中的值
	*name = "hello world"
	//打印该指针指向的内存中的数据,输出hello world
	fmt.Println(*name)
}

make关键字的限制

此关键字只能用于创建复杂类型的变量,即:

  • channel
  • slice
  • map

反引号的用途

package main

import "fmt"

func main() {
	var message = `My name is "John Work".
Are you ready.
Let's learn "Golang".`

	fmt.Println(message)
}

它会原样输出,不用顾虑转义字符,单双引号,想换行直接回车就行。

nil和零值

nil不是数据类型,而是值。值为nil的变量表示它具有空值。 Go中的数据类型定义后如果没赋值都会有默认的零值,和C不一样,不会出现奇奇怪怪的值 string 零值 "” bool 零值 false 整型 零值 0 浮点型 零值 0.0

下面的类型才能被赋为nil pointer func slice map channel interface

Go流程控制

不支持三元运算符 不支持,不支持,不支持

if 条件不要括号

switch 中 case匹配后不要,不要,不要break,直接跳出 一个case可以同时匹配多个,来实现其他语言中不带break的穿透

var point = 6

switch point {
case 8:
    fmt.Println("perfect")
case 7, 6, 5, 4:
    fmt.Println("awesome")
default:
    fmt.Println("not bad")
}

switch 结合判断,穿透fallthrough的写法

var point = 6

switch {
case point == 8:
    fmt.Println("perfect")
case (point < 8) && (point > 3):
    fmt.Println("awesome")
    fallthrough
case point < 5:
    fmt.Println("you need to learn more")
default:
    {
        fmt.Println("not bad")
        fmt.Println("you need to learn more")
    }
}

数组Array

//先声明再一一赋值
var names [4]string
names[0] = "trafalgar"
names[1] = "d"
names[2] = "water"
names[3] = "law"

//声明赋值一起
var fruits = [4]string{"apple", "grape", "banana", "melon"}

/声明后一起赋值,赋值可以一行也可以多行
var fruits [4]string
// 一行
fruits  = [4]string{"apple", "grape", "banana", "melon"}
// 多行
fruits  = [4]string{
    "apple",
    "grape",
    "banana",
    "melon",
}

//声明赋值数组,但是不写长度
var numbers = [...]int{2, 3, 2, 4, 3}

//多维数组
var numbers1 = [2][3]int{[3]int{3, 2, 3}, [3]int{3, 4, 5}}
var numbers2 = [2][3]int{{3, 2, 3}, {3, 4, 5}}

//变量数组的方式
var fruits = [4]string{"apple", "grape", "banana", "melon"}
for i := 0; i < len(fruits); i++ {
    fmt.Printf("elemen %d : %s\n", i, fruits[i])
}

var fruits = [4]string{"apple", "grape", "banana", "melon"}
for i, fruit := range fruits {
    fmt.Printf("elemen %d : %s\n", i, fruit)
}

var fruits = [4]string{"apple", "grape", "banana", "melon"}
for _, fruit := range fruits {
    fmt.Printf("nama buah : %s\n", fruit)
}

//关键字make声明数组
var fruits = make([]string, 2)
fruits[0] = "apple"
fruits[1] = "manggo"

切片slice

Slice是一个数组元素引用。可以通过操作Array或其他Slice来生成切片。因为它是引用类型的数据,所以在每个切片元素中进行数据更改将对具有相同内存地址的其他切片或数组产生影响。

var fruits = []string{"apple", "grape", "banana", "melon"}
fmt.Println(fruits[0]) // "apple"

声明切片,把声明数组时的长度或...去掉就可以了,因为切片可以变长的。

var fruitsA = []string{"apple", "grape"}      // slice
var fruitsB = [2]string{"banana", "melon"}    // array
var fruitsC = [...]string{"papaya", "grape"}  // array

cap()和len()傻傻分不清?

len就是返回实际元素数量;cap是目前分配的容量大小,它是>=len的,是在变化slice大小时为了提高效率预先可能多分配的内存空间,这样在下次再次添加元素时就不要每次都去分配内存空间。

切片拷贝copy

注意参数位置,先是目标切片,第二个才是源切片。而且可以把长的切片拷贝到更短的切片中。

dst := make([]string, 3)
src := []string{"watermelon", "pinnaple", "apple", "orange"}
n := copy(dst, src)

fmt.Println(dst) // watermelon pinnaple apple
fmt.Println(src) // watermelon pinnaple apple orange
fmt.Println(n)   // 3 拷贝的数量

目标切片有数据的情况,会替换对应位置的数据

dst := []string{"potato", "potato", "potato"}
src := []string{"watermelon", "pinnaple"}
n := copy(dst, src)

fmt.Println(dst) // watermelon pinnaple potato
fmt.Println(src) // watermelon pinnaple
fmt.Println(n)   // 2

Map 映射

package main

import "fmt"

func main() {
	var chicken1 = map[string]int{"mac": 50, "pc": 40}
	chicken1["ipad"] = 100

	for key, val := range chicken1 {
		fmt.Println(key, "  \t:", val)
	}

	var chicken2 = map[string]int{}
	chicken2["iphone"] = 100
	chicken2["hawei"] = 150

	for key, val := range chicken2 {
		fmt.Println(key, "  \t:", val)
	}

	var value, isExist = chicken2["xiaomi"]

	if isExist {
		fmt.Println(value)
	} else {
		fmt.Println("item is not exists")
	}

}

切片和Map一起来

var chickens = []map[string]string{
    map[string]string{"name": "chicken blue",   "gender": "male"},
    map[string]string{"name": "chicken red",    "gender": "male"},
    map[string]string{"name": "chicken yellow", "gender": "female"},
}

for _, chicken := range chickens {
    fmt.Println(chicken["gender"], chicken["name"])
}

随机数,要有随机种子,下面用时间戳来做

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().Unix())
    var randomValue int

    randomValue = randomWithRange(2, 10)
    fmt.Println("random number:", randomValue)
    randomValue = randomWithRange(2, 10)
    fmt.Println("random number:", randomValue)
    randomValue = randomWithRange(2, 10)
    fmt.Println("random number:", randomValue)
}

func randomWithRange(min, max int) int {
    var value = rand.Int() % (max - min + 1) + min
    return value
}

函数的定义和返回值

上面已经写了一个函数了,函数的声明如下:

func nameOfFunc(paramA type, paramB type, paramC type) returnType
func nameOfFunc(paramA, paramB, paramC type) returnType

func randomWithRange(min int, max int) int
func randomWithRange(min, max int) int

返回多个值,而且命名过了,不需要特别返回指定了

func calculate(d float64) (area float64, circumference float64) {
    area = math.Pi * math.Pow(d / 2, 2)
    circumference = math.Pi * d

    return
}

不定长参数

package main

import "fmt"

func main() {
	var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
	var msg = fmt.Sprintf("Avg : %.2f", avg)
	fmt.Println(msg)
}

func calculate(numbers ...int) float64 {
	var total int = 0
	for _, number := range numbers {
		total += number
	}

	var avg = float64(total) / float64(len(numbers))
	return avg
}

可变参数的位置,要放到最后,不然没法处理

func yourHobbies(name string, hobbies ...string) {
    var hobbiesAsString = strings.Join(hobbies, ", ")

    fmt.Printf("Hello, my name is: %s\n", name)
    fmt.Printf("My hobbies are: %s\n", hobbiesAsString)
}

经常用到的string比较函数

var resulta = strings.Contains("Golang", "Golang")
var resultb = strings.Contains("Golang", "lang")
//true,true;Golang 包含了Golang 也包含了lang

strings.Split("ethan hunt", " ")
// ["ethan", "hunt"]

Pointer 指针

指针是引用或内存地址。指针变量表示包含值的内存地址的变量。例如,整数类型变量的值为4,那么其指针的含义是存储值4的存储器地址,而不是值4本身。

具有相同引用或指针地址的变量彼此相关,并且它们的值必须相同。当值发生变化时,它将使其他变量(相同的引用)生效,即值发生变化。

//除了一开始介绍的var name = new(string)来声明,也可以用以下方式
var number *int
var name *string

指针变量的默认值为nil(空)。指针变量不能保存非指针的值,反之亦然普通变量不能保存指针的值。

注意:

  • 可以从常规变量中得到其指针(内存地址),在变量名称之前添加&符号(&)。此方法称为引用。
  • 相反,通过在指针变量名称前添加星号(*),也可以检索指针变量的原始值。此方法称为解除引用。
var numberA int = 4							// int value
var numberB *int = &numberA					// 取numberA的地址赋给numberB指针
											// 他们指向了同一个内存空间,该内存中存储了4

fmt.Println("numberA (value)   :", numberA)  // 4
fmt.Println("numberA (address) :", &numberA) // 0xc20800a220,普通变量通过&取其地址

fmt.Println("numberB (value)   :", *numberB) // 4,通过指针取赋值用*
fmt.Println("numberB (address) :", numberB)  // 0xc20800a220

此时变化numberA的值后,再执行一遍打印,numberA和*numberB都统一改变。

参数也可以用指针

package main

import "fmt"

func main() {
	var number = 4
	fmt.Println("before :", number) // 4

	change(&number, 10)
	fmt.Println("after  :", number) // 10
}

//第一参数为指针,调用时一般传 &普通变量名 取得地址丢过去
//第二参数为新值
func change(original *int, value int) {
	*original = value
}

指针理解到这里也就差不多了,再去搞深了,头就晕了。

Struct 结构体

Struct是变量定义(或属性)和/或函数(或方法)的集合,它们被包装为具有特定名称的新数据类型。结构中的属性,数据类型可以有所不同。与Map类似,只是在开头定义了Key,每个Item的数据类型可以不同。

golang中struct的概念类似于OOP中的类,尽管它实际上是不同的。使用OOP的概念作为类比,目的是为了更容易消化Struct的相关知识。

简单的DEMO,定义一个student结构体,main中新建一个student类型的变量,并给其属性赋值。

package main

import "fmt"

type student struct {
	name  string
	grade int
}

func main() {
	var s1 student
	s1.name = "john wick"
	s1.grade = 2

	fmt.Println("name  :", s1.name)
	fmt.Println("grade :", s1.grade)
}

结构体初始化

var s1 = student{}
s1.name = "wick"
s1.grade = 2

var s2 = student{"ethan", 2}

var s3 = student{name: "jason"}

var s4 = student{name: "wayne", grade: 2}
var s5 = student{grade: 2, name: "bruce"}


fmt.Println("student 1 :", s1.name)
fmt.Println("student 2 :", s2.name)
fmt.Println("student 3 :", s3.name)
fmt.Println("student 4 :", s4.name)
fmt.Println("student 5 :", s5.name)

Struce 结合 Pointer

看例子

package main

import "fmt"

type student struct {
	name  string
	grade int
}

func main() {
	var s1 = student{name: "wick", grade: 2}

	var s2 *student = &s1
	fmt.Println("student 1, name :", s1.name)
	fmt.Println("student 2, name :", s2.name)

	s2.name = "ethan"
	fmt.Println("student 1, name :", s1.name)
	fmt.Println("student 2, name :", s2.name)

}

注意到没有,s2 是 指针,但是访问其属性时没有,没有,没有用*号!!! 其实可以用的但是要这样写 (*s2).name,可以正确执行,但是很麻烦,也没有必要这么写。

结构的嵌套

package main

import "fmt"

type person struct {
    name string
    age  int
}

type student struct {
    grade int
    person
}

func main() {
    var s1 = student{}
    s1.name = "wick"
    s1.age = 21
    s1.grade = 2

    fmt.Println("name  :", s1.name)
    fmt.Println("age   :", s1.age)
    fmt.Println("age   :", s1.person.age)
    fmt.Println("grade :", s1.grade)
}

这种.后面接属性,再接属性的方式我觉得很优雅,要吐槽js和php的->了有没有?

嵌套中有同名的属性的处理

package main

import "fmt"

type person struct {
    name string
    age  int
}

type student struct {
    person
    age   int
    grade int
}

func main() {
    var s1 = student{}
    s1.name = "wick"
    s1.age = 21        // age of student
    s1.person.age = 22 // age of person

    fmt.Println(s1.name)
    fmt.Println(s1.age)
    fmt.Println(s1.person.age)
}

好吧,就是不处理。不再提升到外层结构,一一对应就好。

匿名结构

匿名结构是未预先声明直接在创建对象时声明的结构。对于创建仅使用一次的结构对象变量非常有效。如以下的s1

package main

import "fmt"

type person struct {
	name string
	age  int
}

func main() {
	var s1 = struct {
		person
		grade int
	}{}
	s1.person = person{"wick", 21}
	s1.grade = 2

	fmt.Println("name  :", s1.person.name)
	fmt.Println("age   :", s1.person.age)
	fmt.Println("grade :", s1.grade)
}

Struce 和 Slice

type person struct {
    name string
    age  int
}

var allStudents = []person{
    {name: "Wick", age: 23},
    {name: "Ethan", age: 23},
    {name: "Bourne", age: 22},
}

for _, student := range allStudents {
    fmt.Println(student.name, "age is", student.age)
}

匿名的

var allStudents = []struct {
    person
    grade int
}{
    {person: person{"wick", 21}, grade: 2},
    {person: person{"ethan", 22}, grade: 3},
    {person: person{"bond", 21}, grade: 3},
}

for _, student := range allStudents {
    fmt.Println(student)
}

有时为了json解码方便,也可以声明到一个结构体中

type student struct {
    person struct {
        name string
        age  int
    }
    grade   int
    hobbies []string
}

结构体的Tag

标签是可以添加到每个结构属性的可选信息。在实际应用中很常见。

type person struct {
    name string `tag1`
    age  int    `tag2`
}

结构体的方法

有了方法的结构体,功能更加强大

package main

import "fmt"
import "strings"

type student struct {
    name  string
    grade int
}

func (s student) sayHello() {
    fmt.Println("Hello", s.name)
}

func (s student) getNameAt(i int) string {
    return strings.Split(s.name, " ")[i-1]
}

func main() {
    var s1 = student{"john wick", 21}
    s1.sayHello()

    var name = s1.getNameAt(2)
    fmt.Println("Last Name :", name)
}

方法指针

package main

import "fmt"

type student struct {
	name  string
	grade int
}

func (s student) changeName1(name string) {
	fmt.Println("---> on changeName1, name changed to", name)
	s.name = name
}

func (s *student) changeName2(name string) {
	fmt.Println("---> on changeName2, name changed to", name)
	s.name = name
}

func main() {
	var s1 = student{"john wick", 21}
	fmt.Println("s1 before", s1.name)
	// john wick

	s1.changeName1("jason bourne")
	fmt.Println("s1 after changeName1", s1.name)
	// john wick

	s1.changeName2("ethan hunt")
	fmt.Println("s1 after changeName2", s1.name)
	// ethan hunt
}

执行输出

s1 before john wick
---> on changeName1, name changed to jason bourne
s1 after changeName1 john wick
---> on changeName2, name changed to ethan hunt
s1 after changeName2 ethan hunt

s1.changeName2()才会正真改变。changeName1只会在方法内部改变,并没有s1的name。

访问权限

导入的包,其中方法大写字母开头的是可以用的,小写字母开头的不能用。 struct也是一样,我们引用的包中的结构,我们不可以访问小写字母开头的属性。

多个文件的运行

hello.go

package main

import "fmt"

func sayHello(name string) {
	fmt.Println("Hello", name)
}

主文件 main.go

package main

func main() {
    sayHello("Mike")
}

运行go run main.go hello.go,也是可以运行的。

init()函数

假设有个library/mylib.go

package library

import "fmt"

var Student = struct {
	Name  string
	Grade int
}{}

func init() {
	Student.Name = "John Wick"
	Student.Grade = 2

	fmt.Println("--> library/mylib.go imported")
}

主文件main.go

package main

import "./library"
import "fmt"

func main() {
	fmt.Printf("Name  : %s\n", library.Student.Name)
	fmt.Printf("Grade : %d\n", library.Student.Grade)
}

运行

go run main.go
--> library/mylib.go imported
Name  : John Wick
Grade : 2

接口interface

接口是方法定义的集合,没有包含特定的内容(仅定义)

接口是一种数据类型。类型为接口的对象的零值为nil。如果接口已经具有内容,则可以使用接口,这些内容是具有与接口中的最小方法定义相同的最小方法定义的具体对象。

package main

import "fmt"
import "math"

//声明接口
type countit interface {
	area() float64
	around() float64
}

//声明圆的结构
type circle struct {
	diameter float64
}

//结构的方法
func (l circle) radius() float64 {
	return l.diameter / 2
}

//结构的方法,也是接口area的实现
func (l circle) area() float64 {
	return math.Pi * math.Pow(l.radius(), 2)
}

//结构的方法,也是接口around的实现
func (l circle) around() float64 {
	return math.Pi * l.diameter
}

//正方形
type square struct {
	side float64
}

//结构的方法
func (p square) area() float64 {
	return math.Pow(p.side, 2)
}

//结构的方法
func (p square) around() float64 {
	return p.side * 4
}

func main() {
	var bangunDatar countit

	bangunDatar = square{10.0}
	fmt.Println("===== 正方形")
	fmt.Println("面积  :", bangunDatar.area())
	fmt.Println("周长  :", bangunDatar.around())

	bangunDatar = circle{14.0}
	fmt.Println("===== 圆")
	fmt.Println("面积  :", bangunDatar.area())
	fmt.Println("周长  :", bangunDatar.around())
	//以下会报错,ype countit has no field or method radius
	//fmt.Println("半径  :", bangunDatar.radius())
	//要转成结构,调用结构的方法
	fmt.Println("半径  :", bangunDatar.(circle).radius())
}

接口实现了,那么要调用接口外的方法时,需要转成原来的结构对象,才能访问结构的方法。因为实现了接口后,生成的对象默认就是该接口类型了,不再是结构类型,需要转换objName.(structName).

接口的嵌套

上代码吧

package main

import "fmt"
import "math"

//声明接口
type countit2d interface {
	area() float64
	around() float64
}

type countit3d interface {
	volume() float64
}

type countit interface {
	countit2d
	countit3d
}

type cube struct {
	side float64
}

func (c *cube) volume() float64 {
	return math.Pow(c.side, 3)
}

func (c *cube) area() float64 {
	return math.Pow(c.side, 2) * 6
}

func (c *cube) around() float64 {
	return c.side * 12
}

func main() {
	var c countit = &cube{10.0}
	fmt.Println("===== 正方体")
	fmt.Println("面积  :", c.area())
	fmt.Println("周长  :", c.around())
	fmt.Println("体积  :", c.volume())

}

结果

go run main.go
===== 正方体
面积  : 600
周长  : 120
体积  : 1000

要注意,这次用了方法指针,所以定义变量时用了&符号

空接口 interface

空接口是一种非常特殊的数据类型。这种类型的变量可以包含任何类型的数据,甚至是数组,指针,任何东西。具有此概念的数据类型通常称为动态类型。

package main

import "fmt"

func main() {
    var secret interface{}

    secret = "ethan hunt"
    fmt.Println(secret)

    secret = []string{"apple", "manggo", "banana"}
    fmt.Println(secret)

    secret = 12.4
    fmt.Println(secret)
}

输出

ethan hunt
[apple manggo banana]
12.4

一般使用时,需要转换

package main

import "fmt"
import "strings"

func main() {
	var secret interface{}

	secret = 2
	var number = secret.(int) * 10
	fmt.Println(secret, "multiplied by 10 is :", number)

	secret = []string{"apple", "manggo", "banana"}
	var gruits = strings.Join(secret.([]string), ", ")
	fmt.Println(gruits, "is my favorite fruits")
}

interface{}结合指针的用法

package main

import "fmt"

func main() {
	type person struct {
		name string
		age  int
	}

	var secret interface{} = &person{name: "mike", age: 27}
	var name = secret.(*person).name
	fmt.Println(name)
}

interface{}结合slice,map

package main

import "fmt"

func main() {
	var person = []map[string]interface{}{
		{"name": "Mike", "age": 23},
		{"name": "Bruce", "age": 23},
		{"name": "Jay", "age": 22},
	}

	for _, each := range person {
		fmt.Println(each["name"], "age is", each["age"])
	}
}

反射 Reflect

反射是一种检查变量,并从这些变量中获取信息甚至操纵变量的技术。可以通过反射获得的信息范围非常广泛,例如查看变量的结构,类型,指针值等

有两个最重要的函数,即reflect.ValueOf()reflect.TypeOf()

  • reflect.ValueOf()函数将返回一个reflect.Value类型的对象,该对象包含与被搜索变量的值相关的信息。
  • reflect.TypeOf()返回一个reflect.Type类型的对象。该对象包含与搜索的数据类型相关的信息
package main

import "fmt"
import "reflect"

func main() {
	var number = 23
	var reflectValue = reflect.ValueOf(number)

	fmt.Println("type of variabel :", reflectValue.Type())

	if reflectValue.Kind() == reflect.Int {
		fmt.Println("value of variabel :", reflectValue.Int())
	}
	fmt.Println("value of variabel :", reflectValue.Interface())
}

输出:

type of variabel : int
value of variabel : 23
value of variabel : 23

再来

package main

import "fmt"
import "reflect"

type student struct {
    Name  string
    Grade int
}

func (s *student) getPropertyInfo() {
    var reflectValue = reflect.ValueOf(s)

    if reflectValue.Kind() == reflect.Ptr {
		//如果时指针,需要用Elem()方法来获取原始的反射对象
        reflectValue = reflectValue.Elem()
    }

    var reflectType = reflectValue.Type()

	//NumField()方法返回结构中的公共属性数
    for i := 0; i < reflectValue.NumField(); i++ {
        fmt.Println("name      :", reflectType.Field(i).Name)
        fmt.Println("data type :", reflectType.Field(i).Type)
        fmt.Println("value     :", reflectValue.Field(i).Interface())
        fmt.Println("")
    }
}

func main() {
    var s1 = &student{Name: "wick", Grade: 2}
    s1.getPropertyInfo()
}

输出:

name      : Name
data type : string
value     : wick

name      : Grade
data type : int
value     : 2

调用方法

package main

import "fmt"
import "reflect"

type student struct {
    Name  string
    Grade int
}

func (s *student) SetName(name string) {
    s.Name = name
}

func main() {
    var s1 = &student{Name: "john wick", Grade: 2}
    fmt.Println("name :", s1.Name)

    var reflectValue = reflect.ValueOf(s1)
    var method = reflectValue.MethodByName("SetName")
    method.Call([]reflect.Value{
        reflect.ValueOf("wick"),
    })

    fmt.Println("name :", s1.Name)
}

结果:

nama : john wick
nama : wick

Goroutine

Goroutine类似于线程,但并不是线程。线程可以包含很多goroutine。 将goroutine称为迷你线程更合适。 Goroutine很轻,一个goroutine只需要大约2kB的内存。 Goroutine 是异步执行的。

Goroutine是Go上并发编程中最重要的部分之一。 Goroutine如此特别的一个原因是它可以运行在多核处理器上。我们可以确定Cpu的核心数量越多,速度就越快。当然内存也要够大。

并发与并行不同。并行是同时执行许多进程。而并发是一个过程的组成。并发是一种结构,而并行是执行的方式。

要应用goroutine,必须将作为goroutine执行的过程包装到函数中。 在调用该函数时,会在其前面添加go关键字,也就创建一个新的goroutine,其任务是运行函数中的代码。

以下是goroutine的简单实现示例。下面的程序显示10行文本,5行以正常方式执行,5行作为新goroutine执行。

package main

import "fmt"
import "runtime"

func print(till int, message string) {
    for i := 0; i < till; i++ {
        fmt.Println((i + 1), message)
    }
}

func main() {
    //确定为程序执行而激活的核心数
    runtime.GOMAXPROCS(2)

    go print(5, "hello")
    print(5, "how are you")

    var input string
    fmt.Scanln(&input)
}

解释一下,fmt.Scanln(&input)会等待用户回车,将在用户按下回车键之前捕获所有字符,然后将它们保存在变量中。 因为goroutine print()的执行时间可能比main goroutine main() 的执行时间长,因为两者都是异步的。 等待用户回车,可以确保go print可以执行完成,以免main先完成,导致程序退出,go print无法执行完成。 执行的结果是不确定的

➜  learn-golang go run main.go
1 hello
2 hello
3 hello
4 hello
5 hello
1 how are you
2 how are you
3 how are you
4 how are you
5 how are you

➜  learn-golang go run main.go
1 how are you
2 how are you
3 how are you
4 how are you
1 hello
2 hello
5 how are you
3 hello
4 hello
5 hello

试试Scanln的用法

var s1, s2, s3 string
fmt.Scanln(&s1, &s2, &s3)

// user inputs: "how are you"

fmt.Println(s1) // how
fmt.Println(s2) // are
fmt.Println(s3) // you

Channel 通道

通道用于连接一个goroutine和另一个goroutine。 使用的机制是通过Channel交互数据。通道在一个goroutine中充当发送器,并且在另一个goroutine中充当接收器。 在通道上发送和接收数据是阻塞的或者说是同步的。

channerl.png

package main

import "fmt"
import "runtime"

func main() {
    runtime.GOMAXPROCS(2)

	//声明一个字符串类型的通道
    var messages = make(chan string)

	//创建一个闭包,向messages通道发送数据
    var sayHelloTo = func(who string) {
        var data = fmt.Sprintf("hello %s", who)
        messages <- data
    }

    go sayHelloTo("Mike")
    go sayHelloTo("Bruce")
    go sayHelloTo("Jack")

	//message1 从通道messages接收数据,在接收到数据前会阻塞等等
    var message1 = <-messages
    fmt.Println(message1)

    var message2 = <-messages
    fmt.Println(message2)

    var message3 = <-messages
    fmt.Println(message3)
}

执行结果

learn-golang go run main.go
hello Bruce
hello Jack
hello Mike
➜  learn-golang go run main.go
hello Jack
hello Mike
hello Bruce

通道作为参数

package main

import "fmt"
import "runtime"

func printMessage(what chan string) {
    fmt.Println(<-what)
}

func main() {
    runtime.GOMAXPROCS(2)

    var messages = make(chan string)

    for _, each := range []string{"Jack", "Bruce", "Mike"} {
        go func(who string) {
            var data = fmt.Sprintf("hello %s", who)
            messages <- data
        }(each)
    }

    for i := 0; i < 3; i++ {
        printMessage(messages)
    }
}

执行结果

learn-golang go run main.go
hello Mike
hello Jack
hello Bruce
➜  learn-golang go run main.go
hello Jack
hello Mike
hello Bruce

Buffer Channel

带缓冲的通道。 默认情况下,通道上的数据传输过程是非缓冲的,而不是缓冲在内存中。 当从goroutine通过信道发送数据的过程时,必须有另一个goroutine负责从同一信道接收数据,其中交换过程是阻塞的。 也就是说,在交换过程完成之前,将不处理发送和接收数据之后的代码行。

缓冲通道不同。在缓冲通道中,要确定缓冲器的数量。该数量是确定可以同时发送的数据量。 只要发送的数据量不超过缓冲区的数量,传输就会异步运行(不阻塞)。

当发送的数据量不超过缓冲区限制时,在缓冲通道上发送数据的过程是异步的。但在通道数据接收总是同步的。 channel.png

package main

import "fmt"
import "runtime"

func main() {
	runtime.GOMAXPROCS(2)

	messages := make(chan int, 2)

	go func() {
		for {
			i := <-messages
			fmt.Println("receive data", i)
		}
	}()

	for i := 0; i < 5; i++ {
		fmt.Println("send data", i)
		messages <- i
	}
}

执行结果:

➜  learn-golang go run main.go
send data 0
send data 1
send data 2
send data 3
receive data 0
receive data 1
receive data 2
receive data 3
send data 4
receive data 4
➜  learn-golang go run main.go
send data 0
send data 1
send data 2
send data 3
receive data 0
receive data 1
send data 4
receive data 2

第二次执行时,没有接收到3和4,因为main Goroutine已经执行结束,但是匿名的go goroutine还没完成就被退出了。

select

以下程序是在通道中应用select的简单示例。 准备了两个goroutine,一个用于获取平均数,一个用于获取最大值。 每个goroutine的操作结果通过一个通道发送到main()函数(有两个通道)。 在main()函数本身中,使用关键字select接收数据。

package main

import (
	"fmt"
	"runtime"
)

func getAverage(numbers []int, ch chan float64) {
	var sum = 0
	for _, e := range numbers {
		sum += e
	}
	ch <- float64(sum) / float64(len(numbers))
}

func getMax(numbers []int, ch chan int) {
	var max = numbers[0]
	for _, e := range numbers {
		if max < e {
			max = e
		}
	}
	ch <- max
}

func main() {
	runtime.GOMAXPROCS(2)

	var numbers = []int{3, 4, 3, 5, 6, 3, 2, 2, 6, 3, 4, 6, 3}
	fmt.Println("numbers :", numbers)

	var ch1 = make(chan float64)
	go getAverage(numbers, ch1)

	var ch2 = make(chan int)
	go getMax(numbers, ch2)

	for i := 0; i < 2; i++ {
		select {
		case avg := <-ch1:
			fmt.Printf("Avg \t: %.2f \n", avg)
		case max := <-ch2:
			fmt.Printf("Max \t: %d \n", max)
		}
	}
}

结果

➜  learn-golang go run main.go
numbers : [3 4 3 5 6 3 2 2 6 3 4 6 3]
Max     : 6 
Avg     : 3.85 

for range close

package main

import (
	"fmt"
	"runtime"
)

func sendMessage(ch chan<- string) {
	for i := 0; i < 20; i++ {
		ch <- fmt.Sprintf("data %d", i)
	}
	close(ch)
}

func printMessage(ch <-chan string) {
	for message := range ch {
		fmt.Println(message)
	}
}

func main() {
	runtime.GOMAXPROCS(2)

	var messages = make(chan string)
	go sendMessage(messages)
	printMessage(messages)
}

通道方向

  • ch chan string 可以发送和接收
  • ch chan<- string 只能用于发送数据
  • ch <-chan string 只能用于接收数据

通道的Timeout

package main

import "fmt"
import "math/rand"
import "runtime"
import "time"

func sendData(ch chan<- int) {
	for i := 0; true; i++ {
		ch <- i
		time.Sleep(time.Duration(rand.Int()%10+1) * time.Second)
	}
}

func retreiveData(ch <-chan int) {
loop:
	for {
		select {
		case data := <-ch:
			fmt.Print(`receive data "`, data, `"`, "\n")
		case <-time.After(time.Second * 5):
			fmt.Println("timeout. no activities under 5 seconds")
			break loop
		}
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	runtime.GOMAXPROCS(2)

	var messages = make(chan int)

	go sendData(messages)
	retreiveData(messages)
}

select上有2个条件块:

  • case data := <-ch,通道有数据时满足
  • case <-time.After(time.Second * 5),5秒没有接收到数据时满足

Defer & Exit

Defer时延迟触发,会在程序退出前触发 Exit是强制退出程序

package main

import "fmt"

func main() {
    defer fmt.Println("Hello")
    fmt.Println("some text")
}

程序会先打印some text,后打印Hello(程序退出前执行)

package main

import "fmt"
import "os"

func main() {
    defer fmt.Println("Hello")
    //强制退出,defer也不会执行
    os.Exit(1)
    fmt.Println("some text")
}

直接输出

go run main.go
exit status 1

Error, Panic, 还有 Recover

在Go中,有许多函数返回多个返回值。通常,其中一个返回值是错误类型。 例如,在strconv.Atoi()函数中。该函数用于将字符串数据转换为数字。此函数返回2个返回值。第一个返回值是转换结果,第二个返回值是错误。 当转换正常运行时,第二个返回值将为nil。当转换失败时,可以从返回的错误中立即知道原因。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var input string
	fmt.Print("Type some number: ")
	fmt.Scanln(&input)

	var number int
	var err error
	number, err = strconv.Atoi(input)

	if err == nil {
		fmt.Println(number, "is number")
	} else {
		fmt.Println(input, "is not number")
		fmt.Println(err.Error())
	}
}

执行

➜  learn-golang go run main.go
Type some number: 99
99 is number
➜  learn-golang go run main.go
Type some number: abc
abc is not number
strconv.Atoi: parsing "abc": invalid syntax

自定义Error

package main

import (
	"errors"
	"fmt"
	"strings"
)

func validate(input string) (bool, error) {
	if strings.TrimSpace(input) == "" {
		// 输入为空时,返回false和自定义的错误
		return false, errors.New("cannot be empty")
	}
	return true, nil
}
func main() {
	var name string
	fmt.Print("Type your name: ")
	fmt.Scanln(&name)

	if valid, err := validate(name); valid {
		fmt.Println("Hello", name)
	} else {
		fmt.Println(err.Error())
	}
}

尝试直接回车:

➜  learn-golang go run main.go
Type your name: 
cannot be empty

Panic

把main中的fmt.Println(err.Error()),改成panic(err.Error),如下:

func main() {
	var name string
	fmt.Print("Type your name: ")
	fmt.Scanln(&name)

	if valid, err := validate(name); valid {
		fmt.Println("hello", name)
	} else {
		panic(err.Error())
		fmt.Println("end")
	}
}

执行:

➜  learn-golang go run main.go
Type your name: 
panic: cannot be empty

goroutine 1 [running]:
main.main()
        /Users/michael/Desktop/learn-golang/main.go:24 +0x1fe
exit status 2

recover 恢复

recov恢复对于处理panic恐慌错误很有用。发生恐慌错误时,恢复处于恐慌状态的接管goroutine(不会出现紧急消息)。 修改上面的例子,添加catch()函数,在此函数中有一个recov()语句,它将返回应该出现的恐慌错误消息。

package main

import (
	"errors"
	"fmt"
	"strings"
)

func validate(input string) (bool, error) {
	if strings.TrimSpace(input) == "" {
		// 输入为空时,返回false和自定义的错误
		return false, errors.New("cannot be empty")
	}
	return true, nil
}

func catch() {
	if r := recover(); r != nil {
		fmt.Println("Error occured", r)
	} else {
		fmt.Println("Application running perfectly")
	}
}

func main() {
	defer catch()
	var name string
	fmt.Print("Type your name: ")
	fmt.Scanln(&name)

	if valid, err := validate(name); valid {
		fmt.Println("hello", name)
	} else {
		panic(err.Error())
		fmt.Println("end")
	}
}

执行

➜  learn-golang go run main.go
Type your name: 
Error occured cannot be empty
➜  learn-golang go run main.go
Type your name: mike
hello mike
Application running perfectly

recover 和 IIFE

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Panic occured", r)
		} else {
			fmt.Println("Application running perfectly")
		}
	}()

	panic("some error happen")
}

执行结果

➜  learn-golang go run main.go
Panic occured some error happen

字符串格式化输出

%s, %d, %.2f, %t,%T 字符串,整型,2位小数的浮点数, 布尔值,类型名称

%e , %E科学记数法,1.825000e+02, 1.825000E+02

一个unicode代码,形成一串unicode字符

fmt.Printf("%c\n", 1400)
// ո
fmt.Printf("%c\n", 1235)
// ӓ

fmt.Printf("%o\n", data.age),转成8进制表示的字符串

%x , %X,16进制

fmt.Printf("%p\n", &data.name),输出内存地址

%v,用于格式化任何数据(包括接口类型数据{})。返回结果是一串原始数据值,例如下面:

fmt.Printf("%v\n", data)
// {wick 182.5 26 false [eating sleeping]}

%+v,用于格式化结构,根据结构结构依次返回每个属性的名称及其值。更清晰对应关系。

fmt.Printf("%+v\n", data)
// {name:wick height:182.5 age:26 isGraduated:false hobbies:[eating sleeping]}

时间,解析时间和格式化时间

time.Time

time.Time类型是日期时间对象的表示。有两种方法可以选择创建此类型的数据

  • 使用time.Now()将当前时间信息设为time.Time对象
  • 使用time.Date()创建一个具有自定义信息的time.Time类型的新对象
package main

import "fmt"
import "time"

func main() {
	var time1 = time.Now()
	fmt.Printf("time1 %v\n", time1)
	// time1 2019-08-21 17:19:21.673645 +0800 CST m=+0.000212639

	loc, _ := time.LoadLocation("Local") //服务器设置的时区
	var time2 = time.Date(2011, 12, 24, 10, 20, 0, 0, loc)
	fmt.Printf("time2 %v\n", time2)
	//time2 2011-12-24 10:20:00 +0800 CST

	var time3 = time.Date(2011, 12, 24, 10, 20, 0, 0, time.UTC)
	fmt.Printf("time3 %v\n", time3)
	// time3 2011-12-24 10:20:00 +0000 UTC

	var now = time.Now()
	fmt.Println("year:", now.Year(), "month:", now.Month())
	// year: 2019 month: August

	var date, err = time.Parse(time.RFC3339, "2018-12-23T10:30:21Z")
	if err != nil {
		fmt.Println("error", err.Error())
		return
	}
	fmt.Println(date)
	//2018-12-23 10:30:21 +0000 UTC

	//以下方法用到比较多些
	localTime, err := time.ParseInLocation("2006-01-02 15:04:05", "2017-12-03 22:01:02", time.Local)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(localTime)
	//2017-12-03 22:01:02 +0800 CST

	fmt.Println(time.Now())                          //2019-08-21 18:43:05.35283 +0800 CST m=+0.000470403
	fmt.Println(time.Now().Sub(localTime).Seconds()) //5.4074523352835e+07
}

计时器,触发器

###time.Sleep()

package main

import "fmt"
import "time"

func main() {
	fmt.Println("start")
	//阻塞,休眠4秒后再启动
	time.Sleep(time.Second * 4)
	fmt.Println("after 4 seconds")
}

time.NewTimer()

此功能与time.Sleep()略有不同。 time.NewTimer()函数返回类型为*time.Timer的对象,该对象具有类型为channeltime.C属性。 工作原理:在指定的时间延迟后,数据将通过通道C发送。使用此函数后必须跟一个从通道C接收数据的语句.

func main() {
	//4秒后将有数据发送到timer.C通道
	var timer = time.NewTimer(4 * time.Second)
	fmt.Println("start")
	//<-timer.C代码行表示从timer.C通道接收数据。
	<-timer.C
	//由于通道接收本身是阻塞的,因此`fmt.Println("finish")1语句将在4秒后执行。
	fmt.Println("finish")
}

time.AfterFunc()

time.AfterFunc()函数有2个参数。第一个参数是计时器持续时间,第二个参数是回调。如果时间已经满足定时器持续时间,则将执行回调

package main

import "fmt"
import "time"

func main() {
	time.AfterFunc(4*time.Second, func() {
		fmt.Println("expired")
	})

	fmt.Println("start")
	fmt.Println("finish")
	time.Sleep(5 * time.Second)
}

执行:

  • time.AfterFunc本身不会阻塞
  • 先输出start和finish
  • 4秒后,输出expired
  • 程序结束 如果最后不休眠,会看不到expired,time.AfterFunc会是一个gorutine,main gorouting会先结束导致看不到exprired。

使用Ticker调度程序

package main

import (
	"fmt"
	"time"
)

func main() {
	done := make(chan bool)
	ticker := time.NewTicker(time.Second)

    // 休眠10秒后,给通道发送true
	go func() {
		time.Sleep(10 * time.Second) // wait for 10 seconds
		done <- true
	}()

    // 如果通道取到为true,停止tiker,并结束跳出
    // 否则打印出Hello 和 当前时间
	for {
		select {
		case <-done:
			ticker.Stop()
			return
		case t := <-ticker.C:
			fmt.Println("Hello !!", t)
		}
	}
}

执行:

➜  learn-golang go run main.go
Hello !! 2019-08-22 14:20:04.735523 +0800 CST m=+1.004185671
Hello !! 2019-08-22 14:20:05.736374 +0800 CST m=+2.005077374
Hello !! 2019-08-22 14:20:06.736541 +0800 CST m=+3.005285713
Hello !! 2019-08-22 14:20:07.735261 +0800 CST m=+4.004046067
Hello !! 2019-08-22 14:20:08.734633 +0800 CST m=+5.003458281
Hello !! 2019-08-22 14:20:09.735612 +0800 CST m=+6.004478660
Hello !! 2019-08-22 14:20:10.736302 +0800 CST m=+7.005209268
Hello !! 2019-08-22 14:20:11.735903 +0800 CST m=+8.004850939
Hello !! 2019-08-22 14:20:12.736244 +0800 CST m=+9.005233132
Hello !! 2019-08-22 14:20:13.735353 +0800 CST m=+10.004382172

用Goroutine做Timer

package main

import "fmt"
import "os"
import "time"

func timer(timeout int, ch chan<- bool) {
	time.AfterFunc(time.Duration(timeout)*time.Second, func() {
		ch <- true
	})
}

func watcher(timeout int, ch <-chan bool) {
	<-ch
	fmt.Println("\ntime out! no answer more than", timeout, "seconds")
	os.Exit(0)
}

func main() {
	var timeout = 5
	var ch = make(chan bool)

	go timer(timeout, ch)
	go watcher(timeout, ch)

	var input string
	fmt.Print("what is 725/25 ? ")
	fmt.Scan(&input)

	if input == "29" {
		fmt.Println("the answer is right!")
	} else {
		fmt.Println("the answer is wrong!")
	}
}

执行:

➜  learn-golang go run main.go
what is 725/25 ? 9
the answer is wrong!
➜  learn-golang go run main.go
what is 725/25 ? 
time out! no answer more than 5 seconds
➜  learn-golang go run main.go
what is 725/25 ? 29
the answer is right!

数据类型之间的转换

strconv.Atoi()

字符串转数字

package main

import "fmt"
import "strconv"

func main() {
	var str = "124"
	var num, err = strconv.Atoi(str)

	if err == nil {
		fmt.Println(num) // 124
	}
}

数字转字符串

var num = 124
var str = strconv.Itoa(num)

fmt.Println(str) // "124"

strconv.ParseInt()

解析成int

var str = "124"
//原为10进制,int64
var num, err = strconv.ParseInt(str, 10, 64)

if err == nil {
    fmt.Println(num) // 124(int64)
}

另一个demo

var str = "1010"
//原为2进制,int8
var num, err = strconv.ParseInt(str, 2, 8)

if err == nil {
    fmt.Println(num) // 10
}

strconv.FormatInt()

将int64数字数据转换为字符串

var num = int64(24)
var str = strconv.FormatInt(num, 8)
fmt.Println(str) // 30

strconv.ParseFloat()

字符串解析成浮点数float32

var str = "24.12"
var num, err = strconv.ParseFloat(str, 32)

if err == nil {
    fmt.Println(num) // 24.1200008392334
}

strconv.FormatFloat()

float格式化为字符串

var num = float64(24.12)
var str = strconv.FormatFloat(num, 'f', 6, 64)

fmt.Println(str) // 24.120000

在上面的代码中,float64类型的数据24.12被转换为格式为f, 6是转换后的小数数据长度。

strconv.ParseBool()

转为bool类型的值

var str = "true"
var bul, err = strconv.ParseBool(str)

if err == nil {
    fmt.Println(bul) // true
}

strconv.FormatBool()

var bul = true
var str = strconv.FormatBool(bul)

fmt.Println(str) // "true"

数据类型关键字可用于转换或数据类型之间的转换

var a float64 = float64(24)
fmt.Println(a) // 24

var b int32 = int32(24.00)
fmt.Println(b) // 24

string , byte 转换

var text1 = "halo"
var b = []byte(text1)

fmt.Printf("%d %d %d %d \n", b[0], b[1], b[2], b[3])
// 104 97 108 111

interface{}的转化

var data = map[string]interface{}{
    "nama":    "john wick",
    "grade":   2,
    "height":  156.5,
    "isMale":  true,
    "hobbies": []string{"eating", "sleeping"},
}

fmt.Println(data["nama"].(string))
fmt.Println(data["grade"].(int))
fmt.Println(data["height"].(float64))
fmt.Println(data["isMale"].(bool))
fmt.Println(data["hobbies"].([]string))

var data = map[string]interface{}{
		"nama":    "john wick",
		"grade":   2,
		"height":  156.5,
		"isMale":  true,
		"hobbies": []string{"eating", "sleeping"},
	}

	fmt.Println(data["nama"].(string))
	fmt.Println(data["grade"].(int))
	fmt.Println(data["height"].(float64))
	fmt.Println(data["isMale"].(bool))
	fmt.Println(data["hobbies"].([]string))

	fmt.Println("=========================")

	for _, val := range data {
		switch val.(type) {
		case string:
			fmt.Println(val.(string))
		case int:
			fmt.Println(val.(int))
		case float64:
			fmt.Println(val.(float64))
		case bool:
			fmt.Println(val.(bool))
		case []string:
			fmt.Println(val.([]string))
		default:
			fmt.Println(val.(int))
		}
	}

输出:

john wick
2
156.5
true
[eating sleeping]
=========================
true
[eating sleeping]
john wick
2
156.5

strings.HasPrefix()

var isPrefix1 = strings.HasPrefix("john wick", "jo")
fmt.Println(isPrefix1) // true

var isPrefix2 = strings.HasPrefix("john wick", "wi")
fmt.Println(isPrefix2) // false

strings.HasSuffix()

var isSuffix1 = strings.HasSuffix("john wick", "ic")
fmt.Println(isSuffix1) // false

var isSuffix2 = strings.HasSuffix("john wick", "ck")
fmt.Println(isSuffix2) // true

strings.Count()

var howMany = strings.Count("ethan hunt", "t")
fmt.Println(howMany) // 2

strings.Index()

var index1 = strings.Index("ethan hunt", "ha")
fmt.Println(index1) // 2

var index2 = strings.Index("ethan hunt", "n")
fmt.Println(index2) // 4

strings.Replace()

var text = "banana"
var find = "a"
var replaceWith = "o"

var newText1 = strings.Replace(text, find, replaceWith, 1)
fmt.Println(newText1) // "bonana"

var newText2 = strings.Replace(text, find, replaceWith, 2)
fmt.Println(newText2) // "bonona"

var newText3 = strings.Replace(text, find, replaceWith, -1)
fmt.Println(newText3) // "bonono"

strings.Repeat()

var str = strings.Repeat("na", 4)
fmt.Println(str) // "nananana"

strings.Split()

var string1 = strings.Split("the dark knight", " ")
fmt.Println(string1) // ["the", "dark", "knight"]

var string2 = strings.Split("batman", "")
fmt.Println(string2) // ["b", "a", "t", "m", "a", "n"]

strings.Join()

var data = []string{"banana", "papaya", "tomato"}
var str = strings.Join(data, "-")
fmt.Println(str) // "banana-papaya-tomato"

strings.ToLower() ,strings.ToUpper()

var str = strings.ToLower("aLAy")
fmt.Println(str) // "alay"

var str1 = strings.ToUpper("eat!")
fmt.Println(str1) // "EAT!"

最后修改于 2019-08-18