简体   繁体   English

如何在c中分配一个结构数组?

[英]How to allocate an array of structs in c?

I am writing a function that adds items to the shopping list.我正在编写一个将项目添加到购物清单的函数。 I understand how to limit the size of the shopping list, but what I want to do is that I want to use dynamic memory allocation by using malloc ().我了解如何限制购物清单的大小,但我想做的是我想通过使用malloc ().

I want to get rid of the size and store the data as it comes in. Thus if 15 fields of data come in, I want to store 15 and not a specific size.我想摆脱大小并在数据进入时存储数据。因此,如果有 15 个数据字段进入,我想存储 15 个而不是特定的大小。 I'm assuming that I don't know how much data is coming into my program.我假设我不知道有多少数据进入我的程序。

I am still a beginner, so let me know if I presented the question in a wrong way.我还是个初学者,所以如果我以错误的方式提出问题,请告诉我。

Thank you in advance, I appreciate your opinions.提前谢谢你,我很欣赏你的意见。

#define _CRT_SECURE_NO_WARNINGS
#include"ShoppingList.h"
#include<stdio.h>
#include<stdlib.h> // For malloc() and free()



void addItem(struct ShoppingList* list)
{
    
    
    if (list->length > 4)
    {
        return 0;
    }
    
    


        printf("Name for product: ");
        scanf("%s", list->itemList[list->length].productName);

        do
        {
            printf("Enter the amount: ");
            scanf("%f", &list->itemList[list->length].amount);

            if (list->itemList[list->length].amount <= 0.0)
            {
                printf("Input is invalid.\n");
            }

        } while (list->itemList[list->length].amount <= 0.0);


        printf("Enter unit of item: ");
        scanf("%s", list->itemList[list->length].unit);

        printf("%s was added to the shoppinglist.", list->itemList[list->length].productName);

        list->length++;

}
#ifndef SHOPPING_LIST_H
#define SHOPPING_LIST_H


// Struct definitions

struct GroceryItem
{
    char productName[20];
    float amount;
    char unit[10];
};

struct ShoppingList
{
    int length;
    struct GroceryItem itemList[5];
};

// Function declarations

void addItem(struct ShoppingList *list);
void printList(struct ShoppingList *list);
void editItem(struct ShoppingList *list);
void removeItem(struct ShoppingList *list);
void saveList(struct ShoppingList *list);
void loadList(struct ShoppingList* list);

#endif
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include "ShoppingList.h"

int main(void)
{
    struct ShoppingList shoppingList;
    shoppingList.length = 0; // The shopping list is empty at the start

    int option;

    do
    {
        printf("\n\nWelcome to the shopping list manager!\n");
        printf("=====================================\n\n");

        printf("1. Add an item\n");
        printf("2. Display the shopping list\n");
        printf("3. Remove an item\n");
        printf("4. Change an item\n");
        printf("5. Save list\n");
        printf("6. Load list\n");
        printf("7. Exit\n");

        printf("What do you want to do? ");
        scanf("%d", &option);

        switch (option)
        {
        case 1: addItem(&shoppingList); break;
        case 2: printList(&shoppingList); break;
        case 3: removeItem(&shoppingList); break;
        case 4: editItem(&shoppingList); break;
        case 5: saveList(&shoppingList); break;
        case 6: loadList(&shoppingList); break;
        case 7: break;
        default:
            printf("Please enter a number between 1 and 7");
        }
    } while (option != 7);

    return 0;
}

I would use flexible array members.我会使用灵活的数组成员。

typedef struct GroceryItem
{
    char productName[20];
    float amount;
    char unit[10];
}GroceryItem;

typedef struct ShoppingList
{
    size_t length;
    GroceryItem itemList[];
}ShoppingList;


