指针对 C 语言的重要性不言而喻,它是 C 的灵魂,我将指针和数组比作习武之人的任督二 脉,如果学习 C 语言之人一旦将其二脉打通,那么对 C 语言的理解和使用就犹如内功层次 的提升,跟表面的花架子将不再同一档次。
当写出代码 int *p = &a 我们常常说定义了一个整形指针 p,从理论上来说,这句话是 错误的,p 不可能是指针,充其量是一个变量,跟我们直接定义一个普通变量其实质是 一样的
例如,int a = 10; 那么其 p 与 a 的地位是一样的,都是变量,只不过是两者所 保存的数据类型不一样而已,好多人认为这样去死抠字眼没有意义,本人一分为二看待, 如果你已经对 C 语言很了解,能够熟悉使用,那么死抠概念实际上也就没有意义了, 通常也就会把代码 int *p = &a 认为是定义了指针 p,而一般不再去强调 p 是一个指针变 量。但是,如果你是一个 C 语言的初学者,这样的理解和区分就非常的有必要,因为这样的认识能够让你理解指针的本质。
int main()
{
int a = 10;
int *p = &a;
return 0;
}
当我们定义一个整形变量 a 时,我们知道 a 是用来存储整形数据的,换句话说,什么样
的类型变量就是用来存储什么样的类型数据,所以,当我们定义整形指针变量 p 时,p
毫无疑问是用来存储整形指针的,不过却把整形变量 a 的地址赋值给 p,这就说明,指针
的本质就是地址,即就是指针等价于地址。
通过以上的分析,得出结论:
指针的本质就是地址,从而也说明了 p 不能是指针,只是存储指针的变量而已。把 p
称为指针实属习惯称呼,但我们的心里面一定要理解其本质内容。
为了能够分析其他信息,我们必须知道指针的大小,即就是当定义一个指针变量时,我
们该为其开辟多大的内存空间。
int main()
{
printf(“%d \n”, sizeof(char));
printf(“%d \n”, sizeof(short));
printf(“%d \n”, sizeof(int));
printf(“%d \n”, sizeof(float));
printf(“%d \n”,sizeof(double));
return 0;
}
运行程序时,输出结果为 : 1 2 4 4 8
通过运算符 sizeof 的计算,分别得到了各种类型的大小,但是,各位要注意,单独的类型实际上是没有大小的,此大小表明的意思是,当你拿类型定义变量时,其本质是要为变量开辟内存空间,但是开辟多大的类型空间了,其依据就是通过运算符 sizeof 计算出的类型大小,例如,定义整形变量 int a, 其就要为 a 变量开辟 4 个字节的内存空间,定义双精度浮点变量 double d ,就要为 d 变量开辟 8 个字节的内存空间。这就说明不同的类型空间大小是不一样的
那我们试想一下,其各种类型的指针类型到底占多大的内存空间了?
int main()
{
printf(“%d \n”, sizeof(char *));
printf(“%d \n”, sizeof(short *));
printf(“%d \n”, sizeof(int *));
printf(“%d \n”, sizeof(float *));
printf(“%d \n”,sizeof(double *));
return 0;
}
运行程序时,输出结果为: 4 4 4 4 4
结果全是 4 个字节,这不禁会让我们产生出很多疑问 :1 、 指针的大小为什么在 32 位系统下是 4 个字节,而不是其他的大小?2 、 既然只要是指针类型,其大小都是 4 个字节,那为什么还要辛辛苦苦的去区分你是字符类型的指针,我是整形类型的指针,指针的不同类型形式有什么意义了
首先,我们先理解以下的程序 :
#include
void main() { int a = 10; int b = 20; int *p = NULL; int **s = NULL; p = &a; s = &p; *p = 100; *s = &b; } 当我们进行调试时,可以观察到其各变量之间的值,但是,这对于初学者来说,一时也
很难以理解指针,我们换个方式表达指针两值之间的关系。
对于上述程序,首先定义了 a、b、p、s 四个变量,我们说定义变量的本质是为其开辟 内存空间,变量名就相当于给空间所取的名字,所以系统为其四个变量开辟相应大小的 空间,一旦有了空间,空间也就拥有了相应的地址。 当把 a 的地址赋给 p 时,就意味着 p 空间保存了 a 空间的地址,因为 p 是一个指针变量, 当一个指针变量保存了某个变量的地址时,我们就形象地认为 p 指向了 a。此时,对于 p 来说,其拥有两个值,一个是 p 空间本身所保存的地址,称之为自身的值,即 p 的值为: p = 0x0013ff7c,另一个值为 p 空间所保存地址所指空间的值,称之为指向的值,即 p 所 指向的值为: *p = 10。所以,对于任何一个指针来说,其拥有两个值,个人认为,在操 纵指针之前,如果没有很好的区分出其两值,那就不要对指针操作,否则容易造成程序崩溃,崩溃之后而又无从下手
思考:当程序执行*p = 100 时,实际是做什么操作? *s = &b 呢?
指针是一个特殊的变量,它里面存储的数值被解释为内存里的一个地址;要搞清一 个指针需要搞清指针的四个方面:
1》 指针的类型
2》 指针所指向的类型
3》 指针的值或者叫指针所指向的内存区
4》 指针本身所占据的内存区
指针的类型——从语法角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就 是这个指针的类型
例如:
int *ptr //指针类型 int *
int (*ptr)[5] //指针类型 int (*)[5]
指针所指向的类型——当你通过指针来访问所指向的内存区时,指针所指向的类型决定 了编译器将把那片内存区里的内容当做什么来看待,同时也决定了指针加 1 的能力 从语法角度看,你只需把指针声明语句中的指针名字和名字左边的指针声明符 * 去掉, 剩下的就是指针所指向的类型
例如:
int * ptr //指针所指向的类型是 int
int *(*ptr)[4] //指针所指向的类型是 int *()[4]
指针的值或者叫指针所指向的内存区的地址——是指指针本身存储的数值,这个值将被编译器当做一个地址而不是一个一般的数值
指针本身所占据的内存区大小——在 32 位平台里,指针本身占据了 4 个字节的长度
当我们了解了指针的两值四方面之后,现在就可以解释为什么在 32 位系统之下,其指 针的大小永远是四个字节。
首先我们先做一个类比,给你 1 位十进制数,你能够编号 10 个数(0~9),如果给你 2 位 十进制数,则能够编号 100 个数(0~99)……,对于计算机来说,底层所处理的数据都是二 进制,一样的方式,给你 1 位二进制,只能编号 2 个数(0,1),给你 2 位二进制,则能编号 4 个数(00,01,10,11),可以试想,,如果给你 n 位二进制,是不是就能编号 2^n 的状态。那 么,现在,系统是 32 位的,则我们能够编号的范围是 0~2^32,即为 4G 的大小,这样一 来,系统为了保证能够访问到任何一个地址,则我们的指针变量所占空间大小最起码的 包含所有的地址表示,反过来,要想表示 4G 空间的大小,至少的 4 个字节,因此,指 针的大小在 32 位系统下一定是 4 个字节,大了浪费,小了不能保证寻址所有空间。
指针所指类型是指针最重要的方面,因为指针所指类型决定了指针的两个非常重要的能 力。一是指针从起始地址开始,到底要把多少个字节的单位看作一个整体,二是如果指针加 1,那么这个加 1 的能力有多大,即指针加 1 到底跨越了多少个字节单位。我们可以 从如下的内容来了解以上两个方面:
如图,定义变量 ch,i,d,则在相应的内存中开辟空间,并对其赋初值,如果我们定义 相应的指针变量,并把相对应的变量地址进行赋值,首先,我们会面临一个问题,对于 字符变量 ch 来说,这非常容易,因为 ch 只有一个字节空间,所以也就只有一个地址, 因此,在把地址给 pch 赋值时不会产生选择,但是,对于变量 i 和 d 就不一样了,因为 他们的空间都不单一,相当于 i 和 d 分别代表了 4 个字节空间和 8 个字节空间,每个空 间都有一个地址,那么,当我们执行 pi = &i, 以及 pd=&d 时,到底要把哪一个地址赋 值给相应的指针变量了?经验告诉我们,都把整个空间的第一个空间的地址进行了赋 值,也就是我们常说的首地址。
首先,地址赋值问题是解决了,不过,针对指针还有一个非常重要的取值操作,即当指 针指向合法的空间之后,例如 pi 指针指向了 0x0012ff72 的地址空间,则执行*pi 时,我 们需要把相应空间的内容就行提取,这是就面对一个问题,指针从 0x0012ff72 地址开 始,到底要把几个字节空间看作一个整体进行取值就非常关键了,个人认为,内存中的 数据真的不关键,关键的是我们要从何处看待数据,并把多少空间看作一个整体就显得 尤为重要,因为就算起始点一样,空间大小不一样,最终的数据解释也会不一样。那么, 谁能担当此重任了,非指针所指类型不可,例如,对于 int *pi,如果起始地址从 0x0012ff72 开始,因为指针 pi 所指类型为 int,因此,在取值时,pi 就知道从 0x0012ff72 开始,要 把 4 个字节当做一个整体进行提取,从而跟定义变量时进行值的存储完全吻合,达到数 据的提取不出错,所以,这也就解释了,为什么指针大小都一样,还要区分类型,因为 类型起到了如何把一个空间划分为一个整体的作用,换句话说,指针所指类型道出了指 针的识别能力。
如图,对于指针 pch,ps,pi 都指向了相同的 a 空间,由于各自的指针所指类型不一样, 发现,虽然所指空间一样,但最终所访问出来的数据也不一样,因为,pch 指针只会把 一个字节当做一个整体,所以结果为 21,而对于 ps 指针,所指类型为 unsigned short 类 型,所以会把两个字节空间当做一个整体,其结果为 0xCD15(无符号数据位 52501), 当对 pi 取值时,刚好是一个整形空间,所以把 a 空间的值完全读取,这也说明了指针 所指类型的重要性。
也上说明指针所指类型的一个方面,从而也会附带的引出指针的另外一个能力,即加 1 的能力,因为两者息息相关,也是公司常考题目。
#include
void main()
{
int *p = (int*)0x0012ff7c;
char *pc = (char *)p;
printf("%p : %p\n",pc,pc+1);
short *ps = (short *)p;
printf("%p : %p\n",ps,ps+1);
int *pi = p;
printf("%p : %p\n",p,pi+1);
double *pd = (double *)p;
printf("%p : %p\n",pd,pd+1);
}
运行结果:
分析: 同为指针,同为加 1,发现各自加 1 的能力不一样,其实指针加 1 能力就是受到指针所指 类型的影响,虽然只是加 1,但是指针不这么认为,指针认为自己加 1,实则是要跨越一 个类型空间,但是类型多大了,就有指针所指类型来决定,因此我们看到了字符指针加 1 加了 1 个字节,而双精度浮点类型加 1 却加了 8 个字节。
总结:指针最终的就是其两值四方面,只有真实的掌握好了这些方面,才能使我们立于 不败之地,在次,光认识指针是不够,指针在 C 语言中几乎无所不能,根据个人经验, 一旦指针跟数组、函数结合,那么指针的使用复杂度就会陡然上升,反过来,如果我们把指针跟数组和函数的结合掌握好,我想,很多复杂的指针使用就会迎刃而解,后面我 们将一一解析