Go零基础学习笔记[陆续更新,个人学习使用]

一.变量

01.变量声明

1.标准格式

var 变量名 变量类型
//变量声明以关键字var开头,变量类型后置,行尾无须分号
// C/C++变量定义格式:变量类型 变量

//可以用该方法验证数据类型 fmt.Printf("%T",变量)
//声明了一个名为num1的变量,类型为int 
var num1 int
//声明了一个名为num2的变量,类型为float32
var num2 float32
//声明了一个名为num3的变量,类型为float64
var num3 float64
//声明了一个名为pnum的变量,类型为*int
var pnum *int

1.2基本类型

bool
string
int//随系统,一般是占用4个字节
int8//占一个字节
int16//占两个字节
int32//占4个字节
int64//占8个字节
uint//无符号整数 (uint8、uint16、uint32、uint64、uintptr)
//unsiged为无符号 signed为有符号(可省略)
//int8 范围 -128-127,uint8 范围:0-255
byte //uint8 的别名
rune //int32 的别名 代表一个 Unicode 码
float32
float64
complex64
complex128

当一个变量被声明之后,系统自动赋予它该类型的零值:

int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil

package main

import "fmt"

var a int
var b float
var c bool
var d string
var e *int
func main() {//注意:主函数的第一个大括号必须和main在同一行!!!
	fmt.Println(a);//因为a未被初始化,自动赋予为0
    fmt.Println(b);//因为b未被初始化,自动赋予为0
    fmt.Println(c);//因为c未被初始化,自动赋予为0
    fmt.Println(d);//因为d未被初始化,自动赋予为0
    fmt.Println(e);//因为e未被初始化,自动赋予为0
}

1.3 不指明变量类型

//go语言像C++11的auto一样有自动推导数据类型,只不过本人感觉go的更加方便
var a = 1//注意这里没有标明是int
fmt.Printf("%T",a)//打印出来为int

1.4批量格式

var (
	a = 1
	b = 3.14
	c = "ILoveYou"

)
fmt.Printf("a=%d,b=%f,c=%s",a,b,c)

1.5简短格式

aa :=1
fmt.Println(aa)

使用简短格式有以下限制:

1、定义变量,同时显式初始化
2、不能提供数据类型
3、只能用在函数内部

02.初始化变量

2.1初始化规则

package main

func main() {
	
	var num int = 1
	num := 2 //再次声明并赋值 会报错 no new variables on left side of := (左边的变量已经被声明了,不能重复声明)
    num = 2 //可以正常赋值
}

特例:在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错

//net.Dial 提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(error)
package main

import (
	"fmt"
	"net"
)
func main() {

	var conn net.Conn
	var error error
	conn, error = net.Dial("tcp", "127.0.0.1:80")
    conn1, error = net.Dial("tcp", "127.0.0.1:80")//变量error不会发生重复定义冲突,因为有新的变量conn1产生
	fmt.Println(conn)
	fmt.Println(err)
}

2.2举个栗子

如何交换a和b的值?

这个问题几乎是学习任何编程语言都要接触到的

下面举出三个交换a和b的值的方法

//方法一 引入中间变量t(可以类比如何调换两个杯子中的水,需要引入第三个空杯子)
a := 1 
b := 2
var t int 
t = a
a = b
b = t
//方法二 使用异或运算符
a := 1 
b := 2
a = a^b
b = b^a
a = a^b
//方法三 go的独特方式(其实python也有QAQ)
a := 1 
b := 2
a,b = b,a

03.匿名变量

使用多重赋值时,如果不需要在左值中接受变量,可以使用匿名变量 “_”

package main

import (
	"fmt"
	"net"
)
func main() {
    
    //conn, error := net.Dial("tcp", "127.0.0.1:80")
    //如果不想接收error的值,那么可以使用_表示,这就是匿名变量
    conn, _ := net.Dial("tcp", "127.0.0.1:80")
	fmt.Println(conn)
}

匿名变量三大性质:

匿名变量以“_”下划线表示

匿名变量不占用命名空间,也不会分配内存

匿名变量可以重复声明使用

package main

import (
	"fmt"
	"net"
)

func main() {

	conn, _ := net.Dial("tcp", "127.0.0.1:8080")
	//匿名变量可以重复声明
	conn1, _ := net.Dial("tcp", "127.0.0.1:8080")
	// _ :=1 不允许出现这种情况 至少有一个确定的变量,匿名变量不可以单独开头
	fmt.Println(conn)
	fmt.Println(conn1)
}

04.作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。

了解变量的作用域对我们学习Go语言来说是比较重要的,因为Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。如果不能理解变量的作用域,就有可能会带来一些不明所以的编译错误。

根据变量定义位置的不同,可以分为以下三个类型:

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数

4.1局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。

局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。

