Go语言基础语法(二)

本文详细介绍了Go语言中的数组、切片和Map的基本概念及操作。数组是值类型,传入函数时会被拷贝,而切片作为引用类型,传递时不拷贝。切片可以通过扩展和reslice操作来改变大小。Map的定义、操作和遍历也进行了说明,包括键值对的添加、删除和检查。此外,还讨论了Go语言中结构体、方法的封装以及依赖管理的不同阶段:GOPATH、GOVENDOR和gomod。
摘要由CSDN通过智能技术生成

目录

1.数组

2.切片

2.1切片定义

2.2Reslice

2.3切片的扩展

 2.4向切片添加元素

2.5slice ops

3.Map

3.1map的定义

3.2map的操作

3.3map的一个例题

4.字符和字符串的处理

5.结构体和方法

6.封装

7.扩展已有类型

8.go语言的依赖管理

8.1 GOPATH

8.2GOVENDOR

8.3go mod


1.数组

定义数组的几种方法:

var arr1 [5]int                 //定义一个元素为5个的数组
arr2 := [3]int{1, 3, 5}         //用:=定义一个元素为3个的数组,这里要将元素写出来
arr3 := [...]int{2, 3, 4, 5, 3} //[...]表示这个数组的长度是不固定的
var grid [4][5]int

fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)

遍历数组:

    //遍历数组
	for i, v := range arr3 { //i是数组下标,v是数组的值
		fmt.Println(i, v)
	}

	//若不想要下标,只想取值
	for _, v := range arr3 { //i是数组下标,v是数组的值
		fmt.Println(v)
	}

在go语言中,数组是值类型。

这就意味着在传入函数的时候,会被拷贝一遍,函数内的变动不会改变外面的值。

[10]int 和[20]int是不同的类型

func printArrary(arr [5]int) {
	arr[0] = 100
	for i, v := range arr { //i是数组下标,v是数组的值
		fmt.Println(i, v)
	}
}

fmt.Println("printArrary(arr1)")
printArrary(arr1)//这里第一个元素会变成100
fmt.Println("printArrary(arr3)")
printArrary(arr3)//这里第一个元素会变成100
fmt.Println("printArrary(arr1,arr3)")
fmt.Println(arr1, arr3)//这里两个数组的元素不会改变

那么要是想让传入的数组能够被函数改变,如何做呢?

其中一个办法是传入数组指针:
 

func printArraryptr(arr *[5]int) {
	arr[0] = 100
	for i, v := range arr { //i是数组下标,v是数组的值
		fmt.Println(i, v)
	}
}



	fmt.Println("printArraryptr(&arr1)")
	printArraryptr(&arr1)//第一个元素会编程100
	fmt.Println("printArraryptr(&arr3)")
	printArraryptr(&arr3)//第一个元素会变成100
	fmt.Println("fmt.Println(arr1, arr3)")
	fmt.Println(arr1, arr3)//两个数组的第一个元素都会变成100

但是go语言一般不用数组和数组指针当传入参数,因为太麻烦,一般是用切片。

到目前为止,以上第一部分的所有代码如下:

package main

import "fmt"

func printArrary(arr [5]int) {
	arr[0] = 100
	for i, v := range arr { //i是数组下标,v是数组的值
		fmt.Println(i, v)
	}
}

func printArraryptr(arr *[5]int) {
	arr[0] = 100
	for i, v := range arr { //i是数组下标,v是数组的值
		fmt.Println(i, v)
	}
}

func main() {
	var arr1 [5]int                 //定义一个元素为5个的数组
	arr2 := [3]int{1, 3, 5}         //用:=定义一个元素为3个的数组,这里要将元素写出来
	arr3 := [...]int{2, 3, 4, 5, 3} //[...]表示这个数组的长度是不固定的
	var grid [4][5]int

	fmt.Println(arr1, arr2, arr3)
	fmt.Println(grid)

	//遍历数组
	for i, v := range arr3 { //i是数组下标,v是数组的值
		fmt.Println(i, v)
	}

	//若不想要下标,只想取值
	for _, v := range arr3 { //i是数组下标,v是数组的值
		fmt.Println(v)
	}
	fmt.Println("printArrary(arr1)")
	printArrary(arr1)
	fmt.Println("printArrary(arr3)")
	printArrary(arr3)
	fmt.Println("printArrary(arr1,arr3)")
	fmt.Println(arr1, arr3)

	fmt.Println("printArraryptr(&arr1)")
	printArraryptr(&arr1)
	fmt.Println("printArraryptr(&arr3)")
	printArraryptr(&arr3)
	fmt.Println("fmt.Println(arr1, arr3)")
	fmt.Println(arr1, arr3)

}

