12 指针和数组

12.1 指针的算术运算

说明:指针不仅可以指向普通变量,还可以指向数组元素。

1
2
3
int a[10], *p;
p = &a[0];
*p = 5;//a[0]值变为5

3种格式:

  1. 指针加上整数
  2. 指针减去整数
  3. 两个指针相减

    12.1.1指针加上整数

    说明:指针p加上整数j产生指向p元素后j个位置的指针。

1
2
p = &a[2];
q = p + 3;//p指向了数组下标为5的位置

12.1.2 指针减去整数

说明:如果p指向数组元素a[i],那么p-j指向a[i-j]

1
2
p = &a[8];
q = p-3;//指向数组a下标为5的位置

12.1.3 指针相减

说明:当两个指针相减时,结果为指针之间的距离。
用途:用来计算数组中元素的个数。如果p指向a[i]q指向a[j],那么p-q就等于i-j
限制:

  • 只有在p指向数组元素时,指针p上的算数运算才会获得有意义的结果
  • 只有在两个指针指向同一个数组时,指针相减才有意义
1
2
3
4
5
p = &a[5];
q = &a[1];

i = p - q;//4
i = q - p;//-4

12.1.4 指针比较

说明:可以用关系运算符(< <= > >=)和判等运算符(==!=)。
限制:只有在两个指针指向同一个数组时,用关系运算符进行的指针比较才有意义。

1
2
3
4
5
p = &a[5];
q = &a[1];

p <= q;//0
q > p;//1

12.2 指针用于数组处理

说明:指针的算术运算允许通过对指针变量进行重复自增来访问数组的元素。
注意:数组a的下标是0到N-1,但&a[N]是合法的,只要不尝试对该地址存储空间进行读取或写入,就是安全的。
性能:一些编译器依赖下标而不是指针产生的循环代码性会更好。

1
2
3
4
5
6
7
#define N 10
int a[N], sum, *p;
sum = 0;
//计算数组元素的和
for(p = &a[0]; p < &a[N]; p++){
sum += *p;
}

*运算符和++运算符的组合

表达式 含义
*p++*(p++) 表达式的值是*p,然后p自增1
(*p)++ 表达式的值为*p,然后*p自增1
*++p 先自增p,再取自增后的指针对应的值
++*p++(*p) 表达式的值是*p自增后的值
1
2
3
4
p = &a[0];
while(p < &a[N]){
sum += *p++;
}

程序:栈

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
#define STACK_SIZE 100
#define TRUE 1
#define FALSE 0

typedef int Bool;

int contents[STACK_SIZE];
int *top = contents[0];

/**
* 清空栈
* 将top指针指向数组的第一项
*/

void make_empty(void){
top = &contents[0];
}

/**
* 检查栈是否为空
* @return true:空;false:非空
*/

Bool is_empty(void){
return *top == contents[0];
}

/**
* 检查栈是否已满
* @return true:已满;false:未满
*/

Bool is_full(void){
return *top == contents[STACK_SIZE];
}

/**
* 向栈中压入数据
* @param i 要压入的数据项
*/

void push(int i){
if(is_full()){
stace_overflow();
}else{
*top++ = i;
}
}

/**
* 从栈中弹出数据项
* @return 数据项
*/

int pop(void){
if(is_empty()){
stack_underflow();
}else{
return *--top;
}
}

12.3 用数组名作为指针

说明:可以用数组的名字作为指向数组第一个元素的指针。
意义:简化了指针的算术运算,而且使得数组和指针都更加通用。
局限:虽然可以把数组名用作指针,但是不能给数组名赋新的值。试图使数组名指向其它地方是错误的。所以当需要的时候,可以先将数组指针复制给其它变量,然后改变该指针变量。

1
2
3
4
5
6
7
8
9
int a[10];
int sum = 0;
*a = 7;//a[0]=7
*(a+1); = 12;//a[1]=12

//计算数组元素的和
for(p = a; p < a + N; p++){
sum += *p;
}

12.3.1 程序:数列反向(改进版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Reverses a series of numbers (pointer version)
*/

#include <stdio.h>
#define N 10
int main(){
int a[N], *p;

//输入N个数组存储到数组中
printf("Enter %d numbers\n", N);
for(p = a; p < a + N; p++){
scanf("%d", p);
}

//逆向打印所有数组元素
printf("In reverse order:");
for(p = a + N -1; p >= a; p--){
printf(" %d", *p);
}
printf("\n");

return 0;
}
1
2
3
4
$ ./reverse2                             
Enter 10 numbers
1 2 3 4 5 6 7 8 9 0
In reverse order: 0 9 8 7 6 5 4 3 2 1

12.3.2 数组型实际参数(改进版)

说明:数组作为实参传递给函数是是作为指针传递的(传引用),而不会复制数组。

  • 因为没有对数组进行复制,所以数组作为实际参数不会防止原数组被修改
    防改变:为了指明数组形式参数不会改变,可以在它的声明中包含单词const

  • 给函数传递数组所需的时间不依赖于数组的大小

  • 声明形参时,数组型式和指针形式等价,编译器处理这两类声明就好像它们是完全一样的

局限:不适用于普通变量的声明

1
2
int a[10];//会导致编译器为10个整数预留空间
int *a;//会导致编译器为指针变量分配空间
  • 可以给形式参数为数组的函数传递数组的“片段”
1
2
3
4
5
6
7
8
9
10
11
12
int find_largest(int a[], int n){
int i, max;
max = a[0];
for(i = 1; i < n; i++){
if(a[i] > max){
max = a[i];
}
}
return max;
}
int N = 4, b = [2,6,8,1];
int larget = find_largest(b, N);//会把数组b的第一个元素的地址赋给a,数组本身并没有复制

12.3.3 用指针作为数组名

说明:指向数组的指针除了可以通过指针的方式操作数组外,还可以将该指针当作数组来使用。

1
2
3
4
5
6
#define N 100
int a[N], i, sum = 0, *p = a;

for(i = 0; i < N; i++){
sum += p[i];//*(p+i)
}

12.4 指针和多维数组

12.4.1 处理多维数组的元素

说明:可以通过指针递增的方式访问到多维数组的每一个元素,因为c语言始终按照顺序存储多维数组的元素。

数组遍历(数组下标的方式)

1
2
3
4
5
6
7
int row, col;
//将数组元素初始化为0
for(row = 0; row < NUM_ROWS; row++){
for(col = 0; col < NUM_COLS; col++){
a[row][col] = 0;
}
}

数组遍历(指针的方式)

1
2
3
4
int *p;
for(p = &a[0][0]; p <= &a[NUMROWS-1][NUM_COLS-1]; p++){
*p = 0;
}

12.4.2 处理多维数组的行

说明:二维数组第i(从0开始)行第一个元素的地址
a[i] == *(a + i) ==&a[i][0] == &(*(a[i] + 0)) == &*a[i]

1
2
3
4
5
6
int a[NUM_ROWS][NUM_COLS], *p, i;
...
//初始化a的第i行元素为0
for(p = a[i]; p < a[i] + NUM_COLS; p++){
*p = 0;
}

12.4.3 用多维数组名作为指针

指针的指针:int a[10], b[10][10];
a可以看作是int *型的指针,而b用作指针时则是int **型的(指针某个整数的指针的指针)。