新濠天地 > 编程网新闻 > 转摘本文也请注明出处,学习笔记兼吐槽

原标题:转摘本文也请注明出处,学习笔记兼吐槽

浏览次数:197 时间:2019-09-20

Go:学习笔记兼作弄

正文内容是自家对Go语言的变量、常量、数组、切成块、映射、结构体的备忘录,记录了重大的连锁知识点,以供翻查。

Go:学习笔记兼调侃

文中如有错误的地方请大家建议,避防误导!转摘本文也请注解出处:Go语言备忘录(1):基本数据结构,多谢!**

Go:学习笔记兼戏弄

 参谋书籍《The Go Programming Language》、《Go In Action》、《Go语言学习笔记》等


目录:

数组

Golang 中,数组是值类型。

  1. 变量
  2. 常量
  3. 数组
  4. 切片
  5. 映射
  6. 结构体

数组的扬言

vararr[10]int

一、变量

数组的开首化

vararr1[3]int=[3]int{1,2,3}
vararr2=[3]int{4,5,6}
vararr3=[...]int{7,8,9}
vararr4=[...]int{1:100,2:200,3:300}
  •  变量是一段或多段用来存储数据的内部存款和储蓄器;
  • 变量总是有一定的数据类型,类型决定了所占内部存储器的长度和累积格式;
  • 编写翻译后的代码应用变量的内部存款和储蓄器地址来拜见数据,并非变量名;
  • 回顾变量表明只可以在函数内注明(局地变量),var证明方式则无界定(但貌似用来评释未显式起先化的变量);
  • 声称同一成效域中的同名变量时,将回退为赋值,即重用该变量(必得至少有多少个新变量定义);
  • 而注解差别效率域的同名变量则为重新定义(覆盖);

    var q int var y = 453 var (

    n,m = 134,"srf"
    n1,m1 int 
    

    ) func f1() {

    n,m := 25,"sss" 
    n,m1 := 34,"yyy"
    fmt.Println(n,m,m1)
    n = n+5 //赋值表达式中,首先计算右值
    //“_”空标识符用来临时规避编译器对未使用变量和导入包的错误检查
    if _,ok := add1(n);ok {
        fmt.Println(n)
    }
    

    } func add1(n int) (int, bool) {

    return n+1,true
    

    }

用 for-range 遍历数组

中央语法:

forindex,value:=range数组变量{
}

其中:index 为数组下标,value 是该下标地点的值。

  

长度是数组类型的一某个

长度是数组类型的一片段,正是说数组无法脱离长度而存在。听上去不太精通,大家来看上面包车型地铁一个示范就掌握了,那确实是八个南生围。

假若,大家今后要写多少个排序函数,C# 中,大家会如此定义:

publicvoidSort(int[]array)
{
}

不过,在 Golang 中,这是万分的。

funcmain(){
vararr[3]int=[3]int{1,2,3}
Sort
}

funcSort(array[]int){
}

Sort 这句编写翻译就能够报错:cannot use arr (type [3]int) as type []int in argument to Sort。因为 Sort 函数的参数 array []int 是多少个切成片,不是数组,将数组作为参数字传送给 Sort 就能够报类型不相称。

假如一定供给以数组作为参数字传送递,Sort 的参数必需定义成数组,正是带上长度:

funcSort(array[3]int){
}

那般定义那函数还应该有甚用?作弄贰万字…
纵然有切成丝能够用来贯彻大家的效果,但是,数组就变得有一点点鸡肋了。

二、常量、枚举

切片 slice

切开是引用类型,类似于 C# 中的 list 。内部维护多个数组,当追港成分跨越切成块容积时,切成丝自动扩容。(跟 list 是同等的编写制定。)

  • 常量是叁个不得改变的值,它可以为字面量,或编写翻译器能预计出结果的表明式。未接纳的常量不会挑起编写翻译错误;
  • 在常量组中如不钦赐项目和始发值,则与上一行非空常量右值同样;
  • 常量会被编写翻译器在预处理阶段直接进行,作为指令数据应用,所以不或然取常量的地方;

    const i = 5 const (

    x byte = 1
    x1
    x2       //x1,x2均为1
    s = "abs"
    s1       //s1=“abc”
    

    ) const (

    _,_ int = iota,iota*3 //0,0*3 忽略值,并显式指定类型为int
    k1,k2             //1,1*3
    l1,l2             //2,2*3
    o1,o2 = 5,6       //中断iota自增
    r1,r2             //5,6  同上一行
    e1,e2 = iota,iota*3 //5,5*3  恢复iota自增,按行递增
    

    ) //枚举 type color byte const (

    blue color = iota
    red
    green
    

    ) func main() {

    t:= blue
    fmt.Println(t) //0
    //fmt.Println(&i) //错误:无法对常量取地址 cannot take the address of i
    

    }

