Как мне создать массив строк в C?

263

Я пытаюсь создать массив строк в C. Если я использую этот код:

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

gcc выдает «предупреждение: присваивание из несовместимого типа указателя». Как правильно это сделать?

редактировать: мне любопытно, почему это должно выдавать предупреждение компилятора, так как если я это сделаю printf(a[1]);, он правильно печатает «хмм».


источник
12
Для справки char (*a[2])[14]: массив из двух указателей на массив из 14 символов.
Авакар
4
Я думал, что это было четырнадцать указателей на массивы из двух символов xD
Фортран
74
Самый полезный совет, который я когда-либо читал, для расшифровки типов Си: «Начни с имени, читай справа, когда можешь, слева, когда должен»: char (*a[2])[14]- начинай с 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-й массив указателей на 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];

Объявляется как 2-элементный массив указателей на 14-элементные массивы char.

Джон Боде
источник
3
@Slater: да, если это результат mallocзвонка.
Джон Боде
Спасибо за этот очень подробный ответ. Это действительно помогло мне.
cokedude
1
Почему мы можем использовать strcpy только для массивов String, объявленных как 2D-массив. Почему стандартное назначение не выполняется?
Andrew S
4
@AndrewS: полный ответ не помещается в комментарии, но в основном это артефакт того, как C обрабатывает выражения массива; в большинстве случаев выражение типа T [N]преобразуется в выражение типа T *, а значением выражения является адрес первого элемента. Поэтому, если вы написали str = "foo", вы бы попытались присвоить "foo"массиву адрес первого символа str, что не работает. Смотрите этот ответ для более подробной информации.
Джон Боде
@JohnBode не могли бы вы добавить небольшой твик? char *strs[NUMBER_OF_STRINGS] = {0}; Это помогает избежать проблем в будущем инициализации strsв NULL. Многие люди читают этот пост, когда Google делает поиск по массиву строк в C.
cokedude
94

Ack! Постоянные строки:

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

Если я правильно помню.

Да, и вы хотите использовать strcpy для присваивания, а не оператор =. strcpy_s безопаснее, но его нет ни в C89, ни в стандартах C99.

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

Обновление: Томас говорит, strlcpyчто это путь.

mpen
источник
Это С99? Я не верю, что это возможно в ANSI C.
Нолдорин
6
Это возможно как в C89, так и в C99. Также не имеет значения, с const или без него, хотя первое предпочтительнее.
avakar
1
Ну, const новый, и вы должны были указать размер внешнего массива (в данном случае 3), но в остальном это вполне приемлемо K & R C. У меня есть старая книга C, защищенная авторским правом 1984 года, в которой есть раздел, показывающий, как сделай это. Они называют это «рваный массив». Конечно, у него не было «операторов», и strcpy_s - новый для меня.
Тед
6
strcpy_s - это функция Microsoft. Этого, вероятно, следует избегать, потому что это не в стандартном C.
Cromulent
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- сначала вам нужно будет динамически распределить достаточно памяти, а затем их элементы будут указывать на эту память, а затем копировать в нее символы - и тогда вам нужно будет обязательно освободить память, когда закончите.

Бах - я уже скучаю по С ++;)

ps дайте мне знать, если вам нужны примеры.

Фейсал Вали
источник
Мне нужны строковые массивы для проекта Arduino. В конце я использовал стиль a2. Сначала я попробовал стиль a1, определяющий мой строковый массив как char a1 [] [2] = {"F3", "G3" ... и т. Д. } так как он предназначен для хранения двухсимвольных длинных строк. Это дало неожиданный вывод, потому что я забыл, что нулевой терминатор будет означать, что каждая строка должна иметь размер не менее 3 для хранения 2 символов. Используя стиль a2, мне не нужно было указывать длину строки, и она также могла приспосабливаться к переменной длине строки, поэтому я решил придерживаться этого :-)
Jeromy Adofo
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;

FutureSci
источник
10

В ANSI C:

char* strings[3];
strings[0] = "foo";
strings[1] = "bar";
strings[2] = "baz";
нолдорин
источник
8
@Zifre: я совершенно не согласен. Это очень большая часть типа - в данном случае это «указатель на символ». Что бы вы сказали в любом случае ... это часть имени переменной? Я видел много компетентных программистов, использующих этот стиль.
Нолдорин
14
Просто для всех, кто читает это, я хотел бы отметить, что Бьярн Страуструп ставит * по типу ...
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 не гарантируется равным нулю, поэтому цикл может не прерываться, когда должен. Поправь меня, если я ошибаюсь.
Palec
2
Понятия не имею :) Вы можете сравнить с NULL в операторе while, если хотите.
Сергей
9

Строковые литералы const char *s.

И ваше использование скобок странно. Вы, наверное, имеете в виду

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

который объявляет массив из двух указателей на постоянные символы и инициализирует их, чтобы они указывали на две жестко закодированные строковые константы.

dmckee --- котенок экс-модератора
источник
3

Ваш код создает массив указателей на функции. Пытаться

char* a[size];

или

char a[size1][size2];

вместо.

Смотрите викибуки к массивам и указателям

Dario
источник
1
Sahu V Kumar
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;
}
Aominé
источник
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выбирает указатель на строки массива, а count выбирает количество строк с помощью _countofфункции. (Аналогично sizeof(arr) / sizeof(arr[0])).

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

TarmoPikaro
источник
-6

Хороший способ - определить строку самостоятельно.

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

Это действительно так просто.

IceCodr
источник
4
Вы пропустили ;, и как это создает массив строк ?
keyser