Capitolo – 5
Introduzione alla programmazione C
Introduzione
Il linguaggio di programmazione C è uno dei linguaggi di programmazione più diffusi nel mondo informatico moderno. Il linguaggio di programmazione C è stato progettato e creato da Brian Kernighan e Dennis Ritchie presso i Bell Research Labs nel 1972.
Il C è un linguaggio specificamente progettato per consentire al programmatore di accedere a quasi tutti i componenti interni di una macchina: registri, slot I/O e indirizzi assoluti. Allo stesso tempo, il linguaggio di programmazione "C" consente l'elaborazione dei dati e la modularizzazione del testo programmato nella misura necessaria per realizzare progetti di programmazione multipla molto complessi in modo organizzato e tempestivo.
Sebbene in origine il linguaggio fosse stato concepito per funzionare su UNIX, vi fu un grande interesse nell'utilizzarlo sul sistema operativo MS-DOS su IBM PC e compatibili. Si tratta di un linguaggio eccellente per questo ambiente grazie alla semplicità di espressione, alla compattezza del codice e all'ampio campo di applicabilità.
Inoltre, grazie alla semplicità e alla facilità di scrittura di un compilatore C, di solito è il primo linguaggio di alto livello disponibile su qualsiasi nuovo computer, compresi microcomputer, minicomputer e mainframe.
Perché usare C nella programmazione del recupero dati
Nel mondo odierno della programmazione informatica sono disponibili molti linguaggi di alto livello. Questi linguaggi sono validi perché hanno molte funzionalità adatte alla maggior parte delle attività di programmazione. Tuttavia, ci sono diverse ragioni per cui C è la prima scelta dei programmatori che vogliono programmare per il recupero dati, per il sistema, per i dispositivi o per l'hardware:
- Il C è un linguaggio diffuso e preferito dai programmatori professionisti. Di conseguenza, è disponibile un'ampia gamma di compilatori C e utili accessori.
- C è un linguaggio portabile . Un programma in C scritto per un sistema informatico può essere compilato ed eseguito su un altro sistema con poche o nessuna modifica. La portabilità è migliorata dallo standard ANSI per C, un insieme di regole per i compilatori C.
- Il linguaggio di programmazione C consente un ampio utilizzo dei moduli. Il codice C può essere scritto in subroutine chiamate funzioni. Queste funzioni possono essere riutilizzate in altre applicazioni o programmi. Non è necessario compiere sforzi supplementari quando si programma una nuova applicazione per creare lo stesso modulo sviluppato in precedenza in un'altra applicazione.
È possibile utilizzare questa funzionalità nel nuovo programma senza alcuna modifica o con piccole modifiche. Nel caso della programmazione del recupero dati, troverete questa qualità molto utile quando dovete eseguire le stesse funzioni più volte in diverse applicazioni di programmi diversi.
- Il C è un linguaggio potente e flessibile. Ecco perché il linguaggio di programmazione C viene utilizzato per progetti così diversi, come sistemi operativi, elaboratori di testi, programmi di grafica, fogli di calcolo e persino compilatori per altri linguaggi.
- Il C è un linguaggio composto da poche parole, ovvero contiene solo pochi termini, chiamati parole chiave, che costituiscono la base su cui si sviluppa la funzionalità del linguaggio. Queste parole chiave, chiamate anche parole riservate, lo rendono più potente e offrono un'ampia gamma di possibilità di programmazione, consentendo al programmatore di sentirsi in grado di svolgere qualsiasi tipo di programmazione in C.
Supponiamo che tu non sappia nulla di C.
Immagino che tu non sappia nulla di programmazione in C e che non abbia la minima idea di programmazione. Inizierò con i concetti più basilari del linguaggio C e vi guiderò verso la programmazione C di alto livello, compresi i concetti solitamente oscuri di puntatori, strutture e allocazione dinamica della memoria.
Ci vorrà molto tempo e impegno per comprendere appieno questi concetti perché non sono facili da capire, ma sono strumenti molto potenti.
La programmazione in C è una risorsa straordinaria in ambiti in cui potrebbe essere necessario utilizzare il linguaggio assembly ma si preferisce mantenerlo semplice da scrivere e gestire. In questi casi il tempo risparmiato programmando in C può essere enorme.
Sebbene il linguaggio C goda di buoni risultati quando si tratta di trasferire programmi da un'implementazione all'altra, esistono delle differenze nei compilatori che si notano ogni volta che si prova a utilizzare un altro compilatore.
La maggior parte delle differenze diventano evidenti quando si utilizzano estensioni non standard, come le chiamate al BIOS DOS quando si utilizza MS-DOS, ma anche queste differenze possono essere ridotte al minimo mediante una scelta attenta delle strutture di programmazione.
Quando divenne evidente che il linguaggio di programmazione C stava diventando un linguaggio molto popolare e disponibile su un'ampia gamma di computer, un gruppo di persone interessate si riunì per proporre un insieme standard di regole per l'uso del linguaggio di programmazione C.
Il gruppo rappresentava tutti i settori dell'industria del software e dopo molti incontri e molte bozze preliminari, hanno finalmente scritto uno standard accettabile per il linguaggio C. È stato accettato dall'American National Standards Institute (ANSI) e dall'International Standards Organization (ISO) .
Non è imposto a nessun gruppo o utente, ma poiché è così ampiamente accettato, sarebbe un suicidio economico per qualsiasi autore di compilatori rifiutarsi di conformarsi allo standard.
I programmi scritti in questo libro sono destinati principalmente all'uso su un IBM-PC o su un computer compatibile, ma possono essere utilizzati con qualsiasi compilatore standard ANSI, poiché è strettamente conforme allo standard ANSI.
Cominciamo
Prima di poter fare qualsiasi cosa in qualsiasi linguaggio e iniziare a programmare, devi sapere come nominare un identificatore. Un identificatore è usato per qualsiasi variabile, funzione, definizione di dati, ecc. Nel linguaggio di programmazione C, un identificatore è una combinazione di caratteri alfanumerici, il primo è una lettera dell'alfabeto o una sottolineatura, e il rimanente è una qualsiasi lettera dell'alfabeto, una qualsiasi cifra numerica o la sottolineatura.
Quando si assegnano nomi agli identificatori, è necessario tenere a mente due regole.
- Il caso dei caratteri alfabetici è significativo. C è un linguaggio sensibile alle maiuscole e alle minuscole. Ciò significa che Recovery è diverso da recovery e rEcOveRY è diverso da entrambi menzionati prima.
- Secondo lo standard ANSI-C, possono essere utilizzati almeno 31 caratteri significativi, che saranno considerati significativi da un compilatore ANSI-C conforme. Se ne vengono utilizzati più di 31, tutti i caratteri oltre il 31° possono essere ignorati da qualsiasi compilatore.
Parole chiave
Ci sono 32 parole definite come parole chiave in C. Queste hanno usi predefiniti e non possono essere utilizzate per nessun altro scopo in un programma C. Sono utilizzate dal compilatore come ausilio per la compilazione del programma. Sono sempre scritte in minuscolo. Di seguito un elenco completo:
auto |
rottura |
caso |
carattere |
costante |
continuare |
predefinito |
Fare |
raddoppiare |
altro |
enumerazione |
esterno |
galleggiante |
per |
andare a |
Se |
interno |
lungo |
registro |
ritorno |
corto |
firmato |
dimensionedi |
statico |
struttura |
interruttore |
tipodef |
unione |
non firmato |
vuoto |
volatile |
Mentre |
Qui vediamo la magia del C. La meravigliosa raccolta di sole 32 parole chiave offre un ampio utilizzo in diverse applicazioni. Ogni programma per computer ha due entità da considerare, i dati e il programma. Sono altamente dipendenti l'uno dall'altro e un'attenta pianificazione di entrambi porta a un programma ben pianificato e ben scritto.
Cominciamo con un semplice programma C:
/* Primo programma per imparare il C */
#include <stdio.h>
vuoto principale()
{
printf("Questo è un programma C\n"); // stampa un messaggio
}
Sebbene il programma sia molto semplice, alcuni punti sono degni di nota. Esaminiamo il programma di cui sopra. Tutto ciò che si trova all'interno di /* e */ è considerato un commento e verrà ignorato dal compilatore. Non dovresti includere commenti all'interno di altri commenti, quindi qualcosa del genere non è consentito:
/* questo è un /* commento */ all'interno di un commento, il che è sbagliato */
Esiste anche un modo di documentazione che funziona all'interno di una riga. Utilizzando // possiamo aggiungere una piccola documentazione all'interno di quella riga.
Ogni programma C contiene una funzione chiamata main. Questo è il punto di partenza del programma. Ogni funzione dovrebbe restituire un valore. In questo programma la funzione main non restituisce alcun valore di ritorno, quindi abbiamo scritto void main. Potremmo anche scrivere questo programma come:
/* Primo programma per imparare il C */
#include <stdio.h>
principale()
{
printf("Questo è un programma C\n"); // stampa un messaggio
restituisci 0;
}
Entrambi i programmi sono uguali ed eseguono lo stesso compito. Il risultato di entrambi i programmi stamperà il seguente output sullo schermo:
Questo è un programma C
#include<stdio.h> consente al programma di interagire con lo schermo, la tastiera e il file system del tuo computer. Lo troverai all'inizio di quasi ogni programma C.
main() dichiara l'inizio della funzione, mentre le due parentesi graffe mostrano l'inizio e la fine della funzione. Le parentesi graffe in C sono usate per raggruppare le istruzioni insieme come in una funzione, o nel corpo di un ciclo. Tale raggruppamento è noto come istruzione composta o blocco.
printf("Questo è un programma C\n"); stampa le parole sullo schermo. Il testo da stampare è racchiuso tra virgolette doppie. Il \n alla fine del testo indica al programma di stampare una nuova riga come parte dell'output. La funzione printf() è utilizzata per la visualizzazione a monitor dell'output.
La maggior parte dei programmi C sono in lettere minuscole. Di solito troverete lettere maiuscole utilizzate nelle definizioni del preprocessore che saranno discusse più avanti, o all'interno di virgolette come parti di stringhe di caratteri.
Compilazione del programma
Supponiamo che il nome del nostro programma sia CPROG.C. Per inserire e compilare il programma C, segui questi passaggi:
- Crea la directory attiva dei tuoi programmi C e avvia il tuo editor. Per questo puoi usare qualsiasi editor di testo, ma la maggior parte dei compilatori C come Turbo C++ di Borland ha un ambiente di sviluppo integrato (IDE) che ti consente di immettere, compilare e collegare i tuoi programmi in un'unica comoda impostazione.
- Scrivi e salva il codice sorgente. Dovresti chiamare il file CPROG.C.
- Compila e collega CPROG.C. Esegui il comando appropriato specificato dai manuali del tuo compilatore. Dovresti ricevere un messaggio che indica che non ci sono errori o avvisi.
- Controlla i messaggi del compilatore. Se non ricevi errori o avvisi, tutto dovrebbe essere a posto. Se c'è un errore nella digitazione del programma, il compilatore lo catturerà e visualizzerà un messaggio di errore. Correggi l'errore, visualizzato nel messaggio di errore.
- Il tuo primo programma C dovrebbe ora essere compilato e pronto per essere eseguito. Se visualizzi un elenco di directory di tutti i file denominati CPROG, otterrai i quattro file con estensione diversa descritti di seguito:
- CPROG.C, il file del codice sorgente
- CPROG.BAK, il file di backup del file sorgente creato con l'editor
- CPROG.OBJ, contiene il codice oggetto per CPROG.C
- CPROG.EXE, il programma eseguibile creato quando hai compilato e collegato CPROG.C
- Per eseguire, o far girare, CPROG.EXE, basta digitare cprog. Sullo schermo viene visualizzato il messaggio This is a C program.
Esaminiamo ora il seguente programma:
/* Primo programma per imparare il C */ // 1
// 2
#include <stdio.h> // 3
// 4
principale() // 5
{
// 6
printf("Questo è un programma C\n"); // 7
// 8
restituisci 0; // 9
} // 10
Quando compili questo programma, il compilatore visualizza un messaggio simile al seguente:
cprog.c(8) : Errore: `;' previsto
scomponiamo questo messaggio di errore in parti. cprog.c è il nome del file in cui è stato trovato l'errore. (8) è il numero di riga in cui è stato trovato l'errore. Errore: `;' atteso è Una descrizione dell'errore.
Questo messaggio è piuttosto informativo e ti dice che nella riga 8 di CPROG.C il compilatore si aspettava di trovare un punto e virgola, ma non l'ha trovato. Tuttavia, sai che il punto e virgola è stato effettivamente omesso dalla riga 7, quindi c'è una discrepanza.
Perché il compilatore segnala un errore alla riga 8 quando, in realtà, un punto e virgola è stato omesso dalla riga 7. La risposta sta nel fatto che al C non importano cose come le interruzioni tra le righe. Il punto e virgola che si trova dopo l'istruzione printf() avrebbe potuto essere posizionato sulla riga successiva, anche se farlo sarebbe una cattiva programmazione in pratica.
Solo dopo aver incontrato il comando successivo (return) nella riga 8, il compilatore è sicuro che il punto e virgola sia mancante. Pertanto, il compilatore segnala che l'errore è nella riga 8.
Potrebbero esserci diverse possibilità di diversi tipi di errori. Discutiamo i messaggi di errore di collegamento. Gli errori del linker sono relativamente rari e di solito derivano da errori di ortografia del nome di una funzione della libreria C. In questo caso, si ottiene un messaggio di errore Error: undefined symbols:, seguito dal nome scritto male. Una volta corretta l'ortografia, il problema dovrebbe scomparire.
Stampa dei numeri
Vediamo il seguente esempio:
// Come stampare i numeri //
#include<stdio.h>
vuoto principale()
{
numero intero = 10;
printf(“Il numero è %d”, num);
}
L'output del programma verrà visualizzato sullo schermo come segue:
Il numero è 10
Il segno % viene utilizzato per segnalare l'output di molti tipi diversi di variabili. Il carattere che segue il segno % è ad, che segnala alla routine di output di ottenere un valore decimale e di emetterlo.
Utilizzo delle variabili
In C, una variabile deve essere dichiarata prima di poter essere utilizzata. Le variabili possono essere dichiarate all'inizio di qualsiasi blocco di codice, ma la maggior parte si trova all'inizio di ogni funzione. La maggior parte delle variabili locali viene creata quando la funzione viene chiamata e viene distrutta al ritorno da quella funzione.
Per utilizzare le variabili nei programmi C, è necessario conoscere le seguenti regole quando si assegna un nome alle variabili in C:
- Il nome può contenere lettere, cifre e il carattere di sottolineatura (_).
- Il primo carattere del nome deve essere una lettera. Anche il trattino basso è un primo carattere legale, ma il suo utilizzo non è consigliato.
- Il linguaggio C distingue tra maiuscole e minuscole, pertanto il nome della variabile num è diverso da Num.
- Le parole chiave C non possono essere utilizzate come nomi di variabili. Una parola chiave è una parola che fa parte del linguaggio C.
L'elenco seguente contiene alcuni esempi di nomi di variabili C legali e illegali:
Nome variabile |
Legale o no |
In un |
Legal |
Ttpt2_t2p |
Legal |
Tt punto |
Illegale: lo spazio non è consentito |
_1990_tassa |
Legale ma non consigliato |
Jack_telefono# |
Illegale: contiene il carattere non valido # |
Caso |
Illegale: è una parola chiave C |
1 libro |
Illegale: il primo carattere è una cifra |
La prima novità che salta all'occhio è la prima riga del corpo di main():
numero intero = 10;
Questa riga definisce una variabile denominata 'num' di tipo int e la inizializza con il valore 10. Avrebbe potuto anche essere scritta come:
int num; /* definisce la variabile non inizializzata 'num' */
/* e dopo tutte le definizioni delle variabili: */
num = 10; /* assegna il valore 10 alla variabile 'num' */
Le variabili possono essere definite all'inizio di un blocco (tra le parentesi graffe {e}); solitamente ciò avviene all'inizio del corpo di una funzione, ma può avvenire anche all'inizio di un altro tipo di blocco.
Le variabili definite all'inizio di un blocco hanno come impostazione predefinita lo stato "auto". Ciò significa che esistono solo durante l'esecuzione del blocco. Quando inizia l'esecuzione della funzione, le variabili saranno create ma il loro contenuto non sarà definito. Quando la funzione ritorna, le variabili saranno distrutte. La definizione avrebbe potuto anche essere scritta come:
numero intero automatico = 10;
Poiché la definizione con o senza la parola chiave auto è del tutto equivalente, la parola chiave auto è ovviamente piuttosto ridondante.
Tuttavia, a volte non è questo che vuoi. Supponiamo che tu voglia che una funzione tenga il conto di quante volte viene chiamata. Se la variabile venisse distrutta ogni volta che la funzione ritorna, questo non sarebbe possibile.
Pertanto è possibile dare alla variabile quella che viene chiamata durata statica, il che significa che rimarrà intatta durante l'intera esecuzione del programma. Ad esempio:
numero intero statico = 10;
Questo inizializza la variabile num a 10 all'inizio dell'esecuzione del programma. Da quel momento in poi il valore rimarrà intatto; la variabile non verrà reinizializzata se la funzione viene chiamata più volte.
A volte non è sufficiente che la variabile sia accessibile solo da una funzione oppure potrebbe non essere conveniente passare il valore tramite un parametro a tutte le altre funzioni che ne hanno bisogno.
Ma se hai bisogno di accedere alla variabile da tutte le funzioni nell'intero file sorgente, puoi farlo anche con la parola chiave static, ma inserendo la definizione all'esterno di tutte le funzioni. Ad esempio:
#include <stdio.h>
static int num = 10; /* sarà accessibile dall'intero file sorgente */
int principale(vuoto)
{
printf("Il numero è: %d\n", num);
restituisci 0;
}
E ci sono anche casi in cui una variabile deve essere accessibile dall'intero programma, che può essere costituito da diversi file sorgente. Questa è chiamata variabile globale e dovrebbe essere evitata quando non è richiesta.
Questo si ottiene anche inserendo la definizione all'esterno di tutte le funzioni, ma senza usare la parola chiave static:
#include <stdio.h>
int num = 10; /* sarà accessibile dall'intero programma! */
int principale(vuoto)
{
printf("Il numero è: %d\n", num);
restituisci 0;
}
C'è anche la parola chiave extern, che viene usata per accedere alle variabili globali in altri moduli. Ci sono anche alcuni qualificatori che puoi aggiungere alle definizioni delle variabili. Il più importante di questi è const. Una variabile definita come const non può essere modificata.
Ci sono altri due modificatori che sono usati meno comunemente. Il modificatore volatile e register. Il modificatore volatile richiede che il compilatore acceda effettivamente alla variabile ogni volta che viene letta. Potrebbe non ottimizzare la variabile inserendola in un registro o qualcosa del genere. Questo è usato principalmente per scopi di elaborazione multithreading ed interrupt ecc.
Il modificatore register richiede al compilatore di ottimizzare la variabile in un registro. Ciò è possibile solo con variabili auto e in molti casi il compilatore può selezionare meglio le variabili da ottimizzare in registri, quindi questa parola chiave è obsoleta. L'unica conseguenza diretta della creazione di un registro variabile è che il suo indirizzo non può essere preso.
La tabella delle variabili, riportata nella pagina successiva, descrive la classe di archiviazione di cinque tipi di classi di archiviazione.
Nella tabella vediamo che la parola chiave extern è posizionata in due righe. La parola chiave extern è usata nelle funzioni per dichiarare una variabile esterna statica che è definita altrove.
Tipi di variabili numeriche
Il C fornisce diversi tipi di variabili numeriche perché diversi valori numerici hanno requisiti di archiviazione di memoria variabili. Questi tipi numerici differiscono nella facilità con cui determinate operazioni matematiche possono essere eseguite su di essi.
I numeri interi piccoli richiedono meno memoria per essere archiviati e il computer può eseguire operazioni matematiche con tali numeri molto rapidamente. I numeri interi grandi e i valori in virgola mobile richiedono più spazio di archiviazione e più tempo per le operazioni matematiche. Utilizzando i tipi di variabile appropriati, si garantisce che il programma venga eseguito nel modo più efficiente possibile.
Le variabili numeriche del C rientrano nelle due categorie principali seguenti:
- Variabili intere
- Variabili in virgola mobile
In ciascuna di queste categorie ci sono due o più tipi di variabili specifici. La tabella seguente mostra la quantità di memoria, in byte, richiesta per contenere una singola variabile di ogni tipo.
Il tipo char può essere equivalente sia a signed char che a unsigned char, ma è sempre un tipo separato da entrambi.
In C non c'è differenza tra l'archiviazione di caratteri o dei loro valori numerici corrispondenti in una variabile, quindi non c'è bisogno di una funzione per convertire tra un carattere e il suo valore numerico o viceversa. Per gli altri tipi di interi, se si omette signed o unsigned il valore predefinito sarà signed, quindi ad esempio int e signed int sono equivalenti.
Il tipo int deve essere maggiore o uguale al tipo short e minore o uguale al tipo long. Se hai semplicemente bisogno di memorizzare alcuni valori che non sono enormemente grandi, spesso è una buona idea usare il tipo int; di solito è la dimensione che il processore può gestire più facilmente e quindi più velocemente.
Con diversi compilatori double e long double sono equivalenti. Questo, unito al fatto che la maggior parte delle funzioni matematiche standard funziona con il tipo double, è una buona ragione per usare sempre il tipo double se si deve lavorare con numeri frazionari.
La seguente tabella descrive meglio i tipi di variabili:
Tipi speciali comunemente usati:
Tipo di variabile |
Descrizione |
dimensione_t |
tipo senza segno utilizzato per memorizzare le dimensioni degli oggetti in byte |
tempo_t |
utilizzato per memorizzare i risultati della funzione time() |
orologio_t |
utilizzato per memorizzare i risultati della funzione clock() |
FILE |
utilizzato per accedere a un flusso (solitamente un file o un dispositivo) |
ptrdiff_t |
tipo firmato della differenza tra 2 puntatori |
divisione |
utilizzato per memorizzare i risultati della funzione div() |
ldiv_t |
utilizzato per memorizzare i risultati della funzione ldiv() |
fpos_t |
utilizzato per contenere informazioni sulla posizione del file |
elencherà |
utilizzato nella gestione degli argomenti variabili |
carattere_t |
tipo di carattere largo (utilizzato per set di caratteri estesi) |
sig_atomico_t |
utilizzato nei gestori di segnali |
Jmp_buf |
utilizzato per salti non locali |
Per comprendere meglio queste variabili prendiamo un esempio:
/* Programma per indicare l'intervallo e la dimensione in byte della variabile C */
#include <stdio.h>
int principale()
{
int a; /* tipo intero semplice */
long int b; /* tipo intero lungo */
short int c; /* tipo intero short */
unsigned int d; /* tipo intero senza segno */
char e; /* tipo di carattere */
float f; /* tipo a virgola mobile */
doppia g; /* virgola mobile a doppia precisione */
un = 1023;
b = 2222;
c = 123;
d = 1234;
e = 'X';
= 3,14159;
1.10.000;
printf( "\nUn carattere è di %d byte", sizeof( char ));
printf( "\nUn int è %d byte", sizeof( int ));
printf( "\nUno short è %d byte", sizeof( short ));
printf( "\nUn long è %d byte", sizeof( long ));
printf( "\nUn carattere senza segno è di %d byte",
sizeof( carattere senza segno ));
printf( "\nUn int senza segno è %d byte",
sizeof( intero senza segno ));
printf( "\nUn unsigned short è %d byte",
sizeof(unsigned short));
printf( "\nUn unsigned long è %d byte",
sizeof(unsigned long));
printf( "\nUn float è di %d byte", sizeof( float ));
printf( "\nUn valore double è %d byte\n", sizeof( double ));
printf("a = %d\n", a); /* output decimale */
printf("a = %o\n", a); /* output ottale */
printf("a = %x\n", a); /* output esadecimale */
printf("b = %ld\n", b); /* output decimale lungo */
printf("c = %d\n", c); /* output decimale breve */
printf("d = %u\n", d); /* output senza segno */
printf("e = %c\n", e); /* carattere in uscita */
printf("f = %f\n", f); /* output mobile */
printf("g = %f\n", g); /* output con doppio float */
stampa("\n");
printf("a = %d\n", a); /* semplice output int */
printf("a = %7d\n", a); /* usa una larghezza di campo di 7 */
printf("a = %-7d\n", a); /* giustifica a sinistra in
campo di 7 */
c = 5;
d = 8;
printf("a = %*d\n", c, a); /* usa una larghezza di campo di 5*/
printf("a = %*d\n", d, a); /* usa una larghezza di campo di 8 */
stampa("\n");
printf("f = %f\n", f); /* semplice output float */
printf("f = %12f\n", f); /* usa la larghezza del campo di 12 */
printf("f = %12.3f\n", f); /* usa 3 cifre decimali */
printf("f = %12.5f\n", f); /* usa 5 cifre decimali */
printf("f = %-12.5f\n", f); /* giustifica a sinistra nel campo */
restituisci 0;
}
Il risultato del programma dopo l'esecuzione verrà visualizzato come:
Un char è 1 byte
Un int è di 2 byte
Uno short è di 2 byte
Un long è di 4 byte
Un carattere senza segno è 1 byte
Un int senza segno è di 2 byte
Un unsigned short è di 2 byte
Un unsigned long è di 4 byte
Un float è di 4 byte
Un doppio è 8 byte
un = 1023
un = 1777
a = 3ff
e = 2222
c = 123
d = 1234
e = X
f = 3,141590
g = 3,141593
un = 1023
un = 1023
un = 1023
un = 1023
un = 1023
f = 3,141590
f = 3,141590
f = 3,142
f = 3,14159
f = 3,14159 |
Prima di essere utilizzata, una variabile in un programma C, deve essere dichiarata. Una dichiarazione di variabile comunica al compilatore il nome e il tipo di una variabile e, facoltativamente, inizializza la variabile a un valore specifico.
Se il tuo programma tenta di usare una variabile che non è stata dichiarata, il compilatore genera un messaggio di errore. Una dichiarazione di variabile ha la seguente forma:
nome tipo nome variabile;
typename specifica il tipo di variabile e deve essere una delle parole chiave. varname è il nome della variabile. Puoi dichiarare più variabili dello stesso tipo su una riga separando i nomi delle variabili con virgole:
int count, numero, inizio; /* tre variabili intere */
float percentuale, totale; /* due variabili float */
La parola chiave typedef
La parola chiave typedef viene utilizzata per creare un nuovo nome per un tipo di dati esistente. In effetti, typedef crea un sinonimo. Ad esempio, l'istruzione
typedef int intero;
qui vediamo che typedef crea integer come sinonimo di int. Puoi quindi usare integer per definire variabili di tipo int, come in questo esempio:
conteggio degli interi;
Quindi typedef non crea un nuovo tipo di dati, ma consente solo di utilizzare un nome diverso per un tipo di dati predefinito.
Inizializzazione delle variabili numeriche
Quando viene dichiarata una variabile, il compilatore riceve l'istruzione di riservare spazio di archiviazione per la variabile. Tuttavia, il valore archiviato in quello spazio, il valore della variabile, non è definito. Potrebbe essere zero, oppure potrebbe essere un valore "spazzatura" casuale. Prima di usare una variabile, dovresti sempre inizializzarla a un valore noto. Prendiamo questo esempio:
int count; /* Metti da parte lo spazio di archiviazione per count */
count = 0; /* Memorizza 0 nel conteggio */
Questa istruzione usa il segno di uguale (=), che è l'operatore di assegnazione di C. Puoi anche inizializzare una variabile quando viene dichiarata. Per farlo, fai seguire al nome della variabile nell'istruzione di dichiarazione un segno di uguale e il valore iniziale desiderato:
int conteggio = 0;
tasso doppio = 0,01, complessità = 28,5;
Fai attenzione a non inizializzare una variabile con un valore al di fuori dell'intervallo consentito. Ecco due esempi di inizializzazioni fuori intervallo:
int importo = 100000;
intero senza segno lunghezza = -2500;
Il compilatore C non rileva tali errori. Il tuo programma può compilare e collegarsi, ma potresti ottenere risultati inaspettati quando il programma viene eseguito.
Prendiamo il seguente esempio per calcolare il numero totale di settori in un disco:
// Programma modello per calcolare i settori in un disco //
#include<stdio.h>
#define SETTORE_PER_LATO 63
#define SIDE_PER_CILINDRO 254
vuoto principale()
{
int cilindro=0;
clrscr();
printf("Inserisci il numero di cilindri nel disco \n\n\t");
scanf("%d",&cylinder); // Ottieni il valore dall'utente //
printf("\n\n\t Numero totale di settori nel disco = %ld", (long)SECTOR_PER_SIDE*SIDE_PER_CYLINDER* cilindro);
ottenere();
}
L'output del programma è il seguente:
Inserisci il numero di cilindri nel disco
1024
Numero totale di settori nel disco = 16386048
In questo esempio vediamo tre nuove cose da imparare. #define viene utilizzato per usare costanti simboliche nel programma o in alcuni casi per risparmiare tempo definendo parole lunghe in simboli piccoli.
Qui abbiamo definito il numero di settori per lato, che è 63, come SECTOR_PER_SIDE per rendere il programma facile da capire. Lo stesso caso è vero per #define SIDE_PER_CYLINDER 254. scanf() è usato per ottenere l'input dall'utente.
Qui prendiamo il numero di cilindri come input dall'utente. * viene utilizzato per moltiplicare due o più valori, come mostrato nell'esempio.
La funzione getch() fondamentalmente riceve un singolo carattere di input dalla tastiera. Digitando getch(); qui fermiamo lo schermo finché non viene premuto un tasto qualsiasi dalla tastiera.
Operatori
Un operatore è un simbolo che istruisce C a eseguire un'operazione, o azione, su uno o più operandi. Un operando è qualcosa su cui un operatore agisce. In C, tutti gli operandi sono espressioni. Gli operatori C sono delle seguenti quattro categorie:
- L'operatore di assegnazione
- Operatori matematici
- Operatori relazionali
- Operatori logici
Operatore di assegnazione
L'operatore di assegnazione è il segno di uguale (=). L'uso del segno di uguale nella programmazione è diverso dal suo uso nelle normali relazioni matematiche algebriche. Se scrivi
la x = y;
In un programma C, non significa "x è uguale a y". Invece, significa "assegna il valore di y a x". In un'istruzione di assegnazione C, il lato destro può essere qualsiasi espressione e il lato sinistro deve essere un nome di variabile. Quindi, la forma è la seguente:
variabile = espressione;
Durante l'esecuzione, l'espressione viene valutata e il valore risultante viene assegnato alla variabile.
Operatori matematici
Gli operatori matematici di C eseguono operazioni matematiche come addizione e sottrazione. C ha due operatori matematici unari e cinque operatori matematici binari. Gli operatori matematici unari sono così chiamati perché accettano un singolo operando. C ha due operatori matematici unari.
Gli operatori di incremento e decremento possono essere utilizzati solo con variabili, non con costanti. L'operazione eseguita è aggiungere uno o sottrarre uno dall'operando. In altre parole, le istruzioni ++x; e --y; sono gli equivalenti di queste istruzioni:
x = x + 1;
y = y - 1;
gli operatori matematici binari prendono due operandi. I primi quattro operatori binari, che includono le comuni operazioni matematiche che si trovano su una calcolatrice (+, -, *, /), ti sono familiari. Il quinto operatore Modulo restituisce il resto quando il primo operando è diviso per il secondo operando. Ad esempio, 11 modulo 4 è uguale a 3 (11 è diviso per 4, due volte e 3 rimane).
Operatori relazionali
Gli operatori relazionali di C vengono utilizzati per confrontare le espressioni. Un'espressione contenente un operatore relazionale viene valutata come true (1) o false (0). C ha sei operatori relazionali.
Operatori logici
Gli operatori logici del C consentono di combinare due o più espressioni relazionali in un'unica espressione che restituisce true o false. Gli operatori logici restituiscono true o false, a seconda del valore true o false dei loro operandi.
Se x è una variabile intera, le espressioni che utilizzano operatori logici potrebbero essere scritte nei seguenti modi:
(x > 1) e (x < 5)
(x >= 2) e (x <= 4)
Operatore |
Simbolo |
Descrizione |
Esempio |
Operatori di assegnazione |
pari |
= |
assegna il valore di y a x |
x = y |
Operatori matematici |
Incremento |
++ |
Incrementa l'operando di uno |
++x, x++ |
Decremento |
-- |
Decrementa l'operando di uno |
--x, x-- |
Aggiunta |
+ |
Aggiunge due operandi |
x + y |
Sottrazione |
- |
Sottrae il secondo operando dal primo |
x-e |
Moltiplicazione |
* |
Moltiplica due operandi |
x * e |
Divisione |
/ |
Divide il primo operando per il secondo operando |
x / e |
Modulo |
% |
Fornisce il resto quando il primo operando viene diviso per il secondo operando |
x % e |
Operatori relazionali |
Pari |
= = |
Uguaglianza |
x = = e |
Maggiore di |
> |
Maggiore di |
x > e |
Meno di |
< |
Meno di |
x < y |
Maggiore o uguale a |
>= |
Maggiore o uguale a |
x >= y |
Minore o uguale a |
<= |
Minore o uguale a |
x <= e |
Non uguale |
!= |
Non uguale a |
x != y |
Operatori logici |
E |
&& |
Vero (1) solo se sia exp1 che exp2 sono veri; falso (0) altrimenti |
exp1 e exp2 |
O |
|| |
Vero (1) se exp1 o exp2 è vero; falso (0) solo se entrambi sono falsi |
esp1 || esp2 |
NON |
! |
Falso (0) se exp1 è vero; vero (1) se exp1 è falso |
!esp1 |
Cose da ricordare sulle espressioni logiche
x * = y |
è uguale a |
x = x * y |
y - = z + 1 |
è uguale a |
y = y - z + 1 |
un / = b |
è uguale a |
un = un / b |
x + = y / 8 |
è uguale a |
x = x + y / 8 |
e % = 3 |
è uguale a |
y = y % 3 |
L'operatore virgola
La virgola è spesso utilizzata in C come semplice segno di punteggiatura, per separare dichiarazioni di variabili, argomenti di funzioni, ecc. In determinate situazioni, la virgola funge da operatore.
È possibile formare un'espressione separando due sottoespressioni con una virgola. Il risultato è il seguente:
- Vengono valutate entrambe le espressioni, con l'espressione di sinistra valutata per prima.
- L'intera espressione viene valutata come il valore dell'espressione corretta.
Ad esempio, la seguente istruzione assegna il valore di b a x, quindi incrementa a e poi incrementa b: x = (a++, b++);
Precedenza degli operatori C (Riepilogo degli operatori C)
Rango e Associatività |
Operatori |
1 (da sinistra a destra) |
() [] -> . |
2 (da destra a sinistra) |
! ~ ++ -- * (indirezione) & (indirizzo-di) (tipo) sizeof + (unario) - (unario) |
3 (da sinistra a destra) |
* (moltiplicazione) / % |
4 (da sinistra a destra) |
+ - |
5 (da sinistra a destra) |
<< >> |
6 (da sinistra a destra) |
< <= > >= |
7 (da sinistra a destra) |
= = != |
8 (da sinistra a destra) |
& ( AND bit a bit ) |
9 (da sinistra a destra) |
^ |
10 (da sinistra a destra) |
| |
11 (da sinistra a destra) |
&& |
12 (da sinistra a destra) |
|| |
13 (da destra a sinistra) |
?: |
14 (da destra a sinistra) |
= += -= *= /= %= &= ^= |= <<= >>= |
15 (da sinistra a destra) |
, |
() è l'operatore di funzione; [] è l'operatore di array. |
|
Prendiamo un esempio di utilizzo degli operatori:
/* Uso degli operatori */
int principale()
{
se il numero è intero x = 0, y = 2, z = 1025;
numero decimale a = 0,0, b = 3,14159, c = -37,234;
/* incrementando */
x = x + 1; /* Questo incrementa x */
x++; /* Questo incrementa x */
++x; /* Questo incrementa x */
z = y++; /* z = 2, y = 3 */
z = ++y; /* z = 4, y = 4 */
/* decremento */
y = y - 1; /* Questo decrementa y */
y--; /* Questo decrementa y */
--y; /* Questo decrementa y */
y = 3;
z = y--; /* z = 3, y = 2 */
z = --y; /* z = 1, y = 1 */
/* operazione aritmetica */
a = a + 12; /* Questo aggiunge 12 ad a */
a += 12; /* Questo aggiunge altri 12 ad a */
a *= 3.2; /* Questo moltiplica a per 3.2 */
a -= b; /* Questo sottrae b da a */
a /= 10.0; /* Questo divide a per 10.0 */
/* espressione condizionale */
a = (b >= 3.0 ? 2.0 : 10.5 ); /* Questa espressione */
se (b >= 3.0) /* E questa espressione */
a = 2.0; /* sono identici, entrambi */
altrimenti /* causerà lo stesso */
a = 10.5; /* risultato. */
c = (a > b ? a : b); /* c avrà il massimo di a o b */
c = (a > b ? b : a); /* c avrà il minimo di a o b */
printf("x=%d, y=%d, z= %d\n", x, y, z);
printf("a=%f, b=%f, c= %f", a, b, c);
restituisci 0;
}
e il risultato di questo programma verrà visualizzato sullo schermo come:
x=3, y=1, z=1
a=2,000000, b=3,141590, c=2,000000
Qualcosa in più su printf() e Scanf()
Considerare le seguenti due istruzioni printf
printf(“\t %d\n”, num);
printf(“%5.2f”, frazione);
nella prima istruzione printf \t richiede lo spostamento della tabulazione sullo schermo, l'argomento %d indica al compilatore che il valore di num deve essere stampato come intero decimale. \n fa sì che il nuovo output inizi da una nuova riga.
Nella seconda istruzione printf %5.2f dice al compilatore che l'output deve essere in virgola mobile, con cinque cifre in tutto e due cifre a destra della virgola decimale. Maggiori informazioni sul carattere backslash sono riportate nella seguente tabella:
Costante |
Senso |
'\UN' |
Avviso acustico (campanello) |
'\B' |
Indietro |
'\F' |
Avanzamento modulo |
'\N' |
Nuova linea |
'\R' |
Ritorno a capo |
'\T' |
Scheda orizzontale |
'\v' |
Scheda verticale |
'\'' |
Citazione singola |
'\”' |
Doppia virgoletta |
'\?' |
Punto interrogativo |
'\\' |
Barra rovesciata |
'\0' |
Nullo |
Consideriamo la seguente istruzione scanf
scanf(“%d”, &num);
I dati dalla tastiera vengono ricevuti dalla funzione scanf. Nel formato sopra, il simbolo & (e commerciale) prima di ogni nome di variabile è un operatore che specifica l'indirizzo del nome di variabile.
Facendo ciò, l'esecuzione si ferma e attende che venga digitato il valore della variabile num. Quando viene immesso il valore intero e viene premuto il tasto Invio, il computer procede all'istruzione successiva. I codici di formato scanf e printf sono elencati nella seguente tabella:
Codice |
Legge... |
%C |
Carattere singolo |
%D |
Numero intero decimale |
%E |
Valore in virgola mobile |
%F |
Valore in virgola mobile |
%G |
Valore in virgola mobile |
%H |
Numero intero corto |
%io |
Numero intero decimale, esadecimale o ottale |
%IL |
Numero intero ottale |
%S |
Corda |
%In |
Numero intero decimale senza segno |
%X |
Numero intero esadecimale |
Dichiarazioni di controllo
Un programma è costituito da un certo numero di istruzioni che di solito vengono eseguite in sequenza. I programmi possono essere molto più potenti se possiamo controllare l'ordine in cui le istruzioni vengono eseguite.
Le dichiarazioni rientrano in tre tipologie generali:
- Assegnazione, in cui i valori, solitamente i risultati dei calcoli, vengono memorizzati in variabili.
- Input/Output, i dati vengono letti o stampati.
- Controllo: il programma decide cosa fare dopo.
Questa sezione discuterà l'uso delle istruzioni di controllo in C. Mostreremo come possono essere utilizzate per scrivere programmi potenti mediante:
- Ripetizione di sezioni importanti del programma.
- Selezione tra sezioni opzionali di un programma.
L'istruzione if else
Viene utilizzato per decidere se fare qualcosa in un momento specifico o per scegliere tra due linee di condotta.
Il seguente test decide se uno studente ha superato un esame con un punteggio di passaggio di 45
se (risultato >= 45)
printf("Passa\n");
altro
printf("Errore\n");
È possibile utilizzare la parte if senza else.
se (temperatura < 0)
print("Congelato\n");
Ogni versione consiste in un test, nell'istruzione tra parentesi che segue if. Se il test è vero, allora si obbedisce all'istruzione successiva. Se è falso, allora si obbedisce all'istruzione che segue else, se presente. Dopo questo, il resto del programma continua normalmente.
Se desideriamo che più di un'istruzione segua if o else, queste devono essere raggruppate tra parentesi graffe. Tale raggruppamento è chiamato istruzione composta o blocco.
se (risultato >= 45)
{ printf("Superato\n");
printf("Congratulazioni\n");
}
altro
{ printf("Non riuscito\n");
printf("Sarà più fortunato la prossima volta\n");
}
A volte vogliamo prendere una decisione multi-way basata su diverse condizioni. Il modo più generale per farlo è usare la variante else if nell'istruzione if.
Questo funziona tramite la cascata di diversi confronti. Non appena uno di questi fornisce un risultato vero, viene eseguita la seguente istruzione o blocco e non vengono eseguiti ulteriori confronti. Nell'esempio seguente stiamo assegnando i voti in base al risultato dell'esame.
se (risultato <=100 e risultato >= 75)
printf("Superato: Voto A\n");
altrimenti se (risultato >= 60)
printf("Superato: voto B\n");
altrimenti se (risultato >= 45)
printf("Superato: voto C\n");
altro
printf("Non riuscito\n");
In questo esempio, tutti i confronti testano una singola variabile chiamata risultato. In altri casi, ogni test può coinvolgere una variabile diversa o una combinazione di test. Lo stesso schema può essere utilizzato con più o meno else if, e l'ultimo else da solo può essere omesso.
Spetta al programmatore ideare la struttura corretta per ogni problema di programmazione. Per comprendere meglio l'uso di if else, vediamo l'esempio
#include <stdio.h>
int principale()
{
numero intero;
per(numero = 0; numero < 10; numero = numero + 1)
{
se (numero == 2)
printf("num è ora uguale a %d\n", num);
se (numero < 5)
printf("num ora è %d, che è minore di 5\n", num);
altro
printf("num ora è %d, che è maggiore di 4\n", num);
} /* fine del ciclo for */
restituisci 0;
}
Risultato del programma
num è ora 0, che è inferiore a 5
num è ora 1, che è inferiore a 5
num è ora uguale a 2
num è ora 2, che è meno di 5
num è ora 3, che è meno di 5
num è ora 4, che è meno di 5
num è ora 5, che è maggiore di 4
num è ora 6, che è maggiore di 4
num è ora 7, che è maggiore di 4
num è ora 8, che è maggiore di 4
num è ora 9, che è maggiore di 4
La dichiarazione switch
Questa è un'altra forma di decisione multi-way. È ben strutturata, ma può essere utilizzata solo in alcuni casi in cui;
- Viene testata solo una variabile, tutti i rami devono dipendere dal valore di quella variabile. La variabile deve essere di tipo integrale. (int, long, short o char).
- Ogni possibile valore della variabile può controllare un singolo ramo. Un ramo finale, catch all, predefinito può essere opzionalmente utilizzato per intercettare tutti i casi non specificati.
L'esempio fornito di seguito chiarirà le cose. Questa è una funzione che converte un numero intero in una descrizione vaga. È utile quando siamo interessati solo a misurare una quantità quando è piuttosto piccola.
stima(numero)
numero intero;
/* Stima un numero come nessuno, uno, due, diversi, molti */
{ switch(numero) {
caso 0 :
printf("Nessuno\n");
rottura;
caso 1 :
printf("Uno\n");
rottura;
caso 2 :
printf("Due\n");
rottura;
caso 3 :
caso 4 :
caso 5 :
printf("Diversi\n");
rottura;
predefinito :
printf("Molti\n");
rottura;
}
}
Ogni caso interessante è elencato con un'azione corrispondente. L'istruzione break impedisce che vengano eseguite altre istruzioni lasciando lo switch. Poiché il caso 3 e il caso 4 non hanno break successivi, continuano a consentire la stessa azione per diversi valori di numero.
Sia i costrutti if che switch consentono al programmatore di effettuare una selezione tra un certo numero di azioni possibili. Vediamo un esempio:
#include <stdio.h>
int principale()
{
numero intero;
per (num = 3 ; num < 13 ; num = num + 1)
{
interruttore (num)
{
caso 3 :
printf("Il valore è tre\n");
rottura;
caso 4 :
printf("Il valore è quattro\n");
rottura;
caso 5 :
caso 6 :
caso 7 :
caso 8 :
printf("Il valore è compreso tra 5 e 8\n");
rottura;
caso 11 :
printf("Il valore è undici\n");
rottura;
predefinito :
printf("È uno dei valori indefiniti\n");
rottura;
} /* fine dello switch */
} /* fine del ciclo for */
restituisci 0;
}
L'output del programma sarà
Il valore è tre
Il valore è quattro
Il valore è compreso tra 5 e 8
Il valore è compreso tra 5 e 8
Il valore è compreso tra 5 e 8
Il valore è compreso tra 5 e 8
È uno dei valori indefiniti
È uno dei valori indefiniti
Il valore è undici
È uno dei valori indefiniti
La dichiarazione di interruzione
Abbiamo già incontrato break nella discussione dell'istruzione switch. Viene utilizzata per uscire da un ciclo o da uno switch, passando il controllo alla prima istruzione oltre il ciclo o lo switch.
Con i loop, break può essere utilizzato per forzare un'uscita anticipata dal loop, o per implementare un loop con un test per uscire nel mezzo del corpo del loop. Un break all'interno di un loop dovrebbe sempre essere protetto all'interno di un'istruzione if che fornisce il test per controllare la condizione di uscita.
La dichiarazione continua
È simile a break ma si incontra meno frequentemente. Funziona solo all'interno di loop in cui il suo effetto è quello di forzare un salto immediato all'istruzione di controllo del loop.
- In un ciclo while, salta all'istruzione di test.
- In un ciclo do while, salta all'istruzione di test.
- In un ciclo for, salta al test ed esegui l'iterazione.
Come un break, continue dovrebbe essere protetto da un'istruzione if. È improbabile che lo usiate molto spesso. Per comprendere meglio l'uso di break e continue, esaminiamo il seguente programma:
#include <stdio.h>
int principale()
{
valore intero;
for(valore = 5 ; valore < 15 ; valore = valore + 1)
{
se (valore == 8)
rottura;
printf("Nel ciclo di interruzione, il valore è ora %d\n", valore);
}
for(valore = 5 ; valore < 15 ; valore = valore + 1)
{
se (valore == 8)
continuare;
printf("Nel ciclo continue, il valore è ora %d\n", valore);
}
restituisci 0;
}
L'output del programma sarà il seguente:
Nel ciclo di interruzione, il valore è ora 5
Nel ciclo di interruzione, il valore è ora 6
Nel ciclo di interruzione, il valore è ora 7
Nel ciclo continuo, il valore è ora 5
Nel ciclo continuo, il valore è ora 6
Nel ciclo continuo, il valore è ora 7
Nel ciclo continuo, il valore è ora 9
Nel ciclo continuo, il valore è ora 10
Nel ciclo continuo, il valore è ora 11
Nel ciclo continuo, il valore è ora 12
Nel ciclo continuo, il valore è ora 13
Nel ciclo continuo, il valore è ora 14
Anelli
L'altro tipo principale di istruzione di controllo è il ciclo. I cicli consentono di ripetere un'istruzione, o un blocco di istruzioni. I computer sono molto bravi a ripetere compiti semplici più volte. Il ciclo è il modo di C per raggiungere questo obiettivo.
Il linguaggio C offre la possibilità di scegliere tra tre tipi di ciclo: while, do-while e for.
- Il ciclo while continua a ripetere un'azione finché un test associato non restituisce false. Questo è utile quando il programmatore non sa in anticipo quante volte verrà attraversato il ciclo.
- Il ciclo do while è simile, ma il test avviene dopo l'esecuzione del corpo del ciclo. Ciò assicura che il corpo del ciclo venga eseguito almeno una volta.
- Il ciclo for è usato frequentemente, solitamente quando il ciclo verrà attraversato un numero fisso di volte. È molto flessibile e i programmatori alle prime armi dovrebbero fare attenzione a non abusare della potenza che offre.
Il ciclo while
Il ciclo while ripete un'istruzione finché il test in cima non risulta falso. Come esempio, ecco una funzione per restituire la lunghezza di una stringa. Ricorda che la stringa è rappresentata come un array di caratteri terminati da un carattere nullo '\0'.
int lunghezza_stringa(char stringa[])
{ int i = 0;
mentre (stringa[i] != '\0')
io++;
ritorno(i);
}
La stringa viene passata alla funzione come argomento. La dimensione dell'array non è specificata, la funzione funzionerà per una stringa di qualsiasi dimensione.
Il ciclo while viene utilizzato per esaminare i caratteri nella stringa uno alla volta finché non viene trovato il carattere null. Quindi il ciclo viene interrotto e viene restituito l'indice del null.
Finché il carattere non è nullo, l'indice viene incrementato e il test viene ripetuto. Approfondiremo gli array più avanti. Vediamo un esempio di ciclo for while:
#include <stdio.h>
int principale()
{
int conteggio;
conteggio = 0;
mentre (conteggio < 6)
{
printf("Il valore di count è %d\n", count);
contare = contare + 1;
}
restituisci 0;
}
e il risultato viene visualizzato come segue:
Il valore del conteggio è 0
Il valore del conteggio è 1
Il valore del conteggio è 2
Il valore del conteggio è 3
Il valore del conteggio è 4
Il valore del conteggio è 5
Il ciclo do while
Questo è molto simile al ciclo while, tranne per il fatto che il test avviene alla fine del corpo del ciclo. Ciò garantisce che il ciclo venga eseguito almeno una volta prima di continuare.
Una configurazione di questo tipo è usata frequentemente quando i dati devono essere letti. Il test verifica quindi i dati e torna indietro per leggerli di nuovo se non sono accettabili.
Fare
{
printf("Inserisci 1 per sì, 0 per no :");
scanf("%d", &valore_input);
} while (valore_input != 1 && valore_input != 0)
Per comprendere meglio il ciclo do while vediamo il seguente esempio:
#include <stdio.h>
int principale()
{
int io;
io = 0;
Fare
{
printf("Il valore di i è ora %d\n", i);
io = io + 1;
} mentre (i < 5);
restituisci 0;
}
Il risultato del programma viene visualizzato come segue:
Il valore di i è ora 0
Il valore di i è ora 1
Il valore di i è ora 2
Il valore di i è ora 3
Il valore di i è ora 4
Il ciclo for
Il ciclo for funziona bene quando il numero di iterazioni del ciclo è noto prima di entrare nel ciclo. La testa del ciclo è composta da tre parti separate da punto e virgola.
- Il primo viene eseguito prima che il ciclo venga avviato. Di solito si tratta dell'inizializzazione della variabile del ciclo.
- Il secondo è un test: il ciclo viene interrotto quando restituisce false.
- La terza è un'istruzione da eseguire ogni volta che il corpo del ciclo è completato. Di solito è un incremento del contatore del ciclo.
L'esempio è una funzione che calcola la media dei numeri memorizzati in un array. La funzione accetta l'array e il numero di elementi come argomenti.
float media(float array[], int conteggio)
{
totale float = 0,0;
int io;
per(i = 0; i < conteggio; i++)
totale += array[i];
ritorno(totale / conteggio);
}
Il ciclo for garantisce che venga sommato il numero corretto di elementi dell'array prima di calcolare la media.
Le tre istruzioni all'inizio di un ciclo for solitamente fanno solo una cosa ciascuna, tuttavia una qualsiasi di esse può essere lasciata vuota. Una prima o ultima istruzione vuota significherà nessuna inizializzazione o incremento in esecuzione. Un'istruzione di confronto vuota sarà sempre trattata come vera. Ciò causerà l'esecuzione indefinita del ciclo a meno che non venga interrotta da qualche altro mezzo. Potrebbe essere un'istruzione return o break.
È anche possibile comprimere diverse istruzioni nella prima o terza posizione, separandole con virgole. Ciò consente un ciclo con più di una variabile di controllo. L'esempio seguente illustra la definizione di tale ciclo, con le variabili hi e lo che iniziano rispettivamente a 100 e 0 e che convergono.
Il ciclo for fornisce una varietà di abbreviazioni da utilizzare al suo interno. Fai attenzione alla seguente espressione, in questa espressione il singolo ciclo contiene due cicli for al suo interno. Qui hi-- è uguale a hi = hi - 1 e lo++ è uguale a lo = lo + 1,
for(hi = 100, lo = 0; hi >= lo; hi--, lo++)
Il ciclo for è estremamente flessibile e consente di specificare in modo semplice e rapido molti tipi di comportamento del programma. Vediamo un esempio di ciclo for
#include <stdio.h>
int principale()
{
int indice;
per(indice = 0 ; indice < 6 ; indice = indice + 1)
printf("Il valore dell'indice è %d\n", indice);
restituisci 0;
}
Il risultato del programma viene visualizzato come segue:
Il valore dell'indice è 0
Il valore dell'indice è 1
Il valore dell'indice è 2
Il valore dell'indice è 3
Il valore dell'indice è 4
Il valore dell'indice è 5
L'istruzione goto
Il C ha un'istruzione goto che consente di effettuare salti non strutturati. Per usare un'istruzione goto, basta usare la parola riservata goto seguita dal nome simbolico a cui si desidera saltare. Il nome viene quindi inserito in qualsiasi punto del programma seguito da due punti. È possibile saltare quasi ovunque all'interno di una funzione, ma non è consentito saltare in un ciclo, sebbene sia consentito saltare fuori da un ciclo.
Questo programma in particolare è davvero un pasticcio, ma è un buon esempio del perché gli autori di software stanno cercando di eliminare il più possibile l'uso dell'istruzione goto. L'unico punto in questo programma in cui è ragionevole usare goto è dove il programma salta fuori dai tre cicli annidati in un salto. In questo caso sarebbe piuttosto complicato impostare una variabile e saltare fuori in successione da ciascuno dei tre cicli annidati, ma un'istruzione goto ti fa uscire da tutti e tre in modo molto conciso.
Alcune persone dicono che l'istruzione goto non dovrebbe mai essere usata in nessuna circostanza, ma questo è un modo di pensare ristretto. Se c'è un posto in cui un goto eseguirà chiaramente un flusso di controllo più ordinato rispetto a qualche altro costrutto, sentiti libero di usarlo, comunque, come nel resto del programma sul tuo monitor. Vediamo l'esempio:
#include <stdio.h>
int principale()
{
int cane, gatto, maiale;
vai a real_start;
in qualche luogo:
printf("Questa è un'altra riga del pasticcio.\n");
vai a stop_it;
/* la sezione seguente è l'unica sezione con un goto utilizzabile */
inizio_reale:
per(cane = 1 ; cane < 6 ; cane = cane + 1)
{
per(gatto = 1 ; gatto < 6 ; gatto = gatto + 1)
{
per(maiale = 1 ; maiale < 4 ; maiale = maiale + 1)
{
printf("Cane = %d Gatto = %d Maiale = %d\n", cane, gatto, maiale);
se ((cane + gatto + maiale) > 8 ) vai a abbastanza;
}
}
}
basta: printf("Per ora ci sono abbastanza animali.\n");
/* questa è la fine della sezione con un'istruzione goto utilizzabile */
printf("\nQuesta è la prima riga del codice.\n");
vai lì;
Dove:
printf("Questa è la terza riga del codice.\n");
vai a qualche_parte;
Là:
printf("Questa è la seconda riga del codice.\n");
vai dove;
fermalo:
printf("Questa è l'ultima riga di questo pasticcio.\n");
restituisci 0;
}
Vediamo i risultati visualizzati
Cane = 1 Gatto = 1 Maiale = 1
Cane = 1 Gatto = 1 Maiale = 2
Cane = 1 Gatto = 1 Maiale = 3
Cane = 1 Gatto = 2 Maiale = 1
Cane = 1 Gatto = 2 Maiale = 2
Cane = 1 Gatto = 2 Maiale = 3
Cane = 1 Gatto = 3 Maiale = 1
Cane = 1 Gatto = 3 Maiale = 2
Cane = 1 Gatto = 3 Maiale = 3
Cane = 1 Gatto = 4 Maiale = 1
Cane = 1 Gatto = 4 Maiale = 2
Cane = 1 Gatto = 4 Maiale = 3
Cane = 1 Gatto = 5 Maiale = 1
Cane = 1 Gatto = 5 Maiale = 2
Cane = 1 Gatto = 5 Maiale = 3
Per ora basta con gli animali.
Questa è la prima riga del codice.
Questa è la seconda riga del codice.
Questa è la terza riga del codice.
Questa è un'altra linea del pasticcio.
Questa è l'ultima riga di questo pasticcio.
Puntatori
A volte vogliamo sapere dove risiede una variabile nella memoria. Un puntatore contiene l'indirizzo di una variabile che ha un valore specifico. Quando si dichiara un puntatore, un asterisco viene posizionato immediatamente prima del nome del puntatore.
L'indirizzo della posizione di memoria in cui è memorizzata la variabile può essere trovato inserendo una e commerciale davanti al nome della variabile.
int num; /* Variabile intera normale */
int *numPtr; /* Puntatore a una variabile intera */
L'esempio seguente stampa il valore della variabile e l'indirizzo di memoria di tale variabile.
printf("Il valore %d è memorizzato all'indirizzo %X\n", num, &num);
Per assegnare l'indirizzo della variabile num al puntatore numPtr, si assegna l'indirizzo della variabile num, come nell'esempio riportato di seguito:
numPtr = #
Per scoprire cosa è memorizzato all'indirizzo puntato da numPtr, la variabile deve essere dereferenziata. La dereferenziazione si ottiene con l'asterisco con cui è stato dichiarato il puntatore.
printf("Il valore %d è memorizzato all'indirizzo %X\n", *numPtr, numPtr);
Tutte le variabili in un programma risiedono in memoria. Le istruzioni fornite di seguito richiedono che il compilatore riservi 4 byte di memoria su un computer a 32 bit per la variabile in virgola mobile x, quindi vi inserisca il valore 6.5.
galleggiante x;
x = 6,5;
Poiché la posizione dell'indirizzo in memoria di qualsiasi variabile si ottiene inserendo l'operatore & prima del suo nome, quindi &x è l'indirizzo di x. C ci consente di andare oltre e definire una variabile, chiamata puntatore, che contiene l'indirizzo di altre variabili. Piuttosto possiamo dire che il puntatore punta ad altre variabili. Ad esempio:
galleggiante x;
numero decimale* px;
x = 6,5;
px = &x;
definisce px come un puntatore a oggetti di tipo float e lo imposta uguale all'indirizzo di x. Quindi, *px si riferisce al valore di x:

