指针与数组

前言

数组,指针,数组指针,指针数组,函数指针,函数指针数组,指向函数指针数组的指针 … 这些绕口的名字让我们对这里的知识看上去就望而生畏,但经过仔细的分析,研究就会慢慢熟悉了解他们。

一.指针数组和数组指针

首先举个例子:
A).int p1[10]
B).int (
p2)[10]
那这两个哪个是指针数组哪个是数组指针呢?
答案是A为指针数组,B为数组指针.为什么呢?
首先我们需要明白一个优先级的问题。
A)中,[]的优先级是高于的,所以p1先与[]结合,构成一个数组的定义,数组名为p1,int修饰的是数组的内容,即数组的每个元素。所以A)是一个指针数组,其包含10个指向int类型数据的指针。
B)中()的优先级是最高的,所以*和p2构成一个指针的定义,指针变量名为p2,int修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。所以p2是一个指针,他指向一个包含10个int类型数据的数组,即数组指针.

指针数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
#include<stdlib.h>

int main()
{
char *arr[3] = { "hello", "my", "friend" };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%s\n", arr[i]);
}
system("pause");
return 0;
}

如上便是一个简单的指针数组。arr是一个存放了3个指针变量的数组,指针分别指向三个字符串首元素的地址

数组指针的基本用法(数组指针为一个指针,指向一个数组,在32位操作系统下,他的大小永远为4个字节.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<stdlib.h>
int main()
{
int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 4, 5, 6, 7, 8 }, { 7, 8, 9, 7, 8 } };
int(*p)[5] = &arr;
int i = 0;
int j = 0;
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
printf("%d", *(*(p+i)+j));
}
printf("\n");
}
system("pause");
return 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
#include<stdio.h>
#include<stdlib.h>

void print(int(*p)[5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
printf("%d", *(*(p+i)+j));
}
printf("\n");
}
}

int main()
{
int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 4, 5, 6, 7, 8 }, { 7, 8, 9, 7, 8 } };
print(arr, 3, 5);
system("pause");
return 0;
}

((p+i)+j)解析:(p+i)得到第i行的首元素的地址,解引用后为第i行首元素的地址,*(p+i)+j 为第i行第j列元素的地址 ,解引用得到第i行j列处的元素

二.指针和数组的定义、声明

我们要明确知道,数组就是数组,指针就是指针,他们是完全不同的两码事。

Ⅰ.定义为数组,声明为指针

在一个源文件中将arr定义为一个数组,在另一个源文件中将arr声明为指针

1
char arr[] = "abcdef";

1
2
3
4
5
6
7
8
9
extern char *arr;

int main()
{
printf("%s", arr); //①
printf("%s",&arr); //②
system("pause");
return 0;
}

我们发现情况①时程序会崩溃,而情况二时可以正常输出.什么原因呢
首先我们要看看数组定义时内存的分配情况

这也就是为什么 extern char arr[]和extern char arr[7]等同的原因.因为在声明时,内存不会在为arr开辟空间,声明只是让编译器知道在一个源文件中arr已经被定义为一个数组,而arr则是这个数组的名字,即数组首元素的地址,有了这个地址,便可以随意拿出数组中的任何一个元素
但是,当声明为char *arr时,在运行时,程序会崩溃,原因如下

Ⅱ.定义为指针,声明为数组

1
char *p = "abcdef";
1
2
3
4
5
6
7
8
extern char p[];

int main()
{
printf("%s", p);
system("pause");
return 0;
}

为什么会输出一起奇怪的东西呢?请看下图:

三.函数指针

首先我们应该知道,函数也是有地址的,例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}

int main()
{
int a = 2;
int b = 3;
int ret = Add(a, b);
printf("%d\n", ret);
printf("address = %p\n", &Add);
system("pause");
return 0;
}

图片无法正常显示
那么如何保存函数的地址呢,这就要用到函数指针
函数指针在定义的时候需要注意的是,必须指明函数的参数个数及类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}

int main()
{
int (*p)(int, int) = Add; //*和p先结合 构成一个指针 p 该指针指向一个类型为 int,有两个类型分别为int类型的参数的函数的地址
printf("%p \n", p);
printf("%p \n", Add);
printf("%d\n", (*p)(2, 3));
printf("%d \n", Add(2, 3));
system("pause");
return 0;
}

此处便为函数指针的用法