//本示例中a,b,c均为局部变量
package main
import (
    "fmt"
)
func main() {
    //声明局部变量 a 和 b 并赋值
    var a int = 3
    var b int = 4
    //声明局部变量 c 并计算 a 和 b 的和
    c := a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

4.2全局变量

在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用“import”关键字引入全局变量所在的源文件之后才能使用这个全局变量。

全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。

//本示例中a,b均为局部变量,c为全局变量
package main
import "fmt"
//声明全局变量
var c int
func main() {
    //声明局部变量
    var a, b int
    //初始化参数
    a = 3
    b = 4
    c = a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。

package main
import "fmt"
var bb float32 = 3.14 //声明全局变量
func main() {
	bb := 3 //声明同名局部变量
	fmt.Println(bb)
}
//执行结果 3

4.3形式参数

在定义函数时函数名后面括号中的变量叫做形式参数(简称形参)。

形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。

形式参数会作为函数的局部变量来使用。

package main
import (
    "fmt"
)
//全局变量 a
var a int = 13
func main() {
    //局部变量 a 和 b
    var a int = 3
    var b int = 4
    fmt.Printf("main() 函数中 a = %d\n", a)
    fmt.Printf("main() 函数中 b = %d\n", b)
    c := sum(a, b)
    fmt.Printf("main() 函数中 c = %d\n", c)
}
func sum(a, b int) int {
    fmt.Printf("sum() 函数中 a = %d\n", a)
    fmt.Printf("sum() 函数中 b = %d\n", b)
    num := a + b
    return num
}
/*
打印结果:
main() 函数中 a = 3
main() 函数中 b = 4
sum() 函数中 a = 3
sum() 函数中 b = 4
main() 函数中 c = 7
*/

二.基本类型

01整型

Go语言同时提供了有符号和无符号的整数类型

  • 有符号整型:int、int8、int64、int32、int64
  • 无符号整型:uint、uint8、uint64、uint32、uint64、uintptr

有符号整型范围:-2^(n-1) 到 2^(n-1)-1

无符号整型范围: 0 到 2^n-1

实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。uint在硬件开发中使用

用来表示 Unicode 字符的 rune 类型和 int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样,byte 和 uint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。

无符号的整数类型 uintptr,它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

02浮点型

Go语言支持两种浮点型数:

  1. float32: 范围 约1.4e-45 到 约3.4e38
  2. float64 :范围约4.9e-324 到 约1.8e308
floatStr1 := 3.1415926
fmt.Printf("%f\n",floatStr1)//打印3.141593(默认6位,多出四舍五入)
fmt.Printf("%.2f\n",floatStr1)//打印3.14

通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。

var f float32 = 1 << 24;
fmt.Println(f == f+1) // true
var g float64 = 1 << 24;
fmt.Println(g == g+1) // false

浮点数在声明的时候可以只写整数部分或者小数部分

var a = .12345 // 0.12345
var b = 1.     // 1
fmt.Printf("%.5f,%.1f",a,b)

很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分

var avogadro = 6.02214129e23  // 阿伏伽德罗常数
var planck   = 6.62606957e-34 // 普朗克常数
fmt.Printf("%f,%.35f",avogadro,planck)

03布尔型

在Go语言中,以bool类型进行声明:

var 变量名 bool
var aVar = 10
aVar == 5  // false
aVar == 10 // true
aVar != 5  // true
aVar != 10 // false

Go语言对于值之间的比较有非常严格的限制,只有两个相同类型的值才可以进行比较,如果值的类型是接口(interface),那么它们也必须都实现了相同的接口。

如果其中一个值是常量,那么另外一个值可以不是常量,但是类型必须和该常量类型相同。

如果以上条件都不满足,则必须将其中一个值的类型转换为和另外一个值的类型相同之后才可以进行比较。

与其他编程语言一样,&&(AND),||(OR)是具有短路行为的,如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值(&&优先级高于||)

布尔型数据只有true和false,且不能参与任何计算以及类型转换

04字符类型

Go语言的字符有以下两种:

  • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
  • 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

byte 类型是 uint8 的别名,rune 类型是int32的别名

ASCII 码的一个字符占一个字节ASCII定义 128 个字符,由码位 0 – 127 标识。它涵盖英文字母,拉丁数字和其他一些字符。

//使用单引号 表示一个字符
var ch byte = 'A'
//在 ASCII 码表中,A 的值是 65,也可以这么定义
var ch byte = 65
//65使用十六进制表示是41,所以也可以这么定义 \x 总是紧跟着长度为 2 的 16 进制数
var ch byte = '\x41'
//65的八进制表示是101,所以使用八进制定义 \后面紧跟着长度为 3 的八进制数
var ch byte = '\101'

fmt.Printf("%c",ch)

Unicode 是 ASCII 的超集,它定义了 1,114,112 个代码点的代码空间。 Unicode 版本 10.0 涵盖 139 个现代和历史文本集(包括符文字母,但不包括 Klingon )以及多个符号集。

Go语言同样支持 Unicode(UTF-8), 用rune来表示, 在内存中使用 int 来表示。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u或者\U。如果需要使用到 4 字节,则使用\u前缀,如果需要使用到 8 个字节,则使用\U前缀。

var ch rune = '\u0041'//rune等价于int32,所以4位
var ch1 int64 = '\U00000041'//int64==>8位
//格式化说明符%c用于表示字符,%v或%d会输出用于表示该字符的整数,%U输出格式为 U+hhhh 的字符串。
fmt.Printf("%c,%c,%U",ch,ch1,ch)//A,A,U+0041

Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)
  • 判断是否为数字:unicode.IsDigit(ch)
  • 判断是否为空白符号:unicode.IsSpace(ch)

05字符串类型

5.1字符串介绍

一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列。

字符串的定义:

var mystr string = "hello"

go语言从底层就支持UTF-8编码。

UTF-8 是一种被广泛使用的编码格式,是文本文件的标准编码。

由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用 1 至 4 个字节,这与其它编程语言不同。

Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容。
当字符为 ASCII 码表上的字符时则占用 1 个字节

字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:

  1. \n:换行符
  2. \r:回车符
  3. \t:tab 键
  4. \u 或 \U:Unicode 字符
  5. \:反斜杠自身

字符串是字节的定长数组,byte 和 rune 都是字符类型,若多个字符放在一起,就组成了字符串

比如 hello ,对照 ascii 编码表,每个字母对应的编号是:104,101,108,108,111

var mystr01 string = "hello"
var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111}
fmt.Printf("myStr01: %s\n", mystr01)//myStr01:hello
fmt.Printf("myStr02: %s", mystr02)//myStr02:hello

