Total Hack Cheat
Benvenuto/a su Total hack Cheat....
non aspettate altro tempo Registratevi!!

Parte 2 (2) guida linguaggio C

Vedere l'argomento precedente Vedere l'argomento seguente Andare in basso

Parte 2 (2) guida linguaggio C

Messaggio Da RaYoZ il Gio Mag 20, 2010 12:55 pm

Parole chiave

Vediamo un elenco delle parole chiave del linguaggio C mediante le quali possono essere create le varie funzioni.
Queste conservano per il linguaggio significati particolari e non possono essere utilizzate in altro modo.

int char float
double struct union
long short unsigned
auto extern register
typedef static goto
return sizeof break
continue if else
for do while
switch case default



Variabili

Alcune delle parole chiave viste corrispondono ai vari tipi di variabili ammesse dal linguaggio C.
Abbiamo gia' parlato precedentemente sul significato di "tipo". Le variabili dichiarate con int, ossia come interi, contengono valori numerici contenuti in due byte. Di questi due byte, 16 bit, 15 bit sono riservati al valore
mentre il sedicesimo rappresenta il segno.
Questo fa si che il valore memorizzabile in una variabile int sia compreso tra -32768 e 32767.
Per superare questa limitazione e' possibile dichiarare una variabile intera come long int.
Il valore assegnabile e' in questo caso compreso tra -2147483648
e 2147483647.
Difatti il long occupa 4 byte in memoria e quindi il valore e' dato da 2^(num bit -1).
Le variabili char, int e long int (interi long) non contengono parti frazionarie.

Es: int alpha;
long int beta;

alpha = 127;
beta = 64532;

Il tipo char occupa un solo byte e quindi puo' memorizzare valori compresi tra -128 e 127.
Nel caso che non siamo interessati alla rappresentazione dei valori negativi, possiamo dichiarare unsigned il tipo di variabile avendo cosi a disposizione tutti i bit del tipo.


In questo caso i valori assegnabili diventano i seguenti.

unsigned char - da 0 a 255
unsigned int - da 0 a 65535
unsigned short - da 0 a 65535
unsigned long - da 0 a 4294967295

Se nei nostri programmi dobbiamo fare uso di numeri frazionari abbiamo a disposizione due tipi di variabili in virgola mobile e precisamente il float e il double.
L'occupazione in byte e il valore assegnabile a ciscun tipo sono precisamente :

float - 4 bytes da +- 1.701411E-38 a +- 1.701411E38
double - 8 bytes da +- 1.0E-307 a +- 1.0E307

Un tipo particolare di cui non avevamo fatto accenno nei capitoli precedenti sono i bitfield.
Una dichiarazione di un bitfield e' costituita da
specificatore_tipo [identificatore]:costante

dove lo specificatore e' unsigned e la costante, che esprime il numero di bit, e' un valore non negativo.
In alcune funzione spesso occorre eseguire un cast (forzatura) di un tipo di dato.
In altre parole se un' espressione e' preceduta da un tipo semplice racchiuso tra parentesi questo viene convertito nel tipo specificato all'interno di queste.
Supponiamo che funzione() richieda un argomento long :

int numero;
long funzione();

funzione((long)numero);

Tra parentesi ritroviamo infatti specificato il tipo long che forza il tipo originale di numero.
Un altro esempio potrebbe essere, nel caso che funzione() pretenda come argomento un puntatore :

int numero;
long funzione();

funzione((long *)numero);



Identificatori di variabile

