C++Pierwsze kroki w C ++


Uwagi

Program „Hello World” jest częstym przykładem, którego można po prostu użyć do sprawdzenia obecności kompilatora i biblioteki. Korzysta ze standardowej biblioteki C ++ ze std::cout z <iostream> i ma tylko jeden plik do skompilowania, minimalizując ryzyko błędu użytkownika podczas kompilacji.


Proces kompilacji programu C ++ z natury różni się między kompilatorami a systemami operacyjnymi. Temat Kompilowanie i budowanie zawiera szczegółowe informacje na temat kompilowania kodu C ++ na różnych platformach dla różnych kompilatorów.

Wersje

Wersja Standard Data wydania
C ++ 98 ISO / IEC 14882: 1998 1998-09-01
C ++ 03 ISO / IEC 14882: 2003 2003-10-16
C ++ 11 ISO / IEC 14882: 2011 01.09.2011
C ++ 14 ISO / IEC 14882: 2014 15.12.2014
C ++ 17 TBD 01.01.2017
C ++ 20 TBD 2020-01-01

Witaj świecie

Ten program drukuje Hello World! do standardowego strumienia wyjściowego:

#include <iostream>

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

Zobacz na żywo na Coliru .

Analiza

Przyjrzyjmy się szczegółowo każdej części tego kodu:

  • #include <iostream> to dyrektywa preprocesorowa, która zawiera zawartość standardowego pliku nagłówkowego C ++ iostream .

    iostream to standardowy plik nagłówkowy biblioteki, który zawiera definicje standardowych strumieni wejściowych i wyjściowych. Te definicje są zawarte w przestrzeni nazw std , wyjaśnionej poniżej.

    Standardowe strumienie wejścia / wyjścia (I / O) zapewniają programom możliwość pobierania i wysyłania danych do zewnętrznego systemu - zazwyczaj terminala.

  • int main() { ... } definiuje nową funkcję o nazwie main . Zgodnie z konwencją main funkcja jest wywoływana podczas wykonywania programu. W programie C ++ musi być tylko jedna main funkcja i zawsze musi ona zwracać liczbę typu int .

    Tutaj int to tak zwany typ zwracany przez funkcję. Wartość zwracana przez funkcję main to kod wyjścia.

    Zgodnie z konwencją, kod wyjścia programu 0 lub EXIT_SUCCESS jest interpretowany jako sukces przez system, który wykonuje program. Każdy inny kod powrotu jest powiązany z błędem.

    Jeśli nie ma instrukcji return , funkcja main (a zatem sam program) domyślnie zwraca 0 . W tym przykładzie nie musimy jawnie pisać return 0; .

    Wszystkie inne funkcje, z wyjątkiem tych, które zwracają typ void , muszą jawnie zwracać wartość zgodnie ze swoim typem zwracanym, w przeciwnym razie nie mogą w ogóle zwracać.

  • std::cout << "Hello World!" << std::endl; drukuje „Hello World!” do standardowego strumienia wyjściowego:

    • std jest przestrzenią nazw , a :: jest operatorem rozpoznawania zakresu, który umożliwia wyszukiwanie obiektów według nazwy w przestrzeni nazw.

      Istnieje wiele przestrzeni nazw. Używamy :: aby pokazać, że chcemy używać cout ze std przestrzeni nazw. Aby uzyskać więcej informacji, zobacz Scope Resolution Operator - Microsoft Documentation .

    • std::cout jest standardowym obiektem strumienia wyjściowego , zdefiniowanym w iostream , i wypisuje na standardowe wyjście ( stdout ).

    • << jest w tym kontekście operatorem wstawiania strumienia , tzw. ponieważ wstawia obiekt do obiektu strumienia .

      Standardowa biblioteka definiuje operator << do wykonywania wstawiania danych dla niektórych typów danych do strumieni wyjściowych. stream << content wstawia content do strumienia i zwraca ten sam, ale zaktualizowany strumień. Umożliwia to łańcuchowe wstawianie strumieni: std::cout << "Foo" << " Bar"; wypisuje „FooBar” na konsoli.

    • "Hello World!" to dosłowny ciąg znaków lub „dosłowny tekst”. Operator wstawiania strumienia dla literałów ciągów znaków jest zdefiniowany w pliku iostream .

    • std::endl to specjalny obiekt manipulatora strumienia we / wy , również zdefiniowany w pliku iostream . Wstawienie manipulatora do strumienia zmienia stan strumienia.

      Manipulator strumienia std::endl robi dwie rzeczy: najpierw wstawia znak końca linii, a następnie opróżnia bufor strumienia, aby wymusić wyświetlanie tekstu na konsoli. Dzięki temu dane wstawione do strumienia faktycznie pojawią się na konsoli. (Dane strumienia są zwykle przechowywane w buforze, a następnie „opróżniane” partiami, chyba że natychmiast wymusisz opróżnianie.)

      Alternatywną metodą, która pozwala uniknąć spłukiwania, jest:

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

      gdzie \n jest sekwencją zmiany znaku dla znaku nowej linii.

    • Średnik ( ; ) informuje kompilator o zakończeniu instrukcji. Wszystkie instrukcje i definicje klas C ++ wymagają średnika kończącego / kończącego.

