一、什么是指针
可以这样理解内存:
指针是个变量,存放的是内存单元的地址(编号) 。
#include<stdio.h>
int main()
{
int a = 10;
int *p = &a;//创建一个指针变量,取地址符将a的地址取出用来初始化p
return 0;
}
也就是说指针就是变量,用来存放地址的变量(存放在指针中的值都被当做地址处理)。
二、指针与指针类型
变量有各种各样的类型,整形,浮点型等。指针有没有这样的类型呢?准确来讲是有的。
char *pc = NULL;//指针创建时,必须要初始化,将一个变量的地址给它或者赋值为NULL
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的创建是type + *的。其中,char*类型的指针存放的是char类型变量的地址。short*类型的指针是为了存放short类型变量的地址。int*类型的指针是为了存放int类型变量的地址。
那么指针的类型到底有什么意义呢?
#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);
}
输出结果为:
也就是说指针的类型决定了指针向前或向后走一步会移动几个字节。
指针的解引用
#include<stdio.h>
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
*pc = 0;
return 0;
}
这里char*类型的指针只能控制一个字节。
#include<stdio.h>
int main()
{
int n = 0x11223344;
int* pc = &n;
*pi = 0;
return 0;
}
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
三、野指针
野指针就是一个指向的位置是不可知的指针变量。在定义的时候没有初始化指针变量,其值就是随机的,指针变量的值是别的变量的地址,意味着野指针指向了一个地址不确定的变量,这时候如果解引用那就是去访问一个不确定的地址,结果是不可知的。
野指针的成因:
指针未初始化
当声明一个指针变量但没有给它赋值之前,默认情况下它的值是不确定的。如果此时使用这个指针,就可能导致野指针问题。
#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;
}
指针指向空间的释放
如果指针指向的是一个局部变量的地址,并且这个变量的作用域已经结束,那么这个指针也就成为了野指针。
int *p = malloc(sizeof(int));
free(p);
printf("%d", *p); // 内存已释放,但仍然尝试访问
如何规避野指针?
- 指针在创建时初始化它。
- 小心指针越界。
- 指针指向的空间释放时,将其置为NULL。
- 指针使用之前检查其有效性。
#include <stdio.h>
int main()
{
int* p = NULL;
int a = 10;
p = &a;
if (p != NULL)//检查其有效性
{
*p = 20;
}
return 0;
}
四、指针和数组
什么是数组名?
#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;
}
输出结果为:
数组名和数组首元素的地址是一样的。也就是说数值名表示的是数组首元素的地址。
#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 (int i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
输出结果为:
所以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]);
for (int i = 0; i < sz; i++)
{
printf("&arr[%d] = %d\n", i, *(p + i));
}
return 0;
}
五、二级指针
指针变量也是变量,只要是变量就有地址,那指针变量的地址存放在哪里?这就是二级指针。
int a = 10;
int *p = &a;//a的地址存放在p中
int **pa = &p;//p的地址存放在pa中
//p是一级指针,pa是二级指针
二级指针的运算
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** pa = &p;
printf("%p\n", &a);
printf("%p\n", *pa);
printf("%d\n", **pa);
return 0;
}
*pa对pa中的地址进行解引用,这样找到的是p,p中存放的是a的地址。
**pa,先找到p,再对p进行解引用,那找到的就是a。
六、指针数组
指针数组是一个存放指针的数组。与整型数组,字符数组等类似,指针数组里的每一个元素都是一个指针变量,不过这种数组的元素可以指向不同类型的数据,也可以指向相同类型的数据。