特有语法

Goland运行测试问题

运行测试显示 No tests were run

../../../_images/%E9%97%AE%E9%A2%98SQL.png ../../../_images/NoTestsWereRun.png

花了几小时排除后发现是Gorm的Debug方法导致,去掉之后又正常了

../../../_images/%E6%AD%A3%E5%B8%B8SQL.png ../../../_images/%E6%AD%A3%E5%B8%B8%E6%B5%8B%E8%AF%95.png

chan(信道)

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。

ch <- v    // 将 v 发送至信道 ch。
v := <-ch  // 从 ch 接收值并赋予 v。

“箭头”就是数据流的方向。

创建chan

// 双项通道,支持读写
ch := make(chan int)
// 单项通道,只能读
var ch_read <-chan int
// 单项通道,只能写
var ch_write chan<- int

定义只读和只写的channel意义不大,一般用于在参数传递中

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。 这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

遍历chan

原文链接:https://blog.csdn.net/zhaominpro/article/details/77584534

读channel时,第二个值返回为false时表示channel被关闭。

方法一:

for{
    if value,ok:=<-ch;ok{
        //do somthing
    }else{
        break;//表示channel已经被关闭,退出循环
    }
}

方法二:

//range
ch:=make(chan int ,3)
ch<-1
ch<-2
ch<-3

for value:=range ch{
    fmt.Print(value)
}

//输出:123
//然后会一直阻塞当前协程,如果在其他协程中调用了close(ch),那么就会跳出for range循环。这也就是for range的特别之处

goto

将控制转移到被标记的语句

func main() {
    var C, c int //声明变量
    C = 1        /*这里不写入for循环是因为for语句执行之初会将C的值变为1,当goto A时for
    语句会重新执行(不是重新一轮循环) */
LOOP:
    for c < 50 {
        C++ //C=1不能写入for这里就不能写入
        for c = 2; c < C; c++ {
            if C%c == 0 {
                goto LOOP //若发现因子不是素数
            }
        }
        fmt.Printf("%d \t", c)
    }
}

select

select 语句使一个 Go 程可以等待多个通信操作。

select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。

/* 摘自: https://gobyexample.com/select */
package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		time.Sleep(1 * time.Second)
		c1 <- "one"
	}()
	go func() {
		time.Sleep(2 * time.Second)
		c2 <- "two"
	}()

	// 使用select同时等待值,当到达值打印起值
	for i := 0; i < 2; i++ {
		select {
		case msg1 := <-c1:
			fmt.Println("received", msg1)
		case msg2 := <-c2:
			fmt.Println("received", msg2)
		}
	}
}

interface

package main

import (
	"fmt"
)

type Phone interface {
	call()
}

type AndroidPhone struct {
}
type IPhone struct {
}

func (a AndroidPhone) call() {
	fmt.Println("我是安卓手机,可以打电话了")
}

func (i IPhone) call() {
	fmt.Println("我是苹果手机,可以打电话了")
}

func main() {
	//定义接口类型的变量
	var phone Phone
	phone = new(AndroidPhone)
	fmt.Printf("%T,%v,%p \n", phone, phone, &phone)
	phone.call()
	phone = AndroidPhone{}
	fmt.Printf("%T,%v,%p \n", phone, phone, &phone)
	phone.call()
	phone = new(IPhone)
	fmt.Printf("%T,%v,%p \n", phone, phone, &phone)
	phone.call()
	phone = IPhone{}
	fmt.Printf("%T,%v,%p \n", phone, phone, &phone)
	phone.call()
}

关于duck typing

  • duck typing是描述事物的外部行为而非内部结构。

  • duck typing关注的不是对象的类型本身,而是它是如何使用的。

空接口

  • 空接口中没有任何方法。任意类型都可以实现该接口。空接口这样定义:interface{},也就是包含0个方法(method)的interface。

  • 空接口可表示任意数据类型,类似于Java中的object。

空接口常用于以下情形。

  • println的参数就是空接口。

  • 定义个map:key是string,value是任意数据类型。

  • 定义一个切片,其中存储任意类型的数据。

package main

import (
    "fmt"
)

type A interface {
}

type Cat struct {
    name string
    age  int
}

type Person struct {
    name string
    sex  string
}

func main() {
    var a1 A = Cat{"Mimi", 1}
    var a2 A = Person{"Steven", ""}
    var a3 A = "Learn golang with me!"
    var a4 A = 100
    var a5 A = 3.14
    fmt.Println("-----------------")
    //1.fmt.println参数就是空接口
    fmt.Println("println的参数就是空接口,可以是任何数据类型", 100, 3.14, Cat{"旺旺", 2})
    //2.定义map,value是任何数据类型
    map1 := make(map[string]interface{})
    map1["name"] = "Daniel"
    map1["age"] = 13
    map1["height"] = 1.71
    fmt.Println(map1)
    fmt.Println("-----------------")
    //3.定义一个切片,其中存储任意数据类型
    slice1 := make([]interface{}, 0, 10)
    slice1 = append(slice1, a1, a2, a3, a4, a5)
    fmt.Println(slice1)
}

接口对象类型

