Go(1)-- 基础知识

       看过我博客的朋友可以知道,之前我一直在搞Java,但是由于去了实习公司后,技术栈是 py 和 go,之前也是一直挺想学习 go的,接下来便开启新的征程吧。
       部分内容参考:李文塔老师编著的《Go语言核心编程》

1.语言简介

1.1 Go 语言诞生原因

  • 并发不友好
  • 编译速度慢
  • 编程复杂

       Go 语言的诞生就是为了解决以上三个问题。

1.2 Go 应用领域

  • 区块链的应用开发
  • 后台的服务应用
  • 云计算/云服务后台应用

       我本人对后台开发的方向比较感兴趣,学习 Java 的时候便一直在学习后台开发,在公司时,旁别的小姐姐就是从事 Go 的后台开发。

1.3 Go 的特点

       Go 语言保证了既能达到静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容Go语言:Go = C + Python,说明 Go 语言既有 C 静态语言程序的运行速度,又能达到 Python 动态语言的快速开发。

  1. 从 C 语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等,也保留了和 C 语言一样的编译执行方式及弱化的指针。
  2. 引入包的概念,用于组织程序结构,Go语言的每一个文件都要属于一个包,而不能单独存在。
  3. 垃圾回收机制,内存自动回收,不需要开发人员管理
  4. 天然并发
  5. 函数可以返回多个值
  6. 新的创新:比如切片 slice、延时执行 defer 等

1.3 Hello World

       学习任何语言的第一个程序相比都是 "Hello,World“吧,接下来让我们看下 Go 下的该程序。

在这里插入图片描述

       第一行,定义一个包,在 Go 中,任何一个 .go 文件都要保含在一个包中。与 Java 不同的是,包名并不要求是所在的文件夹名。
       第三行,导入一个包 fmt,可以是标准库提供的包,也可以是自定义的包。
       第五行,func 表示这是一个函数。main 表示这是 go 程序的执行入口,程序的执行便从这里开始。
       第六行,调用 fmt 包下的 Println 函数,将 “Hello World”输出到标准输出流即显示器。

1.3.1 编译运行

  1. 方式一(不生成可执行文件)

在这里插入图片描述
2. 方式二(生成可执行文件)

在这里插入图片描述
在这里插入图片描述
       两种执行流程的方式区别
       1)如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器上运行。
       2)如果我们执行go run 源码,那么在另一台机器运行,需要有go 的开发环境。
       3)在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多。

1.4 Go 开发注意事项

  1. Go 源文件以 “go”为扩展名
  2. Go 应用程序的执行入口是 main() 方法
  3. Go 语言严格区分大小写
  4. Go 方法由一条条语句构成,每个语句后不需要分号(Go 语言会自动在每行后自动加分号)
  5. Go 编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一行,否则报错
  6. Go 语言定义的变量或者 import 的包没有使用到,代码不能编译通过
  7. 大括号都是成对出现的,缺一不可
  8. 源程序默认为 UTF-8 编码
  9. main 函数所在的包名必须是 main

1.5 Go 词法单元

1.5.1 标识符

       标识符用来标识变量、类型、常量等语法对象的符号名称。

       标识符分为两类:一类是语言设计者预留的标识符,另一类是编程者自定义的标识符,自定义的标识符应该避开预留的标识符

       标识符的命名规则

  • 由 26 个英文字母大小写,0-9,_组成
  • 不可以以数字开头
  • 严格区分大小写
  • 标识符不能包含空格
  • 下划线“_”本身在 Go 中是一个特殊的标识符,称为空标识符,可以代表任何其他的标识符,但是它对应的值会被忽略(比如:忽略某个返回值),所以仅能呗作为占位符使用,不能作为标识符使用

       Go 语言预声明的标识符包括关键字、内置数据类型标识符、常量值标识符、零值、内置函数和空白标识符。

关键字(25)

在这里插入图片描述

内置数据类型标识符(20)

在这里插入图片描述

内置函数(15)

在这里插入图片描述
       内置函数是语言内置的,不需要 import 引入,可以直接调用。

常量(3)

在这里插入图片描述
        itoa 用在连续的枚举类型的声明中。

零值(1)

在这里插入图片描述
       nil 是指针/引用类型的变量的默认值,类似于 Java 中的 NULL。

空白标识符(1)

_

       空标示符”_”是一个占位符,它用于在赋值操作的时候将某个值赋值给空标示符号,从而达到丢弃该值的目的。空标示符不是一个新的变量,因此将它用于:=操作符号的时候,必须同时为至少另一个值赋值。

1.5.2 运算符

算术运算符

在这里插入图片描述
       在 Go 中,++ 以及 – 只能放在变量的后面,即只能 a++,不能 ++a。

比较运算符

在这里插入图片描述

逻辑运算符

在这里插入图片描述

赋值运算符

在这里插入图片描述

位运算符

在这里插入图片描述

其他运算符

在这里插入图片描述

1.5.3 字面常量

       编程语言源程序中标识固定值的符号叫做字面常量,简称字面量。

  • 整性字面量

42
0600
0xAb

  • 浮点型字面量

0.0
5.2

  • 复数类型字面量

0i
2.7i

  • 字符型字面量

‘a’
‘人’

  • 字符串型字面量

“Hello”
“中国”

1.6 变量和常量

1.6.1 变量

       变量:使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址里面存放的内容可以改变。

       变量基本类型的声明有两种方式方式:

  1. 显式的完整声明

var varName dataType [ = value]

       说明:

  • 关键字 var 用于变量声明
  • dataType 基本类型
  • val 是变量的初始值,初始值可以是字面量,也可以是其他变量名,还可以是表达式。如果不指定初始值,则各基本类型有自己的零值。
  • Go 变量声明后会立即为其分配空间。
package main

import (
	"fmt"
)

func main()  {
	var a int
	var b float32 = 10.5

	fmt.Println("a = ", a)
	fmt.Println("b = ", b)
}

在这里插入图片描述

  1. 类型推导声明

varName := value

       说明:

  • := 声明只能出现在方法或者函数内
  • Go 编译器会自动进行数据类型推断
  • Go 支持多个类信息变量同时声明且赋值
package main

import (
	"fmt"
)

func main()  {

	a, b := 20.5, "Hello"

	fmt.Println("a = ", a)
	fmt.Println("b = ", b)

}

在这里插入图片描述

1.6.2 变量的属性

  • 变量名

       变量名:Go 中使用自定义标识符来声明一个变量。

  • 变量值

       变量实际指向的是地址里存放的值,变量的值具体怎么解析由变量的类型决定。

  • 变量存储和生存期
           Go 提供自动内存管理,通常不需要程序员关注变量的生存期和存放位置。编译器使用栈逃逸技术能够自动为变量分配空间:可能在栈上,也可能在堆上

  • 类型信息

       类型决定了该变量存储的值怎么解析,以及支持哪些操作和运算,不同类型的变量支持的操作和运算是不一样的。

  • 可见性和作用域
           Go 内部使用统一的命名空间堆变量进行管理,每隔变量都有一个唯一的名字,包名是这个名字的前缀。

1.6.3 常量

       常量使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义常量时指定的类型决定,而且该内存地址里面存放的内容不可以改变。Go 中常量分为布尔型、字符串型和数值型常量。常量存储在程序的只读段里

       语法:const identifier [type] = value

package main

import (
	"fmt"
)

func main(){


	const a int = 10

	const b string = "hello"

	fmt.Println("a = ", a)
	fmt.Println("b = ", b)

}

       预声明标识符 iota 用在常量声明中,其初始值为0。一组多个常量同时声明其值逐行增加,iota 可以看作自增的美剧变量,专门用来初始化常量。

package main

import (
	"fmt"
)

func main(){


	const (
		c0 = iota		// c0 =  0
		c1 = iota		// c1 =  1
		c2 = iota		// c2 =  2
	)

	// 简写模式
	const (
		c3 = iota		// c3 =  0
		c4			    // c4 =  1
		c5				// c5 =  2
	)

	const (
		c6 = iota		// c6 =  0
		c7 = 3			// c7 =  3
		c8				// c8 =  3
	)
	
	// iota 逐行增加
	const (
		a = 1 << iota   // a = 1, iota = 0
		b = 1 << iota	// b = 2, iota = 1
		c = 3			// c = 3, iota = 2
		d = 1 << iota	// d = 8, iota = 3
	)

	const(
		u = iota * 42			// u = 0
		v float64 = iota * 42	// v = 42.0
		w = iota * 42			// w = 84
	)

	// 分开的 const 语句,iota 每次都从 0 开始
	const x = iota	// x = 0
	const y = iota	// y = 0

}

       常量在定义的时候必须初始化

在这里插入图片描述

       常量不能修改

在这里插入图片描述

1.7 数据类型

在这里插入图片描述

1.7.1 布尔类型:bool

  • bool 类型占1个字节
  • 不可以使用 0 或非 0 的整数代替 false 和 true
  • 声明的布尔型如不指定初始值,则默认为 false

1.7.2 整型

       Go 语言内置了 12 种 整数类型,分别是 byte、int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、uintptr。其中 byte 是 uint8 的别名,不同类型的整型必须进行强制类型转换

1.7.3 浮点型

       浮点型用于表示包含小数点的数据,Go 语言内置两种浮点数类型,分别是 float32 和 float64.

       浮点数有两个注意事项:

  1. 浮点数字面量被自动类型推导为 float64 类型
  2. 计算机很难进行浮点数的精确表示和存储,因此两个浮点数之间不应该使用 == 或 != 进行比较操作,高精度科学计算应该使用 math 标准库

1.7.4 复数类型

       Go 语言内置的复数类型有两种,分别是 complex64 和 complex128,符数在计算机里面使用两个浮点数表示,一个表示实部,一个表示虚部。complex64 是由两个 float32 构成,complex 使用两个 float64 构成。

package main

import (
	"fmt"
)

func main()  {

	var value1 complex64 = 3.1 + 5i

	value2 := 3.1 + 6i

	fmt.Println("value1 = ", value1)

	fmt.Println("value2 = ", value2)

	// Go 有三个内置函数处理复数

	var value = complex(2.1, 3) // 构造一个复数
	a := real(value) // 返回复数实部
	b := imag(value) // 返回复数虚部

	fmt.Println("a = ", a)
	fmt.Println("b = ", b)

}

在这里插入图片描述

1.7.5 字符串

       Go 语言将字符串作为一种原生的基本数据类型。

  1. 字符串是常量,可以通过类似数组索引访问其字节单元,但是不能修改某个字符串的字面量值。

在这里插入图片描述

  1. 字符串转换为切片 []byte(str) 要慎用,尤其是当数据量较大时(每转换一次都需要复制内容)。

在这里插入图片描述

  1. 字符串尾部不包含 \0字符,这点和 C/C++ 不一样
  2. 字符串类型底层实现是一个二元的数据结构,一个是指针指向字节数组的起点,另一个是长度。
type stringStruct struct{
	str unsafe.Pointer // 指向底层字节数组的指针
	len int			   // 字节数组的长度
}
  1. 基于字符串创建的切片和原字符串指向相同的底层字符串数组,一样不能修改,堆字符串的切片操作返回的子串仍然是 string,而非 slice。

在这里插入图片描述

  1. 字符串和切片的转换:字符串可以转换为字节数组,也可以转换为 Unicode 的字数组。

在这里插入图片描述

  1. 字符串的运算:
package main

import (
	"fmt"
)

func main() {

	a := "hello"
	b := "world"
	c := a + b					  // 字符串拼接
	fmt.Println("len = ", len(c)) // 内置的 len 函数获取字符串长度

	d := "hello, 世界"
	
	for i := 0; i < len(d); i++{  // 遍历字节数组
		fmt.Println(d[i])
	}

	for i, v := range d{		  // 遍历 rune 数组
		fmt.Println(i, v)
	}
}