切开的证明

vararr[]int

  

切开的运用

//方法一:
vararr1[5]int=[5]int{1,2,3,4,5}
slice1:=arr1[1:3]//这里的使用跟Python很像

//方法二:
varslice2[]int=make([]int,5,10)

//方法三:
varslice3[]int=[]int{1,2,3,4,5}

使用 make 初阶化切成丝,make 的多少个参数依次为:切成片数据类型,切条长度,切成块容积。

三、数组

给切成条追台币素

//方法一:追加一个或多个同类型
varslice1[]int=make([]int,5,10)
slice1=append(slice1,100,200)
fmt.Printf("%vn",slice1)

//方法二:追加切片(只能是切片,不可以是数组)
varslice2[]int=[]int{1,2,3,4,5}
slice1=append(slice1,slice2...)//三个点不能少
fmt.Printf("%v",slice1)

append 函数也很滑稽,其重临值必需赋值给叁个切开,不然编写翻译都过不了。假使贰个切开调用 append 追英镑素后,又赋值给了团结(我们一般也是那样用的),则切成块的地点不会发出更动。假使 切片 1 调用 append 后赋值给了 切片 2,则 切片 1 保持未扩展前的面目不改变,另生成贰个新的切成片赋给 切片 2

示例:

varslice1[]int=make([]int,5,10)
fmt.Printf("%v%pn",slice1,&slice1)//[00000]0xc000004460

slice1=append(slice1,100)
fmt.Printf("%v%pn",slice1,&slice1)//[00000100]0xc000004460

slice2:=append(slice1,200)
fmt.Printf("%v%pn",slice1,&slice1)//[00000100]0xc000004460
fmt.Printf("%v%pn",slice2,&slice2)//[00000100200]0xc0000044e0

数组是切条和照耀的基本功数据结构。数组是值类型,在赋值和传递数组时将拷贝整个数组。

映射 map

便是字典。

数组是三个尺寸固定的数据类型,存款和储蓄着一段具备同等数据类型成分的连年内部存款和储蓄器块。

map 的声明

varmmap[int]string

因为数组占用的内部存储器是连接分配的,所以对数组成分的询问、修改等操作速度异常的快。

map 的使用

//方式一:使用make函数
m:=make(map[int]string,10)

//方式二:直接赋值
m:=map[int]string{
1:"张三",
2:"李四",
}

make 方法的率先个参数是 map 的数据类型,第一个参数是从头容积。

只顾,假使是情势二直接赋值,最终一个 key-value 后边也要加逗号。

扬言数组的格局:

删除成分

delete(map,key)

参数:

  • map:要去除成分的 map
  • key:要去除的 key,当 key 在 map 中不设临时,不进行任何操作,也不报错。

Golang 中 map 未有看似别的语言中的 clear 方法,要是要一回性删除全体成分,可遍历 map 逐条删除,可能重新 make 一下使其针对性贰个新的内部存款和储蓄器空间。

  • var array1 [5]int
  • array1 := [5]int{3,5,6,3,2}
  • array1 := [...]int{3,4,7,8,1} //按照数组字面量兰月素的个数来鲜明数组的长短
  • array1 := [5]int{0:3,3:5,4:8} //只开头化钦点索引的因素,其他成分保持零值
  • array1 := [...]int{1,2,9:32}

招来成分

val,finded:=m[1]
iffinded{
fmt.Println
}

数组成分的档期的顺序可认为其余内置类型,也足以是某种结构类型,也能够是指针类型。

遍历成分

只能用 for-range 遍历

fork,v:=rangem{
fmt.Printf("%v:%vn",k,v)
}

数组变量的项目包含数经理度和因素的项目,唯有两有的都一模一样的数组才可互相赋值。

结构体 struct

  • Golang 中绝非类,Go 中的结构体和其他语言中的类有同等的地点。能够知道为 Golang 是依据struct 来促成面向对象。
  • 结构体是值类型。结构体的保有字段在内部存款和储蓄器中是连连的。