/*
instance,ok:=接口对象.(实际类型)

如果该接口对象是对应的实际类型,那么instance就是转型之后的对象,ok的值为true,配合
if...else if...语句使用。

接口对象转型第二种方式示例如下。
接口对象.(type)
*/
//样例:接口对象转型。
package main

import (
    "fmt"
    "math"
)

//1.定义接口
type Shape interface {
    perimeter() float64
    area() float64
}

//2.矩形
type Rectangle struct {
    a, b float64
}

//3.三角形
type Triangle struct {
    a, b, c float64
}

//4.圆形
type Circle struct {
    radius float64
}

//定义实现接口的方法
func (r Rectangle) perimeter() float64 {
    return (r.a + r.b) * 2
}
func (r Rectangle) area() float64 {
    return r.a * r.b
}
func (t Triangle) perimeter() float64 {
    return t.a + t.b + t.c
}
func (t Triangle) area() float64 {
    //海伦公式
    p := t.perimeter() / 2 //半周长
    return math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))
}
func (c Circle) perimeter() float64 {
    return 2 * math.Pi * c.radius
}
func (c Circle) area() float64 {
    return math.Pow(c.radius, 2) * math.Pi
}

//接口对象转型方式1
//instance,ok:=接口对象.(实际类型)
func getType(s Shape) {
    if instance, ok := s.(Rectangle); ok {
        fmt.Printf("矩形:长度%.2f,宽度%.2f,", instance.a, instance.b)
    } else if instance, ok := s.(Triangle); ok {
        fmt.Printf("三角形:三边分别:%.2f,%.2f,%.2f,", instance.a, instance.b, instance.c)
    } else if instance, ok := s.(Circle); ok {
        fmt.Printf("圆形:半径%.2f,", instance.radius)
    }
}
//接口对象.(type),配合switch和case语句使用
func getType2(s Shape) {
    switch instance := s.(type) {
    case Rectangle:
        fmt.Printf("矩形:长度为%.2f,宽为%.2f,\t", instance.a, instance.b)
    case Triangle:
        fmt.Printf("三角形:三边分别为%.2f,%.2f,%.2f,\t", instance.a, instance.b, instance.c)
    case Circle:
        fmt.Printf("圆形:半径为%.2f,\t", instance.radius)
    }
}

func getResult(s Shape) {
    getType2(s)
    fmt.Printf("周长:%.2f,面积:%.2f \n", s.perimeter(), s.area())
}
func main() {
    var s Shape
    s = Rectangle{3, 4}
    getResult(s)
    showInfo(s)
    s = Triangle{3, 4, 5}
    getResult(s)
    showInfo(s)
    s = Circle{1}
    getResult(s)
    showInfo(s)
    x := Triangle{3, 4, 5}
    fmt.Println(x)
}

func (t Triangle) String() string { //实现了系统接口,最后的打印部分会改变
    return fmt.Sprintf("Triangle对象,属性分别为:%.2f,%.2f,%.2f", t.a, t.b, t.c)
}

func showInfo(s Shape) {
    fmt.Printf("%T,%v \n", s, s)
    fmt.Println("----------------")
}

指针

  • 指针是存储另一个变量的内存地址的变量。

  • 例如:变量b的值为156,存储在内存地址0x1040a124。变量a持有b的地址,则a被认为指向b。

  • 在Go语言中使用取地址符(&)来获取变量的地址,一个变量前使用&,会返回该变量的内存地址。

  • Go语言指针的最大特点是:指针不能运算(不同于C语言)

package main

import "fmt"

// 声明指针
var ip *int     //指向整型的指针
var fp *float32 //指向浮点型的指针

func main() {
	//声明实际变量
	var a int = 120
	//声明指针变量
	var ip *int
	//给指针变量赋值,将变量a的地址赋给ip
	ip = &a
	//打印ip的类型和值
	fmt.Printf("ip的类型是%T,值是%v \n", ip, ip)
	//打印变量*ip的类型和值
	fmt.Printf("*ip变量的类型是%T,值是%v \n", *ip, *ip)

	// 空指针
	// 当一个指针被定义后没有分配到任何变量时,它的值为nil。nil指针也成为空指针。nil在概念上和其他语言的 null、None、NULL一样,都指代零值或空值。

	// 通过指针修改变量的值
	b := 3158
	c := &b
	*c++
	fmt.Println("b的新值:", b)

	// 指针的指针
	/*
		如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。当定义一个指向指针的指针变量
		时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址。
	*/
	var d int
	var ptr *int
	var pptr **int
	a = 1234
	/* 指针ptr地址 */
	ptr = &d
	fmt.Println("ptr", ptr)
	/*指向指针ptr的地址*/
	pptr = &ptr
	fmt.Println("pptr", ptr)
	/*获取pptr的值*/
	fmt.Printf("变量a=%d\n", d)
	fmt.Printf("指针变量 *ptr=%d\n", *ptr)
	fmt.Printf("指向指针的指针变量**ptr=%d\n", **pptr)
}

new

https://golang.google.cn/ref/spec#Allocation

package main

import "fmt"

type S struct { a int; b float64}

func main() {
    s := new(S)
    // 指针类型
    fmt.Println(s)
}