【问题标题】:Create 2D array from existing 1D arrays in C?从C中现有的一维数组创建二维数组?
【发布时间】:2015-07-27 12:47:12
【问题描述】:

在 perl 中,我可以创建一维数组,然后从它们创建二维数组,方法如下:

@a1=(a,b,c)
@a2=(d,e,f)
@a3=(g,h,i)
@m23_v1=(\@a1,\@a2,\@a3)

这是另一种方式(假设@a1@a2@a3 与前面的示例相同):

@m23_v2=([@a1],[@a2],[@a3])

这两种方式的区别在于,当使用反斜杠时,更改$a[0][0] 也会更改$a1[0]。另一方面,当使用括号时,值会被复制,因此更改$a[0][0] 不会更改$a1[0]。下面是变量的内存地址,应该澄清我的意思:

print \$a1[0]
SCALAR(0x2006c0a0)

print \$m23_v1[0][0]
SCALAR(0x2006c0a0)

print \$m23_v2[0][0]
SCALAR(0x2030a7e8)

如何在 C 中实现相同的功能?我试过以下代码:

# include <stdio.h>
int main(){
  int a1[3] = {1,2,3};
  int a2[3] = {4,5,6};
  int m23[2][3] = {a1, a2};
  printf("%d\n", a1[0]);
  printf("%d\n", m23[0][0]);
}

但它给了我以下编译警告:

2d.c: In function ‘main’:
2d.c:4:3: warning: initialization makes integer from pointer without a cast [enabled by default]
2d.c:4:3: warning: (near initialization for ‘m23[0][0]’) [enabled by default]
2d.c:4:3: warning: initialization makes integer from pointer without a cast [enabled by default]
2d.c:4:3: warning: (near initialization for ‘m23[0][1]’) [enabled by default]
2d.c:5:3: warning: incompatible implicit declaration of built-in function ‘printf’ [enabled by default]

执行后C代码返回如下:

1
-1077371888

问题:

  1. 为什么会收到编译警告以及如何修改代码以消除它们?
  2. 如果给定的 C 相当于带反斜杠的 perl 版本,那么括号版本的等价物是什么(反之亦然)?
  3. 为什么我得到 -1077371888 而不是 1?

【问题讨论】:

  • 谢谢大家。我赞成所有答案,因为每个人都提出了一些有趣的观点。我只能接受一个,所以我选择了@GrzegorzSzpetkowski,因为它很好地解释了幕后发生的事情。

标签: c arrays


【解决方案1】:

您会收到警告,因为当您初始化 m23 数组时,数组 a1a2 衰减为 指针,使您拥有一个不是数组而是指针的数组。并且这也是您解决问题的方法,将m23 声明为指针数组:

int *m23[2] = { a1, a2 };

【讨论】:

  • 所以这相当于 perl 的反斜杠版本(因为使用了指针),对吗?如何创建大括号版本?
【解决方案2】:

您可以使用指针数组来获得等效的反斜线版本(即@m23_v1):

#include <stdio.h>

int main(void)
{
    int a1[3] = {1,2,3};
    int a2[3] = {4,5,6};

    int *m23[2] = {a1, a2};

    printf("%d\n", a1[0]);
    printf("%d\n", m23[0][0]);

    return 0;
}

在您的代码中:

int m23[2][3] = {a1, a2};

initializer 期望用整数填充。基本上你用两个整数创建二维数组:a1a2。其余元素用零初始化。为了说明这一点,这看起来像:

int m23[2][3] = {0xaaee33, 0xaaee55, 0, 0, 0, 0};

实际上等同于:

int m23[2][3] = {{0xaaee33, 0xaaee55, 0}, {0, 0, 0}}; // two rows, three columns

但是,a1 不是整数。它是数组的名称,隐式转换为int(转换为指针后,指向数组的第一个元素)。换句话说,您将a1a2 的地址隐式转换为两个整数。事实上,这样的操作在 C 语言中是非法的,这样的代码甚至不应该使用-pedantic-errors 标志(或等效标志)编译。

括号版本的等价物是什么(反之亦然)?

大括号版本的等价物是定义一个特定大小的多维数组,然后将a1a2数组的每个元素复制到其中:

#include <stdio.h>
#include <string.h>

int main(void)
{
    int a1[3] = {1,2,3};
    int a2[3] = {4,5,6};

    int m23[2][3];
    memcpy(m23 + 0, a1, sizeof(a1));
    memcpy(m23 + 1, a2, sizeof(a2));

    printf("%d\n", a1[0]);
    printf("%d\n", m23[0][0]);

    return 0;
}

【讨论】:

  • m23[2][3] 看起来像你解释过{0xaaee33, 0xaaee55, 0, 0, 0, 0}; 还是更确切地说是{{0xaaee33, 0xaaee55, 0}, {0, 0, 0}};?此外,当 a1 隐式转换为 int 时, m23[0][0] 应该返回 a1 第零个元素。或者问题也可以成立:a1 是如何隐式转换为 int 的?
  • @WakanTanka:我澄清了我的回答。对于您的第二个问题,在这种情况下,m23[0][0] 确实返回了a1地址,而不是它的第一个元素。
  • 谢谢,你的代码给了我以下警告:2d.c:8:3: warning: incompatible implicit declaration of built-in function ‘printf’ [enabled by default]你能帮忙吗?
  • @WakanTanka:我相信您忘记包含 &lt;stdio.h&gt; 标头,它包含 printf 的正确原型。
  • 是的,就是这样。谢谢
【解决方案3】:

你不能使用其他数组来初始化数组。你可以这样做:

int m[2][3] = {{1,2,3},{4,5,6}};

