B站郝斌C语言指针笔记

b站郝斌C语言指针笔记

指针的重要性

  • 表示复杂的数据结构
  • 可以快速的传递数据
  • 使函数放回一个以上的值
  • 能直接访问硬件
  • 能够方便的处理字符串
  • 理解面向对象语言的引用基础
  • 指针是C语言的灵魂

指针的定义

  • 地址

    • 内存的单元编号
    • 从零开始的非负整数
    • 范围0-FFFFFFFF 【0 到 4G-1】(32位电脑)
  • 指针

    • 指针就是地址,地址就是指针
    • 指针变量就是存放内存单元编号的变量,或者说指针变量就是存放内存地址的变量
    • 指针本质上就是一个操作受限的非负整数
    • 指针的值的类型都是一个代表内存地址的长为16进制的数
  • 定义指针的常用操作

  • 定义一个指针变量 ->把变量地址的值赋值给指针变量-> 访问一个可用的地址的值

  • image-20221102165400180

指针的基本类型

  • #include<stdio.h>
    int main(void)
    {
    	int* p;//int *表示只能存放整形变量的地址 所以可以对整形取地址&
    	//int *p不表示定义了一个名字叫做*p的变量(数据类型为 int*,*p不表示是一个变量)
    	//int *p应该是:p是变量名 ,p变量的类型是int *类型 
    	//所谓int *类型就是存放int变量地址的类型
    	
        int i = 12; 
    	p = &i;//不能存放int类型的值 比如i 这里只是对i取地址 
    	//所以修改p的值不影响i的值 修改i的值也不会影响p的值 注意是p 不是*p
    	//如果一个指针变量指向了某个普通变量 则 *指针变量(*p) 就完全等同于普通变量(i)
    	//注意是*p 所有出现i的地方都可以换为*p 反过来也一样
        //*p就是以p的内容为地址的变量 我们可以以这个内容 去访问我们指向变量的值
        
        printf("%p,%p \n",i,*p);
    	return 0;
        
        //对指针的一些补充
        //指针就是地址 地址就是指针 地址就是内存单元的编号
        //指针变量是存放地址的变量
        //指针和指针变量是两个不同的概念
        //指针变量是一个变量 但是是一个存储地址的变量(储存内存单元的地址) 不是储存内存单元的内容
    }
    
    //运行结果
    000000000000000C
    000000000000000C
    
    E:\VSCode\2022.10.28\x64\Debug\date_one.exe (进程 17908)已退出,代码为 0。
    按任意键关闭此窗口. . .
    

    ​ 根据以上结构可以知道 p的地址就是i的地址(这要得益于p对i的取地址,所以p变量里面存放了i的地址(改变p的地址对i没影响,改变i的地址对p也没影响),这个时候我们对p进行取地址就是i的地址,所以i的值就是p的值)这也是为什么我们只输出p的地址的时候p有一个不定的地址 因为p没有得到初始化,所以漂泊不定,下面的图可以非常清楚的解释指针变量如何调用i的值:

    image-20221102165728364

  • 常见错误

//程序一:
int main(void)
{
	int* p;
	int i = 5;
	*p = i;//因为*p没有指向i 所以i赋给了一个没有目标的一个值(垃圾值)所以报错
	printf("%d\n", *q);
	return 0;
	
}
//运行结果 
// 程序报错
      
//程序二:
      
int main(void)
{
	//常见错误
	int* p;
	int i = 5;
	int* q;
	p = &i;
	*q=p//报错原因为类型不一致 int 与Int *
	printf("%d\n", *q);
	return 0;
	
}
      
//程序三:
int main(void)
{
	//常见错误
	int* p;
	int i = 5;
	int* q;
	p = &i;
	*q = *p; //类型一致 但程序报错 因为q没有赋值  可以改为 q=p 
    //改成q=p程序正常运行 因为q也指向了i

	printf("%d\n", *q);
	return 0;
	
}


