如何在C函数中使用指向二维结构体数组的指针

3

这是我的代码:

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

#define X 5       
#define Y 7 

struct mystruct {
    char name[10];
    int id[1];
    int status[1];
};

void show_struct(struct mystruct* table, int X_selected, int Y_selected);


void main(void)
{
    struct mystruct* table[X][Y];

    int X_selected = 3;
    int Y_selected = 4;

    table[X_selected][Y_selected] = (struct mystruct*)malloc(sizeof(struct mystruct));

    strcpy(table[X_selected][Y_selected]->name, "NAME");
    *table[X_selected][Y_selected]->id = 0;
    *table[X_selected][Y_selected]->status = 1;

    show_struct(table, X_selected, Y_selected);
}

void show_struct(struct mystruct* table, int X_selected, int Y_selected)
{
    if (**CONDITION**) {
        printf("OK , STATUS IS 1");
    }
    else {
        printf("ERROR , STATUS IS NOT 1");
    }
}

我需要帮助找到代码中检查状态是否为1的条件

当我在调试时,在进入函数之前,我可以看到状态为1已成功放置在表格上:

内存表


1
如果我没记错的话,你不能从第一个元素的地址名义上访问二维数组中的任意元素。你应该会在这里收到一个警告。(实际上它确实可以工作。)你需要传递整个行的指针。我马上会展示一些代码。 - Peter - Reinstate Monica
1
顺便提一下,拥有只有一个元素计数的数组(id、status)也是不必要的。 - Peter - Reinstate Monica
2
看起来你混淆了数组的分配方式。你已经将table定义为指向mystruct的二维指针数组。但是当你分配项目时,你创建了X*Y个结构体的存储空间。你应该从table定义中删除[X][Y],只执行一次分配,或者从分配中删除X*Y。 - tinman
1
小细节:通常情况下,强制转换malloc的输出既不必要也没有用。 - picchiolu
1
这段代码根本没有任何意义。有很多编译器警告(实际上是致命错误),有无数种方法可以修复它们,但不知道哪种修复方法与您的要求兼容。请用简单易懂的英语发布需求。 - n. m.
显示剩余14条评论
2个回答

1

好的,现在你已经说明了你要做什么,让我们从你的代码开始。我们使用godbolt编译器探索器。我只会链接到你的代码版本,这样我就不需要将那么多的代码复制到这个答案中。

如果我们在godbolt上使用最新的gcc(12.2)运行你的原始代码(用0代替你的条件标记),我们会得到一些警告和当然应该有的错误(版本0)。让我们修复第一个警告,因为它们很简单:

<source>:16:6: warning: return type of 'main' is not 'int' [-Wmain]
   16 | void main(void)
      |      ^~~~
<source>: In function 'main':
<source>:25:5: warning: implicit declaration of function 'strcpy' [-Wimplicit-function-declaration]
   25 |     strcpy(table[X_selected][Y_selected]->name, "NAME");
      |     ^~~~~~
<source>:3:1: note: include '<string.h>' or provide a declaration of 'strcpy'
    2 | #include <stdlib.h>
  +++ |+#include <string.h>
    3 | 
<source>:25:5: warning: incompatible implicit declaration of built-in function 'strcpy' [-Wbuiltin-declaration-mismatch]
   25 |     strcpy(table[X_selected][Y_selected]->name, "NAME");
      |     ^~~~~~
<source>:25:5: note: include '<string.h>' or provide a declaration of 'strcpy'
  • 好的,主函数应该返回一个int。通过谷歌搜索可以找到SO答案指出,int main(void)是可以的,没有返回语句也可以。明白了。
  • strcpy必须被声明。gcc已经建议包含头文件string.h。懂了。
  • 下一个警告就比较难了:
<source>: In function 'main':
<source>:30:17: warning: passing argument 1 of 'show_struct' from incompatible pointer type [-Wincompatible-pointer-types]
   30 |     show_struct(table, X_selected, Y_selected);
      |                 ^~~~~
      |                 |
      |                 struct mystruct * (*)[7]
<source>:14:35: note: expected 'struct mystruct *' but argument is of type 'struct mystruct * (*)[7]'
   14 | void show_struct(struct mystruct* table, int X_selected, int Y_selected);
      |                  ~~~~~~~~~~~~~~~~~^~~~~

