Podstawowe pojęcia¶
Pierwszy program¶
Kod programu¶
#include <iostream>
int main()
{
std::cout << "Hello world!" << std::endl;
return 0;
}
Kompilacja¶
Język C++ jest językiem kompilowanym. Kompilator przetwarza kod źródłowy do kodu obiektowego w języku assembler.
Pliki wynikowe zawierające kod obiektowy mają rozszerzenie .o
lub .obj
.
Łączenie¶
Program łączący (linker) łączy pliki z kodem obiektowym w program wykonywalny.
Podstawowe typy danych¶
Typ void¶
Typ z pustą listą wartości. Nie można tworzyć zmiennych typu void
- jest to typem niekompletny.
Zabronione jest tworzenie tablic typu void
oraz referencji do typu void
.
Można tworzyć zmienne typu void*
oraz funkcje zwracające typ void
.
Typy liczbowe¶
Nazwa |
Rozmiar |
Zakres |
---|---|---|
|
1 bajt |
signed: [-128; 127]
unsigned: [0; 255]
|
|
2 bajty |
signed: [-32768; 32767]
unsigned: [0; 65535]
|
|
4 bajty |
signed: [-2147483648; 2147483647]
unsigned: [0; 4294967295]
|
|
4 bajty |
signed: [-2147483648; 2147483647]
unsigned: [0; 4294967295]
|
|
8 bajtów |
signed: [-9223372036854775808; 9223372036854775807]
unsigned [0; 18446744073709551615]
|
|
1 bajt |
|
|
4 bajty |
+/- 3.4e +/- 38 (~7 cyfr) |
|
8 bajty |
+/- 1.7e +/- 308 (~15 cyfr) |
|
8 bajty |
+/- 1.7e +/- 308 (~15 cyfr) |
|
2 lub 4 bajty |
Znak Unicode |
Literały¶
Dla typów całkowitych można podawać wartości korzystając z następujących sufixów:
int d = 42;
int o = 052; // octal
int x = 0x2a; // hex
int X = 0X2A; // hex
int b = 0b101010; // binary - C++14
Dla typów całkowitych obowiązują następujące rodzaje literałów:
suffix |
typy |
---|---|
brak |
int long int long long int |
u lub U |
unsigned int unsigned long int unsigned long long int |
l lub L |
long int long long int |
l/L i u/U |
unsigned long int unsigned long long int |
ll/LL |
long long int |
ll/LL i u/U |
unsigned long long int |
Łańcuchy znaków¶
W C++ występują dwa typy łańcuchów znaków:
Niskopoziomowy
const char*
std::cout << "Simple C-string!\n"; const char* text = "C-style text";
Wysokopoziomowy std::string
#include <string> std::string text1("Szkolenie"); std::string text2 = "C++"; auto text3 = "C++14"s; std::cout << text1 << " " << text2 << " " << text3 << std::endl;
Zmienne¶
Definicja zmiennych:
int x = 39;
unsigned int number_of_steps = 100;
double result = 3.14;
char decimal_point = ',';
const char* name = "Anna Maria";
std::string text = "Ala ma kota";
std::string text2 = "text"s;
Dedukcja typu auto
¶
Słowo kluczowe auto
umożliwia dedukcję typu obiektu na podstawie wyrażenia inicjalizującego.
Typ dedukowany może być typem zmiennej, stałej (const
) lub stałego wyrażenia (constexpr
).
auto a1 = 78; // a1 - int
auto text1 = "Text"; // const char*
auto text2 = "Text"s; // std::string
auto text3 = text2 + " " + text1; // std::string
Konwersje typów¶
Konwersje bezpieczne dla typów¶
W razie potrzeby można bezpiecznie przekonwertować char
na int
, a int
na double
:
char c = 'x';
int i1 = c;
int i2 = 'x';
Bezpieczne są następujące konwersje typów:
bool
nachar
bool
naint
char
naint
char
nadouble
int
nadouble
Konwersje niebezpieczne dla typów¶
Język C++ dopuszcza także niejawne konwersje zawężające, które mogą być niebezpieczne. Polegają one na wstawianiu wartości do zmiennych, których typ jest za mały (wąski), aby je pomieścić. Wszystkie poniższe konwersje kompilator zaakceptuje mimo, że są one niebezpieczne dla typów:
double
naint
double
nachar
int
nachar
int
nabool
char
nabool
Standardowe strumienie IN/OUT¶
std::cout
– standardowy strumień wyjścia (konsola)std::cin
– standardowy strumień wejścia (klawiatura)
#include <iostream>
int main()
{
int number;
std::cout << "Podaj liczbe: ";
std::cin >> number;
std::cout << "wartosc w zapisie szesnastkowym = 0x"
<< std::hex << number << std::endl;
}
Operatory¶
Operator |
Nazwa |
Opis |
---|---|---|
|
wywołanie funkcji |
przekazuje a do f jako argument |
|
preinkrementacja |
zwiększa wartość i potem jej używa |
|
predekrementacja |
zmniejsza wartość i potem jej używa |
|
postinkrementacja |
używa wartości, a potem ją zwiększa |
|
postdekrementacja |
używa wartości, a potem ją zmniejsza |
|
negacja |
wynikiem jest wartość logiczna |
|
jednoargumentowy minus |
|
|
mnożenie |
|
|
dzielenie |
|
|
reszta z dzielenia (modulo) |
dotyczy tylko typów całkowitoliczbowych |
|
dodawanie |
|
|
odejmowanie |
|
|
wysyłanie b na wyjście |
out jest strumieniem ostream |
|
wczytywanie z wejścia do b |
in jest strumieniem istream |
|
znak mniejszości |
wynikiem jest wartość logiczna |
|
mniejszy lub równy |
wynikiem jest wartość logiczna |
|
znak większości |
wynikiem jest wartość logiczna |
|
większy lub równy |
wynikiem jest wartość logiczna |
|
znak równości |
nie mylić z = |
|
różny od |
wynikiem jest wartość logiczna |
|
iloczyn logiczny |
wynikiem jest wartość logiczna |
|
suma logiczna |
wynikiem jest wartość logiczna |
|
przypisanie |
nie mylić z == |
|
przypisanie złożone |
l-wartość = l-wartość * a dotyczy także operatorów /, %, + oraz - |
Blok instrukcji¶
Instrukcje mogą zostać zgrupowane przez umieszczenie ich między nawiasami { i }
int foo()
{
//...
{
instr_1;
instr_2;
// ...
instr_n;
}
//...
}
Instrukcje sterujące przebiegiem wykonania programu¶
Instrukcja wyboru if¶
if ( traffic_light == green )
go();
Instrukcja wyboru if else¶
if ( x % 2 == 0 )
{
std::cout << "x jest parzysta" << std::endl;
}
else
std::cout << "x jest nieparzysta" << std::endl;
Instrukcja wyboru switch¶
size_t day = 2;
//…
switch (day)
{
case 1:
std::cout << "poniedzialek" << std::endl;
break;
case 2:
case 3:
std::cout << "wtorek lub sroda" << std::endl;
break;
default:
std::cout << "inny dzien tygodnia" << std::endl;
break;
}
Reguły użycia instrukcji switch
:
Wartość wykorzystywana do porównania w instrukcji
switch
musi być liczbą całkowitą typu char lub wyliczeniem. Nie można używać typu std::string.Wartości w etykietach
case
muszą być wyrażeniami stałymi. Nie można w nich używać zmiennych.Nie można zastosować takiej samej wartości w dwóch etykietach
case
.Można użyć kilku etykiet
case
dla jednego przypadku.Zestaw instrukcji każdej etykiety powinien kończyć się instrukcją
break
.
Pętla while i do… while¶
Pętla while
:
int i = 0;
while (i < 10)
{
std::cout << i << std::endl;
++i;
}
Pętla do… while
:
int i = 0;
do
{
std::cout << i << std::endl;
++i;
} while (i < 10);
Pętla for¶
Pętla for
:
for(int i = 0; i < 10; ++i)
std::cout << i << std::endl;
Zagnieżdżona pętla for
:
for(int i = 0; i < 10; ++i)
for(int j = 0; j < 10; ++j)
std::cout << i << "*" << j << "=" << i*j << std::endl;
Instrukcja break¶
Instrukcja break
jest używana w instrukcjach switch, while, do oraz for:
#include <iostream>
int main()
{
for (int i = 1; i < 10; i++)
{
std::cout << i << std::endl;
if (i == 4) break;
}
}
1
2
3
4
Instrukcja continue¶
Instrukcja continue powoduje natychmiastowy przeskok do kontrolnego wyrażenia instrukcji iteracyjnych do
, while
oraz for
.
#include <iostream>
int main()
{
int i = 0;
do
{
i++;
std::cout << "before the continue\n";
continue;
std::cout << "after the continue, should never print\n";
} while (i < 3);
std::cout << "after the do loop\n";
}
before the continue
before the continue
before the continue
after the do loop
Deklaracja a definicja¶
Deklaracja – zapowiada istnienie w programie obiektu określonego typu lub określonej funkcji
Definicja – tworzy (rezerwuje miejsce w pamięci) określony obiekt (zmienną lub funkcję)
extern int var; // deklaracja zmiennej
int func1(int, int); // deklaracja funkcji
extern float func2(float); // deklaracja funkcji
int i; // definicja zmiennej
bool negat(bool b) // definicja funkcji
{
return !b;
}
Typ wyliczeniowy¶
Typ wyliczeniowy (enum) to prosty definiowany przez użytkownika typ danych, który zawiera zestaw wartości (elementy wyliczenia) w postaci stałych symbolicznych.
enum Month
{
jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};
void f(Month m)
{
switch(m)
{
case jan:
// ...
break;
case feb:
// ...
break;
}
}
Dla typów wyliczeniowych można w C++11 specyfikować typ całkowity na którym definiowane jest wyliczenie.
Wartości podawane w wyliczeniu muszą mieścić się w dozwolonym zakresie dla typu.
enum Coffee : std::uint_8_t { espresso, cappucino, latte };
enum State : unsigned char { opened, closed, unknown = 999 }; // error!
Typ na bazie którego definiowane jest wyliczenie jest dostępny za pomocą metafunkcji std::underlying_type<EnumType>
:
#include <type_traits>
using namespace std;
enum Coffee : uint8_t { espresso, cappucino, latte };
int main()
{
Coffee coffee = espresso;
bool enum_type_is_uint8
= std::is_same<uint8_t, typename std::underlying_type<Coffee>::type>::value;
if (enum_type_is_uint8)
cout << "Underlying type is uint8_t" << endl;
}
Wyliczenia silnie typizowane - Scoped Enumerations¶
Dla wyliczeń silnie typizowanych (scoped enums):
Można specyfikować na bazie jakiego typu definiowane jest wyliczenie
Domyślnym typem na którym definiowane jest wyliczenie jest
int
Niejawna konwersja do i z typu całkowitego nie jest dozwolona
Nie ma możliwości porównania obiektów różnych typów wyliczeniowych
Wartości wyliczenia są umieszczone w zakresie typu
Istnieje możliwość użycia deklaracji zapowiadającej (forward declaration). Gdy używany jest tylko typ nie ma potrzeby rekompilacji przy dodaniu nowej wartości dla wyliczenia.
enum class Engine : char; // forward declaration
Engine e;
enum class Engine : char { petrol, diesel, wankel };
e = Engine::petrol; // OK
e = diesel; // error - no "diesel" in scope
e = 3; // error
e = static_cast<Engine>(1); // OK: e == Engine::diesel
int index = Engine::wankel; // error
Zamiast enum class
może być użyte enum struct
- nie ma żadnej semantycznej różnicy między tymi formami.
Stałe¶
C++ oferuje koncepcję zdefiniowanej przez użytkownika wartości stałej. Aby zadeklarować obiekt jako stały należy poprzedzić jego deklarację słowem kluczowym const. Stała musi zostać zainicjowana w momencie deklaracji.
const double g = 9.807; // g jest stałą
const int x; // błąd – brak inicjalizacji
g = 10.788; // błąd
C-łańcuchy¶
Literały znakowe reprezentowane są jako tablice znaków zakończone wartością «0». Typem literałów znakowych jest const char*
.
const char* s = "hello";
for(const char* p=s; *p != '\0'; ++p)
std::cout << *p << std::endl;
for(size_t i = 0; i < strlen(s); ++i)
std::cout << s[i] << std::endl;
Problemy z C-łańcuchami.
const char* s = "hello";
const char* t = "C++";
s = t; // Uwaga! przypisanie wskaźników
Operacje na łańcuchach są kłopotliwe. Wymagają użycia funkcji znanych z języka C.
size_t strlen(const char* s); // Liczy znaki
char* strcat(char* s1, const char* s2); // Kopiuje s2 na koniec s1
int strcmp(const char* s1, const char* s2); // Porównanie leksykograficzne
char* strcpy(char* s1, const char* s2); // Kopiuje s2 do s1
char* strchr(const char* s, int c); // Znajduje c w s
char* strstr(const char* s1, const char* s2); // Znajduje s2 w s1
char* strncpy(char*, const char*, size_t n); // strcpy, maks. n znaków
char* strncat(char*, const char*, size_t n); // strcat, maks. n znaków
int strncmp(const char*, const char*, size_t n); // strcmp, maks. n znaków
Typ string¶
Typ string hermetyzuje szczegóły niskopoziomowej reprezentacji łańcuchów za pomocą typów podstawowych.
#include <string>
std::string firstname = "bjarne";
//powoduje powstanie obiektu klasy std::string zainicjowanego wartością
//typu const char*
std::string lastname("stroustrup");
firstname[0] = 'B';
lastname[0] = 'S';
Łańcuchy możemy używać w taki sam sposób, co typów podstawowych
Za pomocą operatorów == i != możemy je porównywać
Za pomocą operatorów + możemy je konkatenować
Za pomocą operatora = możemy przypisywać jeden łańcuch do drugiego
std::string name = firstname + " " + lastname;
if (name != "")
{
std::cout << name;
}
std::string text = "hello" + " " + "world"; // źle
text = static_cast<string>("hello") + " " + "world";
Operacje łańcuchowe
Operacja |
Opis |
---|---|
|
Przypisuje s2 do s. s2 może być zwykłym łańcuchem lub łańcuchem w stylu języka C. |
|
Dodaje x na końcu s. x może być znakiem, zwykłym łańcuchem lub łańcuchem w stylu języka C. |
|
Indeksowanie |
|
Konkatenacja. Wynikiem jest nowy łańcuch zawierający znaki z s i znaki z s2. |
|
Porównywanie łańcuchów. s lub s2, ale nie obydwa jednocześnie. Mogą być łańcuchami w stylu języka C. |
|
Porównywanie łańcuchów. s lub s2, ale nie obydwa jednocześnie. Mogą być łańcuchami w stylu języka C. |
|
Leksykograficzne porównywanie łańcuchów. s lub s2, ale nie obydwa jednocześnie. Mogą być łańcuchami w stylu języka C. |
|
Leksykograficzne porównywanie łańcuchów. s lub s2, ale nie obydwa jednocześnie. Mogą być łańcuchami w stylu języka C. |
|
Leksykograficzne porównywanie łańcuchów. s lub s2, ale nie obydwa jednocześnie. Mogą być łańcuchami w stylu języka C. |
|
Leksykograficzne porównywanie łańcuchów. s lub s2, ale nie obydwa jednocześnie. Mogą być łańcuchami w stylu języka C. |
|
Liczba znaków w s. |
|
Liczba znaków w s. |
|
Wersja w postaci łańcucha w stylu języka C (zakończona zerem) znaków w s. |
|
Iterator wskazujący pierwszy znak |
|
Iterator wskazujący miejsce za ostatnim znakiem |
|
Wstawia x przed s[pos]. x może być znakiem, zwykłym łańcuchem lub łańcuchem w stylu języka C. |
|
Wstawia x za s[pos]. x może być znakiem, zwykłym łańcuchem lub łańcuchem w stylu języka C. |
|
Usuwa znak s[pos]. |
|
Dodaje na końcu znak c. |
|
Znajduje x w s. x może być znakiem, zwykłym łańcuchem lub łańcuchem w stylu języka C. pos jest indeksem pierwszego znalezionego znaku lub npos (miejscem za końcem łańcucha s). |
|
Wczytuje do s słowo ze strumienia |
|
Zapisuje s słowo do strumienia |
Raw String Literals¶
W C++11 możemy uniknąć specjalnego traktowania „znaków specjalnych” w literałach znakowych stosując tzw. „Raw String Literals”.
„Raw string” zaczyna się od R"(
, a kończy się ")
.
np. n i podwójny cudzysłów
std::string no_newlines = R"(\n\n)"; std::string cmd(R"(cd "C:\new folder\text")");
łamanych lini w tekście
std::string with_newlines(R"(Line 1 of text... Line 2... Line 3)");
Aby mieć możliwość umieszczenia sekwencji ")
w literale „raw string”, należy użyć sekwencji przestankowej delim.
W rezultacie kompletna składnia literału „raw string” to: R"delim()delim"
.
string str1 = R"nc(a\
b\nc()"
)nc";
string str2 = "a\\\n b\\nc()\"\n ";
Literały „raw string” są szczególnie przydatne do definiowania wyrażeń regularnych.
std::regex re1(R"!("operator\(\)"|"operator->"!")); // "operator()"|"operator->"
Sekwencja przestankowa może mieć długośc do 16 znaków i nie może zawierać białych znaków (spacji itp.).
Odniesienia¶
Referencja (odniesienie) jest inną nazwą dla danego obiektu. Jest wykorzystywana przy przesyłaniu argumentów do funkcji lub jako sposób zwracania wartości przez funkcję (np. przeładowywanie operatorów). Referencja musi zostać zainicjalizowana w momencie tworzenia.
Wartość referencji nie może zostać zmieniona po inicjalizacji (referencja zawsze odnosi się do zainicjalizowanego obiektu).
int i = 1;
int& r = i; // r oraz i odnoszą się teraz do tej samej zmiennej typu int
int x = r; // x = 1
r = 2; // i = 2
Inicjalizacja referencji dla „zwykłego” typu T& wymaga l-wartości (lvalue) – obiektu, dla którego możliwe jest pobranie adresu. Inicjalizacja dla const T& nie wymaga l-wartości. Obiekt tymczasowy utworzony aby zainicjalizować referencję istnieje do końca istnienia referencji.
double& dr = 1; // błąd! wymagana l-wartość
const double& cdr = 1; // ok
Aliasy typów¶
Słowo kluczowe typedef
umożliwia tworzenie aliasów typów.
typedef unsigned long ulong; // alias
ulong i; // definicja zmiennej typu unsigned long
typedef int* PtrInt;
PtrInt px;
Aliasy typów using
¶
W C++11 deklaracja using
może zostać użyta do tworzenia bardziej czytelnych aliasów dla typów - zamiennik dla typedef
.
using CarID = int;
using Func = int(*)(double, double);
using DictionaryDesc = std::map<std::string, std::string, std::greater<std::string>>;
Aliasy typów mogą być parametryzowane typem. Można je wykorzystać do tworzenia częściowo związanych typów szablonowych.
template <typename T>
using StrKeyMap = std::map<std::string, T>;
StrKeyMap<int> my_map; // std::map<std::string, int>
Struktury¶
Słowo kluczowe struct umożliwia łączenie w grupę kilku zmiennych. W połączeniu z typedef (C) umożliwia zdefiniowanie przez użytkownika nowego typu.
typedef struct Structure
{
char c;
int f;
double d;
};
int main()
{
Structure s1, s2;
s1.c = 'a';
s2.d = 3.14;
}