C mảng tăng trưởng động


126

Tôi có một chương trình đọc danh sách các thực thể trong trò chơi "thô" và tôi dự định tạo một mảng chứa số chỉ mục (int) của một số lượng thực thể không xác định, để xử lý nhiều thứ khác nhau. Tôi muốn tránh sử dụng quá nhiều bộ nhớ hoặc CPU để giữ các chỉ mục như vậy ...

Một giải pháp nhanh và bẩn mà tôi sử dụng cho đến nay là khai báo, trong hàm xử lý chính (tiêu điểm cục bộ) mảng có kích thước của các thực thể trò chơi tối đa và một số nguyên khác để theo dõi số lượng đã được thêm vào danh sách. Điều này không thỏa đáng, vì mỗi danh sách chứa hơn 3000 mảng, không nhiều, nhưng cảm thấy lãng phí, vì tôi có thể sử dụng giải pháp cho 6-7 danh sách cho các chức năng khác nhau.

Tôi chưa tìm thấy bất kỳ giải pháp cụ thể C (không phải C ++ hoặc C #) nào để đạt được điều này. Tôi có thể sử dụng con trỏ, nhưng tôi hơi sợ sử dụng chúng (trừ khi đó là cách khả thi duy nhất).

Các mảng không rời khỏi phạm vi chức năng cục bộ (chúng sẽ được chuyển đến một chức năng, sau đó loại bỏ), trong trường hợp thay đổi mọi thứ.

Nếu con trỏ là giải pháp duy nhất, làm thế nào tôi có thể theo dõi chúng để tránh rò rỉ?


1
Đây là một vấn đề (rất, rất nhỏ) trong C, nhưng làm thế nào bạn bỏ lỡ tất cả các giải pháp C ++ và C # cho việc này?
Ignacio Vazquez-Abrams

11
"Nếu con trỏ là giải pháp duy nhất, làm thế nào tôi có thể theo dõi chúng để tránh rò rỉ?" Chăm sóc, chú ý và valgrind. Đây chính xác là lý do tại sao mọi người rất sợ nếu C ở nơi đầu tiên.
Chris Lutz

27
Bạn không thể sử dụng C hiệu quả mà không sử dụng con trỏ. Đừng sợ.
qrdl

không có
lib

6
Sử dụng C mà không có con trỏ cũng giống như sử dụng xe không cần nhiên liệu.
martinkunev

Câu trả lời:


210

Tôi có thể sử dụng con trỏ, nhưng tôi hơi sợ sử dụng chúng.

Nếu bạn cần một mảng động, bạn không thể thoát con trỏ. Tại sao bạn sợ? Họ sẽ không cắn (miễn là bạn cẩn thận, đó là). Không có mảng động tích hợp trong C, bạn sẽ phải tự viết một cái. Trong C ++, bạn có thể sử dụng std::vectorlớp tích hợp. C # và mọi ngôn ngữ cấp cao khác cũng có một số lớp tương tự quản lý các mảng động cho bạn.

Nếu bạn có kế hoạch tự viết, đây là thứ giúp bạn bắt đầu: hầu hết các triển khai mảng động đều hoạt động bằng cách bắt đầu với một mảng có kích thước mặc định (nhỏ), sau đó bất cứ khi nào bạn hết dung lượng khi thêm phần tử mới, hãy nhân đôi kích thước của mảng. Như bạn có thể thấy trong ví dụ dưới đây, điều đó không khó lắm: (Tôi đã bỏ qua kiểm tra an toàn cho sự ngắn gọn)

typedef struct {
  int *array;
  size_t used;
  size_t size;
} Array;

void initArray(Array *a, size_t initialSize) {
  a->array = malloc(initialSize * sizeof(int));
  a->used = 0;
  a->size = initialSize;
}

void insertArray(Array *a, int element) {
  // a->used is the number of used entries, because a->array[a->used++] updates a->used only *after* the array has been accessed.
  // Therefore a->used can go up to a->size 
  if (a->used == a->size) {
    a->size *= 2;
    a->array = realloc(a->array, a->size * sizeof(int));
  }
  a->array[a->used++] = element;
}

void freeArray(Array *a) {
  free(a->array);
  a->array = NULL;
  a->used = a->size = 0;
}

Sử dụng nó rất đơn giản:

Array a;
int i;

initArray(&a, 5);  // initially 5 elements
for (i = 0; i < 100; i++)
  insertArray(&a, i);  // automatically resizes as necessary
printf("%d\n", a.array[9]);  // print 10th element
printf("%d\n", a.used);  // print number of elements
freeArray(&a);

1
Cảm ơn mã mẫu. Tôi đã triển khai chức năng cụ thể bằng cách sử dụng một mảng lớn, nhưng sẽ triển khai các thứ tương tự khác bằng cách sử dụng chức năng này và sau khi tôi kiểm soát được nó, hãy thay đổi các chức năng khác trở lại :)
Balkania

