深入理解指针1

深入理解指针1

一.内存和地址以及指针间的关系

举一个生活中的例子,假如你去找你的朋友玩,你的朋友告诉了你酒店的名字,但是没有告诉告诉你他具体住哪一件房间,于是你为了找到你的朋友,只好一间房间一间房间的去找,但是如果告诉了你房间号,那你就可以很快的找到你的朋友,所以在生活中有了具体的地址,就可以很快的提高效率。


那么在计算机中也是一样的,计算机的 cpu 处理的数据都是在内存中获取的,处理好的数据,也会放到内存里,那么内存是如何管理自己的数据的?其实,++内存里会划分很多个内存单元,每一个内存单元占一个字节空间++,也就是8个比特位,每个内存单元都会有自己的编号,而这个内存单元编号就相当于酒店的房间号,也就是地址,有了地址,我们就可以访问 cpu里的数据。我们可以把内存看成一个酒店,内存单元


总结:

我们可以把内存看成一个酒店,内存单元看成酒店里的每个房间,房间号看成内存单元的编号,这个房间号就是一个地址,而房间号=内存编号=地址=指针,所以指针就是地址

1.内存中常见的单位:

c 复制代码
bit ------  比特位                
byte ------ 字节                  
KB                          
MB							
GB
TB
PB
 1byte=8bit
 1KB=1024B
 1MB=1024KB
 1GB=1024MB
 1TB=1024GB
 1PB=1024TB

2.内存究竟是如何编址的呢?

cpu 访问内存中的数据前,必须要知道它的地址,因为内存中很多字节,所以要给内存进行编址,就相当于酒店里有很多间房间,为了方便管理,我们需要给房间编上号码。那么,内存是如何进行编址的呢?

计算机中的编址是通过硬件设备完成的。我们都知道计算机中有很多硬件设备,它们的通信就是用线连接起来的,有数据总线,地址总线,控制总线,但是今天我们只讨论地址总线,因为地址总线是用来传输内存单元编号的,假设你的电脑是32位的,那么就有32根地址总线,每一根地址总线传输一个比特位,每根线表示两种含义,0或1,那么两根地址总线就可以表示2^2^种含义,那么32根地址总线就可以表示2^32^种含义,

地址总线将地址传给内存,内存找到该地所对应的数据,再通过数据总线将数据传给 cpu 内寄存器。

二.指针变量和地址

1. 取地址操作符&

c语言中创建变量的本质就是在内存中申请一块内存空间,就相当于你去酒店住房,需要找前台给你开一个房间,比如int a=10;,就是向内存中申请4个字节的空间,用来存放10,每一个字节都有对应的地址,

注意!!!&取地址操作符要与按位与操作符&区分开来,按位与操作符是双目符,也就是有2个操作数,而取地址操作符是单目操作符,他的意思是取内存单元中的编号,也就是地址,即指针。

2.指针变量

&取地址操作符取出来的地址也是1个数值,有的时候我们想把这个数值存储起来,方便后期使用,那么我们就可以存到指针变量里,这个指针变量就是专门用来存放地址的,换句话说,存在指针变量里的值,都会被当作地址使用。

3.如何创建指针变量

c 复制代码
int main()
{
	int a = 10;
	int *p = &a; //把a的内存单元编号,即地址取出来,然后存放到指针变量p里面
	//int*是变量p的类型
	//int表示指针变量所指向的地址里面的数据是整型
	//*解引用操作符,表示p是指针变量
	//总结:创建指针变量的时候要说明  指针变量类型*  变量名


	return 0;
}
c 复制代码
char ch='a';
char* p=&a;//把a的地址取出来,然后赋值给指针变量p
printf("%p",p);
//char*是变量p的类型,*表示p是一个指针变量,char表示指针指向一个字符类型的数据

4.解引用操作符*(间接访问操作符)

我们将地址保存了起来,以后如果想使用这个地址访问到他里面存放的数据,怎么操作呢?就好比你找到了酒店房间的地址,你想进这个房间拿里面的物品,或打扫卫生,但是你没有钥匙。所以接下来我们需要了解一个操作符,解引用操作符('*'),它就相当于一把钥匙,有了他,我们只要拿到了地址,就可以通过地址,找到地址指向的对象。