声明:1.和p先结合 构成一个指针 p 该指针指向一个类型为 int,有两个类型分别为int类型的参数的函数的地址
2.函数名被编译之后其实就是一个地址,所以给函数指针赋值时,&Add和Add没有什么本质的区别
3.printf(“%d\n”, (
p)(2,3)); (*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
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}

int Sub(int x, int y)
{
return x - y;
}

int Mul(int x, int y)
{
return x * y;
}

int Div(int x, int y)
{
return x / y;
}

int main()
{
int (*arr[4])(int, int) = { Add, Sub, Mul, Div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", (*(*(arr + i)))(12, 4));
}
system("pause");
return 0;
}

int(arr[4])(int,int) 首先arr先和[]结合,成为一个数组,而int()()则是这个数组的类型,固为函数指针数组.
此函数指针数组中有四个元素,类型都为函数指针,分别存放了Add,Sub,Mul,Div地址
附录:简单计算器的实现
首先是一种最常规的实现方式:

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
 #include<stdio.h>
#include<stdlib.h>

void menu()
{
printf("***************************\n");
printf("*** 1.Add 2.Sub ***\n");
printf("*** 3.Mul 4.Div ***\n");
printf("****** 0.eixt ******\n");
printf("***************************\n");
}

int Add(int x, int y)
{
return x + y;
}

int Sub(int x, int y)
{
return x - y;
}

int Mul(int x, int y)
{
return x * y;
}

int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int a = 0;
int b = 0;
int ret = 0;
do
{
menu();
printf("请选择算术运算:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两位操作数:");
scanf("%d%d", &a, &b);
ret = Add(a, b);
printf("%d\n", ret);
break;
case 2:
printf("请输入两位操作数:");
scanf("%d%d", &a, &b);
ret = Sub(a, b);
printf("%d\n", ret);
break;
case 3:
printf("请输入两位操作数:");
scanf("%d%d", &a, &b);
ret = Mul(a, b);
printf("%d\n", ret);
break;
case 4:
printf("请输入两位操作数:");
scanf("%d%d", &a, &b);
ret = Div(a, b);
printf("%d\n", ret);
break;
case 0:
printf("退出\n");
break;
default:
printf("错误选项,请重新选择\n");
break;
}
} while (input);

system("pause");
return 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
 #include<stdio.h>
#include<stdlib.h>

void menu()
{
printf("***************************\n");
printf("*** 1.Add 2.Sub ***\n");
printf("*** 3.Mul 4.Div ***\n");
printf("****** 0.eixt ******\n");
printf("***************************\n");
}

int Add(int x, int y)
{
return x + y;
}

int Sub(int x, int y)
{
return x - y;
}

int Mul(int x, int y)
{
return x * y;
}

int Div(int x, int y)
{
return x / y;
}

void calc(int(*p)(int, int))
{
int a = 0;
int b = 0;
int ret;
printf("请输入两个操作数\n");
scanf("%d%d", &a, &b);
ret = p(a, b);
printf("ret = %d\n", ret);
}

int main()
{
int input = 0;
int a = 0;
int b = 0;
int ret = 0;
do
{
menu();
printf("请选择算术运算:\n");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出\n");
break;
default:
printf("错误选项,请重新选择\n");
break;
}
} while (input);

system("pause");
return 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
#include<stdio.h>
#include<stdlib.h>

void menu()
{
printf("***************************\n");
printf("*** 1.Add 2.Sub ***\n");
printf("*** 3.Mul 4.Div ***\n");
printf("****** 0.eixt ******\n");
printf("***************************\n");
}

int Add(int x, int y)
{
return x + y;
}

int Sub(int x, int y)
{
return x - y;
}

int Mul(int x, int y)
{
return x * y;
}

int Div(int x, int y)
{
return x / y;
}



int main()
{
int input = 0;
int a = 0;
int b = 0;
int ret = 0;
//转移表
int(*p[5])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
printf("请选择算术运算:\n");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d%d", &a, &b);
ret = p[input](a, b);
printf("ret = %d\n", ret);
}
else if(input = 0)
{
printf("退出\n");
break;
}
else
{
printf("选择错误,请重新选择\n");
continue;
}
} while (input);

system("pause");
return 0;
}

这便用转移表(函数指针数组)实现了计算器

五.指向函数指针数组的指针

指向函数指针数组的指针,本质上来说是一个指针,他指向一个数组,数组内的每个元素都是函数指针

1
int(*p[5])(int, int) = { 0, Add, Sub, Mul, Div };

拿此函数指针数组来说,那么指向他的指针(指向函数指针数组的指针)便为

1
int(*(*p1)[5])(int,int);