2026/08/01 C语言的指针

对于32位系统,内存地址范围是0x0000_0000~0xFFFF_FFFF,也就是内存大小为4GB,内存地址指的是内存中单元的编号,编号是固定的。

所以内存地址(存储单元的编号)本质就是一个整数,对于32位系统而言,地址所对应的编号是4字节的正整数。

用户如果打算定义一个变量来存储一个内存地址,则需要定义变量的时候指明该变量中存储的是一个地址

C语言中把用于存储地址的变量称为指针变量,因为通过变量中的地址可以指向某个存储单元!

指针指向的是地址,所以可以把指针理解为地址,也可以把地址当做指针使用。
注意:如果打算获取某个地址下的值,必须使用' * '间接运算符 , *地址 == 地址下的值

1.指针变量定义格式:数据类型 *变量名; 比如 int *p; char *p; int * buf[5];

定义变量的目的就是为了申请内存单元,32bit系统下需要4个存储单元才能记录一个地址,而记录的地址和变量本身的地址是不一样的。变量的存储单元就相当于是一个容器,记录的地址就相当于数据而已。

为了提高程序的可靠性,为了避免异常出现所以就算不存储有效地址,也应该对定义的指针变量进行初始化。
注意:对指针变量进行初始化,则应该把指针变量对应的内存初始化为0,但是0只是一个整数,并不是地址,而指针变量就是应该存储地址。

用户应该把普通整数0进行强制转换,转换为一个地址 0x00000000 --> (void *)0x00000000
int *p = (void *)0x00000000; //可读性较差,所以C语言中提供了一个宏定义 NULL 空指针

linux系统的内存中有一部分内存是属于保留区,保留区地址范围就是0x0000_0000 ~ 0x0804_8000,属于用户没有权限访问的内存空间,一旦用户访问这块区域,就会导致段错误。

int *p = NULL; //对指针变量进行初始化,目的是防止野指针出现,为了避免段错误的!!!

普通整数的算术运算和地址的算术运算的理解是不同的,一般对于普通整数可以进行算术运算,则结果也是一个整数。但是对于指针变量中存储的地址进行算术运算,一般只能进行加法运算和减法运算,对地址进行加法运算和减法运算,其实就是对地址进行偏移而已,偏移的单位一般是应该以字节为单位。对于指针变量的偏移,要考虑到变量中存储的地址的数据类型,所以 地址 + 1 不表示存储单元向后偏移1个字节,应该是向后偏移 (1 * 数据类型)个字节。

#include <stdio.h>
int main(int argc, char const *argv[])
{int a=0;printf("%p\n", &a);	        //打印变量a的地址 printf("%p\n", &a+1);		//打印变量a的地址加1,即a的地址加上一个int类型的大小(通常为4字节)return 0;
}

运行结果:

image

2.数组指针
用指针变量指向数组的地址,就相当利用指针变量来对数组的地址进行备份,提高了访问数组的安全性,而利用指针变量来指向数组的地址,被称为数组指针!!!! int buf[5]; int *p = buf;

#include <stdio.h>
int main(int argc, char const *argv[])
{int buf[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};for (int i = 0; i < 10; i++){printf("buf[%d]: %d\n", i, buf[i]);}int *ptr = buf;						//定义一个数组指针ptrbuf[0] = 100;						//修改buf数组的第一个元素的值为100printf("ptr[0]: %d\n", ptr[0]);*(ptr + 1) = 200;			 		//通过指针ptr修改buf数组的第二个元素的值为200printf("buf[1]: %d\n", buf[1]);return 0;
}

运行结果:
image

#include <stdio.h>
int main(int argc, char const *argv[])
{int aa[2][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}};	//二维数组int* ptr1 = (int*)(&aa+1);							//指向二维数组的地址加1,指向二维数组的末尾int* ptr2 = (int*)(*(aa+1));						//指向二维数组第二行的首元素printf("%d,%d", *(ptr1-1), *(ptr2-1));return 0;
}

运行结果:
image

指针变量访问二维数组中的元素

#include <stdio.h>
int main(int argc, char const *argv[])
{int buf[2][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}};	//二维数组for (int i = 0; i < 2; i++){for (int j = 0; j < 5; j++){printf("buf[%d][%d]: %d\n", i, j, buf[i][j]);}}int (*p)[5] = buf;		//指向二维数组的指针buf[0][0] = 100;		//修改二维数组的值printf("buf[0][0]: %d\n", buf[0][0]);*(*(p+1) + 1) = 200;	//修改二维数组的值printf("buf[1][1]: %d\n", buf[1][1]);return 0;
}

运行结果:

image

注意:

int *(*p)[2][3];//(*p)说明该变量是一个指针//[2]说明该指针指向一个包含2个元素的数组//[3]说明该数组的每个元素是一个包含3个元素的匿名数组// * 说明该数组的每个元素是一个指针//int说明该指针指向的类型是int类型

3.指针数组
如果在一个数组中,每个元素都是一个指针,则C语言中把这种结构称为指针数组。

指针数组的定义格式: 数据类型 *数组名[元素个数]; // 比如 int *buf[5];

注意:[]后缀运算符 优先级高于 ‘*’间接运算符 ,所以 buf[5] 作为一个整体,剩余部分就是数组中元素的类型,所以 int *就是数组中元素的类型,其中int是用于修饰指针指向的地址中数据的类型。

一个有10个指针的数组,该指针是指向一个整型数的:int* p[10];
一个指向有10个整型数数组的指针:int (*p)[10];

4.多级指针
如果一个指针变量中存储的地址是另一个指针变量的地址,则把这种结构称为二级指针。
二级指针定义格式: int data; //整型变量 int *p1 = &data; //指针变量 int **p2 = &p1;

#include <stdio.h>
int main(int argc, char const *argv[])
{int data = 10;int *p1 = &data;int **p2 = &p1;\data = 20;					//直接修改data的值printf("data: %d\n", data);*p1 = 30;					//通过指针p1修改data的值printf("data: %d\n", data);**p2 = 40;					//通过指针p2修改data的值printf("data: %d\n", data);p1[0] = 50;					//通过指针p1修改data的值printf("data: %d\n", data);p2[0][0] = 60;				//通过指针p2修改data的值printf("data: %d\n", data);return 0;
}

运行结果:

image

#include <stdio.h>
int main(int argc, char const *argv[])
{char *c[] = {"ENTER", "NEW", "POINT", "FIRST"};char **cp[] = {c + 3, c + 2, c + 1, c};char ***cpp = cp;printf("%s\n", **++cpp);			//指针先偏移,然后解引用printf("%s\n", *--*++cpp + 3);		//指针先偏移,然后解引用,再偏移printf("%s\n", *cpp[-2] + 3);		//cpp[-2]表示cpp指针向前偏移两个位置,然后解引用,得到c+2,再偏移3个位置printf("%s\n", cpp[-1][-1] + 1);	//cpp[-1]表示cpp指针向前偏移一个位置,然后解引用,得到c+2,再偏移-1个位置,得到c+1,再偏移1个位置return 0;
}

运行结果:

image