2
Cảm ơn rất nhiều cho mã. Một removeArrayphương pháp loại bỏ yếu tố cuối cùng cũng sẽ gọn gàng. Nếu bạn cho phép nó, tôi sẽ thêm nó vào mẫu mã của bạn.
brimborium

5
% d và size_t ... bit không có ở đó. Nếu bạn sử dụng C99 trở lên, có thể tận dụng việc thêm% z
Randy Howard vào

13
Không bao giờ bỏ qua kiểm tra an toàn với phân bổ bộ nhớ và phân bổ lại.
Alex Reynold

3
Đó là một sự đánh đổi hiệu suất. Nếu bạn nhân đôi mỗi lần, thì đôi khi bạn có 100% chi phí và trung bình 50%. 3/2 cung cấp cho bạn 50% tồi tệ nhất và 25% điển hình. Nó cũng gần với cơ sở hiệu quả của chuỗi Fibionacci trong giới hạn (phi) thường được ca ngợi và sử dụng cho các đặc tính "theo cấp số nhân nhưng ít bạo lực hơn so với cơ sở 2", nhưng dễ tính toán hơn. +8 có nghĩa là các mảng nhỏ một cách hợp lý không kết thúc quá nhiều bản sao. Nó thêm một thuật ngữ nhân cho phép mảng phát triển nhanh nếu kích thước của nó không liên quan. Trong chuyên gia sử dụng điều này nên được điều chỉnh.
Dan Sheppard

10

Có một vài lựa chọn tôi có thể nghĩ ra.

  1. Danh sách liên kết. Bạn có thể sử dụng một danh sách được liên kết để tạo ra một mảng tăng trưởng linh hoạt như điều. Nhưng bạn sẽ không thể làm array[100]mà không cần phải đi qua 1-99trước. Và nó có thể không hữu ích cho bạn để sử dụng.
  2. Mảng lớn. Đơn giản chỉ cần tạo một mảng với không gian đủ cho tất cả mọi thứ
  3. Thay đổi kích thước mảng. Tái tạo mảng một khi bạn biết kích thước và / hoặc tạo một mảng mới mỗi khi bạn hết dung lượng với một số lề và sao chép tất cả dữ liệu sang mảng mới.
  4. Danh sách liên kết kết hợp mảng. Đơn giản chỉ cần sử dụng một mảng với kích thước cố định và một khi bạn hết dung lượng, hãy tạo một mảng mới và liên kết đến đó (sẽ là khôn ngoan khi theo dõi mảng và liên kết đến mảng tiếp theo trong một cấu trúc).

Thật khó để nói lựa chọn nào sẽ là tốt nhất trong tình huống của bạn. Đơn giản chỉ cần tạo một mảng lớn là một trong những giải pháp đơn giản nhất và không nên cung cấp cho bạn nhiều vấn đề trừ khi nó thực sự lớn.


Làm thế nào để bảy mảng của 3264 số nguyên âm thanh cho một trò chơi 2D hiện đại? Nếu tôi chỉ bị hoang tưởng, giải pháp sẽ là mảng lớn.
Balkania