fmt.Printf("length of myStr01:%d\n",len(mystr01))//5
fmt.Printf("length of myStr01:%d\n",len(mystr01))//5

var myStr03 string = "我在学习Go"
fmt.Printf("length of myStr01:%d\n",len(myStr03))//一个汉字3个字节,一个字母一个字节

5.2字符串应用

一般的比较运算符(==、!=、<、<=、>=、>)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。

字符串所占的字节长度可以通过函数 len() 来获取,例如 len(str)。

字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[ ]内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0]
  • 第 i 个字节:str[i - 1]
  • 最后 1 个字节:str[len(str)-1]

需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。

ASCII字符使用**len()**函数

Unicode字符串长度使用**utf8.RuneCountInString()**函数

//如何计算字符串的长度
str3 := "hello"
str4 := "你好"
fmt.Println(len(str3))  //5 	1个字母占1个字节
fmt.Println(len(str4))  //6 	1个中文占3个字节,go从底层支持utf8
fmt.Println(utf8.RuneCountInString(str4)) // 2

字符串拼接符“+”

两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。将 s2 追加到 s1 尾部并生成一个新的字符串 s。

//因为编译器会在行尾自动补全分号,所以拼接字符串用的加号“+”必须放在第一行末尾。
str := "第一部分 " +
		"第二部分"
fmt.Println(str)

也可以使用**“+=”**来对字符串进行拼接:

s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 “hello, world!”

除了使用+进行拼接,我们也可以使用WriteString()

str1 := "你好,"
str2 := "爱学习的小白"
var stringBuilder bytes.Buffer
//节省内存分配,提高处理效率
stringBuilder.WriteString(str1)
stringBuilder.WriteString(str2)
fmt.Println(stringBuilder.String())

如果想获得“你好,爱学习的小白”中的“爱”该如何获取呢?

直接索引对rune类型无效,可以使用string方法转换

string([]rune(str6)[0])

var myStr01 string = "你好,爱学习的小白"
fmt.Println(string([]rune(myStr01)[3]))//爱
fmt.Printf("%c\n",myStr04[3])//å

遍历

unicode字符集使用for range进行遍历,ascii字符集可以使用for range或者for循环遍历

var str1 string = "hello"
var str2 string = "hello,爱学习的小白"
// 遍历
for i :=0; i< len(str1); i++{
	fmt.Printf("ascii: %c %d\n", str1[i], str1[i])
}
for _, s := range  str1{
	fmt.Printf("unicode: %c %d\n ", s, s)
}
// 中文只能用 for range
for _, s := range  str2{
	fmt.Printf("unicode: %c %d\n ", s, s)
}

字符串的格式化

  • print : 结果写到标准输出
  • Sprint:结果会以字符串形式返回