2.切片

2.1切片定义

arr:=[...]int{0,1,2,3,4,5,6,7}
s:=arr[2:6]

//s就是切片,s的值是[2 3 4 5]
//在计算机里面[2:6]一般是包前不包后,也就是包含下标为2的但不包含为6的。

fmt.Println("arr[:6]", arr[:6])
fmt.Println("arr[2:]", arr[2:])
fmt.Println("arr[:]", arr[:])


//arr[2:6]: [2 3 4 5]
//arr[:6]: [0 1 2 3 4 5]
//arr[2:]: [2 3 4 5 6 7]
//arr[:]:[0 1 2 3 4 5 6 7]

切片不是值类型,所以传入函数的时候不是拷贝:

func updateSlice(s []int) {
	s[0] = 100
}


s1 := arr[2:]   //[2 3 4 5 6 7]
updateSlice(s1)
fmt.Println(s1)//[100 3 4 5 6 7]
fmt.Println(arr)//[0 1 100 3 4 5 6 7]

2.2Reslice

切片是可以反复取的(Reslice):

	fmt.Println("Reslice")//Reslic
	s2 := arr[:]
	fmt.Println(s2)//[0 1 100 3 4 5 6 7]
	s2 = s2[:5]
	fmt.Println(s2)//[0 1 100 3 4]
	s2 = s2[2:]
	fmt.Println(s2)//[100 3 4]

2.3切片的扩展

切片还可以扩展:

fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
s1 = arr[2:6]
s2 = s1[3:5]
fmt.Println("s1", s1)
fmt.Println("s2", s2)


//s1: [2 3 4 5],这没什么好说的
//s2,[5 6],这里s1明明没有s[4]这个元素如何取出来的呢?

s2如何能取出s[4]呢?

原因是切片是数组的一个视图,s1取数组下标为2-5的元素。

所以s1的下标为s[0]-s[3],但这时候s1是能够知道有s[4](为6),s[5](为7)的

只不过要是硬取是取不出来的,但是用s2对其取切片是能够取到的。

这就是切片扩展:

那么slice是如何实现的呢:

 首先这个slice是有一个指针指向array的其中一段的开始,然后len是本切片可以直接访问的长度,cap是记录了指针到结尾的长度,所以可以实现切片扩展,只要不超过cap的长度即可。

 slice是可以向后扩展的,但是不可以向前扩展。

s[i]不可以超过len(s),向后扩展不可以超越底层数组cap(s)

fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
fmt.Println("arr=", arr)
s1 = arr[2:6]
s2 = s1[3:5]
fmt.Printf("s1=%v,len(s1)=%d,cap(s1)=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2=%v,len(s2)=%d,cap(s2)=%d\n", s2, len(s2), cap(s2))

结果:

 2.4向切片添加元素

s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println("s3,s4,s5=", s3, s4, s5)
//s2的cap是3,[5 6]后面隐藏了一个7,这时候s3执行的时候,将7换成了10
//s4,s5后面再添加就超过了cap,这时候go语言会在内部新建一个arr,也就是说s4和s5不再view当前arr了
fmt.Println("arr=", arr)

结果:

添加元素时如果超越了cap,系统会重新分配更大的底层数组,以便于能够执行添加元素的操作,这个更大的底层数组会将原来的数组拷贝过去,那么原来的数组如果程序还用到了就保留,如果程序没用到了,那么go语言的垃圾回收机制会将原来的数组回收掉。

由于值传递的髋膝,必须接收append的返回值,因为添加元素后,slice的len可能改变,cap也可能改变(创建了更大的底层数组后),所以必须用一个新的切片来接收这个append的返回值。

