C++Iniziare con C ++

Osservazioni

Il programma "Hello World" è un esempio comune che può essere semplicemente utilizzato per verificare la presenza di compilatori e librerie. Usa la libreria standard C ++, con std::cout da <iostream> , e ha un solo file da compilare, riducendo al minimo la possibilità di un possibile errore dell'utente durante la compilazione.


Il processo per la compilazione di un programma C ++ differisce intrinsecamente tra compilatori e sistemi operativi. L'argomento Compilazione e costruzione contiene i dettagli su come compilare codice C ++ su piattaforme diverse per una varietà di compilatori.

Versioni

Versione Standard Data di rilascio
C ++ 98 ISO / IEC 14882: 1998 1998/09/01
C ++ 03 ISO / IEC 14882: 2003 2003/10/16
C ++ 11 ISO / IEC 14882: 2011 2011-09-01
C ++ 14 ISO / IEC 14882: 2014 2014/12/15
C ++ 17 TBD 2017/01/01
C ++ 20 TBD 2020/01/01

Ciao mondo

Questo programma stampa Hello World! al flusso di output standard:

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
}
 

Guardalo dal vivo su Coliru .

Analisi

Esaminiamo in dettaglio ciascuna parte di questo codice:

  • #include <iostream> è una direttiva per il preprocessore che include il contenuto del file di intestazione standard di C ++ iostream .

    iostream è un file di intestazione di libreria standard che contiene le definizioni degli stream di input e output standard. Queste definizioni sono incluse nello spazio dei nomi std , spiegato di seguito.

    Gli stream standard di input / output (I / O) forniscono ai programmi modalità per ottenere input e output su un sistema esterno, in genere il terminale.

  • int main() { ... } definisce una nuova funzione denominata main . Per convenzione, la funzione main viene chiamata all'esecuzione del programma. Ci deve essere una sola funzione main in un programma C ++ e deve sempre restituire un numero del tipo int .

    Qui, l' int è quello che viene chiamato il tipo di ritorno della funzione. Il valore restituito dalla funzione main è un codice di uscita.

    Per convenzione, un codice di uscita del programma pari a 0 o EXIT_SUCCESS viene interpretato come esito positivo da un sistema che esegue il programma. Qualsiasi altro codice di ritorno è associato a un errore.

    Se non è presente alcuna dichiarazione di return , la funzione main (e quindi il programma stesso) restituisce 0 per impostazione predefinita. In questo esempio, non è necessario scrivere esplicitamente return 0; .

    Tutte le altre funzioni, ad eccezione di quelle che restituiscono il tipo void , devono restituire esplicitamente un valore in base al tipo restituito, altrimenti non devono restituire del tutto.

  • std::cout << "Hello World!" << std::endl; stampa "Hello World!" al flusso di output standard:

    • std è uno spazio dei nomi e :: è l' operatore di risoluzione dell'ambito che consente di cercare oggetti per nome all'interno di uno spazio dei nomi.

      Ci sono molti spazi dei nomi. Qui, usiamo :: per mostrare che vogliamo usare cout dallo spazio dei nomi std . Per ulteriori informazioni, consultare Operatore risoluzione ambito - Documentazione Microsoft .

    • std::cout è l'oggetto standard del flusso di output , definito in iostream , e stampa sullo standard output ( stdout ).

    • << è, in questo contesto , l' operatore di inserimento del flusso , così chiamato perché inserisce un oggetto nell'oggetto del flusso .

      La libreria standard definisce l'operatore << per eseguire l'inserimento dei dati per determinati tipi di dati in flussi di output. stream << content inserisce il content nello stream e restituisce lo stesso flusso ma aggiornato. In questo modo è possibile concatenare inserimenti di stream: std::cout << "Foo" << " Bar"; stampa "FooBar" sulla console.

    • "Hello World!" è un letterale stringa di caratteri o un "testo letterale". L'operatore di inserimento del flusso per i valori letterali delle stringhe di caratteri è definito nel file iostream .

    • std::endl è uno speciale oggetto manipolatore di flusso I / O , anch'esso definito nel file iostream . L'inserimento di un manipolatore in un flusso cambia lo stato del flusso.

      Il manipolatore di flusso std::endl fa due cose: prima inserisce il carattere di fine riga e poi scarica il buffer del flusso per forzare la visualizzazione del testo sulla console. Ciò garantisce che i dati inseriti nello stream vengano effettivamente visualizzati sulla tua console. (I dati di flusso vengono solitamente memorizzati in un buffer e quindi "svuotati" in batch, a meno che non si imponga immediatamente un lavaggio.)

      Un metodo alternativo che evita il flush è:

      std::cout << "Hello World!\n";
       

      dove \n è la sequenza di escape dei caratteri per il carattere di nuova riga.

    • Il punto e virgola ( ; ) notifica al compilatore che un'istruzione è terminata. Tutte le istruzioni C ++ e le definizioni di classe richiedono un punto e virgola di fine / fine.