c 复制代码
int main()
{
	int a = 10;
	int* p = &a;//创建指针变量p,存放a的地址
	*p = 0;//*p的意思是找到p所指向的内存空间,然后访问里面的数据,把a的值修改成0
	printf("%d", a);
	return 0;
}

5.指针变量大小

int类型的变量是4个字节,char类型的变量是1个字节,那指针变量占内存多少个字节呢?
sizeof() 操作符计算变量或类型的大小,即在内存中占多少个字节。

c 复制代码
printf("%d\n",sizeof(int));
printf("%d\n",sizeof(float));
sizeof("%d\n",sizeof(double));
printf("%d\n",sizeof(long));
printf("%d\n",sizeof(char));

判断下面两个指针变量所占内存大小,

c 复制代码
int a=10;
char ch='a';
int*p=&a;
char*arr=&ch;
printf("%d\n",sizeof(p));
printf("%d\n",sizeof(arr));
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(char*));
//指针变量p和arr的大小都是一样的,指针变量大小却决于地址大小,如果是32位环境,那么指针变量的大小都是4个字节,如果是64位环境,指针变量大小就是8个字节

指针变量大小与类型无关,取决于地址大小

c 复制代码
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(float*));
printf("%d\n",sizeof(double*));
printf("%d\n",sizeof(long*));

总结:

c 复制代码
//指针变量的大小取决于地址的大小,32位平台下,指针大小是4个字节
//64位环境下,指针大小是8个字节,在相同的平台下,指针大小是相同的

6.指针变量类型的意义

既然在相同的平台下,指针大小都是一样的,与指针的类型无关,那么为什么还要有各种类型的指针呢?
其实指针的类型是有意义的,指针变量的类型决定了指针在解引用的时,就是访问指针指向的对象的时候,一次访问几个字节,

1.指针变量的类型决定了指针解引用时的权限
c 复制代码
//对比下面两组代码,并用调试观察他们在内存中的变化
c 复制代码
int a=0x11223344;//0x开头表示十六进制
int*p=&a;
*p=0;
printf("%d",a);
c 复制代码
int a=0x11223344;
char* p2=&a;
*p2=0;
printf("%d",a);

如果是int*类型的,那么指针访问的时候,就会访问4个字节
如果是char *,那么指针访问的时候,就会访问1个字节所以通过解引用操作符将a的值修改成0的时候,因为只拿到第一个字节里的数据,所以只修改第一个字节里的数据。

2.指针变量类型决定了指针的步长

指针变量的类型决定了指针==+1/-1==时,一次跳过几个字节,相当于指针的步长;

*或者说指针变量的类型==+n/-n==时,一次跳过了n个值,如果是char,一次跳过n个字符,如果是int*;一次跳过n个整型。**

7.指针加减整数

c 复制代码
int main()
{
	int a = 10;
	char* pc = &a;
	int* pi = &a;
	printf("%p\n", &a);
	printf("%p\n", pc);
	printf("%p\n", pi);
	printf("char*类型:%p\n", pc+1);
	printf("int*类型:%p\n", pi+1);
	return 0;
}
c 复制代码
//这是上面代码的运行结果,我们发现char*类型的指针+1,一次跳过了一个字节,而int*类型指针+1,一次跳过了4个字节

总结:

指针变量的类型决定了指针+n/-n时,一次跳过几个整型或几个字符。

如果是char*类型+1,一次跳过1个字节,跳过一个字符;

如果是int*类型+1,一次跳过4个字节,跳过1个整型

8.指针变量类型的使用

竟然知道了指针类型具有特殊的意义,那么怎么使用呢?
之前遍历数组元素是用的下标的方式,现在学了指针类型的特殊意义,我们可以用指针来访问数组元素
在写代码之前我们要想清楚是一个整型一个整型的访问呢,还是一个字符一个字符的访问呢

