C中结构中的二维数组

时间:2018-04-17 13:18:25

标签: c multidimensional-array c99 flexible-array-member

我正在尝试在结构中初始化一个二维数组,但我总是得到一个错误:

gcc -g -Wall -W -I/usr/include/SDL   -c -o fractal.o fractal.c
In file included from fractal.c:2:0:
fractal.h:12:12: error: array type has incomplete element type ‘double[]’
     double values[][];

以下是代码:

struct fractal {

    char name[64];
    int height;
    int width;
    double a;
    double b;
    double meanValue;       
    double values[][];  /*This line is causing the error*/
 };

理想情况下,我想像这样初始化二维数组的高度和宽度:

struct fractal {

    /*... Same code as above ...*/      

    double values[width][height];  
 };

但是在编译时我又遇到了两个错误:

gcc -g -Wall -W -I/usr/include/SDL   -c -o fractal.o fractal.c
In file included from fractal.c:2:0:
fractal.h:12:19: error: ‘width’ undeclared here (not in a function)
     double values[width][height];
                   ^
fractal.h:12:26: error: ‘height’ undeclared here (not in a function)
     double values[width][height];
                          ^

我到处都看过,但我的代码应该可行,我无法弄清楚它为什么没有。

感谢您的帮助

3 个答案:

答案 0 :(得分:2)

动态维度数组不是C最佳的点...简单可变长度数组仅在C99版本的语言中引入,并且在C11版本中是可选的。它们仍未被MSVC 2017接受......

但是在这里,你试图在一个结构中设置一个。这根本不受支持,因为结构必须具有恒定大小(*)(如何处理结构数组)。所以我很抱歉,但这段代码不应该,我也无法用C语言表达。

一种常见的方法是用指针替换2D动态数组,将指针分配给2D数组然后使用它,但即使这样也不是很简单。

你必须以不同的方式设计你的结构......

(*)结构的最后一个元素可能是不完整的类型,例如int tab[];。这是一个危险的特性,因为程序员有责任为它提供空间。但无论如何你不能建立一个不完整类型的数组。

答案 1 :(得分:2)

作为免责声明,这是一个高级主题,所以如果你是初学者,你可能只想完全退出它,只需使用double*数组,然后为每个指针调用malloc 。 (对于初学者来说很好,在专业代码中是不可接受的。)

这是一个高级主题,因为这个特殊情况是C中的一个弱点。您尝试使用的功能,在结构的末尾有一个空数组,称为灵活的数组成员 。这只适用于一个维度。如果在编译时两个维度都是未知的,则必须进行解决。

分配部分与任何灵活的数组成员一样:动态分配结构并为尾随数组设置大小。

fractal_t* f = malloc(sizeof *f + sizeof(double[height][width]) );

(在这种情况下利用方便的VLA语法,尽管灵活的数组成员不是VLA。)

从技术上讲,结构的最后一个成员现在应该是double[],或者说结构声明。但是,malloc返回的内存在您访问它之前没有实际的有效类型,之后该内存的有效类型将成为用于访问的类型。

我们可以使用此规则来访问该内存,就好像它是double[][]一样,即使结构中的指针类型是不同的。给定分形f,通过指针访问的代码变为如下:

double (*array_2D)[width] = (double(*)[width]) f->values;

其中array_2D是数组指针。这里使用的最正确的类型是一个指向double double (*)[height][width]数组的数组指针,但是那个带有强制丑陋的访问(*array_2D)[i][j]。为了避免这种丑陋,一个常见的技巧是在数组指针声明中省略最左边的维度,然后我们可以将它作为array_2D[i][j]访问,它看起来更漂亮。

示例代码:

#include <stdlib.h>
#include <stdio.h>

typedef struct 
{
  char name[64];
  size_t height;
  size_t width;
  double a;
  double b;
  double meanValue;       
  double values[];
} fractal_t;


fractal_t* fractal_create (size_t height, size_t width)
{
  // using calloc since it conveniently fills everything with zeroes
  fractal_t* f = calloc(1, sizeof *f + sizeof(double[height][width]) );
  f->height = height;
  f->width = width;
  // ...
  return f;
}

void fractal_destroy (fractal_t* f)
{
  free(f);
}

void fractal_fill (fractal_t* f)
{
  double (*array_2D)[f->width] = (double(*)[f->width]) f->values;

  for(size_t height=0; height < f->height; height++)
  {
    for(size_t width=0; width < f->width; width++)
    {
      array_2D[height][width] = (double)width; // whatever value that makes sense
    }
  }
}

