指针
一、指针和指针类型
指针就是地址,我们平常说的指针其实是指针变量,用来存放内存地址的变量
指针的创建形式:指针类型+* 指针名
例:
int a=0;
int* pa=&a;//存放变量a的内存地址
1. 表示pa是一个指针,int表示指针pa存放的是整型数据的内存地址
2. 在32位平台上,指针所占内存空间为4字节,在64位平台上,指针所占内存空间为8字节,与它所存储哪种数据类型的地址无关
3. 指针类型决定了指针被解引用时能访问的内存空间,决定了指针加减整数时能够跳过的字节数
例1:int型指针被解引用时能访问4个字节的内存空间,而char型指针被解引用时只能访问1个字节
例2:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0;
int* pa = &a;
char* pb = (char*)&a;//取地址a取出的地址是int型的,需要进行强制类型转换成char型再赋值给char型指针
printf("pa=%p\n", pa);
printf("pb=%p\n", pb);
printf("pa+1=%p\n", pa + 1);//对不同类型的指针进行加减
printf("pb+1=%p\n", pb + 1);
return 0;
}
输出结果为:
可以看出int型指针pa加1后能跳过4个字节,而char型指针pb加1后跳过1个字节
二、指针的解引用
int *p=NULL;//初始化指针p,表示p是空指针
int a=0;
p=&a;//将a的地址赋值给p
*p=10;//将a的值改为10
*是解引用操作符, *p表示通过指针p所存储的地址找到变量a,即解引用,所以 *p就是变量a
三、指针的运算
1. 指针加减整数
例:
int arr[3]={1,2,3};
int* p=arr;//p存储首元素的地址
printf("%d %d\n", *p ,*(p+1));//p+1表示p跳过4个字节,也就是下一个元素的地址,对p+1解引用就是arr[1]
//输出结果为1 2
2. 指针减指针
表示两个指针之间的元素个数
例:打印字符串长度
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char ch[] = "abcdef";
char* p1 = ch;
char* p2 = ch;
while (*p2 != '\0')
{
p2++;
}
printf("字符串的长度为:%d\n", p2 - p1);
return 0;
}
输出结果为 字符串的长度为:6
3. 指针的关系运算
指针可以用关系运算符进行比较:
①==、!= 用于比较两个内存地址是否相等
②<、>、<=、>= 用于比较两个地址在内存中的先后顺序,一般用来比较两个地址在数组中的相对位置
例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3 };
int* p = arr;
for (p = &arr[0]; p <= &arr[2]; p++)//通过比较指针的值遍历数组
{
*p = 20;//将数组元素全部改为20
}
for (p = &arr[0]; p <= &arr[2]; p++)
{
printf("%d ", *p);//通过指针对各元素的值进行操作后,打印出数组元素
}
return 0;
}
输出结果为:
四、指针和数组
例:
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
输出结果为:
1. 由此可知,数组名本质上就是首元素的地址,即指针
2. 函数调用时数组传参实际上就是传递首元素的地址,因此可以通过指针来访问整个数组
例:
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,7 };
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;
}
输出结果为:
五、野指针
所谓野指针就是指针所指向的位置是未知的(随机的、不正确的、没有明确限制的)
成因:
1. 指针未进行初始化,指向的对象不明确
2. 指针越界访问,超出了数组的范围
3. 指针指向的空间释放
例:
#include <stdio.h>
int* test()
{
int a = 0;
return &a;
}
int main()
{
int* p = test();//test函数完成调用后局部变量a就销毁了,它所占的内存空间就还给系统了
//但指针p还存储着a的地址,因此p为野指针
return 0;
}
因此为了规避野指针需要:
1. 指针进行初始化
2. 注意越界访问数组元素的问题
3. 避免返回局部变量的地址
六、二级指针
二级指针:存储一级指针变量地址的指针
例:
int a=10;//变量a的值为10
int* pa=&a;//一级指针pa存储a的地址
int** ppa=&pa;//二级指针ppa存储一级指针pa的地址
图解如下:
对于int** ppa如何理解?
a=20;//直接改变a的值
*pa=20;//对pa一次解引用得到a,并改变a的值
**ppa=20;//*ppa表示一次解引用得到*pa,第二次解引用才得到a,即**ppa=a=20
七、指针数组
指针数组:存放指针的数组
基本形式:
存放的指针类型 数组名[]={}
例1:
int a,b,c;
int* p1=&a;
int* p2=&b;
int* p3=&c;
int* arr[]={&a,&b,&c};//等价于上述操作,更为简洁
例2:利用指针数组打印一个二维数组
#define _CRT_SECURE_N0_WARNINGS
#include <stdio.h>
int main()+
{
int arr1[] = { 1,2,3,4 };
int arr2[] = { 2,3,4,5 };
int arr3[] = { 3,4,5,6 };
int* arr[] = { arr1,arr2,arr3 };//数组名实际上就是首元素的地址
for (int i = 0; i < 3; i++)//外循环对指针数组进行遍历,找到每行一维数组的首元素地址
{
for (int j = 0; j < 4; j++)//内循环对每行一维数组进行遍历,通过每行首元素的地址进行索引
{
printf("%d ", arr[i][j]);//arr[i]相当于*(arr+i),即遍历指针数组
//同理,arr[i][j]相当于*(arr[i]+j),即找到每行一维数组的具体元素
}
printf("\n");
}
return 0;
}