Il compilatore accetta come nomi di variabili sequenze di caratteri composte mediante le seguenti regole.
1 .. L'identificatore inizia con una lettera o con un underscore
2 .. Puo' contenere solo lettere, numeri e underscore (_)
3 .. Le lettere maiuscole sono considerate da compilatore come diverse da quelle minuscole
4 .. Non possono essere uguali alle parole chiave (char ad esempio e' una parola chiave)
Benche' siano accettati come primi caratteri gli underscore e' bene non utilizzarli per non creare confusione con i risultati del compilatore.
Come per i commenti, le variabili giocano un ruolo importantissimo nella chiarezza del programma ed e' quindi una buona regola utilizzare dei nomi che abbiano attinenza con lo scopo di queste.
Tra due variabili e' buona regola differenziare l'identificatore entro i primi otto caratteri in quanto il compilatore in genere pur accettando come nomi di variabile stringhe di qualsiasi lunghezza, ne considera soltanto i primi otto .
Il compilatore Microsoft C ne considera fino a 31.
Sono nomi validi di variabili i seguenti:

nome str_per_ctrl riga_321



Costanti

Una costante e' un numero, un carattere o una stringa di caratteri che puo' essere usata come un valore nel programma.
Una costante non cambia di valore da un esecuzione ad un altra del programma.
Il compilatore sostituira' tutte le occorrenze del nome della costante con il valore ad essa assegnata.
Normalmente delle costanti simboliche vengono scritte con caratteri maiuscoli allo scopo di differenziarle dalle variabili. La direttiva #define si utilizza per definire una costante. Normalmente sono poste in testa al programma con le direttive #include di cui parleremo tra poco.
Esempi di definizioni di costanti :

#define LENBUFFER 512
#define PASSWORD "programma"

Come si sara' notato, essendo queste direttive al compilatore, non necessitano di ; finale.
Se questo fosse inserito allora verrebbe incorporato nella costante.



ATTENZIONE: non e' possibile all'interno di un programma modificare il valore di una costante mediante una semplice assegnazione.

Dopo le precedenti definizioni risulterebbe illegale un istruzione del genere.

LENBUFFER = 23;

Nel caso che la definizione di costante sia relativa ad una stringa, quest'ultima viene racchiusa tra virgolette.
Nel caso invece che si tratti di un solo carattere vengono utilizzati gli apici '.

#define AFFERM 'Y'
#define NEGATO 'N'



Direttive al compilatore

I programmi che utilizzano funzioni implementate nelle librerie normalmente necessitano di dichiarazioni di funzioni, di costanti e di macro contenute nei file di header contrassegnati da .H .
Questi vengono anche chiamati con il nome di "files di intestazione".
Un file di inclusione e' associato al programma mediante l'istruzione :

#include

Le direttive al compilatore devono essere precedute dal carattere # e non devono essere messe sulla stessa linea di altre istruzioni.
Ci sono due metodi per includere un file di header nel programma.

#include <stdio.h>
#include "stdio.h"

Il primo tipo specifica che il file di intestazione potrebbe non essere presente nella stessa directory dove si trova il programma per cui la ricerca avviene in altre ubicazioni standard.
In questo caso il compilatore utilizzerebbe per cercare il file di intestazione la variabile INCLUDE che abbiamo settato nel file visto nel paragrafo relativo all'organizzazione del compilatore.
Gli include possono essere nidificati e cioe' in un file di intestazione potrebbe comparire il richiamo ad un altro. Molte funzioni richiedono piu' files di intestazione.
In questo caso potrebbero verificarsi degli errori dovuti al fatto di includere prima uno e poi l'altro non nel dovuto ordine.



Infatti se un file di header si allaccia a dichiarazioni presenti in un altro e questo non e' stato precedentemente incluso si ottengono degli errori di "indefinito".
Uno dei file che viene incluso sempre e' appunto stdio.h che contiene le definizioni delle costanti, macro e tipi delle routine di IO (input/output).
Le parti di programma precedute dal simbolo # sono anche chiamate direttive al preprocessore.
Oltre alla dichiarazione di inclusione e alla definizione di costanti e di macro (#define) ne esistono altre che permettono ad esempio di definire tipi particolari di dati o di eseguire una compilazione condizionale.
Vediamo la prima.

#typedef int double_char

definisce double_char come sinonimo del tipo int.
Una dichiarazione

double_char var;

e' a questo punto l'equivalente di

int var;

Uno potrebbe chiedersi il motivo per cui ci si dovrebbe creare dei sinonimi a dei tipi che di fatto esistono gia'.
Una delle risposte potrebbe essere la leggibilita' del programma, argomento gia' discusso parlando dei nomi degli identificatori di variabile e dello scopo dei commenti.

Qualvolta esistono circostanze per cui il software creato potrebbe girare su vari sistemi o sotto molti compilatori esclusivamente cambiando alcune definizioni.
Gli enunciati #ifdef e #ifndef permettono di creare "zone" in cui inserire codice vagliato dal compilatore solo in presenza di altre definizioni.
Supponiamo che un programma compilato sotto Microsoft necessiti della dichiarazione di una struttura stat mentre sotto compilatore Echo 88 di un altra p_stat.
La scrittura del programma potrebbe essere :

#define ??? /* Sostituire con ECH per Echo 88
o con MSC per Microsoft */

#ifdef MSC
struct stat m_sca;
#endif

#ifdef ECH
struct p_stat m_sca;
#endif


Il compilatore prenderebbe in considerazione la dichiarazione di struttura in base alla definizione effettuata inizialmente.
Lo stesso discorso, per portare un altro esempio, e' quello in cui alcune parti di codice devono essere utilizzate al posto di altre in funzione della macchina sulla quale verra' compilato il programma.
In questo caso sara' possibile utilizzare :

#define ??? /* Sostituire con IBM per PC
o con VAX per VAX 11/780 */

#ifdef IBM
#endif

#ifdef VAX
#endif

Potrebbe essere utilizzata anche la forma #ifndef (se non definito) per la stesura di costrutti del tipo di quelli visti.
Parlando precedentemente dell'istruzione condizionale if avevamo visto che era possibile fare eseguire una parte di codice in funzione dello stato di vero o di falso che assumeva una determinata espressione o variabile.
La parola else fatta seguire a una condizione if comportava l'esecuzione del codice specificato a seguito di questa nel caso che la prima risultasse falsa.
Tra le direttive al compilatore ritroviamo l'if nella forma :

#if espressione

e cioe', se e' vera l'espressione allora .......
Per tutte e tre le forme viste e' valida la direttiva

#else

che deve essere specificata prima dell' #endif e che mantiene le stesse caratteristiche di utilizzo viste nei capitoli riguardanti la programmazione strutturata.
Il compilatore Microsoft C contempla alcune estensioni alla compilazione condizionale.

#if defined(xxx)

compila le righe seguenti solo se xxx e' definito come simbolo o come macro.
E' equivalente a #ifdef.

#elif espressione

come dire "else if".
In altre parole le righe seguenti vengono compilate se la condizione di if precedente risulta falsa e vera l'espressione abbinata a #elif.



#elif defined(xxx)

Piu' o meno risulta simile a #elif solo che qui la valutazione viene fatta in base al fatto che xxx sia stato definito precedentemente.




Struttura di un programma C

Abbiamo parlato abbondantemente nei capitoli riguardanti la programmazione strutturata sull' anatomia di un file di programma.
Sperando che sia stata abbastanza chiara non perdero' ulteriore tempo se non quello di riportare uno schema di un classico programma C.


Files di intestazione e definizioni

Dichiarazione delle variabili

Funzione 1
Funzione 2
........ .

I files di intestazione e le definizioni sono quelli visti precedentemente parlando delle direttive al compilatore.
Lo scopo delle dichiarazioni delle variabili esterne alle funzioni lo approfondiremo nel paragrafo successivo parlando delle regole di visibilita' di queste.
Una funzione in linguaggio C e' da considerarsi come un capitolo di un testo in cui si affronta un determinato argomento.
In altre parole e' un unita' di programma che svolge una certo compito.
I nomi delle funzioni seguono come regola quella delle variabili.
Una funzione fondamentale che deve esistere in tutti i programmi e' quella chiamata main .
Un discorso particolare, visto gia' nei capitoli riguardanti la programmazione strutturata, sta nel fatto di poter inserire o meno delle determinate funzioni in libreria.
In ogni caso il discorso relativo alla struttura , che la funzione sia fisicamente scritta nel nostro programma o che in questo compaia solo una chiamata, non cambia.
La convenienza ad inserire o meno una funzione in libreria sta solo nel fatto di valutare se questa e' riutilizzabile in un ambito diverso da quello per cui e' stata scritta inizialmente.
Chiaramente se la funzione e' talmente particolare da poter essere utilizzata solo nel programma che stiamo scrivendo diventa inutile un suo inserimento in libreria.
Un esempio di programma che utilizza due funzioni per gestire due opzioni presentate da menu e' il seguente.

/*Intestaz.*/ #include <stdio.h>

/*Fun. main*/ main()
{
int scelta;
printf("1 .. Ordinamento\n");
printf("2 .. Stampa\n");
printf("Scelta : ");
scelta = getchar();
if(scelta==1) ordina();
if(scelta==2) stampa();
.............
}

/*Fun. 1 */ ordina()
{
.............
}

/*Fun. 2 */ stampa()
{
.............
}

Anche getchar() e printf() sono funzioni che vengono utilizzate per inputare un carattere e per stampare ma sono implementate nelle librerie del compilatore.


Visibilita' di una variabile

Una variabile nel contesto del linguaggio C puo' essere dichiarata esterna oppure interna a una funzione.
Una variabile dichiarata esternamente sara' visibile a tutte le funzioni che potranno utilizzarla e modificarla.
Nel caso invece di variabili dichiarate interne a una funzione la visibilita' rimarra' solo nell'ambito di quest' ultima.
Il valore, delle variabili interne, viene resettato tutte le volte che il programma esce dalla funzione stessa e deve essere quindi riassegnato al momento del richiamo di quest' ultima.
Le variabili interne vengono puntate e mantenute per tutta la durata della funzione nello stack.
Il compilatore mantiene di default una certa dimensione, circa 2 Kbytes, per questo.
Nel caso di utilizzo di matrici dichiarate internamente di dimensioni superiori a tale spazio occorre utilizzare l'opzione /STACK del linker per aumentare le dimensioni di questo. In caso contrario al momento dell'esecuzione il programma si blocchera' segnalando l'errore di stack overflow nell'istante in cui, richiamata la funzione, tentera' di dimensionare per un vettore o per una matrice uno spazio che non esiste.


Eseguendo una dichiarazione di una variabile esterna si dovra' porre attenzione a non dichiarare con lo stesso nome di identificatore una variabile interna magari di tipo diverso.
Il discorso in ogni caso si evolve nel momento in cui si incomincia a parlare di classi di memorizzazione.


Classe di memoria

Una variabile possiede oltre all'identificatore ed a un tipo anche una classe di memorizzazione.
Le variabili, se non dichiarate altrimenti, vengono considerate come automatiche e valgono per queste le regole appena viste.
Un ulteriore classe di memorizzazione e' costituita da quelle definite come statiche per le quali viene allocata una memoria privata nella funzione in cui viene dichiarata.
Mentre una variabile auto perde il valore all'uscita dalla funzione, la variabile statica lo mantiene inalterato.
Un esempio di dichiarazione

static char alpha;

Nel caso in cui si faccia un ampio uso di una variabile automatica esiste la classe di memorizzazione definita register.
Questa classe non accetta tutti i tipi ma solo i char, gli int e i puntatori che vedremo a suo tempo.
Una dichiarazione di questo tipo fa si che l'allocazione avvenga in un registro, ottenendo in questo modo un codice estremamente veloce e compatto.
Per le variabili register esiste la possibilita' di richiedere l'allocazione per sole due variabili per ogni funzione. Richiedere non significa in ogni caso ottenere. Nel caso che non sia possibile ottenere questa classe di
memorizzazione la variabile viene trasformata in una semplice variabile automatica.
Fate attenzione che comunque la classe register appartiene alle variabili automatiche.
Esempio

register int variabile;

L'ultima classe di memorizzazione e' costituita dalle variabili dichiarate come extern.
Una variabile dichiarata internamente ad una funzione, come abbiamo visto precedentemente, viene considerata locale.
E' possibile renderla visibile all'esterno della stessa mediante
una dichiarazione extern.

extern char pippo[];


Di default le variabili dichiarate fuori dalle funzioni vengono
considerate extern.



Funzioni

Nei capitoli riguardanti la programmazione strutturata abbiamo parlato, a riguardo della modularita'di un programma, di procedure ovvero di funzioni.
Un altro accenno al concetto di funzione lo abbiamo visto parlando della struttura di un programma in C. Vediamo ora di aggiungere qualche cosa a tutto questo. Possiamo intendere come funzione una parte di programma composta da un nome identificatore della funzione stessa, da una dichiarazione degli argomenti, se ce ne sono, dalla dichiarazione delle variabili locali ed infine dalle istruzioni il tutto racchiuso tra parentesi graffe aperte all'inizio e chiuse alla fine che hanno lo stesso significato, per quelli che conoscono il pascal, del begin e dell' end.
La chiamata avverra' per nome o per indirizzo.
In altre parole possiamo concepire una funzione come un' unita' elementare di programma a cui e' data la possibilita' di restituire o meno un valore.

Esempio
main(argc,argv) /* Identificatore */
int argc; /* Dichiarazione */
char *argv[]; /* argomenti passati */
{
int flag; /* Dichiarazione var. */

if(argc < 2) .... /* Istruzioni */
..................
}

Come dicevamo prima una funzione potrebbe svolgere un compito a se' stante come ad esempio stampare una lista di nomi ma potrebbe ritornare un valore alla funzione chiamante.
Una funzione che non restituisce alcun valore e' dichiarata all'interno del programma come void. Nel caso in cui il valore sia diverso da un int allora saremo costretti a dichiarare la funzione con il tipo del valore restituito.
Chiaramente la funzione chiamante ha anche il compito di dichiarare dello stesso tipo la variabile che accettera' il valore di ritorno dalla funzione.
Un esempio di chiamata con restituzione di valore e' il seguente:


/*Funzione main */ main()
{
int x, y, risultato;
x=y=2;
risultato = somma(x,y); /*Passaggio val.*/
printf("%d", risultato);
}

/*Funzione somma*/ somma(val1,val2)
int val1,val2;
{
return(val1+val2); /*Ritorna somma */
}

Vedremo come e' possibile restituire piu' di un valore alla funzione chiamante nel paragrafo relativo ai puntatori.
In ogni caso nell'esempio precedente abbiamo utilizzato la funzione return per ritornare un valore al main.
Si sarebbe potuto anche dichiarare l'intero risultato esterno in modo da renderlo visibile anche al di fuori della funzione principale.
L'esempio precedente si sarebbe tramutato nel seguente:

int risultato;

main()
{
int x, y;
x=y=2;
somma(x,y);
printf("%d",risultato);
}
somma(val1,val2)
int val1,val2;
{
risultato=val1+val2;
}

anche se in questo caso la funzione somma diventa banale e inutile in quanto la somma avremmo potuto eseguirla direttamente nel main.

RaYoZ
Admin
Admin

Messaggi : 1040
Punti : 2245
Data d'iscrizione : 03.04.10
Età : 22
Località : immerso nei pensieri

Tornare in alto Andare in basso

Vedere l'argomento precedente Vedere l'argomento seguente Tornare in alto

- Argomenti simili

 
Permesso di questo forum:
Non puoi rispondere agli argomenti in questo forum