3
Cả # 1 và # 4 ở đây đều yêu cầu sử dụng con trỏ và cấp phát bộ nhớ động. Tôi đề nghị sử dụng reallocvới số 3 - phân bổ mảng có kích thước bình thường, và sau đó phát triển nó bất cứ khi nào bạn hết. reallocsẽ xử lý sao chép dữ liệu của bạn nếu cần thiết. Đối với câu hỏi của OP về quản lý bộ nhớ, bạn chỉ cần mallocmột lần khi bắt đầu, freemột lần ở cuối và reallocmỗi lần bạn hết phòng. Nó không quá tệ.
Borealid

1
@Balkania: bảy mảng gồm 3264 số nguyên là một sợi tóc dưới 100KB. Đó không phải là rất nhiều bộ nhớ.
Borealid

1
@Balkania: 7 * 3264 * 32 bitnghe như 91.39 kilobytes. Không nhiều bằng bất kỳ tiêu chuẩn nào trong những ngày này;)
Wolph

1
Thiếu sót đặc biệt này là một sự xấu hổ, bởi vì nó không hoàn toàn rõ ràng những gì sẽ xảy ra khi realloctrở về NULL: a->array = (int *)realloc(a->array, a->size * sizeof(int));... Có lẽ nó sẽ được viết tốt nhất là: int *temp = realloc(a->array, a->size * sizeof *a->array); a->array = temp;... Theo cách đó, rõ ràng là bất cứ điều gì xảy ra cần phải xảy ra trước đó các NULLgiá trị được gán cho a->array(nếu nó là tất cả).
tự kỷ

10

Như với mọi thứ ban đầu có vẻ đáng sợ hơn so với sau này, cách tốt nhất để vượt qua nỗi sợ hãi ban đầu là đắm mình vào sự khó chịu của những điều chưa biết ! Rốt cuộc, đó là lúc chúng ta học hỏi nhiều nhất.

Thật không may, có những hạn chế. Trong khi bạn vẫn đang học cách sử dụng một chức năng, chẳng hạn, bạn không nên đảm nhận vai trò của một giáo viên. Tôi thường đọc câu trả lời từ những người dường như không biết cách sử dụng realloc(nghĩa là câu trả lời hiện đang được chấp nhận! ) Nói cho người khác biết cách sử dụng không chính xác, đôi khi theo giả định rằng họ đã bỏ qua việc xử lý lỗi , mặc dù đây là một lỗi phổ biến trong đó cần đề cập. Đây là một câu trả lời giải thích làm thế nào để sử dụng reallocchính xác . Hãy lưu ý rằng câu trả lời là lưu trữ giá trị trả về vào một biến khác để thực hiện kiểm tra lỗi.

Mỗi khi bạn gọi một hàm và mỗi khi bạn sử dụng một mảng, bạn đang sử dụng một con trỏ. Các chuyển đổi đang diễn ra ngầm, nếu có bất cứ điều gì thậm chí còn đáng sợ hơn, vì đó là những điều chúng ta không thấy thường gây ra nhiều vấn đề nhất. Ví dụ, rò rỉ bộ nhớ ...

Toán tử mảng là toán tử con trỏ. array[x]thực sự là một lối tắt cho *(array + x), có thể được chia thành: *(array + x). Rất có thể đó *là điều khiến bạn bối rối. Chúng tôi cũng có thể loại bỏ việc bổ sung từ các vấn đề bằng cách giả sử x0, do đó, array[0]trở thành *arrayvì thêm 0sẽ không thay đổi giá trị ...

... và do đó chúng ta có thể thấy điều đó *arraytương đương với array[0]. Bạn có thể sử dụng một nơi mà bạn muốn sử dụng cái khác và ngược lại. Toán tử mảng là toán tử con trỏ.

malloc, reallocVà bạn bè không phát minh ra khái niệm về một con trỏ mà bạn đã sử dụng tất cả cùng; họ chỉ sử dụng điều này để thực hiện một số tính năng khác, đây là một dạng thời lượng lưu trữ khác, phù hợp nhất khi bạn muốn thay đổi mạnh mẽ, năng động về kích thước .