特别是,struct mystruct * (*)[7] 是什么意思?为什么编译器认为我们传递了这个类型,而实际上我们传递的是一个二维数组?在我提出一个更简单避免使用复杂类型的解决方案之前,我将给出简短的解释。
在 C 语言中,一个二维数组实际上是一个由(一维)数组组成的数组。对于类型 T 的一维数组很容易理解(T 可以像你程序中的 struct mystruct * 一样,但参数是通用的)。T table[7]; 定义了一个由 7 个 T 组成的数组。现在我可以定义一个由 5 个这些数组组成的数组;因为方括号从左到右进行求值,所以我必须在旧维度的左边写入新维度(想象一下“我首先索引二维数组,得到一个元素——它是一个一维数组,然后我再次索引以获取第 j 个 T”——从左到右):T table[5][7]。5 个由 7 个 T 组成的一维数组。
关键点是要记住当你将数组传递给函数时会发生什么:它被“调整”为指向其第一个元素的指针。一个 T table[5][7] 的元素是由 7 个 T 组成的数组,或者说是 T [7],指向它的指针是 T (*)[7]。我们必须把 * 放在括号中,否则根据 运算符优先级,索引操作会首先执行,导致得到一个指向 T 的指针,这是不同的东西:我们没有七个指向 T 的指针的数组——我们有一个指向七个 T 的数组的指针。这两个句子中“指针”和“数组”的顺序反映了求值的顺序,这是由第二种情况中的括号强制执行的。现在,在我们的情况下,T 是 struct mystruct *,因此实际传递给函数的参数是 gcc 报告的: struct mystruct * (*)[7]它是一个指向七个指向 mystruct 的指针的数组的指针。 这与指向 mystruct 的指针不同。

实现一个打印mystruct对象的函数最简单的方法实际上是定义一个函数,只需接受指向该mystruct对象的指针即可。调用者负责提供正确的指针。这使得函数更通用:也许我们想要打印不在表中的mystructs?

指针有一个很好的保留值来表示它们不指向任何东西,在C语言中就是NULL。我们将测试指针是否为NULL以检查它是否已初始化。请注意,我们不能测试例如`if(myStructPtr->id == 0)`,因为如果myStructPtr未初始化,程序将出错并可能崩溃。但是,我们可以并且经常必须检查实际的指针值。

void show_struct(struct mystruct* myStructPtr)
{
    if (myStructPtr != NULL) {
        printf("OK , initialized");
    }
    else {
        printf("ERROR, uninitialized");
    }
}

传递这样的指针相对简单:正确地索引表格!毕竟,获得的元素是一个指针:

    show_struct(table[X_selected][Y_selected]);

现在我们必须确保存储在表中的指针实际上是空的。我们可以使用大括号初始化来做到这一点。我们实际上不需要初始化所有元素,缺失的元素将默认填充为零:
    struct mystruct* table[X][Y] = {{0}};

现在我们已经准备好 一个可以工作的程序

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

#define X 2       
#define Y 3 

struct mystruct {
    char name[10];
    int id[1];
    int status[1];
};

void show_struct(struct mystruct* myStructPtr)
{
    if (myStructPtr != NULL) {
        printf("OK , initialized\n");
        printf("Status: %d\n", *myStructPtr->status);
    }
    else {
        printf("ERROR, uninitialized\n");
    }
}

int main(void)
{
    struct mystruct* table[X][Y] = {{0}};
    int X_selected = 1;
    int Y_selected = 1;

    table[X_selected][Y_selected] = (struct mystruct*)malloc(sizeof(struct mystruct));

    strcpy(table[X_selected][Y_selected]->name, "NAME");
    *table[X_selected][Y_selected]->id = 0;
    *table[X_selected][Y_selected]->status = 1;

    printf("Selected: ");
    show_struct(table[X_selected][Y_selected]);
    printf("\n");

    for(int xInd = 0; xInd < X; xInd++)
    {
        for(int yInd = 0; yInd < Y; yInd++)
        {
            printf("%d, %d:\n", xInd, yInd);
            show_struct(table[xInd][yInd]);
            printf("****************************************\n");
   
        }
    }

}