2.5slice ops

 定义一个切片:

	var s []int //声明一个切片,这时候s==nil
	for i := 0; i < 100; i++ {
		s = append(s, 2*i)
	}
	fmt.Println(s)

那么这样定义的slice,它的len和cap是多少呢:

func printSlice(s []int) {
	fmt.Printf("len=%d,cap=%d\n", len(s), cap(s))
}
func main() {
	var s []int //声明一个切片,这时候s==nil
	for i := 0; i < 100; i++ {
		printSlice(s)
		s = append(s, 2*i)
	}
	fmt.Println(s)
}

结果为:

可以看到,len是一个一个增加,cap每次装不下了会成倍增加。

还可以这样创建slice:

	s1 := []int{2, 4, 6, 8}
	printSlice(s1)
	s2 := make([]int, 16) //创建len为16,cap也是16的slice
	printSlice(s2)
	s3 := make([]int, 16, 32) //创建len为16,cap为32的slice
	printSlice(s3)

 复制slice:

	fmt.Println("Copying slice")
	copy(s2, s1) //把s1copy给s2
	printSlice(s2)

删除slice中间的元素:

	fmt.Println("Deleting elements from slice")
	s2 = append(s2[:3], s2[4:]...)
	printSlice(s2)

删除头元素:

	fmt.Println("Popping from front")
	front := s2[0]
	s2 = s2[1:]
	fmt.Println(front)

删除尾元素:

	fmt.Println("Poping from back")
	tail := s2[len(s2)-1]
	s2 = s2[:len(s2)-1]
	fmt.Println(tail)

到目前为止第二部分的全部代码如下:

package main

import "fmt"

func updateSlice(s []int) {
	s[0] = 100
}

func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
	s := arr[2:6]
	fmt.Println("arr[2:6]", s)

	//s就是切片,s的值是[2 3 4 5]
	//在计算机里面[2:6]一般是包前不包后,也就是包含下标为2的但不包含为6的。
	fmt.Println("arr[:6]", arr[:6])
	fmt.Println("arr[2:]", arr[2:])
	fmt.Println("arr[:]", arr[:])

	s1 := arr[2:]
	//s2 := arr[:]
	updateSlice(s1)
	fmt.Println(s1)
	fmt.Println(arr)
	fmt.Println("Reslice")
	s2 := arr[:]
	fmt.Println(s2)
	s2 = s2[:5]
	fmt.Println(s2)
	s2 = s2[2:]
	fmt.Println(s2)

	fmt.Println("Extending slice")
	arr[0], arr[2] = 0, 2
	fmt.Println("arr=", arr)
	s1 = arr[2:6]
	s2 = s1[3:5]
	fmt.Printf("s1=%v,len(s1)=%d,cap(s1)=%d\n", s1, len(s1), cap(s1))
	fmt.Printf("s2=%v,len(s2)=%d,cap(s2)=%d\n", s2, len(s2), cap(s2))
	fmt.Println("---------------------------")
	s3 := append(s2, 10)
	s4 := append(s3, 11)
	s5 := append(s4, 12)
	fmt.Println("s3,s4,s5=", s3, s4, s5)
	//s2的cap是3,[5 6]后面隐藏了一个7,这时候s3执行的时候,将7换成了10
	//s4,s5后面再添加就超过了cap,这时候go语言会在内部新建一个arr,也就是说s4和s5不再view当前arr了
	fmt.Println("arr=", arr)
}


//--------------------------------------------------------------------

package main

import "fmt"

func printSlice(s []int) {
	fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}