Thật xấu hổ khi câu trả lời hiện đang được chấp nhận cũng đi ngược lại với một số lời khuyên rất có căn cứ khác về StackOverflow , đồng thời, bỏ lỡ một cơ hội để giới thiệu một tính năng ít được biết đến tỏa sáng cho chính xác usecase này: mảng linh hoạt các thành viên! Đó thực sự là một câu trả lời khá bị hỏng ... :(

Khi bạn xác định struct, khai báo mảng của bạn ở cuối cấu trúc, không có giới hạn trên. Ví dụ:

struct int_list {
    size_t size;
    int value[];
};

Điều này sẽ cho phép bạn hợp nhất mảng của bạn intthành phân bổ giống như của bạn countvà việc ràng buộc chúng như thế này có thể rất tiện lợi !

sizeof (struct int_list)sẽ hoạt động như thể valuecó kích thước bằng 0, vì vậy nó sẽ cho bạn biết kích thước của cấu trúc với một danh sách trống . Bạn vẫn cần thêm vào kích thước được truyền reallocđể xác định kích thước danh sách của bạn.

Một mẹo hữu ích khác là hãy nhớ rằng realloc(NULL, x)nó tương đương với malloc(x)và chúng ta có thể sử dụng điều này để đơn giản hóa mã của mình. Ví dụ:

int push_back(struct int_list **fubar, int value) {
    size_t x = *fubar ? fubar[0]->size : 0
         , y = x + 1;

    if ((x & y) == 0) {
        void *temp = realloc(*fubar, sizeof **fubar
                                   + (x + y) * sizeof fubar[0]->value[0]);
        if (!temp) { return 1; }
        *fubar = temp; // or, if you like, `fubar[0] = temp;`
    }

    fubar[0]->value[x] = value;
    fubar[0]->size = y;
    return 0;
}

struct int_list *array = NULL;

Lý do tôi chọn sử dụng struct int_list **làm đối số thứ nhất có vẻ không rõ ràng ngay lập tức, nhưng nếu bạn nghĩ về đối số thứ hai, mọi thay đổi được thực hiện valuetừ bên trong push_backsẽ không hiển thị đối với chức năng mà chúng ta đang gọi, phải không? Điều tương tự cũng xảy ra với đối số đầu tiên và chúng ta cần có thể sửa đổi array, không chỉ ở đâycòn ở bất kỳ chức năng nào khác mà chúng ta chuyển nó sang ...

arraybắt đầu chỉ vào không có gì; nó là một danh sách trống Khởi tạo nó cũng giống như thêm vào nó. Ví dụ:

struct int_list *array = NULL;
if (!push_back(&array, 42)) {
    // success!
}

PS Hãy nhớfree(array); khi bạn hoàn thành nó!


" array[x]Thực sự là một lối tắt cho *(array + x), [...]" Bạn có chắc về điều đó không ???? Xem phần trình bày về các hành vi khác nhau của họ: eli.thegreenplace.net/2009/10/21/ trên .
C-Star-W-Star

1
Than ôi, @ C-Star-Puppy, tài liệu tham khảo mà tài nguyên của bạn dường như không đề cập đến là tiêu chuẩn C. Đó là đặc điểm kỹ thuật mà trình biên dịch của bạn phải tuân thủ một cách hợp pháp tự gọi mình là trình biên dịch C. Tài nguyên của bạn dường như không mâu thuẫn với thông tin của tôi. Tuy nhiên, tiêu chuẩn thực sự có một số ví dụ như viên ngọc này mà nó tiết lộ rằng array[index]thực sự là ptr[index]trong ngụy trang ... "Định nghĩa của các nhà điều hành subscript []được rằng E1[E2]giống hệt (*((E1)+(E2)))" Bạn không thể bác bỏ std
mắc chứng tự kỷ

Hãy thử trình diễn này, @ C-Star-Puppy: int main(void) { unsigned char lower[] = "abcdefghijklmnopqrstuvwxyz"; for (size_t x = 0; x < sizeof lower - 1; x++) { putchar(x[lower]); } }... Có lẽ bạn sẽ cần #include <stdio.h><stddef.h>... Bạn có thấy cách tôi viết x[lower](với xkiểu số nguyên) chứ không phải là lower[x]? Trình biên dịch C không quan tâm, bởi vì *(lower + x)có cùng giá trị với *(x + lower), và lower[x]là giá trị trước đó giống như x[lower]sau. Tất cả các biểu thức này là tương đương. Hãy thử chúng ... hãy tự mình xem, nếu bạn không thể tin lời tôi ...
tự kỷ

... và sau đó tất nhiên là có phần này, tôi đã nhấn mạnh vào đó, nhưng bạn thực sự nên đọc toàn bộ trích dẫn mà không nhấn mạnh: "Ngoại trừ khi đó là toán hạng của toán tử sizeof, toán tử _Alignof hoặc unary & toán tử, hoặc là một chuỗi ký tự được sử dụng để khởi tạo một mảng, một biểu thức có kiểu '' mảng kiểu '' được chuyển đổi thành một biểu thức với kiểu '' con trỏ để gõ '' trỏ đến phần tử ban đầu của mảng đối tượng và không phải là một giá trị . Nếu đối tượng mảng có lớp lưu trữ đăng ký, hành vi không được xác định. " Điều này cũng đúng với các hàm, btw.
tự kỷ

Ồ và trên một lưu ý cuối cùng, @ C-Star-Puppy, Microsoft C ++ không phải là trình biên dịch C và đã là một trong gần 20 năm. Bạn có thể kích hoạt chế độ C89, suuuure , nhưng chúng tôi đã phát triển hơn cuối những năm 1980 trong điện toán. Để biết thêm về chủ đề đó, tôi khuyên bạn nên đọc bài viết này ... và sau đó chuyển sang trình biên dịch C thực tế như gcchoặc clangcho tất cả các trình biên dịch C của bạn, bởi vì bạn sẽ thấy có rất nhiều gói đã áp dụng các tính năng C99 ...
tự kỷ

3

Dựa trên thiết kế của Matteo Furlans , khi ông nói " hầu hết các triển khai mảng động hoạt động bằng cách bắt đầu với một mảng có kích thước mặc định (nhỏ), sau đó bất cứ khi nào bạn hết dung lượng khi thêm một phần tử mới, hãy tăng gấp đôi kích thước của mảng ". Sự khác biệt trong " công việc đang tiến triển " dưới đây là nó không tăng gấp đôi kích thước, nó chỉ nhằm mục đích sử dụng những gì được yêu cầu. Tôi cũng đã bỏ qua các kiểm tra an toàn vì đơn giản ... Ngoài ra, dựa trên ý tưởng của brimboriums , tôi đã cố gắng thêm chức năng xóa vào mã ...

Tệp Storage.h trông như thế này ...

#ifndef STORAGE_H
#define STORAGE_H

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct 
    {
        int *array;
        size_t size;
    } Array;

    void Array_Init(Array *array);
    void Array_Add(Array *array, int item);
    void Array_Delete(Array *array, int index);
    void Array_Free(Array *array);

#ifdef __cplusplus
}
#endif

