Як створити масив рядків у C?


263

Я намагаюся створити масив рядків у C. Якщо я використовую цей код:

char (*a[2])[14];
a[0]="blah";
a[1]="hmm";

gcc видає мені "попередження: призначення від несумісного типу вказівника". Який правильний спосіб це зробити?

редагувати: мені цікаво, чому це повинно попереджати компілятора, оскільки якщо я це зробити printf(a[1]);, він правильно надрукує "хм".

c  arrays  string 

12
Тільки для запису, char (*a[2])[14]це масив з двох покажчиків на масив з 14 символів.
avakar

4
Я подумав, що це чотирнадцять покажчиків на масиви двох знаків xD
fortran

74
Найкорисніша порада, яку я коли-небудь читав для розшифровки типів С: "Почніть з імені, читайте право, коли зможете, ліворуч, коли потрібно": char (*a[2])[14]- почніть з a, перемістіться праворуч: "масив двох", перемістіться вліво: "вказівник на", дужка завершена так, щоб читати праворуч: "масив із сорока", читати ліворуч: "char" ... Склади це, і у нас є "a масив двох покажчиків на масиви сорок символів"
Марк К Коуан

4
@dotancohen: Ця порада остаточно переконала мене писати вказівники, char *strа не char* str. Виходячи з фона Delphi / Pascal, я дуже звик до останнього способу, поки не натрапив на більш складні типи. Колишній спосіб все ще виглядає мені некрасиво, але робить позначення типу більш послідовними (IMO).
Марк К Коуан

@MarkKCowan, дивовижний! Дякую! :)
доктор Ессен

Відповіді:


232

Якщо ви не хочете змінювати рядки, ви можете просто зробити

const char *a[2];
a[0] = "blah";
a[1] = "hmm";

Коли ви зробите це так, ви виділите масив з двох покажчиків на const char. Потім ці покажчики будуть встановлені на адреси статичних рядків "blah"та "hmm".

Якщо ви хочете мати можливість змінити фактичний вміст рядка, вам доведеться зробити щось на кшталт

char a[2][14];
strcpy(a[0], "blah");
strcpy(a[1], "hmm");

Це виділить два послідовних масиви по 14 charс кожен, після чого вміст статичних рядків буде скопійовано в них.


185

Існує кілька способів створення масиву рядків у C. Якщо всі рядки будуть однакової довжини (або принаймні мають однакову максимальну довжину), ви просто оголосите двовимірний масив char та призначите за необхідності:

char strs[NUMBER_OF_STRINGS][STRING_LENGTH+1];
...
strcpy(strs[0], aString); // where aString is either an array or pointer to char
strcpy(strs[1], "foo");

Ви також можете додати список ініціалізаторів:

char strs[NUMBER_OF_STRINGS][STRING_LENGTH+1] = {"foo", "bar", "bletch", ...};

Це передбачає, що розмір і кількість рядків ініціалізатора збігаються з розмірами вашого масиву. У цьому випадку вміст кожного рядкового літералу (який сам по собі є нульовим завершеним масивом char) копіюється в пам'ять, виділену strs. Проблема такого підходу - можливість внутрішньої фрагментації; якщо у вас 99 рядків, що мають 5 символів або менше, але в 1 рядку довжиною 20 символів, 99 рядків матиме не менше 15 невикористаних символів; це марна трата місця.

Замість використання двовимірного масиву char, ви можете зберігати 1-d масив покажчиків на char:

char *strs[NUMBER_OF_STRINGS];

Зверніть увагу, що в цьому випадку ви виділили пам'ять лише для утримання покажчиків на рядки; пам'ять для самих рядків повинна бути розподілена в іншому місці (або як статичні масиви, або за допомогою malloc()або calloc()). Ви можете використовувати список ініціалізатора, як попередній приклад:

char *strs[NUMBER_OF_STRINGS] = {"foo", "bar", "bletch", ...};

Замість того, щоб копіювати вміст рядкових констант, ви просто зберігаєте покажчики на них. Зверніть увагу, що струнні константи можуть бути недоступними для запису; Ви можете перепризначити покажчик так:

strs[i] = "bar";
strs[i] = "foo"; 

Але ви, можливо, не зможете змінити вміст рядка; тобто

