Jak utworzyć tablicę ciągów w C?

263

Próbuję utworzyć tablicę ciągów znaków w C. Jeśli użyję tego kodu:

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

gcc daje mi „ostrzeżenie: przypisanie z niezgodnego typu wskaźnika”. Jaki jest właściwy sposób to zrobić?

edytuj: Jestem ciekawy, dlaczego powinno to dać kompilatorowi ostrzeżenie, ponieważ jeśli to zrobię printf(a[1]);, to poprawnie wypisuje „hmm”.


źródło
12
Dla przypomnienia char (*a[2])[14]jest to tablica dwóch wskaźników do tablicy 14 znaków.
avakar
4
Myślałem, że czternaście wskaźników do tablic dwóch znaków xD
fortran
74
Najbardziej użyteczna rada, jaką kiedykolwiek czytałem, aby rozszyfrować typy C: „Zacznij od nazwy, czytaj w prawo, kiedy możesz, w lewo, kiedy musisz”: char (*a[2])[14]- zacznij od a, przesuń w prawo: „tablica dwóch”, przesuń w lewo: „wskaźnik do”, nawias zakończony, więc przeczytaj w prawo: „tablica czternastu”, przeczytaj w lewo: „char” ... Złóż to i mamy „a to tablica dwóch wskaźników do tablic czternastu znaków”
Mark K Cowan
4
@dotancohen: Ta wskazówka przekonała mnie do napisania wskaźników char *strzamiast char* str. Pochodząc z Delphi / Pascal, byłem bardzo przyzwyczajony do tej drugiej drogi, dopóki nie natknąłem się na bardziej złożone typy. Ten pierwszy sposób nadal wydaje mi się brzydki, ale sprawia, że ​​notacja typu jest bardziej spójna (IMO).
Mark K Cowan
@MarkKCowan, amazing one! Dzięki! :)
Dr. Essen

Odpowiedzi:

232

Jeśli nie chcesz zmieniać ciągów, możesz po prostu to zrobić

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

Kiedy zrobisz to w ten sposób, przydzielisz tablicę dwóch wskaźników const char. Wskaźniki te zostaną następnie ustawione na adresy ciągów statycznych "blah"i "hmm".

Jeśli chcesz mieć możliwość zmiany rzeczywistej zawartości łańcucha, musisz zrobić coś takiego

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

Spowoduje to przydzielenie dwóch kolejnych tablic po 14 charsekund, po których zawartość ciągów statycznych zostanie do nich skopiowana.

Mikael Auno
źródło
185

Istnieje kilka sposobów utworzenia tablicy ciągów w C. Jeśli wszystkie ciągi będą mieć tę samą długość (lub przynajmniej taką samą maksymalną długość), wystarczy zadeklarować tablicę 2-d char i przypisać w razie potrzeby:

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

Możesz także dodać listę inicjatorów:

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

Zakłada się, że rozmiar i liczba łańcuchów w inicjalizatorze pasują do wymiarów tablicy. W tym przypadku zawartość każdego literału łańcuchowego (który sam jest tablicą znaków zakończoną zerem) jest kopiowana do pamięci przydzielonej do łańcuchów. Problemem w tym podejściu jest możliwość fragmentacji wewnętrznej; jeśli masz 99 łańcuchów, które mają 5 znaków lub mniej, ale 1 łańcuch o długości 20 znaków, 99 łańcuchów będzie zawierać co najmniej 15 nieużywanych znaków; to strata miejsca.

Zamiast używać dwuwymiarowej tablicy znaków, możesz przechowywać tablicę jednowymiarowych wskaźników do znaku:

char *strs[NUMBER_OF_STRINGS];

Zauważ, że w tym przypadku pamięć została przydzielona tylko do przechowywania wskaźników do ciągów; pamięć dla samych łańcuchów musi być przydzielona gdzie indziej (jako tablice statyczne lub za pomocą malloc()lub calloc()). Możesz użyć listy inicjalizacyjnej jak we wcześniejszym przykładzie:

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

Zamiast kopiować zawartość stałych ciągów, po prostu przechowujesz do nich wskaźniki. Zauważ, że stałe łańcuchowe mogą nie być zapisywalne; możesz ponownie przypisać wskaźnik, w ten sposób:

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

Ale możesz nie być w stanie zmienić zawartości łańcucha; to znaczy,

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

może nie być dozwolone.

Możesz użyć malloc()do dynamicznego przydzielania bufora dla każdego łańcucha i kopiowania do tego bufora:

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

BTW,

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

Deklaruje jako 2-elementową tablicę wskaźników do 14-elementowych tablic char.

John Bode
źródło
3
@ Później: tak, jeśli jest to wynik mallocpołączenia.
John Bode,
Dziękuję za tę bardzo szczegółową odpowiedź. To mi naprawdę pomogło.
Cokedude
1
Dlaczego możemy używać strcpy tylko na tablicach String zadeklarowanych jako tablica 2D. Dlaczego standardowe przypisanie kończy się niepowodzeniem?
Andrew S
4
@AndrewS: Pełna odpowiedź nie pasuje do komentarza, ale w zasadzie jest artefaktem tego, jak C traktuje wyrażenia tablicowe; w większości przypadków wyrażenie typu T [N]jest konwertowane na wyrażenie typu T *, a wartością wyrażenia jest adres pierwszego elementu. Więc jeśli napiszesz str = "foo", będziesz próbował przypisać adres pierwszej postaci "foo"do tablicy str, co nie działa. Zobacz tę odpowiedź, aby uzyskać więcej informacji.
John Bode
@JohnBode, czy możesz dodać małą poprawkę? char *strs[NUMBER_OF_STRINGS] = {0}; Pomaga to zapobiec przyszłym problemom poprzez inicjowanie strsdo NULL. Wiele osób czyta ten post, gdy Google wyszukuje tablicę ciągów znaków w C.
cokedude
94

Ack! Stałe ciągi:

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

Jeżeli dobrze pamiętam.

Aha, i chcesz użyć strcpy do przypisania, a nie operatora =. strcpy_s jest bezpieczniejszy, ale nie jest ani w C89, ani w standardach C99.

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

Aktualizacja: Thomas mówi, że strlcpyjest to najlepsza droga.

mpen
źródło
Czy to C99? Nie wierzę, że jest to możliwe w ANSI C.
Noldorin
6
Jest to możliwe zarówno w C89, jak i C99. Nie ma również znaczenia, czy jest to const, czy nie, chociaż ten pierwszy jest preferowany.
avakar
1
No cóż, const jest nowy i trzeba było określić rozmiar tablicy zewnętrznej (w tym przypadku 3), ale poza tym jest to całkowicie do przyjęcia K&R C. Mam starą książkę C chronioną prawami autorskimi z 1984 roku, która zawiera sekcję pokazującą jak Zrób to. Nazywają to „obszarpaną tablicą”. Oczywiście nie miał żadnych „operatorów”, a strcpy_s jest dla mnie nowy.
TED,
6
strcpy_s to funkcja Microsoft. Prawdopodobnie należy tego unikać, ponieważ nie ma go w standardzie C.
Cromulent
5
strcpy_s i inne „bezpieczne funkcje” są znormalizowane jako ISO / IEC TR 24731 (jest to norma opublikowana przez ISO i jako taka nie jest dostępna online za darmo; najnowszy szkic to open-std.org/jtc1/sc22/wg14/www /docs/n1225.pdf )
Pavel Minaev
14

Oto niektóre z twoich opcji:

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

Zaletą a2jest to, że możesz wykonać następujące czynności z literałami łańcuchowymi

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

A dla a3ciebie możesz wykonać następujące czynności:

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

Bo a1będziesz musiał użyć strcpy()(jeszcze lepiej strncpy()), nawet przy przypisywaniu literałów łańcuchowych. Powodem jest to a2, że a3są to tablice wskaźników i możesz sprawić, aby ich elementy (tj. Wskaźniki) wskazywały na dowolną pamięć, podczas gdy a1jest tablicą „tablicy znaków”, a zatem każdy element jest tablicą, która „posiada” własną pamięć ( co oznacza, że ​​ulega zniszczeniu, gdy wykracza poza zakres) - możesz kopiować tylko rzeczy do swojej pamięci.

To także prowadzi do niekorzystnej strony używania a2i a3- ponieważ wskazują one na przechowywanie statyczne (w którym przechowywane są literały łańcuchowe), którego zawartości nie można zmienić w niezawodny sposób (tj. Niezdefiniowane zachowanie), jeśli chcemy przypisać literały nie łańcuchowe do elementy a2lub a3- najpierw będziesz musiał dynamicznie przydzielić wystarczającą ilość pamięci, a następnie ich elementy wskażą tę pamięć, a następnie skopiuj do niej znaki - a następnie musisz pamiętać, aby cofnąć przydział pamięci po zakończeniu.

Bah - już tęsknię za C ++;)

ps Daj mi znać, jeśli potrzebujesz przykładów.

Faisal Vali
źródło
Potrzebowałem tablic ciągów dla projektu Arduino. Na koniec użyłem stylu A2. Początkowo próbowałem stylu a1 definiującego moją tablicę ciągów jako char a1 [] [2] = {"F3", "G3" ... itd. }, ponieważ miał przechowywać 2-znakowe długie łańcuchy. Dało to nieoczekiwany wynik, ponieważ zapomniałem, że terminator zerowy oznaczałby, że każdy łańcuch powinien mieć rozmiar co najmniej 3, aby przechowywać 2 znaki. Korzystając ze stylu a2, nie musiałem określać długości sznurka i może on również uwzględniać różne długości sznurka, więc postanowiłem się tego trzymać :-)
Jeromy Adofo
char (* a3 []) [] = {& "blah", & "hmm"}; => nie działa w g ++ Apple LLVM w wersji 9.1.0, ale działa w gcc
1234
12

