文章目录
助记提要
- 指针支持的算术运算及其含义;
*
和++
运算符结合的4种情况和含义;- 数组名作为指针、指针做为数组名;
- 数组做为实参时的特点;
- 指向数组的指针的特性;
- 如何处理二维数组的行和列;
- 多维数组名做指针时的类型和特性;
- 什么是变量修改类型;
12章 指针和数组
12.1 指针的算术运算
指针可以指向数组元素。&a[i]
是指向数组a中元素i的指针。
int a[10], *p;
// 指向数组元素的指针
p = &a[2];
// 给数组元素赋值
*p = 4
指向数组元素的指针变量可以进行指针算术运算。
- 指针加上整数
得到新的指向后面元素的指针。
若p指向a[i]
,则p+j
指向a[i+j]
。 - 指针减去整数
得到新的指向前面元素的指针。
若p指向a[i]
,则p-j
指向a[i-j]
。 - 指针相减
得到指针的距离(用数组元素的个数衡量)。
若p指向a[i]
,q指向a[j]
,则p-q
等于i-j
。
指向数组元素的指针之间还可以进行比较,相当于把对应的数组元素的下标进行比较。
指针可以指向由复合字面量创建的数组:
int *p = (int []){3, 4, 0, 1, 3};
12.2 指针用于数组处理
指针变量重复自增,可以遍历数组中的元素。
// 对数组求和
int a[10], sum, *p;
...
sum = 0;
for (p = &a[0]; p < &a[10]; p++)
sum += *p;
元素a[10]
不存在,但是对它使用取地址运算符是合法的。之后也不会用到它的值。
结合使用*
和++
运算符
*
和++
两种运算符的结合方式有四种:
结合方式 | 含义 |
---|---|
*p++ 或*(p++) | 表达式的值是*p ,之后p 自增 |
(*p)++ | 表达式的值是*p ,之后*p 自增 |
*++p 或*(++p) | 先自增p ,然后计算表达式的值是*p |
++*p 或++(*p) | 先自增*p ,然后计算表达式的值是*p |
*
的优先级低于++
。
*
和--
运算符结合的情况和上面类似。
12.3 数组名作为指针
数组名可以用作指针
数组的名字可以作为指向数组的第一个元素的指针。
int a[10];
// 等价于 a[0] = 7;
*a = 7;
// 等价于 a[1] = 7;
*(a+1) = 12;
通常a+i
等价于&a[i]
,*(a+i)
等价于a[i]
。
*(a + i)
等价于a[i]
,而*(a + i)
和*(i + a)
是一样的,*(i + a)
等价于i[a]
。因此i[a]
和a[i]
的结果是一致的。但是不要这么写。
把数组名作为指针,可以简化指针和数组的操作:
// 改写数组求和
for (p = a; p < a + 10; p++)
sum += *p
注意 数组名可以用作指针,但是不可以给数组名赋新值。
// 错误操作
while (*a != 0)
a++;
// 正确处理
p = a;
while (*p != 0)
p++
指针也可以当做数组名
数组名可以作为指针,进行指针的算术运算。
指针也可以看做数组名,进行取下标操作。
编译器会把p[i]
看作*(p+i)
。
数组型实参
数组名在传递给函数时,会被转换为指针。
这个特性会有以下重要结果:
- 由于数组实际参数传递给函数时,没有对本身进行复制,所以做为实际参数的数组是肯呢个被更改的;
- 函数传递数组的时间和数组大小无关;
- 数组型的形式参数
a[]
可以被声明为指针*a
,编译器把这两类形参声明看做完全一样的。对数组a可进行的运算(算术运算和取下标)都是一样的。 - 利用指针,可以给接收数组的函数传递数组的片段。
注意 声明为数组和声明为指针一样的情况仅限于形式参数。
因为变量在声明为数组时,会预留数组元素的空间;而声明为指针时,只会为指针变量分配空间。把指针变量做为数组使用会导致未知的影响。
12.4 指针和多维数组
处理每个元素
二维数组是按行主序存储的,因此可以通过自增指向元素的指针来遍历每一个元素。
有的编译器会进行越界检查,遍历完第一行后,指向元素的指针继续自增会报错。
处理行
指针指向二维数组的第i行的第一个元素:
// 指向i行的首元素
p = &a[i][0];
// 等价表达
p = a[i];
指向某行的首元素后,自增指针即可遍历该行所有元素。
处理列
由于数组不是按列存储的,按列处理比较难。
先定义一个指向长度为数组列数的整形数组的指针(*p)[10]
。
注意 必须使用圆括号,因为*p[10]
会被当做指针数组,而不是指向数组的指针。
指向元素的指针,每次自增,会指向下一个元素;指向数组的指针,每次自增会指向下一个数组。
int main(){
int int a[5][10] = {};
int (*p)[10];
p = a;
// 元素之间的距离,指向元素的指针自增的距离
printf("%d\n", &a[0][0]);
printf("%d\n", &a[0][1]);
// 指向数组的指针的自增的距离
printf("%d\n", p);
printf("%d\n", p+1);
return 0;
}
6421824
6421828
6422024
6422064
指向整型元素的指针自增,地址增加值为4。而指向长度为10的整型数组的指针,每次自增,所表示的地址增加的值为40,即跳过了这个数组。
按列遍历数组的方式:
int a[5][10], (*p)[10], i;
...
for (p = &a[0]; p < &a[5]; p++)
// *p表示某行,然后选取该行下标为i的元素
(*p)[i] = 0;
多维数组名做指针
声明二维数组int a[5][10];
。
作为指针时,C语言认为二维数组名a
不是指向元素a[0][0]
的指针,而是指向行a[0]
的指针。
a的类型是int (*)[10]
,指向长度为10的整型数组的指针。
测试二维数组名做指针的等价情况:
int main() {
int a[5][10] = {};
printf("a %d\n", a);
printf("&a %d\n", &a);
printf("a[0] %d\n", a[0]);
printf("&a[0] %d\n", &a[0]);
printf("&a[0]+1 %d\n", &a[0] + 1);
printf("a+1 %d\n", a+1);
printf("&a[1] %d\n", &a[1]);
printf("&a[0][0] %d\n", &a[0][0]);
printf("&a[0][1] %d\n", &a[0][1]);
}
结果如下:
a 6421824
&a 6421824
a[0] 6421824
&a[0] 6421824
a+1 6421864
&a[0]+1 6421864
&a[1] 6421864
&a[0][0] 6421824
&a[0][1] 6421828
对于接收一维数组的函数int find_largest(int [], int n)
,用于计算出一维数组中的最大值。第一个参数为一维整型数组(即指针),第二个参数n为数组的元素数。
可以让这个函数把二维数组看做一维数组进行计算:
find_largest(a[0], 5*10);
注意 不能写为find_largest(a, 5*10);
。虽然a
和a[0]
指向同一位置,但是a
是int (*)[10]
类型,a[0]
类型为int *
。函数需要的实参类型是int *
。
12.5 指针和变长数组
指针可以指向变长数组中的元素。
变长数组如果是多维的,指针类型取决于除第一维外的每一维的长度。
void f(int m, int n)
{
int a[m][n], (*p)[n];
p = a;
...
}
p的类型依赖于n,把这样的情况称为变量修改类型。
注意 下面的代码可以通过编译,但只有在m=n
时才是正确的。当m不等于n时,会出现未定义的行为。
int a[m][n], (*p)[m];
p = a;
更多相关内容参考: 《C语言程序设计:现代方法》笔记