C++Aan de slag met C ++


Opmerkingen

Het programma 'Hallo wereld' is een veelvoorkomend voorbeeld dat eenvoudig kan worden gebruikt om de aanwezigheid van de compiler en de bibliotheek te controleren. Het gebruikt de C ++ standaardbibliotheek, met std::cout van <iostream> , en heeft slechts één bestand om te compileren, waardoor de kans op mogelijke gebruikersfouten tijdens het compileren wordt geminimaliseerd.


Het proces voor het compileren van een C ++ -programma verschilt inherent tussen compilers en besturingssystemen. Het onderwerp Compileren en bouwen bevat de details over het compileren van C ++ -code op verschillende platforms voor verschillende compilers.

versies

Versie Standaard Publicatiedatum
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

Hallo Wereld

Dit programma drukt Hello World! naar de standaarduitvoerstroom:

#include <iostream>

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

Zie het live op Coliru .

Analyse

Laten we elk deel van deze code in detail bekijken:

  • #include <iostream> is een preprocessorrichtlijn die de inhoud van het standaard C ++ headerbestand iostream .

    iostream is een standaardbibliotheekkopbestand dat definities van de standaard invoer- en uitvoerstromen bevat. Deze definities zijn opgenomen in de std naamruimte, die hieronder wordt uitgelegd.

    De standaard I / O-streams (input / output) bieden manieren waarop programma's invoer van en uitvoer naar een extern systeem kunnen krijgen - meestal de terminal.

  • int main() { ... } definieert een nieuwe functie met de naam main . Volgens afspraak wordt de main opgeroepen bij de uitvoering van het programma. Er moet enige zijn main functie in een C ++ programma, en moet altijd een aantal keren int type.

    Hier, de int is wat de functie genaamd return type . De waarde die door de main functie is een exit-code.

    Volgens afspraak wordt een programma- EXIT_SUCCESS van 0 of EXIT_SUCCESS als succesvol geïnterpreteerd door een systeem dat het programma uitvoert. Elke andere retourcode is gekoppeld aan een fout.

    Als er geen return statement aanwezig is, de main functie (en dus, het programma zelf) geeft 0 standaard. In dit voorbeeld hoeven we niet expliciet return 0; schrijven return 0; .

    Alle andere functies, behalve functies die het void type retourneren, moeten expliciet een waarde retourneren volgens hun retourtype, of anders helemaal niet.

  • std::cout << "Hello World!" << std::endl; print "Hallo wereld!" naar de standaarduitvoerstroom:

    • std is een naamruimte en :: is de operator voor bereikresolutie waarmee objecten op naam binnen een naamruimte kunnen worden opgezocht.

      Er zijn veel naamruimten. Hier gebruiken we :: om aan te geven dat we cout uit de std naamruimte willen gebruiken. Raadpleeg Scope Resolution Operator - Microsoft-documentatie voor meer informatie.

    • std::cout is het standaarduitvoerstroomobject , gedefinieerd in iostream , en het print naar de standaarduitvoer ( stdout ).

    • << is in deze context de operator voor het invoegen van streams , zo genoemd omdat deze een object in het streamobject invoegt .

      De standaardbibliotheek definieert de operator << om gegevensinvoer uit te voeren voor bepaalde gegevenstypen in uitvoerstromen. stream << content voegt content aan de stream en retourneert dezelfde, maar bijgewerkte stream. Hiermee kunnen streaminvoegingen worden gekoppeld: std::cout << "Foo" << " Bar"; drukt "FooBar" af naar de console.

    • "Hello World!" is een letterlijke tekenreeks of een "letterlijke tekst". De operator voor het invoegen van streams voor iostream tekenreeksen wordt gedefinieerd in het bestand iostream .

    • std::endl is een speciaal I / O- iostream , ook gedefinieerd in bestand iostream . Het invoegen van een manipulator in een stream verandert de status van de stream.

      De std::endl doet twee dingen: eerst wordt het einde-van-regel-teken ingevoegd en vervolgens wordt de std::endl om de tekst op de console te laten verschijnen. Dit zorgt ervoor dat de gegevens die in de stream zijn ingevoegd, daadwerkelijk op uw console verschijnen. (Stream-gegevens worden meestal opgeslagen in een buffer en vervolgens in batches "gespoeld", tenzij u onmiddellijk een spoeling forceert.)

      Een alternatieve methode die het doorspoelen voorkomt is:

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

      waarbij \n de escape-reeks voor tekens voor het teken voor de nieuwe regel is.

    • De puntkomma ( ; ) geeft de compiler aan dat een instructie is beëindigd. Alle C ++ -instructies en klassedefinities vereisen een afsluitende / afsluitende puntkomma.