Commenti

Un commento è un modo per inserire del testo arbitrario all'interno del codice sorgente senza che il compilatore C ++ lo interpreti con alcun significato funzionale. I commenti sono usati per dare un'idea del design o del metodo di un programma.

Esistono due tipi di commenti in C ++:

Commenti a riga singola

La sequenza double forward-slash // contrassegnerà tutto il testo fino a una nuova riga come commento:

int main()
{
   // This is a single-line comment.
   int a;  // this also is a single-line comment
   int i;  // this is another single-line comment
}
 

Commenti tipo C / stile

La sequenza /* viene utilizzata per dichiarare l'inizio del blocco di commenti e la sequenza */ viene utilizzata per dichiarare la fine del commento. Tutto il testo tra le sequenze di inizio e fine è interpretato come un commento, anche se il testo è altrimenti valido sintassi C ++. Questi sono a volte chiamati commenti in "stile C", poiché questa sintassi dei commenti è ereditata dal linguaggio predecessore di C ++, C:

int main()
{
   /*
    *  This is a block comment.
    */
   int a;
}
 

In qualsiasi commento di blocco, puoi scrivere tutto ciò che vuoi. Quando il compilatore incontra il simbolo */ , termina il commento di blocco:

int main()
{
   /* A block comment with the symbol /*
      Note that the compiler is not affected by the second /*
      however, once the end-block-comment symbol is reached,
      the comment ends.
   */
   int a;
}
 

L'esempio precedente è un codice C ++ (e C) valido. Tuttavia, l'aggiunta di /* all'interno di un commento di blocco potrebbe comportare un avviso su alcuni compilatori.

I commenti bloccati possono anche iniziare e terminare all'interno di una singola riga. Per esempio:

void SomeFunction(/* argument 1 */ int a, /* argument 2 */ int b);
 

Importanza dei commenti

Come con tutti i linguaggi di programmazione, i commenti offrono numerosi vantaggi:

  • Documentazione esplicita del codice per semplificare la lettura / manutenzione
  • Spiegazione dello scopo e della funzionalità del codice
  • Dettagli sulla storia o il ragionamento dietro il codice
  • Inserimento di diritti d'autore / licenze, note di progetto, ringraziamenti speciali, crediti contributori, ecc. Direttamente nel codice sorgente.

Tuttavia, i commenti hanno anche i loro lati negativi:

  • Devono essere mantenuti per riflettere eventuali modifiche nel codice
  • I commenti eccessivi tendono a rendere il codice meno leggibile

La necessità di commenti può essere ridotta scrivendo un codice chiaro e autodocumentante. Un semplice esempio è l'uso di nomi esplicativi per variabili, funzioni e tipi. Factoring attività logicamente correlati in funzioni discrete va di pari passo con questo.

Marcatori di commento utilizzati per disabilitare il codice

Durante lo sviluppo, i commenti possono essere utilizzati anche per disabilitare rapidamente porzioni di codice senza eliminarlo. Questo è spesso utile per scopi di test o di debug, ma non è buono per qualcosa di diverso dalle modifiche temporanee. Questo è spesso definito come "commento".

Allo stesso modo, mantenere le vecchie versioni di un pezzo di codice in un commento a scopo di riferimento è disapprovato, poiché ingombra file pur offrendo poco valore rispetto all'esplorazione della cronologia del codice tramite un sistema di controllo delle versioni.

Funzione

Una funzione è un'unità di codice che rappresenta una sequenza di istruzioni.