strs[i] = "bar";
strcpy(strs[i], "foo");

може не дозволено.

Ви можете використовувати malloc()для динамічного розподілу буфера для кожного рядка та копіювати в цей буфер:

strs[i] = malloc(strlen("foo") + 1);
strcpy(strs[i], "foo");

До речі,

char (*a[2])[14];

Оголошує як двоелементний масив покажчиків на 14-елементні масиви char.


3
@Slater: так, якщо це результат mallocдзвінка.
Джон Боде

Дякую за цю дуже детальну відповідь. Це мені справді допомогло.
кокеду

1
Чому ми можемо використовувати лише strcpy на масивах String, оголошених як 2D масив. Чому стандартне завдання не вдається?
Андрій S

4
@AndrewS: Повна відповідь не впишеться в коментар, але в основному це артефакт того, як C поводиться з виразами масиву; за більшості обставин вираз типу T [N]перетворюється на вираз типу T *, а значення виразу - адресу першого елемента. Отже, якщо ви писали str = "foo", ви б намагалися призначити адресу першого символу "foo"масиву str, який не працює. Дивіться цю відповідь для отримання більш детальної інформації.
Джон Боде

@JohnBode Ви можете, будь-ласка, додати маленьку настройку? char *strs[NUMBER_OF_STRINGS] = {0}; Це допомагає запобігти майбутнім проблемам шляхом ініціалізації strsдо NULL. Дуже багато людей читають цю публікацію, коли Google google шукає масив рядків у C.
cokedude

94

Ак! Постійні рядки:

const char *strings[] = {"one","two","three"};

Якщо я правильно пам’ятаю.

О, і ви хочете використовувати strcpy для призначення, а не оператор =. strcpy_s є більш безпечним, але він не є ні в C89, ні в стандартах C99.

char arr[MAX_NUMBER_STRINGS][MAX_STRING_SIZE]; 
strcpy(arr[0], "blah");

Оновлення: Томас каже, що strlcpyце шлях.


Це C99? Я не вірю, що це можливо в ANSI C.
Noldorin

6
Це можливо і в C89, і в C99. Це також не має значення, чи це з const чи без нього, хоча перший є кращим.
avakar

1
Що ж, const є новим, і вам раніше доводилося вказувати розмір зовнішнього масиву (3 в даному випадку), але в іншому випадку це цілком прийнятно K&R C. У мене є стара книга C, захищена авторським правом 1984 року, яка містить розділ, який показує, як зробити зробити це. Вони називають це "рваним масивом". Звичайно, у неї не було "операторів", а strcpy_s - це новий для мене.
ТЕД

6
strcpy_s - це функція Microsoft. Ймовірно, цього слід уникати, оскільки це не в стандартній С.
Хромуючий

5
strcpy_s та інші "безпечні функції" стандартизовані як ISO / IEC TR 24731 (це стандарт, опублікований ISO і як такий, не доступний в Інтернеті безкоштовно; останній проект - open-std.org/jtc1/sc22/wg14/www /docs/n1225.pdf )
Павло Мінаєв

14

Ось кілька варіантів:

char a1[][14] = { "blah", "hmm" };
char* a2[] = { "blah", "hmm" };
char (*a3[])[] = { &"blah", &"hmm" };  // only since you brought up the syntax -

printf(a1[0]); // prints blah
printf(a2[0]); // prints blah
printf(*a3[0]); // prints blah

Перевага a2полягає в тому, що ви можете робити наступне за допомогою рядкових літералів

a2[0] = "hmm";
a2[1] = "blah";

А a3ви можете зробити наступне:

a3[0] = &"hmm";
a3[1] = &"blah";

Для a1 вам доведеться використовувати strcpy()(ще краще strncpy()) навіть при призначенні рядкових літералів. Причина полягає в тому a2, що і a3це масиви покажчиків, і ви можете зробити їх елементи (тобто вказівники) вказівками на будь-яке сховище, тоді якa1 як це масив 'масив символів', і тому кожен елемент є масивом, який "володіє" власним сховищем ( а це означає, що він руйнується, коли він виходить за межі) - ви можете копіювати речі лише на його сховище.

Це також приводить нас до недоліків використання a2і a3- оскільки вони вказують на статичне зберігання (де зберігаються рядкові літерали), вміст якого неможливо достовірно змінити (наприклад, невизначена поведінка), якщо ви хочете призначити не рядкові літерали для елементи a2абоa3 - спочатку вам доведеться динамічно виділити достатню кількість пам'яті, а потім їх елементи вказати на цю пам’ять, а потім скопіювати в неї символи - і тоді вам доведеться переконатись, що буде розміщено пам'ять, коли буде зроблено.

Бах - я вже сумую по C ++;)

ps Дайте мені знати, якщо вам потрібні приклади.


Мені потрібні рядкові масиви для проекту Arduino. Наприкінці я використав стиль a2. Спочатку я спробував стиль a1, визначивши мій рядок як char a1 [] [2] = {"F3", "G3" ... тощо. }, як було призначено для зберігання 2-х символьних довгих рядків. Це дало несподіваний вихід, тому що я забув, що нульовий термінатор означатиме, що кожна строка повинна мати розмір принаймні 3, щоб зберігати 2 символи. Використовуючи стиль a2, мені не потрібно було вказувати довжину струни, і вона може вміщувати різну довжину рядка, тому я вирішив дотримуватися цього :-)
Джеромі Адофо

char (* a3 []) [] = {& "blah", & "hmm"}; => не працює в g ++ Apple LLVM версії 9.1.0, але працює в gcc
1234

12

Або ви можете оголосити тип структури, що містить масив символів (1 рядок), вони створюють масив структур і, таким чином, багатоелементний масив

typedef struct name
{
   char name[100]; // 100 character array
}name;

main()
{
   name yourString[10]; // 10 strings
   printf("Enter something\n:);
   scanf("%s",yourString[0].name);
   scanf("%s",yourString[1].name);
   // maybe put a for loop and a few print ststements to simplify code
   // this is just for example 
 }

Однією з переваг цього перед будь-яким іншим методом є те, що це дозволяє сканувати безпосередньо в рядок без використання strcpy;


10

В ANSI C:

char* strings[3];
strings[0] = "foo";
strings[1] = "bar";
strings[2] = "baz";

8
@Zifre: Я повністю не згоден. Це дуже часто є частиною типу - "покажчиком" в цьому випадку. Що б ви сказали все-таки ... це частина назви змінної? Я бачив, як багато грамотних програмістів використовують цей стиль.
Нолдорін

14
Просто для тих, хто читає це, я хотів би зазначити, що Bjarne Stroustrup ставить * за типом ...
MirroredFate

1
@MirroredFate: Правильно. Дійсно, рекомендується практика в C ++ з того, що я знаю. Семантично для мене немає сенсу ставити його за ідентифікатором, через спосіб його використання. : /
Нолдорін

16
@Noldorin, char* foo, bar;що таке тип bar?
MASOUD

10
C був розроблений Деннісом Річі в 1972 році, а в 1988 році він і Брайан Керніган опублікували друге видання K&R - Мова програмування на C, книгу, яку багато хто вважає фактично стандартною для C. Вони поставили * за ідентифікатором.
Маріус Ліан

10

Якщо рядки статичні, вам краще:

const char *my_array[] = {"eenie","meenie","miney"};

Хоча не є частиною базового ANSI C, швидше за все, ваше середовище підтримує синтаксис. Ці рядки є незмінними (лише для читання), і тому в багатьох середовищах використовують менше накладних витрат, ніж динамічно будують рядковий масив.

Наприклад, для невеликих мікроконтролерів, цей синтаксис використовує програмну пам'ять, а не (як правило) більш дорогу пам'ять оперативної пам'яті. AVR-C - приклад середовища, що підтримує цей синтаксис, але так само, як і більшість інших.


10

Якщо ви не хочете відслідковувати кількість рядків у масиві і хочете повторити їх, просто додайте NULL рядок у кінці:

char *strings[]={ "one", "two", "three", NULL };

int i=0;
while(strings[i]) {
  printf("%s\n", strings[i]);
  //do something
  i++;
};

Я вважаю, що це справедливо лише на C ++. У C NULL не гарантовано дорівнює нулю, тому цикл може не розірватися, коли повинен. Виправте мене, якщо я помиляюся.
Палець

2
Не маю ідеї :) Якщо ви хочете, ви можете порівняти з NULL у заяві
Сергій

9

Літеральні рядки є const char * s.

І ваше використання дужок дивно. Ви, мабуть, маєте на увазі

const char *a[2] = {"blah", "hmm"};

який оголошує масив з двох покажчиків на постійні символи та ініціалізує їх для вказівки на два твердо кодованих рядкових константи.


3

Ваш код створює масив покажчиків функцій. Спробуйте

char* a[size];

або

char a[size1][size2];

замість цього.

Перегляньте wikibooks до масивів та покажчиків


1
капелюхи для вашого іншого підходу ... Такі люди, як ви, роблять стек для переповнення ...
Саху V Кумар,

1

привіт, ви можете спробувати це нижче:

 char arr[nb_of_string][max_string_length]; 
 strcpy(arr[0], "word");

приємний приклад використання масиву рядків у c, якщо ви цього хочете

#include <stdio.h>
#include <string.h>


int main(int argc, char *argv[]){

int i, j, k;

// to set you array
//const arr[nb_of_string][max_string_length]
char array[3][100];

char temp[100];
char word[100];

for (i = 0; i < 3; i++){
    printf("type word %d : ",i+1);
    scanf("%s", word);
    strcpy(array[i], word);
}

for (k=0; k<3-1; k++){
    for (i=0; i<3-1; i++)
    {
        for (j=0; j<strlen(array[i]); j++)
        {
            // if a letter ascii code is bigger we swap values
            if (array[i][j] > array[i+1][j])
            {
                strcpy(temp, array[i+1]);
                strcpy(array[i+1], array[i]);
                strcpy(array[i], temp);

                j = 999;
            }

            // if a letter ascii code is smaller we stop
            if (array[i][j] < array[i+1][j])
            {
                    j = 999;
            }

        }
    }
}

for (i=0; i<3; i++)
{
    printf("%s\n",array[i]);
}

return 0;
}

0
char name[10][10]
int i,j,n;//here "n" is number of enteries
printf("\nEnter size of array = ");
scanf("%d",&n);
for(i=0;i<n;i++)
{
    for(j=0;j<1;j++)
    {
        printf("\nEnter name = ");
        scanf("%s",&name[i]);
    }
}
//printing the data
for(i=0;i<n;i++)
{
    for(j=0;j<1;j++)
    {
        printf("%d\t|\t%s\t|\t%s",rollno[i][j],name[i],sex[i]);
    }
    printf("\n");
}

Ось спробуйте це !!!


1
чи можете ви пояснити, для чого вам потрібен цикл for-змінної j, тобто для (j = 0; j <1; j ++)?
SouvikMaji

0

Мені не вистачало якимось більш динамічного масиву рядків, де кількість рядків може змінюватися залежно від вибору часу виконання, але в іншому випадку слід виправити рядки.

Я закінчив кодування фрагмента коду таким чином:

#define INIT_STRING_ARRAY(...)          \
    {                                   \
        char* args[] = __VA_ARGS__;     \
        ev = args;                      \
        count = _countof(args);         \
    }

void InitEnumIfAny(String& key, CMFCPropertyGridProperty* item)
{
    USES_CONVERSION;
    char** ev = nullptr;
    int count = 0;

    if( key.Compare("horizontal_alignment") )
        INIT_STRING_ARRAY( { "top", "bottom" } )

    if (key.Compare("boolean"))
        INIT_STRING_ARRAY( { "yes", "no" } )

    if( ev == nullptr )
        return;

    for( int i = 0; i < count; i++)
        item->AddOption(A2T(ev[i]));

    item->AllowEdit(FALSE);
}

char** evпідбирає вказівник на масив рядків, а підрахунок набирає кількість рядків за допомогою _countofфункції. (Подібно до sizeof(arr) / sizeof(arr[0])).

І є додаткова конверсія Ansi to unicode за допомогою A2Tмакросу, але це може бути необов’язково для вашого випадку.


-6

Хороший спосіб - визначити рядок за вашим "я".

#include <stdio.h>
typedef char string[]
int main() {
    string test = "string";
    return 0;
}

Це дійсно так просто.


4
Вам не вистачає а ;, і як це створює масив рядків ?
клавішник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.