精华内容
下载资源
问答
  • 多重指针 指针运算 到底什么是指针呢? 内存就是一系列有序列号的存储单元,变量就是编译器为内存地址分配的昵称,那么指针是什么呢? 指针就是一个指向另一个内存地址变量的值 指针指向变量的内存地址,指针...

    目录

    到底什么是指针呢?

    指针

    指针地址和指针类型

    指针取值

    指针变量初始化

    指针运算符

    多重指针

    指针运算


    到底什么是指针呢?

    内存就是一系列有序列号的存储单元,变量就是编译器为内存地址分配的昵称,那么指针是什么呢?

    指针就是一个指向另一个内存地址变量的值

    指针指向变量的内存地址,指针就像该变量值的内存地址一样

    我们来看一个代码片段

    func main() {
        a := 200
        b := &a
        *b++
        fmt.Println(a)
    }
    

    在 main 函数的第一行,我们定义了一个新的变量 a ,并赋值为 200。接下来我们定义了一个变量 b ,并将变量 a 的地址赋值给 b 。我们并不知道 a 的准确存储地址,但是我们依然可以将 a 的地址存储在变量 b 中。

    因为 Go 强类型的特性,第三行代码也许是最具干扰性的了,b 包含 a 变量的地址,但是我们想增加存储在 a 变量中的值。

    这样我们必须取消引用 b ,而是跟随指针由 b 引用 a。
    然后我们将该值加 1 后,存储回 b 中存储的内存地址上。

    最后一行打印了 a 的值,可以看到 a 的值已经增加为了 201

    指针

    Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量

    区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针

    要搞明白Go语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值

    指针地址和指针类型

    Go语言中的指针操作非常简单,只需要记住两个符号:&(取地址)和*(根据地址取值)

    每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。

    取变量指针的语法如下:

    ptr := &v    // v的类型为T

    其中:

    v:代表被取地址的变量,类型为T
    ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针。

    func main() {
        a := 10
        b := &a
        fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
        fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
        fmt.Println(&b)                    // 0xc00000e018
    }

    指针取值

     取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

    变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

    1.对变量进行取地址(&)操作,可以获得这个变量的指针变量。
    2.指针变量的值是指针地址。
    3.对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

    • 当一个指针被定义后没有分配到任何变量时,它的值为 nil

    指针变量初始化

    func main() {
        var a *int
        *a = 100
        fmt.Println(*a)
    
        var b map[string]int
        b["测试"] = 100
        fmt.Println(b)
    }
    
    
    // panic: runtime error: invalid memory address or nil pointer dereference
    //[signal 0xc0000005 code=0x1 addr=0x0 pc=0x49a7ca]

    执行上面的代码会引发panic,为什么呢?

    •  在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。
    • 而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间

     Go语言中new和make是内建的两个函数,主要用来分配内存

    func new(Type) *Type
    func make(t Type, size ...IntegerType) Type

    1. 二者都是用来做内存分配的。
    2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身
    3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

    指针运算符

    1.指针运算符为左值时,我们可以更新目标对象的状态;而为右值时则是为了获取目标的状态。

    func main() {
        x := 10
        var p *int = &x  //获取地址,保存到指针变量
        *p += 20        //用指针间接引用,并更新对象
        println(p, *p)  //输出指针所存储的地址,以及目标对象
    }
    

    输出:

    0xc000040780 30
    

    2.指针类型支持相等运算符,但不能做加减运算和类型转换。如果两个指针指向同一地址,或都为nil,那么它们相等。

    func main() {
        x := 10
        p := &x
    
        p++   //编译报错 invalid operation: p++ (non-numeric type *int)
        var p2 *int = p+1  //invalid operation: p + 1 (mismatched types *int and int)
        p2 = &x
        println(p == p2)   //指向同一地址
    }
    

    可通过unsafe.Pointer将指针转换为uintptr后进行加减法运算,但可能会造成非法访问。


    多重指针

    指针可以指向任何类型的变量。所以也可以指向另一个指针。以下示例显示如何创建指向另一个指针的指针:

    package main
    
    import "fmt"
    
    func main() {
    
    	var a = 3.141596
    	var p = &a
    	var pp = &p
    
    	fmt.Println("a = ", a)
    	fmt.Println("p = ", p)
    	fmt.Println("pp = ", pp)
    
    	fmt.Println("&p = ", &p)
    	fmt.Println("&pp = ", &pp)
    
    	fmt.Println("*p = ", *p)
    	fmt.Println("*pp = ", *pp)
    	fmt.Println("**pp = ", **pp)
    
    	//a =  3.141596
    	//p =  0xc00008e060
    	//pp =  0xc000090018
    	//&p =  0xc000090018
    	//&pp =  0xc000090020
    	//*p =  3.141596
    	//*pp =  0xc00008e060
    	//**pp =  3.141596
    
    }
    

    指针运算

    在很多 golang 程序中,虽然用到了指针,但是并不会对指针进行加减运算,这和 C 程序是很不一样的。Golang 的官方入门学习工具(go tour) 甚至说 Go 不支持指针算术。虽然实际上并不是这样的,但我在一般的 go 程序中,好像确实没见过指针运算(嗯,我知道你想写不一般的程序)。

    • 但实际上,go 可以通过 unsafe.Pointer 来把指针转换为 uintptr 类型的数字,来实现指针运算。
    • 这里请注意,uintptr 是一种整数类型,而不是指针类型。

    比如:

    uintptr(unsafe.Pointer(&p)) + 1
    

    就得到了 &p 的下一个字节的位置。然而,根据 《Go Programming Language》 的提示,我们最好直接把这个计算得到的内存地址转换为指针类型:

    unsafe.Pointer(uintptr(unsafe.Pointer(&p) + 1))
    

    因为 go 中是有垃圾回收机制的,如果某种 GC 挪动了目标值的内存地址,以整型来存储的指针数值,就成了无效的值。

    同时也要注意,go 中对指针的 + 1,真的就只是指向了下一个字节,而 C 中 + 1 或者 ++ 考虑了数据类型的长度,会自动指向当前值结尾后的下一个字节(或者说,有可能就是下一个值的开始)。如果 go 中要想实现同样的效果,可以使用 unsafe.Sizeof 方法:

    unsafe.Pointer(uintptr(unsafe.Pointer(&p) + unsafe.Sizeof(p)))
    

    最后,另外一种常用的指针操作是转换指针类型。这也可以利用 unsafe 包来实现:

    var a int64 = 1
    (*int8)(unsafe.Pointer(&a))
    

    如果你没有遇到过需要转换指针类型的需求,可以看看这个项目(端口扫描工具),其中构建 IP 协议首部的代码,就用到了指针类型转换。

    展开全文
  • c语言指针和多重指针

    2020-04-28 13:43:08
    数据结构是一种思想,不限制于某一种语言,但再各种语言实现中却有许多小细节不可忽视,比如c语言中的指针,是个让人十分头疼的问题。 先以链表为例,这里定义了链表的结点表示,data为该结点存储的数据,next是指向...

    数据结构是一种思想,不限制于某一种语言,但再各种语言实现中却有许多小细节不可忽视,比如c语言中的指针,是个让人十分头疼的问题。

    先以链表为例,这里定义了链表的结点表示,data为该结点存储的数据,next是指向该节点的数据,
    注:下文的所有代码都只是简化的代码,并不能上机运行。

    typedef struct node
    {
    	Element data;
    	struct node *next;
    }Node;
    

    这是链表一个结点的样子
    在这里插入图片描述

    为防止C初学者迷惑,做个例子
    Node a -->int b; 这里Node就是类型,声明一个Node结构体a,跟声明整型变量b是一样的。

    Node *c -->int *d; c是指向Node结构体的指针,d是指向整型数据的指针。他们的本质也是一样的。

    在链表的操作中,有对指针的初始化,清空链表,增加和删除链表等等,接下来我们分两种情况讨论,第一种是没有头节点,第二种是有头节点

    1: 没有头节点的单链表中,即第一个结点就存储数据的链表,如下图所示是有四个结点的无头结点单链表。
    在这里插入图片描述
    下面演示无头节点空链表的添加结点操作,在实际中没有意义,但很好理解c数据结构指针的问题。

    //第一段代码
    void Add(int num,Node *list2)
    {	
    	list2 = (Node *)malloc(sizeof(Node));
    }
    int main()
    {	
    	Node *list = NULL;
    	Add(1,list);
    	return 0;
    }
    

    第一段代码是错误的操作,下面是正确的

    //第二段代码
    void Add(int data,Node **list2)
    {
    	*list2 = (Node *)malloc(sizeof(Node));
    }
    int main()
    {
    	Node *list = NULL;
    	Add(1,&list);
    	return 0;
    }
    

    因为第一段代码在main函数中声明了指向Node的指针,想通过调用Add函数使list指向新的结点,但实际程序是这样做的,在Addt中临时创建指向Node的指针list2,把list的值赋给了list2,然后把list2指向新节结点,然后Addt函数执行完,list2变量被销毁,list原封不动,还是指向NULL。

    而第二段代码中,把list的地址传递给InitList函数,在InitList中创建了指向指针的指针list2,即list2指向一个指针,这个指针是指向Node结构体的指针。即list2 = &list,然后又执行*list2 = (Node *)malloc(Node),因为指针list2的值就是指针list的地址,即list = (Node *)malloc(Node)。实参list的值原先指向NULL,现指向新结点。
    这种现象在无头节点的链表的许多操作中都会出现,比如头插法,清空链表,初始化链表等等

    接下来看有头节点的链表添加操作,即头节点不存储数据的单链表,如下图所示是有四个结点的有头结点单链表,四个节点中,三个是存放真实数据,头节点的数据域也不是没用,一般用来存放链表个数。

    在这里插入图片描述
    下面的代码是有头节点链表的添加操作

    //第三段代码
    void Add(int data,Node *list2)
    {	
    	list2->next = (Node *)malloc(sizeof(Node));
    	list2->data ++;
    }
    int main()
    {
    	Node *list = (Node *)malloc(sizeof(Node));
    	list->data = 0;//链表数据域是有效数据的结点个数
    	Add(1,list);
    	//执行完Add(list)后,list->data已变为1;
    	return 0;
    }
    

    可能会以为第三段代码是错误的,添加不上代码,其实是正确的,程序在Add函数中声明了指向结构体Node的指针list2,并且list赋值给list2,即list和list2都指向了同一个结点
    在这里插入图片描述
    然后执行了 list2->next = (Node *)malloc(sizeof(Node));即通过形参list2改变了实参list所指向的Node结构体中指针null的值,list的值并未发生改变,也与第一段代码失败的结论相照应。执行完函数,list2被摧毁,而链表已完成添加结点的操作。

    可以看出当没有头节点时,在空链表中添加元素,改动的是头指针本身的值,所以需要在函数中传递指针的指针,而有头结点时,改变的是头指针所指向结构体的值。从而发现有头节点的链表很大程度上简化了无头结点链表的操作,使很多需要单独判断的情况消失。

    树和图也有类似需要注意的指针现象,因为树结构没有头节点等等原因。

    补充:如果想在没有头节点的链表的操作中不使用双重指针的函数,可以用下面的思想,在函数中返回指针,并在main函数中用头指针list接收

    Node *Add(int data,Node *list2)
    {
    	//添加结点的代码
    	return list2;
    }
    
    int main()
    {
    	Node *list = NULL;
    	list = Add(1);
    	list = Add(2);
    	return 0;
    }
    

    c的指针是真的有魅力,java实现的数据结构都看不到好多细节。

    展开全文
  • 一个指向指针指针变量必须如下声明,即在变量名前放置俩个*号,例如,下面声明了一个指向int 类型的指针: int **p, p的类型是int** 当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用2个*号运算符,...

    入门介绍

    • 一个指向指针的指针变量必须如下声明,即在变量名前放置俩个*号,例如,下面声明了一个指向int 类型的指针:   int **p, p的类型是int**
    • 当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用2个*号运算符,比如 **p

     代码演示

    #include<stdio.h>
    int main()
    {
    	int a;
    	int *p;
    	int **pp1;
    	a=100;
    	p=&a;
    	pp1=&p;
    	printf("a的地址=%p,a=%d \n",&a,a);
    	printf("p的本身地址为=%p,p存放的地址为=%p,*p=%d\n",&p,p,*p);
    	printf("pp1本身的地址为=%p, pp1存放的地址为=%p,**pp1=%d",&pp1,pp1,**pp1); 
    	return 0;
    }

    查看结果 

    p存放的地址就是a的地址

    pp1存放的地址是p本身的地址

     

    展开全文
  • golang多重指针

    2019-09-05 01:24:26
    1,首先看普通指针: 什么是指针? Runnob:一个指针变量指向了一个值的内存地址 我们知道golang中指针的基本语法是: 通过var Name *type, 开辟指针: 然后通过&操作符使指针指向一个值的内存地址,这个指向的...

    1,首先看普通指针:

    什么是指针?
    Runnob:一个指针变量指向了一个值的内存地址
    我们知道golang中指针的基本语法是:
    通过var Name *type, 开辟指针:
    然后通过&操作符使指针指向一个值的内存地址,这个指向的内容可以是一个已经声明好的变量:
    var a int = 5
    var ptr *int
    ptr=&a
    

    通过这种方式,我们创建了一个已经指向了具体内存地址的指针变量
    现在我们可以分别查看&ptr, ptr , *ptr的值是什么,直接在fmt.Print()中输出即可:

    package main
    
    import "fmt"
    
    func main(){
    	var a int=5
    	var ptr *int
    	ptr = &a
    	fmt.Println(&a,&ptr, ptr, *ptr)
    }
    
    我这里得到的结果是地址A,地址B,地址A,一个数这种格式的输出结果在这里插入图片描述
    可以看出&a和ptr是同样的结果,说明ptr显示的就是他所指向的地址,而&ptr则是该指针自己的存储地址, 而最后*ptr显示的就是指向的地址所对应的变量值。

    2,更复杂的多重指针

    现在我们已经有一个一个创建好的指针和变量,并且他们已经产生了联系,在使用多重指针之前先看一下这个:
    var pts *int
    pts = ptr
    
    创建了另一个指针变量并且给他赋值ptr,那对应的&pts, pts, *pts会显示什么结果呢?这个比较简单可以先说答案,最后再看测试结果,他分别对应的是pts指针所在的地址(一个新的地址),pts指向的地址(这里就是a的地址),以及pts指针指向的地址对应的值(同样是a的值)
    接下来使用更复杂的二重和三重指针:
    	//二级指针,指向一个地址,这个地址存储的是一级指针的地址
    	var pto **int = &ptr
    	//三级指针,指向一个地址,这个地址存储的是二级指针的地址,二级指针同上
    	var pt3 ***int = &pto
    

    这里我创建了一个二级(重)指针,使得他和ptr指针对应的地址绑定也就是说pto这个指针指向了ptr,而对应的三重指针pt3指向了二重指针pto,那么现在的对应关系是

    pt3 - > pto - > ptr - > a
    接下来依次查看如下内容:
    	fmt.Println("a的地址:",&a,
    				"\n 值", a, "\n\n",
    
    				"ptr指针所在地址:",&ptr,
    				"\n ptr指向的地址:",ptr,
    				"\n ptr指针指向地址对应的值",*ptr,"\n\n", 
    
    				"pts指针所在地址:",&pts,
    				"\n pts指向的地址:", pts,
    				"\n pts指针指向地址对应的值:",*pts,"\n\n", 
    
    				"pto指针所在地址:",&pto,
    				"\n pto指向的指针(ptr)的存储地址:",pto, 
    				"\n pto指向的指针(ptr)所指向的地址: " ,*pto, 
    				"\n pto最终指向的地址对应的值(a)",**pto,"\n\n",
    
    				"pt3指针所在的地址:",&pt3,
    				"\n pt3指向的指针(pto)的地址:",pt3,//等于&*pt3,
    				"\n pt3指向的指针(pto)所指向的指针的(ptr)地址", *pt3, //等于&**pt3,
    				"\n pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a):",**pt3, //等于&***pt3,
    				"\n pt3最终指向的地址对应的值(a)", ***pt3)
    
    
    
    返回结果如下:

    在这里插入图片描述

    这个返回结果可以很清晰地看出这几个指针变量之间对应的关系,并且pt3和&*pt3的值是一样的,下面的三个?的也是如此,这种感觉有点像抵消,实际上我的个人理解是星号和&这两个操作确实是朝着两个方向,这里我表述的不太清楚,可以自行体会。
    展开全文
  • 以前对指针的概念很模糊,只知道指针和地址相关,但是最近看了一本《深入理解计算机系统》,感觉好像有点开窍了,再加上stm32的寄存器开发,以及最近在Linux中总是看到多重指针,于是花了点时间去理顺一下,在这做个...
  • 如果对多重指针不理解,可以结合着数组来考虑。定义一个数组,int a[10],这是一个大小为10个整型变量所占空间的数组。a是数组名,同时a也是数组的首地址。(a + 1)指向数组的下一个元素,也就是指向a[1]。想要使用...
  • 数组是存放一批同类型数据的容器,其中的元素可以是整型、浮点型、字符,也可以是指针型等,存放内存地址的数组叫做指针数组,就像返回指针类型的函数一样(被称为指针函数)。 定义指针数组的方式: 类型名 * 数组名 ...
  • 指针数组和多重指针

    千次阅读 2016-09-11 10:49:58
    一个数组,若其元素均为指针类型数组,称为**指针数组**,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
  • [C++]多重指针

    千次阅读 2019-03-10 11:44:52
    首先,声明一个二重指针 int **a; 这里**a可以看作是一个整型变量,*a则存储的是**a这个变量的地址,a存储*a的地址。由于**a未被初始化,只是被声明,系统没有分配给**a存储空间,所以*a没有被赋值初始化,a同理。...
  • 从本质讲解指针,指针数组,多重指针,数组指针以及与数组之间关系1.为什么需要指针?2.为什么需要指针数组?3.为什么需要多重指针?4.为什么需要数组指针? 由于下面我会多次说到关于地址和数据这两个词,所以为了...
  • 多重指针操作

    千次阅读 2017-06-06 12:04:01
    之前对多重指针操作心存忐忑,不能很熟练使用,本质原因是不了解其实质,因此对其进行了学习。   一、简单的代码如下   #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &...
  • 单链表插入函数的形参为什么要用多重指针(C语言)
  • C语言多重指针问题——自我理解

    千次阅读 2018-07-14 15:26:15
    在STM32嵌入式开发系列中,因为是C语言写代码为主,所以对于指针的使用十分的常用。例如在指导书中经常这么写: // GPIOH 端口全部输出 高电平2 *(unsigned int*)(0x4002 1C14) = 0xFFFF; 0x4002 1C14 在我们看来是...
  •   C语言指针基础知识点(一)–指针指针变量   C语言指针基础知识点(二)–指针变量的引用   C语言指针基础知识点(三)–指针变量作为函数参数   C语言指针基础知识点(四)–通过指针引用数组   C语言指针...
  • 二重指针以及多重指针的分析方法

    千次阅读 2016-09-28 22:59:11
    u-boot中有这么一段代码。 .../*这里定义了一个新的数据类型init_fnc_t, ... *这个数据类型是参数为空,.../*init_sequence是一个指针数组,指向的是init_fnc_t类型的函数*/ init_fnc_t *init_sequence[] = {
  • 多重指针内存地址

    2021-06-13 18:17:26
    例子 #include<... //创建二重指针并指向一个指针 std::cin.get(); } 内存结构 从图中我们可以看到创建的指针buffer内存地址为0x00b8f1e8且内存中都被初始化为0. 这时已经执行了创建二重指针的代
  • C语言多重指针

    2021-07-29 15:01:37
    二级指针中存的是一级指针的地址,不是一级指针中存的地址。 再往上三级指针中存储二级指针的地址,以此类推。
  • 多重指针:一个指针指向另一个指针 离值越近的指针级别越大:一级 内存布局 代码 图示: 多重指针–整型 #include<stdio.h> #include<string.h> //多重指针--整型 //二级指针 void two() { printf("二...
  • C++学习:** 多重指针

    2015-07-02 11:55:57
    定义一个多重指针ad,又叫指向指针的指针,把ac的地址赋值给ad 看一下运行结果: 我对*的理解是取值(非初始化时和变量一起使用)。 *ac:取出ac指向的地址存放的值 10 ac存放了一个地址 ad也是一个...
  • 以Turbo Pascal为语言工具,利用多重指针动态数据结构技术,实现实方阵的动态建立及动态运算TPU,应用于高层建筑结构抗震分析中,能大幅度节省计算机内存并提高程序的运行效率。
  • 多重指针

    2021-02-20 21:58:40
  • 2. 本博文只是对多重指针和指针数组的一个小总结; 指针数组 是什么?是一个数组,是一个元素都是用来存储地址的数组; 定义方式:类型名 *数组名[数组长度] 特点:以往的数组中,一个数组名只能确定一段...
  • 指针数组;指针数组举例#include <stdio.h> int main) { int i; char *arr[4] = {"C"C++"Java"VBA"}; for (i=0; i; i++) printf"Address of String %d : %u\n,i+1,arr[i]; return 0; };0x0042204C;例8.27 将多个字符...
  • C++ 多重指针

    千次阅读 2013-07-31 10:45:13
    可以理解为二重指针的第一层,分配了10个int型的二重指针空间 a是三重指针的地址,*a是二重指针的首地址;(*a)[0];(*a)[1];(*a)[2];(*a)[3];(*a)[4]........ 2: (*a)[i]=(int *)malloc(sizeof(int)*...
  • 在学习闲暇时间,把做工程过程中常用的一些内容做个收藏,如下内容内容是关于C语言基础:多重指针演示的内容,希望能对各朋友也有用途。#include <stdio.h> { } int main(void) { level_1 = &value...
  • Golang多重指针

    2021-04-29 12:07:41
    多重指针因垂丝汀 指针的引用也是一种拷贝的现象。不同于值传递拷贝的是值;指针传递拷贝的是地址,所以改变该地址下的值被拷贝的对象的值当然也随着改变。可是!如果如果你动的不是拷贝指针地址下的值而是该地址呢...
  • 为什么要用指针 CE寻找到的一些地址往往是一个动态地址,它是动态生成的(例如malloc函数),每次重启游戏后,它都会随之改变,我们不可能每次打开游戏时都重新搜索一波。 当我们想要找一个通用的地址,让我们每次...
  • C++ void*指针、多重指针void**和指针大小

    万次阅读 多人点赞 2017-09-16 23:31:10
     用void* 定义一个void类型的指针,它不指向任何类型的数据,意思是,void*指针“指向空类型”或“不指向确定的类型”,而不要理解为void*指针能指向“任何的类型”数据。简而言之:void*只提供一个地址,没有指向...
  • 太菜了吧》(17)5分钟搞懂指针与多重指针——指针与多重指针 《看聊天记录都学不会C语言?太菜了吧》(16)我一直以为校花很漂亮,直到我叫了她一声…——生命空间、命名规则 《看聊天记录都学不会C语言?太菜了吧...
  • } 使用多重指针 多重指针我以前的博客有介绍过,可以去看看 多重指针的简单理解 为什么说是用多重指针呢? 我们先来回顾下指针的引用 int a = 20; int *p = &a; 整型变量 a 有自己的地址 整型指针变量 p 也有自己...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,822
精华内容 23,528
关键字:

多重指针