9 Stimmen

Konvertieren Sie mit C ein dynamisch zugewiesenes int-Array so sauber wie möglich in eine durch Kommata getrennte Zeichenkette

Ich habe viel weniger Erfahrung mit C als mit höheren Sprachen. Bei Cisco verwenden wir C, und manchmal stoße ich auf etwas, das in Java oder Python leicht, in C aber sehr schwer zu bewerkstelligen wäre.

Ich habe ein dynamisch zugewiesenes Array von ganzen Zahlen ohne Vorzeichen, die ich in eine kommagetrennte Zeichenfolge für die Protokollierung konvertieren muss. Während die Integer wahrscheinlich nicht sehr groß sein, könnten sie konzeptionell überall von 0 bis 4.294.967.295 In Python, das ist eine kurze Zeile sein.

my_str = ','.join(my_list)

Wie elegant kann man das in C machen? Ich habe einen Weg gefunden, aber er ist ekelhaft. Wenn jemand einen schönen Weg kennt, es zu tun, bitte erleuchten Sie mich.

2 Stimmen

Können Sie die Art und Weise posten, wie Sie es in C gemacht haben?

2 Stimmen

Moment, join() ist eine Funktion der Klasse string!?

0 Stimmen

@BibedalShark: es ist C und nicht C++; keine Klasse

9voto

Jed Smith Punkte 15042

Der Code ist jetzt getestet und lässt sich unter gcc bauen.

Im Gegensatz zu anderen Antworten ist C99 nicht vorgeschrieben.

Das eigentliche Problem besteht darin, dass Sie die Länge der benötigten Zeichenkette nicht kennen. Eine Zahl zu erhalten ist so einfach wie sprintf("%u", *num) mit num um Ihr Angebot an int s, aber wie viel Platz werden Sie benötigen? Um zu vermeiden, dass ein Puffer überläuft, müssen Sie eine Menge Ganzzahlen im Auge behalten.

size_t join_integers(const unsigned int *num, size_t num_len, char *buf, size_t buf_len) {
    size_t i;
    unsigned int written = 0;

    for(i = 0; i < num_len; i++) {
        written += snprintf(buf + written, buf_len - written, (i != 0 ? ", %u" : "%u"),
            *(num + i));
        if(written == buf_len)
            break;
    }

    return written;
}

Beachten Sie, dass ich die Menge des verbrauchten Puffers im Auge behalte und verwende snprintf damit ich das Ende nicht überziehe. snprintf wird eine \0 aber da ich die buf + written Ich beginne mit der \0 des vorherigen snprintf .

Im Einsatz:

int main() {
    size_t foo;
    char buf[512];

    unsigned int numbers[] = { 10, 20, 30, 40, 1024 };

    foo = join_integers(numbers, 5, buf, 512);
    printf("returned %u\n", foo);
    printf("numbers: %s\n", buf);
}

Ausgänge:

returned 20
numbers: 10, 20, 30, 40, 1024

Erzwingen des Eintretens der Begrenzung statt des Überlaufens:

char buf[15];    
foo = join_integers(numbers, 5, buf, 14);
buf[14] = '\0';

Die erwarteten Ergebnisse:

returned 14
numbers: 10, 20, 30, 4

1 Stimmen

Warum sagen Sie, dass es C99 nicht mandatiert? snprintf ist aus C99.

0 Stimmen

Technisch gesehen, snprintf() ist eine C99-Funktion und nicht in C89. Sie ist jedoch weit genug verbreitet, um kein großes Problem darzustellen. Verwenden Sie außerdem sizeof(buf) und nicht 512, auch im Testcode.

1 Stimmen

Ich nehme an, man könnte für einige Plattformen argumentieren, dass snprintf() es in SUSV2.

2voto

Suppressingfire Punkte 3168

Sie können auch eine Bibliothek wie Glattes , die Funktionen enthält wie

gchar* g_strjoin (const gchar *separator, ...);

Verbindet eine Reihe von Zeichenfolgen zu einer langen Zeichenfolge, wobei zwischen den einzelnen Zeichen das optionale Trennzeichen eingefügt wird. Die zurückgegebene Zeichenkette sollte mit g_free() freigegeben werden.

(Sie müssen noch die g_snprintf() möglicherweise mit g_printf_string_upper_bound() um Platz zu schaffen)

2voto

Jonathan Leffler Punkte 694013

Und was ist damit?

char *join_int_list(const unsigned int *list, size_t n_items)
{
     enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
     char *space = malloc(SIZEOF_INT_AS_STR * n_items);
     if (space != 0)
     {
         size_t i;
         char *pad = "";
         char *dst = space;
         char *end = space + SIZEOF_INT_AS_STR * n_items;
         for (i = 0; i < n_items; i++)
         {
              snprintf(dst, end - dst, "%s%u", pad, list[i]);
              pad = ",";
              dst += strlen(dst);
         }
         space = realloc(space, dst - space + 1);
     }
     return(space);
}

Es liegt in der Verantwortung des Aufrufers, den zurückgegebenen Zeiger freizugeben - und zu prüfen, dass er nicht null ist, bevor er ihn verwendet. Die Funktion 'realloc()' gibt zusätzlichen Speicherplatz frei, wenn die zugewiesene Menge zu groß war, als dass es sich gelohnt hätte. Dieser Code geht munter davon aus, dass die Werte tatsächlich 32-Bit-Ganzzahlen ohne Vorzeichen sind; wenn sie größer sein können, muss die Aufzählung entsprechend angepasst werden.

Geprüfter Code:

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

char *join_int_list(const unsigned int *list, size_t n_items)
{
    enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
    char *space = malloc(SIZEOF_INT_AS_STR * n_items);
    if (space != 0)
    {
        size_t i;
        char *pad = "";
        char *dst = space;
        char *end = space + SIZEOF_INT_AS_STR * n_items;
        for (i = 0; i < n_items; i++)
        {
            snprintf(dst, end - dst, "%s%u", pad, list[i]);
            pad = ",";
            dst += strlen(dst);
        }
        space = realloc(space, dst - space + 1);
    }
    return(space);
}

int main(void)
{
    static unsigned int array[]= { 1, 2, 3, 49, 4294967295U, 0, 332233 };
    char *str = join_int_list(array, sizeof(array)/sizeof(array[0]));
    printf("join: %s\n", str);
    free(str);
    return(0);
}

Überprüft mit valgrind - scheint OK zu sein.


Diskussion über die Umwandlung INT_MAX o UINT_MAX zur Zeichenkette:

Sie können sizeof("," STRINGIZE(INT_MAX)) verwenden, anstatt es hart zu kodieren. Das Makro stringize ist ein gängiges cpp-Werkzeug, das als #define STRINGIZE_(v) #v und #define STRINGIZE(v) STRINGIZE_(v) definiert werden kann. - R. Kumpel

@R Pate: gute Idee - ja, das könnte man recht effektiv machen. Tatsächlich gibt es zwei interessante Ideen: die Verwendung von String-Verkettung mit sizeof() (Klammern sind für die Klarheit erforderlich - aber die String-Verkettung geschieht früh genug, so dass der Compiler nicht besorgt ist) und die Verwendung einer stringize-Operation auf INT_MAX . - Jonathan Leffler

Die Verwendung einer Stringize-Operation auf INT_MAX ist keine gute Idee - es muss nur ein "konstanter Ausdruck" sein, nicht unbedingt eine Folge von Ziffern. Er könnte als ((1<<32)-1) oder sogar als etwas Verrücktes wie __int_max definiert werden, solange der Compiler die Verwendung überall dort zulässt, wo man einen konstanten Ausdruck verwenden kann. - caf

Und @caf hat Recht. Betrachten Sie diesen Code:

#include <limits.h>
#include <stdio.h>

#undef INT_MAX
#define INT_MAX (INT_MIN-1 - 100 + 100)

#define QUOTER(x)   #x
#define STRINGIZER(x)   QUOTER(x)

enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
enum { SIZEOF_INT_AS_STR_1 = sizeof(STRINGIZER(INT_MAX) ",")-1 };

int main(void)
{
    printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1);
    printf("INT_MAX  = %d\n", INT_MAX);
    printf("UINT_MAX = %u\n", UINT_MAX);
    return(0);
}

Das lässt sich nicht einmal unter MacOS X 10.5.8 mit GCC 4.0.1 kompilieren - weil der Bezeichner INT_MAX ist nicht definiert. Eine vorläufige Version des Codes, die nicht gedruckt wurde INT_MAX o UINT_MAX funktionierte; sie zeigte, dass der Wert von SIZEOF_INT_AS_STR_1 war 31 - also hatte @caf recht. Durch das Hinzufügen der doppelten Überprüfung der Werte von INT_MAX y UINT_MAX dann nicht kompilieren, was mich überrascht hat. Ein Blick auf die Ausgabe von gcc -E zeigt, warum:

enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
enum { SIZEOF_INT_AS_STR_1 = sizeof("((-INT_MAX - 1)-1 - 100 + 100)" ",")-1 };

int main(void)
{
 printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1);
 printf("INT_MAX  = %d\n", ((-INT_MAX - 1)-1 - 100 + 100));
 printf("UINT_MAX = %u\n", (((-INT_MAX - 1)-1 - 100 + 100) * 2U + 1U));
 return(0);
}

