本题要求编写程序,将给定 n× n方阵中的每个元素循环向右移 m个位置,即将第0、1、⋯、 n−1列变换为第 n− m、 n− m+1、⋯、 n−1、0、1、⋯、 n− m−1列。
输入格式:
输入第一行给出两个正整数m和n(1≤n≤6)。接下来一共n行,每行n个整数,表示一个n阶的方阵。
输出格式:
按照输入格式输出移动后的方阵:即输出n行,每行n个整数,每个整数后输出一个空格。
输入样例:
2 3
1 2 3
4 5 6
7 8 9
输出样例:
2 3 1
5 6 4
8 9 7
通关代码:
#include <stdio.h>
#define MAXS 6
void Input(int x[][MAXS], int n)
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &x[i][j]);
}
void Moveright(int x[MAXS][MAXS], int n)
{
int i = 0, j = 0;
for (i = 0; i < n; i++)
{
int t = x[i][n-1];
for ( j = n-1; j > 0; j--)
x[i][j] = x[i][j-1];
x[i][j] = t;
}
}
void Output(int x[][MAXS], int n)
{
for (int i = 0; i < n; i++, printf("\n"))
for (int j = 0; j < n; j++)
printf("%d ", *(*(x+i)+j));
}
int main()
{
int m, n;
scanf("%d %d", &m, &n);
int x[MAXS][MAXS];
Input(x, n);
for (int i = 0; i < m; i++)
Moveright(x,n);
int (*p)[MAXS] = x;
Output(p, n);
return 0;
}
零基础解析:
0. 思路分析:
此题思路较为清晰,主要用做例子对二维数组的函数调用进行说明:
- 数组的输入:
void Input(int x[][MAXS], int n)
- 数组的右移:
void Moveright(int x[MAXS][MAXS], int n)
- 数组的输出:
void Output(int x[][MAXS], int n)
在不考虑算力的情况下(因为本身计算量不大),可以每次让数组右移一次,然后循环m
次,当然更好的方法是循环m%n
次,【也可以直接一步到位】不过都不是此处想说的重点,因此不搞那些花里胡哨的。
1. 二维数组的数组名和首地址:
相比于一维数组,二维数组稍微复杂一些:对于一维数组来说,数组名等于首地址,这个规则同样适用,但是需要稍作变化。
通过上一篇博客的内容,我们可以知道的是:数组名其实就是地址,而且是数组的首地址,不过对于二维数组来说,稍微特殊一点的是,首地址既是第一行的首地址,也是第一行第一列的首地址(可能暂时感觉不到什么区别),下边我们通过一段程序进行说明:
#include <stdio.h>
#define M 3
#define N 4
int main()
{
int x[M][N];
int *p1, (*p2)[N];
p1 = *x; //一级指针
p2 = x; //二级指针
printf("x:%d p1:%d p2:%d &x[0]:%d &x[0][0]:%d\n", x, p1, p2, &x[0], &x[0][0]);
return 0;
}
此程序的输出为如下,其中:
x
:表示该二维数组的首地址;p1
:表示该二维数组对应的一级指针;p2
:表示该二维数组对应的二级指针;&x[0]
:表示该二维数组第一行的首地址【第一行为一个一维数组】;&x[0][0]
:表示该二维数组第一行第一列元素的地址。
x:6618592 p1:6618592 p2:6618592 &x[0]:6618592 &x[0][0]:6618592
通过输出结果,不难看出,乱七八糟的东西,全部对应一个值,也就是对应一个地址。如果没有理解的很清楚,可能你现在已经有点晕了,不过这些都不重要,暂时只需要记住的是这些变量的值都相等,但是使用方法有所不同。
2. 各地址之间的区别:
为便于指代,我们对x
、&x[0]
、&x[0][0]
进行命名【名字是我胡乱取的,只是为了便于说明,如果有知道标准的叫法的朋友欢迎指出】
- 数组名:
x
- 二维数组的一级首地址:
&x[0][0]
- 二维数组的二级首地址:
&x[0]
注:以上命名都是我瞎编的,不知道是否存在或者是否正确,目的是为了便于理解,请不要拘泥于名字
之所以需要对此加以区分,是在于虽然这三个变量均对应同一个值,但是在运算的时候存在差异,而理解其中的差异有助于后续内容的了解。在上文中已经进行过说明:当x
表示一维数组名的时候,x+i
已经不在具有数学意义上的加法,实际上表示的是x
数组中的第i
个元素对应的地址,【如果要说数值的话,应该是x+i*sizeof(int)
】。
而我们还知道的是当x
表示二维数组的时候,x+i
表示的是二维数组第i
行的首地址。换句话说,一维数组是按照元素移动的,二维数组是按照行数移动的。通过一个例子,可以更好的理解刚才的内容:
#include <stdio.h>
#define M 3
#define N 4
int main()
{
int x[M][N];
printf("x:%d x+1:%d &x[0]+1:%d &x[0][0]+1:%d\n", x, x+1, &x[0]+1, &x[0][0]+1);
return 0;
}
此程序的输出结果为:
x:6618608 x+1:6618624 &x[0]+1:6618624 &x[0][0]+1:6618612
由于我们知道,x[3][4]
是一个三行四列的数组,也就是说每一行占3*sizeof(int) = 12
位。对比一下输出结果,不难看出:
x+1
和&x[0]+1
是相同的,都是移动了一行(12位)&x[0][0]+1
则与另外两个不同,只是移动了一个(4位)
#include <stdio.h>
#define M 3
#define N 4
int main()
{
int x[M][N];
int *p1, (*p2)[N];
p1 = *x; //一级指针
p2 = x; //二级指针
printf("x+1:%d p1+1:%d p2+1:%d &x[0]+1:%d &x[0][0]+1:%d\n", x+1, p1+1, p2+1, &x[0]+1, &x[0][0]+1);
return 0;
}
如果运行上述代码,输出结果为:
x+1:6618608 p1+1:6618596 p2+1:6618608 &x[0]+1:6618608 &x[0][0]+1:6618596
可以发现:
- 一级指针(
p1
)和一级首地址(&x[0][0]
)的移动情况相同,符合一维数组的移动规则。 - 二级指针(
p2
)和二级首地址(&x[0]
)的移动情况相同,符合二维数组的移动规则。
换句话说,也就是虽然这些变量对应的都是同一个值,但是数据类型的不同,会导致其运算规则的不同。而指针和同级首地址之间的差别在于:指针变量对应的值能够改变,而首地址的值不能变,就比如说:
p1++, p2++; //正确
&x[0][0]++, &x[0]++; //错误:因为不能改变
相当于 &x[0][0]的类型为
int const *p
【注意和const * int p
以及const int *p
区分】
3. 参数的的传递:
在理解清楚上述内容过后,接下来的部分将显得非常容易,直接上结论:二维数组的函数调用,可以分为两种情况:
- 将二维数组当做一维数组的形式(一级指针、地址)调用:【用法和一维数组相同】
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define M 3
#define N 4
void Input(int x[], int m, int n) //x[]形式
{
srand(time(NULL));
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
x[i * n + j] = rand() % 10; //对应输出:x[i]形式
//x[i][j] = x[i*n+j]对应位置输入
}
void Output(int *x, int m, int n) //*x形式
{
for (int i = 0; i < m * n; i++)
{
printf("%d ", *(x + i)); //无脑输出:*(x+i)形式
if ((i + 1) % n == 0) //输出n个就换一行
printf("\n");
}
}
int main()
{
int x[M][N];
int *p1;
p1 = *x;
Input(*x, M, N); //函数名调用【注意“*”】
Output(p1, M, N); //指针调用
printf("\n");
Output(&x[0][0], M, N); //一维首地址调用
printf("\n");
return 0;
}
关于一维的
*x
和x[]
的传入子函数形式,以及x[i]
和*(x+i)
的元素调用形式已经说得很详细了,此处不再赘述。
再啰嗦一点:由于x
是二维数组的数组名(对应二级地址),需要将格式搞成一维数组数组名的格式(对应一级地址),需要通过*
来实现。
个人的理解为:
*
表示取值,取了一次值,就相当于地址少了一级
- 按照二维数组的形式(二级指针,地址)调用:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define M 3
#define N 4
void Input(int x[][N], int m, int n) //x[][M]形式
{
srand(time(NULL) + 1);
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
*(*(x + i) + j) = rand() % 10; //对应输入:*(*(x+i)+j)形式
//x+i:代表x的第i行的首地址,二维形式【按行移动】 ---> 加上*以后变为一维形式【按列移动】
//*(x+i)+j:代表x的第i行第j列的地址:先移动到i行,再向右移动j列
//*(*(x+i)+j):上述地址对应的值,即:x[i][j]
}
void Output(int x[M][N], int m, int n) //x[M][N]
{
for (int i = 0; i < m; i++, printf("\n"))
for (int j = 0; j < n; j++)
printf("%d ", *(x[i] + j)); //对应输出:*(x[i] + j)
//和上一种方法类似,只不过将 x[i] <---> *(x+i)
}
int main()
{
int x[M][N];
int (*p2)[N];
p2 = x; //二级指针
Input(x, M, N); //二维数组的二维输入形式
Output(p2, M, N); //二维数组的二维输出形式
printf("\n");
Output(&x[0], M, N);
return 0;
}
这里需要进行几点说明:
- 二维数组
x[M][N]
中,M
可以缺省,但是N
不能,就像一维数组中的N
不能缺省一样。 - 关于数组中元素的调用,最常见的还是
x[i][j]
的调用方法,和例子中的*(*(x + i) + j)
和*(x[i] + j)
等价,且更加简便。
4. 总结:
二维数组当做一维的进行输入输出的的方法和一维类似(注意是*x
),不再此说明,作为二维的用如下:
- 子函数接收的是指针变量,可选择以下两种方法【即保证是指针变量】
void test(int x[M][N])
void test(int x[][N])
- 传递进入子函数的是地址,可选择以下两种方法【
x
本身是地址,&
是取值对应的地址】test(x);
test(&x[0]);
- 调用参与运算的是数组的元素(值):可选择以下两种方法【
*
表示取地址对应的值】x[i][j]
*(*(x + i) + j)
*(x[i] + j)