#endif /* STORAGE_H */

Tệp Storage.c trông như thế này ...

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

/* Initialise an empty array */
void Array_Init(Array *array) 
{
    int *int_pointer;

    int_pointer = (int *)malloc(sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to allocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer; 
        array->size = 0;
    }
}

/* Dynamically add to end of an array */
void Array_Add(Array *array, int item) 
{
    int *int_pointer;

    array->size += 1;

    int_pointer = (int *)realloc(array->array, array->size * sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to reallocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer;
        array->array[array->size-1] = item;
    }
}

/* Delete from a dynamic array */
void Array_Delete(Array *array, int index) 
{
    int i;
    Array temp;
    int *int_pointer;

    Array_Init(&temp);

    for(i=index; i<array->size; i++)
    {
        array->array[i] = array->array[i + 1];
    }

    array->size -= 1;

    for (i = 0; i < array->size; i++)
    {
        Array_Add(&temp, array->array[i]);
    }

    int_pointer = (int *)realloc(temp.array, temp.size * sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to reallocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer; 
    } 
}

/* Free an array */
void Array_Free(Array *array) 
{
  free(array->array);
  array->array = NULL;
  array->size = 0;  
}

Main.c trông như thế này ...

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

int main(int argc, char** argv) 
{
    Array pointers;
    int i;

    Array_Init(&pointers);

    for (i = 0; i < 60; i++)
    {
        Array_Add(&pointers, i);        
    }

    Array_Delete(&pointers, 3);

    Array_Delete(&pointers, 6);

    Array_Delete(&pointers, 30);

    for (i = 0; i < pointers.size; i++)
    {        
        printf("Value: %d Size:%d \n", pointers.array[i], pointers.size);
    }

    Array_Free(&pointers);

    return (EXIT_SUCCESS);
}