int main(void)
{

	int* p;
	int i = 5;
	int* q;
    p=&i;
	p = q;//如果q没有赋值 q里面是一个垃圾值 因为q赋值给了p 所以p也变成了一个垃圾值
    
    
    //q是属于本程序的单元 控制权在自己,所以可以读写q的
    //但是如果q内部是一个垃圾值,则本程序不能读写*q的内容 
	//*q不可读 不可写(*q代表一个不知但单元 向内存开辟的单元 控制权限不在自己)
	printf("%d\n", *q);
	return 0;
	
}

  • 补充一个关于内存泄漏(野指针的问题)

    如果开辟的一个内存单元 不free() 将会造成内存泄漏的问题

    一直不free()的话将会一直消耗内存,但内存消耗完就会造成死机

    这也是电脑为什么用久了会卡顿的问题(电脑程序一定有地方是忘记了要free的地方 所以电脑用久了卡顿)

指针互换的知识

  • 直接看代码

  • //只交换了地址 没有交换内存单元的内容 我们可以对他们的地址进行打印查看
    void huhuan(int* p, int* q)
    {
    	printf("p:%p\nq:%p\n", p, q);
    	int* t;
    	t = p;
    	p = q;
    	q = t; //注意这样的写法 只是互换了指针变量(p q)里面内存单元地址的值  
    	//注意是地址的值 我们并没有对地址里面的值进行修改 指针也无法做到互换一个静态变量的地址
    	printf("p:%p\nq:%p\n", p, q);
    
    }
    
    int main(void)
    {
    	int a = 3;
    	int b = 5;//两个地址已经定死 不可以互换地址
    	printf("a:%p\nb:%p\n", &a, &b);
    	huhuan(&a, &b);//接收的是地址 所以进行取地址&
    	printf("a=%d\n b=%d\n", a, b);
    	return 0;
    
    }
    
    
    //程序结果
    a:0000006C3399FC34
    b:0000006C3399FC54
    p:0000006C3399FC34
    q:0000006C3399FC54
    p:0000006C3399FC54
    q:0000006C3399FC34
    a=3
     b=5
    
    E:\VSCode\2022.10.28\x64\Debug\pointer_two.exe (进程 22944)已退出,代码为 0。
    按任意键关闭此窗口. . .
    
    
  • //程序互换了a b的内容 
    void huhuan(int* p, int* q)
    {
    	printf("p:%p\nq:%p\n", p, q);
    	int t;
    	t = *p;
    	*p = *q;
    	*q = t;//这样写才是互换了a,b的内存单元里面的内容 所以我们*q *p来互换即可 整形对整形
    	//这样的写法接管的是地址 所以可以改变主函数里面的值
    	//这也是为什么void huhuan(int p, int q) 这样的写法来传参数是不可以改变主函数里面的值的
    	printf("p:%p\nq:%p\n", p, q);
    }
    
    int main(void)
    {
    	int a = 3;
    	int b = 5;//两个地址已经定死 不可以互换地址
    	printf("a:%p\nb:%p\n", &a, &b);
    	huhuan(&a, &b);//接收的是地址 所以进行取地址&
    	printf("a=%d\nb=%d\n", a, b);
    	return 0;
    
    }
    
    //运行结果
    a:0000002B799AF974
    b:0000002B799AF994
    p:0000002B799AF974
    q:0000002B799AF994
    p:0000002B799AF974
    q:0000002B799AF994
    a=5
    b=3
    
    E:\VSCode\2022.10.28\x64\Debug\pointer_two.exe (进程 14016)已退出,代码为 0。
    按任意键关闭此窗口. . .
    
  • int main(void)
    {
    	int a = 3;
    	int b = 8;//两个地址已经定死 不可以互换地址
    	int* p;
    	p = &a;
    	a = b;//这样的写法 只是a的内存单元里面的内容改变了 但没有对a进行取地址操作来修改a的值
    	b = *p;//这里指针取到的值还是a的地址 所以b取到的值是a刷新后的内存单元
    
    	printf("a=%d\nb=%d",a,b);
    	return 0;
    
    
    }
    
    //运行结果
    a=8
    b=8
    E:\VSCode\2022.10.28\x64\Debug\pointer_two.exe (进程 1152)已退出,代码为 0。
    按任意键关闭此窗口. . .
    

指针返回函数的值

  • 实参为相关变量的地址

  • 形参为以该变量的类型为类型的指针变量

  • 通过指针可以很容易修改被调函数的值(修改主调函数一个以上变量的值)

  • 在被调函数中通过 *形参变量名 的方式就可以修改主函数相关变量的值

