我正在编写一个函数,将商品添加到购物列表中。我知道如何限制购物清单的大小,但我想做的是,我想通过使用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("nnWelcome to the shopping list manager!n");
printf("=====================================nn");
printf("1. Add an itemn");
printf("2. Display the shopping listn");
printf("3. Remove an itemn");
printf("4. Change an itemn");
printf("5. Save listn");
printf("6. Load listn");
printf("7. Exitn");
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]
表示法得出的指针算术)转换为可用的数组(事实上,它是一个指向元素数组的指针。重要的是,数组未初始化,数据内容可能是rubish,您必须将元素一个接一个地初始化为所需值。如果您想要一个初始化的数组,您可以使用专为分配数组而定义的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_len
和A_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
后缀变量是从这里使用的名称派生的,因此名称被绑定在一起(这使得代码不太容易出错)- CCD_ 22是数组元素的类型。您将看到,它只用于将返回的指针从
realloc()
强制转换为正确的类型(这在C中通常是不鼓励的,但对于使用C++测试代码测试C代码的环境(如GTest/GLock),这是必需的,以避免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 */
}
当然,当您完成购物列表时,您需要将所有三个全局变量都设置为NULL
、0
和0
。
free(shopping_list);
shopping_list = NULL;
shopping_list_len = shopping_list_cap = 0;
注意:我故意不做错误检查,因为在你的家庭作业中不清楚如何处理偶尔(和罕见)从realloc()
得到NULL
的情况。这种情况有点麻烦,因为它要求您在调用realloc()
之前制作指针的第二个副本(因为存储在shopping_list
中的值将被NULL
分配给指针变量所破坏,以防realloc()
失败)
通常,内存分配失败意味着你在某个地方有内存泄漏,例如:
- 系统现在有足够的虚拟内存可分配给每个进程(这意味着物理内存加上交换空间),因此不太可能在单个进程中分配机器的内存总量
- 如果你需要限制进程的数据内存量,假设你知道如何处理内存分配错误,为这种情况编写一个有效的解决方案,只需复制指针,运行上面的代码,如果你得到
NULL
返回值,然后在没有分配的情况下继续(如果您仍然调用)(例如,尝试较低的增量,等待一段时间,这样我们就有更多的内存,等等),将副本恢复到数组指针中 - 如果您在内存较小的嵌入式系统中运行,那么很多时候都不希望动态分配内存,因为几乎没有堆空间来进行动态内存分配。然后,您必须预先分配静态数组并以这种方式运行