C语言-指针

167 阅读5分钟

指针

一、指针和指针类型

指针就是地址,我们平常说的指针其实是指针变量,用来存放内存地址的变量

指针的创建形式:指针类型+* 指针名

例:

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;
}

输出结果为:

屏幕截图 2024-12-02 195751.png

可以看出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;
}

输出结果为:

屏幕截图 2024-12-04 205209.png

四、指针和数组

例:

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

输出结果为:

屏幕截图 2024-12-04 211225.png

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;
}

输出结果为:

屏幕截图 2024-12-05 165003.png

五、野指针

所谓野指针就是指针所指向的位置是未知的(随机的、不正确的、没有明确限制的)

成因:

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的地址

图解如下:

屏幕截图 2024-12-05 212744.png

对于int** ppa如何理解?

屏幕截图 2024-12-05 213411.png
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;
}