簡體   English   中英

如何在c中分配一個結構數組?

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

我正在編寫一個將項目添加到購物清單的函數。 我了解如何限制購物清單的大小,但我想做的是我想通過使用malloc ().

我想擺脫大小並在數據進入時存儲數據。因此,如果有 15 個數據字段進入,我想存儲 15 個而不是特定的大小。 我假設我不知道有多少數據進入我的程序。

我還是個初學者,所以如果我以錯誤的方式提出問題,請告訴我。

提前謝謝你,我很欣賞你的意見。

#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;
}

我會使用靈活的數組成員。

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;
}

但就個人而言,我寧願使用鏈表而不是數組來完成這項任務。

有幾種方法可以實現您的目標:

  • 第一個不涉及任何機器,而是malloc(3)使用,並且僅在您事先知道時使用(提前分配,但在運行時,這是,不是在程序啟動時,而是對於數組的這種用法)
    struct the_thing {
         /* .... */
    };

    ...

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

這將返回一個元素乘積大小的內存塊( array_of_things[0]這次不是真正的表達式,而是運算符sizeof的參數)計算所用表達式的評估類型的大小,並且不評估它---這很重要,因為你還不能評估那個表達式,因為指針還沒有指向分配的內存---你要使用的元素數量)這會轉換你的指針(以及使用[index]表示法引起的指針算術)轉換為可用數組(實際上它是指向元素數組的指針。重要的是要說數組未初始化並且數據內容可能很亂,你必須將元素一個一個地初始化為所需的值。如果你想要一個初始化的數組,你可以使用專門定義的calloc(3)來分配事物的數組。

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

看一個細節,這次我們使用逗號將兩個量指定為calloc()的參數,而不是傳遞兩者的乘積。 結果是calloc(3)返回一個已初始化的數組(所有內容都初始化為零),並且您分別指定元素的數量和一個元素的大小。 此調用特定於(但不是強制特定的)創建動態數組。 這兩個調用分別將要分配的內存塊的總大小、元素數量和元素大小作為參數。

  • 當您需要使數組隨着需要越來越多的元素而動態增長時,就會出現第二種方式。 這可以用前面的函數解決,但是有一個函數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]));

有了這個, array_of_things已經傳遞給沒有元素(因為它被分配了NULL )來分配n1元素。 正常工作允許你使用這個數組,直到你達到它的極限,現在事情變得更有趣了:

    /* 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]));

此調用(使用已經有效的指針)將嘗試查看分配是否可以就地解決,但如果不能,將分配一個新數組,該數組具有足夠的空間來容納n2個元素,之前的n1個元素將被復制到第二個數組,新分配的指針將被返回,前一個將被釋放。

這可能有一個復雜的缺點,就是你不能讓指針指向數組內的元素(或元素字段),因為如果新數組在內存中重新定位,這將使所有這些指針無效(它們仍將指向舊數組地方,現在什么都不存在)但是如果你能處理好,一切都很好。

我寫了一個宏函數來處理這種情況並且它工作正常(除非我有指向數組內部的指針,當我使用宏時我必須重新排列所有這些指針)宏需要一個數組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)

您將數組聲明為:

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

然后,你使用它:

      /* 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 */

其中參數

  • array_of_things是數組名稱。 從宏定義中可以看出, _cap后綴變量和_len后綴變量都是從這里使用的名字派生而來的,所以名字是綁在一起的(這樣代碼不容易出錯)
  • struct the_thing是數組元素的類型。 您會看到它僅用於將返回的指針從realloc()轉換為正確的類型(這在 C 中通常是不鼓勵的,但對於使用 C++ 測試代碼測試 C 代碼的環境——如 GTest/GMock --- 這是必需的,以避免 C++ 編譯器產生的錯誤)因此,雖然這種轉換不僅在 C 中是不必要的(甚至使用起來很危險),但在 C++ 中是被禁止的,並且允許與 C++ 兼容(並且是一部分為方便起見,提供了經過測試的宏)。
  • 1是所需元素的數量。 假設您調用此宏以向其中添加一定數量的元素(通常是一個),一次拍攝,這是您要添加的此類元素的數量。
  • 10是在需要增長的情況下要增長的元素數量。 盡管你需要增加元素的數量,分配更大的數量通常更有效,所以不是每次我們需要一個元素時我們只分配一個,而是分配一堆,所以對realloc()的總調用次數減少了,增加了整體代碼的效率。

在你的情況下,例如:

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 */
}

當然,完成購物清單后,您需要將所有三個全局變量設置為NULL00

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

注意:我故意沒有進行錯誤檢查,因為在你的家庭作業中不清楚如何處理你從realloc()得到NULL的偶然(和罕見)情況。 這種情況有點麻煩,因為它要求您在調用realloc()之前制作指針的第二個副本(因為存儲在shopping_list中的值將通過將NULL分配給指針變量來銷毀,以防realloc()失敗)

通常,內存分配失敗意味着你在某處有一些內存泄漏,如:

  • 今天的系統有足夠的虛擬內存可分配給每個進程(這意味着物理內存量加上交換空間),因此您不可能在單個進程中分配機器的全部內存量。
  • 如果您需要限制進程的數據內存量,假設您知道如何處理內存分配錯誤來為這種情況編寫有效的解決方案,只需復制指針,運行上面的代碼並如果您得到NULL返回值,則通過將副本恢復到數組指針中來繼續(如果您仍然調用)而不進行分配(例如嘗試較低的增量,等待一段時間以便我們有更多內存等)。
  • 如果你運行在一個內存較小的嵌入式系統中,很多時候動態分配內存是不希望的,因為幾乎沒有堆空間來進行動態內存分配。 然后你必須預先分配靜態數組並以這種方式運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM