2016-11-15 71 views
0

對於我的作業,我必須使用malloc動態創建一個字符串數組。 A我沒有真正瞭解已有的解決方案,因爲它的使用方式稍微不同於我想要的解決方案。C - 動態創建一個字符串數組(malloc)

我的問題: 所以,簡而言之:我正在製作一個基於文本的冒險遊戲。我的教授說,庫存應該是「任意大小」。如果該人沒有物品,那麼庫存的大小也應該爲零,其中1個物品只應包含1個物品等等。

我試着做一些工作,但我真的不能真正理解malloc是如何工作的,所以這裏是我的代碼(這顯然是不正確的,但是如何做到我想做的事是正確的?):

char* inventory; 
int amount=0; 
inventory=(char*) malloc(sizeof(char)*amount); 
//NOW THERE SHOULD BE AN INVENTORY WITH SIZE ZERO SINCE AMOUNT=0 
//NOW I WANT TO GIVE THE PLAYER AN ITEM: 
amount++; 
inventory=(char*) malloc(sizeof(char)*(amount+1); 
inventory[0]="sword"; 
//I WANT TO WRITE OUT INVENTORY TO TEST IF IT WORKS: 
printf("%s", inventory[0]); 
//FREE THE BITS LOCKED WITH MALLOC: 
free(inventory); 

教授告訴我們,我們必須寫,因爲字符串的最後一個字符的數量+ 1必須是「\ 0」或類似的東西。

所以,這裏是我的理解是如何的malloc工程(但也許這是不是它是如何工作和我的理解不正確地): 通常情況下,這是你會如何創建的字符串,例如一個數組:

char strings[10][200]; 

這意味着你有10個字符串,每個字符串可以有200個字符長。 當我在我的代碼中使用malloc時: 在我的示例中,整數'amount'基本上與數字10相同,sizeof(char)與我的示例中的number 200基本相同,對嗎? 如果沒有,那麼我完全失去了。

儘管如此,我的代碼並沒有明顯的工作,所以我真的很感謝你們的幫助,如果你有時間,可以使用malloc C代碼和一些解釋。

+0

':

我怎麼會寫(會不會深入講解)。 :) –

+0

在C中,未知/動態長度的字符串用'char *'指向第一個字符。該指針指向由該字符串分配的內存的開始處(+尾部\ 0)。要管理一些動態字符串,你需要一些'char *',例如這些指針的數組。指針數組又是指向第一個元素的指針表示的內存空間。所以,如果你需要一個字符串數組,那麼該數組可以被聲明爲'char **',因爲它指向一個字符串的第一個指針('char *')。 – JimmyB

+2

當我谷歌確切的問題,前8個結果都是關於這個主題的Stackoverflow問題。如果您已經查找了解決方案,但沒有找到確切的答案,請檢查這些問題,以便理解主題(使用malloc動態地創建一個字符串數組),並開發您自己的解決方案。 –

回答

0

這是我不太清楚你想要做什麼,但讓我們分析代碼:

char* inventory; 
int amount=0; 
inventory=(char*) malloc(sizeof(char)*amount); 

你剛纔因爲amount是零分配零個字節的內存。該文檔顯示「如果大小爲0,malloc會在堆中分配一個零長度項並返回一個指向該項的有效指針」,因此您有一個有效的指針,但沒有存儲。

amount++; 
inventory=(char*) malloc(sizeof(char)*(amount+1); 

您現在已經分配了2個字節的存儲空間。但是,由於您將第二次調用的結果分配給將第一次調用的結果保存爲malloc的變量,將第二次調用的結果分配給了malloc,但您已丟失剛剛分配的存儲空間,即使它是零字節。你應該有free之前。

inventory[0]="sword"; 

這是無效的,因爲inventorychar *,所以inventory[0]是一個char。您的編譯器應該已經警告過您,因此請關閉警告。

printf("%s", inventory[0]); 

這個printf的調用的結果是不確定的,因爲你要打印一個字符串,但你只傳遞一個字符。

free(inventory); 

這很好,雖然你失去了第一次分配(見上文)。

從這個分析,我希望你可以修改你的代碼來做你想做的。

+0

「您爲庫存[0]指定了一個常量字符串的指針。兩個字節大「不,這絕對不是發生了什麼。 'inventory [0]'*不是一個指針*,它是一個'char'。這項任務簡直無效,應引發診斷。在printf –

+0

@ n.m。中使用'inventory [0]'也是一樣,我看到了我的錯誤並修復了它。不管怎麼說,還是要謝謝你。 –

0

你似乎想要一個字符串數組(即一組數組char s),但你有一個指向char的指針。在C語言中,指針和數組通常是可以互換的(儘管有人說它們是同一件事物,但沒有使用C) - 你可以這樣想,因爲你可以對指針進行算術運算來訪問相鄰的數據,而數組只是存儲作爲相鄰的數據。所以你需要一個指向字符的指針,或者char **

您需要仔細考慮您的數據將如何存儲在內存中。在這種情況下,您需要有一個存儲指針數組的清單列表(指針是固定長度的,所以這很好)。每個指針都將是一個指向包含清單中該項目的實際字符串的指針。

其次,庫存本身需要爲malloc'd,但是如果你想存儲的所有字符串在編譯時都是固定的,那麼字符串本身不一定是。如果這讓你感到困惑,那就這樣想 - 當你運行程序時,它需要被加載到內存中的某個地方。因此,編譯到程序中的字符串將存在於內存中的某處,並且C會讓您在內存中獲取指向該字符串的指針,就像您可以將指針指向內存中的變量。因此,每次您需要向廣告資源中添加字符串時,您首先需要通過增加廣告資源大小來調整廣告資源尺寸,使用memcpy或類似的方法創建新廣告資源,以將廣告資源的現有內容複製到新副本,然後在舊副本上調用free(非常重要,否則你會得到內存泄漏,這很難發現,但意味着如果你的程序運行足夠長,它最終會吃掉你所有的內存),然後最終覆蓋指向舊副本的指針,指向新副本的指針。

然後,你需要做一些類似於inventory[amount-1] = "some string";的字符串(當然,字符串也可以作爲參數傳遞給一個函數,在這種情況下,它應該是一個變量類型爲char * )。

如果你的字符串在編譯時並不都是固定的(例如,如果你想在命令行輸入的庫存中輸入某個人的名字,或者如果你想擁有一個類似「thing 1」的數字) ,「事物2」等等,這是在循環或其他東西中產生的),那麼你還需要malloc這個字符串的空間。

對不起,如果這是令人困惑的,但這實際上是你需要能夠理解能夠聲稱在C中的任何技能的東西。這是一個非常複雜的話題的簡短解釋,所以如果你不理解它,你真的需要花更多的時間用更多的基礎培訓材料來學習它。我不會僅僅因爲這個原因給你提供代碼(也因爲它需要更多的時間,而不是我寫的價值)。你需要對C中指針的工作原理及其與數組的關係有一個基本的瞭解,並且至少對這種事情如何存儲在內存中有一個模糊的理解(包括堆棧和堆之間的區別),以便能夠正確解決這個問題。祝你好運!

+0

我想感謝您長期解釋的答案。我會盡我所能,盡我所能去理解它。我會給你一個關於我能夠用它完成的反饋。謝謝!對於這個答案,我明確表示讚賞! – Noxter

0

我可以給的一些提示:

  • 使用結構到組變量一起(如陣列和陣列長度)
  • 使用結構來創建abstract data type(例如:「庫存」數據類型)
  • 創建常用操作功能(初始化庫存,打印庫存,增加庫存容量,添加到庫存,免費庫存,...)
  • 除非絕對必要,否則不要打擾(永遠)字符串切片,ropes和2D數組。在這種情況下,字符指針數組工作得很好。 (不要以爲你打算實現這個,但我會提到它)無論如何,
  • 但是,你可能想在將來創建一個inventory_item結構,而不是隻存儲字符指針。這樣您可以添加其他項目屬性,如計數和質量。 ("You have %d %s, it is %s", item.count, item.name, item.quality

重構代碼:

struct inventory { 
    size_t num_items; // What you call `amount`. 
    char **items; // What you call `inventory`. Note that it's a pointer to a _character pointer_, as the other answers indicated. 
}; 

int main(void) { 
    struct inventory inv; 

    // Initialize the inventory. 
    // You might want to create a function for this. 
    // In C++, you would use constructors. 
    inv.num_items = 0; 
    inv.items = NULL; // Set `items` to NULL, see below at realloc 

    // Once again, you might want to create a function for this, 
    // since you'll probably end up doing this a lot 
    void *old; // Store the old inv.items; in case we fail to allocate a new one. 
    old = inv.items; 
    inv.items = realloc(inv.items, sizeof(*inv.items) * inv.num_items+1); 
    // Explaination: `ptr = realloc(ptr, size)` is the same as `new_ptr = malloc(size); memcpy(ptr, old_ptr, /* size of old_ptr */); free(old_ptr);` 
    // realloc works the same as malloc when it's first argument is NULL. 
    if (!inv.items) { // Failed to allocate memory. 
     free(old); // Deinitialize the inventory. A `goto fail;` would be useful here. 
     return 1; 
    } 
    // Maybe you'd want seperate functions for adding items and growing the array, then call the growth function here. 
    inv.items[inv.num_items] = "sword"; 
    inv.items++; 

    // Print the inventory 
    for (size_t i=0; i < inv.num_items; i++) { 
     printf("inventory[%lu] = %s\n", i, inv.items[i]); // %lu = unsigned long, `size_t` on my system. 
    } 

    // You might again want a function for this 
    free(inv.items); // Deinitialize the inventory 
    // Optional, but cleaner: 
    inv.items = NULL; 
    inv.num_items; 

    return 0; 
} 

缺點上述方法是不能混用字符串和分配的人,因爲你不知道哪一個是對free當去初始化。 (字符串上的free是未定義的行爲)。

但是,如果所有項目都是字符串文字,應該沒問題。這樣,您也可以安全地在這些字符串上使用==運算符。但請注意,如果您發現自己能夠在字符串上安全使用==運算符,則可能需要定義枚舉。有工作的mallocÇcodes`..nopes,沒有機會

typedef struct inventory inv_t; 
typedef struct inventory_item inv_item_t; 

struct inventory { 
    size_t num_items; 
    inv_item_t items; 
}; 
struct inventory_item { 
    int type; 
}; 

// Convenience macro for grouping enum data 
#define GENERATE_ITEM_TYPES(XX) \ 
XX(ITEM_NONE, "<unknown item>") \ 
XX(ITEM_SWORD, "sword") 

enum inventory_type { 
#define XX(enumname, ignore) enumname, 
    GENERATE_ITEM_TYPES(XX) 
#undef XX 
    NUM_ITEMS, // Automatically equals 2 
}; 
static char *names[NUM_ITEMS] = { 
#define XX(ignore, strname) strname, 
    GENERATE_ITEM_TYPES(XX) 
#undef XX 
}; 

static int inv_init(inv_t *self); 
static int inv_fini(inv_t *self); 

static int inv_print(inv_t *self); 

static int inv_reserve(inv_t *self, size_t new_size); 

static int inv_add(inv_t *self, inv_type_t type); 

static inv_item_t inv_item_from_type(int type) { 
    inv_item_t item = { .type = type }; 
    return item; 
} 

int main(void) 
{ 
    inv_t inv; 

    if (inv_init(&inv) != 0) goto fail; 

    if (inv_add(&inv, inv_item_from_type(ITEM_SWORD)) != 0) goto fail; 
    if (inv_print(&inv) != 0) goto fail; 

    inv_fini(&inv); 

    return 0; 

fail: 
    inv_fini(&inv); 

    return 1; 
} 

static int inv_init(inv_t *self) 
{ 
    self->num_items = 0; 
    self->items = NULL; 

    return 0; 
} 
static int inv_fini(inv_t *self) 
{ 
    free(self->items); 
    self->items = NULL; 
    self->num_items; 

    return 0; 
} 

static int inv_print(inv_t *self) 
{ 
    for (size_t i=0; i < inv.num_items; i++) { 
     printf("inventory[%llu] = %s\n", (unsigned long long int)i, names[inv.items[i]]); // No format for size_t, so convert to unsigned long long and use format `%llu`. More portable. 
    } 
} 

static int inv_reserve(inv_t *self, size_t new_size) 
{ 
    void *old; 

    old = inv.items; 
    inv.items = realloc(inv.items, sizeof(*inv.items) * new_size); 
    if (!inv.items) goto fail; 

    return 0; 

fail: 
    free(old); 
    return 1; 
} 
static int inv_add(inv_t *self, char *item) 
{ 
    if (inv_reserve(self, inv.num_items + 1) != 0) return 1; 

    inv.items[inv.num_items++] = item; 

    return 0; 
}