本帖最后由 JadeBamb 于 2021-8-3 16:51 编辑
#申请原创# #每日话题#
前言本文适合有一定的C语言基础朋友,对指针有一定的概念,对于完全没有指针概念的朋友,需要先看一些C语言的基础教程后,再来阅读,相信会有一定的收获。
一、指针的概念指针的本质
指针的本质就是一个变量,与普通变量没有实质的区别,全称指针变量,在32位的系统上面,指针占4byte空间(32位的系统支持的最大内存0~4GB),以下内容都是基于32位系统;
内存与指针的关系
指针变量存储的内容就是一个内存地址,实际上任何一个变量存储的内容都可以是一个内存地址,因为内存地址本质上就是一个数值,关键点在于开发人员在写程序时怎么使用。
内存是一种物理存在的存储介质,是实实在在存在,看得见摸得着的物体,功能就是用来存储数据。指针变量的值就是一块实际内存的地址;
二、指针变量定义、运算
int * p = NULL; // int型指针变量定义char * ptr = NULL; //char型指针变量定义
上面两个指针变量虽然基础数据类型不同,但是都有一个共同的身份,都是指针变量,所以他们所占的内存空间都是4byte;
至于基础数据类型影响到的就是指针运算这个功能了,另外就是显示的指出来定义的这个指针变量是用来存储什么数据变量的地址,比如int *p,就是指出p这个指针是用来存储int型变量的地址,但实际上它也是可以存储其它类型的变量的,但是编译器会报错或者警告,但C语言有强制类型转换,可以消除错误和警告;
p += 1; // 结果 p = 4
ptr += 1; // 结果 ptr = 1
上面两个运算,虽然都是让这两个指针加1,但指针的加减运算是指加减几个单位,而不是数值,所以运算结果却是不相同的;受到指针基类型的影响,第一个p的基类型是int,int型变量所占的空间是4byte,所以加1个单位就是加4,而第二个基类型是char,一个单位就是1。
int a = 10;p = &a;这里的重点是&运算符号,在这里叫取地址符(取出变量a的地址赋值给指针p),这也就是取地址符的作用;
int a = 10;p = &a;
*p = 20; // *p做左值,等价于a = 20
int b = *p; // *p做右值
printf("b = %d", b); // 打印结果 “b = 20”
这里的重点是符号,在指针概念里面符号有三个不同使用场景,对应三个不同使用功能。
一是定义指针变量时
在指针变量定义值时与基本数据类型结合(int p),此时表示这是个指针类型,这里提示一点就是(int )是一个整体,不要被与后面的变量名比较近而影响理解;
二是*p做左值时
此时p就表示p所指向的地址所对应的变量,对比到前面这个例子(p 等价于 a),在这个过程可以空间想象一下这个指针变量p与变量a它们在内存当中的实际存储关系,这个过程可以说就是指针的精髓;
三是*p做右值
此时p就表示取出p所指向的地址所存储的内容(数值),所以int b = *p;就可以把变量a的值20赋值给b;
三、字符指针
char *p = “hello world”;对于一个字符串,是需要存储的,所以肯定需要内存,有内存就一定有地址,上面这个操作就是将这个字符串的的首地址(第一个字符的地址)赋值给p这个指针,当需要访问这个字符串常量时就可以用p这个指针来表示了;
C标准库函数,strcpy完美的诠释了字符指针的意义;
char * strcpy(char *dst,const char *src)
{
assert(dst != NULL && src != NULL);
char *ret = dst;
while ((*dst++=*src++)!='\0');
return ret;
}
char buf[20 = {0};
strcpy(buf, "hello world");
四、指针数组和数组指针
int (*p)[n; // 数组指针,p是指针
int *ptr[n; // 指针数组,ptr是数组
int buf[10;p = buf; // 给数组指针赋值
p[0 = 10; // 用数组指针来操作数组
int a = 10;ptr[0 = 10; // 错误操作,编译器会报警告
// 可以写成
ptr[0 = (int *)10; // 使用强制类型转换时,必须明确的知道这个地址是可以用的
ptr[0 = &a; // 给指针数组赋值
数组指针
p的实质是一个指针,是一个指向一个数组的指针,当p进行运算时加一个单位,这一个单位就表示一个数组的大小,不再是数值1,也不是int 大小4,因为这里的p的值所代表的就是一个数组的地址,此时这个单位就是这个数组所占内存的大小。
指针数组
ptr的实质是一个数组,是一个用来存储指针的数组,这个数组运算时,就不能直接赋值int类型的数据(编译器会报警告,这个错误可以用强制类型转换消除,使用强制类型转换时,必须明确的知道这个地址是可以用的,不然编译虽然能通过,但程序运行起来,如果访问了一个不能使用的内存地址,程序就会崩溃,linux上的段错误大多数情况就是非法地址的使用引起的),只能赋值int类型的指针。
五、函数与指针
函数同样是一段实体数据,同样是需要存储的,所以函数也同样有地址,有地址就可以用指针来操作。
变量可以通过指针来取值、运算,函数是一段逻辑功能,是需要CPU来取指令运行的,所以用函数指针就是用指针来指向一段函数,然后通过调用这个指针来运行这段逻辑函数;
int(*p)(int,char); // 函数指针定义
这里就是定义了一个函数指针,从内容中可以看出这个指针可以指向一个,返回值为int,形参1为int,形参2为char的函数;
int fun_test(int x, char y)
{
return x + y;
}
p = fun_test; // 给函数指针赋值
int a = p(1, 2); // 函数指针使用,与int a = fun_test(1, 2);等价
函数指针只要理解到函数就是一段存在内存中的逻辑数据,而函数名可以将它理解成地址(但是需要时刻明白函数名不是地址),这里就跟数组、数组名一样;
总结理解指针的关键点在于,思维空间中构建出指针、内存地址、内存数据这三者之间的关系,将一切变量、数组、函数、字符串理解成一段内存数据;再根据不同的指针的基础类型,来理解指针的运算。对于结构体、链表等复杂的指针操作,同样用这种思路来理解,也可以达到比较深入的理解效果。
|