《C语言程序设计现代方法》note-8 指针和数组的关系

助记提要

  1. 指针支持的算术运算及其含义;
  2. *++运算符结合的4种情况和含义;
  3. 数组名作为指针、指针做为数组名;
  4. 数组做为实参时的特点;
  5. 指向数组的指针的特性;
  6. 如何处理二维数组的行和列;
  7. 多维数组名做指针时的类型和特性;
  8. 什么是变量修改类型;

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);。虽然aa[0]指向同一位置,但是aint (*)[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语言程序设计:现代方法》笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值