Go初学入门之官方文档摘要

不错的教程

package main  //程序启动于main包

import (
	"fmt"
	"math/rand"  //包的名字与引入路径的最后一个元素相同
)
import "math"

func main() {
	fmt.Println("My favorite number is", rand.Intn(10))  //包的导出名字是以大写开头的
}

函数

package main

import "fmt"

func add(x int, y int) int, int {    //函数参数类型在后,其中x、y类型相同,可以省略第一个int
	return x + y, x  //返回类型在参数括号后
}

func split(sum int) (x, y int) {  //可以提前声明要返回的变量名
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(add(42, 13))
}

defer:在包含函数return时执行,不过函数参数是直接初始化的,只不过延后调用
func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

// defer押入栈中,按照先入后出规则调用
for i := 0; i < 10; i++ {
		defer fmt.Println(i)
}
函数作为参数:
func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}
hypot := func(x, y float64) float64 {
	return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))

fmt.Println(compute(hypot))
函数闭包:
func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()  //每一个adder是独立的
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),  //pos中的sum使用的同一个引用
			neg(-2*i),
		)
	}
}

0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

变量

变量类型:

bool

string   //字符串(String)是 byte 的集合。RuneCountInString 返回字符串中 Unicode 字符的个数,而 len 返回字符串中 byte 的个数,注意两者的区别。

// int, uint, and uintptr 大小依赖于系统,在32位上为32,在64位上位64.无特殊情况,优先使用int
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128

变量声明:

var i, j int   //声明变量,默认初始化(0、false、""),引用类型初始化默认为nil

var i, j int = 1, 2  //带初始化

c, python, java := true, false, "no!"  //函数内简洁声明,在函数外每个声明以func var等开头,所以这种方法不可行

var (   //批量声明定义
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
)

类型转化和推断:

类型转化:Go是强类型的语言,没有隐式的类型提升和转换。
var i int = 42
var f float64 = float64(i)  //转化必须显式,不能除去float64
var u uint = uint(f)

类型推断:var和:=都能实现类型的推导
var i int
j := i // j is an int,如果右边类型已经声明,那么左边类型相同

i := 42           // int
f := 3.142        // float64   //推断类型取决于常量的精度
g := 0.867 + 0.5i // complex128

常量:
1、常量的值必须在编译期间确定。因此不能将函数的返回值赋给常量,因为函数调用发生在运行期。
2、编译器可以根据字面值常量的表现形式来确定它的默认类型,比如 5.6 表现为浮点数,因此它的默认类型为 float64 ,而 “Sam” 表现为字符串,因此它的默认类型为 stirng。

const常量:
const Pi = 3.14  //不可通过:=获取
const hello = "Hello World"   //"Hello World" 赋给一个名为 hello 的常量。不过该常量是没有类型的。无类型常量有一个默认的类型,当且仅当代码中需要无类型常量提供类型时,它才会提供该默认类型。
const typedhello string = "Hello World"    //有类型常量

// 数字常量的精度依赖于其上下文
var a = 5.9/8   //语法上 5.9 是一个浮点数,8 是一个整数。因为它们都是数值常量,因此 5.9/8 是合法的。相除的结果为 0.7375,是一个浮点数。因此变量 a 的类型为浮点型。
const (
	Big = 1 << 100
	Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}
func main() {
	fmt.Println(needInt(Big))  //用于int
	fmt.Println(needFloat(Small))   //用于float
	fmt.Println(needFloat(Big))   //用于float
}
var defaultName = "Sam" //allowed,提供默认类型string
type myString string
var customName myString = "Sam" //allowed,customName为myString类型,可以将无类型常量赋值给它。
customName = defaultName //not allowed,defaultName为string类型,customName为myString类型。Go的强类型机制不允许将一个类型的变量赋值给另一个类型的变量。

指针:

变量指针:go指针不支持算术操作
var p *int  //指针的空值为 nil 。
i := 42
p = &i
fmt.Println(*p) // read i through the pointer p
*p = 21         // set i through the pointer p
*p++
可以使用指针传递数组,但尽量使用切片
func modify(arr *[3]int) {  
    (*arr)[0] = 90
}
func modify(arr *[3]int) {  
    arr[0] = 90   // a[x] 是 (*a)[x] 的简写
}
modify(&a)