指针和数组

  • 指针和一维数组

    • 数组名

      • 一维数组名是一个指针常量 它存放的是一维数组第一个元素的地址

        • int main(void)
          {
          	int a[5] = {0};
          	int* p;
          	//int a[3][4];//a[i][j]表示第i+1行 j+1列 long int 输出为%ld 如果想要一个值为16进制输出可以%#X
          	int b[5] = {0};
          	p = a;//因为一维数组名是一个指针常量 所以我们可以直接让p=a 他们的数据类型是一致的
          	printf("%#X\n",p);
          	printf("%d\n", *p);//还是如此 p是存放了a的地址 所以*p可以取到地址中的元素
          	//*(p+i)的作用跟a[i]的作用相同  如下验证
          	printf("%d\n",*(p+1));
          	printf("%d\n",p[1]);//因为p指向了a可以用a[数组下标]方式来表示 所以也可以用p[数组下标]的方式来表示
          
          	printf("%d\n",*(a+1));//既然a是一维数组名的指针变量 所以可以用*(a+i) 来表示第i个元素
          
          	//printf("%#X\n",&a[0]);//一维数组名是一个指针常量 存放的是一维数组名的地址
          	//printf("%#X\n", a);
          
          	//printf("%#X\n", (a+1));
          	//数组的第i个元素(a[i])   编译器本就是先通过a来查找a的地址(首地址)的 
          	// 然后再查找到了a+i的地址 ,最后才取到a[i]的内容
          	//那么我们是不是可以直接*(a+i)来直接输出a的值呢 这样编译器也省去了很多步骤 提高了效率
          
          	return 0;
          }
          
          
    • 下标和指针的关系

      • a[i]<<==>>*(a+i)

      • 假设指针变量的名字是p 则p+i存放的内容就是第i+1个元素的地址 (p所指向的变量所占的字节数)

      • 如何通过被调函数修改主调函数中一维数组的内容【如何界定一维数组】

        • 两个参数
          • 存放数组首元素的指针变量
          • 存放数组元素长度的整型变量
        void f(int* parr,int len)//数组的长度通过数组名是无法获取的 确定一个数组需要的2个参数 
        //parr存放的是第一个元素的地址 所以可以通过parr+i的方式访问到宿主的元素
        //因为没有可以当为数组结束的标记 而字符串是有\0的 所以我们在函数中 我们需要
        //把a发送给parr parr[3]是先找到首元素的地址 然后在搜索到parr+3的地址 最后取得元素  a[3]也是同样的道理 
        //这就是指针与下标的知识 指针对数组可以快速的传输数据
        {
        	//parr[3] = 88;//因为搜索的是a的地址 parr存放的是以数组地址为内容的值 所以我们可以通过查找到地址 
        	//进而修改里面的值
        	int i;
        	for (i = 0; i < len; i++)
        	{
        		printf("%d",*(parr+i));//*(parr+i)等价于parr[i] 
        	}
        	printf("\n");
        }
        int main()
        {
        	int a[5] = {1,2,3,4,5};
        	int b[6] = {-1,-2,-3,4,5,6};
        	int c[100] = { 1,99,22,33 };
        	//printf("%d", a[3]);
        	f(a, 5);
        	//printf("%d", a[3]);//加取地址为把a[3]的地址以10进制表示
        	f(b, 6);
        	f(c, 100);
        
        	return 0;
        }
        
        //运行结果
        12345
        -1-2-3456
        1992233000000
        
        E:\VSCode\2022.10.28\x64\Debug\point_three.exe (进程 20212)已退出,代码为 0。
        按任意键关闭此窗口. . .
        
        
    • 指针变量的运算

      • 指针变量不能相加 不能相减 不能相除

      • 如果是指向同一个存储单元(同一数组),则可以相减

      • 指针变量可以加减一整数,前提是最终结果不能超过指针允许指向的范围

        • p+i的值是p+i*(p所指向的变量所占的字节数)
        • p-i的值是p-i*(p所指向的变量所占的字节数)
        • p++ <==>p+1
        • p–<==> p-1
      • image-20221030164136362

专题

动态内存分配

  • 引人一个指针变量占多少字节的问题