func main() {

	fmt.Println("Creating slice")
	var s []int //声明一个切片,这时候s==nil
	for i := 0; i < 100; i++ {
		printSlice(s)
		s = append(s, 2*i)
	}
	fmt.Println(s)

	s1 := []int{2, 4, 6, 8}
	printSlice(s1)
	s2 := make([]int, 16) //创建len为16,cap也是16的slice
	printSlice(s2)
	s3 := make([]int, 16, 32) //创建len为16,cap为32的slice
	printSlice(s3)

	fmt.Println("Copying slice")
	copy(s2, s1) //把s1copy给s2
	printSlice(s2)

	fmt.Println("Deleting elements from slice")
	s2 = append(s2[:3], s2[4:]...)
	printSlice(s2)

	fmt.Println("Popping from front")
	front := s2[0]
	s2 = s2[1:]
	fmt.Println(front)

	fmt.Println("Poping from back")
	tail := s2[len(s2)-1]
	s2 = s2[:len(s2)-1]
	fmt.Println(tail)

	fmt.Println(s2)
}



3.Map

3.1map的定义

map的定义:

map[key的类型]值的类型

复合map的定义:

map[key1的类型]map[key2的类型]值的类型

map的定义:

	m1 := map[string]string{ //第一种定义方式
		"name":   "maoweiyang",
		"age":    "18",
		"yanzhi": "good",
	}
	m2 := make(map[string]int) //第二种定义方式,m2==empty map

	var m3 map[string]int //第三种定义方式,m3==nil

	fmt.Println(m1)
	fmt.Println(m2)
	fmt.Println(m3)

3.2map的操作

map的遍历:

	fmt.Println("Traversing map")
	for k, v := range m1 {
		fmt.Println(k, v)
	}

注意:map里面的元素是无序的。

map取值:

	fmt.Println("Getting values")
	myname := m1["name"]
	fmt.Println(myname)

注意:当key填写错误的时候,go并不会报错,而是会取出一个空值。

那么如何判断所取关键字对应的元素在不在:

	fmt.Println("Getting values")
	myname, ok := m1["name"]
	fmt.Println(myname, ok)
	myneme, ok := m1["neme"]
	fmt.Println(myneme, ok)

结果为:

所以一般这样操作:

	if myname, ok := m1["neme"]; ok {
		fmt.Println(myname, ok)
	} else {
		fmt.Println("key does not exist")
	}

 删除元素:

	fmt.Println("Deleting values")
	age, ok := m1["age"]
	fmt.Println(age, ok)
	delete(m1, "age")
	age, ok = m1["age"]
	fmt.Println(age, ok)

关于map的key:

  1. map使用哈希表,必须可以比较相等。
  2. 除了slice,map,function的内建类型都可以作为key。
  3. Struct类型不包含上述字段,也可以作为key。

到目前为止第三部分的所有代码如下:

package main

import "fmt"

func main() {
	m1 := map[string]string{ //第一种定义方式
		"name":   "maoweiyang",
		"age":    "18",
		"yanzhi": "good",
	}
	m2 := make(map[string]int) //第二种定义方式,m2==empty map

	var m3 map[string]int //第三种定义方式,m3==nil

	fmt.Println(m1)
	fmt.Println(m2)
	fmt.Println(m3)

	fmt.Println("Traversing map")
	for k, v := range m1 {
		fmt.Println(k, v)
	}

	fmt.Println("Getting values")

	myneme, ok := m1["neme"]
	fmt.Println(myneme, ok)

	if myname, ok := m1["neme"]; ok {
		fmt.Println(myname, ok)
	} else {
		fmt.Println("key does not exist")
	}

	fmt.Println("Deleting values")
	age, ok := m1["age"]
	fmt.Println(age, ok)
	delete(m1, "age")
	age, ok = m1["age"]
	fmt.Println(age, ok)
}

3.3map的一个例题

题目:给定一个字符串s,请你找出其中不含有重复字符的 最长子串的长度 

例如:

abcabcbb   :   3

bbbbbbb:1

pwwkew:3

思路:

start:当前检测的无重复子串的开始位置。

x:需要检查其是否需要加入当前无重复子串的元素。

lastOccurred[X]:x最后出现的位置。

lastOccurred[X]不存在,或者小于start的位置,那么无需操作

lastOccurred[X]大于等于start的位置,那么更新start到lastOccurred[X]+1

package main

import "fmt"