Mong chờ những lời chỉ trích mang tính xây dựng để làm theo ...


1
Nếu đó là lời chỉ trích mang tính xây dựng mà bạn tìm kiếm, tốt hơn là đăng lên tại Đánh giá mã . Điều đó nói rằng, một vài gợi ý: điều bắt buộc là mã kiểm tra sự thành công của các cuộc gọi đến malloc()trước khi thử sử dụng phân bổ. Trong cùng một hướng, thật sai lầm khi gán trực tiếp kết quả của realloc()con trỏ vào bộ nhớ ban đầu được phân bổ lại; nếu realloc()thất bại, NULLđược trả về và mã bị rò rỉ bộ nhớ. Việc nhân đôi bộ nhớ khi thay đổi kích thước sẽ hiệu quả hơn nhiều so với việc thêm 1 khoảng trống tại một thời điểm: ít cuộc gọi đến hơn realloc().
ex nihilo

1
Tôi biết rằng tôi sẽ bị xé toạc, tôi chỉ nói đùa khi tôi nói "những lời chỉ trích mang tính xây dựng" ... Cảm ơn vì lời khuyên ...

2
Không cố gắng xé toạc bất cứ ai, chỉ đưa ra một số lời chỉ trích mang tính xây dựng, có thể đã được đưa ra ngay cả khi không có sự nhẹ dạ của bạn gần hơn;)
ex nihilo

1
David, tôi đã suy nghĩ về nhận xét của bạn "Sẽ hiệu quả hơn gấp đôi bộ nhớ khi thay đổi kích thước so với việc thêm 1 khoảng trống tại một thời điểm: ít cuộc gọi đến realloc ()". Bạn có thể giải thích điều đó cho tôi không, tại sao tốt hơn là phân bổ gấp đôi lượng bộ nhớ và có thể không sử dụng nó, do đó gây lãng phí bộ nhớ, hơn là chỉ gán số lượng cần thiết cho nhiệm vụ? Tôi hiểu những gì bạn đang nói về các cuộc gọi đến realloc (), nhưng tại sao lại gọi realloc () mỗi khi có vấn đề? Đó không phải là những gì nó có, để phân bổ lại bộ nhớ?

1
Mặc dù nhân đôi nghiêm ngặt có thể không tối ưu, nhưng chắc chắn tốt hơn là tăng bộ nhớ một byte (hoặc một int, v.v.) tại một thời điểm. Nhân đôi là một giải pháp điển hình, nhưng tôi không nghĩ rằng có bất kỳ giải pháp tối ưu nào phù hợp với mọi hoàn cảnh. Đây là lý do tại sao nhân đôi là một ý tưởng tốt (một số yếu tố khác như 1,5 cũng sẽ tốt): nếu bạn bắt đầu với phân bổ hợp lý, bạn có thể không cần phải phân bổ lại. Khi cần thêm bộ nhớ, phân bổ hợp lý sẽ tăng gấp đôi, v.v. Theo cách này, bạn có thể chỉ cần một hoặc hai cuộc gọi đến realloc().
ex nihilo