int main(void)
{

	char ch = 'A';
	int i = 99;
	double x = 66.6;
	char* p = &ch;
	int* q=&i;//我们只保存了第一个字节的编号 
	double* r = &x;
	printf("%2d %2d %2d\n", sizeof(p), sizeof(q), sizeof(r));//32为占4个字节 64位占8个字节
	//p q r指向的是第一个字节的地址 但是保存却用了8个字节的 所以是编号用了8个字节来表示 64根线 2^64种状态  所以64位系统下地址范围应该时0-8G-1

	printf("%2c %2d %2f",*p,*q,*r);

	return 0;
}
//运行结果
 8  8  8
 A 99 66.600000
E:\VSCode\2022.10.28\x64\Debug\point_four.exe (进程 1660)已退出,代码为 0。
按任意键关闭此窗口. . .

  • 传统数组的缺点:

    • 数组长度必须事先制定,且只能是常整数,不能是变量 //c99已经支持 也就是int len=5 int a[len]的形式

    • 传统形式定义的数组,该数组的内存程序员无法手动释放 因为它存放在栈区 只能是函数结束时由系统自动释放

    • 数组的长度一旦定义 它的长度就不能在函数运行过程中动态的扩充或缩小

    • A定义的数组 只能A在运行期间被别的函数使用 A运行完毕后 不能被别的函数使用(因为已经被释放了)

    • void g(int* parr, int len)
      {
      	parr[2] = 88;
      }
      void f()
      {
      	int a[5] = {1,2,3,4,5};//这20个字节的数据 不能手动释放 因为它存放在栈区 只能是函数结束时由系统自动释放
      	g(a, 5);//只能在函数运行时调用其他的函数 数组才可以在其他函数使用
      	printf("%d\n",a[2]);
      
      }
      int main(void)
      {
      	f();
      	return 0;
      }
      //运行结果 
      88
      
  • 为什么要动态分配内存:

    • 动态分配内存解决了传统数组的缺点

    • 传统数组就是静态数组

    • //英文小知识 malloc 是memory(内存) allocate(分配)的缩写 
      #include<stdlib.h>
      int main(void)
      {
      	int i = 5;
      	//*p
      	int* p = (int*)malloc(4);//如果是100 就是25个整形变量 形参必须是整形  malloc函数会分配4字节的空间 但只能返回第一个字节的地址(p只有首地址) 
      	//如果不成功 就返回NULL
      	// *p代表的是一个int变量 只不过*p这个整形变量的内存分配跟6行的不同 是动态的
      	//int*表示第一个字节的地址转换为整形的地址(注意是地址) 4表示分配4个字节的空间 所以强制类型转换是很有必须要的
      	
      	//第7行分配了8个字节 4个静态(本身所占的内存是静态分配的) 4个动态(指向的内存是动态分配的) 64位下应该是12个字节 8个静态 4个动态
      	//printf("%d\n", sizeof(*p));//这里打印4的意思是 int的整形类型指针是4个字节
          printf("%d\n", sizeof(p));//这里的8是64位系统下输出的结果   
      	free(p);//所指的内存释放了
       
      	return 0;
      }
      //第2个补充
      int main(void)
      {
      	int a[5] = { 1,2,3,4,5 };
      	int* p;
      	char* l;
      	p = a;
      	printf("%d\t%d\n", sizeof(p), sizeof(l));//不管什么数据类型 指针变量占取的空间都是8个字节
      	printf("%p\t%p",p,&p);//p储存在&p的内存地址上 该储存单元储存了内存单元为(a[0])的地址
      
      	return 0;
      }
      //运行结果
      8       8
      0000005DB08FF9A8        0000005DB08FF9D8
      E:\VSCode\2022.10.28\x64\Debug\point5.exe (进程 21024)已退出,代码为 0。
      按任意键关闭此窗口. . .
      
      
      
  • 动态构造一维数组

    • 动态内存可以不先指定长度

    • 动态内存可以在程序运行中动态的扩充或缩小(realloc)

    • 可以手动申请 手动释放

      • int main(void)
        {
        
        	//int a[5];//一共20个字节,每个占4个字节(表示空间大小 不是地址大小) 
        	int len;
        	int* parr;
        	scanf_s("%d",&len);
        	parr = (int*)malloc(4 * len);//这样的写法就是造出了一个 动态的一维数组 类似于int parr[len];
        	//parr指向的是前面4个字节的地址 *parr就是前4个字节代表的变量 parr+1代表第2个4个字节
        	//如果是开始指向的是8个字节 parr+1就是第2个8个字节
        	//对一维数组进行操作
        	for (int i = 0; i < len; i++)
        	{
        		scanf_s("%d",&parr[i]);
        	}
        	//对一维数组进行输出
        	for (int i = 0; i < len; i++)
        	{
        		printf("%d\n",parr[i]);
        	}
        	free(parr);//释放掉了动态分配的数组
        
        	return 0;
        }
        
        //运行结果
        5
        1
        2
        3
        4
        5
        1
        2
        3
        4
        5
        
        E:\VSCode\2022.10.28\x64\Debug\point_malloc.exe (进程 13596)已退出,代码为 0。
        按任意键关闭此窗口. . .
        
      • void f(int *p)//变成二维指针**p便可以接收一维指针变量的地址&p
        {
        	*p = 200;//如果再加一个* 也就是**p 因为*p是一个整形变量
        	//如果这里写free(p)下面的第2个printf将不能打印 因为空间释放了
        }
        
        int main(void)
        {
        	//int* p;
        	int* p = (int*)malloc(sizeof(int)*10);//如果p不初始化 是不可以直接对*p赋值的     //也就是没有对p的存储单元开辟空间
        	//sizeof(int)返回值为一个int变量返回的字节数
        	//malloc函数只能返回第一个字节的地址
        	*p = 10;
        	printf("%d\n",p[0]);
        	printf("%d\n",sizeof(*p));
        	f(p);
        	printf("%d\n", *p);
        	free(p);
        
        	return 0;
        }
        
        //运行结果
        10
        4
        200
        
        E:\VSCode\2022.10.28\x64\Debug\point_malloc.exe (进程 18284)已退出,代码为 0。
        按任意键关闭此窗口. . .
        
  • 动态内存可以跨函数使用 但上面程序没有体现