c 复制代码
//访问数组元素之下标方式
c 复制代码
int arr[]={1,2,3,4,5,6,7,8,9,10};
int i=0;
int sz=sizeof(arr)/sizeof(arr[0]);
for(i=0;i<sz;i++)
{
    printf("%d ",arr[i]);
    
}
c 复制代码
//访问数组元素之指针方式,因为数组在内存中是连续存放的,知道了首元素的地址,再通过指针加减整数的方式就可以顺藤摸瓜拿到后面的元素
c 复制代码
int arr[]={1,2,3,4,5,6,7,8,9,10};
int*p=&arr[0];//指针变量p存储的是数组首元素的地址
int i=0;
int sz=sizeof(arr)/sizeof(arr[0]);
for(i=0;i<sz;i++)
{
   // printf("%d ",*(p+i));//指针加减整数得到的还是地址,要想拿到里面的数据,需要解引用
    
    //另一种写法:
    printf("%d ",*p);
    p=p+1;
    //p+1的结果是个地址,不能写成*p=p+1,*p他里面存储的是一个数据,你不能把地址赋值给他,这样写是错的
    //
}

四.const修饰指针

1.const修饰变量

const是c语言的关键字,当const用来修饰变量的时候,在定义的时候必须进行初始化,该变量就变成了一个常量,不能再对其进行赋值操作,修改

c 复制代码
#include <stdio.h>

int main() {
    const int num = 10;
    // 下面这行代码会导致编译错误,因为 num 是只读的
    // num = 20; 
    printf("num 的值是: %d\n", num);
    return 0;
}
c 复制代码
//变量a用const修饰后,不能修改了,但是我们可以用指针中的解引用来修改a的值
c 复制代码
int main()
{
const int a=10;
    int*p=&a;
    *p=20;
    printf("%d",a);
return 0;}

但是这样做显然是不合理的,我们用const修饰指针a的目的就是为了让a的值不发生修改,如果拿到了a的地址,就可以修改,这就会有很大的风险,那么怎么才能即使拿到了a的地址,也不让a的值被修改呢?

2.const修饰指针变量

const修饰指针时,分两种情况讨论,const在*的左边和右边的代表意义是不同的,

c 复制代码
当const在*的左边的时候
c 复制代码
int a=10;
int b=20;
const int *p=&a;
int const *p=&a;//这两种写法都对
*p=20;//会报错,const在*左边时,不能修改指针变量指向的内容
p=&b;//但是可以修改指针变量本身,即指针变量的指向
c 复制代码
当const在*右边的时候
c 复制代码
int a=10;
int b=30;
int* const p=&a;
*p=20;//const在*右边的时候,可以修改指针指针变量指向的内容
p=&b;//会报错,但是不能修改指针变量本身,就是里面存的地址

当然,还有一种情况就是*左右两边都有const,这种情况就是即不能修改指针指针变量本身,也不能修改指针指向的内容

c 复制代码
int main()
{
	int a = 110;
	int b = 220;
	const int* const p = &a;
	*p = 20;
	p = &b;

	return 0;
}

总结:

1.当const在*左边的时候,指针指向的内容不能被修改,但是指针变量本身可以被修改

2.当const在*左边的时候,指针指向的内容可以被修改,但是指针变量本身不能被修改

3.当*左右两边都有const的时候,指针 *变量本身和指针指向的内容都不能被修改

五.指针运算

1.指针加减整数

在数学里,如果500+1就等于501,因为500是一个int类型的,而1也是1个int 类型的,所以结果也是一个int类型的 ,但是int*类型的指针变量+1 ,得到的是地址。指针加减整数其实在前面已经举过例子了,就是用指针的方式访问数组的元素,前提条件是数组在内存中是连续存放的,地址由低到高变化,不光数组这个例子,以后只要遇到连续的的空间,都可以用指针加减整数的方法

c 复制代码
//前面我们举得是整型数组的例子,下面用字符数组举例
c 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
    char arr[]="abcdef";
   int i=0;
   int sz=strlen(arr);//统计字符串长度,并不会包括\0
    char*p=&arr[0];
    for(i=0;i<sz;i++)
    {
      printf("%c",*p);//对指针变量解引用,就可以拿到指针指向的内容
        p++;//指针变量跳过一个字节,即1个字符,指向下一个字符的地址
     
        //另一种写法
       // printf("%c",*(p+i));
    }
    return 0;
}

2.指针- 指针