Comments

Een opmerking is een manier om willekeurige tekst in de broncode te plaatsen zonder dat de C ++ compiler deze met enige functionele betekenis interpreteert. Opmerkingen worden gebruikt om inzicht te geven in het ontwerp of de methode van een programma.

Er zijn twee soorten opmerkingen in C ++:

Enkele regel opmerkingen

De dubbele voorwaartse schuine streep // markeert alle tekst tot een nieuwe regel als een opmerking:

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
}
 

C-Style / Block-opmerkingen

De reeks /* wordt gebruikt om het begin van het commentaarblok aan te geven en de reeks */ wordt gebruikt om het einde van de reactie aan te geven. Alle tekst tussen de begin- en eindreeks wordt geïnterpreteerd als een opmerking, zelfs als de tekst anders een geldige C ++ -syntaxis is. Dit worden soms "C-stijl" opmerkingen genoemd, omdat deze syntaxis van de commentaren is overgenomen van C ++ 's voorgangerstaal, C:

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

In elke blokcommentaar kun je alles schrijven wat je wilt. Wanneer de compiler het symbool */ tegenkomt, beëindigt het de blokopmerking:

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;
}
 

Het bovenstaande voorbeeld is een geldige C ++ (en C) code. Het hebben van extra /* in een blokcommentaar kan echter leiden tot een waarschuwing voor sommige compilers.

Blokreacties kunnen ook binnen een enkele regel beginnen en eindigen. Bijvoorbeeld:

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

Belang van opmerkingen

Zoals bij alle programmeertalen, bieden opmerkingen verschillende voordelen:

  • Expliciete documentatie van code om het lezen / onderhouden gemakkelijker te maken
  • Uitleg van het doel en de functionaliteit van code
  • Details over de geschiedenis of redenering achter de code
  • Plaatsing van auteursrechten / licenties, projectnotities, speciale dank, contributors, enz. Rechtstreeks in de broncode.

Reacties hebben echter ook nadelen:

  • Ze moeten worden onderhouden om eventuele wijzigingen in de code weer te geven
  • Overmatige opmerkingen maken de code meestal minder leesbaar

De behoefte aan opmerkingen kan worden verminderd door duidelijke, zelfdocumenterende code te schrijven. Een eenvoudig voorbeeld is het gebruik van verklarende namen voor variabelen, functies en typen. Logeren van logisch gerelateerde taken in discrete functies gaat hierbij hand in hand.

Opmerkingenmarkeringen die worden gebruikt om code uit te schakelen

Tijdens de ontwikkeling kunnen opmerkingen ook worden gebruikt om delen van code snel uit te schakelen zonder deze te verwijderen. Dit is vaak handig voor test- of foutopsporingsdoeleinden, maar is geen goede stijl voor iets anders dan tijdelijke bewerkingen. Dit wordt vaak "commentaar geven" genoemd.

Op dezelfde manier wordt het negeren van oude versies van een stuk code in een opmerking als referentiedoel, omdat het bestanden rommelig maakt terwijl het weinig waarde biedt in vergelijking met het verkennen van de geschiedenis van de code via een versiesysteem.

Functie

Een functie is een code-eenheid die een reeks uitspraken weergeeft.

Functies kunnen argumenten of waarden accepteren en een enkele waarde retourneren (of niet). Als u een functie wilt gebruiken, wordt een functieaanroep gebruikt voor argumentwaarden en wordt het gebruik van de functieaanroep zelf vervangen door de retourwaarde.

Elke functie heeft een typeaanduiding - de typen argumenten en het type retourtype.

Functies worden geïnspireerd door de concepten van de procedure en de wiskundige functie.

  • Opmerking: C ++ -functies zijn in wezen procedures en volgen niet de exacte definitie of regels van wiskundige functies.

Functies zijn vaak bedoeld om een specifieke taak uit te voeren. en kan worden opgeroepen vanuit andere delen van een programma. Een functie moet worden gedeclareerd en gedefinieerd voordat deze elders in een programma wordt aangeroepen.

  • Opmerking: populaire functiedefinities kunnen verborgen zijn in andere opgenomen bestanden (vaak voor het gemak en hergebruik in veel bestanden). Dit is een veelgebruikt gebruik van header-bestanden.

Functie verklaring

Een functieverklaring verklaart het bestaan van een functie met zijn naam en typeaanduiding aan de compiler. De syntaxis is als volgt:

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

In het bovenstaande voorbeeld verklaart de functie int add2(int i) het volgende aan de compiler:

  • Het retourtype is int .
  • De naam van de functie is add2 .
  • Het aantal argumenten voor de functie is 1:
    • Het eerste argument is van het type int .
    • Het eerste argument zal in de inhoud van de functie worden aangeduid met de naam i .

De naam van het argument is optioneel; de verklaring voor de functie kan ook de volgende zijn:

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

Volgens de regel met één definitie kan een functie met een bepaalde typeaanduiding slechts eenmaal worden gedeclareerd of gedefinieerd in een volledige C ++ codebasis die zichtbaar is voor de C ++ compiler. Met andere woorden, functies met een specifieke typeaanduiding kunnen niet opnieuw worden gedefinieerd - ze moeten slechts eenmaal worden gedefinieerd. Het volgende is dus geen geldige 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.
 

Als een functie niets retourneert, wordt het retourtype als void geschreven. Als er geen parameters nodig zijn, moet de parameterlijst leeg zijn.

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

Functie-oproep

Een functie kan worden opgeroepen nadat deze is gedeclareerd. Het volgende programma roept bijvoorbeeld add2 met de waarde 2 binnen de functie 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;  
}
 

Hier is add2(2) de syntaxis voor een functieaanroep.

Functie definitie

Een functiedefinitie * is vergelijkbaar met een verklaring, behalve dat deze ook de code bevat die wordt uitgevoerd wanneer de functie in zijn hoofdgedeelte wordt aangeroepen.

Een voorbeeld van een functiedefinitie voor add2 kan zijn:

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.
}
 

Functie overbelasting

U kunt meerdere functies maken met dezelfde naam maar verschillende parameters.

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.
}
 

Beide functies worden met dezelfde naam add2 , maar de werkelijke functie die wordt aangeroepen, hangt rechtstreeks af van de hoeveelheid en het type parameters in de aanroep. In de meeste gevallen kan de C ++ compiler berekenen welke functie moet worden aangeroepen. In sommige gevallen moet het type expliciet worden vermeld.

Standaard parameters

Standaardwaarden voor functieparameters kunnen alleen worden opgegeven in functieverklaringen.

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 dit voorbeeld kan multiply() worden opgeroepen met een of twee parameters. Als slechts één parameter wordt opgegeven, heeft b standaardwaarde 7. De standaardargumenten moeten in de laatste argumenten van de functie worden geplaatst. Bijvoorbeeld:

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
 

Speciale functie-oproepen - Operators

Er zijn speciale functie-aanroepen in C ++ die een andere syntaxis hebben dan name_of_function(value1, value2, value3) . Het meest voorkomende voorbeeld is dat van operators.

Bepaalde speciale tekenreeksen die door de compiler worden gereduceerd tot functieaanroepen, zoals ! , + , - , * , % en << en nog veel meer. Deze speciale tekens worden normaal gesproken geassocieerd met niet-programmeergebruik of worden gebruikt voor esthetiek (het + -teken wordt bijvoorbeeld algemeen herkend als het toevoegingssymbool zowel binnen C ++ programmeren als in elementaire wiskunde).

C ++ verwerkt deze tekenreeksen met een speciale syntaxis; maar in wezen wordt elk optreden van een operator gereduceerd tot een functieaanroep. Bijvoorbeeld de volgende C ++ -uitdrukking:

3+3
 

is gelijk aan de volgende functieaanroep:

operator+(3, 3)
 

Alle operatorfunctienamen beginnen met operator .

Hoewel in C ++ 's onmiddellijke voorganger, C, aan operatorfunctienamen geen verschillende betekenissen kunnen worden toegewezen door aanvullende definities met verschillende typeaanduidingen te geven, is dit in C ++ geldig. Het "verbergen" van aanvullende functiedefinities onder één unieke functienaam wordt operatoroverbelasting genoemd in C ++ en is een relatief veel voorkomende, maar geen universele conventie in C ++.

preprocessor

De preprocessor is een belangrijk onderdeel van de compiler.

Het bewerkt de broncode, verwijdert enkele bits, verandert andere en voegt andere dingen toe.

In bronbestanden kunnen we preprocessorrichtlijnen opnemen. Deze richtlijnen vertellen de preprocessor om specifieke acties uit te voeren. Een richtlijn begint met een # op een nieuwe regel. Voorbeeld:

#define ZERO 0
 

De eerste preprocessorrichtlijn die u tegenkomt, is waarschijnlijk de

#include <something>
 

richtlijn. Wat het doet, is alles something en in uw bestand invoegen waar de richtlijn was. Het hallo wereldprogramma begint met de regel

#include <iostream>
 

Deze regel voegt de functies en objecten toe waarmee u de standaard invoer en uitvoer kunt gebruiken.

De C-taal, die ook de preprocessor gebruikt, heeft niet zoveel header-bestanden als de C ++ taal, maar in C ++ kunt u alle C-header-bestanden gebruiken.


De volgende belangrijke richtlijn is waarschijnlijk de

#define something something_else
 

richtlijn. Dit vertelt de preprocessor dat het tijdens het doorgaan van het bestand elk voorval van something zou moeten vervangen door something_else . Het kan ook dingen doen lijken op functies, maar dat geldt waarschijnlijk als geavanceerde C ++.

De something_else is niet nodig, maar als je something als niets definieert, dan buiten preprocessorrichtlijnen, zullen alle gebeurtenissen van something verdwijnen.

Dit is eigenlijk handig, vanwege de #if , #else en #ifdef . Het formaat hiervoor zou het volgende zijn:

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

#ifdef thing_that_you_want_to_know_if_is_defined
//code
#endif
 

Deze richtlijnen voegen de code in die zich in het ware bit bevindt en verwijdert de valse bits. dit kan worden gebruikt om stukjes code te hebben die alleen op bepaalde besturingssystemen zijn opgenomen, zonder dat de hele code hoeft te worden herschreven.

Het standaard C ++ compilatieproces

Uitvoerbare C ++ programmacode wordt meestal geproduceerd door een compiler.

Een compiler is een programma dat code van een programmeertaal vertaalt naar een andere vorm die (meer) direct uitvoerbaar is voor een computer. Het gebruik van een compiler om code te vertalen wordt compilatie genoemd.

C ++ neemt de vorm van zijn compilatieproces over van de "hoofd" taal, C. Hieronder is een lijst met de vier belangrijkste compilatiestappen in C ++:

  1. De C ++ preprocessor kopieert de inhoud van eventuele opgenomen headerbestanden naar het broncodebestand, genereert macrocode en vervangt symbolische constanten die zijn gedefinieerd met #define door hun waarden.
  2. Het uitgebreide broncodebestand geproduceerd door de C ++ preprocessor is gecompileerd in de montagetaal die geschikt is voor het platform.
  3. De assemblercode gegenereerd door de compiler wordt geassembleerd in een geschikte objectcode voor het platform.
  4. Het objectcodebestand dat door de assembler is gegenereerd, is gekoppeld aan de objectcodebestanden voor alle bibliotheekfuncties die worden gebruikt om een uitvoerbaar bestand te produceren.
  • Opmerking: sommige gecompileerde code is aan elkaar gekoppeld, maar niet om een definitief programma te maken. Gewoonlijk kan deze "gekoppelde" code ook worden verpakt in een indeling die door andere programma's kan worden gebruikt. Deze "bundel van verpakte, bruikbare code" is wat C ++ programmeurs een bibliotheek noemen .

Veel C ++ -compilers kunnen ook bepaalde delen van het compilatieproces samenvoegen of desgewenst voor extra gemak of voor extra analyse. Veel C ++ programmeurs zullen verschillende tools gebruiken, maar alle tools zullen over het algemeen dit algemene proces volgen wanneer ze betrokken zijn bij de productie van een programma.

De onderstaande link breidt deze discussie uit en biedt een mooie afbeelding om te helpen. [1]: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

Zichtbaarheid van functieprototypes en verklaringen

In C ++ moet code vóór gebruik worden gedeclareerd of gedefinieerd. Het volgende levert bijvoorbeeld een compilatietijdfout op:

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
{
}
 

Er zijn twee manieren om dit op te lossen: de definitie of verklaring van foo() vóór het gebruik ervan in main() . Hier is een voorbeeld:

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.
}
 

Het is echter ook mogelijk om de functie "vooruit te declareren" door alleen een "prototype" -verklaring voor gebruik te plaatsen en vervolgens de functielichaam later te definiëren:

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
}
 

Het prototype moet het retourtype ( void ), de naam van de functie ( foo ) en de variabeletypes van de argumentlijst ( int ) opgeven, maar de namen van de argumenten zijn NIET vereist .

Een veel voorkomende manier om dit te integreren in de organisatie van bronbestanden is om een header-bestand te maken met alle prototypeverklaringen:

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

en geef vervolgens de volledige definitie elders:

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

en vervolgens, na het compileren, het overeenkomstige objectbestand foo.o aan het gecompileerde objectbestand waar het wordt gebruikt in de koppelingsfase, 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
 

Een “onopgeloste externe symbool” fout treedt op wanneer de functieprototype en call bestaan, maar de functie lichaam niet gedefinieerd. Deze kunnen moeilijker zijn om op te lossen, omdat de compiler de fout pas in de laatste koppelingsfase rapporteert en hij niet weet naar welke regel hij in de code moet springen om de fout weer te geven.