在这里插入图片描述

  1. Go 程序是以 UTF-8 表示,字符串同样,而一个汉字是三个字节,所以需要注意

1.7.6 rune 类型

       Go 内置两种字符类型:一种是 byte 的字节类类型(byte 是 uint8 的别名),另一种是 Unicode 编码的字符 rune。rune 在 Go 内部是 int32 类型的别名,占用4各字节。Go 语言默认的字符编码是 UTF-8 类型的,如果需要特殊转换,则使用 Unicde/UTF-8 标准包。

1.7.7 指针

       Go语言支持指针,指针的声明类型为 *T,Go同样支持多级指针 **T。通过在变量名前加 &来获取变量的地址。

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
       指针特点:

  1. 结构体指针访问结构体字段仍然使用.点操作符,Go语言没有->操作符。
  2. Go不支持指针的运算
  3. 函数种允许返回局部变量的地址。
           Go编译器使用 “栈逃逸”机制将这种局部变量的空间分配在堆上。例如:
func sum(a int, b int) *int{
	sum := a + b
	return &sum // 允许,sum 会分配在 heap 上
}

1.7.8 数组

       数组的类型名是 [n]elementType,其中 n 是数组长度,elementType 是数组元素类型。

在这里插入图片描述

       数组初始化

在这里插入图片描述       数组的特点

  1. 数组创建完长度就固定了,不可以再追加元素
  2. 数组是值类型,数组赋值或作为函数参数都是值拷贝
  3. 数组长度是数组类型的组成部分,[10]int 和 [20]int 表示不同的类型
  4. 可以根据数组创建切片

       数组的相关操作

	a := [...]int{1, 2, 3}
	
	for i, v := range a{				// 遍历数组
		fmt.Printf("a[%d] = %v\n", i, v)
	}

	fmt.Println("len a = ", len(a))		// 获取数组长度

在这里插入图片描述

1.7.9 切片

       Go 语言的数组的定长性和值拷贝限制了其使用场景,Go 提供了另一种数据类型 slice,这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型

type slice struct{
	array unsafe.Pointer
	len int
	cap int
}

       Go 切片维护了三个元素–指向底层数组的指针、切片的元素数量和底层数组的容量。

在这里插入图片描述
       (1)切片的创建

  • 由数组创建

       创建语法如下:array[i:j],其中,array 表示数组名;i 表示开始索引,可以不指定,默认是0;j表示结束索引,可以不指定,默认是 len(array)。array[i:j] 表示创建一个包含 i-j 个元素的切片,第一个元素是 array[i],最后一个元素是 array[j-1]。

	var array = [...]int {0, 1, 2, 3, 4, 5, 6}

	fmt.Printf("array type = %T, value = %v \n", array, array)

	s1 := array[0:4]

	s2 := array[:4]

	s3 := array[2:]

	fmt.Printf("s1 type = %T, value = %v \n", s1, s1)
	fmt.Printf("s2 type = %T, value = %v \n", s2, s2)
	fmt.Printf("s3 type = %T, value = %v \n", s3, s3)

在这里插入图片描述

  • 通过内置函数 make 创建切片
	var a []int
	a = make([]int, 10)			// len = 10, cap = 10

	b := make([]int, 10, 15)	// len = 10, cap = 15

	fmt.Println("a = ", a)
	fmt.Println("b = ", b)

