2009-06-20 3 views
7

Sto iniziando a imparare C leggendo K & R e passando attraverso alcuni degli esercizi. Dopo un po 'in lotta, sono stato finalmente in grado di completare l'esercizio 1-19 con il codice qui sotto:Perché non posso copiare un array usando `=`?

/* reverse: reverse the character string s */ 
void reverse(char s[], int slen) 
{ 
    char tmp[slen]; 
    int i, j; 

    i = 0; 
    j = slen - 2; /* skip '\0' and \n */ 

    tmp[i] = s[j]; 
    while (i <= slen) { 
    ++i; 
    --j; 
    tmp[i] = s[j]; 
    } 

    /* code from copy function p 29 */ 
    i = 0; 
    while ((s[i] = tmp[i]) != '\0') 
    ++i; 

} 

La mia domanda è per quanto riguarda che l'ultimo pezzo di codice in cui la matrice tmp char viene copiato s. Perché non funziona un semplice s = tmp;? Perché si deve ripetere l'array copiando l'indice per indice?

+4

K & R non ha utilizzato la soluzione: hanno scritto per C89, non C99, ma la soluzione utilizza un VLA (array a lunghezza variabile) che è stato aggiunto solo in C99. Inoltre, non vi è alcuna garanzia che ci sia una nuova riga nella stringa - AFAIK. E convenzionalmente, la lunghezza di una stringa esclude già il terminale null. Ciò significa che il tuo '-2' è probabilmente errato. La condizione nel primo ciclo while dovrebbe usare '<' invece di '<='; non c'è virtù nello scambiare il personaggio centrale con se stesso. La soluzione più veloce scambia l'array in situ senza array temporaneo. –

+1

Grazie per l'intuizione. Hai ragione, stavo pensando al -2 stamattina dato che quello era un trucco che ho fatto ieri sera alle 3 del mattino solo per farlo funzionare. Probabilmente dovrei semplicemente mettere un if (s [i]! = '\ N' || s [i]! = '\ 0') nel ciclo in while. – felideon

+1

Vecchio commento, ma (s [i]! = '\ N' || s [i]! = '\ 0') è sempre vero. – kyrias

risposta

20

Forse sono solo vecchio e scontroso, ma le altre risposte Ho visto sembrano mancare completamente il punto.

C non esegue le assegnazioni di array, punto. Non è possibile assegnare un array a un altro array con un semplice compito, a differenza di altri linguaggi (PL/1, ad esempio, Pascal e molti dei suoi discendenti - Ada, Modula, Oberon, ecc.). Né C ha davvero un tipo di stringa. Ha solo matrici di caratteri e non è possibile copiare matrici di caratteri (non più di quanto è possibile copiare matrici di qualsiasi altro tipo) senza utilizzare un ciclo o una chiamata di funzione. [I valori letterali delle stringhe non contano realmente come tipo stringa.]

L'unica matrice di tempo che viene copiata è quando l'array è incorporato in una struttura e si esegue un'assegnazione di struttura.

Nella mia copia di K & R 2nd Edition, l'esercizio 1-19 richiede una funzione reverse(s); nella mia copia di K & R 1st Edition, era l'esercizio 1-17 invece di 1-19, ma è stata posta la stessa domanda.

Poiché i puntatori non sono stati trattati in questa fase, la soluzione deve utilizzare gli indici anziché i puntatori. Credo che porta a:

#include <string.h> 
void reverse(char s[]) 
{ 
    int i = 0; 
    int j = strlen(s) - 1; 
    while (i < j) 
    { 
     char c = s[i]; 
     s[i++] = s[j]; 
     s[j--] = c; 
    } 
} 

#ifdef TEST 
#include <stdio.h> 
int main(void) 
{ 
    char buffer[256]; 
    while (fgets(buffer, sizeof(buffer), stdin) != 0) 
    { 
     int len = strlen(buffer); 
     if (len == 0) 
      break; 
     buffer[len-1] = '\0'; /* Zap newline */ 
     printf("In: <<%s>>\n", buffer); 
     reverse(buffer); 
     printf("Out: <<%s>>\n", buffer); 
    } 
    return(0); 
} 
#endif /* TEST */ 

compilare questo con -DTEST per includere il programma di test e senza avere solo la funzione di reverse() definita.

Con la firma della funzione indicata nella domanda, si evita di chiamare strlen() due volte per riga di input. Nota l'uso di fgets() - anche nei programmi di test, è una cattiva idea usare gets(). Lo svantaggio di fgets() rispetto a gets() è che fgets() non rimuove la riga finale finale dove lo fa gets(). I lati positivi di fgets() sono che non si ottiene un overflow di array e si può stabilire se il programma ha trovato una nuova riga o se ha esaurito lo spazio (oi dati) prima di incontrare una nuova riga.