func lengthofNonRepeatingSubStr(s string) int {
	lastOccurred := make(map[byte]int)
	start := 0 //当前检测的无重复子串的开始位置。
	maxLength := 0
	for i, ch := range []byte(s) {
		//ch最后出现的位置存在且在start后。
		if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
			start = lastI + 1 //start移动到ch最后出现的位置的后面一位。
		}

		if i-start+1 > maxLength {
			//如果当前无重复字符串比记录的长度要长
			maxLength = i - start + 1
		}
		lastOccurred[ch] = i //更新ch最后出现的位置
	}

	return maxLength
}
func main() {
	fmt.Println(lengthofNonRepeatingSubStr("abcabcbb"))
	fmt.Println(lengthofNonRepeatingSubStr("bbbbb"))
	fmt.Println(lengthofNonRepeatingSubStr("pwwkew"))
	fmt.Println(lengthofNonRepeatingSubStr(""))
	fmt.Println(lengthofNonRepeatingSubStr("b"))
	fmt.Println(lengthofNonRepeatingSubStr("abcdef"))
}

4.字符和字符串的处理

rune就是go的char类型

func main() {
	s := "Yes我爱我的祖国!" //UTF-8编码
	for _, b := range []byte(s) {
		fmt.Printf("%X ", b)

	}
	fmt.Println()
}

结果:

 可以看到,UTF-8的编码方式中,英文字符一个占1个字节(8位),中文字符一个占3个字节。

	for i, ch := range s { // ch is a rune
		fmt.Printf("(%d %X)", i, ch)
	}
	fmt.Println()

可以看到,ch是一个rune类型,它将string进行了UTF-8的解码,然后进行unicode编码,然后放在了rune类型里面。

统计rune的数目:

fmt.Println("Rune count", utf8.RuneCountInString(s)) //结果为10

 遍历s:

	bytes := []byte(s)
	for len(bytes) > 0 {
		ch, size := utf8.DecodeRune(bytes) //ch是字符,size是位置,英文隔1,中文隔3
		bytes = bytes[size:]
		fmt.Printf("%c ", ch)
	}
	fmt.Println()

其他的一些字符串的操作:

Fields, Split, Join
Contains, Index
ToLower,ToUpper
Trim, TrimRight,TrimLeft

到这里为止,第四部分的全部代码如下:

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	s := "Yes我爱我的祖国!" //UTF-8编码
	for _, b := range []byte(s) {
		fmt.Printf("%X ", b)
	}
	fmt.Println()

	for i, ch := range s { // ch is a rune
		fmt.Printf("(%d %X)", i, ch)
	}
	fmt.Println()
	fmt.Println("Rune count", utf8.RuneCountInString(s)) //结果为10

	bytes := []byte(s)
	for len(bytes) > 0 {
		ch, size := utf8.DecodeRune(bytes) //ch是字符,size是位置,英文隔1,中文隔3
		bytes = bytes[size:]
		fmt.Printf("%c ", ch)
	}
	fmt.Println()

	for i, ch := range []rune(s) {
		fmt.Printf("(%d %c)", i, ch)
	}
	fmt.Println()

}

5.结构体和方法

go语言仅支持封装,不支持继承和多态。

结构体定义以及声明:

package main

import "fmt"

type treeNode struct {
	value       int
	left, right *treeNode
}

func createNode(value int) *treeNode { //工厂函数
	return &treeNode{value: value}
}

func main() {
	var root treeNode
	root = treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{5, nil, nil}
	root.right.left = new(treeNode)
	root.left.right = createNode(2)
	nodes := []treeNode{
		{value: 3},
		{},
		{6, nil, &root},
	}
	fmt.Println(nodes)
}

注意:上面用了自定义工厂函数来创建结构体,且返回了局部变量的地址(go语言是允许这样的),用这种方法来代替构造函数的功能,go语言中是没有构造函数这种说法的。

在go语言的结构体中,函数如何实现呢:

func (node treeNode) print() {
	fmt.Println(node.value)
}

函数有个接收者,这个接收者是在函数名字前面价格括号,里面协商接收的结构体类型。

使用时候:

	root.print()

这里实际上也相当于一个参数传递,也是值传递:

func (node treeNode) setValue(value int) {
	node.value = value
}


root.right.left.setValue(4)
root.right.left.print()
fmt.Println()