Lub możesz zadeklarować typ struktury, który zawiera arry znaków (1 łańcuch), tworzą tablicę struktur, a tym samym tablicę wieloelementową

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 
 }

Jedną z zalet tego nad jakąkolwiek inną metodą jest to, że pozwala ci skanować bezpośrednio do łańcucha bez konieczności użycia strcpy;

FutureSci
źródło
10

W ANSI C:

char* strings[3];
strings[0] = "foo";
strings[1] = "bar";
strings[2] = "baz";
Noldorin
źródło
8
@Zifre: Całkowicie się nie zgadzam. Jest to bardzo część tego typu - w tym przypadku „wskaźnik char”. Co byś powiedział w każdym razie ... to część nazwy zmiennej? Widziałem wielu kompetentnych programistów używających tego stylu.
Noldorin
14
Tylko dla każdego, kto to czyta, chciałbym zauważyć, że Bjarne Stroustrup stawia * według typu ...
MirroredFate
1
@MirroredFate: Poprawnie. Rzeczywiście, jest to zalecana praktyka w C ++ z tego co wiem. Semantycznie nie ma dla mnie sensu umieszczanie go według identyfikatora, ze względu na sposób jego użycia. : /
Noldorin
16
@Noldorin char* foo, bar;jaki jest typ bar?
mASOUD
10
C został opracowany przez Dennisa Ritchiego w 1972 r., A w 1988 r. Wraz z Brianem Kernighanem opublikowali drugie wydanie K&R - The C Programming Language, książki, którą wiele osób uważa de facto za standard dla C. Umieścili * według identyfikatora.
Marius Lian
10

Jeśli ciągi są statyczne, najlepiej jest:

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

Chociaż nie jest to część podstawowego ANSI C, istnieje prawdopodobieństwo, że twoje środowisko obsługuje składnię. Ciągi te są niezmienne (tylko do odczytu), dlatego w wielu środowiskach zużywają mniej obciążenia niż dynamiczne budowanie tablicy ciągów.

Na przykład w małych projektach mikrokontrolerów ta składnia wykorzystuje pamięć programu zamiast (zwykle) cenniejszej pamięci RAM. AVR-C to przykładowe środowisko obsługujące tę składnię, ale podobnie jak większość innych.

Bryce
źródło
10

Jeśli nie chcesz śledzić liczby ciągów w tablicy i chcesz iterować nad nimi, po prostu dodaj na końcu ciąg NULL:

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

int i=0;
while(strings[i]) {
  printf("%s\n", strings[i]);
  //do something
  i++;
};
Siergiej
źródło
Uważam, że jest to poprawne tylko w C ++. W C, NULL nie ma gwarancji, że wynosi zero, dlatego pętla może nie pęknąć, kiedy powinna. Popraw mnie, jeśli się mylę.
Palec,
2
Nie mam pojęcia :) Możesz porównać z NULL w instrukcji while, jeśli chcesz.
Siergiej
9

Literały łańcuchowe to const char *s.

A twoje użycie nawiasu jest dziwne. Prawdopodobnie masz na myśli

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

który deklaruje tablicę dwóch wskaźników do stałych znaków i inicjuje je, aby wskazywały na dwie stałe w łańcuchu znaków.

dmckee --- były kot moderator
źródło
3

Twój kod tworzy tablicę wskaźników funkcji. Próbować

char* a[size];

lub

char a[size1][size2];

zamiast.

Zobacz wikibooks do tablic i wskaźników

Dario
źródło
1
czapki z głów za inne podejście ... Ludzie tacy jak ty robią stosy do przepełnienia ...
Sahu V Kumar
1

cześć, możesz spróbować tego poniżej:

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

dobry przykład użycia, tablica ciągów w c, jeśli chcesz

#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é
źródło
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");
}

Tutaj spróbuj tego !!!

Aditya
źródło
1
czy możesz wyjaśnić, dlaczego potrzebujesz pętli for ze zmienną j, tj. dla (j = 0; j <1; j ++)?
SouvikMaji
0

Brakowało mi w jakiś sposób bardziej dynamicznej tablicy ciągów, gdzie liczba ciągów może się zmieniać w zależności od wyboru w czasie wykonywania, ale w przeciwnym razie ciągi powinny zostać naprawione.

Skończyłem kodować fragment kodu w ten sposób:

#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 podnosi wskaźnik do ciągów tablic i zlicza liczbę używanych ciągów _countof funkcji. (Podobne do sizeof(arr) / sizeof(arr[0])).

I jest dodatkowa konwersja Ansi na Unicode przy użyciu A2Tmakra, ale może to być opcjonalne w twoim przypadku.

TarmoPikaro
źródło
-6

Dobrym sposobem jest zdefiniowanie łańcucha siebie.

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

To naprawdę takie proste.

IceCodr
źródło
4
Brakuje a ;, a jak to tworzy tablicę ciągów ?
keyser