PDA

View Full Version : [C] Vettore di puntatori


kwb
10-07-2010, 17:34
Devo fare un programma che creato un array di puntatori, ad ogni indice, inserisco una stringa di testo ( di lunghezza indefinita ).
Siccome quando è stato assegnato l'esercizio, non ci era dato sapere come si allocava dinamicamente la memoria, deduco che il vettore avrà una lunghezza massima specificata ( quindi x puntatori ).
Il mio problema ora è capire come caricare le stringhe in ogni indice dell'array.

Sui libri che ho, dando uno sguardo, utilizzano tutti il malloc per fare ciò... Vorrei capire se è possibile farlo senza specificare la lunghezza massima di ogni stringa ( penso di si visto che comunque ogni indice punta ad un indirizzo ).

Ho provato una cosa del genere:

/*
Write a C function that takes an array of pointers to strings as an argument and sorts the array in ascending length order.
Write also a main that tests the function by sorting a sequence of words read from keyboard.
Hint: For the main, you can re-use the functions of the stringsort.c program.
For the sort function, you can modify the sort function in the stringsort.c program.

*/
#include <stdio.h>
#define MAX 10 //Maximum number of strings

char *sarray[MAX];

void string_sort (void);
void get_size_sarray( int *n);
void get_strings(int *n);
void get_lenght(void);

int main (void)
{
int n; //How many string we want to enter

printf("Enter how many strings you want to enter\n");
printf("This program can hold a maximum of %d strings\n", MAX);
get_size_sarray(&n);
get_strings(&n);

return 0;
}

void get_size_sarray(int *n)
{
scanf("%d", n);
while( *n <= 0 || *n > MAX)
{
printf("Invalid number entered, try again\n");
scanf("%d\n", n);
}

}

void get_strings(int *n)
{
int i;

for ( i=0; i < *n; i++)
scanf("%s", sarray[i]);
}


Però va in crash il programma, come devo fare?

cionci
10-07-2010, 18:18
void get_strings(int *n)

Qui non ha senso fare il passaggio per indirizzo.

Chiaro che tu abbia problemi, non essendo stato allocato sarray[i].
L'unico modo è allocare staticamente il vettore di stringhe:

char sarray[SMAX][MAX];

Consiglio: non usare mai variabili globali. Prendila come regola ;)

kwb
10-07-2010, 18:55
void get_strings(int *n)

Qui non ha senso fare il passaggio per indirizzo.

Chiaro che tu abbia problemi, non essendo stato allocato sarray[i].
L'unico modo è allocare staticamente il vettore di stringhe:

char sarray[SMAX][MAX];

Consiglio: non usare mai variabili globali. Prendila come regola ;)

Ho pensato di farlo per indirizzo perchè sarebbe poi magari sarebbe tornato utile poterlo modificarlo globalmente, non si sa mai.
Per la variabile globale, so che non è buona abitudine usarle, ma ho avuto due professori durante il corso di programmazione: il primo si sarebbe fatto fucilare prima di usare una variabile globale, l'altro era solito impostare alcune variabili come globali ( quelle che quasi sicuramente vengono chiamate in ogni funzione del programma ). Mi pare una buona idea fare così per questo tipo di variabili, invece che chiamarle ogni volta... Alla fine chiamarle in ogni funzione e avere la memoria allocata per queste variabili durante tutta l'esecuzione del programma è pressochè uguale no?

Comunque per il vettore di puntatori: scritto come hai detto tu, vado a porre un limite al numero massimo di caratteri di ogni stringa, o sbaglio?

cionci
10-07-2010, 19:07
Ho pensato di farlo per indirizzo perchè sarebbe poi magari sarebbe tornato utile poterlo modificarlo globalmente, non si sa mai.
Se non lo cambi dentro la funzione, passarlo per indirizzo non serve a niente, anche se lo vai a modificare globalmente.
Alla fine chiamarle in ogni funzione e avere la memoria allocata per queste variabili durante tutta l'esecuzione del programma è pressochè uguale no?
E' una questione diversa. Comode lo sono sicuramente, ma programmando in modo ben strutturato le variabili globali tendono a rendere meno leggibile una funzione. Questo perché non si capisce subito a colpo d'occhio da quali parametri questa funzione dipende.
Metti caso volessi spostare la funzione in un altro modulo... Ti toccherebbe usare extern in ogni modulo in cui usi quella variabile globale.
Comunque per il vettore di puntatori: scritto come hai detto tu, vado a porre un limite al numero massimo di caratteri di ogni stringa, o sbaglio?
Certo, ma se non usi l'allocazione dinamica non puoi fare altrimenti.

kwb
11-07-2010, 10:38
Se non lo cambi dentro la funzione, passarlo per indirizzo non serve a niente, anche se lo vai a modificare globalmente.
Intendi dire, se non lo cambi dopo avergli già assegnato un valore.


E' una questione diversa. Comode lo sono sicuramente, ma programmando in modo ben strutturato le variabili globali tendono a rendere meno leggibile una funzione. Questo perché non si capisce subito a colpo d'occhio da quali parametri questa funzione dipende.
Metti caso volessi spostare la funzione in un altro modulo... Ti toccherebbe usare extern in ogni modulo in cui usi quella variabile globale. Uhm intuisco il problema di fondo, anche se non sono giunto ancora fino a questo "livello" di programmazione


Certo, ma se non usi l'allocazione dinamica non puoi fare altrimenti.
Ma scrivere *sarray[MAX] e sarray[][MAX] non è la stessa cosa?
Se io lo pongo come argomento di funzione la sintassi è corretta...
Quando lavoravo con le matrici, negli argomenti definivo solo l'ultima dimensione.

cionci
11-07-2010, 11:02
Intendi dire, se non lo cambi dopo avergli già assegnato un valore.
Non ti seguo...
Quello che sto dicendo è che se il valore non viene modificato all'interno della funzione, non ha senso passare un argomento per puntatore.

Ma scrivere *sarray[MAX] e sarray[][MAX] non è la stessa cosa?
Se io lo pongo come argomento di funzione la sintassi è corretta...
Quando lavoravo con le matrici, negli argomenti definivo solo l'ultima dimensione.
Solo quando lo passi come argomento di una funzione. Questo perché gli array vengono passati tramite il puntatore. Non è quindi necessario conoscere la dimensione di ogni singolo vettore, ma basta il puntatore al primo elemento.

Però la memoria per contenere gli elementi di ogni singolo vettore deve essere allocata. Quindi l'unico modo che attualmente conosci è quello di creare una matrice di caratteri.
Così come per allocare una stringa usi:

char s[MAX];

così per allocare un vettore di stringhe dovrai usare

char v[MAX][N];

tuccio`
11-07-2010, 11:23
Intendi dire, se non lo cambi dopo avergli già assegnato un valore.se la funzione avesse un prototipo

void get_size_sarray(int n):

invece di

void get_size_sarray(int *n);

daresti la garanzia che il valore di n non viene modificato (perché eventuali modifiche non si ripercuoterebbero sul chiamante).. e funzionerebbe comunque

quello che ti sta dicendo è che se non serve realmente a qualcosa scriverci (ad esempio potresti avere due valori da ritornare, quindi uno lo ritorni e uno lo scrivi in una variabile di cui ti sei fatto passare il puntatore) è bene che tu garantisca che non ci scrivi passando per valore

a volte ti può anche capitare di dover necessariamente passare un puntatore (come per le stringhe e gli array) anche se non devi modificare niente, se ci fai caso, in questi casi, le funzioni della standard library che usano le stringhe e che non le modificano, come strcmp o printf, prendono un const char * proprio per garantire che non vengano modificate, quindi tu, dopo averle chiamate, sei certo che siano rimaste uguali a quelle di prima.. quello che sto cercando di dire in questo italiano stentato è che è bene fornire una garanzia che le variabili nel main rimangano invariate se non è necessario

kwb
11-07-2010, 15:15
Non ti seguo...
Quello che sto dicendo è che se il valore non viene modificato all'interno della funzione, non ha senso passare un argomento per puntatore.

se la funzione avesse un prototipo

void get_size_sarray(int n):

invece di

void get_size_sarray(int *n);

daresti la garanzia che il valore di n non viene modificato (perché eventuali modifiche non si ripercuoterebbero sul chiamante).. e funzionerebbe comunque

quello che ti sta dicendo è che se non serve realmente a qualcosa scriverci (ad esempio potresti avere due valori da ritornare, quindi uno lo ritorni e uno lo scrivi in una variabile di cui ti sei fatto passare il puntatore) è bene che tu garantisca che non ci scrivi passando per valore

a volte ti può anche capitare di dover necessariamente passare un puntatore (come per le stringhe e gli array) anche se non devi modificare niente, se ci fai caso, in questi casi, le funzioni della standard library che usano le stringhe e che non le modificano, come strcmp o printf, prendono un const char * proprio per garantire che non vengano modificate, quindi tu, dopo averle chiamate, sei certo che siano rimaste uguali a quelle di prima.. quello che sto cercando di dire in questo italiano stentato è che è bene fornire una garanzia che le variabili nel main rimangano invariate se non è necessario

Si, era quello che intendevo dire.



Solo quando lo passi come argomento di una funzione. Questo perché gli array vengono passati tramite il puntatore. Non è quindi necessario conoscere la dimensione di ogni singolo vettore, ma basta il puntatore al primo elemento.

Però la memoria per contenere gli elementi di ogni singolo vettore deve essere allocata. Quindi l'unico modo che attualmente conosci è quello di creare una matrice di caratteri.
Così come per allocare una stringa usi:

char s[MAX];

così per allocare un vettore di stringhe dovrai usare

char v[MAX][N];Quindi in realtà io non sto creando un vettore di puntatori, ma un semplice array bidimensionale, giusto?

cionci
11-07-2010, 15:25
Si, era quello che intendevo dire.


Quindi in realtà io non sto creando un vettore di puntatori, ma un semplice array bidimensionale, giusto?
Come un stringa è un array monodimensionale, allora un array di stringhe è un array bidimensionale ;)

kwb
07-08-2010, 11:44
Riesumo il topic.
Ho reiniziato con un programma semplificato, cercando prima di fare in modo che mi ordini le parole contenute in un file, ecco ciò che ho scritto ( non è tutto di mio pugno, ho preso un po' dal libro cercando di capire esattamente cosa facesse ogni comando ):

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

#define MWORDLENGHT 30 //We need to define a maximum word lenght since we must know what's the lenght of each string
#define MSTRING 10 //Maximum number of strings in array of pointers

void error_calloc (void); //Memory allocation failed, error!
void error_string(void); //The string is too long ( too many words ), error!
void error_word(void); //The word is too long, error!

void word_sort (char *w[], int n);
void swap(char **p, char **q );
void write_words(char *w[], int n);

int main (void)
{
char word[MWORDLENGHT]; //Stores single words
char *w[MSTRING]; //Stores strings ( Array of pointers )
int n; //Stores how many words we have to sort
int i;

for (i=0; scanf("%s", word) == 1; i++) //This stops when scanf cannot read anymore char
{
if ( i >= MSTRING )
error_string();
if ( strlen(word) >= MWORDLENGHT )
error_word();

w[i] = calloc(strlen(word)+1, sizeof(char)); //Memory allocation, we add +1 to have space for \0
if ( w[i] == NULL )
error_calloc();
strcpy ( w[i], word );
}
n = i; //Now we now how many words we must sort
word_sort( w, n); //Sort words
write_words( w, n); //Write words

return 0;
}

void error_calloc (void)
{
printf("%s", "ERROR: Failed in using calloc(), cannot allocate requested memory \n QUIT\n");
exit(1); //Exit the whole program
}

void error_string (void)
{
printf("ERROR: you are trying to sort more than %d words, that's not allowed\n QUIT", MSTRING);
exit(1);
}

void error_word(void)
{
printf("%s%d%s", "ERROR: You entered a word with lenght greater than ", MWORDLENGHT, "\n QUIT\n");
exit(1);
}

void word_sort ( char *w[], int n)
{
int i, j;
for (i=0; i < n; i++)
for (j=0; j<n; j++)
if ( strcmp(w[i], w[j]) > 0 )
swap ( &w[i], &w[j]);
}

void swap ( char **p, char **q )
{
char *tmp; //Temporary stores address of pointer

tmp = *p;
*p = *q;
*q = tmp;
}

void write_words(char *w[], int n)
{
int i;
for ( i=0; i < n; i++)
printf("%s\n", w[i]);
}
A parte il puntatore di puntatore ( usato nella funzione swap, per scambiare le parole ) che non ho mai utilizzato, cmq sia mi pare di capire che l'esempio del libro voglia scambiare gli indirizzi all'interno del vettore e non le parole stesse, corretto? Detto questo, il programma non funziona perchè non riesco a fermare lo scanf prima che oltrepassi le dieci parole... Perchè? :muro:

cionci
07-08-2010, 12:25
Per fermare la scanf:

for (i=0; scanf("%s", word) == 1 && i < MSTRING; i++)

Esatto, lo swap scambia i puntatori ;)

kwb
07-08-2010, 15:08
Per fermare la scanf:

for (i=0; scanf("%s", word) == 1 && i < MSTRING; i++)

Esatto, lo swap scambia i puntatori ;)

Perchè mai scambiare i puntatori? Non è più semplice cambiare la posizione delle stringhe?

cionci
07-08-2010, 16:59
Perchè mai scambiare i puntatori? Non è più semplice cambiare la posizione delle stringhe?
No, al contrario, scambiando i puntatori non devi ricopiare le stringhe.
Parlando in termini di byte, supponendo 4 la dimensione di un puntatore, swappare i puntatori comporta la lettura e la scrittura di 3*4 byte (swap = a, a = b, b = swap).
Scambiare due stringhe significa leggere e scrivere strlen(a) + strlen(b) + strlen(b oppure a) byte.
Senza contare che quando non si ha un limite superiore sulla dimensione della stringa siamo costretti anche a fare l'allocazione dinamica delle stringa che deve contenere lo swap.

kwb
07-08-2010, 18:35
No, al contrario, scambiando i puntatori non devi ricopiare le stringhe.
Parlando in termini di byte, supponendo 4 la dimensione di un puntatore, swappare i puntatori comporta la lettura e la scrittura di 3*4 byte (swap = a, a = b, b = swap).
Scambiare due stringhe significa leggere e scrivere strlen(a) + strlen(b) + strlen(b oppure a) byte.
Senza contare che quando non si ha un limite superiore sulla dimensione della stringa siamo costretti anche a fare l'allocazione dinamica delle stringa che deve contenere lo swap.

Ah ho capito, insomma è più conveniente in termini di prestazioni

kwb
22-08-2010, 09:51
Devo veramente capire come si fa sto esercizio. Sono giunto alla conclusione che per ordinare una stringa si intende ordinare una serie di parole date in input.
Quindi, ho pensato a come operare:.

1. CARICAMENTO:
- Inserisco la stringa
- Ad ogni spazio carico la parola nel vettore di puntatori
- Alloco spazio sufficiente per la parola dentro il vettore di puntatori
- Torno indietro di uno, sostituisco lo spazio con \0
- Calcolo la lunghezza della parola
- Ciclo finchè non ho terminato la stringa

2. ORDINAMENTO
- Uso funzione strcmp
...

Per l'ordinamento devo ancora pensarci, però per caricare le parole mi pare possa funzionare la cosa.
Quello che non capisco è perchè sui vari libri viene imposta una costante per la lunghezza massima della stringa e della singola parola, quando secondo me è superfluo visto che si potrebbe ( credo ) allocare lo spazio.

Siccome mi rendo conto che le parole possono essere di lunghezza diversa, e quindi immagino occupino spazio differente, non potrei utilizzare la funzione realloc qualora lo spazio precedentemente allocato non sia sufficiente?

cionci
22-08-2010, 10:31
Esatto, per la lettura ve bene così.
A cose normali la lunghezza massima della parola serve comunque, il buffer su cui la parola viene riversata temporaneamente è comunque un buffer di dimensione finita.
Anche se si possono attuare varie soluzioni per togliere queste limitazioni. Tra l'altro potrebbe essere un ottimo esercizio. Tu pensa che fgets permette di specificare la dimensione del buffer, quindi può terminare la lettura anche in caso di raggiungimento della massima dimensione, pur lasciando altri caratteri nell'input (in questo caso l'ultimo carattere non è '\n'): http://www.cplusplus.com/reference/clibrary/cstdio/fgets/

kwb
23-08-2010, 15:41
Ho fatto così per la lettura ma ho alcuni problemi, infatti non riesco a caricare dentro l'array di puntatori...

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

#define WORD_MAX 40
#define STRING_MAX 300

int main (void)
{
char **pstring, input_string[STRING_MAX], word[WORD_MAX];
int i=0, word_counter=0, slen=0, j=0;
int actual_index=0;

printf("Insert a string to be sorted\n");
if ((fgets(input_string, WORD_MAX, stdin))==NULL)
{
printf("Read line error, QUIT\n");
exit(1);
}
slen = strlen(input_string);
for ( i=0; i < slen; i++)
{
if ( isspace(input_string[i]))
word_counter++; // How many words we have in the string? So we know how much memory to allocate

}
if ((pstring = calloc(word_counter, sizeof(char *)))==NULL)
{
printf("Memory row allocation error, QUIT\n");
exit(1);
}

for ( i=0; i < slen; i++)
{
if ( isspace(input_string[i]))
{
strncpy ( word, input_string, i);
for ( j = 0; j < word_counter; j++)
if ((pstring[i] = calloc(strlen(word), sizeof(char)))==NULL)
{
printf("Memory columns allocation error, QUIT\n");
exit(1);
}
actual_index=i+1;
input_string += actual_index;
}
}
for (i =0; i < word_counter; i++)
fputs(pstring[i], stdout);
return 0;
}

Mi pare l'allocazione sia corretta, inoltre non riesco a far andare ad un altro indirizzo il vettore in cui carico la stringa, così da poter eliminare la parola caricata dentro il vettore word...
So che il programma è ben poco ottimizzato e ci sono risoluzioni migliori, però il mio scopo ora è quello di riuscire a fargli stampare ste stringhe...

cionci
24-08-2010, 08:36
http://www.cplusplus.com/reference/clibrary/cstring/strncpy/

No null-character is implicitly appended to the end of destination, so destination will only be null-terminated if the length of the C string in source is less than num.

Devi aggiungere \0 in fondo alla stringa.

if ( isspace(input_string[i]))
{
strncpy ( word, input_string, i);
word[i] = '\0';


Inoltre non capisco a cosa ti serve questo:

actual_index=i+1;
input_string += actual_index;

Se fai così dovresti anche azzerare nuovamente i.
Senza contare che non puoi cambiare l'indirizzo a cui punta input_string, visto che allocata staticamente.
Dovresti usare una variabile per contare il numero dei caratteri da copiare in word.
Inoltre in pstring[i] non copi alcuna stringa e la dimensione di pstring[i] dovrebbe essere pari a strlen(word) + 1, perché deve contenere anche il carattere di fine stringa.

kwb
24-08-2010, 16:05
http://www.cplusplus.com/reference/clibrary/cstring/strncpy/

No null-character is implicitly appended to the end of destination, so destination will only be null-terminated if the length of the C string in source is less than num.

Devi aggiungere \0 in fondo alla stringa.

if ( isspace(input_string[i]))
{
strncpy ( word, input_string, i);
word[i] = '\0';

A parte che il vettore parte di default riempito di \0, quindi è superfluo farlo... Inoltre non ne capisco l'utilità...


Inoltre non capisco a cosa ti serve questo:

actual_index=i+1;
input_string += actual_index;

Se fai così dovresti anche azzerare nuovamente i.
Senza contare che non puoi cambiare l'indirizzo a cui punta input_string, visto che allocata staticamente.
Dovresti usare una variabile per contare il numero dei caratteri da copiare in word.
Inoltre in pstring[i] non copi alcuna stringa e la dimensione di pstring[i] dovrebbe essere pari a strlen(word) + 1, perché deve contenere anche il carattere di fine stringa.
Questo mi serve per eliminare ciò che ho caricato dentro input_string, così da poter mettere in word sempre l'ultima parola.
Il fatto è che cercavo di spostarmi tra gli indirizzi perchè lo avevo già fatto in un altro programma, e aveva funzionato, solo che li l'array veniva passato ad una funzione come argomento, sebbene fosse allocato in modo statico ( grandezza predefinita ).

EDIT: Ne sono venuto a capo, ora le stringhe vengono caricate dentro il vettore di puntatori correttamente, così:

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

#define WORD_MAX 40
#define STRING_MAX 300

int main (void)
{
char **pstring, input_string[STRING_MAX], word[WORD_MAX];
int i=0, word_counter=0, slen=0, j=0;
int iactual_index=0, jactual_index=0;

printf("Insert a string to be sorted\n");
if ((fgets(input_string, WORD_MAX, stdin))==NULL)
{
printf("Read line error, QUIT\n");
exit(1);
}
slen = strlen(input_string);
for ( i=0; i < slen; i++)
{
if ( isspace(input_string[i]))
word_counter++; // How many words we have in the string? So we know how much memory to allocate

}
if ((pstring = calloc(word_counter, sizeof(char *)))==NULL)
{
printf("Memory row allocation error, QUIT\n");
exit(1);
}

for ( i=0; i < slen; i++)
{
if ( isspace(input_string[i]))
{
strncpy ( word, input_string + iactual_index, i);
iactual_index += i+1;
for ( j = jactual_index; j < word_counter; j++)
{ if ((pstring[j] = calloc(strlen(word)+1, sizeof(char)))==NULL)
{
printf("Memory columns allocation error, QUIT\n");
exit(1);
}
strcpy(pstring[j], word);
}
jactual_index++;
}
}
for (i =0; i < word_counter; i++)
{
fputs(pstring[i], stdout);
printf("\n");
}
return 0;
}

cionci
25-08-2010, 08:09
A parte che il vettore parte di default riempito di \0, quindi è superfluo farlo... Inoltre non ne capisco l'utilità...
E' un vettore allocato staticamente. Tu non devi supporre quale sarà il suo contenuto dopo la creazione.
Ma anche supponendo che sia inizializzato con 0, il contenuto cambia...
Se io la prima volta ci copio dentro "abcdef" e la seconda volta "abcd", la strlen ritornerà nuovamente 6. Questo perché strncpy non aggiunge automaticamente '\0'.
Questo mi serve per eliminare ciò che ho caricato dentro input_string, così da poter mettere in word sempre l'ultima parola.
Questo lo avevo capito, comunque non sarebbe funzionato anche se il vettore invece di essere statico fosse stato un puntatore.
Il fatto è che cercavo di spostarmi tra gli indirizzi perchè lo avevo già fatto in un altro programma, e aveva funzionato, solo che li l'array veniva passato ad una funzione come argomento, sebbene fosse allocato in modo statico ( grandezza predefinita ).

Probabilmente in quel caso lo passavi come puntatore. Quando passi un vettore per puntatore non fai altro che passare l'indirizzo al primo elemento del vettore. Quindi puoi usare l'aritmetica degli indirizzi sul parametro della funzione.

Nel codice sopra continui a fare lo stesso errore concettuale di prima (supponiamo che tu aggiunga il carattere di fine stringa dopo strncpy, altrimenti non se ne esce):
strncpy ( word, input_string + iactual_index, i);
iactual_index += i+1;

Supponiamo che la stringa sia:

"nel mezzo del cammin"

Quando i == 2 c'è la prima iterazione:
- strncpy(word, input_string + 0, 3);
- word è "Nel "
- iactual_index = 4;

Quando i == 9 c'è la seconda iterazione:
- strncpy(word, input_string + 4, 9);
- word è "mezzo del "
- iactual_index = 4 + 10 = 14;

Quando i == 9 c'è la seconda iterazione:
- strncpy(word, input_string + 4, 9);
- word è "mezzo del "
- iactual_index = 4 + 10 = 14;

Quando i == 13 c'è la seconda iterazione:
- strncpy(word, input_string + 14, 13);
- ovvio che tu abbia un overflow

Inoltre perché allochi ogni volta i vettori che vanno da jactual_index a word_counter ?
Non serve quel for lì dentro: per ogni parola trovata devi allocare un solo elemento di pstring.

Suggerimento: dato un puntatore a char, scrivi una funzione che ti ritorna la lunghezza della PRIMA parola incontrata.

Altro suggerimento: avendo come parametro un puntatore alla stringa contente i dati, devi usare la funzione sopra, allocare lo spazio necessario e copia la parola trovata con la funzione sopra nella nuova stringa allocata. La funzione ritorna il puntatore alla memoria allocata.

Ultimo suggerimento: avendo la funzione del suggerimento precedente, usa l'aritmetica dei puntatori per dare in pasto una stringa contente molte parole alla funzione sopra.