指针减指针其实就是地址减地址,得到的是一个整型数,

c 复制代码
int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int ret = &arr[5] - &arr[0];//指针减指针得到的指针之间的元素个数
	printf("%d", ret);//打印结果为5
	return 0;
}
c 复制代码
//错误代码示例,指针减指针的前提是指针指向的都是同一片空间
c 复制代码
int main()
{
	int arr[10]={0};
    char str[11]={0};
    int ret=&arr[9]-&str[5];//这样写就不行,首先它们是不同类型的数组,指向的不是同一片空间,其次我们也不知道str数组空间和arr数组空间之间相差多少个元素,这是不确定的
    printf("%d",ret);
	return 0;
}

总结:

1.指针减指针得到的是一个整型数,是一个绝对值,他表示指针之间相差多少个元素,大地址减小地址得到的是一个正数,小地址减大地址,得到的是一个负数。

2.指针减指针的前提是,指向的空间必须是同一片空间,不然没意义,就像日期-日期=天数,日期加天数都是有意义的,但是日期+日期就没有意义了。

3.指针的关系运算

指针和指针之间是可以比较大小的,指针比较大小就是地址与地址之间比较大小

c 复制代码
//除了利用指针加减整数来遍历数组元素之外,还可以利用指针的关系运算的方式
c 复制代码
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	//当元素的地址是否小于第10个元素的地址,意味着还有元素没有访问完,大于等于第10个元素的地址,就表示数组里的元素已经全部访问完了
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//int*p=&arr[0};
	while (p<arr+sz)//指针和指针之间进行比较
	{
		printf("%d ", *p);
		p++;//指针+1,跳过一个整型元素
	}
	return 0;
}

有个规律,

1.数组里,如果想知道某个元素的地址,首元素的地址加上元素对应的下标就可以得到地址;

2.假设数组有n个元素,那么第n+1个元素的下标就等于数组元素的个数

六.野指针

所谓的野指针就是没有没有明确的指向,你可以把他理解为一条野狗,四处为家,是危险的。

1.什么样的情况下会出现野指针

1.指针变量未初始化
c 复制代码
int a=10;
int*p;//指针变量没有初始化会报错,且他是一个野指针
*p=20;
2.指针越界访问
c 复制代码
//数组只有10个元素,但是你用指针访问了11个元素,越界了
c 复制代码
int arr[]={1,2,3,4,5,6,7,8,9,10};
int sz=sizeof(arr)/sizeof(arr[0]);
int i=0;
int *p=arr;
for(i=0;i<=sz;i++)//sz=10,i<=10,意味着当下标为10的时候还可以访问,但是下标为10就已经是第11个元素了
{
    printf("%d ",*(p+i));
}
c 复制代码
//正常情况下说,如果代码没有越界访问的话,就是写成下面的样子
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int* p = arr;
	for (i = 0; i < 10; i++)
	{
		*p = i;//将每一个元素的值改成对应的下标
		p++;//指针跳过1个整形元素

		//这样写也是对的
		//*(p++) = i;//第一步:*p=i,第二步:p++
	}

	for (i=0;i<10;i++)
	{
		printf("%d ",arr[i]);
	}

		return 0;
}
3.指针指向的空间释放

就是返回栈空间的地址

c 复制代码
int* test()
{
    int n=100;//n是局部变量,它的作用域就是test函数内
    return &n;//一旦出了函数体,局部变量向内存申请的空间就没有意义了,已经被回收了,
}

int main()
{
    int*p=test();
    printf("%d",*p);//那么此时p就变成了野指针,因为n申请的内存空间已经销毁了
    
return 0;
}

2.如何避免野指针

1.指针初始化

如果不知道指针指向哪里,但又必须初始化,因为如果不初始化,指针就是野指针,我们可以赋值为NULL,

NULL是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址

会报错.
举个例子,如果指针没有初始化就是一条野狗,但是现在我们将指针初始化为NULL,就相当于用绳子把野狗绑到了树上,虽然它看似安全,但是我们也不能在它旁边撒尿,得绕着他走,所以指针赋值为NULL后,就不能使用指针了,即不能访问里面的数据了,直到你为他找到新的地址

c 复制代码
int main()
{
	int a = 10;
	int* p;//没有初始化,就是野指针

	//如果暂时不知道指针指向哪里,可以赋值为空指针
	int* p = NULL;
	*p = 100;//但是赋值为空指针后,就不能使用指针p了

	//如果确定了指针指向的地址,再重新赋值、
	int* p = &a;

	return 0;
}
2.小心指针越界
c 复制代码
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i=0;i<sz;i++)
	{

		printf("%d ", *(p + i));
	}

	//循环结束后会来到这,此时i=8,指针已经指向第9个元素了,已经越界了,所以指针属于野指针
	p = NULL;//为了避免p变成野指针,赋值为空指针,你可以理解把一条野狗拴在树上
	//赋值为空指针后,指针p就不能使用了,除非指向新的地址
	return 0;
}
3.避免返回局部变量的地址

返回局部变量地址其实就是返回栈空间地址,因为局部变量是存放在栈里的,

c 复制代码
int* test()
{
    
    int arr[]={1,2,3,4,5,6};//出了函数体,数组向内存申请的空间就释放了
    return arr;//返回首元素地址
}
int main()
{
    int*p=test();//定义指针变量p接受函数返回的地址
    *p=200;//p此时是野指针,因为他返回的是局部变量的地址
retrun 0;}
4.使用指针前检查下是否是空指针
c 复制代码
int a=20;
int*p=&a;
if(p!=NULL)//p如果为空指针返回的就是0,0为假;如果不是空指针,返回的就是非0,C语言中非0就是真
{
    *P=200;
}

其实还可以用assert断言来判断是否是空指针,更方便,后面有详细介绍

5.不使用指针的时候设置为空指针
c 复制代码
int a=10;
int*p=&a;
*p=200;
p=NULL;//这意味着p不再指向任何有效的地址,指针p不能在使用了

七.assert断言

assert.h头文件中,包含了宏assert(),这个宏常常被称为断言,用于在运行程序的时候判断程序是否符合某个条件,如果不符合就报错,程序终止运行。

assert()的参数是一个表达式,如果表达式为真,则返回非零,则程序正常运行;如果返回的值是0,assert就会报错
如果每次在使用指针前都要用if语句判断是不是空指针,就会很蛮烦,我们可以用assert断言来判断程序中有没有空指针。如果确定程序没有问题,不需要再做断言,就可以在~#include #include <assert.h>前面定义一个宏NDEBUG, 他就会禁用程序中所有的assert语句;如果程序又出现问题,那么就可以把#define NDEBUG 这条语句注释掉,这样就重新使用了assert语句。

c 复制代码
#include <stdio.h>
#include <assert.h>
#define NDEBUG  //开启或关闭 assert() 的机制
int main()
{
    int a=10;
    int*p=&a;
    assert(p!=NULL);//指针p如果等于空指针,为0,表达式就为假,程序就会报错
    //assert(p);这样写也是对的
    *p=20;
return 0;}

八.传值调用和传址调用

1.传值调用

学习指针的目的是为了解决问题,那么什么问题非指针不可呢?下面下一个函数,这个函数的功能是负责交换两个变量的值 , 想一下为什么下面的代码最终没有出现交换的效果

c 复制代码
void swap(int x,int y)//x=a  y=b
{
    int c=0;
    c=x;
    x=y;
    y=c;
}

int main()
{
    int a=20;
    int b=30;
    printf("交换前:a=%d b=%d\n",a,b);
    swap(a,b); //传值调用
    printf("交换后:a=%d b=%d\n",a,b);
return 0;}

通过调试我们可以发现,x和y的值都发生改变了,但是a和b的值并没有发生改变,这是因为我们函数的实参传递的是a和b的值,把值传给形参后,因为形参是实参的一份临时拷贝,形参有自己的独立空间,所以在函数swap内部交换,并不会影响a和b。所以这个代码就必须用指针来解决,要把a和b的地址传给形参,也就是传址调用。

2.传址调用
c 复制代码
void swap(int*x,int*y)//指针x存放a的地址,指针y存放b的地址
{
    int c=0;
    c=*x;//其实是把x的的值,也就是a的值赋值给c
    *x=*y;//把y的值赋值给x
    *y=c;//把x的值赋值给y
}