Esaminiamo le seguenti affermazioni:
int var_x;
int* ptrX;
dove_x = 6;
ptrX = &var_x;
*ptrX = 12;
printf("valore di x : %d", var_x);
La prima riga fa sì che il compilatore riservi uno spazio in memoria per un intero. La seconda riga dice al compilatore di riservare spazio per memorizzare un puntatore.
Un puntatore è una posizione di archiviazione per un indirizzo. La terza riga dovrebbe ricordarti le istruzioni scanf. L'operatore "&" dell'indirizzo dice al compilatore di andare al posto in cui ha archiviato var_x, e poi di dare l'indirizzo della posizione di archiviazione a ptrX.
L'asterisco * davanti a una variabile indica al compilatore di dereferenziare il puntatore e andare in memoria. Quindi puoi fare assegnazioni alla variabile memorizzata in quella posizione. Puoi fare riferimento a una variabile e accedere ai suoi dati tramite un puntatore. Vediamo un esempio di puntatori:
/* illustrazione dell'uso del puntatore */
#include <stdio.h>
int principale()
{
int indice, *pt1, *pt2;
indice = 39; /* qualsiasi valore numerico */
pt1 = &index; /* l'indirizzo dell'indice */
punto2 = punto1;
printf("Il valore è %d %d %d\n", indice, *pt1, *pt2);
*pt1 = 13; /* questo cambia il valore dell'indice */
printf("Il valore è %d %d %d\n", indice, *pt1, *pt2);
restituisci 0;
}
L'output del programma verrà visualizzato come segue:
Il valore è 39 39 39
Il valore è 13 13 13
Vediamo un altro esempio per comprendere meglio l'uso dei puntatori:
#include <stdio.h>
#include <stringa.h>
int principale()
{
char strg[40], *lì, uno, due;
int *pt, lista[100], indice;
strcpy(strg, "Questa è una stringa di caratteri.");
/* la funzione strcpy() serve per copiare una stringa in un'altra. Leggeremo più avanti sulla funzione strcpy() nella sezione String */
uno = strg[0]; /* uno e due sono identici */
due = *ctrl;
printf("Il primo output è %c %c\n", uno, due);
uno = strg[8]; /* uno e due sono identici */
due = *(ctrl+8);
printf("Il secondo output è %c %c\n", uno, due);
lì = ctrl+10; /* strg+10 è identico a &strg[10] */
printf("Il terzo output è %c\n", strg[10]);
printf("Il quarto output è %c\n", *there);
per (indice = 0 ; indice < 100 ; indice++)
lista[indice] = indice + 100;
pt = lista + 27;
printf("Il quinto output è %d\n", list[27]);
printf("Il sesto output è %d\n", *pt);
restituisci 0;
}
L'output del programma sarà simile a questo:
Il primo output è TT
Il secondo output è aa
Il terzo output è c
Il quarto output è c
Il quinto output è 127
Il sesto output è 127
Matrici
Un array è una raccolta di variabili dello stesso tipo. I singoli elementi dell'array sono identificati da un indice intero. In C l'indice inizia da zero ed è sempre scritto tra parentesi quadre.
Abbiamo già incontrato array unidimensionali dichiarati in questo modo
int risultati[20];
Gli array possono avere più dimensioni, nel qual caso potrebbero essere dichiarati come
int risultati_2d[20][5];
int risultati_3d[20][5][3];
Ogni indice ha il suo set di parentesi quadre. Un array è dichiarato nella funzione principale, solitamente include dettagli sulle dimensioni. È possibile usare un altro tipo chiamato puntatore al posto di un array. Ciò significa che le dimensioni non sono fissate immediatamente, ma lo spazio può essere allocato come richiesto. Questa è una tecnica avanzata che è richiesta solo in alcuni programmi specializzati.
A titolo di esempio, ecco una semplice funzione per sommare tutti gli interi in un array unidimensionale.
int add_array(int array[], int dimensione)
{
int io;
int totale = 0;
per(i = 0; i < dimensione; i++)
totale += array[i];
ritorno(totale);
}
Il programma dato di seguito creerà una stringa, accederà ad alcuni dati in essa contenuti, la stamperà. Vi accederà di nuovo tramite puntatori, quindi stamperà la stringa. Dovrebbe stampare "Ciao!" e "012345678" su righe diverse. Vediamo la codifica del programma:
#include <stdio.h>
#definisci STR_LENGTH 10
vuoto principale()
{
carattere Str[STR_LENGTH];
carattere* pStr;
int io;
Str[0] = 'H';
Str[1] = 'i';
Str[2] = '!';
Str[3] = '\0'; // carattere speciale di fine stringa NULL
printf("La stringa in Str è: %s\n", Str);
pStr = &Str[0];
per (i = 0; i < LUNGHEZZA_STR; i++)
{
*pStr = '0'+i;
; ...
}
Str[STR_LENGTH-1] = '\0';
printf("La stringa in Str è: %s\n", Str);
}
[] (parentesi quadre) vengono utilizzate per dichiarare l'array. La riga del programma char Str[STR_LENGTH]; dichiara un array di dieci caratteri. Si tratta di dieci caratteri individuali, che vengono tutti messi insieme in memoria nello stesso posto. È possibile accedervi tramite il nome della nostra variabile Str insieme a [n] dove n è il numero dell'elemento.
Bisogna sempre tenere a mente quando si parla di array che quando C dichiara un array di dieci, gli elementi a cui si può accedere sono numerati da 0 a 9. L'accesso al primo elemento corrisponde all'accesso all'elemento 0. Quindi nel caso degli array si conta sempre da 0 alla dimensione dell'array - 1.
Successivamente, nota che abbiamo inserito le lettere "Ciao!" nell'array, ma poi abbiamo inserito un '\0', probabilmente ti starai chiedendo cosa sia. "\0" sta per NULL e rappresenta la fine della stringa. Tutte le stringhe di caratteri devono terminare con questo carattere speciale '\0'. Se non lo fanno, e poi qualcuno chiama printf sulla stringa, allora printf partirà dalla posizione di memoria della tua stringa e continuerà a stampare, dicendo che incontra '\0' e quindi finirai con un mucchio di spazzatura alla fine della tua stringa. Quindi assicurati di terminare correttamente le tue stringhe.
Array di caratteri
Una stringa costante, come
"Io sono una corda"
è un array di caratteri. È rappresentato internamente in C dai caratteri ASCII nella stringa, ovvero "I", blank, "a", "m", ... o la stringa sopra, e terminato dal carattere speciale null "\0" in modo che i programmi possano trovare la fine della stringa.
Le costanti stringa vengono spesso utilizzate per rendere comprensibile l'output del codice mediante printf:
printf("Ciao, mondo\n");
printf("Il valore di a è: %f\n", a);
Le costanti stringa possono essere associate a variabili. C fornisce la variabile di tipo carattere, che può contenere un carattere (1 byte) alla volta. Una stringa di caratteri è memorizzata in un array di tipo carattere, un carattere ASCII per posizione.
Non dimenticare mai che, poiché le stringhe terminano convenzionalmente con il carattere nullo "\0", è necessaria una posizione di archiviazione aggiuntiva nell'array.
Il C non fornisce alcun operatore che manipoli intere stringhe in una volta sola. Le stringhe vengono manipolate tramite puntatori o tramite speciali routine disponibili nella libreria string standard string.h.
Utilizzare i puntatori di carattere è relativamente facile poiché il nome di un array è solo un puntatore al suo primo elemento. Considerate il programma dato di seguito:
#include<stdio.h>
vuoto principale()
{
carattere testo_1[100], testo_2[100], testo_3[100];
carattere *ta, *tb;
int io;
/* imposta il messaggio come array */
/* di caratteri; inizializzalo */
/* alla stringa costante "..." */
/* lascia che sia il compilatore a decidere */
/* la sua dimensione usando [] */
char message[] = "Ciao, sono una stringa; cosa sono
Voi?";
printf("Messaggio originale: %s\n", messaggio);
/* copia il messaggio in text_1 */
io=0;
while ( (text_1[i] = messaggio[i]) != '\0' )
io++;
printf("Testo_1: %s\n", testo_1);
/* usa l'aritmetica dei puntatori esplicita */
il tuo=messaggio;
tb=testo_2;
mentre ( ( *tb++ = *ta++ ) != '\0' )
;
printf("Testo_2: %s\n", testo_2);
}
L'output del programma sarà il seguente:
Messaggio originale: Ciao, io sono una stringa; tu cosa sei?
Testo_1: Ciao, io sono una stringa; tu cosa sei?
Testo_2: Ciao, io sono una stringa; tu cosa sei?
La libreria standard "string" contiene molte funzioni utili per manipolare le stringhe, che impareremo più avanti nella sezione sulle stringhe.
Accesso agli elementi
Per accedere a un singolo elemento nell'array, il numero di indice segue il nome della variabile tra parentesi quadre. La variabile può quindi essere trattata come qualsiasi altra variabile in C. L'esempio seguente assegna un valore al primo elemento nell'array.
x[0] = 16;
L'esempio seguente stampa il valore del terzo elemento in un array.
printf("%d\n", x[2]);
L'esempio seguente utilizza la funzione scanf per leggere un valore dalla tastiera nell'ultimo elemento di un array con dieci elementi.
scanf("%d", &x[9]);
Inizializzazione degli elementi dell'array
Gli array possono essere inizializzati come qualsiasi altra variabile tramite assegnazione. Poiché un array contiene più di un valore, i singoli valori vengono inseriti tra parentesi graffe e separati da virgole. L'esempio seguente inizializza un array a dieci dimensioni con i primi dieci valori della tabellina del tre.
int x[10] = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30};
In questo modo si evita di dover assegnare i valori singolarmente, come nell'esempio seguente.
intero x[10];
x[0] = 3;
x[1] = 6;
x[2] = 9;
x[3] = 12;
x[4] = 15;
x[5] = 18;
x[6] = 21;
x[7] = 24;
x[8] = 27;
x[9] = 30;
Eseguire un ciclo in un array
Poiché l'array è indicizzato in sequenza, possiamo usare il ciclo for per visualizzare tutti i valori di un array. Il seguente esempio visualizza tutti i valori di un array:
#include <stdio.h>
int principale()
{
intero x[10];
int contatore;
/* Rendi casuale il generatore di numeri casuali */
srand((unsigned)time(NULL));
/* Assegna valori casuali alla variabile */
per (contatore=0; contatore<10; contatore++)
x[contatore] = rand();
/* Visualizza il contenuto dell'array */
per (contatore=0; contatore<10; contatore++)
printf("l'elemento %d ha il valore %d\n", contatore, x[contatore]);
restituisci 0;
}
sebbene l'output stamperà ogni volta valori diversi, il risultato verrà visualizzato più o meno così:
l'elemento 0 ha il valore 17132
l'elemento 1 ha il valore 24904
l'elemento 2 ha il valore 13466
l'elemento 3 ha il valore 3147
l'elemento 4 ha il valore 22006
l'elemento 5 ha il valore 10397
l'elemento 6 ha il valore 28114
l'elemento 7 ha il valore 19817
l'elemento 8 ha il valore 27430
l'elemento 9 ha il valore 22136
Array multidimensionali
Un array può avere più di una dimensione. Permettere all'array di avere più di una dimensione fornisce una maggiore flessibilità. Ad esempio, i fogli di calcolo sono costruiti su un array bidimensionale; un array per le righe e un array per le colonne.
L'esempio seguente utilizza un array bidimensionale con due righe, ciascuna contenente cinque colonne:
#include <stdio.h>
int principale()
{
/* Dichiara un array multidimensionale 2 x 5 */
intero x[2][5] = { {1, 2, 3, 4, 5},
{2, 4, 6, 8, 10}};
int riga, colonna;
/* Visualizza le righe */
per (riga=0; riga<2; riga++)
{
/* Visualizza le colonne */
per (colonna=0; colonna<5; colonna++)
printf("%d\t", x[riga][colonna]);
{NS} = "Puntare il dito sulla tastiera";
}
restituisci 0;
}
L'output di questo programma verrà visualizzato come segue:
1 2 3 4 5
2 4 6 8 10
Corde
Una stringa è un gruppo di caratteri, solitamente lettere dell'alfabeto, che consente di formattare la visualizzazione di stampa in modo che abbia un bell'aspetto, abbia nomi e titoli significativi e sia esteticamente gradevole per te e per le persone che utilizzano l'output del tuo programma.
In effetti, hai già utilizzato le stringhe negli esempi degli argomenti precedenti. Ma non è l'introduzione completa delle stringhe. Ci sono molti casi possibili nella programmazione, in cui l'uso di stringhe formattate aiuta il programmatore a evitare troppe complicazioni nel programma e troppi bug, ovviamente.
Una definizione completa di stringa è una serie di dati di tipo carattere terminati da un carattere nullo ('\0').
Quando il linguaggio C deve utilizzare una stringa di dati in qualche modo, per confrontarla con un'altra stringa, per emetterla in output, per copiarla in un'altra stringa o altro, le funzioni sono impostate per fare ciò per cui sono state chiamate finché non viene rilevato un valore null.
Non esiste un tipo di dati di base per una stringa in C Invece; le stringhe in C sono implementate come un array di caratteri. Ad esempio, per memorizzare un nome potresti dichiarare un array di caratteri abbastanza grande da memorizzare il nome, e quindi usare le funzioni di libreria appropriate per manipolare il nome.
L'esempio seguente visualizza sullo schermo la stringa immessa dall'utente:
#include <stdio.h>
int principale()
{
char name[80]; /* Crea un array di caratteri
chiamato nome */
printf("Inserisci il tuo nome: ");
ottiene(nome);
printf("Il nome che hai inserito era %s\n", name);
restituisci 0;
}
L'esecuzione del programma sarà:
Inserisci il tuo nome: Tarun Tyagi
Il nome che hai inserito era Tarun Tyagi
Alcune funzioni comuni delle stringhe
La libreria string.h standard contiene molte funzioni utili per manipolare le stringhe. Alcune delle funzioni più utili sono state esemplificate qui.
La funzione strlen
La funzione strlen è usata per determinare la lunghezza di una stringa. Impariamo l'uso di strlen con un esempio:
#include <stdio.h>
#include <stringa.h>
int principale()
{
nome del carattere[80];
lunghezza intera;
printf("Inserisci il tuo nome: ");
ottiene(nome);
lunghezza = strlen(nome);
printf("Il tuo nome contiene %d caratteri\n", lunghezza);
restituisci 0;
}
E l'esecuzione del programma sarà la seguente:
Inserisci il tuo nome: Tarun Subhash Tyagi
Il tuo nome ha 19 caratteri
Inserisci il tuo nome: Preeti Tarun
Il tuo nome ha 12 caratteri
La funzione strcpy
La funzione strcpy è usata per copiare una stringa in un'altra. Impariamo l'uso di questa funzione con un esempio:
#include <stdio.h>
#include <stringa.h>
int principale()
{
carattere primo[80];
char secondo[80];
printf("Inserisci la prima stringa: ");
ottiene(primo);
printf("Inserisci la seconda stringa: ");
ottiene(secondo);
printf("primo: %s, e secondo: %s Prima di strcpy()\n "
, primo, secondo);
strcpy(secondo, primo);
printf("primo: %s, e secondo: %s Dopo strcpy()\n",
primo, secondo);
restituisci 0;
}
e l'output del programma sarà:
Inserisci la prima stringa: Tarun
Inserisci la seconda stringa: Tyagi
prima: Tarun e seconda: Tyagi Prima di strcpy()
primo: Tarun e secondo: Tarun Dopo strcpy()
La funzione strcmp
La funzione strcmp viene utilizzata per confrontare due stringhe insieme. Il nome della variabile di un array punta all'indirizzo base di quell'array. Pertanto, se proviamo a confrontare due stringhe utilizzando quanto segue, confronteremo due indirizzi, che ovviamente non saranno mai gli stessi poiché non è possibile memorizzare due valori nella stessa posizione.
if (first == second) /* Non è mai possibile confrontare stringhe */
L'esempio seguente utilizza la funzione strcmp per confrontare due stringhe:
#include <stringa.h>
int principale()
{
char primo[80], secondo[80];
int t;
per(t=1;t<=2;t++)
{
printf("\nInserisci una stringa: ");
ottiene(primo);
printf("Inserisci un'altra stringa: ");
ottiene(secondo);
se (strcmp(primo, secondo) == 0)
puts("Le due stringhe sono uguali");
altro
puts("Le due stringhe non sono uguali");
}
restituisci 0;
}
E l'esecuzione del programma sarà la seguente:
Inserisci una stringa: Tarun
Inserisci un'altra stringa: tarun
Le due stringhe non sono uguali
Inserisci una stringa: Tarun
Inserisci un'altra stringa: Tarun
Le due stringhe sono uguali
La funzione strcat
La funzione strcat serve per unire una stringa a un'altra. Vediamo come? Con l'aiuto di un esempio:
#include <stringa.h>
int principale()
{
char primo[80], secondo[80];
printf("Inserisci una stringa: ");
ottiene(primo);
printf("Inserisci un'altra stringa: ");
ottiene(secondo);
strcat(primo, secondo);
printf("Le due stringhe unite insieme: %s\n",
Primo);
restituisci 0;
}
E l'esecuzione del programma sarà la seguente:
Inserisci una stringa: Dati
Inserisci un'altra stringa: Recupero
Le due stringhe unite: DataRecovery
La funzione strtok
La funzione strtok viene utilizzata per trovare il token successivo in una stringa. Il token è specificato da un elenco di possibili delimitatori.
L'esempio seguente legge una riga di testo da un file e determina una parola usando i delimitatori, spazio, tabulazione e nuova riga. Ogni parola viene quindi visualizzata su una riga separata:
#include <stdio.h>
#include <stringa.h>
int principale()
{
FILE *in;
linea di carattere[80];
char *delimitatori = " \t\n";
carattere *token;
se ((in = fopen("C:\\text.txt", "r")) == NULL)
{
puts("Impossibile aprire il file di input");
restituisci 0;
}
/* Leggi ogni riga una alla volta */
mentre(!feof(in))
{
/* Ottieni una riga */
fgets(linea, 80, in);
se (!feof(in))
{
/* Spezza la riga in parole */
token = strtok(linea, delimitatori);
mentre (token != NULL)
{
mette(token);
/* Ottieni la parola successiva */
token = strtok(NULL, delimitatori);
}
}
}
fclose(in);
restituisci 0;
}
Il programma sopra, in = fopen("C:\\text.txt", "r"), apre un file esistente C:\\text.txt. Se non esiste nel percorso specificato o per qualsiasi motivo il file non può essere aperto, sullo schermo viene visualizzato un messaggio di errore.
Consideriamo il seguente esempio, che utilizza alcune di queste funzioni:
#include <stdio.h>
#include <stringa.h>
vuoto principale()
{
char line[100], *sottotesto;
/* inizializza la stringa */
strcpy(line,"ciao, sono una stringa;");
printf("Riga: %s\n", riga);
/* aggiungi alla fine della stringa */
strcat(line,"cosa sei?");
printf("Riga: %s\n", riga);
/* trova la lunghezza della stringa */
/* strlen riporta indietro */
/* lunghezza come tipo size_t */
printf("Lunghezza della riga: %d\n", (int)strlen(riga));
/* trova l'occorrenza delle sottostringhe */
se ( (sub_text = strchr ( riga, 'W' ) )!= NULL )
printf("Stringa che inizia con \"W\" ->%s\n",
sotto_testo);
se ( ( sub_text = strchr ( riga, 'w' ) )!= NULL )
printf("Stringa che inizia con \"w\" ->%s\n",
sotto_testo);
se ( ( sotto_testo = strchr ( sotto_testo, 'u' ) )!= NULL )
printf("Stringa che inizia con \"w\" ->%s\n",
sotto_testo);
}
L'output del programma verrà visualizzato come segue:
Linea: ciao, sono una stringa;
Linea: ciao, io sono una corda; tu cosa sei?
Lunghezza della linea: 35
Stringa che inizia con "w" -> cosa sei?
Stringa che inizia con "w" ->u?
Funzioni
Il modo migliore per sviluppare e mantenere un programma di grandi dimensioni è costruirlo da pezzi più piccoli, ognuno dei quali è più facile da gestire (una tecnica a volte chiamata Divide et Impera). Le funzioni consentono al programmatore di modularizzare il programma.
Le funzioni consentono di suddividere programmi complessi in piccoli blocchi, ognuno dei quali è più facile da scrivere, leggere e gestire. Abbiamo già incontrato la funzione main e fatto uso di printf dalla libreria standard. Possiamo ovviamente creare le nostre funzioni e file di intestazione. Una funzione ha il seguente layout:
return-type function-name (elenco argomenti se necessario)
{
dichiarazioni-locali;
dichiarazioni ;
restituisci valore-di-ritorno;
}
Se return-type viene omesso, C imposta di default int. Il return-value deve essere del tipo dichiarato. Tutte le variabili dichiarate all'interno delle funzioni sono chiamate variabili locali, in quanto sono note solo nella funzione per cui sono state definite.
Alcune funzioni hanno un elenco di parametri che fornisce un metodo di comunicazione tra la funzione e il modulo che ha chiamato la funzione. I parametri sono anche variabili locali, in quanto non sono disponibili all'esterno della funzione. I programmi trattati finora hanno tutti main, che è una funzione.
Una funzione può semplicemente eseguire un'attività senza restituire alcun valore, nel qual caso ha il seguente layout:
void function-name (elenco degli argomenti se necessario)
{
dichiarazioni-locali ;
dichiarazioni;
}
Gli argomenti vengono sempre passati per valore nelle chiamate di funzione C. Ciò significa che copie locali dei valori degli argomenti vengono passate alle routine. Qualsiasi modifica apportata agli argomenti internamente alla funzione viene apportata solo alle copie locali degli argomenti.
Per modificare o definire un argomento nell'elenco degli argomenti, questo argomento deve essere passato come indirizzo. Si utilizzano variabili regolari se la funzione non modifica i valori di quegli argomenti. DEVI utilizzare puntatori se la funzione modifica i valori di quegli argomenti.
Impariamo con degli esempi:
#include <stdio.h>
scambio vuoto (int *a, int *b)
{
temperatura interna;
temperatura = *a;
*a = *b;
*b = temperatura;
printf("Dalla funzione exchange: ");
printf("a = %d, b = %d\n", *a, *b);
}
vuoto principale()
{
interno a, b;
un = 5;
e = 7;
printf("Da principale: a = %d, b = %d\n", a, b);
scambia(&a, &b);
printf("Torna in main: ");
printf("a = %d, b = %d\n", a, b);
}
E l'output di questo programma verrà visualizzato come segue:
Da principale: a = 5, b = 7
Dalla funzione di scambio: a = 7, b = 5
Torna al principale: a = 7, b = 5
Vediamo un altro esempio. L'esempio seguente usa una funzione chiamata square che scrive il quadrato dei numeri tra 1 e 10.
#include <stdio.h>
int square(int x); /* Prototipo della funzione */
int principale()
{
int contatore;
per (contatore=1; contatore<=10; contatore++)
printf("Il quadrato di %d è %d\n", contatore, quadrato(contatore));
restituisci 0;
}
/* Definisci la funzione 'quadrato' */
int quadrato(int x)
{
restituisci x * x;
}
L'output di questo programma verrà visualizzato come segue:
Il quadrato di 1 è 1
Il quadrato di 2 è 4
Il quadrato di 3 è 9
Il quadrato di 4 è 16
Il quadrato di 5 è 25
Il quadrato di 6 è 36
Il quadrato di 7 è 49
Il quadrato di 8 è 64
Il quadrato di 9 è 81
Il quadrato di 10 è 100
Il prototipo di funzione square dichiara una funzione che accetta un parametro intero e restituisce un intero. Quando il compilatore raggiunge la chiamata di funzione a square nel programma principale, è in grado di controllare la chiamata di funzione rispetto alla definizione della funzione.
Quando il programma raggiunge la riga che richiama la funzione square, il programma salta alla funzione ed esegue quella funzione prima di riprendere il suo percorso attraverso il programma principale. I programmi che non hanno un tipo di ritorno devono essere dichiarati usando void. Quindi i parametri della funzione possono essere Pass By Value o Pass By Reference.
Una funzione ricorsiva è una funzione che richiama se stessa. E questo processo è chiamato ricorsione.
Funzioni di passaggio per valore
I parametri della funzione square nell'esempio precedente vengono passati per valore. Ciò significa che solo una copia della variabile è stata passata alla funzione. Qualsiasi modifica al valore non verrà riflessa nella funzione chiamante.
L'esempio seguente usa pass-by-value e modifica il valore del parametro passato, il che non ha alcun effetto sulla funzione chiamante. La funzione count_down è stata dichiarata come void in quanto non c'è alcun tipo di ritorno.
#include <stdio.h>
void count_down(int x);
int principale()
{
int contatore;
per (contatore=1; contatore<=10; contatore++)
count_down(contatore);
restituisci 0;
}
conteggio alla rovescia vuoto(int x)
{
int contatore;
per (contatore = x; contatore > 0; contatore--)
{
printf("%d ", x);
X--;
}
{NS} = "Puntare il dito sulla tastiera";
}
L'output del programma verrà visualizzato come segue:
1
2 1
3 2 1
4 3 2 1
5 4 3 2 1
6 5 4 3 2 1
7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1
Vediamo un altro esempio di C Pass By Value per capirlo meglio. L'esempio seguente converte un numero tra 1 e 30.000 digitato dall'utente in parole.
#include <stdio.h>
void do_units(int num);
void do_tens(int num);
void do_teens(int num);
int principale()
{
int num, resto;
Fare
{
printf("Inserisci un numero compreso tra 1 e 30.000: ");
{NS} = "%d";
} while (num < 1 || num > 30000);
resto = numero;
printf("%d in parole = ", num);
do_tens(residuo/1000);
se (num >= 1000)
printf("mille");
residuo %= 1000;
do_units(residuo/100);
se (residuo >= 100)
{
printf("cento ");
}
se (num > 100 && num%100 > 0)
printf("e ");
residuo %=100;
do_tens(residuo);
{NS} = "Puntare il dito sulla tastiera";
restituisci 0;
}
vuoto do_units(int num)
{
interruttore(numero)
{
caso 1:
printf("uno ");
rottura;
caso 2:
printf("due ");
rottura;
caso 3:
printf("tre ");
rottura;
caso 4:
printf("quattro ");
rottura;
caso 5:
printf("cinque ");
rottura;
caso 6:
printf("sei ");
rottura;
caso 7:
printf("sette ");
rottura;
caso 8:
printf("otto ");
rottura;
caso 9:
printf("nove ");
}
}
vuoto do_tens(int num)
{
interruttore(num/10)
{
caso 1:
do_teens(numero);
rottura;
caso 2:
printf("venti ");
rottura;
caso 3:
printf("trenta ");
rottura;
caso 4:
printf("quaranta ");
rottura;
caso 5:
printf("cinquanta ");
rottura;
caso 6:
printf("sessanta ");
rottura;
caso 7:
printf("settanta ");
rottura;
caso 8:
printf("ottanta ");
rottura;
caso 9:
printf("novanta ");
}
se (num/10 != 1)
do_units(num%10);
}
vuoto do_teens(int num)
{
interruttore(numero)
{
caso 10:
printf("dieci ");
rottura;
caso 11:
printf("undici ");
rottura;
caso 12:
printf("dodici ");
rottura;
caso 13:
printf("tredici ");
rottura;
caso 14:
printf("quattordici ");
rottura;
caso 15:
printf("quindici ");
rottura;
caso 16:
printf("sedici ");
rottura;
caso 17:
printf("diciassette ");
rottura;
caso 18:
printf("diciotto ");
rottura;
caso 19:
printf("diciannove ");
}
}
e l'output del programma sarà il seguente:
Inserisci un numero compreso tra 1 e 30.000: 12345
12345 in lettere = dodicimilatrecentoquarantacinque
Chiamata per riferimento
Per fare una chiamata di funzione per riferimento, invece di passare la variabile stessa, passa l'indirizzo della variabile. L'indirizzo della variabile può essere preso usando l'operatore &. Quanto segue chiama una funzione di scambio passando l'indirizzo delle variabili invece dei valori effettivi.
scambia(&x, &y);
Dereferenziazione
Il problema che abbiamo ora è che alla funzione swap è stato passato l'indirizzo anziché la variabile, quindi dobbiamo dereferenziare le variabili in modo da considerare i valori effettivi anziché gli indirizzi delle variabili per poterle scambiare.
La dereferenziazione si ottiene in C usando la notazione del puntatore (*). In parole povere, questo significa mettere un * prima di ogni variabile prima di usarla in modo che faccia riferimento al valore della variabile piuttosto che al suo indirizzo. Il seguente programma illustra il passaggio per riferimento per scambiare due valori.
#include <stdio.h>
= ...
int principale()
{
intero x=6, y=10;
printf("Prima della funzione swap, x = %d e y =
{NS} = "x, y";
scambia(&x, &y);
printf("Dopo la funzione swap, x = %d e y =
{NS} = "x, y";
restituisci 0;
}
scambiare(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temperatura;
}
Vediamo l'output del programma:
Prima dello scambio di funzioni, x = 6 e y = 10
Dopo lo scambio di funzioni, x = 10 e y = 6
Le funzioni possono essere ricorsive, ovvero una funzione può chiamare se stessa. Ogni chiamata a se stessa richiede che lo stato corrente della funzione venga spinto sullo stack. È importante ricordare questo fatto, poiché è facile creare uno stack overflow, ovvero lo stack ha esaurito lo spazio per inserire altri dati.
L'esempio seguente calcola il fattoriale di un numero usando la ricorsione. Un fattoriale è un numero moltiplicato per ogni altro intero al di sotto di sé, fino a 1. Ad esempio, il fattoriale del numero 6 è:
Fattoriale 6 = 6 * 5 * 4 * 3 * 2 * 1
Pertanto il fattoriale di 6 è 720. Dall'esempio precedente si può vedere che fattoriale 6 = 6 * fattoriale 5. Allo stesso modo, fattoriale 5 = 5 * fattoriale 4, e così via.
La seguente è la regola generale per il calcolo dei numeri fattoriali.
fattoriale(n) = n * fattoriale(n-1)
La regola di cui sopra termina quando n = 1, poiché il fattoriale di 1 è 1. Cerchiamo di capirla meglio con l'aiuto di un esempio:
#include <stdio.h>
long int fattoriale(int num);
int principale()
{
numero intero;
intero lungo f;
printf("Inserisci un numero: ");
{NS} = "%d";
f = fattoriale(num);
printf("il fattoriale di %d è %ld\n", num, f);
restituisci 0;
}
long int fattoriale(int num)
{
se (numero == 1)
restituisci 1;
altro
restituisci num * fattoriale(num-1);
}
Vediamo l'output dell'esecuzione di questo programma:
Inserisci un numero: 7
fattoriale di 7 è 5040
Allocazione della memoria in C
Il compilatore C ha una libreria di allocazione della memoria, definita in malloc.h. La memoria viene riservata tramite la funzione malloc e restituisce un puntatore all'indirizzo. Accetta un parametro, la dimensione della memoria richiesta in byte.
L'esempio seguente alloca spazio per la stringa "hello world".
ptr = (char *)malloc(strlen("Ciao mondo") + 1);
Il byte extra è necessario per tenere conto del carattere di terminazione della stringa, '\0'. Il (char *) è chiamato cast e forza il tipo di ritorno a essere char *.
Poiché i tipi di dati hanno dimensioni diverse e malloc restituisce lo spazio in byte, per motivi di portabilità è buona norma utilizzare l'operatore sizeof quando si specifica una dimensione da allocare.
L'esempio seguente legge una stringa nel buffer dell'array di caratteri, quindi alloca la quantità esatta di memoria richiesta e la copia in una variabile denominata "ptr".
#include <stringa.h>
#include <malloc.h>
int principale()
{
carattere *ptr, buffer[80];
printf("Inserisci una stringa: ");
ottiene(buffer);
ptr = (char *)malloc((strlen(buffer) + 1) *
dimensione(carattere));
strcpy(ptr, buffer);
printf("Hai inserito: %s\n", ptr);
restituisci 0;
}
L'output del programma sarà il seguente:
Inserisci una stringa: L'India è la migliore
Hai inserito: L'India è la migliore
Riallocazione della memoria
È possibile molte volte durante la programmazione che si voglia riallocare la memoria. Questo si fa con la funzione realloc. La funzione realloc accetta due parametri, l'indirizzo base della memoria che si desidera ridimensionare e la quantità di spazio che si desidera riservare e restituisce un puntatore all'indirizzo base.
Supponiamo di avere spazio riservato per un puntatore chiamato msg e di voler riassegnare lo spazio alla quantità di spazio che occupa già, più la lunghezza di un'altra stringa, in tal caso potremmo usare quanto segue.
msg = (char *)realloc(msg, (strlen(msg) + strlen(buffer) + 1)*sizeof(char));
Il seguente programma illustra l'uso di malloc, realloc e free. L'utente immette una serie di stringhe che vengono unite insieme. Il programma interrompe la lettura delle stringhe quando viene immessa una stringa vuota.
#include <stringa.h>
#include <malloc.h>
int principale()
{
buffer di caratteri[80], *msg;
int primavolta=0;
Fare
{
printf("\nInserisci una frase: ");
ottiene(buffer);
se (!primavolta)
{
messaggio = (char *)malloc((strlen(buffer) + 1) *
dimensione(carattere));
strcpy(msg, buffer);
primavolta = 1;
}
altro
{
msg = (char *)realloc(msg, (strlen(msg) +
strlen(buffer) + 1) * sizeof(char));
strcat(msg, buffer);
}
mette(messaggio);
} while(strcmp(buffer, ""));
gratuito(msg);
restituisci 0;
}
L'output del programma sarà il seguente:
Inserisci una frase: C'era una volta C'era
una volta
Inserisci una frase: c'era un re
C'era una voltac'era un re
Inserisci una frase: il re era C'era
una voltac'era un reil re era
Inserisci una frase:
C'era una volta un reil re era
Liberare la memoria
Quando hai finito con la memoria che è stata allocata, non dovresti mai dimenticare di liberare la memoria poiché libererà risorse e migliorerà la velocità. Per liberare la memoria allocata, usa la funzione free.
libero(ptr);
Strutture
Oltre ai tipi di dati di base, C ha un meccanismo di struttura che consente di raggruppare elementi di dati correlati tra loro sotto un nome comune. Questo è comunemente definito come un tipo definito dall'utente.
La parola chiave struct avvia la definizione della struttura e un tag fornisce il nome univoco alla struttura. I tipi di dati e i nomi delle variabili aggiunti alla struttura sono membri della struttura. Il risultato è un modello di struttura che può essere utilizzato come specificatore di tipo. Quella che segue è una struttura con un tag di mese.
mese della struttura
{
nome del carattere[10];
carattere abbreviato[4];
int giorni;
};
Un tipo di struttura viene solitamente definito all'inizio di un file mediante un'istruzione typedef. typedef definisce e nomina un nuovo tipo, consentendone l'utilizzo in tutto il programma. typedef solitamente si trova subito dopo le istruzioni #define e #include in un file.
La parola chiave typedef può essere usata per definire una parola per fare riferimento alla struttura anziché specificare la parola chiave struct con il nome della struttura. Di solito si nomina typedef in lettere maiuscole. Ecco alcuni esempi di definizione di struttura.
tipodef struttura {
nome del carattere[64];
corso di char[128];
età intera;
anno interno;
} studente;
Questo definisce un nuovo tipo di variabile studente di tipo studente che può essere dichiarata come segue.
studente st_rec;
Nota quanto sia simile alla dichiarazione di un int o float. Il nome della variabile è st_rec, ha membri chiamati name, course, age e year. Allo stesso modo,
elemento struct typedef
{
dati del carattere;
elemento struct *next;
} ELEMENTO STACK;
Una variabile dell'elemento struct di tipo definito dall'utente può ora essere dichiarata come segue.
ELEMENTOSTACK *pila;
Consideriamo la seguente struttura:
studente strutturato
{
carattere *nome;
grado int;
};
Un puntatore alla struttura student può essere definito come segue.
struct studente *hnc;
Quando si accede a un puntatore a una struttura, l'operatore puntatore membro, -> viene utilizzato al posto dell'operatore punto. Per aggiungere un voto a una struttura,
s.grado = 50;
È possibile assegnare un voto alla struttura come segue.
s->grado = 50;
Come per i tipi di dati di base, se vuoi che le modifiche apportate in una funzione ai parametri passati siano persistenti, devi passare per riferimento (passare l'indirizzo). Il meccanismo è esattamente lo stesso dei tipi di dati di base. Passa l'indirizzo e fai riferimento alla variabile usando la notazione puntatore.
Dopo aver definito la struttura, puoi dichiararne un'istanza e assegnare valori ai membri usando la notazione a punto. Il seguente esempio illustra l'uso della struttura mese.
#include <stdio.h>
#include <stringa.h>
mese della struttura
{
nome del carattere[10];
abbreviazione del carattere[4];
int giorni;
};
int principale()
{
struct mese m;
strcpy(m.name, "Gennaio");
strcpy(m.abbreviazione, "Gennaio");
m.giorni = 31;
printf("%s è abbreviato come %s e ha %d giorni\n", m.name, m.abbreviation, m.days);
restituisci 0;
}
L'output del programma sarà il seguente:
Gennaio è abbreviato in Jan e ha 31 giorni
Tutti i compilatori ANSI C consentono di assegnare una struttura a un'altra, eseguendo una copia membro per membro. Se avessimo strutture mese chiamate m1 e m2, potremmo assegnare i valori da m1 a m2 con quanto segue:
- Struttura con membri puntatori.
- La struttura viene inizializzata.
- Passaggio di una struttura a una funzione.
- Puntatori e strutture.
Strutture con membri puntatori in C
Mantenere le stringhe in un array di dimensioni fisse è un uso inefficiente della memoria. Un approccio più efficiente sarebbe quello di usare i puntatori. I puntatori sono usati nelle strutture esattamente nello stesso modo in cui sono usati nelle definizioni di puntatori normali. Vediamo un esempio:
#include <stringa.h>
#include <malloc.h>
mese della struttura
{
carattere *nome;
char *abbreviazione;
int giorni;
};
int principale()
{
struct mese m;
m.name = (char *)malloc((strlen("Gennaio")+1) *
dimensione(carattere));
strcpy(m.name, "Gennaio");
m.abbreviazione = (char *)malloc((strlen("Jan")+1) *
dimensione(carattere));
strcpy(m.abbreviazione, "Gennaio");
m.giorni = 31;
printf("%s è abbreviato come %s e ha %d giorni\n",
m.nome, m.abbreviazione, m.giorni);
restituisci 0;
}
L'output del programma sarà il seguente:
Gennaio è abbreviato in Jan e ha 31 giorni
Inizializzatori di struttura in C
Per fornire un set di valori iniziali per la struttura, gli Initialisers possono essere aggiunti all'istruzione di dichiarazione. Poiché i mesi iniziano da 1, ma gli array iniziano da zero in C, un elemento extra in posizione zero chiamato junk è stato utilizzato nell'esempio seguente.
#include <stdio.h>
#include <stringa.h>
mese della struttura
{
carattere *nome;
char *abbreviazione;
int giorni;
} dettagli_mese[] =
{
"Spazzatura", "Spazzatura", 0,
"Gennaio", "Gennaio", 31,
"Febbraio", "Febbraio", 28,
"Marzo", "Mar", 31,
"Aprile", "Apr", 30,
"Maggio", "Maggio", 31,
"Giugno", "Giugno", 30,
"Luglio", "Luglio", 31,
"Agosto", "Agosto", 31,
"Settembre", "Set", 30,
"Ottobre", "Ott", 31,
"Novembre", "Nov", 30,
"Dicembre", "Dicembre", 31
};
int principale()
{
int contatore;
per (contatore=1; contatore<=12; contatore++)
printf("%s è abbreviato come %s e ha %d giorni\n",
dettagli_mese[contatore].nome,
dettagli_mese[contatore].abbreviazione,
dettagli_mese[contatore].giorni);
restituisci 0;
}
E l'output verrà visualizzato come segue:
Gennaio è abbreviato in Jan e ha 31 giorni
Febbraio è abbreviato in Feb e ha 28 giorni
Marzo è abbreviato come Mar e ha 31 giorni
Aprile è abbreviato come Apr e ha 30 giorni
Maggio è abbreviato in May e ha 31 giorni
Giugno è abbreviato in Jun e ha 30 giorni
Luglio è abbreviato in Jul e ha 31 giorni
Agosto è abbreviato in Aug e ha 31 giorni
Settembre è abbreviato in Sep e ha 30 giorni
Ottobre è abbreviato in Oct e ha 31 giorni
Novembre è abbreviato come Nov e ha 30 giorni
Dicembre è abbreviato come Dec e ha 31 giorni
Passaggio di strutture alle funzioni in C
Le strutture possono essere passate come parametro a una funzione, proprio come qualsiasi tipo di dati di base. L'esempio seguente usa una struttura chiamata date che è passata a una funzione isLeapYear per determinare se l'anno è bisestile.
Normalmente si passerebbe solo il valore del giorno, ma l'intera struttura viene passata per illustrare il passaggio di strutture alle funzioni.
#include <stdio.h>
#include <stringa.h>
mese della struttura
{
carattere *nome;
char *abbreviazione;
int giorni;
} dettagli_mese[] =
{
"Spazzatura", "Spazzatura", 0,
"Gennaio", "Gennaio", 31,
"Febbraio", "Febbraio", 28,
"Marzo", "Mar", 31,
"Aprile", "Apr", 30,
"Maggio", "Maggio", 31,
"Giugno", "Giugno", 30,
"Luglio", "Luglio", 31,
"Agosto", "Agosto", 31,
"Settembre", "Set", 30,
"Ottobre", "Ott", 31,
"Novembre", "Nov", 30,
"Dicembre", "Dicembre", 31
};
data della struttura
{
int giorno;
int mese;
anno interno;
};
int isLeapYear(struct data d);
int principale()
{
data della struttura d;
printf("Inserisci la data (ad esempio: 11/11/1980): ");
scanf("%d/%d/%d", &d.giorno, &d.mese, &d.anno);
printf("La data %d %s %d è ", d.day,
dettagli_mese[g.mese].nome, g.anno);
se (èAnnoBisestile(d) == 0)
printf("non ");
puts("un anno bisestile");
restituisci 0;
}
int isLeapYear(struct data d)
{
se ((d.anno % 4 == 0 && d.anno % 100 != 0) ||
d.anno % 400 == 0)
restituisci 1;
restituisci 0;
}
E l'esecuzione del programma sarà la seguente:
Inserisci la data (ad esempio: 11/11/1980): 9/12/1980
La data 9 dicembre 1980 è un anno bisestile
L'esempio seguente alloca dinamicamente un array di strutture per memorizzare i nomi e i voti degli studenti. I voti vengono quindi visualizzati all'utente in ordine crescente.
#include <stringa.h>
#include <malloc.h>
studente strutturato
{
carattere *nome;
grado int;
};
void swap(struct studente *x, struct studente *y);
int principale()
{
struct studente *gruppo;
buffer di caratteri[80];
int spurio;
int interno, esterno;
int contatore, numStudenti;
printf("Quanti studenti ci sono nel gruppo: ");
scanf("%d", &numStudenti);
gruppo = (struct studente *)malloc(numStudenti *
sizeof(struct studente));
per (contatore=0; contatore<numStudenti; contatore++)
{
spurio = getchar();
printf("Inserisci il nome dello studente: ");
ottiene(buffer);
gruppo[contatore].nome = (char *)malloc((strlen(buffer)+1) * sizeof(char));
strcpy(gruppo[contatore].nome, buffer);
printf("Inserisci il voto: ");
scanf("%d", &group[counter].grade);
}
per (esterno=0; esterno<numStudenti; esterno++)
per (interno=0; interno<esterno; interno++)
se (gruppo[esterno].grado <
gruppo[interno].grado)
swap(&gruppo[esterno], &gruppo[interno]);
puts("Il gruppo in ordine crescente di voti ...");
per (contatore=0; contatore<numStudenti; contatore++)
printf("%s ha raggiunto il voto %d \n”,
gruppo[contatore].nome,
gruppo[contatore].grado);
restituisci 0;
}
void swap(struct studente *x, struct studente *y)
{
struttura studente temporaneo;
temp.name = (char *)malloc((strlen(x->name)+1) *
dimensione(carattere));
strcpy(temp.nome, x->nome);
temp.grado = x->grado;
x->grado = y->grado;
x->nome = (char *)malloc((strlen(y->nome)+1) *
dimensione(carattere));
strcpy(x->nome, y->nome);
y->grado = temp.grado;
y->nome = (char *)malloc((strlen(temp.nome)+1) *
dimensione(carattere));
strcpy(y->nome, temp.nome);
}
L'esecuzione dell'output sarà la seguente:
Quanti studenti ci sono nel gruppo: 4
Inserisci il nome dello studente: Anuraaj
Inserisci il voto: 7
Inserisci il nome dello studente: Honey
Inserisci il voto: 2
Inserisci il nome dello studente: Meetushi
Inserisci il voto: 1
Inserisci il nome dello studente: Deepti
Inserisci il voto: 4
Il gruppo in ordine crescente di voti ...
Meetushi ha ottenuto il grado 1
Honey ha ottenuto il grado 2
Deepti ha ottenuto il grado 4
Anuraaj ha ottenuto il grado 7
Unione
Un'unione consente di guardare gli stessi dati con tipi diversi o di usare gli stessi dati con nomi diversi. Le unioni sono simili alle strutture. Un'unione viene dichiarata e usata nello stesso modo di una struttura.
Un'unione differisce da una struttura in quanto solo uno dei suoi membri può essere utilizzato alla volta. Il motivo è semplice. Tutti i membri di un'unione occupano la stessa area di memoria. Sono disposti uno sopra l'altro.
Le unioni sono definite e dichiarate nello stesso modo delle strutture. L'unica differenza nelle dichiarazioni è che la parola chiave union è usata al posto di struct. Per definire una semplice unione di una variabile char e una variabile integer, dovresti scrivere quanto segue:
unione condivisa {
carattere c;
int io;
};
Questa unione, condivisa, può essere utilizzata per creare istanze di un'unione che può contenere un valore di carattere c o un valore intero i. Questa è una condizione OR. A differenza di una struttura che conterrebbe entrambi i valori, l'unione può contenere solo un valore alla volta.
Un'unione può essere inizializzata sulla sua dichiarazione. Poiché solo un membro può essere utilizzato alla volta e solo uno può essere inizializzato. Per evitare confusione, solo il primo membro dell'unione può essere inizializzato. Il seguente codice mostra un'istanza dell'unione condivisa che viene dichiarata e inizializzata:
unione condivisa generic_variable = {`@'};
Si noti che l'unione generic_variable è stata inizializzata esattamente come verrebbe inizializzato il primo membro di una struttura.
I singoli membri dell'unione possono essere utilizzati nello stesso modo in cui i membri della struttura possono essere utilizzati tramite l'operatore membro (.). Tuttavia, esiste un'importante differenza nell'accesso ai membri dell'unione.
Si dovrebbe accedere a un solo membro del sindacato alla volta. Poiché un sindacato archivia i suoi membri uno sopra l'altro, è importante accedere a un solo membro alla volta.
La parola chiave dell'unione
tag unione {
membro(i) dell'unione;
/* qui possono essere inserite ulteriori dichiarazioni */
}esempio;
La parola chiave union è usata per dichiarare unioni. Un'unione è una raccolta di una o più variabili (union_members) che sono state raggruppate sotto un singolo nome. Inoltre, ognuno di questi membri union occupa la stessa area di memoria.
La parola chiave union identifica l'inizio di una definizione di union. È seguita da un tag che è il nome dato all'union. Dopo il tag ci sono i membri dell'union racchiusi tra parentesi graffe.
Un'istanza, la dichiarazione effettiva di un'unione, può anche essere definita. Se si definisce la struttura senza l'istanza, è solo un modello che può essere utilizzato in seguito in un programma per dichiarare strutture. Di seguito è riportato il formato di un modello:
tag unione {
membro(i) dell'unione;
/* qui possono essere inserite ulteriori dichiarazioni */
};
Per utilizzare il modello, dovresti usare il seguente formato: istanza tag unione;
Per utilizzare questo formato, è necessario aver precedentemente dichiarato un'unione con il tag specificato.
/* Dichiara un modello di unione chiamato tag */
tag unione {
numero intero;
alpi salmastre;
}
/* Utilizza il modello di unione */
tag unione mixed_variable;
/* Dichiara un'unione e un'istanza insieme */
unione generic_type_tag {
carattere c;
int io;
galleggiante f;
doppia d;
} generico;
/* Inizializza un'unione. */
data_tag dell'unione {
carattere data_completa[9];
struttura part_date_tag {
carattere mese[2];
carattere break_value1;
giorno di char[2];
carattere break_value2;
anno char[2];
} data_parte;
}data = {"09/12/80"};
Cerchiamo di capirlo meglio con l'aiuto di alcuni esempi:
#include <stdio.h>
int principale()
{
unione
{
int valore; /* Questa è la prima parte dell'unione */
struttura
{
char first; /* Questi due valori sono la seconda parte */
carattere secondo;
} metà;
} numero;
indice lungo;
per (indice = 12 ; indice < 300000L ; indice += 35231L)
{
numero.valore = indice;
printf("%8x %6x %6x\n", numero.valore,
numero.metà.primo,
numero.mezzo.secondo);
}
restituisci 0;
}
E l'output del programma verrà visualizzato come segue:
copia 0
89ab favoloso ff89
134a 4a 13
9ce9 ffe9 ff9c
2688 ff88 26
b027 27 ffb0
39c6 ffc6 39
c365 65 ffc3
4g04 4 4g
Un uso pratico di un'unione nel recupero dei dati
Ora vediamo un uso pratico di union nella programmazione del recupero dati. Prendiamo un piccolo esempio. Il seguente programma è il piccolo modello di programma di scansione di settori danneggiati per un'unità floppy disk (a: ), tuttavia non è il modello completo del software di scansione di settori danneggiati.
Esaminiamo il programma:
#include<dos.h>
#include<conio.h>
int principale()
{
int rp, testa, traccia, settore, stato;
carattere *buf;
unione REGS dentro, fuori;
costruire SREGS s;
clrscr();
/* Reimposta il sistema del disco per inizializzarlo su disco */
printf("\n Reimpostazione del sistema del disco....");
per(rp=0;rp<=2;rp++)
{
in.h.ah = 0;
in.h.dl = 0x00;
int86(0x13,&dentro,&fuori);
}
printf("\n\n\n Ora testiamo il disco per settori danneggiati....");
/* scansione per settori danneggiati */
per(traccia=0;traccia<=79;traccia++)
{
per(testa=0;testa<=1;testa++)
{
per(settore=1;settore<=18;settore++)
{
in.h.ah = 0x04;
in.al. = 1;
in.h.dl = 0x00;
in.h.ch = traccia;
in.h.dh = testa;
in.h.cl = settore;
in.x.bx = FP_OFF(buf);
s.es = FP_SEG(buf);
int86x(0x13,&entrata,&uscita,&s);
se(out.x.cflag)
{
stato=out.h.ah;
printf("\n traccia:%d Testa:%d Settore:%d Stato ==0x%X",traccia,testa,settore,stato);
}
}
}
}
printf("\n\n\nFatto");
restituisci 0;
}
Vediamo ora come apparirà il suo output se ci sono settori danneggiati nel floppy disk:
Reimpostazione del sistema del disco....
Ora testiamo il disco per individuare settori danneggiati...
traccia:0 Testa:0 Settore:4 Stato ==0xA
traccia:0 Testa:0 Settore:5 Stato ==0xA
traccia:1 Testa:0 Settore:4 Stato ==0xA
traccia:1 Testa:0 Settore:5 Stato ==0xA
traccia:1 Testa:0 Settore:6 Stato ==0xA
traccia:1 Testa:0 Settore:7 Stato ==0xA
traccia:1 Testa:0 Settore:8 Stato ==0xA
traccia:1 Testa:0 Settore:11 Stato ==0xA
traccia:1 Testa:0 Settore:12 Stato ==0xA
traccia:1 Testa:0 Settore:13 Stato ==0xA
traccia:1 Testa:0 Settore:14 Stato ==0xA
traccia:1 Testa:0 Settore:15 Stato ==0xA
traccia:1 Testa:0 Settore:16 Stato ==0xA
traccia:1 Testa:0 Settore:17 Stato ==0xA
traccia:1 Testa:0 Settore:18 Stato ==0xA
traccia:1 Testa:1 Settore:5 Stato ==0xA
traccia:1 Testa:1 Settore:6 Stato ==0xA
traccia:1 Testa:1 Settore:7 Stato ==0xA
traccia:1 Testa:1 Settore:8 Stato ==0xA
traccia:1 Testa:1 Settore:9 Stato ==0xA
traccia:1 Testa:1 Settore:10 Stato ==0xA
traccia:1 Testa:1 Settore:11 Stato ==0xA
traccia:1 Testa:1 Settore:12 Stato ==0xA
traccia:1 Testa:1 Settore:13 Stato ==0xA
traccia:1 Testa:1 Settore:14 Stato ==0xA
traccia:1 Testa:1 Settore:15 Stato ==0xA
traccia:1 Testa:1 Settore:16 Stato ==0xA
traccia:1 Testa:1 Settore:17 Stato ==0xA
traccia:1 Testa:1 Settore:18 Stato ==0xA
traccia:2 Testa:0 Settore:4 Stato ==0xA
traccia:2 Testa:0 Settore:5 Stato ==0xA
traccia:14 Testa:0 Settore:6 Stato ==0xA
Fatto
Potrebbe essere un po' difficile comprendere le funzioni e gli interrupt utilizzati in questo programma per verificare la presenza di settori danneggiati sul disco e per reimpostare il sistema del disco, ecc., ma non preoccuparti, impareremo tutte queste cose nelle sezioni dedicate al BIOS e alla programmazione degli interrupt più avanti nei prossimi capitoli.
Gestione dei file in C
L'accesso ai file in C si ottiene associando uno stream a un file. C comunica con i file utilizzando un nuovo tipo di dati chiamato puntatore di file. Questo tipo è definito in stdio.h e scritto come FILE *. Un puntatore di file chiamato output_file è dichiarato in un'istruzione come
FILE *file_di_output;
Le modalità file della funzione fopen
Il tuo programma deve aprire un file prima di potervi accedere. Questo viene fatto usando la funzione fopen, che restituisce il puntatore del file richiesto. Se il file non può essere aperto per qualsiasi motivo, verrà restituito il valore NULL. Di solito userai fopen come segue
se ((file_output = fopen("file_output", "w")) == NULL)
fprintf(stderr, "Impossibile aprire %s\n",
"file di output");
fopen accetta due argomenti, entrambi stringhe: il primo è il nome del file da aprire, il secondo è un carattere di accesso, che solitamente è r, a o w ecc. I file possono essere aperti in diverse modalità, come mostrato nella tabella seguente.
Modalità file |
R |
Aprire un file di testo per la lettura. |
In |
Crea un file di testo per la scrittura. Se il file esiste, viene sovrascritto. |
UN |
Apre un file di testo in modalità append. Il testo viene aggiunto alla fine del file. |
r.b. |
Aprire un file binario per la lettura. |
bianco sporco |
Crea un file binario per la scrittura. Se il file esiste, viene sovrascritto. |
da |
Apre un file binario in modalità append. I dati vengono aggiunti alla fine del file. |
e+ |
Aprire un file di testo per la lettura e la scrittura. |
nel+ |
Crea un file di testo per la lettura e la scrittura. Se il file esiste, viene sovrascritto. |
un+ |
Alla fine, aprire un file di testo per la lettura e la scrittura. |
r+b o rb+ |
Apre il file binario per la lettura e la scrittura. |
w+b o wb+ |
Crea un file binario per la lettura e la scrittura. Se il file esiste, viene sovrascritto. |
a+b o ab+ |
Alla fine, aprire un file di testo per la lettura e la scrittura. |
Le modalità di aggiornamento sono utilizzate con le funzioni fseek, fsetpos e rewind. La funzione fopen restituisce un puntatore a file, o NULL se si verifica un errore.
L'esempio seguente apre un file, tarun.txt in modalità di sola lettura. È una buona pratica di programmazione testare l'esistenza del file.
se ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Impossibile aprire il file");
restituisci 0;
}
Chiusura dei file
I file vengono chiusi usando la funzione fclose. La sintassi è la seguente:
fclose(in);
Lettura dei file
La funzione feof è usata per testare la fine del file. Le funzioni fgetc, fscanf e fgets sono usate per leggere i dati dal file.
L'esempio seguente elenca il contenuto di un file sullo schermo, utilizzando fgetc per leggere il file un carattere alla volta.
#include <stdio.h>
int principale()
{
FILE *in;
chiave int;
se ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Impossibile aprire il file");
restituisci 0;
}
mentre (!feof(in))
{
chiave = fgetc(in);
/* L'ultimo carattere letto è il marcatore di fine file, quindi non stamparlo */
se (!feof(in))
putchar(chiave);
}
fclose(in);
restituisci 0;
}
La funzione fscanf può essere utilizzata per leggere diversi tipi di dati dal file, come nell'esempio seguente, a condizione che i dati nel file siano nel formato della stringa di formato utilizzata con fscanf.
fscanf(in, "%d/%d/%d", &giorno, &mese, &anno);
La funzione fgets viene utilizzata per leggere un certo numero di caratteri da un file. stdin è il flusso di file di input standard e la funzione fgets può essere utilizzata per controllare l'input.
Scrittura su file
I dati possono essere scritti nel file usando fputc e fprintf. L'esempio seguente usa le funzioni fgetc e fputc per fare una copia di un file di testo.
#include <stdio.h>
int principale()
{
FILE *dentro, *fuori;
chiave int;
se ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Impossibile aprire il file");
restituisci 0;
}
out = fopen("copia.txt", "w");
mentre (!feof(in))
{
chiave = fgetc(in);
se (!feof(in))
fputc(chiave, uscita);
}
fclose(in);
fclose(fuori);
restituisci 0;
}
La funzione fprintf può essere utilizzata per scrivere dati formattati in un file.
fprintf(out, "Data: %02d/%02d/%02d\n",
giorno, mese, anno);
Argomenti della riga di comando con C
La definizione ANSI C per dichiarare la funzione main() è:
int main() oppure int main(int argc, char **argv)
La seconda versione consente di passare argomenti dalla riga di comando. Il parametro argc è un contatore di argomenti e contiene il numero di parametri passati dalla riga di comando. Il parametro argv è il vettore di argomenti che è un array di puntatori a stringhe che rappresentano i parametri effettivi passati.
L'esempio seguente consente di passare un numero qualsiasi di argomenti dalla riga di comando e di stamparli. argv[0] è il programma effettivo. Il programma deve essere eseguito da un prompt dei comandi.
#include <stdio.h>
int main(int argomento, carattere **argomento)
{
int contatore;
puts("Gli argomenti del programma sono:");
per (contatore=0; contatore<argc; contatore++)
puts(argv[contatore]);
restituisci 0;
}
Se il nome del programma fosse count.c, potrebbe essere chiamato come segue dalla riga di comando.
conta 3
O
conta 7
O
conta 192 ecc.
Il prossimo esempio usa le routine di gestione file per copiare un file di testo in un nuovo file. Ad esempio, l'argomento della riga di comando potrebbe essere chiamato come:
txtcpy uno.txt due.txt
#include <stdio.h>
int main(int argomento, carattere **argomento)
{
FILE *dentro, *fuori;
chiave int;
se (argomento < 3)
{
puts("Utilizzo: txtcpy origine destinazione\n");
puts("La sorgente deve essere un file esistente");
puts("Se il file di destinazione esiste, verrà
sovrascritto");
restituisci 0;
}
se ((in = fopen(argv[1], "r")) == NULL)
{
puts("Impossibile aprire il file da copiare");
restituisci 0;
}
se ((out = fopen(argv[2], "w")) == NULL)
{
puts("Impossibile aprire il file di output");
restituisci 0;
}
mentre (!feof(in))
{
chiave = fgetc(in);
se (!feof(in))
fputc(chiave, uscita);
}
fclose(in);
fclose(fuori);
restituisci 0;
}
Manipolatori bit a bit
A livello hardware, i dati sono rappresentati come numeri binari. La rappresentazione binaria del numero 59 è 111011. Il bit 0 è il bit meno significativo e, in questo caso, il bit 5 è il bit più significativo.
Ogni set di bit viene calcolato come 2 alla potenza del set di bit. Gli operatori bit a bit consentono di manipolare variabili intere a livello di bit. Quanto segue mostra la rappresentazione binaria del numero 59.
rappresentazione binaria del numero 59 |
pezzo 5 4 3 2 1 0 |
2 potenza n 32 16 8 4 2 1 |
impostare 1 1 1 0 1 1 |
Con tre bit è possibile rappresentare i numeri da 0 a 7. La seguente tabella mostra i numeri da 0 a 7 nella loro forma binaria.
Cifre binarie |
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
Nella tabella seguente sono elencati gli operatori bit a bit che possono essere utilizzati per manipolare numeri binari.
Cifre binarie |
e |
AND bit a bit |
| |
OR bit a bit |
^ |
OR esclusivo bit a bit |
~ |
Complemento bit a bit |
<< |
Spostamento bit a bit a sinistra |
>> |
Spostamento bit a bit a destra |
AND bit a bit
L'AND bit a bit è True solo se entrambi i bit sono impostati. L'esempio seguente mostra il risultato di un AND bit a bit sui numeri 23 e 12.
10111 (23)
01100 (12) E
Italiano: ____________________
00100 (risultato = 4) |
È possibile utilizzare un valore maschera per verificare se determinati bit sono stati impostati. Se volessimo verificare se i bit 1 e 3 sono stati impostati, potremmo mascherare il numero con 10 (il valore dei bit 1 e 3) e testare il risultato rispetto alla maschera.
#include <stdio.h>
int principale()
{
int num, maschera = 10;
printf("Inserisci un numero: ");
{NS} = "%d";
se ((num & maschera) == maschera)
puts("I bit 1 e 3 sono impostati");
altro
puts("I bit 1 e 3 non sono impostati");
restituisci 0;
}
OR bit a bit
L'OR bit a bit è vero se uno dei due bit è impostato. Di seguito viene mostrato il risultato di un OR bit a bit sui numeri 23 e 12.
10111 (23)
01100 (12) OPPURE
Italiano: ______________________
11111 (risultato = 31) |
È possibile utilizzare una maschera per garantire che uno o più bit siano stati impostati. Il seguente esempio garantisce che il bit 2 sia impostato.
#include <stdio.h>
int principale()
{
int num, maschera = 4;
printf("Inserisci un numero: ");
{NS} = "%d";
num |= maschera;
printf("Dopo essersi assicurati che il bit 2 sia impostato: %d\n", num);
restituisci 0;
}
OR esclusivo bit a bit
L'OR esclusivo bit a bit è True se uno dei due bit è impostato, ma non entrambi. Quanto segue mostra il risultato di un OR esclusivo bit a bit sui numeri 23 e 12.
10111 (23)
01100 (12) OR esclusivo (XOR)
Italiano: _____________________________
11011 (risultato = 27) |
L'OR esclusivo ha alcune proprietà interessanti. Se si esegue l'OR esclusivo di un numero da solo, si imposta a zero poiché gli zeri rimarranno zero e gli uni non possono essere entrambi impostati, quindi sono impostati a zero.
Di conseguenza, se si esegue l'operazione Exclusive OR di un numero con un altro numero, quindi Exclusive OR del risultato con l'altro numero di nuovo, il risultato è il numero originale. È possibile provare con i numeri utilizzati nell'esempio precedente.
23 O 12 = 27
27 O 12 = 23
27 O 23 = 12
Questa funzionalità può essere utilizzata per la crittografia. Il programma seguente utilizza una chiave di crittografia di 23 per illustrare la proprietà di un numero immesso dall'utente.
#include <stdio.h>
int principale()
{
int num, chiave = 23;
printf("Inserisci un numero: ");
{NS} = "%d";
tasto num ^=;
printf("OR esclusivo con %d restituisce %d\n", chiave, num);
tasto num ^=;
printf("OR esclusivo con %d restituisce %d\n", chiave, num);
restituisci 0;
}
Complimento bit a bit
Il complemento bitwise è un operatore di complemento a uno che attiva o disattiva il bit. Se è 1, verrà impostato su 0, se è 0 verrà impostato su 1.
#include <stdio.h>
int principale()
{
numero intero = 0xFFFF;
printf("Il complemento di %X è %X\n", num, ~num);
restituisci 0;
}
Spostamento bit a bit a sinistra
L'operatore Bitwise Shift Left sposta il numero a sinistra. I bit più significativi vengono persi quando il numero si sposta a sinistra e i bit meno significativi lasciati liberi sono zero. Di seguito è mostrata la rappresentazione binaria di 43.
0101011 (decimale 43)
Spostando i bit a sinistra, perdiamo il bit più significativo (in questo caso, uno zero) e il numero viene riempito con uno zero nel bit meno significativo. Il seguente è il numero risultante.
1010110 (decimale 86)
Spostamento bit a bit a destra
L'operatore Bitwise Shift Right sposta il numero a destra. Lo zero viene introdotto nei bit più significativi lasciati liberi, e i bit meno significativi lasciati liberi vengono persi. Quanto segue mostra la rappresentazione binaria del numero 43.
0101011 (decimale 43)
Spostando i bit verso destra, perdiamo il bit meno significativo (in questo caso, un uno) e il numero viene riempito con uno zero nel bit più significativo. Il seguente è il numero risultante.
0010101 (decimale 21)
Il seguente programma usa Bitwise Shift Right e Bitwise AND per visualizzare un numero come numero binario a 16 bit. Il numero viene spostato a destra in successione da 16 a zero e sottoposto a Bitwise AND con 1 per vedere se il bit è impostato. Un metodo alternativo sarebbe quello di usare maschere successive con l'operatore Bitwise OR.
#include <stdio.h>
int principale()
{
int contatore, num;
printf("Inserisci un numero: ");
{NS} = "%d";
printf("%d è binario: ", num);
per (contatore=15; contatore>=0; contatore--)
printf("%d", (num >> contatore) & 1);
{NS} = "Puntare il dito sulla tastiera";
restituisci 0;
}
Funzioni per conversioni binario-decimali
Le due funzioni fornite di seguito sono per la conversione da binario a decimale e da decimale a binario. La funzione fornita di seguito per convertire un numero decimale nel corrispondente numero binario supporta fino a 32 bit di numeri binari. È possibile utilizzare questa o il programma fornito in precedenza per la conversione in base alle proprie esigenze.
Funzione per la conversione da decimale a binario:
void Decimal_to_Binary(void)
{
int ingresso =0;
int io;
int conteggio = 0;
int binario [32]; /* 32 Bit, MASSIMO 32 elementi */
printf ("Inserisci il numero decimale da convertire in
Binario :");
scanf ("%d", &input);
Fare
{
i = input%2; /* MOD 2 per ottenere 1 o 0*/
binary[count] = i; /* Carica gli elementi nell'array binario */
input = input/2; /* Dividi input per 2 per decrementare tramite binario */
count++; /* Conta quanti elementi sono necessari*/
}mentre (input > 0);
/* Inverti e restituisci cifre binarie */
printf ("La rappresentazione binaria è: ");
Fare
{
printf ("%d", binario[conteggio - 1]);
contare--;
} while (conteggio > 0);
stampa("\n");
}
Funzione per la conversione da binario a decimale:
La seguente funzione serve a convertire qualsiasi numero binario nel corrispondente numero decimale:
void Binario_in_Decimale(void)
{
carattere binariohold[512];
char *binario;
int i=0;
int dec = 0;
int z;
printf ("Inserisci le cifre binarie.\n");
printf ("Le cifre binarie sono solo 0 o 1 ");
printf ("Entrata binaria: ");
binario = gets(binaryhold);
i=strlen(binario);
per (z=0; z<i; ++z)
{
dec=dec*2+(binary[z]=='1'? 1:0); /* se Binary[z] è
uguale a 1,
allora 1 altrimenti 0 */
}
stampa("\n");
printf ("Il valore decimale di %s è %d",
binario, dec);
stampa("\n");
}
Debug e test
Errori di sintassi
La sintassi si riferisce alla grammatica, alla struttura e all'ordine degli elementi in un'istruzione. Un errore di sintassi si verifica quando si infrangono le regole, ad esempio dimenticando di terminare un'istruzione con un punto e virgola. Quando compili il programma, il compilatore produrrà un elenco di tutti gli errori di sintassi che potrebbe incontrare.
Un buon compilatore produrrà l'elenco con una descrizione dell'errore e potrebbe fornire una possibile soluzione. La correzione degli errori potrebbe causare la visualizzazione di ulteriori errori durante la ricompilazione. Il motivo è che gli errori precedenti hanno modificato la struttura del programma, il che significa che ulteriori errori sono stati soppressi durante la compilazione originale.
Allo stesso modo, un singolo errore può causare diversi errori. Prova a mettere un punto e virgola alla fine della funzione principale di un programma che compila ed esegue correttamente. Quando lo ricompili, otterrai un'enorme lista di errori, e tuttavia è solo un punto e virgola fuori posto.
Oltre agli errori di sintassi, i compilatori possono anche emettere avvisi. Un avviso non è un errore, ma può causare problemi durante l'esecuzione del programma. Ad esempio, assegnare un numero in virgola mobile a doppia precisione a un numero in virgola mobile a precisione singola può causare una perdita di precisione. Non è un errore di sintassi, ma potrebbe causare problemi. In questo particolare esempio, potresti mostrare l'intento eseguendo il cast della variabile nel tipo di dati appropriato.
Si consideri il seguente esempio in cui x è un numero in virgola mobile a precisione singola e y è un numero in virgola mobile a precisione doppia. y viene esplicitamente convertito in float durante l'assegnazione, il che eliminerebbe qualsiasi avviso del compilatore.
x = (virgola mobile)y;
Errori logici
Gli errori logici si verificano quando c'è un errore nella logica. Ad esempio, potresti testare che un numero sia minore di 4 e maggiore di 8. Ciò non potrebbe mai essere vero, ma se è sintatticamente corretto il programma verrà compilato correttamente. Considera il seguente esempio:
se (x < 4 && x > 8)
puts("Non accadrà mai!");
La sintassi è corretta, quindi il programma verrà compilato, ma l'istruzione puts non verrà mai stampata poiché il valore di x non potrebbe essere inferiore a quattro e maggiore di otto allo stesso tempo.
La maggior parte degli errori logici vengono scoperti tramite il test iniziale del programma. Quando non si comporta come previsto, si esaminano più attentamente le istruzioni logiche e le si corregge. Questo vale solo per gli errori logici evidenti. Più grande è il programma, più percorsi ci saranno al suo interno, più difficile diventa verificare che il programma si comporti come previsto.
Prova
Nel processo di sviluppo del software, gli errori possono essere iniettati in qualsiasi fase durante lo sviluppo. Questo perché i metodi di verifica delle fasi precedenti di sviluppo del software sono manuali. Quindi il codice sviluppato durante l'attività di codifica è probabile che abbia alcuni errori di requisito ed errori di progettazione, oltre agli errori introdotti durante l'attività di codifica. Durante il test, il programma da testare viene eseguito con un set di casi di test e l'output del programma per i casi di test viene valutato per determinare se la programmazione sta eseguendo come previsto.
Quindi, il testing è il processo di analisi di un elemento software per rilevare la differenza tra le condizioni esistenti e quelle richieste (ad esempio, bug) e per valutare le funzionalità degli elementi software. Quindi, il testing è il processo di analisi di un programma con l'intento di trovare errori.
Alcuni principi di test
- I test non possono dimostrare l'assenza di difetti, ma solo la loro presenza.
- Quanto prima si commette un errore, tanto più costoso sarà.
- Quanto più tardi viene rilevato un errore, tanto più costoso sarà.
Ora parliamo di alcune tecniche di test:
Test della scatola bianca
Il white box testing è una tecnica in cui tutti i percorsi attraverso il programma vengono testati con ogni valore possibile. Questo approccio richiede una certa conoscenza di come dovrebbe comportarsi il programma. Ad esempio, se il tuo programma accetta un valore intero tra 1 e 50, un white box testing testerebbe il programma con tutti i 50 valori per assicurarsi che sia corretto per ciascuno, quindi testerebbe ogni altro valore possibile che un intero può assumere e verificherebbe che si comporti come previsto. Considerando il numero di elementi di dati che un programma tipico può avere, le possibili permutazioni rendono il white box testing estremamente difficile per programmi di grandi dimensioni.
Il test white box può essere applicato a funzioni critiche per la sicurezza di un programma di grandi dimensioni, e gran parte del resto è testato utilizzando il test black box, discusso di seguito. A causa del numero di permutazioni, il test white box viene solitamente eseguito utilizzando un test harness, in cui intervalli di valori vengono immessi rapidamente nel programma tramite un programma speciale, registrando le eccezioni al comportamento previsto. Il test white box è talvolta definito test structural, clear o open box.
Test della scatola nera
Il test della scatola nera è simile al test della scatola bianca, tranne per il fatto che anziché testare ogni valore possibile, vengono testati valori selezionati. In questo tipo di test, il tester conosce gli input e quali dovrebbero essere i risultati attesi, ma non necessariamente come il programma vi è arrivato. Il test della scatola nera è talvolta definito test funzionale.
I casi di test per i test della scatola nera vengono solitamente sviluppati subito dopo il completamento delle specifiche del programma. I casi di test si basano sulle classi di equivalenza.
Classi di equivalenza
Per ogni input, una classe di equivalenza definisce gli stati validi e non validi. Quando si definiscono le classi di equivalenza, in genere è necessario pianificare tre scenari.
Se i dati di input specificano un intervallo o un valore specifico, verranno definiti uno stato valido e due stati non validi. Ad esempio, se un numero deve essere compreso tra 1 e 20, lo stato valido sarà compreso tra 1 e 20, ci sarà uno stato non valido per i valori inferiori a 1 e uno stato non valido per i valori superiori a 20.
Se i dati di input escludono un intervallo o un valore specifico, verranno definiti due stati validi e uno stato non valido. Ad esempio, se un numero non deve essere compreso tra 1 e 20, gli stati validi saranno minori di 1 e maggiori di 20, mentre gli stati non validi saranno compresi tra 1 e 20.
Se l'input specifica un valore booleano, ci saranno solo due stati: uno valido e uno non valido.
Analisi del valore limite
L'analisi dei valori al contorno considera solo i valori al confine dei dati di input. Ad esempio, nel caso di un numero da 1 a 20, i casi di test potrebbero essere 1, 20, 0 e 21. L'idea è che se il programma funziona come previsto con questi valori, anche altri valori funzioneranno come previsto.
La tabella seguente fornisce una panoramica dei confini tipici che potresti voler definire.
Intervalli di prova |
Tipo di input |
Valori di prova |
Allineare |
- x[limite_inferiore]-1
- x[limite_inferiore]
- x[limite_superiore]
- x[limite_superiore]+1
|
Booleano |
|
Sviluppo di un piano di test
Definire le classi di equivalenza e determinare i limiti per ciascuna classe. Dopo aver definito i limiti della classe, scrivi un elenco dei valori accettabili e inaccettabili al limite e quale dovrebbe essere il comportamento previsto. Il tester può quindi eseguire il programma con i valori limite e indicare cosa è successo quando il valore limite è stato testato rispetto al risultato richiesto.
Di seguito è riportato un tipico piano di test utilizzato per convalidare le età di input, in cui i valori accettabili vanno da 10 a 110.
Classe di equivalenza |
Valido |
Non corretto |
Tra 10 e 110 |
> 110 |
|
< 10 |
Dopo aver determinato la nostra classe di equivalenza, possiamo ora sviluppare un piano di test per età.
Piano di prova |
Valore |
Stato |
Risultato atteso |
Risultato effettivo |
10 |
Valido |
Continua l'esecuzione per ottenere il nome |
|
110 |
Valido |
Continua l'esecuzione per ottenere il nome |
|
9 |
Non corretto |
Chiedi di nuovo l'età |
|
111 |
Non corretto |
Chiedi di nuovo l'età |
|
La colonna "Risultato effettivo" viene lasciata vuota poiché verrà compilata durante il test. Se il risultato è quello previsto, la colonna verrà controllata. In caso contrario, dovresti inserire un commento che indichi cosa è successo.