Go语法

本文详细介绍了Go语言的基础知识,包括变量、常量、内置基本类型、错误处理、数据存储、编程技巧、数组、切片、映射、流程控制、函数、并发特性等内容,帮助读者深入理解Go语法。
摘要由CSDN通过智能技术生成

Go基础

变量

基本结构:var 变量名 变量类型 = 值
注:_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃

package main

/* 全局变量 */
// 仅声明, 必要有var和变量类型
var a int
var b, c int

// 声明并初始化,变量类型可省略
var d int = 1
var e, f int = 1, 2
var g = 1 // 自动推断类型
var h, i = 1, "string" // 类型可以不一样

func main() {
    /* 局部变量特有的声明方式 */
    j := 1
    k, l := 1, 2
}

常量

常量可定义为数值、布尔值或字符串等类型。

/* 全局和局部声明方式相同, 常量通常以大写字母开头,但又不想暴露出去,可以加c或_ */
const A int = 1
const B = 1
const C, D = 1, "string" // 类型可以不一样

内置基本类型

Boolean

布尔值的类型为bool,值是true或false,默认为false。

注:不能用0和非0表示true或false

数值类型

1. 整型 
    * 分为无符号和带符号,例如:int和uint
    * 8,16,32,64位,例如:int32和uint32
    * rune是int32的别称,byte是uint8的别称
    * int和uint会根据运行平台指定,32位操作系统为int32
2. 浮点型 float32和float64
3. 复数 complex64和complex128
注:不同类型之间不能进行运算

字符串

定义
var a string 
var b string = "" 
func test() {
    no, yes := "no", "yes" 
}
修改
s := "hello"
c := []byte(s)  // 将字符串 s 转换为 []byte 类型
c[0] = 'c'
s2 := string(c)  // 再转换回 string 类型
fmt.Printf("%s\n", s2)
连接
s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)
原始格式输出

` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。

m := `hello
    world`

错误类型

Go内置有一个error类型,专门用来处理错误信息,Go的package里面还专门有一个包errors来处理错误:

err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
    fmt.Print(err)
}

Go数据底层的存储

下面这张图来源于Russ Cox Blog中一篇介绍Go数据结构的文章

一些技巧

分组声明

import(
    "fmt"
    "os"
)

const(
    i = 100
    prefix = "Go_"
)

var(
    i int
    prefix string
)

iota枚举

Go里面有一个关键字iota,这个关键字用来声明enum的时候采用,它默认开始值是0,每调用一次加1:

const(
    x = 1  // x == 1
    y = iota  // y == 1; 每定义一个常量iota就会加1
    z = iota  // z == 2
    w  // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
)

const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0

const ( 
  e, f, g = iota, iota, iota // e=0, f=0, g=0 iota在同一行值相同
)

除非被显式设置为其它值或iota,每个const分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是iota,则它也被设置为iota。

私有和公有

  • 大写字母开头的变量或函数,为公有,相当于java中的public
  • 小写字母开头的变量或函数,为私有,相当于java中的private

array

基本结构:var 变量名 [长度]类型

  • 数组长度不能改变
  • 长度也是数组类型的一部分,因此[3]int与[4]int是不同的类型
  • 当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。
  • 数组间可以通过”==”或”!=”比较,但不能用”>”等比较
var arr [10]int  // 声明了一个int类型的数组
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为123,其它默认为0
c := [...]int{4, 5, 6} // 采用`...`的方式,Go会自动计算长度
d := [...]int{1: 1, 2: 1, 3: 3} // 通过数组下标指定值,未指定的则为零值
// 数组比较
e := [...]int{1: 1, 2: 1, 3: 3}
f := [4]int{0, 1, 1, 3}
g := [3]int{0, 1, 1}
fmt.Println(e == f)
fmt.Println(e != f)
fmt.Println(e > f) // 这里会出错
fmt.Println(f == g) // 这里会出错,f和g不是一种类型
// 数组与数组的指针
h := [10]int{}
h[1] = 2
fmt.Println(h)
i := new([10]int)
i[1] = 2
fmt.Println(i)
// 多维数组
j := [2][3]int{
    {1,1,1},
    {2,2,2},
}
fmt.Println(j)

slice

声明

基本结构:var 变量名 []类型

  • slice是一个引用类型
  • slice总是指向一个底层array
<!-- 直接声明 -->
var slice []int
var slice = []int{1, 2, 3}
slice := []byte {'a', 'b', 'c'}
slice := make([]int, 3, 10) // 第一个参数:类型;第二个参数:长度;第三个参数:容量
slice := make([]int, 0) // 长度可以指定为0,容量可以省略
<!-- 从数组中截取 -->
array := [3]byte {'a', 'b', 'c'}
slice := array[1: 2] // slice通过array[i:j]来获取,其中i是数组的开始位置,j是结束位置,但不包含array[j],它的长度是j-i。
slice := array[: 2] // 取array[0]和array[1] 

内置函数

  • len 获取slice的长度
  • cap获取slice的最大容量
  • appendslice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice

copy

copy函数从源slice的src中复制元素到目标dst,并且返回复制的元素的个数

a := []int{1, 2, 3, 4, 5, 6}
b := []int{7, 8, 9}
copy(a, b)
fmt.Println(a) // 输出:[7 8 9 4 5 6]
copy(b, a)
fmt.Println(b) // 输出:[1 2 3]
copy(b[1: 3], a[4: 6])
fmt.Println(b) // 输出:[7 5 6]

长度与容量

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
s := a[0:]
s = append(s, 11, 22, 33)
sa := a[2:7]
sb := sa[3:5]
sc := sa[5:8]
fmt.Println(a, len(a), cap(a))    //输出:[1 2 3 4 5 6 7 8 9 0] 10 10
fmt.Println(s, len(s), cap(s))    //输出:[1 2 3 4 5 6 7 8 9 0 11 22 33] 13 20
fmt.Println(sa, len(sa), cap(sa)) //输出:[3 4 5 6 7] 5 8
fmt.Println(sb, len(sb), cap(sb)) //输出:[6 7] 2 5
fmt.Println(sc) // 输出:[8 9 0],虽然sa不能访问890,但依然可以从中截取出来
  • 长度为已存放个数,容量为可存放个数
  • 对数组来说,长度和容量总是相等的
  • slice的容量可以大于长度,如果容量不足,调用append时将动态分配新的数组空间
  • array[i:j:k],k - i为容量,k默认为数组长度,k值不能大于数组长度

陷阱

当slice的容量还有空闲的时候,append进来的元素会直接使用空闲的容量空间,但是一旦append进来的元素个数超过了原来指定容量值的时候,内存管理器就是重新开辟一个更大的内存空间,用于存储多出来的元素,并且会将原来的元素复制一份,放到这块新开辟的内存空间。

a := []int{1, 2, 3, 4}
sa := a[1:3]
fmt.Printf("%p\n", sa) //输出:0xc0840046e0
sa = append(sa, 11, 22, 33)
fmt.Printf("%p\n", sa) //输出:0xc084003200

参考链接

map

声明

基本结构:map[keyType]valueType

m1 := make(map[string]string)
m1["aa"] = "bb" // 添加
m2 := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
m2["C++"] = 5 // 修改
delete(m2, "C")  // 删除key为C的元素
len(m2) // 长度
s := m2["Swift"] // 由于没有key为Swift,所以s为空字符串
s, ok := m["Swift"] // ok为是否存在key为Swift的值

特点

  • map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
  • map的长度是不固定的,也就是和slice一样,也是一种引用类型
  • map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
  • map[key],有两个返回值,第一个是value,第二个是是否有对应的值,有则为true

make、new操作

make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。

  • new返回的是指针
  • make返回初始化后的(非零)值

零值

关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。 此处罗列 部分类型 的 “零值”

int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 //rune的实际类型是 int32
byte    0x0 // byte的实际类型是 uint8
float32 0 //长度为 4 byte
float64 0 //长度为 8 byte
bool    false
string  ""

流程与函数

流程控制

Go中流程控制分三大类:条件判断,循环控制和无条件跳转

if

  • 基本规则如Java类似,只是条件判断语句不需要括号
a := 5
if a < 3 {
    fmt.Println("a < 3")
} else if a < 6 {
    fmt.Println("a < 6")
} else {
    fmt.Println("a >= 6")
}
  • 允许声明一个变量,这个变量的作用域是条件逻辑块内
  • 如果if内部和外部有同名变量,if语句会使用内部声明的变量
  • if语句中声明变量,else if和else中只能写条件判断语句
// if语句中会使用内部声明的a变量,屏蔽if语句外的a变量
a := 1
if a, b := 5, 6; a < 3 {
    fmt.Println("a < 3")
} else if a < 6 { // 不能出现这样的语句: else if b := 1; a < 6
    fmt.Println("a < 6")
} else {
    fmt.Println("a >= 6")
    fmt.Println("b =", b)
}
// 这里会输出1,而不是5
fmt.Println("a =", a)
// b变量在if中声明,不能在外部使用,因此这里会报错
fmt.Println("b =", b)

for

正常for循环
  • expression1在循环开始之前调用
  • expression2是用来条件判断
  • expression3在每轮循环结束之时调用
for expression1; expression2; expression3 {
    //...
}

例子

for i, a := 0, 1; i < 3; i++{
    a++
    fmt.Println("a =", a)
}
fmt.Println("Over")
while(条件)
  • expression1, expression3可以省略
  • 其中的分号也可以省略
for expression2 {
    //...
}

例子

a := 1
for a < 3 {
    a++
    fmt.Println("a =", a)
}
fmt.Println("Over")

/* 也可以写成这样,内部声明a变量,省略expression3*/
for a := 1; a < 3; {
    a++
    fmt.Println("a =", a)
}
fmt.Println("Over")
while(true)
a := 1
for { // 这一行也可写成: for true {
    a++
    if a> 3 {
        break
    }
    fmt.Println("a =", a)
}
fmt.Println("Over")

/* 也可以写成这样,内部声明a变量,但必须加后面两个分号 */
for a:=1; ; { 
    a++
    if a> 3 {
        break
    }
    fmt.Println("a =", a)
}
fmt.Println("Over")
break和continue
/* breakcontinue与java一致 */
for index := 10; index>0; index-- {
    if index == 5{
        break // 或者continue
    }
    fmt.Println(index)
}
// break打印出来109876
// continue打印出来1098764321
range
/* for配合range可以用于读取slice和map的数据 */
// 第一个返回值是key,第二个返回值是value
// 如果是slice,那么key为下标
for k, v: = range map {
    fmt.Println("map's key:",k)
    fmt.Println("map's val:",v)
}
// key和value都是map中的copy
arr := []int{1}
fmt.Println("%p", &arr[0])
for k, v := range arr {
    fmt.Printf("%p\n", &v)
    fmt.Printf("%p\n", &k)
}

switch

正常模式
  • java不同的是,默认每个case执行完毕后会跳出switch语句
  • 如果希望继续执行下一个case,需要加入fallthrough关键字
  • 有与if语句相似的初始化表达式
switch i := 10; i {
case 1:
    fmt.Println("i is equal to 1")
case 2, 3, 4:
    fmt.Println("i is equal to 2, 3 or 4")
case 10:
    fmt.Println("i is equal to 10")
    fallthrough
default:
    fmt.Println("------------------------")
}
表达式模式
switch a := 1; {
    case a < 1:
    fmt.Println("a < 1")
    case a > 1:
    fmt.Println("a = 1")
    default :
    fmt.Println("a = 1")
}

break

正常用法:跳出循环

for i := 0; i < 5; i++ {
    if i == 2 {
        break;
    }
    fmt.Println("i =", i)
}

利用标签:跳出多层循环

LABEL1:
for {
    for i := 0; i < 5; i++ {
        if i > 2 {
            break LABEL1
        }
        fmt.Println("i =", i)
    }
}

continue

正常用法:开始下次循环

for i := 0; i < 5; i++ {
    if i == 2 {
        break;
    }
    fmt.Println("i =", i)
}

利用标签:继续执行标签层的下次循环

LABEL1:
for i := 0; i < 3; i++ {
    fmt.Println("i =", i)
    for {
        continue LABEL1
        fmt.Println("haha")
    }
    fmt.Println("hehe")
}

goto

跳转到当前函数内定义的标签(大小写敏感)位置

i := 0
Here:   //这行的第一个词,以冒号结束作为标签
fmt.Println(i)
i++
goto Here   //跳转到Here去

函数

/* 基本结构 */
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    //这里是处理逻辑代码
    //返回多个值
    return output1, output2
}

/* output1和output2,可以不声明,但必须注明返回类型*/
func funcName(input1 type1, input2 type2) (type1, type2) {

    return "", "" 
}

/* 如果只有一个返回值且不声明返回值变量,可以这样写 */
func funcName(input1 type1, input2 type2) type {

}

/* 没有返回值,可全部省略 */
func funcName(input1 type1, input2 type2) {

}

/* 前后参数类型相同,可省略前面的参数类型*/
func funcName(input1, input2 int, input3 string, input4, input5 float32) (output1, output2 int){

}

/* 官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。 */
func SumAndProduct(A, B int) (add int, Multiplied int) {
    add = A+B
    Multiplied = A*B
    // 这里也是个特别的地方
    return
}

变参

基本结构:func myfunc(arg ...int) {}

  • 参数的类型全部是int
  • 变量arg是一个int的slice
  • 必须是最后一个参数
for _, n := range arg {
    fmt.Printf("And the number is: %d\n", n)
}
// 正确,例如a(1, 2, 3),1会传递给a,2和3会给b
func a(a int, b ...int) {
    fmt.Println(a, b, c)
}
// 不正确,变参必须是最后一个参数
func a(b ...int, a int) {
    fmt.Println(a, b, c)
}
// 不正确,老老实实用slice吧
func a(a int, b ...int, c ...string) {
    fmt.Println(a, b, c)
}

传值与传指针

  • 函数的参数,传入的都是copy
  • 即使传入的是指针,也是指针的copy

指针的优点

  • 传指针使得多个函数能操作同一个对象。
  • 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
  • Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
package main
import "fmt"

//简单的一个函数,实现了参数+1的操作
func add1(a *int) int { // 请注意,
    *a = *a+1 // 修改了a的值
    return *a // 返回新值
}

func main() {
    x := 3

    fmt.Println("x = ", x)  // 应该输出 "x = 3"

    x1 := add1(&x)  // 调用 add1(&x) 传x的地址

    fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
    fmt.Println("x = ", x)    // 应该输出 "x = 4"
}

defer

  • defer语句会在函数返回前执行
func ReadWrite() bool {
    file.Open("file")
    defer file.Close()
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}
  • defer是采用后进先出模式,所以如下代码会输出4 3 2 1 0
for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

函数作为值、类型

基本结构:type typeName func(input1 inputType1 , input2 inputType2) (result1 resultType1)

  • 拥有相同参数列表和返回值列表的函数,属于同一函数类型。比如下面例子中的isOdd和isEven函数。
package main
import "fmt"

type testInt func(int) bool // 声明了一个函数类型

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

// 声明的函数类型在这个地方当做了一个参数

func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }
    }
    return result
}

func main(){
    slice := []int {1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd)    // 函数当做值来传递了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven)  // 函数当做值来传递了
    fmt.Println("Even elements of slice are: ", even)
}

Panic和Recover

  • Panic类似java中的Exception
  • Recover类似java中的catch
  • Recover仅在defer函数中有效。在正常的执行过程中,调用recover会返回nil

基本结构:

func throwsPanic() {
    defer func() {
        if x := recover(); x != nil {

        }
    }()
    // a() // 抛出Panic
    // b(1) // 运行时Panic
}

/* 抛出Panic */
func a() {
     panic("throw panic")
}

/* 运行时Panic */
func b(nums ...int) int {
    return nums[1] * nums[2]
}

import

点操作

调用的fmt.Println(“hello world”)可以省略的写成Println(“hello world”)

import(
    . "fmt"
)

别名操作

调用的fmt.Println(“hello world”)可以改写成f.Println(“hello world”)

import(
f “fmt”
)

_操作

_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。

import (
    "database/sql"
    _ "github.com/ziutek/mymysql/godrv"
)

struct

定义

type person struct {
    name string
    age int
}

声明

// 仅声明,下面例子会进一步说明
var P person
// 声明并初始化,按照顺序提供初始化值
P := person{"Tom", 25}
// 通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
// 通过new函数分配一个指针,此处P的类型为*person
P := new(person)
package main

import "fmt"

type Man struct {
    Name string
    Age  int
}

func main() {
    var man Man // 这样的声明会初始化一个空的struct,所有的字段都是零只
    fmt.Printf("%p\n", &man)
    fmt.Println(man)

    man = Man{
        Name: "Leo",
        Age:  25,
    }
    // 两次打印的地址相同
    fmt.Printf("%p\n", &man)
    fmt.Println(man)
}

匿名struct

a := &struct {
    Name string
    Age int
}{
    Name: "Leo",
    Age: 25,
}
fmt.Println(a)

// 匿名结构作为一个字段
package main

import (
        "fmt"
)

type person struct {
        Name string
        Age int
        Contact struct {
                Phone, City string
        }
}

func main() {
        a := &person{
                Name: "Leo",
                Age: 25,
        }
        a.Contact.Phone = "1234567890"
        a.Contact.City = "Beijing"
        fmt.Println(a)
}

匿名字段

type Human struct {
    name string
}

type Student struct {
    Human  // 匿名字段,那么默认Student就包含了Human的所有字段
    name string // 重载Human中的name字段
    int // 内置类型作为匿名字段
}
  • Student继承Human中的全部字段,并且可以直接访问Human中的字段,如:student.name,或student.Human.name
  • 所有的内置类型和自定义类型都是可以作为匿名字段
  • student.name会访问Student中的name字段,而不是Human中的,可以通过student.Human.name访问Human中的name字段

面向对象

基本结构:func (r ReceiverType) funcName(parameters) (results)

package main
import "fmt"

type Human struct {
}

type Student struct {
    Human //匿名字段
}

type Employee struct {
    Human //匿名字段
}

//在human上面定义了一个method,Student和Employee将继承SayHi方法
func (h *Human) SayHi() {
}

// 重写Human中的SayHi方法
func (s *Student) SayHi() {
}

interface

定义

type Man interface {
    SayHi()
}

实现

  • interface可以被任意的对象实现
  • 一个对象可以实现任意多个interface
  • 任意的类型都实现了空interface(interface{}),也就是包含0个method的interface
type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段Human
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段Human
    company string
    money float32
}

//Human对象实现Sayhi方法
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

//Human对象实现Guzzle方法
func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //此句可以分成多行
}

//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
    s.loan += amount // (again and again and...)
}

//Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// 定义interface
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}

type YoungChap interface {
    SayHi()
    Sing(song string)
    BorrowMoney(amount float32)
}

type ElderlyGent interface {
    SayHi()
    Sing(song string)
    SpendSalary(amount float32)
}

interface值

存储实现这个interface的任意类型的对象,以调用各自的方法

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段
    company string
    money float32
}

//Human实现SayHi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human实现Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

//Employee重载Human的SayHi方法
func (e Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone)
    }

// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
    SayHi()
    Sing(lyrics string)
}

func main() {
    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
    Tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

    //定义Men类型的变量i
    var i Men

    //i能存储Student
    i = mike
    fmt.Println("This is Mike, a Student:")
    i.SayHi()
    i.Sing("November rain")

    //i也能存储Employee
    i = Tom
    fmt.Println("This is Tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //定义了slice Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //这三个都是不同类型的元素,但是他们实现了interface同一个接口
    x[0], x[1], x[2] = paul, sam, mike

    for _, value := range x{
        value.SayHi()
    }
}

空interface

  • 空interface(interface{})不包含任何的method,因为所有的类型都实现了空interface
  • 空interface可以存储任意类型的数值
  • 一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数
  • 一个函数返回interface{},那么也就可以返回任意类型的值
// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

interface函数参数

interface变量存储的类型

嵌入interface

反射

并发

goroutine

文本文件处理

模板处理

模板函数

baseBytes, _ := ioutil.ReadFile("views/home.html")
t := template.New("as").Funcs(template.FuncMap{"formatTime": formatTime})
//  t, _ = t.ParseFiles("views/home.html")  // 使用t.ParseFiles方法时,模板函数调用错误,不知道为什么
t, _ = t.Parse(string(baseBytes))
t.Execute(w, data)  

// 调用
{{formatTime .}}
或者
{{. | formatTime}}

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值