指针
1、指针是什么
指针就是地址。
平时口语中的指针,通常指的是指针变量,即用来存放指针的变量,也就是用来存放地址的变量。
多字节变量取地址得到的地址是它的第一个字节的地址。
指针变量在32位平台上是4个字节,在64位平台上是8个字节。以32位平台为例,一个地址是32个比特位,一个字节可以存储8个比特位,所以需要4个字节来存储。
演示如下:
int main()
{
int a = 1;
int* pa = &a;
printf("%p\n", pa); //打印结果 004FF73C
printf("%p\n", &a); //打印结果 004FF73C
printf("%p\n", *pa); //打印结果 00000001。只是以16位进制打印了1。
printf("%d\n", *pa); //打印结果 1
//可以看出 pa 内存储的就是a的地址。
// pa就是指针变量。指针是pa内存储的数据 004FF73C
//*pa(解引用)得到的就是pa。
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pb = &arr[2];
printf("%p\n", arr);
printf("%p\n", pb);
return 0;
}
2、指针和指针类型
指针类型决定了指针解引用的时候权限有多大。
指针类型决定了指针走一步能走多远(步长)。
演示如下:
int main()
{
int a = 0x11223344; //0x11223344 是16进制数,十进制下是287454020
int b = 0x11223344; //0x11223344 是16进制数,十进制下是287454020
int* pa = &a;
//进入调试页面可以看到 pa 的值是 0x004ffd84 十进制为5242244
char* pb = &b;
//进入调试页面可以看到 pb 的值是 0x004ffd78 '\x1' 十进制为 5242232
//即pb只占int b地址的第1个字节。
*pa = 0;
*pb = 0;
//进入调试页面可以看到pa的值变为 0 。
//进入调试页面可以看到pb的值变为 0'\0' 。
//进入内存可以看到地址pa上的值变成00 00 00 00 。
//进入内存可以看到地址pb上的值编程00 33 22 11 。
printf("%d\n", a); //打印结果 0 。
printf("%d\n", b); //打印结果 287453952 ,16进制是11 22 33 00。
//可以看到char*的权限比int*的权限小,只改变了b的最低位4字节的44。
int c = pa + 1;
//进入调试看到 c 是5242248 (上述十进制+4)
int d = pb + 1;
//进入调试看到 d 是5242233 (上述十进制+1)
//可见char* 的步长为1,int*的步长为4。
return 0;
}
3、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1)野指针成因
a、指针未初始化
演示如下:
int main()
{
int* pa;
*pa = 0;
//报错,因为pa未初始化,指向是随机的。
//部分编译器能执行,但也有其他问题。
return 0;
}
b、指针越界访问
演示如下:
int main()
{
int arr[10] = { 0 };
int* pa = arr;
printf("%d\n", *(pa + 10)); //打印结果 -858993460。
//指针越界,数组最后一个元素地址是 pa+9 。
//有些编译器会报错,无法执行。
return 0;
}
c、指针指向的空间释放
演示如下:
int* test()
{
int a = 0;
return &a;
}
int main()
{
int* pa = test();
*pa = 0;
//a的地址空间已经被释放,此时是非法访问,容易出BUG。
return 0;
}
野指针属于编程陋习,尽量避免使用!
2)如何规避野指针
a、指针初始化
b、小心指针越界
c、指针指向空间释放及时置NULL
d、避免返回局部变量的地址
e、指针使用之前检查有效性
演示如下:
int main()
{
int* pa = NULL;
//不确定地址的时候,可以给指针赋值NULL。
//指针初始化NULL相当于 int初始化0;
//指针指向的空间释放后可以及时置 NULL。
//需要使用该指针的时候先判断一下。
int a = 0;
pa = &a;
//判断指针是否为NULL,不为NULL再使用。
if (pa != NULL)
{
*pa = 1;
printf("%d\n", *pa); //打印结果 1 。
}
return 0;
}
如上,做到使用指针时初始化,如int a = 0,int * pa = &a,没有变量时,可以先用NULL初始化。使用指针时则先确定指针不为NULL。这样基本可以规避大部分野指针。
4、指针运算
1)指针 + - 整数
2)指针 - 指针
3)指针的关系运算
演示如下:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int* paend = &arr[9];
printf("%d\n", *(pa + 2));
//打印结果 3。即指针+2 指向的是第3个元素。
printf("%d\n", paend - pa);
//打印结果 9 。即指针相减得到的是元素个数。
//同类指针相减才有意义。
if (paend > pa)
{
printf("关系运算"); //打印结果 关系运算。
}
//数组地址由低到高,最后一个元素地址比第一位元素地址高。
//可以看到,指针之间可以进行关系运算,判断大小。
return 0;
}
5、指针和数组
数组名表示的是数组首元素的地址(例外情况初识数组中已说明)。
可以直接通过指针访问数组。
演示如下:
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%p\n", arr); //打印结果是 007DF9F4
printf("%p\n", &arr[0]); //打印结果是 007DF9F4
//可见数组名是数组首元素地址。
int* pa = arr;
printf("%d\n", *(pa + 1)); //打印结果 1。
//可见通过指针直接找到了数组第二个元素。
return 0;
}
6、二级指针
指针变量也是变量,这个指针变量存放的指针就是二级指针。
演示如下:
int main()
{
int a = 9;
int* pa = &a;
int** ppa = &pa; //ppa 就是二级指针,即存放指针变量的指针。
printf("%d\n", **ppa); //打印结果是 9 。
return 0;
}
7、指针数组
指针数组是存放指针的数组。
演示如下:
int main()
{
int* parr[5] = { NULL };
//parr[] 就是指针数组,数组里存放的是指针。
return 0;
}