str1 := "你好,"
str2 := "爱学习的小白"
var stringBuilder bytes.Buffer
stringBuilder.WriteString(str1)
stringBuilder.WriteString(str2)
// Sprint 以字符串形式返回
result := fmt.Sprintf(stringBuilder.String())//fmt不仅仅是打印的功能,还有修改格式等等作用
fmt.Println(result)

%c  单一字符
%T  动态类型
%v  本来值的输出
%+v 字段名+值打印
%d  十进制打印数字
%p  指针,十六进制
%f  浮点数
%b 二进制
%s string

字符串查找

如何获取字符串中的某一段字符?

  • strings.Index(): 正向搜索子字符串
  • strings.LastIndex():反向搜索子字符串
	// 查找
	tracer := "小白你好,小白拜拜"
	// 正向搜索字符串
	comma := strings.Index(tracer, ",")
	fmt.Println(",所在的位置:",comma)//,所在的位置: 12
	fmt.Println(tracer[comma+1:])  // 小白拜拜

	add := strings.Index(tracer, "+")
	fmt.Println("+所在的位置:",add)  // +所在的位置: -1

	pos := strings.Index(tracer[comma:], "小白")
	fmt.Println("小白 所在的位置", pos) // 小白 所在的位置 1

	fmt.Println(comma, pos, tracer[5+pos:])  // 12 1 你好,小白拜拜

06类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:

a := 5.0
b := int(a)

类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将 int16 转换为 int32)。

当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失的情况。

只有相同底层类型的变量之间可以进行相互转换(如将 int16 类型转换成 int32 类型),不同底层类型的变量相互转换时会引发编译错误(如将 bool 类型转换为 int 类型)

 	// 初始化一个32位整型值
    var a int32 = 1047483647
    // 输出变量的十六进制形式和十进制值
    fmt.Printf("int32: 0x%x %d\n", a, a)
    // 将a变量数值转换为十六进制, 发生数值截断
    b := int16(a)
    // 输出变量的十六进制形式和十进制值
    fmt.Printf("int16: 0x%x %d\n", b, b)
    // 将常量保存为float32类型
    var c float32 = math.Pi
    // 转换为int类型, 浮点发生精度丢失
    fmt.Println(int(c))
//int32: 0x3e6f54ff 1047483647
//int16: 0x54ff 21759
根据输出结果,16 位有符号整型的范围是 -3276832767,而变量 a 的值 1047483647 不在这个范围内。1047483647 对应的十六进制为 0x3e6f54ff,转为 int16 类型后,长度缩短一半,也就是在十六进制上砍掉一半,变成 0x54ff,对应的十进制值为 21759int16截取了int32的后四位,再根据这个十六进制数得出十进制数字,可见进度转换高精度转低精度的风险

07练习

将“birthday:8-30”修改为“birthday:8-31”?

通过类型转换实现,先将string类型转换为字符数组进行修改,再转换为新的string类型

s1 := "birthday:8-30"
fmt.Println(s1)
strByte := []byte(s1)
strByte[len(s1)-1]='1'
fmt.Println(strByte)
s2 := string(strByte)
fmt.Println(s2)

字符串替换, 比如将 “Hello, 小白的C++博客” 替换为 “Hello, 小白的Go博客”

思路:

  1. 找到C++所在的位置
  2. 根据C++的长度将其分为两部分
  3. 加上Go总共三部分,进行拼接
str1 := "Hello,小白的C++教程"
source := "C++"
target := "Go"
fmt.Println(str1)//Hello,小白的C++教程
index := strings.Index(str1,source)
sourceLength := len(source)
start := str1[:index]
end := str1[index+sourceLength:]
var stringBuffer bytes.Buffer
stringBuffer.WriteString(start)
stringBuffer.WriteString(target)
stringBuffer.WriteString(end)
fmt.Println(stringBuffer.String())//Hello,小白的Go教程

三.常量指针

01常量

1.1常量的基本使用

Go语言中的常量使用关键字const定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型数字型(整数型、浮点型和复数)和字符串型

由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。

声明格式:

const name [type] = value
例如:
const pi = 3.1415926

注意:

1.type可以省略

2.和变量声明一样,可以批量声明多个常量:

const (
    e  = 2.7182818
    pi = 3.1415926
)

所有常量的运算都可以在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof。

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:

const (
    a = 1
    b
    c = 2
    d
)
fmt.Println(a, b, c, d) // 1 1 2 2

1.2iota 常量生成器

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。

在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加1

举个栗子,定义星期日到星期六,从0-6(有点像C语言的enum结构)

const (
    Sunday  = iota 	//0
    Monday			//1
    Tuesday			//2
    Wednesday		//3
    Thursday		//4
    Friday			//5
    Saturday    	//6
)
    pi = 3.1415926
)

所有常量的运算都可以在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof。

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:

const (
    a = 1
    b
    c = 2
    d
)
fmt.Println(a, b, c, d) // 1 1 2 2
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值