Komentarze

Komentarz jest sposobem na umieszczenie dowolnego tekstu w kodzie źródłowym bez konieczności interpretowania go przez kompilator C ++ o dowolnym znaczeniu funkcjonalnym. Komentarze służą do wglądu w projekt lub metodę programu.

Istnieją dwa rodzaje komentarzy w C ++:

Komentarze jednowierszowe

Podwójna sekwencja ukośnika // spowoduje zaznaczenie całego tekstu do nowego wiersza jako komentarza:

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
}
 

Komentarze w stylu C / Block

Sekwencja /* służy do deklarowania początku bloku komentarza, a sekwencja */ służy do deklarowania końca komentarza. Cały tekst między sekwencją początkową i końcową jest interpretowany jako komentarz, nawet jeśli w innym przypadku tekst jest poprawną składnią C ++. Są one czasami nazywane komentarzami w stylu „C”, ponieważ ta składnia komentarzy jest dziedziczona z języka poprzednika C ++, C:

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

W dowolnym komentarzu blokowym możesz napisać, co chcesz. Gdy kompilator napotka symbol */ , kończy komentarz do bloku:

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

Powyższy przykład jest poprawnym kodem C ++ (i C). Jednak dodanie dodatkowego /* w komentarzu do bloku może spowodować ostrzeżenie w niektórych kompilatorach.

Blokowane komentarze mogą również zaczynać się i kończyć w jednym wierszu. Na przykład:

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

Znaczenie komentarzy

Podobnie jak w przypadku wszystkich języków programowania, komentarze zapewniają kilka korzyści:

  • Jawna dokumentacja kodu, aby ułatwić czytanie / utrzymanie
  • Wyjaśnienie celu i funkcjonalności kodu
  • Szczegóły dotyczące historii lub uzasadnienia kodu
  • Umieszczenie praw autorskich / licencji, notatek projektowych, specjalnych podziękowań, kredytów współpracowników itp. Bezpośrednio w kodzie źródłowym.

Jednak komentarze mają również swoje wady:

  • Należy je zachować, aby odzwierciedlały wszelkie zmiany w kodzie
  • Nadmierne komentarze powodują, że kod jest mniej czytelny

Zapotrzebowanie na komentarze można zmniejszyć, pisząc przejrzysty, samodokumentujący się kod. Prostym przykładem jest użycie objaśniających nazw zmiennych, funkcji i typów. Dzielenie logicznie powiązanych zadań na funkcje dyskretne idzie w parze z tym.

Znaczniki komentarzy używane do wyłączania kodu

Podczas programowania komentarze można również wykorzystać do szybkiego wyłączenia części kodu bez usuwania go. Jest to często przydatne do testowania lub debugowania, ale nie jest dobrym stylem do niczego poza tymczasowymi edycjami. Jest to często określane jako „komentowanie”.

Podobnie, trzymanie starych wersji fragmentu kodu w komentarzu w celach referencyjnych jest niezadowolone, ponieważ zaśmieca pliki, oferując niewielką wartość w porównaniu do eksploracji historii kodu za pomocą systemu wersjonowania.

Funkcjonować

Funkcja to jednostka kodu reprezentująca sekwencję instrukcji.

Funkcje mogą akceptować argumenty lub wartości i zwracać pojedynczą wartość (lub nie). Aby użyć funkcji, wywołanie funkcji jest używane dla wartości argumentów, a użycie samego wywołania funkcji jest zastępowane wartością zwracaną.

Każda funkcja ma podpis typu - typy jej argumentów i typ zwracanego typu.

Funkcje są inspirowane pojęciami procedury i funkcją matematyczną.

  • Uwaga: Funkcje C ++ są zasadniczo procedurami i nie są zgodne z dokładną definicją lub regułami funkcji matematycznych.

Funkcje często służą do wykonywania określonego zadania. i mogą być wywoływane z innych części programu. Funkcja musi zostać zadeklarowana i zdefiniowana, zanim zostanie wywołana w innym miejscu programu.

  • Uwaga: popularne definicje funkcji mogą być ukryte w innych dołączonych plikach (często dla wygody i wielokrotnego użytku w wielu plikach). Jest to powszechne użycie plików nagłówkowych.

Deklaracja funkcji

Deklaracja funkcji deklaruje istnienie funkcji z jej nazwą i podpisem typu w kompilatorze. Składnia jest następująca:

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

W powyższym przykładzie funkcja int add2(int i) deklaruje w kompilatorze następujące informacje:

  • Typ zwracany jest int .
  • Nazwa funkcji to add2 .
  • Liczba argumentów funkcji wynosi 1:
    • Pierwszy argument jest typu int .
    • Pierwszy argument będzie oznaczony w treści funkcji nazwą i .

Nazwa argumentu jest opcjonalna; deklaracja dla funkcji może być również następująca:

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

Zgodnie z regułą z jedną definicją funkcja z określonym typem podpisu może być zadeklarowana lub zdefiniowana tylko raz w całej bazie kodu C ++ widocznej dla kompilatora C ++. Innymi słowy, funkcje z określonym typem podpisu nie mogą być ponownie zdefiniowane - muszą być zdefiniowane tylko raz. Dlatego następujące informacje nie są poprawne w 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.
 

Jeśli funkcja nic nie zwraca, jej typ zwracany jest zapisywany jako void . Jeśli nie przyjmuje parametrów, lista parametrów powinna być pusta.

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

Wywołanie funkcji

Funkcja może zostać wywołana po jej zadeklarowaniu. Na przykład następujące wywołania programu add2 o wartości 2 w funkcji 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;  
}
 

Tutaj add2(2) to składnia wywołania funkcji.

Definicja funkcji

Definicja funkcji * jest podobna do deklaracji, z tym że zawiera również kod wykonywany, gdy funkcja jest wywoływana w jej ciele.

Przykładem definicji funkcji dla add2 może być:

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

Przeciążenie funkcji

Możesz utworzyć wiele funkcji o tej samej nazwie, ale o różnych parametrach.

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

Obie funkcje są wywoływane pod tą samą nazwą add2 , ale rzeczywista funkcja, która jest wywoływana, zależy bezpośrednio od ilości i rodzaju parametrów w wywołaniu. W większości przypadków kompilator C ++ może obliczyć, którą funkcję wywołać. W niektórych przypadkach typ musi być wyraźnie określony.

Parametry domyślne

Domyślne wartości parametrów funkcji można określić tylko w deklaracjach funkcji.

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.
 

W tym przykładzie multiply() można wywołać z jednym lub dwoma parametrami. Jeśli podany jest tylko jeden parametr, b będzie mieć domyślną wartość 7. Domyślne argumenty muszą być umieszczone w ostatnich argumentach funkcji. Na przykład:

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
 

Wywołania funkcji specjalnych - operatorzy

W C ++ istnieją specjalne wywołania funkcji, które mają inną składnię niż name_of_function(value1, value2, value3) . Najczęstszym przykładem są operatory.

Niektóre sekwencje znaków specjalnych, które zostaną zredukowane do wywołań funkcji przez kompilator, takie jak ! , + , - , * , % i << i wiele innych. Te znaki specjalne są zwykle kojarzone z użyciem nieprogramowym lub są używane do estetyki (np. Znak + jest powszechnie rozpoznawany jako symbol dodawania zarówno w programowaniu w C ++, jak i w matematyce elementarnej).

C ++ obsługuje te sekwencje znaków ze specjalną składnią; ale w zasadzie każde wystąpienie operatora sprowadza się do wywołania funkcji. Na przykład następujące wyrażenie C ++:

3+3
 

odpowiada następującemu wywołaniu funkcji:

operator+(3, 3)
 

Wszystkie nazwy funkcji operatora zaczynają się od operator .

Podczas gdy w bezpośrednim poprzedniku C ++, C, nazwom funkcji operatora nie można przypisywać różnych znaczeń poprzez zapewnienie dodatkowych definicji z różnymi podpisami typów, w C ++ jest to poprawne. „Ukrywanie” dodatkowych definicji funkcji pod jedną unikalną nazwą funkcji jest nazywane przeciążeniem operatora w C ++ i jest stosunkowo powszechną, ale nie uniwersalną konwencją w C ++.

Preprocesor

Preprocesor jest ważną częścią kompilatora.

Edytuje kod źródłowy, wycinając niektóre bity, zmieniając inne i dodając inne rzeczy.

W plikach źródłowych możemy uwzględnić dyrektywy preprocesora. Te dyrektywy nakazują preprocesorowi wykonanie określonych działań. Dyrektywa zaczyna się od # w nowej linii. Przykład:

#define ZERO 0
 

Pierwsza dyrektywa preprocesorowa, którą spotkasz, to prawdopodobnie

#include <something>
 

dyrektywa. Co robi to bierze wszystko o something i wstawia go w pliku, w którym dyrektywa została. Program hello world zaczyna się od linii

#include <iostream>
 

Ta linia dodaje funkcje i obiekty, które pozwalają na korzystanie ze standardowego wejścia i wyjścia.

Język C, który również korzysta z preprocesora, nie ma tylu plików nagłówkowych, co język C ++, ale w C ++ można używać wszystkich plików nagłówka C.


Następna ważna dyrektywa to prawdopodobnie

#define something something_else
 

dyrektywa. Mówi to preprocesorowi, że idąc wzdłuż pliku, powinien zamieniać każde wystąpienie something something_else . Może także sprawiać, że rzeczy będą podobne do funkcji, ale prawdopodobnie liczy się to jako zaawansowany C ++.

something_else nie jest potrzebne, ale jeśli zdefiniujesz something jako nic, wówczas poza dyrektywami preprocesora wszystkie wystąpienia something znikną.

To rzeczywiście jest przydatna, ponieważ z #if , #else i #ifdef dyrektyw. Ich format byłby następujący:

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

#ifdef thing_that_you_want_to_know_if_is_defined
//code
#endif
 

Te dyrektywy wstawiają kod w prawdziwym bicie i usuwają fałszywe bity. może to być użyte do uzyskania bitów kodu, które są zawarte tylko w niektórych systemach operacyjnych, bez konieczności przepisywania całego kodu.

Standardowy proces kompilacji w C ++

Wykonalny kod programu C ++ jest zwykle wytwarzany przez kompilator.

Kompilator to program, który tłumaczy kod z języka programowania na inną formę, która jest (więcej) bezpośrednio wykonywalna dla komputera. Używanie kompilatora do tłumaczenia kodu nazywa się kompilacją.

C ++ dziedziczy formę procesu kompilacji po „macierzystym” języku C. Poniżej znajduje się lista pokazująca cztery główne etapy kompilacji w C ++:

  1. Preprocesor C ++ kopiuje zawartość dołączonych plików nagłówkowych do pliku kodu źródłowego, generuje kod makra i zastępuje stałe symboliczne zdefiniowane za pomocą #define ich wartościami.
  2. Rozszerzony plik kodu źródłowego utworzony przez preprocesor C ++ jest kompilowany do języka asemblera odpowiedniego dla platformy.
  3. Kod asemblera generowany przez kompilator jest składany w odpowiedni kod obiektowy dla platformy.
  4. Plik kodu obiektowego wygenerowany przez asembler jest połączony z plikami kodu obiektowego dla dowolnych funkcji bibliotecznych używanych do tworzenia pliku wykonywalnego.
  • Uwaga: niektóre skompilowane kody są połączone razem, ale nie w celu utworzenia końcowego programu. Zazwyczaj ten „połączony” kod można również spakować do formatu, z którego mogą korzystać inne programy. Ten „pakiet spakowanego, użytecznego kodu” jest tym, co programiści C ++ nazywają biblioteką.

Wiele kompilatorów C ++ może również łączyć lub rozłączać niektóre części procesu kompilacji w celu ułatwienia lub dodatkowej analizy. Wielu programistów C ++ będzie korzystać z różnych narzędzi, ale wszystkie narzędzia będą na ogół podążać za tym uogólnionym procesem, gdy będą zaangażowane w tworzenie programu.

Poniższy link rozszerza tę dyskusję i zapewnia miłą grafikę, która może pomóc. [1]: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

Widoczność prototypów funkcji i deklaracji

W C ++ kod należy zadeklarować lub zdefiniować przed użyciem. Na przykład następujące powoduje błąd czasu kompilacji:

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

Istnieją dwa sposoby rozwiązania tego: umieszczenie definicji lub deklaracji foo() przed użyciem w main() . Oto jeden przykład:

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

Możliwe jest jednak również „zadeklarowanie do przodu” funkcji poprzez umieszczenie tylko deklaracji „prototypowej” przed jej użyciem, a następnie zdefiniowanie treści funkcji później:

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
}
 

Prototyp musi określać typ zwracany ( void ), nazwę funkcji ( foo ) i typy zmiennych listy argumentów ( int ), ale nazwy argumentów NIE są wymagane .

Jednym z powszechnych sposobów zintegrowania tego z organizacją plików źródłowych jest utworzenie pliku nagłówkowego zawierającego wszystkie prototypowe deklaracje:

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

a następnie podaj pełną definicję w innym miejscu:

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

a następnie po skompilowaniu połącz odpowiedni plik obiektowy foo.o do skompilowanego pliku obiektowego, w którym jest on używany w fazie łączenia, 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
 

Określenie „nierozwiązanych zewnętrznych symbolu” Błąd występuje, gdy prototyp funkcji i wezwanie istnieje, ale ciało funkcji nie jest zdefiniowana. Mogą być trudniejsze do rozwiązania, ponieważ kompilator nie zgłosi błędu do ostatniego etapu łączenia i nie wie, do której linii przeskoczyć w kodzie, aby wyświetlić błąd.