【C零基础详解】方阵循环右移 (20分)【二维数组的函数指针调用】


本题要求编写程序,将给定 n× n方阵中的每个元素循环向右移 m个位置,即将第0、1、⋯、 n−1列变换为第 nmnm+1、⋯、 n−1、0、1、⋯、 nm−1列。

输入格式:

输入第一行给出两个正整数mn(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. 思路分析:

此题思路较为清晰,主要用做例子对二维数组的函数调用进行说明:

  1. 数组的输入:void Input(int x[][MAXS], int n)
  2. 数组的右移:void Moveright(int x[MAXS][MAXS], int n)
  3. 数组的输出: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]进行命名【名字是我胡乱取的,只是为了便于说明,如果有知道标准的叫法的朋友欢迎指出】

  1. 数组名:x
  2. 二维数组的一级首地址:&x[0][0]
  3. 二维数组的二级首地址:&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. 参数的的传递:

在理解清楚上述内容过后,接下来的部分将显得非常容易,直接上结论:二维数组的函数调用,可以分为两种情况:

  1. 将二维数组当做一维数组的形式(一级指针、地址)调用:【用法和一维数组相同】
#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;
}

关于一维的*xx[]的传入子函数形式,以及x[i]*(x+i)的元素调用形式已经说得很详细了,此处不再赘述。

再啰嗦一点:由于x是二维数组的数组名(对应二级地址),需要将格式搞成一维数组数组名的格式(对应一级地址),需要通过*来实现。

个人的理解为:*表示取值,取了一次值,就相当于地址少了一级

  1. 按照二维数组的形式(二级指针,地址)调用:
#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;
}

这里需要进行几点说明:

  1. 二维数组x[M][N]中,M可以缺省,但是N不能,就像一维数组中的N不能缺省一样。
  2. 关于数组中元素的调用,最常见的还是x[i][j]的调用方法,和例子中的*(*(x + i) + j)*(x[i] + j)等价,且更加简便。
4. 总结:

二维数组当做一维的进行输入输出的的方法和一维类似(注意是*x),不再此说明,作为二维的用如下:

  1. 子函数接收的是指针变量,可选择以下两种方法【即保证是指针变量】
    • void test(int x[M][N])
    • void test(int x[][N])
  2. 传递进入子函数的是地址,可选择以下两种方法【x本身是地址,&是取值对应的地址】
    • test(x);
    • test(&x[0]);
  3. 调用参与运算的是数组的元素(值):可选择以下两种方法【*表示取地址对应的值】
    • x[i][j]
    • *(*(x + i) + j)
    • *(x[i] + j)
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值