4

Poiché sia ​​s che tmp sono destinatari della memoria. Se s = tmp, entrambi i puntatori puntano allo stesso array.

Supponiamo di avere

char s[] ="ab"; 

/* 
* Only for explanatory purposes. 
* 
*/ 
void foo(char s[]){ 
    char tmp [] = "cd"; 
    s= tmp; 
} 

foo(s); 

dopo s = tmp si avrebbe

s[0] : 'c' 
s[1] : 'd' 
s[2] : '\0' 

Anche se entrambi gli array hanno gli stessi dati, un cambiamento di tmp, interesserà tutti e due, perché entrambi gli array sono in realtà gli stessi. Entrambi contengono dati che si trovano nello stesso indirizzo di memoria. Quindi, cambiando qualsiasi posizione dell'array tmp, o distruggendo l'array tmp, s sarebbe interessato allo stesso modo.

Effettuando il ciclo sull'array, ciò che si sta facendo è spostare un pezzo di dati da un indirizzo di memoria a un altro.

Nella mia copia di K & R, i puntatori sono spiegati nel capitolo 4. Una rapida occhiata alle prime pagine può essere d'aiuto.

+1

Trovo questo confuso e capisco l'aritmetica del puntatore. –

+0

Ben S: Grazie per il vostro feedback. Ho organizzato la mia risposta. – Tom

+1

Non è possibile eseguire l'assegnazione s = tmp; Gli array non possono essere assegnati in C. I puntatori possono però, ma un puntatore non è un array. (Il nome di un array viene promosso a un puntatore al suo primo elemento quando viene usato come valore, quindi se "s" era un puntatore avresti avuto ragione. – nos

0

perché tmp è un puntatore e occorre ottenere una copia, non un "collegamento".

+1

tmp non è un puntatore; è un array. –

+0

tmp è un carattere *, un puntatore. – Macarse

+1

Raccomando questo animale domestico http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/484900#484900 –

-2

Una risposta molto semplice sarebbe - entrambi s e tmp sono puntatori a una posizione di memoria e non agli array stessi. In altre parole, s e tmp sono indirizzi di memoria in cui sono memorizzati i valori dell'array ma non i valori stessi. E uno dei modi più comuni per accedere a questi valori di array è usando indici come s [0] o tmp [0].

Ora, se tenterai semplicemente di copiare, s = tmp, l'indirizzo di memoria dell'array tmp verrà copiato su s. Ciò significa che, l'array s originale andrà perso e anche il puntatore di memoria s ora punterà all'array tmp.

Capirai bene questi concetti a tempo debito, quindi continua a leggere il libro. Spero che questa spiegazione elementare aiuti.

+0

Lol, sono troppo confuso ?? – Elitecoder

+1

Raccomando questo animale domestico http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/484900#484900 –

+0

-1: 'tmp' è un array, non un puntatore. Gli array non sono indicatori, sebbene siano strettamente correlati. Sfortunatamente, solo 10k utenti possono vedere le informazioni a cui è rivolto. –

8

L'array tmp è stato dichiarato su stack e quindi al termine del metodo, la memoria utilizzata per contenere i valori verrà liberata a causa di scoping.

s = tmp significa che s deve puntare alla stessa posizione di memoria di tmp. Ciò significa che quando viene liberato tmp, s continuerà a puntare a una posizione di memoria libera e non valida ora possibile.

Questo tipo di errore è denominato dangling pointer.

Modifica: Questo non è un modificatore penzolante come indicato nei commenti di questa risposta.Il problema è che dicendo s = tmp cambia solo ciò a cui punta il parametro, non ciò che l'array reale è stato passato.

Inoltre, è possibile eseguire l'inverso con una sola passata e senza allocare un intero array in memoria semplicemente scambiando i valori in luogo uno per uno:

void reverse(char s[], int slen) { 
    int i = 0;  // First char 
    int j = slen - 2; // Last char minus \n\0 
    char tmp = 0;  // Temp for the value being swapped 

    // Iterate over the array from the start until the two indexes collide. 
    while(i < j) { 
     tmp = s[i]; // Save the eariler char 
     s[i] = s[j]; // Replace it with the later char 
     s[j] = tmp; // Place the earlier char in the later char's spot 
     i++;   // Move forwards with the early char 
     j--;   // Move backwards with the later char 
    } 
} 
+1

+1 - perché a differenza del carico di altre risposte, in realtà si dice che tmp * è un array * e * non * un puntatore. Le altre risposte mi confonderebbero se fossi in lui, pensando "eh, di quale puntatore parlano? Tmp è un array!" –