ShoppingList *additem(ShoppingList  *sh, const GroceryItem *item)
{
    size_t newsize = sh ? sh -> length + 1 : 1;
    sh = realloc(sh, newsize * sizeof(sh -> itemList[0]) + sizeof(*sh));
    if(sh)
    {
        sh -> length = newsize;
        sh -> itemList[newsize - 1] = *item;
    }
    return sh;
}

But personally, I would rather use a linked list instead of an array for this task.但就个人而言,我宁愿使用链表而不是数组来完成这项任务。

There are several ways to achieve your target:有几种方法可以实现您的目标:

  • The first involves no machinery but malloc(3) use, and it is used only when you know in advance (in advance to allocate, but at run time, this is, not when the program is started, but for this usage of the array)第一个不涉及任何机器,而是malloc(3)使用,并且仅在您事先知道时使用(提前分配,但在运行时,这是,不是在程序启动时,而是对于数组的这种用法)
    struct the_thing {
         /* .... */
    };

    ...

    struct the_thing *array_of_things = malloc(number_of_things * sizeof(array_of_things[0]);

This will return a block of memory of size of the product of one element ( array_of_things[0] is not a real expression this time, but the argument to the operator sizeof ) that calculates the size of the type of the evaluation of the used expression, and doesn't evaluates it ---this is important because you cannot evaluate that expression yet, as the pointer doesn't point yet to the allocated memory--- by the number of elements you are going to use) This converts your pointer (and the pointer arithmetic induced by using the [index] notation) into an usable array (indeed it is a pointer to an array of elements. Important to say that the array is uninitialized and the data contents can be rubish and you have to initialize the elements, one by one, to the desired values. In the case you want an initialized array, you can use, instead, calloc(3) that was defined specifically to allocate arrays of things.这将返回一个元素乘积大小的内存块( array_of_things[0]这次不是真正的表达式,而是运算符sizeof的参数)计算所用表达式的评估类型的大小,并且不评估它---这很重要,因为你还不能评估那个表达式,因为指针还没有指向分配的内存---你要使用的元素数量)这会转换你的指针(以及使用[index]表示法引起的指针算术)转换为可用数组(实际上它是指向元素数组的指针。重要的是要说数组未初始化并且数据内容可能很乱,你必须将元素一个一个地初始化为所需的值。如果你想要一个初始化的数组,你可以使用专门定义的calloc(3)来分配事物的数组。

    struct the_thing *array_of_things = calloc(number_of_things, sizeof(array_of_things[0]));

look at one detail, we have used a comma this time to specify two quantities as parameters to calloc() , instead of passing the product of both.看一个细节,这次我们使用逗号将两个量指定为calloc()的参数,而不是传递两者的乘积。 The result is that calloc(3) returns an initialized array (everything is initialized to zeros) and also, you specify separately the number of elements and the size of one element.结果是calloc(3)返回一个已初始化的数组(所有内容都初始化为零),并且您分别指定元素的数量和一个元素的大小。 This call is specific (but not mandatory specific) to create dynamic arrays.此调用特定于(但不是强制特定的)创建动态数组。 Both calls take as arguments the size of the total block of memory to allocate, and the number of elements and the element size, respectively.这两个调用分别将要分配的内存块的总大小、元素数量和元素大小作为参数。

  • The second way occurs when you need to make the array grow dynamically as you require more and more elements.当您需要使数组随着需要越来越多的元素而动态增长时,就会出现第二种方式。 This can be solved with the previous functions, but there's a function realloc() that does the dirty work for your.这可以用前面的函数解决,但是有一个函数realloc()可以为你做脏活。
    struct the_thing *array_of_things = NULL;

    /* now I need n1 elements */
    array_of_things = realloc(array_of_things, n1 * sizeof(array_of_things[0]));

with this, array_of_things has passed to hold no element (because it was assigned NULL ) to allocate n1 elements.有了这个, array_of_things已经传递给没有元素(因为它被分配了NULL )来分配n1元素。 The normal workings allow you to work with this array, until you get to it's limit, now things get more interesting:正常工作允许你使用这个数组,直到你达到它的极限,现在事情变得更有趣了:

    /* now I'll need n2 elements (n2 can be larger or smaller than n1 before) */
    array_of_things = realloc(array_of_things, n2 * sizeof(array_of_things[0]));

this call (with an already valid pointer) will try to see if the allocation can be solved in place, but if it cannot, a new array will be allocated with enough space to hold n2 elements, the n1 previous elements will be copied into the second array, and the new allocated pointer will be returned, and the previous one will be freed.此调用(使用已经有效的指针)将尝试查看分配是否可以就地解决,但如果不能,将分配一个新数组,该数组具有足够的空间来容纳n2个元素,之前的n1个元素将被复制到第二个数组,新分配的指针将被返回,前一个将被释放。

This has a possibly complicated drawback, is that you cannot have pointers pointing to element (or element fields) inside the array, because if the new array is repositioned in memory this will make all those pointers invalid (they will still be pointing to the old places, where now nothing exists) But if you can handle that, everything is fine.这可能有一个复杂的缺点,就是你不能让指针指向数组内的元素(或元素字段),因为如果新数组在内存中重新定位,这将使所有这些指针无效(它们仍将指向旧数组地方,现在什么都不存在)但是如果你能处理好,一切都很好。

I wrote a macro function to handle this situation and it works fine (except when I have pointers pointing inside the array that I have to rearrange all those pointers when I use the macro) The macro requires an array A , with two associated variables, A_len and A_cap (both of type size_t ) to be declared with the array:我写了一个宏函数来处理这种情况并且它工作正常(除非我有指向数组内部的指针,当我使用宏时我必须重新排列所有这些指针)宏需要一个数组A ,有两个相关变量, A_lenA_cap (均为size_t类型)用数组声明:

#define DYNARRAY_GROW(_array _elem_type, _need, _increment) do { \
            if (_array##_len + (_need) > _array##_cap) {         \
                _array##_cap += (_increment);                    \
                _array = (_elem_type *) realloc(                 \ 
                       _array, _array##_cap * sizeof _array[0]); \
            }                                                    \
        } while (0)

that you declare your array as:您将数组声明为:

      struct the_thing *array_of_things;
      size_t array_of_things_len = 0;
      size_t array_of_things_cap = 0;

and then, you use it:然后,你使用它:

      /* I need n1 elements */ 
      DYNARRAY_GROW(array_of_things,  /* the array */
                    struct the_thing, /* the type of element (see note below) */
                    1,                /* only one element more needed */
                    10);              /* in case of grow, 10 elements are to be added */

In which the parameters其中参数

  • array_of_things is the array name. array_of_things是数组名称。 See from the macro definition that the _cap suffixed variable and the _len suffixed variables are derived from the name used here, so names are tied together (this makes less error prone the code)从宏定义中可以看出, _cap后缀变量和_len后缀变量都是从这里使用的名字派生而来的,所以名字是绑在一起的(这样代码不容易出错)
  • struct the_thing is the type of an array element. struct the_thing是数组元素的类型。 You will see that it is used only to cast the returned pointer from realloc() to the proper type (thing that normally is discouraged in C, but for environments in which C code is tested using C++ testing code ---like GTest/GMock--- this is required, to avoid the resulting error from the C++ compiler) So while this cast is not only unnecessary in C (and even dangerous to use) it is forbidden in C++, and to allow compatibility with C++ (and being part of a tested macro) has been provided for convenience.您会看到它仅用于将返回的指针从realloc()转换为正确的类型(这在 C 中通常是不鼓励的,但对于使用 C++ 测试代码测试 C 代码的环境——如 GTest/GMock --- 这是必需的,以避免 C++ 编译器产生的错误)因此,虽然这种转换不仅在 C 中是不必要的(甚至使用起来很危险),但在 C++ 中是被禁止的,并且允许与 C++ 兼容(并且是一部分为方便起见,提供了经过测试的宏)。
  • 1 is the amount of required elements. 1是所需元素的数量。 It is supposed that you call this macro to add some number of elements (normally one) to it, one shot, this is the amount of such elements you are going to add.假设您调用此宏以向其中添加一定数量的元素(通常是一个),一次拍摄,这是您要添加的此类元素的数量。
  • 10 is the amount of elements to grow in case a grow is required. 10是在需要增长的情况下要增长的元素数量。 Despite the number of elements to grow you need, it is often far more efficient to allocate a bigger ammount so not every time we need one element we allocate only one, but a bunch of them, so the number of total calls to realloc() is decreased, adding efficiency to the overal code.尽管你需要增加元素的数量,分配更大的数量通常更有效,所以不是每次我们需要一个元素时我们只分配一个,而是分配一堆,所以对realloc()的总调用次数减少了,增加了整体代码的效率。

In your case, for example:在你的情况下,例如:

struct ShoppingList *
       shopping_list     = NULL;
size_t shopping_list_cap = 0,
       shopping_list_len = 0;

void addItem(
        struct ShoppingList *element)
{
    DYNARRAY_GROW(shopping_list,
                  struct ShoppingList,
                  1,   /* what is needed */
                  10); /* what we get in that case. */

    /* after that we're able to add it (we have one 
     * free slot at least to add an element) */
    shopping_list[shopping_list_len++] = *element; /* copy the data */
}

Of course, when you are done with the shopping list, you need to set all three global variables to NULL , 0 and 0 .当然,完成购物清单后,您需要将所有三个全局变量设置为NULL00

    free(shopping_list);
    shopping_list = NULL;
    shopping_list_len = shopping_list_cap = 0;

Note: I have deliberately not done error checking, because is not clear in your homework how it is to be handled the occasional (and rare) case that you get NULL from realloc() .注意:我故意没有进行错误检查,因为在你的家庭作业中不清楚如何处理你从realloc()得到NULL的偶然(和罕见)情况。 This case is a bit cumbersome because it requires you to make a second copy of the pointer before calling realloc() (as the value stored in shopping_list will be destroyed by the assignment of NULL to the pointer variable, in case the realloc() fails)这种情况有点麻烦,因为它要求您在调用realloc()之前制作指针的第二个副本(因为存储在shopping_list中的值将通过将NULL分配给指针变量来销毁,以防realloc()失败)

Normally, a memory allocation fail means you have some memory leak somewhere, as:通常,内存分配失败意味着你在某处有一些内存泄漏,如:

  • systems have today enough virtual memory allocatable for each process (this means amount of physical memory plus swap space), so it is improbable that you allocate the total amount of memory of your machine in a single process.今天的系统有足够的虚拟内存可分配给每个进程(这意味着物理内存量加上交换空间),因此您不可能在单个进程中分配机器的全部内存量。
  • In case you need to be limited the amount of data memory for your process, it is assumed you know how to deal with memory allocation errors to write a working solution for that case, just make a copy of the pointer, run the above code and if you got a NULL return value, then continue (if you still call) without the allocation (eg try a lower increment, wait some time so we have more memory, etc.) by recovering the copy into the array pointer.如果您需要限制进程的数据内存量,假设您知道如何处理内存分配错误来为这种情况编写有效的解决方案,只需复制指针,运行上面的代码并如果您得到NULL返回值,则通过将副本恢复到数组指针中来继续(如果您仍然调用)而不进行分配(例如尝试较低的增量,等待一段时间以便我们有更多内存等)。
  • If you run in an embedded system with small memory, it is many times undesired to dynamically allocate the memory because there's almost no heap space to do dynamic memory allocation.如果你运行在一个内存较小的嵌入式系统中,很多时候动态分配内存是不希望的,因为几乎没有堆空间来进行动态内存分配。 Then you have to preassign static arrays and run this way.然后你必须预先分配静态数组并以这种方式运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM