C อาร์เรย์ที่เติบโตแบบไดนามิก


126

ฉันมีโปรแกรมที่อ่านรายการเอนทิตีในเกม "ดิบ" และฉันตั้งใจจะสร้างอาร์เรย์ที่มีหมายเลขดัชนี (int) ของเอนทิตีที่ไม่แน่นอนสำหรับการประมวลผลสิ่งต่างๆ ฉันต้องการหลีกเลี่ยงการใช้หน่วยความจำหรือ CPU มากเกินไปในการรักษาดัชนีดังกล่าว ...

วิธีแก้ปัญหาที่รวดเร็วและสกปรกที่ฉันใช้จนถึงตอนนี้คือการประกาศในฟังก์ชันการประมวลผลหลัก (โฟกัสเฉพาะที่) อาร์เรย์ที่มีขนาดของเอนทิตีเกมสูงสุดและจำนวนเต็มอื่นเพื่อติดตามจำนวนที่ถูกเพิ่มเข้าไปในรายการ สิ่งนี้ไม่น่าพอใจเนื่องจากทุกรายการมีอาร์เรย์มากกว่า 3,000 รายการซึ่งไม่มากนัก แต่รู้สึกว่าเป็นการสิ้นเปลืองเนื่องจากฉันจะใช้วิธีแก้ปัญหาได้ 6-7 รายการสำหรับฟังก์ชันที่แตกต่างกัน

ฉันไม่พบโซลูชันเฉพาะ C (ไม่ใช่ C ++ หรือ C #) เพื่อให้บรรลุสิ่งนี้ ฉันสามารถใช้พอยน์เตอร์ได้ แต่ฉันค่อนข้างกลัวที่จะใช้มัน (เว้นแต่จะเป็นวิธีเดียวที่เป็นไปได้)

อาร์เรย์จะไม่ออกจากขอบเขตฟังก์ชันโลคัล (จะถูกส่งต่อไปยังฟังก์ชันจากนั้นจึงละทิ้ง) ในกรณีที่มีการเปลี่ยนแปลงสิ่งต่างๆ

หากพอยน์เตอร์เป็นทางออกเดียวฉันจะติดตามสิ่งเหล่านี้เพื่อหลีกเลี่ยงการรั่วไหลได้อย่างไร


1
นี่เป็นปัญหา (มากเล็กมาก) ใน C แต่คุณพลาดโซลูชัน C ++ และ C # ทั้งหมดสำหรับสิ่งนี้ได้อย่างไร
Ignacio Vazquez-Abrams

11
"หากพอยน์เตอร์เป็นทางออกเดียวฉันจะติดตามพอยน์เตอร์เพื่อหลีกเลี่ยงการรั่วไหลได้อย่างไร" การดูแลเอาใจใส่และวาลกรินด์ นี่คือสาเหตุที่ผู้คนกลัวมากถ้า C ในตอนแรก
Chris Lutz

27
คุณไม่สามารถใช้ C ได้อย่างมีประสิทธิภาพโดยไม่ต้องใช้พอยน์เตอร์ ไม่ต้องกลัว.
qrdl

ไม่มี libs ขนาดใหญ่เพียงฟังก์ชั่นเดียวสำหรับโครงสร้างเช่นstackoverflow.com/questions/3456446/…
user411313

6
การใช้ C โดยไม่มีพอยน์เตอร์ก็เหมือนกับการใช้รถที่ไม่มีน้ำมันเชื้อเพลิง
martinkunev

คำตอบ:


211

ฉันสามารถใช้พอยน์เตอร์ได้ แต่ฉันค่อนข้างกลัวที่จะใช้พอยน์เตอร์

หากคุณต้องการอาร์เรย์แบบไดนามิกคุณจะไม่สามารถหลีกหนีตัวชี้ได้ ทำไมคุณถึงกลัวล่ะ? พวกมันจะไม่กัด (ตราบใดที่คุณระวังนั่นคือ) ไม่มีอาร์เรย์แบบไดนามิกในตัวใน C คุณจะต้องเขียนด้วยตัวเอง ใน C ++ คุณสามารถใช้std::vectorคลาสในตัว C # และภาษาระดับสูงอื่น ๆ ทุกภาษาก็มีคลาสที่คล้ายกันซึ่งจัดการอาร์เรย์แบบไดนามิกให้คุณ

หากคุณวางแผนที่จะเขียนของคุณเองนี่คือสิ่งที่จะช่วยให้คุณเริ่มต้น: การใช้งานอาร์เรย์แบบไดนามิกส่วนใหญ่จะทำงานโดยเริ่มต้นด้วยอาร์เรย์ของขนาดเริ่มต้น (ขนาดเล็ก) จากนั้นเมื่อใดก็ตามที่คุณไม่มีพื้นที่ว่างเมื่อเพิ่มองค์ประกอบใหม่ให้เพิ่มเป็นสองเท่า ขนาดของอาร์เรย์ ดังที่คุณเห็นในตัวอย่างด้านล่างมันไม่ยากเลย: (ฉันละเว้นการตรวจสอบความปลอดภัยสำหรับความกะทัดรัด)

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

การใช้งานนั้นง่ายมาก:

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
ขอบคุณสำหรับโค้ดตัวอย่าง ฉันใช้ฟังก์ชันเฉพาะโดยใช้อาร์เรย์ขนาดใหญ่ แต่จะใช้สิ่งอื่นที่คล้ายคลึงกันโดยใช้สิ่งนี้และหลังจากที่ฉันได้รับการควบคุมแล้วให้เปลี่ยนฟังก์ชันอื่นกลับ :)
Balkania

2
ขอบคุณมากสำหรับรหัส removeArrayวิธีการที่ได้รับการกำจัดขององค์ประกอบที่ผ่านมานอกจากนี้ยังจะเรียบร้อย หากคุณอนุญาตฉันจะเพิ่มลงในตัวอย่างโค้ดของคุณ
brimborium

5
% d และ size_t ... ไม่มีเลย หากคุณใช้ C99 หรือใหม่กว่าสามารถใช้ประโยชน์จากการเพิ่ม% z
Randy Howard

13
อย่าละเว้นการตรวจสอบความปลอดภัยด้วยการจัดสรรหน่วยความจำและการจัดสรรใหม่
Alex Reynolds

3
มันเป็นการแลกเปลี่ยนประสิทธิภาพ หากคุณเพิ่มเป็นสองเท่าในแต่ละครั้งบางครั้งคุณมีค่าใช้จ่าย 100% และโดยเฉลี่ย 50% 3/2 ให้คุณ 50% แย่ที่สุดและ 25% โดยทั่วไป นอกจากนี้ยังใกล้เคียงกับฐานที่มีประสิทธิผลของลำดับ Fibionacci ในขีด จำกัด (phi) ซึ่งมักได้รับการยกย่องและใช้สำหรับลักษณะ "เลขชี้กำลัง แต่มีความรุนแรงน้อยกว่าฐาน 2" มาก แต่คำนวณได้ง่ายกว่า +8 หมายความว่าอาร์เรย์ที่มีขนาดเล็กพอสมควรจะไม่ต้องทำสำเนามากเกินไป เพิ่มคำที่เป็นตัวคูณเพื่อให้อาร์เรย์เติบโตอย่างรวดเร็วหากขนาดไม่เกี่ยวข้อง ในการใช้ผู้เชี่ยวชาญควรปรับแต่งได้
Dan Sheppard

11

เช่นเดียวกับทุกสิ่งที่ดูน่ากลัวกว่าในตอนแรกวิธีที่ดีที่สุดในการเอาชนะความกลัวครั้งแรกคือการจมดิ่งลงไปในความรู้สึกไม่สบายตัวจากสิ่งที่ไม่รู้จัก ! บางครั้งก็เป็นเช่นนั้นที่เราเรียนรู้มากที่สุดหลังจากนั้น

น่าเสียดายที่มีข้อ จำกัด ในขณะที่คุณยังเรียนรู้ที่จะใช้ฟังก์ชันคุณไม่ควรสวมบทบาทเป็นครูเช่น ฉันมักจะอ่านคำตอบจากผู้ที่ดูเหมือนไม่รู้วิธีใช้realloc(เช่นคำตอบที่ยอมรับในปัจจุบัน! ) บอกคนอื่นว่าใช้ไม่ถูกต้องในบางครั้งภายใต้หน้ากากว่าพวกเขาละเว้นการจัดการข้อผิดพลาดแม้ว่านี่จะเป็นข้อผิดพลาดทั่วไปก็ตาม ที่ต้องพูดถึง นี่คือคำตอบที่อธิบายวิธีการใช้reallocอย่างถูกต้อง จะทราบว่าคำตอบคือการจัดเก็บค่าตอบแทนเป็นที่แตกต่างกันตัวแปรเพื่อดำเนินการตรวจสอบข้อผิดพลาด

ทุกครั้งที่คุณเรียกใช้ฟังก์ชันและทุกครั้งที่คุณใช้อาร์เรย์คุณจะใช้ตัวชี้ การแปลงกำลังเกิดขึ้นโดยปริยายซึ่งหากมีสิ่งใดน่าจะน่ากลัวกว่านี้เนื่องจากเป็นสิ่งที่เราไม่เห็นซึ่งมักทำให้เกิดปัญหามากที่สุด ตัวอย่างเช่นความจำรั่ว ...

ตัวดำเนินการอาร์เรย์เป็นตัวดำเนินการตัวชี้ array[x]เป็นทางลัดสำหรับ*(array + x)ซึ่งสามารถแบ่งออกเป็น: *และ(array + x). เป็นไปได้มากว่านั่น*คือสิ่งที่ทำให้คุณสับสน เราสามารถกำจัดส่วนที่เพิ่มออกจากปัญหาได้มากขึ้นโดยสมมติว่าxเป็น0ดังนั้นจึงarray[0]กลายเป็น*arrayเพราะการเพิ่ม0จะไม่เปลี่ยนค่า ...

... และทำให้เราสามารถเห็นได้ว่าเทียบเท่ากับ*array array[0]คุณสามารถใช้อันที่คุณต้องการใช้อีกอันและในทางกลับกัน ตัวดำเนินการอาร์เรย์เป็นตัวดำเนินการตัวชี้

malloc, reallocและเพื่อน ๆ ไม่ได้คิดค้นแนวคิดของตัวชี้ที่คุณเคยใช้มาตลอดนั้น พวกเขาเพียงใช้นี้ในการดำเนินการบางส่วนคุณสมบัติอื่น ๆ ซึ่งเป็นรูปแบบที่แตกต่างกันของระยะเวลาการจัดเก็บที่เหมาะสมที่สุดเมื่อคุณต้องการที่รุนแรงเปลี่ยนแปลงแบบไดนามิกในขนาด

เป็นเรื่องน่าเสียดายที่คำตอบที่ได้รับการยอมรับในปัจจุบันยังขัดต่อคำแนะนำอื่น ๆ ที่เป็นที่ยอมรับอย่างดีเกี่ยวกับ StackOverflowและในขณะเดียวกันก็พลาดโอกาสที่จะนำเสนอคุณลักษณะที่ไม่ค่อยมีใครรู้จักซึ่งส่องแสงสำหรับกรณีนี้: อาร์เรย์แบบยืดหยุ่น สมาชิก! นั่นเป็นคำตอบที่ค่อนข้างแย่ ... :(

เมื่อคุณกำหนดของคุณstructให้ประกาศอาร์เรย์ของคุณที่ส่วนท้ายของโครงสร้างโดยไม่มีขอบเขตบน ตัวอย่างเช่น:

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

สิ่งนี้จะช่วยให้คุณสามารถรวมอาร์เรย์ของคุณเข้าด้วยกันintในการจัดสรรเดียวกันกับของคุณcountและการมีขอบเขตเช่นนี้จะมีประโยชน์มาก !

sizeof (struct int_list)จะทำหน้าที่เป็นแม้ว่าจะvalueมีขนาด 0, ดังนั้นมันจะบอกคุณขนาดของโครงสร้างที่มีรายการที่ว่างเปล่า คุณยังต้องเพิ่มขนาดที่ส่งถึงreallocเพื่อระบุขนาดรายการของคุณ

เคล็ดลับที่มีประโยชน์อีกประการหนึ่งคือการจำไว้ว่าrealloc(NULL, x)เทียบเท่าmalloc(x)และเราสามารถใช้สิ่งนี้เพื่อทำให้รหัสของเราง่ายขึ้น ตัวอย่างเช่น:

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;

เหตุผลที่ฉันเลือกใช้struct int_list **เป็นอาร์กิวเมนต์แรกอาจดูเหมือนไม่ชัดเจนในทันที แต่ถ้าคุณคิดถึงอาร์กิวเมนต์ที่สองการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นvalueจากภายในpush_backจะไม่สามารถมองเห็นได้จากฟังก์ชันที่เราเรียกใช้ใช่ไหม เช่นเดียวกันกับอาร์กิวเมนต์แรกและเราจำเป็นต้องสามารถปรับเปลี่ยนของเราarrayได้ไม่ใช่แค่ที่นี่แต่อาจเป็นไปได้ในฟังก์ชันอื่น ๆ ที่เราส่งต่อให้ ...

arrayเริ่มจากการชี้ไปที่ความว่างเปล่า มันเป็นรายการว่างเปล่า การเริ่มต้นจะเหมือนกับการเพิ่มเข้าไป ตัวอย่างเช่น:

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

ปล. อย่าลืมว่าfree(array);เมื่อคุณทำเสร็จแล้ว!


" array[x]เป็นทางลัดสำหรับ*(array + x)[... ]" คุณแน่ใจหรือไม่ ???? ดูการแสดงพฤติกรรมที่แตกต่างกันของพวกเขา: eli.thegreenplace.net/2009/10/21/… .
C-Star-W-Star

1
อนิจจา @ C-Star-Puppy ผู้อ้างอิงทรัพยากรของคุณดูเหมือนจะไม่ได้กล่าวถึงเลยคือมาตรฐาน C นั่นคือข้อกำหนดที่คอมไพเลอร์ของคุณต้องปฏิบัติตามกฎหมายเรียกตัวเองว่าคอมไพเลอร์ C ทรัพยากรของคุณดูเหมือนจะไม่ขัดแย้งกับข้อมูลของฉันเลย อย่างไรก็ตามมาตรฐานมีตัวอย่างเช่นอัญมณีนี้ซึ่งถูกเปิดเผยว่าปลอมตัวarray[index]จริงptr[index]... "คำจำกัดความของตัวดำเนินการตัวห้อย[]นั้นE1[E2]เหมือนกันกับ(*((E1)+(E2)))"คุณไม่สามารถหักล้างมาตรฐานได้
ออทิสติก

ลองใช้การสาธิตนี้ @ C-Star-Puppy: int main(void) { unsigned char lower[] = "abcdefghijklmnopqrstuvwxyz"; for (size_t x = 0; x < sizeof lower - 1; x++) { putchar(x[lower]); } }... คุณอาจจะต้อง#include <stdio.h>และ<stddef.h>... คุณเห็นไหมว่าฉันเขียนอย่างไรx[lower](โดยxเป็นประเภทจำนวนเต็ม) แทนที่จะเป็นlower[x]? คอมไพเลอร์ C ไม่สนใจเพราะ*(lower + x)เป็นค่าเดียวกับ*(x + lower)และlower[x]เป็นค่าเดิมโดยที่ - เป็นx[lower]ค่าหลัง นิพจน์ทั้งหมดนี้เทียบเท่ากัน ลองดู ... ดูด้วยตัวคุณเองถ้าคุณไม่สามารถรับคำของฉันได้ ...
ออทิสติก

... และแน่นอนว่ามีส่วนนี้ซึ่งฉันให้ความสำคัญกับตัวเอง แต่คุณควรอ่านคำพูดทั้งหมดโดยไม่เน้น: "ยกเว้นเมื่อเป็นตัวถูกดำเนินการของตัวดำเนินการ sizeof ตัวดำเนินการ _Alignof หรือ ยูนารี & ตัวดำเนินการหรือเป็นสตริงลิเทอรัลที่ใช้ในการเริ่มต้นอาร์เรย์นิพจน์ที่มีประเภท '' array of type '' จะถูกแปลงเป็นนิพจน์ที่มี type '' pointer to type '' ที่ชี้ไปยังองค์ประกอบเริ่มต้นของอาร์เรย์ อ็อบเจ็กต์และไม่ใช่ lvalueหากอ็อบเจ็กต์อาร์เรย์มีรีจิสเตอร์คลาสหน่วยเก็บข้อมูลพฤติกรรมจะไม่ถูกกำหนด " เช่นเดียวกับฟังก์ชัน btw
ออทิสติก

โอ้และในบันทึกสุดท้าย @ C-Star-Puppy Microsoft C ++ ไม่ใช่คอมไพเลอร์ C และไม่ได้เป็นหนึ่งในเกือบ 20 ปี คุณสามารถเปิดใช้งานโหมด C89, suuuureแต่เราได้พัฒนาไปไกลกว่าช่วงปลายทศวรรษ 1980 ในการคำนวณ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนั้นฉันขอแนะนำให้อ่านบทความนี้ ... จากนั้นเปลี่ยนไปใช้คอมไพเลอร์ C จริงเช่นgccหรือclangสำหรับการคอมไพล์ C ทั้งหมดของคุณเพราะคุณจะพบว่ามีแพ็คเกจมากมายที่ใช้คุณสมบัติ C99 ...
ออทิสติก

10

มีสองตัวเลือกที่ฉันคิดได้

  1. รายการที่เชื่อมโยง คุณสามารถใช้รายการที่เชื่อมโยงเพื่อสร้างอาร์เรย์ที่เติบโตแบบไดนามิกเช่นสิ่งของ แต่คุณจะไม่สามารถทำได้array[100]โดยไม่ต้องเดินผ่าน1-99ก่อน และมันอาจไม่สะดวกสำหรับคุณที่จะใช้เช่นกัน
  2. อาร์เรย์ขนาดใหญ่ เพียงสร้างอาร์เรย์ที่มีพื้นที่เพียงพอสำหรับทุกสิ่ง
  3. การปรับขนาดอาร์เรย์ สร้างอาร์เรย์ใหม่เมื่อคุณทราบขนาดและ / หรือสร้างอาร์เรย์ใหม่ทุกครั้งที่คุณไม่มีพื้นที่ว่างโดยมีระยะขอบบางส่วนและคัดลอกข้อมูลทั้งหมดไปยังอาร์เรย์ใหม่
  4. การรวมรายการอาร์เรย์ที่เชื่อมโยง เพียงใช้อาร์เรย์ที่มีขนาดคงที่และเมื่อคุณหมดพื้นที่ให้สร้างอาร์เรย์ใหม่และเชื่อมโยงไปยังสิ่งนั้น (ควรติดตามอาร์เรย์และลิงก์ไปยังอาร์เรย์ถัดไปในโครงสร้าง)

ยากที่จะบอกว่าตัวเลือกใดดีที่สุดในสถานการณ์ของคุณ เพียงแค่สร้างอาร์เรย์ขนาดใหญ่ก็ถือเป็นวิธีแก้ปัญหาที่ง่ายที่สุดวิธีหนึ่งและไม่ควรให้ปัญหามากนักเว้นแต่จะมีขนาดใหญ่มาก


อาร์เรย์เจ็ดอาร์เรย์ของจำนวนเต็ม 3264 เสียงสำหรับเกม 2D ที่ทันสมัยได้อย่างไร ถ้าฉันแค่หวาดระแวงคำตอบก็จะเป็นอาร์เรย์ขนาดใหญ่
Balkania

3
ทั้ง # 1 และ # 4 ที่นี่ต้องการใช้พอยน์เตอร์และการจัดสรรหน่วยความจำแบบไดนามิกอย่างไรก็ตาม ฉันขอแนะนำให้ใช้reallocกับ # 3 - จัดสรรอาร์เรย์เป็นขนาดปกติจากนั้นขยายขนาดเมื่อใดก็ตามที่คุณหมด reallocจะจัดการคัดลอกข้อมูลของคุณหากจำเป็น สำหรับคำถามของ OP เกี่ยวกับการจัดการหน่วยความจำคุณต้องทำเพียงmallocครั้งเดียวในตอนเริ่มต้นfreeครั้งเดียวในตอนท้ายและreallocทุกครั้งที่คุณไม่มีที่ว่าง ก็ไม่เลวร้ายเท่าไหร่
Borealid

1
@Balkania: อาร์เรย์เจ็ดอาร์เรย์ 3264 จำนวนเต็มคือผมที่ต่ำกว่า 100KB นั่นไม่ใช่หน่วยความจำมากนัก
Borealid

1
@Balkania: 7 * 3264 * 32 bitดูเหมือน91.39 kilobytes. วันนี้ไม่มากตามมาตรฐานใด ๆ )
Wolph

1
การละเลยโดยเฉพาะนี้เป็นความอัปยศเพราะมันไม่ชัดเจนว่าจะเกิดอะไรขึ้นเมื่อreallocกลับมาNULL: a->array = (int *)realloc(a->array, a->size * sizeof(int));... บางทีอาจจะเขียนได้ดีที่สุดว่า: int *temp = realloc(a->array, a->size * sizeof *a->array); a->array = temp;... ด้วยวิธีนี้จะเห็นได้ชัดว่าสิ่งที่จะเกิดขึ้นจะต้องเกิดขึ้นก่อนNULLค่าได้รับมอบหมายให้a->array(ถ้ามันเป็นที่ทั้งหมด)
ออทิสติก

3

จากการออกแบบของMatteo Furlansเมื่อเขากล่าวว่า " การใช้อาร์เรย์แบบไดนามิกส่วนใหญ่ทำงานได้โดยเริ่มต้นด้วยอาร์เรย์ของขนาดเริ่มต้นบางส่วน (เล็ก) จากนั้นเมื่อใดก็ตามที่คุณไม่มีพื้นที่ว่างเมื่อเพิ่มองค์ประกอบใหม่ให้เพิ่มขนาดของอาร์เรย์เป็นสองเท่า " ความแตกต่างของ " งานระหว่างทำ " ด้านล่างคือขนาดไม่ได้เป็นสองเท่าโดยมีจุดมุ่งหมายเพื่อใช้เฉพาะสิ่งที่จำเป็น ฉันได้ละเว้นการตรวจสอบความปลอดภัยเพื่อความเรียบง่าย ... นอกจากนี้จากแนวคิดbrimboriumsฉันได้พยายามเพิ่มฟังก์ชันการลบลงในโค้ด ...

ไฟล์ storage.h มีลักษณะดังนี้ ...

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

ไฟล์ storage.c มีลักษณะดังนี้ ...

#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 จะเป็นแบบนี้ ...

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

ตั้งหน้าตั้งตาวิจารณ์อย่างสร้างสรรค์ติดตาม ...


1
ถ้ามันเป็นคำวิจารณ์ที่สร้างสรรค์ที่คุณแสวงหาดีกว่าที่จะโพสต์ไปที่รหัสตรวจสอบ ที่กล่าวว่าคำแนะนำสองสามข้อ: จำเป็นที่โค้ดจะต้องตรวจสอบความสำเร็จของการโทรmalloc()ก่อนที่จะพยายามใช้การจัดสรร ในหลอดเลือดดำเดียวกันเป็นความผิดพลาดที่จะกำหนดผลลัพธ์โดยตรงให้realloc()กับตัวชี้ไปยังหน่วยความจำดั้งเดิมที่ถูกจัดสรรใหม่ หากrealloc()ล้มเหลวNULLจะถูกส่งคืนและรหัสจะถูกปล่อยให้หน่วยความจำรั่วไหล มันมีประสิทธิภาพมากขึ้นเพื่อความทรงจำคู่เมื่อปรับขนาดกว่าที่จะเพิ่ม 1 พื้นที่ในเวลา: realloc()โทรน้อยลงไป
อดีต nihilo

1
ฉันรู้ว่าฉันกำลังจะถูกฉีกทิ้งฉันแค่ล้อเล่นเมื่อฉันพูดว่า "วิจารณ์เชิงสร้างสรรค์" ... ขอบคุณสำหรับคำแนะนำ ...

2
ไม่พยายามที่จะแยกใครออกจากกันเพียงแค่เสนอคำวิจารณ์ที่สร้างสรรค์ซึ่งอาจเกิดขึ้นได้แม้ว่าคุณจะไม่ได้อยู่ใกล้ ๆ ก็ตาม;)
อดีต nihilo

1
เดวิดฉันคิดถึงความคิดเห็นของคุณ "การปรับขนาดหน่วยความจำมีประสิทธิภาพมากกว่าการเพิ่มทีละ 1 ช่อง: การเรียก realloc () น้อยลง" ช่วยอธิบายให้ฉันฟังอย่างละเอียดว่าทำไมการจัดสรรหน่วยความจำเพิ่มขึ้นเป็นสองเท่าและอาจไม่ได้ใช้งานจึงเป็นการสิ้นเปลืองหน่วยความจำมากกว่าการกำหนดจำนวนที่จำเป็นสำหรับงาน ฉันเข้าใจว่าคุณกำลังพูดอะไรเกี่ยวกับการโทรไปที่ realloc () แต่ทำไมถึงโทรหา realloc () ทุกครั้งที่มีปัญหา นั่นไม่ใช่สิ่งที่มีไว้เพื่อจัดสรรหน่วยความจำใหม่ใช่หรือไม่?

1
แม้ว่าการเพิ่มสองเท่าอย่างเข้มงวดอาจไม่เหมาะสม แต่ก็ดีกว่าการเพิ่มหน่วยความจำทีละไบต์ (หรือทีละรายการint) การเพิ่มเป็นสองเท่าเป็นวิธีแก้ปัญหาทั่วไป แต่ฉันไม่คิดว่าจะมีทางออกที่ดีที่สุดที่เหมาะกับทุกสถานการณ์ นี่คือเหตุผลว่าทำไมการเพิ่มเป็นสองเท่าจึงเป็นความคิดที่ดี (ปัจจัยอื่น ๆ เช่น 1.5 ก็ใช้ได้เช่นกัน): หากคุณเริ่มต้นด้วยการจัดสรรที่เหมาะสมคุณอาจไม่จำเป็นต้องจัดสรรใหม่เลย เมื่อต้องการหน่วยความจำมากขึ้นการจัดสรรที่เหมาะสมจะเพิ่มขึ้นเป็นสองเท่าและอื่น ๆ ด้วยวิธีนี้คุณอาจต้องการเพียงหนึ่งหรือสองสายrealloc()เท่านั้น
อดีต nihilo

2

เมื่อคุณกำลังพูด

สร้างอาร์เรย์ที่มีหมายเลขดัชนี (int) ของจำนวนเอนทิตีที่ไม่แน่นอน

โดยพื้นฐานแล้วคุณกำลังบอกว่าคุณกำลังใช้ "พอยน์เตอร์" แต่ตัวชี้ที่เป็นตัวชี้แบบโลคัลแบบอาร์เรย์แทนที่จะเป็นตัวชี้แบบกว้างหน่วยความจำ เนื่องจากคุณมีแนวคิดที่ใช้ "พอยน์เตอร์" อยู่แล้ว (เช่นหมายเลข id ที่อ้างถึงองค์ประกอบในอาร์เรย์) ทำไมคุณไม่ใช้พอยน์เตอร์ปกติ (เช่นหมายเลข id ที่อ้างถึงองค์ประกอบในอาร์เรย์ที่ใหญ่ที่สุด: หน่วยความจำทั้งหมด )

แทนที่จะให้ออบเจ็กต์ของคุณจัดเก็บหมายเลขรหัสทรัพยากรคุณสามารถทำให้วัตถุเหล่านั้นเก็บเป็นตัวชี้แทนได้ โดยพื้นฐานแล้วสิ่งเดียวกัน แต่มีประสิทธิภาพมากกว่าเนื่องจากเราหลีกเลี่ยงการเปลี่ยน "array + index" เป็น "pointer"

พอยน์เตอร์ไม่น่ากลัวถ้าคุณคิดว่ามันเป็นดัชนีอาร์เรย์สำหรับหน่วยความจำทั้งหมด (ซึ่งก็คือสิ่งที่เป็นจริง)


2

ในการสร้างอาร์เรย์ของรายการไม่ จำกัด ประเภทใด ๆ :

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

และวิธีใช้:

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

เวกเตอร์ / อาร์เรย์นี้สามารถเก็บรายการประเภทใดก็ได้และมีขนาดแบบไดนามิกโดยสมบูรณ์


0

ฉันเดาว่าถ้าคุณต้องการลบองค์ประกอบคุณจะทำสำเนาของอาร์เรย์ที่ไม่ให้องค์ประกอบถูกยกเว้น

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

สมมติว่าgetElement2BRemove(), copy2TempVector( void* ...)และfillFromTempVector(...)วิธีการที่ช่วยเสริมการจัดการเวกเตอร์ชั่วคราว


ไม่ชัดเจนว่านี่เป็นคำตอบสำหรับคำถามที่ถามจริงหรือเป็นความคิดเห็น

เป็นความคิดเห็นสำหรับ "วิธีการ" และฉันกำลังขอคำยืนยัน (ฉันผิดหรือไม่) หากมีใครมีความคิดที่ดีกว่า ;)
JOSMAR BARBOSA - M4NOV3Y

ฉันเดาว่าฉันไม่เข้าใจประโยคสุดท้ายของคุณ เนื่องจาก SO ไม่ใช่ฟอรัมที่มีเธรดคำถามเปิดเช่นนี้ในคำตอบจึงดูแปลก

1
ฉันแก้ไขประโยคสุดท้ายของคุณเป็นสิ่งที่ฉันคิดว่าคุณต้องการจะพูด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.