目录
1 指针的长度
2 指针与整数的加减运算
3 指针自增与自减
4 同类型指针相减
5 指针的比较运算
6 测试题
1 指针的长度
在 C 语言中,sizeof 运算符可以用来计算指针的长度。指针的长度实际上与指针所指向的数据类型无关,而是与系统的位数(即系统架构)有关。具体来说:
- 32 位系统:无论指针指向什么类型的数据(如 int、char、float 等),指针的长度都是 4 个字节(32 位)。
- 64 位系统:无论指针指向什么类型的数据,指针的长度都是 8 个字节(64 位)。
下面是一个示例,展示了如何使用 sizeof 运算符来计算不同类型指针的长度:
#include <stdio.h>
int main()
{
int *int_ptr;
char *char_ptr;
float *float_ptr;
double *double_ptr;
printf("int 指针的长度: %zu 字节\n", sizeof(int_ptr));
printf("char 指针的长度: %zu 字节\n", sizeof(char_ptr));
printf("float 指针的长度: %zu 字节\n", sizeof(float_ptr));
printf("double 指针的长度: %zu 字节\n", sizeof(double_ptr));
return 0;
}
在 32 位系统上运行上述代码,输出可能是:
int 指针的长度: 4 字节
char 指针的长度: 4 字节
float 指针的长度: 4 字节
double 指针的长度: 4 字节
在 64 位系统上运行上述代码,输出可能是:
int 指针的长度: 8 字节
char 指针的长度: 8 字节
float 指针的长度: 8 字节
double 指针的长度: 8 字节
结论:
- 指针的长度与系统架构有关:在 32 位系统中,所有指针的长度都是 4 个字节;在 64 位系统中,所有指针的长度都是 8 个字节。
- 指针的长度与指向的数据类型无关:无论指针指向 int、char、float 还是 double,指针本身的长度是固定的,由系统的位数决定。
2 指针与整数的加减运算
指针与整数的加减运算表示指针所指向的内存地址的移动。具体来说:
- 加法运算:指针加一个整数表示指针向后移动。
- 减法运算:指针减一个整数表示指针向前移动。
指针移动的步长与指针指向的数据类型有关。每移动一个单位,指针会移动相应数据类型所占的字节数。例如:
- 如果指针指向 int 类型的数据,int 通常占用 4 个字节,那么指针加 1 会向后移动 4 个字节,指针减 2 会向前移动 8 个字节。
- 如果指针指向 char 类型的数据,char 通常占用 1 个字节,那么指针加 1 会向后移动 1 个字节,指针减 2 会向前移动 2 个字节。
数组的元素在内存中是连续存储的,因此通过数组元素可以很好地演示指针加减整数的情况。以下是一个示例:
#include <stdio.h>
int main()
{
// 创建一个包含 5 个整数的数组
int nums[] = {10, 20, 30, 40, 50};
// 创建一个指针并将其初始化为数组第一个元素的地址
int *ptr = &nums[0];
// int *ptr = nums; 或者直接指向数组名 和上面等价
// 打印指针的地址和指针所指向的值
printf("初始状态: ptr=%p, *ptr=%d \n", (void *)ptr, *ptr);
// 指针加 3,指针指向 int 类型,每个 int 占 4 个字节
// 因此,指针会向后移动 3 * 4 = 12 个字节
ptr += 3;
printf("指针加 3 后: ptr=%p, *ptr=%d \n", (void *)ptr, *ptr);
// 指针减 2,指针会向前移动 2 * 4 = 8 个字节
ptr -= 2;
printf("指针减 2 后: ptr=%p, *ptr=%d \n", (void *)ptr, *ptr);
return 0;
}
输出结果如下所示:
3 指针自增与自减
指针的自增和自减本质上是通过加减整数来实现的。自增会使指针向后移动,自减会使指针向前移动。移动的步长与指针指向的数据类型有关。每移动一个单位,指针会移动相应数据类型所占的字节数。例如,如果指针指向 short 类型的数据,short 通常占用 2 个字节,那么指针自增 1 会向后移动 2 个字节,指针自减 1 会向前移动 2 个字节。
#include <stdio.h>
int main()
{
// 创建数组,元素都是 short 类型,每个元素占据 2 个字节
short nums[] = {10, 20, 30, 40, 50};
// 定义常量记录数组长度
const int len = sizeof(nums) / sizeof(nums[0]); // 5
// 利用指针自增遍历数组元素
// 创建指针并指向数组第一个元素的地址
short *ptr = &nums[0];
// short *ptr = nums; 或者直接指向数组名 和上面等价
// 循环遍历数组
for (int i = 0; i < len; i++)
{
// 打印当前元素的索引、地址和值
printf("元素索引:%d, 元素地址:%p, 元素值:%hd \n", i, (void *)ptr, *ptr);
// 指针自增,向后移动一个 short 类型的单位(2个字节)
ptr++;
}
printf("\n");
// 循环遍历数组,从最后一个元素到第一个元素
// 此时指针超出数组界限,需先自减一次
for (int i = len - 1; i >= 0; i--)
{
// 指针自减,向前移动一个 short 类型的单位(2个字节)
ptr--;
// 打印当前元素的索引、地址和值
printf("元素索引:%d, 元素地址:%p, 元素值:%hd \n", i, (void *)ptr, *ptr);
}
return 0;
}
输出结果如下所示:
在上述示例中,当第一次循环(正序输出数组元素)结束后,指针 ptr 已经超出了数组的边界,指向了数组最后一个元素之后的位置,如下图所示。因此,在进行第二次循环(倒序输出数组元素)之前,需要先将指针重置为数组最后一个元素的地址,以确保能够正常输出。
可以使用 ptr-- 重置指针:在第一次循环结束后,指针 ptr 超出了数组的边界。可以通过 ptr-- 将指针向前移动一个单位,使其指向数组的最后一个元素。如上述代码中的第二次循环一开始就 使用 ptr-- 重置指针。
也可以直接重置指针:可以直接将指针 ptr 重新初始化为数组最后一个元素的地址,这样可以确保指针指向正确的起始位置。如下代码所示:
#include <stdio.h>
int main()
{
// 创建一个包含 5 个 short 类型的数组
short nums[] = {10, 20, 30, 40, 50};
int n = sizeof(nums) / sizeof(nums[0]); // 计算数组的长度
// 创建指针并初始化为数组第一个元素的地址
short *ptr = &nums[0];
// 正序输出数组元素
printf("正序输出数组元素:\n");
for (int i = 0; i < n; i++)
{
printf("元素索引:%d, 元素地址:%p, 元素值:%hd \n", i, (void *)ptr, *ptr);
ptr++; // 指针自增,向后移动一个 short 类型的单位(2个字节)
}
printf("\n");
// 重置指针,使其指向数组最后一个元素的地址
ptr = &nums[n - 1];
// 倒序输出数组元素
printf("倒序输出数组元素:\n");
for (int i = n - 1; i >= 0; i--)
{
printf("元素索引:%d, 元素地址:%p, 元素值:%hd \n", i, (void *)ptr, *ptr);
ptr--; // 指针自减,向前移动一个 short 类型的单位(2个字节)
}
return 0;
}
4 同类型指针相减
相同类型的指针可以进行减法运算,返回它们之间的距离,即相隔多少个数据单位。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。同类型指针相减的结果是一个 ptrdiff_t 类型的数据,ptrdiff_t 是一个带符号的整数,格式输出中对应的格式占位符是 %td。
以下是一个示例,演示了如何使用同类型指针相减来计算它们之间的距离:
#include <stdio.h>
int main()
{
// 创建一个包含 5 个整数的数组
int nums[] = {10, 20, 30, 40, 50};
// 创建指针并指向数组第一个元素的地址
int *ptr1 = &nums[0];
// 创建指针并指向数组第四个元素的地址
int *ptr2 = &nums[3];
// 打印查看原始内容
printf("ptr1地址:%p\n", ptr1);
printf("ptr2地址:%p\n", ptr2);
// 计算两个指针之间的距离
// ptr2 - ptr1 应该等于 3,因为 ptr2 指向第四个元素,ptr1 指向第一个元素
printf("ptr2 - ptr1 = %td \n", ptr2 - ptr1); // 输出 3
// ptr1 - ptr2 应该等于 -3,因为 ptr1 指向第一个元素,ptr2 指向第四个元素
printf("ptr1 - ptr2 = %td \n", ptr1 - ptr2); // 输出 -3
// 再连续创建两个变量
double d1 = 1.0;
double d2 = 2.0;
// 创建指针并分别指向 d1 和 d2 的地址
double *p1 = &d1;
double *p2 = &d2;
printf("p1地址:%p\n", p1);
printf("p2地址:%p\n", p2);
// 计算两个指针之间的距离
// p1 - p2 应该等于 1,因为 p1 指向 d1,p2 指向 d2,d1 和 d2 在内存中是相邻的
printf("p1 - p2 = %td \n", p1 - p2); // 输出 1
// p2 - p1 应该等于 -1,因为 p2 指向 d2,p1 指向 d1,d1 和 d2 在内存中是相邻的
printf("p2 - p1 = %td \n", p2 - p1); // 输出 -1
return 0;
}
输出结果如下所示:
5 指针的比较运算
指针之间可以进行比较运算,如 ==、!=、<、<=、>、>=。这些运算符比较的是指针所指向的内存地址的大小,返回值是 int 类型的整数,1 表示 true,0 表示 false。
以下是一个示例,演示了如何使用指针的比较运算:
#include <stdio.h>
int main()
{
// 创建一个包含 5 个整数的数组
int nums[] = {10, 20, 30, 40, 50};
double n = 1.0;
// 创建指针并指向数组第一个元素的地址
int *ptr1 = &nums[0];
// 创建指针并指向数组第四个元素的地址
int *ptr2 = &nums[3];
// 创建指针也指向数组第一个元素的地址
int *ptr3 = &nums[0];
// 创建指针指向变量 n 的地址
double *ptr4 = &n;
// 输出指针指向的地址
printf("ptr1=%p\n", (void *)ptr1);
printf("ptr2=%p\n", (void *)ptr2);
printf("ptr3=%p\n", (void *)ptr3);
printf("ptr4=%p\n\n", (void *)ptr4);
// 进行比较
// 比较 ptr1 和 ptr2 的地址
printf("ptr1 > ptr2: %d \n", ptr1 > ptr2); // 比较 ptr1 是否大于 ptr2
printf("ptr1 < ptr2: %d \n", ptr1 < ptr2); // 比较 ptr1 是否小于 ptr2
// 比较 ptr1 和 ptr3 的地址
printf("ptr1 == ptr3: %d \n", ptr1 == ptr3); // 比较 ptr1 是否等于 ptr3
// 比较不同类型的指针(ptr4 和 ptr1)
// 注意:不同类型的指针进行比较会引发编译器警告
printf("ptr4 > ptr1: %d \n", ptr4 > ptr1); // 比较 ptr4 是否大于 ptr1
return 0;
}
注意,不同类型的指针进行比较会引发编译器警告。如下所示:
输出结果如下所示:
6 测试题
1. 请写出下面程序的运行结果(64 位操作系统)。
int num = 250;
int *p = #
printf("%zu \n", sizeof p);
printf("%zu \n", sizeof *p);
【答案】
8
4
【解析】
sizeof p 是计算指针本身的长度,指针存储的是地址,在 64 位操作系统下,地址占 8 个字节。
sizeof *p 是计算指针指向的数据的长度,指针 p 指向 int 类型变量 num,int 类型长度是 4 个字节。
2. 请写出下面程序的运行结果。
int arr[] = {10,20,30,40,50};
int *p = arr;
printf("%d", *(p+1) + *(p+2));
【答案】
50
【解析】
指针 p 指向数组 arr 的首元素地址
p+1 得到数组 arr 第二个元素的地址,*(p+1) 得到第二个元素的值 20。
p+2 得到数组 arr 第三个元素的地址,*(p+2) 得到第三个元素的值 30。