Le funzioni possono accettare argomenti o valori e restituire un singolo valore (o non). Per utilizzare una funzione, una chiamata di funzione viene utilizzata su valori di argomento e l'uso della chiamata di funzione stessa viene sostituito con il suo valore di ritorno.

Ogni funzione ha una firma di tipo - i tipi dei suoi argomenti e il tipo del suo tipo di ritorno.

Le funzioni sono ispirate ai concetti della procedura e della funzione matematica.

  • Nota: le funzioni C ++ sono essenzialmente procedure e non seguono la definizione esatta o le regole delle funzioni matematiche.

Le funzioni sono spesso pensate per svolgere un compito specifico. e può essere chiamato da altre parti di un programma. Una funzione deve essere dichiarata e definita prima di essere chiamata altrove in un programma.

  • Nota: le definizioni di funzioni popolari possono essere nascoste in altri file inclusi (spesso per comodità e riutilizzo su molti file). Questo è un uso comune dei file di intestazione.

Dichiarazione delle funzioni

Una dichiarazione di funzione dichiara l'esistenza di una funzione con il suo nome e la firma del tipo nel compilatore. La sintassi è la seguente:

int add2(int i); // The function is of the type (int) -> (int)
 

Nell'esempio sopra, la funzione int add2(int i) dichiara quanto segue al compilatore:

  • Il tipo di ritorno è int .
  • Il nome della funzione è add2 .
  • Il numero di argomenti per la funzione è 1:
    • Il primo argomento è del tipo int .
    • Il primo argomento verrà indicato nel contenuto della funzione con il nome i .

Il nome dell'argomento è facoltativo; la dichiarazione per la funzione potrebbe anche essere la seguente:

int add2(int); // Omitting the function arguments' name is also permitted.
 

Per la regola a una definizione , una funzione con una determinata firma di tipo può essere dichiarata o definita solo una volta in un intero codice C ++ visibile al compilatore C ++. In altre parole, le funzioni con una firma di tipo specifica non possono essere ridefinite: devono essere definite una sola volta. Quindi, il seguente non è valido C ++:

int add2(int i);  // The compiler will note that add2 is a function (int) -> int
int add2(int j);  // As add2 already has a definition of (int) -> int, the compiler
                  // will regard this as an error.
 

Se una funzione non restituisce nulla, il suo tipo di ritorno è scritto come void . Se non ci sono parametri, l'elenco dei parametri dovrebbe essere vuoto.

void do_something(); // The function takes no parameters, and does not return anything.
                     // Note that it can still affect variables it has access to.
 

Chiamata di funzione

Una funzione può essere chiamata dopo che è stata dichiarata. Ad esempio, il seguente programma chiama add2 con il valore di 2 all'interno della funzione di main :

#include <iostream>

int add2(int i);    // Declaration of add2

// Note: add2 is still missing a DEFINITION.
// Even though it doesn't appear directly in code,
// add2's definition may be LINKED in from another object file.

int main()
{
    std::cout << add2(2) << "\n";  // add2(2) will be evaluated at this point,
                                   // and the result is printed.
    return 0;  
}
 

Qui, add2(2) è la sintassi per una chiamata di funzione.

Definizione della funzione

Una definizione di funzione * è simile a una dichiarazione, tranne che contiene anche il codice che viene eseguito quando la funzione viene chiamata all'interno del suo corpo.

Un esempio di una definizione di funzione per add2 potrebbe essere:

int add2(int i)       // Data that is passed into (int i) will be referred to by the name i
{                     // while in the function's curly brackets or "scope."
                    
    int j = i + 2;    // Definition of a variable j as the value of i+2.
    return j;         // Returning or, in essence, substitution of j for a function call to
                      // add2.
}
 

Funzione di sovraccarico

È possibile creare più funzioni con lo stesso nome ma diversi parametri.

int add2(int i)           // Code contained in this definition will be evaluated
{                         // when add2() is called with one parameter.
    int j = i + 2;
    return j;
}

int add2(int i, int j)    // However, when add2() is called with two parameters, the
{                         // code from the initial declaration will be overloaded,
    int k = i + j + 2 ;   // and the code in this declaration will be evaluated
    return k;             // instead.
}
 

Entrambe le funzioni sono chiamate con lo stesso nome add2 , ma la funzione effettiva chiamata dipende direttamente dalla quantità e dal tipo dei parametri nella chiamata. Nella maggior parte dei casi, il compilatore C ++ può calcolare quale funzione chiamare. In alcuni casi, il tipo deve essere dichiarato esplicitamente.

Parametri predefiniti

I valori predefiniti per i parametri di funzione possono essere specificati solo nelle dichiarazioni di funzione.

int multiply(int a, int b = 7); // b has default value of 7.
int multiply(int a, int b)
{
    return a * b;               // If multiply() is called with one parameter, the
}                               // value will be multiplied by the default, 7.
 

In questo esempio, multiply() può essere chiamato con uno o due parametri. Se viene fornito un solo parametro, b avrà il valore predefinito di 7. Gli argomenti predefiniti devono essere inseriti negli ultimi argomenti della funzione. Per esempio:

int multiply(int a = 10, int b = 20); // This is legal 
int multiply(int a = 10, int b);      // This is illegal since int a is in the former
 

Chiamate con funzioni speciali - Operatori

Esistono chiamate di funzioni speciali in C ++ che hanno una sintassi diversa da name_of_function(value1, value2, value3) . L'esempio più comune è quello degli operatori.

Alcune sequenze di caratteri speciali che verranno ridotte a funzioni chiamate dal compilatore, come ad esempio ! , + , - , * , % e << e molti altri. Questi caratteri speciali sono normalmente associati all'uso non di programmazione o sono usati per l'estetica (ad esempio il carattere + è comunemente riconosciuto come simbolo di aggiunta sia nella programmazione C ++ che in matematica elementare).

C ++ gestisce queste sequenze di caratteri con una sintassi speciale; ma, in sostanza, ogni occorrenza di un operatore è ridotta a una chiamata di funzione. Ad esempio, la seguente espressione C ++:

3+3
 

è equivalente alla seguente chiamata di funzione:

operator+(3, 3)
 

Tutti i nomi delle funzioni dell'operatore iniziano con l' operator .

Mentre nell'immediato predecessore di C ++, C, i nomi delle funzioni dell'operatore non possono essere assegnati a significati diversi fornendo definizioni aggiuntive con diversi tipi di firma, in C ++, questo è valido. "Nascondere" le definizioni di funzioni aggiuntive sotto un unico nome di funzione viene definito overloading dell'operatore in C ++ ed è una convenzione relativamente comune, ma non universale, in C ++.

preprocessore

Il preprocessore è una parte importante del compilatore.

Modifica il codice sorgente, tagliando alcuni bit, modificandone altri e aggiungendo altre cose.

Nei file di origine, possiamo includere le direttive del preprocessore. Queste direttive indicano al preprocessore di eseguire azioni specifiche. Una direttiva inizia con un # su una nuova riga. Esempio:

#define ZERO 0
 

La prima direttiva per il preprocessore che incontrerai è probabilmente la

#include <something>
 

direttiva. Quello che fa è prendere tutto di something e inserirlo nel tuo file dove era la direttiva. Il programma Hello World inizia con la linea

#include <iostream>
 

Questa riga aggiunge le funzioni e gli oggetti che consentono di utilizzare lo standard input e output.

Il linguaggio C, che usa anche il preprocessore, non ha tanti file header come il linguaggio C ++, ma in C ++ puoi usare tutti i file header C.


La prossima importante direttiva è probabilmente la

#define something something_else
 

direttiva. Questo dice al preprocessore che mentre va lungo il file, dovrebbe sostituire ogni occorrenza di something con something_else . Può anche fare cose simili alle funzioni, ma probabilmente conta come C ++ avanzato.

Il something_else non è necessario, ma se si definisce something come nulla, quindi al di fuori delle direttive del preprocessore, tutte le occorrenze di something svaniranno.

Questo in realtà è utile, a causa delle direttive #if , #else e #ifdef . Il formato per questi sarebbe il seguente:

#if something==true
//code
#else
//more code
#endif

#ifdef thing_that_you_want_to_know_if_is_defined
//code
#endif
 

Queste direttive inseriscono il codice che si trova nel bit true e cancella i bit falsi. questo può essere usato per avere bit di codice che sono inclusi solo su determinati sistemi operativi, senza dover riscrivere l'intero codice.

Il processo di compilazione C ++ standard

