指针
1. 指针是什么
- 指针是内存中一个最小单元的编号,也就是地址(最小单元为1字节)
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
借助内存理解
指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量(存放在指针中的值都被当成地址处理)
#include <stdio.h>
int main()
{
int a = 10; //在内存中开辟一块空间
int* p = &a; //这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。
return 0;
}
如何编址
- 经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的;
- 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB) 4G的空间进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间
指针变量大小
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
2. 指针和指针类型
下面代码中p指针变量的类型是什么?
int num = 10;
p = #
指针变量的类型有那些?
char *pc = NULL; //存放 char 类型变量的地址
int *pi = NULL; //存放 int 类型变量的地址
short *ps = NULL; //存放 short类型变量的地址
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
指针类型的意义是什么?
2.1 指针±
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
运行结果:
0019FC34
0019FC34
0019FC35
0019FC34
0019FC38
由上可知:
指针的类型决定了指针向前或者向后走一步有多大(距离)
2.2 指针的解引用
#include <stdio.h>
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化
*pi = 0; //重点在调试的过程中观察内存的变化
return 0;
}
调试后可知:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
3. 野指针
什么是野指针?
指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1 野指针成因
- 指针未初始化
#include <stdio.h>
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
- 指针越界访问
#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.2 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
#include <stdio.h>
int main()
{
int* p = NULL;
//....
int a = 10;
p = &a;
if (p != NULL)
{
*p = 20;
}
return 0;
}
4. 指针运算
4.1 指针±整数
数组元素清零
#define N_VALUES 5
float values[N_VALUES];
float* vp;
int main()
{
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
}
4.2 指针-指针
用指针计算字符串长度
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
注意:
指针和指针相减的前提:两指针指向同一块空间
4.3 指针的关系运算
关系运算
将指针指向地址和别的地址进行比较
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
修改为:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
- 两段代码的目的都是从后往前,将数据元素清零
- 不过,他们的地址比较方式不一样(画图更清楚)
对于第二段代码,实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
看下面代码
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
运行结果:
0075F8D8
0075F8D8
可知
数组名表示的是数组首元素的地址(2种情况除外)
- sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
- &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
用指针访问每个元素,打印每个元素地址
#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]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("&arr[%d] = %p; p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
运行结果:
&arr[0] = 010FFB0C; p+0 = 010FFB0C
&arr[1] = 010FFB10; p+1 = 010FFB10
&arr[2] = 010FFB14; p+2 = 010FFB14
&arr[3] = 010FFB18; p+3 = 010FFB18
&arr[4] = 010FFB1C; p+4 = 010FFB1C
&arr[5] = 010FFB20; p+5 = 010FFB20
&arr[6] = 010FFB24; p+6 = 010FFB24
&arr[7] = 010FFB28; p+7 = 010FFB28
&arr[8] = 010FFB2C; p+8 = 010FFB2C
&arr[9] = 010FFB30; p+9 = 010FFB30
由上可知
p+i 其实计算的是数组 arr 下标为i的地址。那我们就可以直接通过指针来访问数组。看下面代码
#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]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
6. 二级指针
#include <stdio.h>
int main()
{
int a = 0;
int* p = &a; //一级指针,存放 数据a 的地址
int** pp = &p; //二级指针,存放 指针p 的地址
printf("pp=%p, &p=%p\r\n", pp, &p);
printf("*pp=%p, p=%p, &a=%p\r\n", *pp, p, &a);
printf("**pp=%d, *p=%d, a=%d\r\n", **pp, *p, a); //数值%d,地址%p
return 0;
}
运行结果:
pp=012FFD0C, &p=012FFD0C
*pp=012FFD18, p=012FFD18, &a=012FFD18
**pp=0, *p=0, a=0
总结
多加练习方能更加理解
7. 指针数组
以下是整形数据,字符数组
int arr1[5];
char arr2[6];
指针数组是怎样的?
int* arr3[5];
arr3是一个数组,有五个元素,每个元素是一个整形指针,如下图