动态内存和静态内存的比较

对内存分区的一点理解:
image-20221101172313992
  • 在栈区 存放函数的参数 由函数完成执行 系统会自动释放栈区的内存 不需要用户管理 在VS默认为1M

    特点

    • 读取速度快,存储和释放的思路是按照数据结构中的栈进行的,存数据就是压栈,释放就是出栈。

      空间小,基本类型的数据占用空间的大小不会随着值的改变而改变,而且占用空间小。

  • 堆区

    由编程人员手动申请,手动释放,然后不手动释放(free)系统会在程序结束后由系统回收

    生命周期是整个程序运行期间。使用malloc或者new进行堆的申请,堆的总大小为机器的虚拟内存的大小。

    特点 读取速度慢

    空间大:引用类型的数据大小是动态的,会随着数据的增加而改变大小。

    malloc返回的是void*

静态内存
  • 静态内存在栈区中分配的 由系统分配 函数结束后由系统释放
  • 先来解释一下函数在栈区的执行过程
    • 先从主函数进入(把函数进入到栈区)->main函数调用第一个函数f(这个函数压栈到栈区)->再到下一个函数g(压栈后函数出栈)->上一个函数f出栈->函数调用结束
    • 以上是形成了一个函数镶套(压栈(把内存压缩到栈区) 出栈(把内存从栈区释放出去))的过程(遵循先进后出的原则)
    • 下面我们假设malloc也是在栈里分配空间的 我们把malloc在g函数中分配100个字节的空间,按照栈的规则,我们g调用结束后,函数的空间就被自动释放了,这样malloc分配的空间也被释放了,但是我们知道malloc是不会自动消失的,所以mallloc开辟的空间一定不是在栈区的 是在堆区

image-20221102165815755

动态内存
  • 动态内存是由程序员手动分配,手动释放的
  • 动态内存是在堆区分配的
  • 可以跨函数使用(堆不存在出栈与压栈)