2

Khi bạn nói

tạo một mảng chứa số chỉ mục (int) của số lượng thực thể không xác định

về cơ bản bạn đang nói rằng bạn đang sử dụng "con trỏ", nhưng một con trỏ là con trỏ cục bộ toàn mảng thay vì con trỏ trên toàn bộ nhớ. Vì về mặt khái niệm bạn đã sử dụng "con trỏ" (tức là số id liên quan đến một phần tử trong một mảng), tại sao bạn không sử dụng các con trỏ thông thường (tức là số id đề cập đến một phần tử trong mảng lớn nhất: toàn bộ bộ nhớ ).

Thay vì các đối tượng của bạn lưu trữ một số id tài nguyên, bạn có thể làm cho chúng lưu trữ một con trỏ thay thế. Về cơ bản điều tương tự, nhưng hiệu quả hơn nhiều vì chúng ta tránh biến "mảng + chỉ mục" thành "con trỏ".

Con trỏ không đáng sợ nếu bạn nghĩ chúng là chỉ số mảng cho toàn bộ bộ nhớ (đó là những gì chúng thực sự là)


2

Để tạo một mảng các mục không giới hạn thuộc bất kỳ loại nào:

typedef struct STRUCT_SS_VECTOR {
    size_t size;
    void** items;
} ss_vector;


ss_vector* ss_init_vector(size_t item_size) {
    ss_vector* vector;
    vector = malloc(sizeof(ss_vector));
    vector->size = 0;
    vector->items = calloc(0, item_size);

    return vector;
}

void ss_vector_append(ss_vector* vec, void* item) {
    vec->size++;
    vec->items = realloc(vec->items, vec->size * sizeof(item));
    vec->items[vec->size - 1] = item;
};

void ss_vector_free(ss_vector* vec) {
    for (int i = 0; i < vec->size; i++)
        free(vec->items[i]);

    free(vec->items);
    free(vec);
}

và cách sử dụng nó:

// defining some sort of struct, can be anything really
typedef struct APPLE_STRUCT {
    int id;
} apple;

apple* init_apple(int id) {
    apple* a;
    a = malloc(sizeof(apple));
    a-> id = id;
    return a;
};


int main(int argc, char* argv[]) {
    ss_vector* vector = ss_init_vector(sizeof(apple));

    // inserting some items
    for (int i = 0; i < 10; i++)
        ss_vector_append(vector, init_apple(i));


    // dont forget to free it
    ss_vector_free(vector);

    return 0;
}

Vectơ / mảng này có thể chứa bất kỳ loại vật phẩm nào và nó hoàn toàn năng động về kích thước.


0

Chà, tôi đoán nếu bạn cần loại bỏ một phần tử, bạn sẽ tạo một bản sao của mảng coi thường phần tử bị loại trừ.

// inserting some items
void* element_2_remove = getElement2BRemove();

for (int i = 0; i < vector->size; i++){
       if(vector[i]!=element_2_remove) copy2TempVector(vector[i]);
       }

free(vector->items);
free(vector);
fillFromTempVector(vector);
//

Giả sử rằng getElement2BRemove(), copy2TempVector( void* ...)fillFromTempVector(...)là các phương thức phụ trợ để xử lý vectơ tạm thời.


Không rõ đây thực sự là một câu trả lời cho câu hỏi được đặt ra, hoặc nếu nó là một bình luận.

Đó là một ý kiến ​​cho "làm thế nào" và tôi đang yêu cầu xác nhận (Tôi có sai không?) NẾU ai đó có ý tưởng tốt hơn. ;)
JOSMAR BARBOSA - M4NOV3Y

Tôi đoán tôi không hiểu câu cuối cùng của bạn. Vì SO không phải là một diễn đàn theo chủ đề, nên những câu hỏi mở như thế này trong câu trả lời có vẻ kỳ quặc.

1
Tôi đã sửa câu cuối cùng của bạn thành những gì tôi nghĩ bạn muốn nói.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.