很多朋友在初学C语言的时候,难免都会在指针这块停留许久,包括我本人。久久不能释怀,对其爱恨交织。静下心来,想总结一下自己在学指针的时候的一点心得,也是第一次写博客,激动万分,希望朋友们能多多指正、多多批评!
首先呢,针对指针,给初学指针的朋友一个最感性的认识:所谓指针,指的是,一个量,是一个储存的内容是地址的一个量,这个概念包含两个点:
一、指针是个量,对应着一块内存区域;
二、指针存储的信息是某个内存单元的地址。
如上图所示,为了存储32位的地址数据,指针占据了4个字节,每个字节8个二进制位,该指针中存储的是某个类型在内存的地址数据,此处以double类型为例,double类型的数据占据8个内存字节,将其首字节的地址存储在指针中,这样,通过指针可以访问该double数据。这类似于现实生活中的地址和名片,习惯于把地址印在名片上,这里名片的作用和指针相同,存储的都是地址数据。
很明显, 在引入指针的时候,多次提到了内存,那么很自然地,接下来先简单说一下内存的一些概念!
关于内存
大家都知道,内存是平时接触较多的一个概念,从硬件上说,内存是一个物理设备,从功能上讲,内存是一个数据仓库,程序在执行前都要装载到内存中才能被中央处理器(CPU)执行。
1、计算机中的内存
以Windows系统为例,执行安装在硬盘上的某个程序,实际上是将该程序的指令和数据读入内存,供CPU执行的过程。内存是由按顺序编号的一系列存储单元组成的,在内存中,每个存储单元都是唯一的地址,通过地址可以方便地在内存单元中存取信息。内存中的数据要靠电源维持,当计算机关机或者意外断电时,其中的所有数据就永远消失了。
2、内存地址
内存地址的引入是同样的道理,为了正确访问每个内存单元,要对其进行编址,以32位计算机为例,其地址空间为32位,采用32为地址编码,诸如0X87654321的形式。内存地址是连续的,相邻内存单元空间的地址差1,可以把内存看成一个平坦连续的一维空间。
3、内存中保存的内容
在计算机中,以二进制数据的形式存放的,每个内存单元的容量是1B,即8bit。内存是CPU唯一可以直接访问的大容量设备,使用Windows的朋友都清楚,双击某个可执行的程序,CPU会执行它,这实际上是复杂的内存载入过程:
① 程序所要进行操作的对应代码装载到代码区
② 全局和静态数据等装载到数据区
③ 开辟堆栈、供临时变量等使用
4、内存与操作系统
程序在运行前,需要向操作系统申请存储空间,在内存空间足够空闲的时候,操作系统将分配一段内存空间并将外存中软件拷贝一份到内存中,并启动软件运行,在运行期间,该软件分配的空间不再分配给其他软件,当软件运行结束后,将回收该软件内存空间(但并不清楚遗留数据,Java局部变量必须初始化,原因就在此,防止被之前的遗留数据影响)
扯了这么多内存的内容,只是为了让初学的朋友有点感觉,也是为学习指针打一剂预防针!
关于指针
指针就是地址,地址就是指针。
指针变量是存放内存单元地址的变量
指针的本质是一个操作受限 的非负整数
下面从两个方面来浅谈一下指针的问题:
1、基本类型的指针
指针的定义:类型 * 指针变量名;
比如int * p; double * q;
1 include<stdio.h> 2 int main() { 3 int *p; 4 int i = 10; 5 int j; 6 // j = *p; 7 // p = &i; 8 // *p = i < -> i = i 9 // j = *p; 10 // char ch = ‘a‘; 11 // p = &ch /*类型不一致 错*/ 12 // printf("i = %d,j = %d,*p = %d",i,j,*p);// 10 10 10 13 return 0; 14 }
在上面的代码3~5行定义了三个变量(如下图),分别是:int *形的变量p、int形的变量i、int形变量j。需要清楚的是在第三行中,p是变量名字,int * 表示该变量只能存储int类型变量的地址。
如果在指针变量声明之初确实不知道将此指针指向何处,最简单的方式是将其置为“0”,C语言中提供了关键字NULL: int * pInt = NULL;
如果放开第6行注释。会出现什么结果呢,势必会报错!因为*p保存的是地址,但这句话上面的代码中并没有给p赋值。p不知道哪个单元的,p指向的是一个不确定单元,垃圾数据!如果放开第9行就不会有这种结果了,因为在第7行对p进行了赋值,p变量保存了i的地址了!
在第七行中,有三句话:
① p保存了i的地址值,p指向了i。
② 但并不说明p等于i或者说i等于p。修改i的值并不影响p的值,修改p的值也不影响i的值。
③ i能代表什么,*p就能代表什么。(如第8行)
再看第10~11行,这个赋值对吗?定义一个char类型字符,将他的地址保存在整形指针p中,毫无疑问,这也是错的,前面已经说了,int * 表示该变量只能存储int类型变量的地址。
接下来再看指针在函数中的作用:
1 #include<stdio.h> 2 3 void f(int i) 4 { 5 i = 100; 6 } 7 8 int main(void) 9 { 10 int i = 5; 11 f(i); 12 printf("%d\n",i); 13 return 0; 14 }
1 #include<stdio.h> 2 void f(int * p) // 不是定义一个名字叫*p的形参,而是定义了一个p的形参,它的类型是int 3 { 4 *p = 100; 5 } 6 7 int main(void) 8 { 9 int i = 5; 10 f(&i); 11 printf("%d\n",i); 12 return 0; 13 }
在第一个代码中,如果想通过函数改变i的值,能成功吗?很明显是不行的,i是局部变量,i输出的最终值将仍是5。
那么如何根据被调函数改变主调函数的值呢,第二段代码就实现了这个问题,通过使用”*指针”的形式,可以直接访问指针所指向的内存空间。换言之,因为通过参数传递,p保存了i的地址,那么*p 就等价于i,同时可以通过间接引用“*p = 100;”可以改写指针指向的区域!注意在第二段代码中的第二行,形参为 int * p ,不是定义一个名字叫*p的形参,而是定义了一个名字为p的形参,它的类型是int *!
2、指针与数组的关系
首先要明白,数组名就是一个指针,并且指向数组的首地址!数组元素的地址是连续的。
数组名的含义:
① 是一个指针变量
② 存放的是一维数组第一个元素的地址
③ 值不能改变
④ 指向第一个元素
下标和指针的关系:
a[i] = *(a+i)
1 #include<stdio.h> 2 void printArray(int * p,int len) { 3 p[2] = -3; 4 /* p[0] = *p p[2] == *(p+2) == *(a+2) == a[2] 5 p[i]就是主函数的a[i] 6 */ 7 int i = 0; 8 for(i = 0; i<len; ++i) { 9 printf("%d\n",p[i]); 10 } 11 } 12 int main(void) 13 { 14 int arr[5] = {1,2,3,4,5}; 15 printArray(arr,5); 16 return 0 ; 17 }
有了上面的理论再看这段代码,就不难理解,p[0] = * p;的含义了,因为数组名是一个指针,指向数组第一个元素的地址,所以数组的第一个元素p[0]就等同于*p了!
因为"a[i] = *(a+i)",可以知道p[2] == *(p+2),因为p指向第一个元素,那么p+2就自然指向第三个元素了,所以p[2] = 3是给数组的第三个元素赋值,这事没问题的!但注意*(p+2)不等同于*p+2,后者是在*p指向的元素上,给这个元素的值加上2,前者并非如此,而是给他的地址“+2”。
今天就先到这里,指针的内容多多,日后还应继续总结,继续和大家讨论!
初次写博,纰漏多多!望朋友们不吝赐教!