多维数组:数组自身唯有一个维度,只可以通过整合多个数组来创建多维数组;内置函数len、cap均重临第一维度的尺寸

结构体的宣示

type结构体名称struct{
field1type
field2type
}
  • var array [4][2]int
  • array := [4][2]int{2:{20,21},3:{41,25}}
  • array := [4][2]int{2:{1:21},3:{0:41}}
  • array := [...][4]int{{2,3},{4,5}} //仅第一维度允许利用“...”
  • array[2][1] = 10

结构体的利用

typePersonstruct{
Namestring
Ageint
}

// 方式一:
p1:=Person{}
p1.Name="Tom"
p1.Age=10

//方式二
p2:=Person{"Jerry",5}

// 方式三
p3 := Person{Name: "张三", Age: 30}
// 或
p3 := Person{
    Name: "张三",
    Age: 30,    // 注意这里要加逗号,否则会被默认加上分号
}

在函数间传递数组:由于在函数间传递变量时,传递的连年变量的值的副本,因为数组是值类型,所以在赋值和传递数组变量时将拷贝整个数组!在概念函数时,对于相当大的数据类型应该把参数设计为指针类型,那样在调用函数时,只需在栈上分配给种种指针8字节的内部存款和储蓄器,但那象征会改动指针指向的值(分享的内部存款和储蓄器),其实很多气象下应当使用切丝类型,并不是数组。

结构体指针

//方式一:
varperson1*Person=new
.Name="Tom"
.Age=10
fmt.Println

//方式二:
person2:=new
person2.Name="Tom"
person2.Age=10
fmt.Println

//方式三:
varperson3*Person=&Person{"Jerry",5}
fmt.Println

那二种方法定义的都以结构体指针,因为是指针,所以给字段赋值的科班措施应该是格局一的写法,可是Go 的设计者为了技术员使用方便,给出了几个语法糖,使 .Name = "Tom" 简化为 person1.Name = "Tom",即情势二的写法,编写翻译时,会自行抬高取值运算。而艺术三的写法能够一向赋值。

留意:因为切块的底部数组恐怕会在堆上分配内部存款和储蓄器,对于小数组在栈上拷贝的消耗也许比make代价小;

结构体标签

struct 的各种字段上得以定义二个标签,该标签能够通过反射机制获得,最布满的施用情况正是连串化和反连串化。

typePersonstruct{
Namestring`json:"name"`
Ageint`json:"age"`
}

p:=Person{"张三",30}
jsonStr,err:=json.Marshal
iferr==nil{
fmt.Println(string//{"name":"张三","age":30}
}

四、切片slice

自定义数据类型

为了简化数据类型定义,Golang 帮助自定义数据类型。

基本语法:

type自定义数据类型名数据类型//相当于起了一个别名

示例:

typemyintint//这时myint就等价于int,但是Go会认为他们还是两个类型
typemySumfunc(int,int)int//这时mySum就等价于一个函数类型

自定义数据类型跟原类型固然在大家的知情上是一模一样的,可是 Golang 会认为它们是两种差异的数据类型。那导致这二种档案的次序是无计可施直接举行比较的,必得强转。

  • 切开slice是援引类型,它里面通过指针援引二个尾部数组,并设定相关属性将数据的读写操作限定在钦点区域。

    //切成块自身是个只读对象,工作机制就像数组指针的一种包装 type slice struct{

    array unsafe.Pointer
    len int //可读写的元素数量
    cap int //所引用数组片段的真实长度
    

    }

创造和早先化:

  • slice1 := make( []string, 5 ) //创立一个长度、体积都为5的string类型的切成条
  • slice1 := make( []string, 3, 5 ) //创立三个尺寸为3,体积为5的string类型的切成条
  • slice2 := []string{ "red","blue","green" } //长度和容积均为3的切成条
  • slice2 := []int{ 99:1 } //长度和容积均为100,并早先化第九十八个因素为1