多级指针

  • 指针可以指向一个普通类型的变量,例如int、double、char等,也可以指向一个指针类型的变量,如int*、double*、char*等。

    如果一个指针指向的是另一个指针,我们就成他为二级指针,或者指向指针的指针。

    • 转换会代码

      • int a=100;
        int *p=&a;
        int **q=&p;
        
  • 因为指针变量会占取内存空间 ,所以可以用&来获取它的指针 ,C语言没有限定指针的级数,每增加一级指针,在定义指针变量时就得增加一个号。p是一级指针,指向普通类型的数据,定义时有一个;q是二级指针,指向一级指针p,定义时又两个*,当然我们也可以定义三极指针来指向二级指针

  • 假设a、p、q、r的地址分别为0X001、0X002、0X003、0X004,他们之间的关系可以用下图来描述:

    • image-20221102190328710
  • 下面看代码

  • void f(int **q)//我们在下方把p的地址传到f函数去,那么q的内容就是p的地址了,
    //f函数的形参int **q 的*q就是p的内容(i的地址) 所以*q=p **q=i;
    {
    	**q = 20;
    }
    void g()
    {
    	int i = 0;
    	int* p = &i;
    	printf("%d\n",*p);
    	f(&p);//p是int *类型,&p(对一级指针的取地址)就是 int **类型
    	printf("%d\n",*p);
    
    }
    int main(void)
    {
    	g();
    	return 0;
    }
    
    //运行结果
    0
    20
        
    E:\VSCode\2022.10.28\x64\Debug\point_point.exe (进程 1440)已退出,代码为 0。
    按任意键关闭此窗口. . .
    
void f(int* q)
{
	*q= 50;
}
void g(int** r)
{
	**r = 100;
}
int main(void)
{

	int* p = (int*)malloc(sizeof(int));
	* p = 10;
	printf("%p\n",p);
	printf("第一次定义的值:%d\n",*p);
	f(p);//对于一级指针传一级指针 传过去的是开辟空间的地址 所以可以修改
	printf("f函数修改的值:%d\n", *p);
	g(&p);//类型不同 因为g函数定义的r只能存放int *类型的地址
	//所以要对p进行取地址 也就是规定的二级指针对一级指针的操作
	printf("g函数修改的值:%d\n",*p);
    free(p);

	return 0;
}

//运行结果
00000294D1546090
第一次定义的值:10
f函数修改的值:50
g函数修改的值:100

E:\VSCode\2022.10.28\x64\Debug\point_point.exe (进程 11328)已退出,代码为 0。
按任意键关闭此窗口. . .

静态内存不可以跨函数使用

  • //静态变量不可以跨函数使用
    void f(int **p)//存放指针变量的地址只能写**类 
    {
    	int j = 5;
    	//*p等价于q p和**p都不等价于q
    	*p = &j;
    	printf("i的地址:%p\n", &j);
    }
    
    int main(void)
    {
    	int* q;
    	int i = 10;
    	q=&i;
    	printf("q指向i的值 %d\n",*q);
    	f(&q);//这里只能发送地址才能修改*q的值 
    	printf("f函数修改q后的值:%d\n", *q);//这句程序语法没问题,但逻辑上存在问题
    	//f的内存已经被释放了(出栈) 但编译器没有检测出来 
    	printf("第二次打印修改后的值:%d\n", *q);//连续输出两次*q的内容我们可以得到答案 这里输出的为乱码
    	printf("访问q的地址%p\n", q);//这里与f函数中i的地址对比可以知道 q可以访问i的地址 但不能访问i的空间(内存单元内容)
    
    	return 0;
    }
    
    //运行结果
    q指向i的值 10
    i的地址:000000956EDAF994
    f函数修改q后的值:5
    第二次打印修改后的值:-858993460
    访问q的地址000000956EDAF994
    
    E:\VSCode\2022.10.28\x64\Debug\stat_dyna.exe (进程 23476)已退出,代码为 0。
    按任意键关闭此窗口. . .//
        
        
        
    

动态内存可以跨函数使用

  • //动态内存可以跨函数使用
    void f(int **q)
    {
    	*q = (int*)malloc(sizeof(int));//malloc函数返回的是开辟字节的第一个的地址
    	//等价于p=(int *)malloc(sizeof(int))
    	**q = 5;
    
    }
    int main(void)
    {
    	int* p;
    	f(&p);
    
    	printf("%d\n",*p);//动态内存在堆里面分配 内存由程序员手动开辟 手动释放 所以可以在f函数中修改*p的值
    	printf("%d\n", *p);//正常打印 
    	free(p);
    	printf("%d\n", *p);//已经释放 打印乱码
    	return 0;
    }
    //运行结果
    5
    5
    -572662307
    
    E:\VSCode\2022.10.28\x64\Debug\stat_dyna.exe (进程 21016)已退出,代码为 0。
    按任意键关闭此窗口. . .
    
  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值