上述代码打印结果还是0,是改不掉的,即这里也是值传递。

那么如果希望改变:

func (node *treeNode) setValue(value int) {
	node.value = value
}

root.right.left.setValue(4)

把接收者改为指针,但是调用的时候还是照常调用。

在go语言中,参数是指针的时候可以用地址调用也可以用值调用,当参数是值的时候,可以用地址调用也可以用值调用,编译器可以帮我们处理这些不对应的关系。

只有指针才可以改变结构内容。

nil指针也可以调用方法。

实现一个遍历节点的函数:

func (node *treeNode) traverse() {
	if node == nil {
		return
	}
	node.left.traverse()
	node.print()
	node.right.traverse()

}

要改变内容的话必须使用指针接收者。

结构过大也需要考虑使用指针接收者,因为拷贝会影响性能。

一致性:有指针接收者,最好都是指针接收者。

6.封装

在go语言中名字一般使用CamelCase

首字母大写代表:public

首字母小写代表:private

这个public或者private是针对包来说的。

每个目录一个包

main包包含可执行入口

为结构定义的方法必须放在同一个包内

但可以是不同的文件

tree目录下的包为tree(当然包名可以和目录名不一样),ebtry目录下的包名为main,那么main包下的代码要用tree包里面的结构和方法,就必须将tree给import进去,“包名.结构体”使用,注意tree包里面的名字都要首字母大写。

7.扩展已有类型

 如何扩充系统类型或者别人的类型:定义别名或者使用组合

例子一:

定义别名:最简单

type myTreeNode struct {
	node *tree.Node
}

func (myNode *myTreeNode) postOrder() {
	if myNode == nil || myNode.node == nil {
		return
	}

	left := myTreeNode{myNode.node.Left}
	left.postOrder()
	right := myTreeNode{myNode.node.Right}
	right.postOrder()
	myNode.node.Print()

}

例子二:

使用组合:最常用

package queue

type Queue []int

func (q *Queue) Push(v int) {
	*q = append(*q, v)
}

func (q *Queue) Pop() int {
	head := (*q)[0]
	*q = (*q)[1:]
	return head
}

func (q *Queue) IsEmpty() bool {
	return len(*q) == 0
}
package main

import (
	"Basic_grammar_go/queue"
	"fmt"
)

func main() {
	q := queue.Queue{1}
	q.Push(2)
	q.Push(3)
	fmt.Println(q.Pop())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
}

例子三:

使用内嵌的方式来扩展已有的类型:需要省下很多代码

package main

import (
	"Basic_grammar_go/tree"
	"fmt"
)

type myTreeNode struct {
	*tree.Node //Ebedding 内嵌,不要名字,那么取值的时候直接忽略前面的,直到Node
}

func (myNode *myTreeNode) postOrder() {
	if myNode == nil || myNode.Node == nil {
		return
	}

	left := myTreeNode{myNode.Left}
	left.postOrder()
	right := myTreeNode{myNode.Right}
	right.postOrder()
	myNode.Node.Print()

}

func main() {
	root := myTreeNode{&tree.Node{Value: 3}}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{5, nil, nil}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreateNode(2)
	nodes := []tree.Node{
		{Value: 3},
		{},
		{6, nil, nil},
	}
	fmt.Println(nodes)

	root.Print()
	root.Right.Left.SetValue(4)
	root.Right.Left.Print()
	fmt.Println()
	root.Traverse()
	root.postOrder()
}

8.go语言的依赖管理

依赖管理的三个阶段 GOPATH,GOVENDOR,go mod

8.1 GOPATH

你告诉我一个GOPATH,你要的所有依赖包,我都去这个GOPATH里面找。

8.2GOVENDOR

每个项目都有自己的vendor目录,存放第三方库

大量第三方依赖管理工具:glide,dep,go dep,...

8.3go mod

例如:

go get -u go.uber.org/zap

会安装zap1.12

那么改为1.11怎么改

//安装1.11版本
go get -u go.uber.org/zap@v1.11

//清除1.12
go mod tidy

Go语言基础语法(一)

Go语言基础语法(二)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值