再次切成丝reslice:以最初和终止原切成块的目录地方鲜明所引述的数组片段,不援救反向索引,实际范围是七个右半开区间
万一原切成片slice体积为k,新切丝newSlice为原切成片的索引 i 成分地方上马,在原切成块的体量范围内取值

  • newSlice := slice[ i : j ]  //长度为j-i,容量为k-i
  • newSlice := slice[ i : j : n ] //限制新切成片的体量为n-i(第八个参数n-1表示新切丝可扩张到的末段三个凸现的底部数组部分的成分索引,那样就完结了限定体量的指标,注意:n必得>=j)
  • 新切丝不能访问它所指向的最底层数组的第多少个成分以前的一些(第二个目录以前的一些)
  • 例子:ss:=[]int{10,20,30,40,50}       newss:=ss[2:4:5]   //newss为[30,40],容量为3
  • 新切块和旧切成块指向同一个后面部分数组;

    //利用reslice达成四个栈式结构(也可将stack定义为叁个品种) var stack = make([]int,0,5)

    func push(x int) error {

    n:=len(stack)
    if n == cap(stack) {
        return errors.New("stack is full")
    }
    stack = stack[:n+1] //新的stack增加了一个可访问元素stack[n]
    stack[n]=x
    return nil
    

    } func pop() (int, error) {

    n:=len(stack)
    if n == 0 {
        return 0,errors.New("stack is empty")
    }
    x:=stack[n-1]
    stack = stack[:n-1] //新的stack减少了一个可访问元素stack[n-1]
    return x,nil
    

    } func main() {

    for i := 0; i < 7; i++ {
        fmt.Printf("push %d: %v,%vn",i,push(i),stack)
    }
    for i := 0; i < 7; i++ {
        x,err:=pop()
        fmt.Printf("push %d: %v,%vn",x,err,stack)
    }
    

    }

切开的尺寸能够按需自行增加或缩短:

  • 动态拉长是通过append()函数达成的
  • 压缩则是由此对它再一次切成片来兑现,通过重新切条得到的新切成条将和原切成丝分享底层数组,它们的指针指向同八个底层数组。

是因为切成丝只是引用了尾部数组,底层数组的数据并不属于切成条自个儿,所以三个切开只须求24字节的内部存款和储蓄器(在六十十个人机器上):指针字段8字节、长度字段8字节、容积字段8字节。所以在函数之间一贯传送切丝是高效的,只需分配24字节的栈内部存款和储蓄器。

nil切条和空中接力成片:

  • nil切丝:只注脚,但未早先化的切丝,如var slice1 []int,nil切丝可用来描述叁个不设有的切条
  • 空接成丝:长度和体量均为0的切块,创造空接条时未对底层数组成分分配任何内部存储器,可用来表示空集结,如slice1 := make( []int, 0 ),slice2 := []int{}
  • 对nil切块和空切成片调用内置函数append、len、cap的法力等同

append()函数:
slice = append(slice, elem1, elem2)  //二次可扩充八个值
slice = append(slice, anotherSlice...)  //使用“...”将anotherSlice的有着因素追加到slice里

  • 当slice还大概有可用的容积时,append()操作将可用的成分合併到切成条的长度,并对其赋值,最后回到贰个全新的切丝(和旧切条分享同一个平底数组);
  • 假定slice的体积不足时,append()操作会创造二个新的最底层数组,并将被援用的旧值复制到新数组里,然后追加新的值;
  • 原切条体量不足时,且小于一千,则新切成丝的体量为原体量的2倍,若抢先一千,则体积的滋长因子变为1.25;
  • 是因为体积不足时,append操作会重回贰个具备友好独自的底部数组的新切成丝,即与原切成块不分享同一底层数组,对新切丝成分的修改将不会影响原切成丝的平底数组,技艺:在创建切块时设置长度等于容积,那样就可以强制在第一回append操作时创建新的底层数组,达到与原数组分离的目标,如newSlice := oldSlice[2:3:3]

copy函数:在七个切块对象时期复制数据,允许指向同八个底层数组,允许指标区间重叠。最后所复制长度以异常的短的切成片长度为准

  • 切开的迭代如:for index, value := range slice{ .... },index为最近迭代到的目录地点,value是从slice的别本中取值,index和value变量的内部存款和储蓄器地址是不改变的,只是指向的值在不断更新。
  • len函数可返还切成片的尺寸、cap函数可返还切成条的体积
  • 多维切成条(类似交错数组):切块和数组同样,本身是一维的,能够结合多少个切成条来产生多维切成块,如:slice := [][]int{ {12},{34,23} },slice[0]为{12},slice[1]为{34,23}
  • 小心:假如切块长日子援引大数组中极小的片段,则应该复制出所需数据,新建独立切成片,以便原大数组内部存款和储蓄器可被当下回收;

 