Wie vorhergesagt, ist die Zeichenfolge für SIZEOF_IN_AS_STR_1 ist überhaupt keine Ziffernfolge. Der Präprozessor kann den Ausdruck auswerten (soweit er das muss), muss aber keine Zeichenkette erzeugen.

Die Ausweitung der INT_MAX stellt sich heraus als in Bezug auf INT_MIN y INT_MIN ist seinerseits definiert in Form von INT_MAX Wenn also die umgeschriebene INT_MAX ausgewertet wird, wird die "rekursive Expansion" durch die Arbeitsregeln des C-Präprozessors verhindert, und INT_MAX erscheint in der vorverarbeiteten Ausgabe - zur Verwirrung aller.

Es gibt also mehrere Gründe, warum sich die oberflächlich betrachtet attraktive Idee als schlecht herausstellt.

0 Stimmen

Beachten Sie, dass SIZEOF_INT_AS_STR nur für Systeme mit 32-Bit-Ints gültig ist.

0 Stimmen

Ja, ich habe einen entsprechenden Kommentar hinzugefügt, aber der vom Fragesteller angegebene Bereich erzwingt dies, so dass er die Spezifikation erfüllt.

1 Stimmen

Sie können verwenden sizeof("," STRINGIZE(INT_MAX)) anstatt sie hart zu kodieren. Das Makro stringize ist ein gängiges cpp-Werkzeug, das wie folgt definiert werden kann #define STRINGIZE_(v) #v y #define STRINGIZE(v) STRINGIZE_(v) .

2voto

DigitalRoss Punkte 138823

Werdet ihr nach Strich und Faden bezahlt? :-)


f() wird deklariert mit einem char * Parameter für Prototyping-Zwecke, ändern Sie einfach char -> int . Ich habe die Frage so interpretiert, dass eine Zeichenkette als Ausgabe erforderlich ist und nicht nur ein Code zum Schreiben in eine Datei.

#define PRINT(s, l, x, i) snprintf((s), (l), "%s%d", (i) ? ",":"", (x)[i]);

char *f(size_t len, char *x) {
  size_t  i, j = 0, k;

  for(i = 0; i < len; ++i)
      j += PRINT(NULL, 0, x, i);
  char *result = malloc(k = ++j);
  for(*result = i = j = 0; i < len; ++i)
      j += PRINT(result + j, k - j, x, i);
  return result;
}

Hier ist ein Testrahmen:

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

// put f() here

int main(int ac, char **av) {
    for(int i = 1; i < ac; ++i) { 
        printf("%s\n", f(strlen(av[i]), av[i]));
    }
    return 0;
}

0 Stimmen

Wenn Sie bereit sind, einen statisch zugewiesenen temporären Puffer zu verwenden, lassen Sie die erste Schleife einfach weg...

1voto

Doug T. Punkte 61739
Ihr und eure unnötigen Sonderfälle, die sich um das nachgestellte Komma kümmern... Es ist billiger, einfach das letzte Komma zu zerstören, als jedes Mal, wenn die Schleife läuft, eine bedingte Prüfung durchzuführen.

)

include <stdio.h>

char toStr(int arr[], unsigned int arrSize, char buff[]) { if (arr && arrSize && buff) { int currInt = arr; char currStr = buff; while (currInt < (arr + arrSize)) { currStr += sprintf(currStr, "%d,", currInt++); } *--currStr = '\0'; } return buff; }

int main() { int arr[] = {1234, 421, -125, 15251, 15251, 52}; char buff[1000];

printf("Arr is:%s\n", toStr(arr, 6, buff));    

}

Geht davon aus, dass buff groß genug ist, um (Länge des größten int + 2) * arrSize) zu sein. Inspiriert von meinem memcpy :)

Editar Ich erkannte, dass ich vorhin einen Hirnfurz hatte und einfach um den Rückgabewert von sprintf hätte inkrementieren können, anstatt temp zu speichern. Anscheinend tun andere Antworten dies auch, ich habe meine Antwort bearbeitet, um die 2 unnötigen Zeilen zu entfernen.

Bearbeiten2 Sieht aus wie wrang-wrang mir zuvorkommen! Seine Antwort ist so ziemlich identisch mit meiner und wurde früher eingereicht. Ich schlage demütig vor, ihm eine + 1 zu geben.

0 Stimmen

*--currStr = '\0' braucht einen erklärenden Kommentar für jeden, der den Code nach Ihnen pflegen muss. (i == 0 ? :) nicht.

0 Stimmen

Ich schätze, es ist eine Frage der Meinung, aber ich +1'd Ihre für tatsächlich sicherer über die Zeichenfolge len.

1 Stimmen

Dies bricht ab, wenn arrSize == 0. Auch müssen Sie s/size/arrSize/, mindestens.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X