结构体及容器

结构体:

结构体struct:
type Vertex struct {
	X int
	Y int
}

v := Vertex{1, 2}
p := &v
p.X = 1e9 

结构体初始化:
var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

数组:
1、在 Go 中数组是值类型而不是引用类型。这意味着当数组变量被赋值时,将会获得原数组的拷贝。
2、数组的长度是数组类型的一部分。因此 [5]int 和 [25]int 是两个不同类型的数组。
3、如果将数组作为参数传递给函数,仍然是值传递,在函数中对(作为参数传入的)数组的修改不会造成原数组的改变。

数组array:长度不可修改,需提供长度
var a [2]string   //所有元素被自动赋值为空字符串
a[0] = "Hello"
fmt.Println(a[0], a[1])

primes := [6]int{2, 3, 5, 7, 11, 13}
a := [...]int{12, 78, 50}   //自动推导数组长度
a := [3]int{12}   //只会第一个元素赋值为12,其他默认为0
a := [2][2]string{{"lion", "tiger"},{"cat", "dog"},}  //多维数组


a := [...]string{"USA", "China", "India", "Germany", "France"}
b := a    // b是a的一份copy,而不是引用
b[0] = "Singapore"  //a[0] 不会改变

切片:
Golang教程:数组和切片
slice结构形式:
在这里插入图片描述
1、切片本身不包含任何数据。它仅仅是底层数组的一个上层表示。对切片进行的任何修改都将反映在底层数组中。当若干个切片共享同一个底层数组时,对每一个切片的修改都会反映在底层数组中。
2、切片的长度是指切片中元素的个数。切片的容量是指从切片的起始元素开始到其底层数组中的最后一个元素的个数。
3、切片的长度可以动态的改变(最大为其容量)。任何超出最大容量的操作都会发生运行时错误。
4、当将一个切片作为参数传递给一个函数时,虽然是值传递,但是指针始终指向同一个数组。因此将切片作为参数传给函数时,函数对该切片的修改在函数外部也可以看到。

切片slice:动态长度的,弹性视图数组
var s []int = primes[1:4]  //左闭右开,s的修改也会影响primes,s不拷贝数据
q := []int{2, 3, 5, 7, 11, 13}  //直接创建切片:如果是array,那需要提供长度6,而slice不需要。注意此切片容量为6.
s := []struct {
	i int
	b bool
}{
	{2, true},
	{3, false},
}

可省略高低边界,以下效果相同:
a[0:10]
a[:10]
a[0:]
a[:]

slice长度和容量:
len(s)  //slice包含元素长度
cap(s)  //array中从slice第一个元素开始到最后一个元素数量

var s []int  等于  nil
s = append(s, 1)  //切片的 0 值为 nil。一个 nil 切片的长度和容量都为 0。可以利用 append 函数给一个 nil 切片追加值。
food := append(veggies, fruits...)  //可以使用 ... 操作符将一个切片追加到另一个切片末尾.

利用slice创建动态数组:
a := make([]int, 5)  // len(a)=5    使用 make 函数将会创建一个数组并返回它的切片。
b := make([]int, 0, 5) // len(b)=0, cap(b)=5   默认值0

b = b[:cap(b)] // len(b)=5, cap(b)=5   改变切片长度,小心这个地方,len变成了5
b = b[1:]      // len(b)=4, cap(b)=4

slice包含slice:
board := [][]string{
	[]string{"_", "_", "_"},
	[]string{"_", "_", "_"},
	[]string{"_", "_", "_"},
}
board[0][0] = "X"

append slice:当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍
s = append(s, 1)
s = append(s, 2, 3, 4)

特别注意在一个函数内部append的时候,有时修改了函数外的数组,有时会生成一个新的数组。
golang slice map 的一些常见坑

范围for Range:注意for range遍历的是副本
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
}