五、映射map

映射map:是贰个囤积键值对的冬季汇集,它能基于键急速找出数据,键就好像索引同样,指向与该键关联的值;

绚烂是冬辰的,每一次迭代它时顺序大概不均等,因为映射内部使用了散列表;

辉映的散列表满含一组桶,每一种桶里存款和储蓄着部分键值对;

照耀内部使用了四个数组:

  • 率先个数组:存款和储蓄着用于选取桶的散列键的高多少人值,该数组用于分别每种键值对要设有哪些桶里;
  • 第二个数组:每一种桶里都有一个字节数组,先逐个存储了该桶里的全数键,之后存款和储蓄了该桶的全数值;

在积攒、删除、查找映射的键值对时,会把钦定的键传给映射的散列函数,该函数把键转换为二个散列值,然后把该散列值与第贰个数组比对来摘取哪位桶,再到桶里的字节数组里寻找对应的键和值;

创建和起先化映射:

  •     dict1 := make(map[string]int) //空映射,等同于dict1 := map[string]int{}
        dict1 := make(map[string]int, 一千) //预先分配丰裕内存空间,有利于提高品质 
        dict2 := map[string]int{"srf":143,"robt":342}
  • 照耀的键:只可以是能用“==”做比较的门类,但不得以是切丝、函数、以及包含切成块的品种,因为他们有着引用语义。而映射的值则足以是即兴档次;
  • nil映射是只评释而未初阶化的照耀,不能直接动用,如var dict map[string]int。空映射则足以直接选取;
  • map类型的零值是nil,也正是未有援用任何哈希表。在向map存数据前必得先创建map,即:援引多少个哈希表。

若是要用map存储大量小指标,应该一向存款和储蓄为值类型,而非指针类型,有助于减弱供给扫描的对象数量,小幅度缩水垃圾回收时间;

从映射中取值:

  • value := dict2["srf"] //键存在时重回对应的值,子虚乌有时重返类型的零值
  • value, ok := dict2["srf"] //ok为键是还是不是留存的布尔标识
    if ok { ...... }
  • map中的成分并不是贰个变量,大家不能够对map的元素进行取址操作(因为map或然会在动态增进时重新分配内部存款和储蓄器),因而不可能直接修改value成员,而应当经过不常变量来修改,或把值定义为指针类型:
m := users[int]user{
    1:{"srf",25}
}
//m[1].age +=1 //错误,无法设置值
u := m[1]
u.age+=1
m[1] = u

遍历映射:

  • for key := range dict2 { ..... } //只接收键
  • for key, value := range dict2 { ...... } //同不时直接收键和值
  • 遍历映射时,能够拉长、删除成员
  • 遍历映射的键值对时的各种是自由,若要有序的获得映射的键值对,则必要先遍历出映射的键存到三个切开中,然后排序该切丝,最终遍历该切成块,按切成块中元素的次第去光彩夺目中取对应的值

delete(dict2,"srf") 从映射中剔除钦点的键值对;

运营时会对映射的产出操作做出检查测试,对映射的操作只好同步实行(同不经常刻只好有一个职责在操作映射),不然会导致进度崩溃。可用读写锁sync.揽胜WMutex达成联机,幸免读写操作同期开展:

func main() {
    var lock sync.RWMutex
    m:=make(map[string]int)

    go func() {
        for {
            lock.Lock()
            m["a"] += 1
            lock.Unlock()  //不能用defer

            time.Sleep(time.Microsecond)
        }
    }()

    go func() {
        for {
            lock.RLock()
            _ = m["b"]
            lock.RUnlock()

            time.Sleep(time.Microsecond)
        }
    }()

    select {} //阻止进程退出
}
  • 在函数间传递映射与传递切块同样(无须再一次取地址),传递的只是映射本身的别本,而不会复制映射所引述的保有底层数据结构,对该映射副本所做的修改将会呈现到具备对那么些映射的引用。
  • 多维映射:即值为照射类型的投射。使用时应小心,作为值的映射也急需开首化后本领利用,如:
        var m1 = make(map[int]map[string]string)
        m1[13]= map[string]string{"srf":"yes"}
  • 看清三个map是或不是等于的函数:

    func equal(x, y map[string]int) bool {

    if len(x) != len(y) {
        return false
    }
    for k, xv := range x {
        if yv, ok := y[k]; !ok || yv != xv {
            return false
        }
    }
    return true
    

    }

  • 用map来表示字符串的集合set: 

    m:=make(map[string]bool) if !m["srf"] {

    m["srf"] = true
    

    }

 