void fractal_print (const fractal_t* f)
{
  double (*array_2D)[f->width] = (double(*)[f->width]) f->values;

  for(size_t height=0; height < f->height; height++)
  {
    for(size_t width=0; width < f->width; width++)
    {
      printf("%.5f ", array_2D[height][width]); 
    }
    printf("\n");
  }
}

int main (void)
{
  int h = 3;
  int w = 4;

  fractal_t* fractal = fractal_create(h, w);
  fractal_fill(fractal); // fill with some garbage value
  fractal_print(fractal);
  fractal_destroy(fractal);
}

答案 2 :(得分:1)

我在设计一个结构以同时保存ODE求解器中的域值(N x 1矢量)和解值(N x M矩阵)时遇到了此问题,从而简化了函数接口。 N和M与仿真有关,因此先验未知。我使用GNU Scientific Library的向量矩阵模块解决了这个问题。我发现它比将FAM(尽管分配为2D)投射到独立的整体数组指针上更加简化。

为结构分配内存后,我们需要做的就是调用gsl_matrix_alloc()来为矩阵保留空间。完成后,调用gsl_matrix_free()将销毁它。请注意,这些功能取决于文档中说明的数据类型。

文件名: struct_mat.c

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_statistics.h>

typedef struct _fractal {
    char name[64];
    size_t height;
    size_t width;
    double a;
    double b;
    double meanValue;
    gsl_matrix *values;
 } fractal;

fractal * fractal_create(size_t height, size_t width) {
    fractal * fractalObj = (fractal *) malloc(sizeof(fractal));
    fractalObj -> values = gsl_matrix_alloc(height, width);

    if (fractalObj == NULL || fractalObj -> values == NULL) {
        fprintf(stderr, "NULL pointer returned while allocating fractal object.. Exiting program.\n");
        exit(EXIT_FAILURE);
    }

    fractalObj -> height = height;
    fractalObj -> width = width;
    fractalObj -> meanValue = 0.0;

    return fractalObj;
}

void fractal_populate(fractal * fractalObj) {

    srand(time(NULL));
    double current_value = 0.0;

    for (size_t r = 0; r < fractalObj -> height; ++r) {
        for (size_t c = 0; c < fractalObj -> width; ++c) {
            current_value = (double) rand() / (double) RAND_MAX;
            gsl_matrix_set(fractalObj -> values, r, c, current_value);
        }
    }
}

void fractal_calcMeanValue(fractal * fractalObj) {

    gsl_vector_view colVec;

    for (size_t col = 0; col < fractalObj -> values -> size2; ++col) {
        colVec = gsl_matrix_column(fractalObj -> values, col);
        fractalObj -> meanValue += gsl_stats_mean(colVec.vector.data, colVec.vector.stride, colVec.vector.size);
    }

    fractalObj -> meanValue /= fractalObj -> values -> size2;

    printf("\nThe mean value of the entire matrix is %lf\n", fractalObj -> meanValue);

}

void fractal_display(fractal * fractalObj) {
    printf("\n");
    for (size_t r = 0; r < fractalObj -> height; ++r) {
        for (size_t c = 0; c < fractalObj -> width; ++c) {
            printf("%lf ", gsl_matrix_get(fractalObj -> values, r, c));
        }
        printf("\n");
    }
}

void fractal_delete(fractal * fractalObj) {
    gsl_matrix_free(fractalObj -> values);
    free(fractalObj);
}

int main(int argc, char const *argv[]){

    // Program takes number of rows and columns as command line parameters
    switch(argc) {
        case 3:
            printf("Running program..\n"); // to avoid the declaration-succeeding-label error

            size_t height = atoi(argv[1]);
            size_t width = atoi(argv[2]);

            fractal * myFractal = fractal_create(height, width);

            fractal_populate(myFractal);
            fractal_display(myFractal);
            fractal_calcMeanValue(myFractal);

            fractal_delete(myFractal);
            return 0;

        default:
            fprintf(stderr, "USAGE: struct_mat <rows> <columns>\n");
            return 1;
    }

}


通过与GSL和GSL CBLAS库链接来进行编译:

gcc -std=c99 struct_mat.c -o struct_mat -lgsl -lgslcblas -lm  

您可以通过发行版的GSL,Windows上的Cygwin或编译package manager来安装source

根据我的有限经验,事实证明,使用标准数据结构要比使用FAM或指向1D数组的指针要容易得多。但是,需要注意的是,我们必须记住在分配结构本身之后为矩阵分配内存。