如果指针变量指向数组,则程序可以对指针执行算术以访问数组的任何元素。在大多数情况下,我们建议不要使用指针算术来访问数组元素:这样做很容易出错,而且更难以调试。然而,有时连续递增指针来迭代元素数组可能会很方便。
当递增时,指针指向_它所指向的类型的_下一个存储位置。例如,递增整数指针 ( int *
) 使其指向下一个int
存储地址(超出当前值 4 个字节的地址),递增字符指针使其指向下一个 char
存储地址(超出当前值 1 个字节的地址)。
在下面的示例程序中,我们演示了如何使用指针算术来操作数组。首先声明类型与数组元素类型匹配的指针变量:
#define N 10
#define M 20
int main(void) {
// array declarations:
char letters[N];
int numbers[N], i, j;
int matrix[N][M];
// declare pointer variables that will access int or char array elements
// using pointer arithmetic (the pointer type must match array element type)
char *cptr = NULL;
int *iptr = NULL;
...
接下来,将指针变量初始化为它们将迭代的数组的基地址:
// make the pointer point to the first element in the array
cptr = &(letters[0]); // &(letters[0]) is the address of element 0
iptr = numbers; // the address of element 0 (numbers is &(numbers[0]))
然后,使用指针取消引用,我们的程序可以访问数组的元素。在这里,我们取消引用以将值分配给数组元素,然后将指针变量递增 1 以将其前进以指向下一个元素:
// initialized letters and numbers arrays through pointer variables
for (i = 0; i < N; i++) {
// dereference each pointer and update the element it currently points to
*cptr = 'a' + i;
*iptr = i * 3;
// use pointer arithmetic to set each pointer to point to the next element
cptr++; // cptr points to the next char address (next element of letters)
iptr++; // iptr points to the next int address (next element of numbers)
}
请注意,在此示例中,指针值在循环内递增。因此,增加它们的值使它们指向数组中的下一个元素。此模式有效地遍历数组的每个元素,其方式与每次迭代访问cptr[i]
或 iptr[i]
的方式相同。
指针算术的语义和底层算术函数
指针算术的语义与类型无关:通过N
, ( ptr = ptr + N
) 更改任何类型的指针值都会使指针指向N
超出其当前值的存储位置(或使其指向N
超出其指向的当前元素的元素)。因此,递增任何类型的指针都会使其指向它所指向的类型的下一个内存位置。
但是,编译器为指针算术表达式生成的实际算术函数会根据指针变量的类型(取决于系统用于存储其指向的类型的字节数)而有所不同。例如,递增char
指针将使其值加一,因为下一个有效char
地址距当前位置一个字节。递增int
指针将使其值增加 4,因为下一个有效整数地址距离当前位置有 4 个字节。
程序员可以简单地编写ptr++
使指针指向下一个元素值。编译器生成代码来为其指向的相应类型添加适当数量的字节。加法有效地将其值设置为该类型内存中的下一个有效地址。
您可以看到上面的代码如何通过打印出数组元素的值来修改数组元素(我们首先使用数组索引进行展示,然后使用指针算术来访问每个数组元素的值):
printf("\n array values using indexing to access: \n");
// see what the code above did:
for (i = 0; i < N; i++) {
printf("letters[%d] = %c, numbers[%d] = %d\n",
i, letters[i], i, numbers[i]);
}
// we could also use pointer arith to print these out:
printf("\n array values using pointer arith to access: \n");
// first: initialize pointers to base address of arrays:
cptr = letters; // letters == &letters[0]
iptr = numbers;
for (i = 0; i < N; i++) {
// dereference pointers to access array element values
printf("letters[%d] = %c, numbers[%d] = %d\n",
i, *cptr, i, *iptr);
// increment pointers to point to the next element
cptr++;
iptr++;
}
输出如下所示:
array values using indexing to access:
letters[0] = a, numbers[0] = 0
letters[1] = b, numbers[1] = 3
letters[2] = c, numbers[2] = 6
letters[3] = d, numbers[3] = 9
letters[4] = e, numbers[4] = 12
letters[5] = f, numbers[5] = 15
letters[6] = g, numbers[6] = 18
letters[7] = h, numbers[7] = 21
letters[8] = i, numbers[8] = 24
letters[9] = j, numbers[9] = 27
array values using pointer arith to access:
letters[0] = a, numbers[0] = 0
letters[1] = b, numbers[1] = 3
letters[2] = c, numbers[2] = 6
letters[3] = d, numbers[3] = 9
letters[4] = e, numbers[4] = 12
letters[5] = f, numbers[5] = 15
letters[6] = g, numbers[6] = 18
letters[7] = h, numbers[7] = 21
letters[8] = i, numbers[8] = 24
letters[9] = j, numbers[9] = 27
指针算术可用于迭代任何连续的内存块。下面是一个使用指针算术来初始化静态声明的二维数组的示例:
// sets matrix to:
// row 0: 0, 1, 2, ..., 99
// row 1: 100, 110, 120, ..., 199
// ...
iptr = &(matrix[0][0]);
for (i = 0; i < N*M; i++) {
*iptr = i;
iptr++;
}
// see what the code above did:
printf("\n 2D array values inited using pointer arith: \n");
for (i = 0; i < N; i++) {
for (j = 0; j < M; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
输出将如下所示:
2D array values initialized using pointer arith:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
指针算术可以以任何模式访问连续的内存位置,在连续的内存块中的任何位置开始和结束。例如,在初始化指向数组元素地址的指针后,其值可以更改多个。例如:
iptr = &numbers[2];
*iptr = -13;
iptr += 4;
*iptr = 9999;
执行上述代码后,打印numbers
数组的值将如下所示(请注意,索引 2 和索引 6 处的值已更改):
numbers[0] = 0
numbers[1] = 3
numbers[2] = -13
numbers[3] = 9
numbers[4] = 12
numbers[5] = 15
numbers[6] = 9999
numbers[7] = 21
numbers[8] = 24
numbers[9] = 27
指针算术也适用于动态分配的数组。但是,程序员必须小心使用动态分配的多维数组。例如,如果程序使用多个malloc
调用来动态分配 2D 数组的各个行(方法 2,数组的数组),则必须重置指针以指向每行的起始元素的地址。重置指针是必要的,因为只有行内的元素位于连续的内存地址中。另一方面,如果 2D 数组被分配为 malloc
总行数乘以列空(方法 1),则所有行都位于连续内存中(就像上面示例中静态声明的 2D 数组一样)。在后一种情况下,只需将指针初始化为指向基地址,然后指针运算将正确访问二维数组中的任何元素。