目录
指针就是地址,口语中说的指针通常指的是指针变量。
不知道你们发现了没,在上文我们用的一直是指针而不是指针变量。这其实只是我们的习惯用法,原理大概和鲁迅先生说的:“世上本没有指针,讲的人多了就形成了指针”一样吧。
我们可以这样理解:
上图旁边的十六进制数就是一个地址,地址其实就是给这一块空间的编号。
而指针变量就是一个专门用来储存这个编号的变量,这个变量本身也占一个空间。
我们知道了指针变量是储存地址的。众所周知我们的内存单位有字节还有比特(1字节=8比特)。
那么接下来的问题是:我们一个地址形容一个字节好还是一个地址形容一个比特好。
经过计算,一个地址形容字节要比形容比特好。
理解如下:
你想象一下,一个int类型整数就有32个比特,要了32个地址,要是一个int类型的数组呢?如果我们要想找到数组中的一个数,可能他的地址就非常的大,不便于计算。如果以字节位单位的话,一个int整数只需要4个字节。
32与64其实形容的是cpu一次能够处理数据的位数。以32位为例:
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0); 那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。也就是我们的电脑能够以管理2^32个内存空间。
每个地址标识一个字节,那我们就可以给(2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB)4G的空间进行编址(用手机的词来形容的话,这就是运行内存,里面储存的数据是一断电就会消失的)。 同样的方法,那64位机器,如果给64根地址线,我们的机器就能管理2^64的内存空间。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
再我们的编译器中其实是能改编这个环境的(仅仅在编译器内)
x86是32位环境
x64是64位环境
我们定义一个整型指针是这样的:
int *p;
字符指针是这样的:
char *p;
p是我们的指针变量而“int *”“char *”则是指针的类型。
顾名思义指针类型的作用就是决定了指针变量所指向的内容。我在这里想讲的是,指针类型具体是如何决定指针变量的所指向的内容的。
我们已知计算机中的内存是以如下的形式分布:
若在地址0x0076F888上存放的是变量a
int *p=NULL;
p=&a;
则上面这段代码就会给 p赋予从0x0076F888到0x0076F89c的地址。
若是
double *p=NULL;
p=&a;
则会给p赋予从包括0x0076F888开始到往后的8个字节。
指针的这个特效还会带来一下的几个效果。
借助一个代码示例来看看只着呢类型的不同会对指针加减整数参赛怎么样的影响:
- #include <stdio.h>
- //演示实例
- int main()
- {
- int n = 10;
- char* pc = (char*)&n;
- int* pi = &n;
-
- printf("&n=%p\n", &n);
- printf("pc==%p\n", pc);
- printf("pc+1=%p\n", pc + 1);
- printf("pi=%p\n", pi);
- printf("pi+1=%p\n", pi + 1);
- return 0;
- }
结果为:
我们会发现char *类型+1只前进了一个字符,但是int *类型的指针前进了四个指针。
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
来看一段代码:
- #include <stdio.h>
- int main()
- {
- int n = 0x11223344;
- char *pc = (char *)&n;
- int *pi = &n;
- *pc = 0; //重点在调试的过程中观察内存的变化。
- *pi = 0; //重点在调试的过程中观察内存的变化。
- return 0;
- }
这个是n的地址以及n内储存的数据。
我们通过调是的手段发现:在运行
*pc=0;的时候,n的值发现了如下的变化:44呗改变为00.
在运行*pi=0;时数据变化为:剩下的几个直接全部改变。
造成这样差距的原因是:指针的类型不同,指针类型决定了调用时访问的字节个数。
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
- int *p;
- p=20;
2.指针越界
- #include <stdio.h>
- int main()
- {
- int arr[10] = {0};
- int *p = arr;
- int i = 0;
- for(i=0; i<=11; i++)
- {
- //当指针指向的范围超出数组arr的范围时,p就是野指针
- *(p++) = i;
- }
- return 0;
- }
3.指针所指向的空间被释放
1. 指针初始化
比如:
int *p=NULL;
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
局部变量只是一个临时创建的变量。
5. 指针使用之前检查有效性
地址是否已经被释放了。
指针的运算总共有三种类型:
1.指针+-整数
2.指针-指针
3.指针的关系运算
🌌4.1指针+-整数
设有一个整型数组:int arr[10];
我们知道arr为整个数组的首地址,我们就可以这样调用数组元素:arr[4]==*(arr+4)。
🌌4.2指针-指针
当我们自己写一个strlen()函数时就会用到这个知识点:
- int my_strlen(char *s)
- {
- char *p = s;
- while(*p != '\0' )
- p++;
- return p-s;
- }
字符串最后一个元素(‘\0’)的地址减去第一个元素的地址即可得到元素个数。
1.指针与指针适合做减法而不是加法;
2.使用的场合,两个指针同时指向一个元素的符号,相减得指针中间得元素个数;
3.不同类型得指针相减时没有你意义的。
🌌4.3指针的关系运算
指针的关系运算(也就是地址之间比较大小)比如:
- float *vp=NULL;
- float values[5]={1,2,3,4,5,}
- for(vp = &values[4]; vp >= &values[0];vp--)
- {
- *vp = 0;
- }
在这里我们就用到了指针之间的比较:vp>=&values[0];
但是这里有一点需要注意:
标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存
就比如数组中指针p1可以和指针p2比较,但是不允许和p3比较。
由这个代码以及运行结果我们可以得出:数组名表示数组首元素地址 。既然如此,我们就可以将数组名当成一个地址存放到一个指针,并用指针来访问数组。
- #include <stdio.h>
- int main()
- {
- int arr[] = {1,2,3,4,5,6,7,8,9,0};
- int *p = arr; //指针存放数组首元素的地址
- int sz = sizeof(arr)/sizeof(arr[0]);
- for(i=0; i<sz; i++)
- {
- printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
- }
- return 0;
- }
运行结果:
二级指针就是存放指针变量地址的指针,定义方式为:type **ppa;
int a=0;
int *pa=&a;
int **ppa=&pa;
二级指针的运算有:
1. *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
2.**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a。
指针数组时指针还是数组?答案是数组,存放指针的数组。
char arr[10],中arr存放的是char类型;int arr[10]存放的是int类型;那么int *arr[10],则存放的是int *类型,int *类型即为指针。