0
这里有一个解决方案,可能比您要求的要多一点。我为结构数组创建了一个新的结构体,它带有大小信息,并且创建了一些操作该“数组结构”而不是原始指针的函数。
我的解决方案假设您最终想要在运行时输入数组大小(您的原型中定义了数组大小;在这种情况下,您可以简单地定义在编译时已知大小的适当数组)。
“数组结构”myTable仅包含指向实际数据的指针。这使得它非常小,可以通过值传递。复制是浅层的:副本指向与原始相同的数据。(这样我们可以使用通过值获取表格的函数来填充表格,例如fill_tbl(struct myTable tbl)
程序具有一个头文件myStructArray.h,其中包含结构定义和函数声明,一个实现文件myStructArray.c,其中包含函数实现以及一个main.c文件。

myStructArray.h

#ifndef MY_STRUCT_ARRAY_H
#define MY_STRUCT_ARRAY_H

#include <stdint.h> // size_t

struct mystruct {
    char name[10];
    int id;
    int status;
};

struct myTable
{
    size_t mXSz;
    size_t mYSz;

    struct mystruct* mStructs;
};

/// <summary>
/// return a table with a pointer to the dynamically allocated array 
/// </summary>
struct myTable createTable(size_t szX, size_t szY);

/// <summary>
/// get a pointer to an element in the given table
/// </summary>
struct mystruct* getStructPtr(struct myTable tbl, size_t x, size_t y);

/// <summary>
/// print  single struct
/// </summary>
/// <param name="s"></param>
void show_struct(struct mystruct* s);
/// <summary>
/// print the entire table
/// </summary>
/// <param name="tbl"></param>
void show_table(struct myTable tbl);

void destroy_table(struct myTable tbl);

#endif

myStructArray.c

#include <stdint.h> // size_t
#include <stdio.h> // printf
#include <string.h> // memset
#include <stdlib.h> // malloc
#include "myStructArray.h"
#include <assert.h>

void show_struct(struct mystruct *s)
{
    if (s->id) {
        printf("OK, STATUS IS 1\n");
        printf("name: ->%s<-\n", s->name? s->name : "Null");
        printf("status: %i\n", s->status);
    }
    else {
        printf("ERROR, STATUS IS NOT 1");
    }
}

struct mystruct* getStructPtr(struct myTable tbl, size_t x, size_t y)
{
    assert(x < tbl.mXSz&& y < tbl.mYSz);
    return &tbl.mStructs[y * tbl.mXSz + x];
}

void show_table(struct myTable tbl)
{
    for (size_t y = 0; y < tbl.mYSz; y++)
    {
        for (size_t x = 0; x < tbl.mXSz; x++)
        {
            printf("*************** x: %zu, y: %zu******\n", x, y);
            show_struct(getStructPtr(tbl, x, y));
        }
        printf("\n");
    }
}

struct myTable createTable(size_t szX, size_t szY)
{
    struct myTable newTbl = {szX, szY, 0};
    
    size_t byteSz = szX * szY * sizeof(struct mystruct);
    newTbl.mStructs = (struct mystruct *) malloc(byteSz);
    memset(newTbl.mStructs, 0, byteSz); // make sure all ids are 0
    return newTbl; // yes, by value (two numbers and a pointer)
}

void destroy_table(struct myTable tbl)
{
    free(tbl.mStructs); // ok if null
}

main.c

#define _CRT_SECURE_NO_WARNINGS // for visual C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "myStructArray.h"

void fill_tbl(struct myTable tbl)
{
    for (size_t y = 0; y < tbl.mYSz; y++)
    {
        for (size_t x = 0; x < tbl.mXSz; x++)
        {
            struct mystruct* sp = getStructPtr(tbl, x, y);
            sprintf(sp->name, "%zu/%zu", x, y);
            sp->id = 1+ y*tbl.mXSz + x;
            sp->status = 1;
        }
    }
}

void main(void)
{
    int size_x, size_y;
    printf("size_x size_y: ");
    scanf("%i %i", &size_x, &size_y);
    struct myTable tbl = createTable(size_x, size_y);

    fill_tbl(tbl);

    int x_selected, y_selected;
    printf("x_selected y_selected: ");
    scanf("%i %i", &x_selected, &y_selected);


    struct mystruct *sPtr = getStructPtr(tbl, x_selected, y_selected);
    strcpy(sPtr->name, "NAME");
    sPtr->id = 1234;
    sPtr->status = 1;

    show_struct(getStructPtr(tbl, x_selected, y_selected));
    show_table(tbl);
}

示例会话

第一行和第二行的"3 2"和"0 1"是用户输入的大小和索引,其余部分则为输出结果。

size_x size_y: 3 2
x_selected y_selected: 0 1
OK, STATUS IS 1
name: ->NAME<-
status: 1
*************** x: 0, y: 0******
OK, STATUS IS 1
name: ->0/0<-
status: 1
*************** x: 1, y: 0******
OK, STATUS IS 1
name: ->1/0<-
status: 1
*************** x: 2, y: 0******
OK, STATUS IS 1
name: ->2/0<-
status: 1

*************** x: 0, y: 1******
OK, STATUS IS 1
name: ->NAME<-
status: 1
*************** x: 1, y: 1******
OK, STATUS IS 1
name: ->1/1<-
status: 1
*************** x: 2, y: 1******
OK, STATUS IS 1
name: ->2/1<-
status: 1

非常感谢,但我不明白如何将它转换为“struct mystruct* table[X][Y];”声明类型,因为我需要这个,我不能修改这行代码 :( - Musa Tombul
我知道需要进行许多不同的更改,但是在我的项目中有这行代码是绝对必要的,难道不能只在函数的“内部”、“头部”或“调用”处进行更改吗? - Musa Tombul
因为创建结构体或使用malloc似乎没有错误,因为当我在行“show_struct(table, X_selected, Y_selected);”读取内存时,我得到了我想要的结果(就像图片https://i.stack.imgur.com/g6vWu.png中所示),所以我认为必须有一种方法可以在不编辑主函数调用和show_struct函数的情况下完成这个任务。 - Musa Tombul
请在函数中将代码行注释掉并编译,将“show_struct(table, X_selected, Y_selected)”添加红色调试点并查看内存? - Musa Tombul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接