这是因为 a1 和 a2 被视为指向 int 的指针。 您可以使用指针数组:

int* m[2] = {a1,a2);

【讨论】:

  • 您确定使用 int* m[3] 而不是 int* m[2]?
  • 我的错应该是 int *m[2]
  • 到目前为止,从您(和另一个)的答案来看,我知道这不能通过数组来完成,但可以通过指针来完成。但是我认为数组和指针在很多方面都是相似的(数组是常量指针AFAIK)所以这句话有什么理由“你不能用其他数组初始化数组”我在这里违反了什么?我是在增加任何常数还是什么?
  • 这是因为语言不支持这种初始化,因为它不支持数组复制。您可以使用聚合样式初始化它,或者使用 for 循环手动执行它,或者使用指针数组。
【解决方案4】:
  1. 为什么我会收到编译警告以及如何修改代码以消除它们?

那是因为您分配了一个指针 (a1),它需要一个整数(如果是 int 类型,则为 m23)而没有适当的强制转换。

这是一种写法:

#include <stdio.h>
#define N 2
#define M 3
int main(){
    int a1[M] = {1,2,3};
    int a2[M] = {4,5,6};
    int* m23[N] = {a1, a2};
    printf("%d\n", a1[0]);
    printf("%d\n", m23[0][0]);
}
  1. 如果给定的 C 等价于反斜杠 perl 版本,那么括号版本的等价物是什么(反之亦然)?

我不是 Perl 专家,但我认为没有像某些类型那样简单的方法。正如您在 Perl 的输出中看到的那样,您正在打印行和向量驻留在内存中的位置。 $a1 与 $m23_v1 相同,因为它获取行的 $a1,但在 $m23_v2 中,您实际上从 $a2 中创建了另一个向量,因此内存位置发生了变化。

  1. 为什么我得到 -1077371888 而不是 1?

那是因为你为 m23 分配了指针 a1 的值,它是内存中的一个地址,当像整数 ( printf("%d\n" ) 一样打印时,它的计算结果为 int,这就是结果。

【讨论】:

  • 是的,我了解它在 perl 中的工作原理。我想知道(除其他问题外)C 中的等价物是什么。或者换句话说:如何在不更改 a1[0] 的情况下更改 m23[0][0] (在使用 int* m23[N] 构建之后)?这在 C 语言中是否可行?
  • 如您所见,执行int* m23[N] = {a1, a2}; 并修改 m23[0][0] 将修改 a1。为了使它们分开,您需要手动构建 m23,迭代每个元素并为其分配值。一种方法是这样的:int* cols[N] = {a1, a2}; int m23[N][M]; int i, j; for (i = 0; i &lt; N; ++i) { for (j = 0; j &lt; M; ++j) { m23[i][j] = cols[i][j]; } }你可以在编辑器中复制/粘贴它并在每个;之后输入,这样会更好看。
【解决方案5】:

正如其他答案已经说明的那样,您可以创建一个指针数组来将两个子数组聚集在一起。然而,它们确实没有被复制,而只是被引用(即像带有反斜杠的 Perl sn-p)。

int *m23[] = {a1, a2};

在 C 中,您不能分配数组,也不能直接从另一个数组初始化一个数组。您必须手动复制它:

int a1[3] = {1,2,3};
int a2[3] = {4,5,6};

// Uninitialized array of sufficient size
int m23[2][3];

// Manual copy of both subarrays
memcpy(&m23[0], &a1, sizeof a1);
memcpy(&m23[1], &a2, sizeof a2);

与您的 cmets 之一相关的旁注:数组是 not 指针,无论是常量还是其他。但在大多数情况下,数组的名称可能衰减为指向其第一个元素的指针。
数组到指针衰减的例外包括上面使用的sizeof 运算符:这里的结果是3 * sizeof(int),数组的实际大小,而不是sizeof(int*)

【讨论】:

    【解决方案6】:

    您必须考虑 C 语言在内存中存储多维数组的方式。

    C 是一种思想简单的语言,它认为在具有 n 维的数组中,只有最后一个定义了存在多少真实对象,其他的则是它的重复。 IE。 int int arr[10][5]; 的数组意味着我们有一个由 5 个整数组成的字符串,它在内存中重复 10 次。

    如果我们添加更多维度,我们会在内存中获得更多重复。 IE。 int arr[4][10][5]; 表示我们有一个由 5 个整数组成的字符串,它自身重复 10 次,连续总计 50 个整数,再重复 4 次。最后,我们在内存中有一个连续的 200 个整数序列,表示一个 3 维整数数组。 现在 C 的阴暗面之一是,缺少边界控制,可以使用我们喜欢的任何索引读取该数组,其中乘积在 0 到 199 之间。因此,当您声明多维数组时,以允许编译器正确寻址元素,您应该给出除最后一个以外的所有维度。

    现在有了你的问题,你可以使用联合来伪造数组混合:

    union
    {
        struct
        {
          int a1[3];
          int a2[3];
        };
        int m23[2][3];
    } stTst = {1,2,3,4,5,6};
    
    printf("%d\n", stTst.a1[0]);
    printf("%d\n", stTst.m23[0][0]);
    printf("%d\n", stTst.a2[2]);
    printf("%d\n", stTst.m23[1][2]);
    

    在这种情况下,我们使用 C 数组内存布局来创建两个多维数组和一个多维数组。

    另一种解决方案是在多维的维度中复制每个单个数组。

    【讨论】:

      猜你喜欢
      • 2016-12-27
      • 1970-01-01
      • 2023-01-04
      • 2023-01-09
      • 2020-06-29
      • 1970-01-01
      • 2013-07-16
      • 2014-02-16
      相关资源
      最近更新 更多