int main()
{
    int a=20;
    int b=30;
    printf("交换前:a=%d b=%d\n",a,b);
    swap(&a,&b); //传址调用
    printf("交换后:a=%d b=%d\n",a,b);
return 0;}

总结:

如果传的外部值需要发生修改,那么就要传地址,如果不发生修改,那么就传值

c 复制代码
//下面的代码就不要传地址,直接传值就可以解决
c 复制代码
//返回最大值
int max(int a,int b)
{
    if(a>b)
        return a;
    else
       return b;
}
int main()
{
    int a=20;
    int b=3;
    int m=max(a,b);
    printf("%d",m);
return 0;
}

九.练习:求字符串长度

(一共有4种方法)

方法1:

c 复制代码
#include <stdio.h>
#include <string.h>//使用strlen函数需要引用头文件
int main()
{
   char arr[]="hello";
    printf("%d",strlen(arr));//strlen函数统计字符串长度,不包括\0
return 0;
}

方法2:

c 复制代码
char str[]="hello";//字符串以\0结尾
int count=0;
char*p=str;//数组名即首元素地址
while(*p !='\0')//只要不是字符'\0',就一直统计,直到遇到\0,因为字符串以\0结束
{
    count++;
    p++;
}
printf("%d",count);
c 复制代码
//还可以用函数的方法
int my_strlen(char* str)
{
    int count=0;
    while((*str)!='\0')
    {
        count++;
        str++;
        
    }
    return count;
}
int main()
{
    char str[]="hello";
    int len=my_strlen(str);
    printf("%d",len);
    
return 0;}

方法3:

c 复制代码
//利用指针-指针的方式,因为指针-指针得到是相差的元素的个数,那么我们只要知道了\0的地址,用\0的地址减去第一个元素的地址,就可以得到字符串的长度
int my_strlen(char* str)
{
   char* start=str;//将第一个元素的地址存储起来,后面指针减指针的时候需要用到
    while((*str)!='\0')
    {
        str++;
    }
    //循环结束后,指针str已经指向\0的地址了
   return str-start;
}
int main()
{
    char str[]="hello";
    int len=my_strlen(str);
    printf("%d",len);
    
return 0;}

方法4:

c 复制代码
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)//首先我们的意图是求取字符串的长度,所以不希望str指向的内容发生改变,用const来修饰指针str,
    
//统计的字符串的长度肯定不会是一个负数,因为strlen函数的返回类型是size_t,就是无符号整型,他表示正数,所以我们在这里把my_strlen的函数返回类型设置为size_t
{
    size_t count=0;
    //在使用指针前,需要判断下str是不是空指针
    assert(str!=NULL);
   while((*str)!='\0')
{
    count++;
    str++;
   
}
    return count;
    
}
int main()
{
    char str[]="hello";
   size_t len=my_strlen(str);
    printf("%zd",len);
    
return 0;}

创作不易!多多支持

相关推荐
张胤尘26 分钟前
C/C++ | 每日一练 (2)
c语言·c++·面试
醉城夜风~37 分钟前
[C语言]指针进阶压轴题
c语言
weixin_535854221 小时前
oppo,汤臣倍健,康冠科技,高途教育25届春招内推
c语言·前端·嵌入式硬件·硬件工程·求职招聘
宋康3 小时前
C/C++ 指针避坑20条
c语言·开发语言·c++
仟濹3 小时前
【二分搜索 C/C++】洛谷 P1873 EKO / 砍树
c语言·c++·算法
YH_DevJourney4 小时前
Linux-C/C++《C/8、系统信息与系统资源》
linux·c语言·c++
Igallta_8136225 小时前
【小游戏】C++控制台版本俄罗斯轮盘赌
c语言·开发语言·c++·windows·游戏·游戏程序
楼台的春风5 小时前
PWM(脉宽调制)技术详解:从基础到应用实践示例
c语言·stm32·单片机·嵌入式硬件·mcu·物联网·嵌入式
WiKiLeaks_successor6 小时前
C从入门到放弃篇1
c语言·开发语言
努力的CV战士6 小时前
数据库-SQLite
c语言·开发语言·sqlite