for i, _ := range pow
for _, value := range pow
for i := range pow  // 跳过第二个value变量
//只要切片存在于内存中,数组就不能被垃圾回收。 通过切片赋值的方式释放原有的切片。也可以方式对切片的修改影响到原数组。
func countries() []string {  
    countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
    neededCountries := countries[:len(countries)-2]
    countriesCpy := make([]string, len(neededCountries))
    copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
    return countriesCpy
}
Go slice的覆盖问题:
func main() {
    a := make([]int, 2, 2)
    a[0], a[1] = 1, 2

    b := append(a[0:1], 3)
    c := append(a[1:2], 4)

    fmt.Println(b,c)
}
解决方法1:copy。copy复制的元素个数其实是min(len(dst), len(src))。如果dst是一个len=0的slice是无法复制进去的。
func main() {
    a := make([]int, 2, 2)
    a[0], a[1] = 1, 2

    b := make([]int, 1)
    copy(b, a[0:1])
    b = append(b, 3)

    c := make([]int, 1)
    copy(c, a[1:2])
    c = append(c, 4)

    fmt.Println(b, c)
}
解决方法二:强制capacity
func main() {
    a := make([]int, 2, 2)
    a[0], a[1] = 1, 2

    b := append(a[0:1:1], 3)    //a[0:1:1] (源[起始index,终止index,cap终止index])
    c := append(a[1:2:2], 4)

    fmt.Println(b, c)
}

字典:
注意使用range遍历map

字典map:
var m map[string]Vertex   //无值等于nil
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
	40.68433, -74.39967,
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},   //略去vertex
	"Google":    {37.42202, -122.08408},
}

增删改查:
m[key] = elem
elem = m[key]
delete(m, key)
elem, ok = m[key]  //elem、ok需先声明。如果key在map,ok为true,反之为false,此时elem为对应类型的0值
elem, ok := m[key]

流程控制

for循环:
for i := 0; i < 10; i++ {
		sum += i
}

sum := 1
for ; sum < 1000; {   //可省略
	sum += sum
}

sum := 1
for sum < 1000 {  //类似于while
	sum += sum
}

for {  //永久循环
}
if条件:
if x < 0 {
	return sqrt(-x) + "i"
}

if v := math.Pow(x, n); v < lim {  //可声明变量
	return v
}else{
}
switch分支:
switch os := runtime.GOOS; os {  //可声明变量
case "darwin": 
	fmt.Println("OS X.")   
case "linux":       //case不可为constants,且不是integers,可为func
	fmt.Println("Linux.")
default:
	fmt.Printf("%s.\n", os)  //不需要break,只会执行一个case
}

switch {   //switch不加condition,直接进入。可用于if then else简洁版
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
}

类实现

1、不能将声明在另一个包里的type在当前包里绑定方法
2、选择pointer receiver有两个原因:需要修改receiver的值,对于大的struct避免value receiver的copy。优先使用这种方式。
3、绑定在一个类型上的所有方法应该要么全是value receiver,要么全是 pointer receiver。而不是二者混用,否则会在interface上出现问题。

结构体上绑定方法:
type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

类型上绑定方法:
type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}
func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

value receiver是在vertex变量的copy上操作的,而Pointer receivers是可以修改变量值的
type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {   //value receiver
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {   //Pointer receivers
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)   //go会自动的转换为(&v).Scale(10);
	// 使用p := &v     p.Scale(10)    p.Abs()也行,Go会自动转换为(*p).Abs()。
	fmt.Println(v.Abs()) 
}

重写成函数形式:
type Vertex struct {
	X, Y float64
}

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

interface接口

1、interface的隐式implements,实现了接口和类的分离
2、interface的值可以理解为一个tuple形式的(value, type)

interface实现:
type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	a = v  //错误,因为Vertex类型并没有实现该方法。因此我们需尽量保证同一个type的所有绑定方法的receiver相同

	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

interface 的nil问题:hold nil是并不代表interface变量是nil;没有hold值的interface才是nil
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {   // hold的是nil,传进来的t也是nil
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T   // nil
	i = t
	describe(i)
	i.M()  //即使hold一个nil,仍然可以正常调用,因为i不是nil

	i = &T{"hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}


// (<nil>, *main.T)
//  <nil>
// (&{hello}, *main.T)
// hello
空interface:能够hold任何类型变量,常用于处理未知类型的变量
func main() {
	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}

// (<nil>, <nil>)
// (42, int)
// (hello, string)
类型assertions:对未知类型进行判断
func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // panic
	fmt.Println(f)
}

// hello
// hello true
// 0 false
// panic: interface conversion: i

批量测试:
func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}
println的打印原理:
import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

