0%

二维数组复习

一、理论方面

1.存储

  • 存储方式:

    • 内存寻址从大到小, 从高地址开辟一块连续没有被使用的内存给数组。

    • 从分配的连续存储空间中, 地址小的位置开始给每个元素分配空间

    • 从每个元素分配的存储空间中, 地址最大的位置开始存储数据

    • 用数组名指向整个存储空间最小的地址。

      (分配从小地址开始,赋值从大地址开始)

二维数组地址详解+指针数组+数组指针(重要!!!)

如果还没有学习指针相关内容可以先跳过

地址问题详解

先看一下各种情况的输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
int a[2][3]={{1,2,3}{4,5,6}};
int *p[3];
p[0]=&a[0][0];
printf("%d",p); //6487520
printf("%d",&p); //6487520
printf("%d",&p[0]); //6487520
printf("%d",*p); //6487552
printf("%d",&a[0][0]); //6487552
printf("%d",**p); //1
printf("%d",*p[0]); //1
return 0;
}
  • 当输出为p和&p时,我们可以发现输出的都是一个地址且二者输出相同,都等于p[0]的地址。这就说明数组指针与数组的相关规则相同——其数组名代表了其第一个元素的地址
  • 当输出为*p和&a[0] [0]时,输出的值为地址且二者相同,&a[0] [0]我们不难理解,但对于*p,上一点已经说到,p代表了p这个数组指针第一个元素的地址,如果我们对这个地址进行解引用(关于解引用*运算符在指针相关复习 | 小董的BLOG 中有详细讲到),返回值也就是这个首元素的地址内所存储的内容,而我们知道,指针变量本身存储的内容就是地址,所以对p进行解引用的结果,就是p的首元素的地址(6487520)所存储的“a[0] [0]的地址(6487552)“
  • 如果你能很好的理解解引用运算符的用法,相信你已经能理解输出为**p和` p[0]的结果了;p即再对 *p`进行一次解引用,也就是对”a[0] [0]的地址(6487552)“解引用,返回值为a[0] [0]的地址所存储的内容,即1。(`p *p[0]`等价)

如果你对以上内容已经比较理解,那可以思考一下以下内容(以下内容是我个人的理解,不能保证正确!)

1
2
3
4
5
6
7
int main()
{
int a[2][3]={{1,2,3}{4,5,6}};
int *p[3];
p[0]=a;
return 0;
}

根据对一维数组的学习,可以推断出二维数组名a即代表第一个元素的地址,把这个元素地址放进p内,完全没问题,全然大丈夫!但是编译器就是报错了捏😋。在网上找了很多资料之后,大概能想明白是怎么回事,但是又并不是那么明白:二维数组名a代表的是a[2][3]中a[0]的地址,而在C语言中,这个a[0]又被看作a[3]={1,2,3};那么这里的a是不是就可以看作是a[0] [0]的地址呢,我们接着尝试:

1
2
3
4
printf("%d",a);//6487552
printf("%d",*a);//6487552
printf("%d",**a);//1
printf("%d",a[0]);//6487552

我超,什么意思捏,a和*a输出值一样???为了解释这个现象,我们不妨来读一下刚刚的报错:

[Error] cannot convert 'int [2][3]' to 'int*' in assignment

这里我的理解是:二维数组a的类型为int [2][3],而a[0]的类型为int[3],通俗一点,就是a代表的是第一行数组的地址,而a[0]代表的是a[0] [0]的地址,二者虽然在地址值上相同,但表达的意思是不同的,这也就是为什么a无法存入p中的原因,因为类型是冲突的。所以这里正常的赋值应为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
int a[2][3]={{1,2,3}{4,5,6}};
int *p[3];
p[0]=*a;
printf("%d",**p); 或 printf("%d",*p[0]);
return 0;
}
//还有一个办法是:
int main()
{
int a[2][3]={{1,2,3}{4,5,6}};
int (*p)[3];
p=a; //此时p单纯的就是一个指针变量
printf("%d",**p); 或 printf("%d",*p[0]);
return 0;
}

说真的感觉不太好理解,所以可以记下一个规律:

对一个代表一行元素的地址解引用,得到的是该行首元素的地址,再对这个地址解引用得到首元素

(所以此处a[0]等同于*a)

指针数组与数组指针

  • 指针数组

    1
    int *p[3];

    在C语言中,[]的优先级高于*因此此式正确理解为:int *(p[3]),说明p为一个数组,且该数组用于存放地址,此时p是一个数组,但其元素内存储的为某个地址

  • 数组指针

    1
    int (*p)[3];

    先表明了p是一个指针变量,再说明了p指向的对象是一个int[3]型的数组,且p本身保存的是指向数组的首地址,此时p是一个指针

    • 对数组指针的调用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      int main()
      {
      int a[][3]={{1,2,3},{4,5,0}},(*p)[3],i;
      p=a;
      printf("%d",p);//6487552
      printf("%d",*p);//6487552
      printf("%d",p[0]);//6487552
      printf("%d",**p);//1
      printf("%d",*p[0]);//1
      printf("%d",p[0][0]);//1
      return 0;
      }

      对于数组指针的使用有以下几点:(都假设存放的是多维数组)

      • 指针名代表二维数组第一行元素的地址
      • 对指针名解引用一次代表二维数组第一行数组中的第一个元素的地址(等同于p[0])
      • 当数组指针下标精准到某一个元素时,例如p[0][0],此时p[0][0]就代表数组元素的值,而不是地址