Il codice del programma C ++ eseguibile viene solitamente prodotto da un compilatore.

Un compilatore è un programma che traduce il codice da un linguaggio di programmazione in un'altra forma che è (più) direttamente eseguibile per un computer. L'uso di un compilatore per tradurre il codice è chiamato compilazione.

C ++ eredita la forma del suo processo di compilazione dal suo linguaggio "genitore", C. Di seguito è riportato un elenco che mostra i quattro passaggi principali della compilazione in C ++:

  1. Il preprocessore C ++ copia i contenuti di qualsiasi file di intestazione incluso nel file del codice sorgente, genera codice macro e sostituisce le costanti simboliche definite usando #define con i loro valori.
  2. Il file di codice sorgente espanso prodotto dal preprocessore C ++ è compilato in linguaggio assembly appropriato per la piattaforma.
  3. Il codice assembler generato dal compilatore viene assemblato nel codice oggetto appropriato per la piattaforma.
  4. Il file di codice oggetto generato dall'assemblatore è collegato insieme ai file di codice oggetto per tutte le funzioni di libreria utilizzate per produrre un file eseguibile.
  • Nota: alcuni codici compilati sono collegati tra loro, ma non per creare un programma finale. Di solito, questo codice "linkato" può anche essere impacchettato in un formato che può essere utilizzato da altri programmi. Questo "pacchetto di codice utilizzabile e confezionato" è ciò che i programmatori C ++ chiamano libreria.

Molti compilatori C ++ possono anche unire o disunire alcune parti del processo di compilazione per facilità o per ulteriori analisi. Molti programmatori C ++ useranno strumenti diversi, ma tutti gli strumenti seguiranno generalmente questo processo generalizzato quando saranno coinvolti nella produzione di un programma.

Il link qui sotto estende questa discussione e fornisce una bella grafica per aiutare. [1]: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

Visibilità di prototipi e dichiarazioni di funzioni

In C ++, il codice deve essere dichiarato o definito prima dell'uso. Ad esempio, il seguente produce un errore in fase di compilazione:

int main()
{
  foo(2); // error: foo is called, but has not yet been declared
}

void foo(int x) // this later definition is not known in main
{
}
 

Ci sono due modi per risolvere questo problema: inserire la definizione o la dichiarazione di foo() prima del suo utilizzo in main() . Ecco un esempio:

void foo(int x) {}  //Declare the foo function and body first

int main()
{
  foo(2); // OK: foo is completely defined beforehand, so it can be called here.
}
 

Tuttavia è anche possibile "inoltrare-dichiarare" la funzione mettendo solo una dichiarazione "prototipo" prima del suo utilizzo e quindi definendo successivamente il corpo della funzione:

void foo(int);  // Prototype declaration of foo, seen by main
                // Must specify return type, name, and argument list types
int main()
{
  foo(2); // OK: foo is known, called even though its body is not yet defined
}

void foo(int x) //Must match the prototype
{
    // Define body of foo here
}
 

Il prototipo deve specificare il tipo di ritorno ( void ), il nome della funzione ( foo ) e il tipo di variabile list dell'argomento ( int ), ma i nomi degli argomenti NON sono richiesti .

Un modo comune per integrare questo nell'organizzazione dei file sorgente è creare un file di intestazione contenente tutte le dichiarazioni del prototipo:

// foo.h
void foo(int); // prototype declaration
 

e quindi fornire la definizione completa altrove:

// foo.cpp --> foo.o
#include "foo.h" // foo's prototype declaration is "hidden" in here
void foo(int x) { } // foo's body definition
 

e quindi, una volta compilato, collega il file oggetto corrispondente foo.o nel file oggetto compilato dove viene utilizzato nella fase di collegamento, main.o :

// main.cpp --> main.o
#include "foo.h" // foo's prototype declaration is "hidden" in here
int main() { foo(2); } // foo is valid to call because its prototype declaration was beforehand.
// the prototype and body definitions of foo are linked through the object files
 

Un errore "simbolo esterno non risolto" si verifica quando la funzione prototipo e chiamata esistono, ma il corpo della funzione non è definito. Questi possono essere più difficili da risolvere in quanto il compilatore non segnalerà l'errore fino alla fase di collegamento finale e non sa a quale riga saltare nel codice per mostrare l'errore.