+2

Non parlare di tmp come array sarebbe come tenerlo sotto confusione più a lungo. tmp è un puntatore a un array, questa è la definizione corretta. Prima impara, meglio è, no? – Elitecoder

+1

! Litb temp è sicuramente un puntatore. È un puntatore al primo carattere dell'array. Capire come scrivere l'operatore [] come una macro richiederebbe di comprenderlo pienamente. – llamaoo7

0

In caso di s = tmp, il valore di tmp che è anche l'indirizzo iniziale dell'array, verrebbe copiato in s.

In questo modo sia s che tmp puntano allo stesso indirizzo in memoria, che a mio avviso non è lo scopo.

applausi

0

C'è un interessante sub-thread in questo thread su vettori e puntatori ho trovato su wikipedia this link con un frammento di codice particolare che mostrano quanto sia C 'plastilina' può essere!

/* x designates an array */ 
x[i] = 1; 
*(x + i) = 1; 
*(i + x) = 1; 
i[x] = 1; /* strange, but correct: i[x] is equivalent to *(i + x) */ 

Naturalmente ciò che è ancora più confusa in C è che io possa fare questo:

unsigned int someval = 0xDEADD00D; 
char *p = (char *)&someval; 

p[2] = (char)0xF0; 

Così l'interchangibility di puntatori e array sembra così infossati nel linguaggio C da essere quasi intenzionale .
Cosa pensano tutti gli altri?

--- Original Post ---
s e tmp sono entrambi i puntatori, quindi facendo s = tmp sarà sufficiente fare s punto all'indirizzo dove tmp vive in memoria.
Un altro problema con ciò che viene delineato è che tmp è una variabile locale, quindi diventerà 'indefinito' quando esce dall'ambito di applicazione quando la funzione ritorna.

Assicurati accuratamente afferrare questi tre concetti e non vi sbaglierete

  1. Ambito
  2. La differenza tra stack e heap
  3. Puntatori

speranza che aiuta e continua!

+1

tmp è un array, non un puntatore. –

0

Provate a sperimentare e vedere cosa succede quando si fanno le cose in questo modo:

void modifyArrayValues(char x[], int len) 
{ 
    for (int i = 0; i < len; ++i) 
     x[i] = i; 
} 

void attemptModifyArray(char x[], int len) 
{ 
    char y[10]; 
    for (int i = 0; i < len; ++i) 
     y[i] = i; 
    x = y; 
} 


int main() 
{ 
    int i = 0; 
    char x[10]; 
    for (i = 0; i < 10; ++i) 
     x[i] = 0; 

    attemptModifyArray(x, 10); 
    for (i=0; i < 10; ++i) 
     printf("%d\n", x[i]); // x is still all 0's 

    modifyArrayValues(x, 10); 
    for (i=0; i < 10; ++i) 
     printf("%d\n", x[i]); // now x has 0-9 in it 
} 

Cosa succede quando si modifica la matrice direttamente in attemptModifyArray, si sono solo sovrascrivendo una copia locale del l'indirizzo della matrice x. Quando si ritorna, l'indirizzo originale è ancora nella copia di x.

Quando si modificano i valori nella matrice in modifyArrayValues, si modifica la matrice effettiva stessa che ha il proprio indirizzo memorizzato nella copia locale modifyArrayValues di x. Al tuo ritorno, x è ancora in attesa sullo stesso array, ma hai modificato i valori in quell'array.

1

Per completare la discussione qui ci sono altri due modi possibili per invertire come stringa:

void reverse(char string1[], char string2[]) 
{ 
    int i = 0, len = 0; 

    while(string2[len] != '\0') // get the length of the string 
     len++; 

    while(len > 0) 
    { 
    string1[i] = string2[len-1]; // copy the elements in reverse 
    i++; 
    len--; 
    } 
    string1[i] = '\0'; // terminate the copied string 
} 

O in modo ricorsivo:

void reverse (const char *const sPtr) 
{ 
    //if end of string 
    if (sPtr[0] == '\0') 
    { 
    return; 
    } 
    else //not end of the string... 
    { 
    reverse(&sPtr[1]); //recursive step 
    putchar(sPtr[0]); //display character 
    } 
} 
+0

La seconda soluzione visualizza la stringa in ordine inverso senza modificare la stringa, mentre l'esercizio richiede che la stringa venga invertita senza stamparla. La prima soluzione sarebbe meglio se usasse strlen() e se il pedice '[len-1]' evitasse la sottrazione ripetuta (diminuendo il valore di len prima di iniziare il ciclo). –