目录
1.指针是什么
在计算机科学中,指针是编程语言的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解:
1.1 内存
内存单元的编号=地址=指针
1.2 指针变量
存放地址的变量
我们可以通过&(取地址操作符)取出变量的内存地址,把地址可以存放到一个变量中,这个
变量就是指针变量
int main()
{
int a = 10;//a占4个字节
int* pa = &a;//a变量占用4个字节的空间,
//这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。
return 0;
}
1.2.1 指针变量的大小
int main()
{
int* pa;
char* pb;
float* pc;
printf("%d ", sizeof(pa));
printf("%d ", sizeof(pa));
printf("%d ", sizeof(pa));
return 0;
}
输出:4 4 4
1.2.2 指针类型的意义
①指着类型决定了指针解引用的权限有多大
int a = 0x11223344;
int* pa = &a;
*pa = 0;
例:指针变量是int型,解引用一次可以访问4个字节
int a = 0x11223344;
char* pa = &a;
*pa = 0;
例:指针变量是char型,解引用一次可以访问1个字节
②指针的类型决定了指针向前或者向后走一步有多大(步长)
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
输出:004FFC40 004FFC41 004FFC40 004FFC44
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 10; i++)
{
*(p + i)=1;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出:1 1 1 1 1 1 1 1 1 1
2.野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
2.1 野指针成因
1. 指针未初始化
int main()
{
int* p;//局部指针变量未初始化,默认为随机值
*p = 20;//非法访问内存
return 0;
}
2. 指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i <= 10; i++)
{
*p= i;//指针指向的范围超出数组arr的范围
p++;
}
return 0;
}
3. 指针指向的空间释放
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p=test();//a在出函数时空间已经被释放
*p = 20;
return 0;
}
2.2 如何规避野指针
1. 指针初始化
int main()
{
//1.初始化为空指针
int* p=NULL;
//2.初始化为有效地址
int a = 10;
int* ptr = &a;
return 0;
}
2. 小心指针越界
C语言本身是不会检查数据的越界行为的
3. 指针指向空间释放及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
int main()
{
int* p=NULL;
if (p != NULL)//检查有效性
{
*p = 10;
}
return 0;
}
3.指针运算
3.1 指针+/-整数
#define N_VALUES 5
float values[N_VALUES];
float* vp;
//指针+-整数;指针的关系运算
int main()
{
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;//随着下标增长,地址由低变高
}
return 0;
}
例:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pend = arr + 9;
while (p <= pend)
{
printf("%d ", *p);
p++;
}
return 0;
}
输出:1 2 3 4 5 6 7 8 9 10
3.2 指针-指针
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d ", &arr[9] - &arr[0]);
return 0;
}
输出:9
指针和指针相减的前提是两个指针指向同一块空间
指针-指针得到的是指针和指针之间的元素个数
3.3 指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
4.指针和数组
数组名是数组首元素地址
#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;
}
输出:0133FB44 0133FB44
既然可以把数组名当成地址存放到一个指针中,我们也可以使用指针来访问一个数组
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
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的地址,那我们就可以直接通过指针来访问数组
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;
}
例:
arr[2] == *(p+2) == *(2+p) == *(2+arr) == *(arr+2)
可以得到arr[2]<==>*(arr+2),因此,*(2+arr)<==>2[arr]
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int* p = arr;
printf("%d ", 2[arr]);
printf("%d ", arr[2]);
printf("%d ", 2[p]);
printf("%d ", p[2]);
return 0;
}
输出:3 3 3 3
这是因为,[ ]是一个操作符,而2和arr是两个操作数,p又与arr等价
5.二级指针
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量,一级指针
//ppa就是二级指针变量
int** ppa = &pa;//pa也是个变量,&pa取出pa在内存中的起始地址
return 0;
}
对于二级指针的运算有:
①*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
②**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a
6.指针数组
指针数组其实就是存放指针的数组,如arr3