在这里插入图片描述

       (2)切片支持的操作

  • 内置函数 len() 返回切片长度
  • 内置函数 cap() 返回切片底层数组容量
  • 内置函数 append() 堆切片追加
  • 内置函数 copy() 用于复制一个切片
	a := [...]int{0, 1, 2, 3, 4, 5, 6}
	b := make([]int, 2, 4)
	c := a[0:3]

	fmt.Println(len(b))		// 2
	fmt.Println(cap(b))		// 4

	b = append(b, 1)
	fmt.Println(b)			// [0 0 1]
	fmt.Println(len(b))		// 3
	fmt.Println(cap(b))		// 4

	b = append(b, c...)		
	fmt.Println(b)			// [0 0 1 0 1 2]
	fmt.Println(len(b))		// 6
	fmt.Println(cap(b))		// 8 底层数组发生扩展

	d := make([]int, 2, 2)
	copy(d, c)
	fmt.Println(d)			// [0 1]
	fmt.Println(len(d))		// 2
	fmt.Println(cap(d))		// 2

       (3)字符串和切片的相关转换

	str := "hello,世界"
	
	a := []byte(str)				// 
	fmt.Printf("a type = %T, value = %v\n", a, a)
	// a type = []uint8, value = [104 101 108 108 111 44 228 184 150 231 149 140]

	b := []rune(str)
	fmt.Printf("b type = %T, value = %v\n", b, b)
	// b type = []int32, value = [104 101 108 108 111 44 19990 30028]

       (4)切片注意事项和细节说明

  • 切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长
  • var slice = arr[0 : end],可以简写var slice = arr[ : end]
  • var slice = arr[start : len(arr)],可以简写var slice = arr[ start : ]
  • var slice = arr[0 : len(arr)],可以简写var slice = arr[ : ]
  • cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
  • 切片定义完毕后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用
  • 切片可以继续切片
  • 切片是引用类型,所以在传递时,遵守引用传递机制

       (5)切片 append 操作的底层原理分析

  • 切片 append 操作的本质就是对数组扩容
  • go 底层会创建一个新的数组 newArr(按照扩容后大小)
  • 将 slice 原来包含的元素拷贝到新的数组 newArr
  • slice 重新引用到newArr

1.7.10 map

       Go 语言内置的字典类型叫 map。map 的类型格式是 map[K]T,其中 K 可以是任意可以进行比较的类型,T 是值类型。map 也是一种引用类型

       (1)map 的创建

  • 使用字面量创建
	ma := map[string]int{"a" : 1, "b" : 2}

	fmt.Println("ma[a] = ", ma["a"])		// ma[a] =  1
	fmt.Println("ma[b] = ", ma["b"])		// ma[b] =  2
  • 使用内置的 make 函数创建
	// make(map[K]T) 	  map 的容量使用默认值
	// make(map[K]T, len) map 的容量使用给定的 len 值
	// make 表示分配内存空间,len 表示可以存放见指对的个数
	
	mp1 := make(map[int]string)
	mp2 := make(map[int]string, 10)

	mp1[1] = "tom"
	mp2[1] = "pony"

	fmt.Println("mp1[1] = ", mp1[1])		// mp1[1] =  tom
	fmt.Println("mp2[1] = ", mp2[1])		// mp2[1] =  pony

       (2)map 支持的操作

  • map 的增加和更新
           map[key] = value ,如果 key 还没有,就是增加,如果 key 存在就是修改。
  • map 删除

       delete(map, key),delete 是一个内置函数,如果key存在,就删除该 key-value,如果 key 不存在,不操作,但是也不报错。

  • map 的查找
    在这里插入图片描述
           第二个返回值表示是否存在该键值对,如果存在,则为 true,否则为 false

  • map 的遍历

       map 的遍历使用 for-range 的结构遍历。

在这里插入图片描述
       (3)map 切片

       切片的数据类型如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 的个数就可以动态变化了。

在这里插入图片描述在这里插入图片描述       (4)map 注意事项和使用细节

  • map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来的 map
  • map 的容量达到后,在想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 动态的增长键值对

在这里插入图片描述

  • Go 内置的 map 不是并发安全的,并发安全的 map 可以使用标准包 sync 中的 map
  • 不能直接修改 map value 内某个元素的值,如果想修改 map 的某个键值,则必须整体赋值。
	userMap := make(map[int]User)

	user := User{
		Name : "wangzhao",
		Age : 21,
	}

	userMap[1] = user

	// userMap[1].Age = 20 cannot assign to struct field userMap[1].Age in map

	user.Age = 20
	userMap[1] = user // 必须整体替换value