fmt的Println会将a赋予类型
type Stringer interface {
    String() string
}
然后调用方法String()打印变量值
error实现:
package main

import (
	"fmt"
	"time"
)

type MyError struct {
	When time.Time
	What string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s",
		e.When, e.What)
}

func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}
reader实现:
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}

n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
image interface:
package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0, 0).RGBA())
}

concurrency并发

1、启动的线程函数是在当前的goroutine对函数参数evaluation的,然后运行在一个新的goroutine中;
2、goroutines共享的同一空间,因此访问共享内存时需要加锁;

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")  //建一个goroutine线程
	say("hello")  //启动线程go
}

信息传递channel:
ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and
           // assign value to v.
对于map、slice类型,在使用channel之前必须开辟空间:
ch := make(chan int)
可以设置channel的buffer大小,当写入已满channel时将会阻塞,同理当读取为空channel时也会阻塞:
ch := make(chan int, 100)

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}
关闭channel,测试channel是否关闭;
channel通常不需要手动关闭close,只有当需要告诉接受者已经写入所有数据时,才推荐使用。
func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)  //写完关闭,关闭后的channel不能再发送数据
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {   //for i := range c自动检测是否关闭
		fmt.Println(i)
	}
}

测试方式:
v, ok := <-ch  // 当ch关闭时ok为false

select选择可运行的goroutine,多个时随机选择
package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:   //2、最初quit为空,运行该case
			x, y = y, x+y
		case <-quit:  //最初quit为空,不会被选择运行。5、拿到quit数据,退出循环
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)  //闭包函数,直接拿到c,quit。1、最开始由于fibo函数没有写入,一直阻塞,3、读取数据
		}
		quit <- 0  //4、写完读完c后,开始quit
	}()
	fibonacci(c, quit)
}


当没有case准备好时,select可以运行default:
select {
case i := <-c:
    // use i
default:
    // receiving from c would block
}
同一变量的mutex加锁:
package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

其他

new与make:
对于基本类型,已经默认为变量分配好内存及初始值,但是对于引用类型,其初识时为nil,也就是不会自动分配内存,需手工。

//错误示例:i为指针类型,在没有分配内存的情况下是不能直接赋值的。
func main() {
	var i *int
	*i=10  //编译器返回panic错误
	fmt.Println(*i)
}
//使用new分配内存
func new(Type) *Type   //new函数返回类型的指针类型
只接受一个参数,是一个类型,分配好内存后,返回一个指向该类型内存地址的指针。注意它同时把分配的内存置为零,也就是类型的零值。

func main() {
	var i *int
	i=new(int)
	*i=10
	fmt.Println(*i)
}
//使用make分配内存
func make(t Type, size ...IntegerType) Type  //make函数返回类型
只用于chan、map以及slice的内存创建,返回的类型就是这三个类型本身,而非指针类型,因为这三种类型就是引用类型,所以没必要返回指针类型。注意,因为这三种类型是引用类型,所以必须得初始化,但不是置为零值。

1. make(map[string]string)  //用于map和chan类型
2. make([]int, 2)  //用于slice,2为slice长度
3. make([]int, 2, 4)  //用于slice,2为size,4为capacity

继承:

NIL:
1、nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值。在Go中,如果声明一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。
2、nil并不是Go的关键字之一,你甚至可以自己去改变nil的值。
3、一个为nil的slice,除了不能索引外,其他的操作都是可以的。
4、对于nil的map,可以简单看成是一个只读的map,不能进行写操作,否则就会panic。
5、关闭一个nil的channel会导致程序panic。
6、interface并不是一个指针,它的底层实现由两部分组成,一个是类型,一个值,也就是类似于:(Type, Value)。只有当类型和值都是nil的时候,才等于nil。

bool      -> false                              
numbers -> 0                                 
string    -> ""      

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值