2.初始化

​ 一维:可以理解为行数,可省略。

​ 二维:~~,不可省略(会报错)。

  • 分行赋值

    1
    2
    3
    4
    5
    6
    int a[3][4]=
    {
    {1,2,,}
    {,,1,}
    {,,,1}
    }
  • 指定元素的初始化

    1
    2
    int a[2][3]={[1][2]=10};
    int a[2][3]={[1]={1,2,3}}

2.二维数组与函数

​ 形参是基本类型则不会影响实参,如果是数组类型则会影响实参。

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
#include<stdio.h>
void change_1(char ch)
{
ch = 'n';
}
void change_2(char ch[])
{
ch[0] = 'n';
}
void change_3(char ch[][3])
{
ch[0][0] = 'n';
}
int main()
{
char cs[2][3] =
{
{'a', 'b', 'c'},
{'d', 'e', 'f'}
}
printf("cs[0][0] = %c\n", cs[0][0]); // 打印a
change(cs); printf("cs[0][0] = %c\n", cs[0][0]); // 基本类型形参不影响实参
change_2(cs); printf("cs[0][0] = %c\n", cs[0][0]); // 数组类型形参影响实参
change_3(cs); printf("cs[0][0] = %c\n", cs[0][0]); // 数组类型形参影响实参
return 0;
}

二、实操

通过键盘录入 w,s,a,d控制小人向不同方向移动,其中w代表向上移动,s代表向 下移动,a代表向左移动,d 代表向右移动,当小人移动到出口位置,玩家胜利。

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
#include <stdio.h>
char map[6][7]={ '#','#','#','#','#','#','\0',
'#','0','\0','#', '\0','\0','\0',
'#','\0','#','#','\0','#','\0',
'#','\0','\0','#','\0','#','\0',
'#','#','\0','\0','\0','#','\0',
'#','#','#','#','#','#','\0' };
void mapdraw();
void move();
void change(char loc[6][7]);
int x=1;
int y=1; //当前坐标
int last_x=1;
int last_y=1; //前一次坐标
int main(void)
{
mapdraw();
while(1)
{
move();
change(map);
mapdraw();
if(map[1][6]=='0') break;
}
printf("YOU WIN!");
return 0;
}

void mapdraw() //打印二维数组
{
for(int i=0;i<7;i++)
{
printf("%c",map[0][i]);
if(i==6) printf("\n");
}
for(int i=0;i<7;i++)
{
printf("%c",map[1][i]);
if(i==6) printf("\n");
}
for(int i=0;i<7;i++)
{
printf("%c",map[2][i]);
if(i==6) printf("\n");
}
for(int i=0;i<7;i++)
{
printf("%c",map[3][i]);
if(i==6) printf("\n");
}
for(int i=0;i<7;i++)
{
printf("%c",map[4][i]);
if(i==6) printf("\n");
}
for(int i=0;i<7;i++)
{
printf("%c",map[5][i]);
if(i==6) printf("\n");
}
for(int i=0;i<7;i++)
{
printf("%c",map[6][i]);
if(i==6) printf("\n");
}
printf("\n");
last_x=x;
last_y=y;
}

void change(char loc[6][7])
{
loc[last_x][last_y]='\0';
loc[x][y]='0';
}

void move()
{

char dir;
scanf("%c",&dir);
switch(dir)
{
case 'w':x=x-1;if(map[x][y]=='#') x=x+1;break;
case 's':x=x+1;if(map[x][y]=='#') x=x-1;break;
case 'a':y=y-1;if(map[x][y]=='#') y=y+1;break;
case 'd':y=y+1;if(map[x][y]=='#') y=y-1;break;
defalut :break;
}
fflush(stdin); //清除缓冲区
}

scanf()停止接受输入及scanf(“%c”,&c)吃掉回车或者空格字符的问题

例如,当输入‘w’+‘回车’时,回车会作为’\n’进入缓冲区等待下一次使用,也就是说main中的循环会执行两次。

解决方法

函数名: fflush
功 能: 清除一个流
用 法: int fflush(FILE *stream);

fflush(stdin); 清除缓冲区内容。

一些记录

1.整个数组指针

1
2
3
4
5
6
7
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1)); //2 5
return 0;
}

&a为整个数组的地址,类型为int(*)[5],需将其强制转化才可以存进prt指针。因此&a+1为整个数组的指针+1。