1.7.11 struct

       Go 中的 struct 类型和 C 类似,由多个不同类型元素组合而成。struct 结构体中的类型可以是任意类型,struct 的存储空间是连续的,其中字段按照声明时的顺序存放。

       (1)struct 类型声明

type typeName struct{
	FieldName FieldType
	FieldName FieldType
	FieldName FieldType
}

       (2)创建结构体变量和访问结构体字段

在这里插入图片描述

  • 方式一:直接声明
    在这里插入图片描述
  • 方式二:{}
    在这里插入图片描述
  • 方式3:&
    在这里插入图片描述
  • 方式4:{}
    在这里插入图片描述
           (3)结构体使用细节和注意事项
  • 结构体的所有字段在内存中是连续的
  • 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型

在这里插入图片描述

  • 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转

在这里插入图片描述

  • struct 每个字段上,可以写上一个 tag,该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

在这里插入图片描述在这里插入图片描述在这里插入图片描述

1.7.12 其他复合类型

       接口和管道后面的博客进行介绍。

1.8 控制结构

1.8.1 if 语句

  • 单分支
if 条件表达式 {
    执行代码块
}
  • 双分支
if 条件表达式 {
    执行代码块
}
else{
    执行代码块
}
  • 多分支
if 条件表达式 {
    执行代码块
}

else if 条件表达式{
    执行代码块
}
......
else{
    执行代码块
}

       特点:

  1. if 后面的条件判断子句不需要用小括号括起来
  2. { 必须放在 行尾,和 if 或 if else 放在一行
  3. if 后面可以带一个带简单的初始化语句,并以分号分割,作用域是整个 if 语句块,包括后面的 else if 和 else 分支
  4. Go 语言没有条件运算符(a > b ? a:b)

1.8.2 switch 语句

       基本语法

switch 表达式{

    case 表达式1,表达式2... :
        语句块1
    case 表达式3,表达式4, ... :
        语句块2
    
    default:
        语句块
}

       switch 使用细节和注意事项

  1. case 后是一个表达式(常量、变量、一个有返回值的函数等都可以)
  2. case 后的各个表达式的数据类型,必须和 switch 的表达式数据类型一致
  3. case 后面可以有多个表达式,使用逗号分隔
  4. case 后面的表达式如果是常量值,则不能重复
  5. case 后面不需要带 break
  6. default 语句不是必须的
  7. switch 后也可以不带表达式,类型 if - else 分支来使用

在这里插入图片描述

  1. switch 后也可以直接声明/定义一个变量,分号结束

在这里插入图片描述

  1. switch 穿透 fallthrough,如果case 语句块后增加 fallthrough,则会继续执行下一个case

在这里插入图片描述

1.8.3 for 语句

  • 类 C 的 for 循环语句
for init; condition; post{}
  • 类 C 的 while 语句
for condition{}
  • 类 C 的 while(1) 语句
for{}

1.8.4 标签和跳转

       标签

       Go 语言使用标签(Lable)标识一个语句的位置,用于 goto、break、continue 语句的跳转,标签的语法是:

Lable:Statement

       goto

       goto 语句用于函数的内部跳转,需要配合标签一起使用。

goto Lable

       Go 语言的 goto 语句可以无条件地转移到程序中指定地行
       goto 语句通常与条件语句配合。
       在 Go 程序设计中一般不主张使用 goto 语句,以免造成程序流程混乱。

       break

       break 语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块。

在这里插入图片描述
       单独使用,跳出当前 break 所在的 for、switch、select 语句的执行。

       continue

       continue 语句出现在多层嵌套的语句块中时,可以通过标签指明要跳过哪一层循环。

       单独使用,跳出当前所在 for 循环的本次迭代。

       return和函数调用

       return 语句也能引发控制流程的跳转,用于函数和方法的退出。函数和方法的调用也能引发程序控制流的跳转。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值