六、结构体

  • 组织体struct是一种复合类型,由三个例外门类的命名字段(田野先生)连串打包而成;
  • 字段名必得独一,可用“_”补位,支持选取自身的指针类型成员(那可以让大家创造递归的数据结构,比如链表和树结构等);
type node struct{
    _ int
    id int `账号`
    next *node
}
  • 组织体类型新闻包罗:字段名、字段标签、排列顺序,独有三者全体同一才可以为是同等档次;
  • 可按顺序开端化全体字段,但建议选拔命名格局伊始化部分或任何字段(可忽视字段的定义顺序,便于结构体成员相继的退换、扩充);
  • 结构体的比较:唯有当结构体的富有成员都以可正如的,那么该结构体才是可正如的
  • 可直接定义三个无名氏的协会体类型,并赋值给叁个变量,或用作字段的档期的顺序(无名氏结构体字段不可能用字面量情势间接起首化,供给“.”语法来初叶化其成员)

    u := struct{

    name string
    

    } type file struct{

    name string
    attr struct{
        owner int
        perm int
    }
    

    } f := file{name:"test.dat"} f.attr.owner = 1 f.attr.perm = 0755

  • 空结构:struct{},长度为0,不分配内部存款和储蓄器,它和别的具有“长度”为0的目的日常都指向runtime.zerobase变量(即它们都指向同三个变量);空结构类型平日作为通道成分的连串,用于事件通报(优点是不占内部存款和储蓄器);

佚名字段(嵌入类型):即未有一点点名显式的名号,唯有项目的字段:

  • 编写翻译器将隐式地以项目名作为字段名称(不含有包名);
  • 外层的结构体不仅仅获得了无名氏成员类型的装有成员,何况也得到了该品种全体的导出的秘技;
  • 可一直引用嵌入类型字段的成员,但在以字面量语法早先化时须显式开头化它的任何结构;
  • 佚名字段的分子的数据类型必得是命名的项目或针对三个命名的项指标指针,不可能是接口指针和层层指针;
  • 不可能将基础项目和其指针类型同时作为佚名字段
  • 字段重名管理:优先利用外层字段(内层字段被挡住了,只好通过一点一滴限定名来采访),对于多少个同样层级的同名字段也非得经过一丝一毫限定名来访谈,不然编写翻译器不能够鲜明目的;

字段标签(tag):用来对字段实行描述的元数据,它就算不属于数据成员,但却是类型音讯的组成都部队分;在运营期,可用反射来赢得字段的价签新闻,它常被当作格式核算(如JSON)、数据库关系映射等;标准库reflect.StructTag提供了分/剖判标签的成效;

type user struct{
    name string `昵称`
    sex byte `性别`
}
func main(){
    u:=user{"TOM",1}
    v:=reflect.ValueOf(u)
    t:=v.Type()

    for i,n:=0,t.NumField();i<n;i++{
        fmt.Printf("%s: %vn", t.Field(i).Tag, v.Field(i))
    }
}
  • 任由结构体有稍许个字段,它的内部存款和储蓄器总是二回性分配的,各字段在邻近的地址空间按定义顺序排列(富含嵌入字段的全体成员)。对于援引类型、字符串、指针,结构内部存款和储蓄器中只含有其大旨(底部)数据。
  • 结构体在分配内部存款和储蓄器时,会开展内部存款和储蓄器对齐管理(依照全体字段中最长的根基项目宽度为行业内部),唯一分裂是编译器把空结构类型字段作为最终贰个字段时的尺寸视为1来做对齐管理(制止越界)。
  • 内部存款和储蓄器对齐与硬件平台、以及会见功效有关(CPU在拜候自然对齐的数码时必要的读周期更加少,还可制止拼接数据)

 

本文由新濠天地发布于编程网新闻,转载请注明出处:转摘本文也请注明出处,学习笔记兼吐槽

关键词:

上一篇:次第设计竞技前读文件手艺

下一篇:没有了