C++ – Wprowadzenie do języka
C++ jest językiem programowania, przy użyciu którego możemy zaprojektować każde oprogramowanie. Pomimo swojego całkiem niskiego poziomu umożliwia abstrakcję oraz pozwala na realizację softu z wykorzystaniem paradygmatów programowania zorientowanego obiektowo.
W porównaniu do języka Python, o którym wpis był jakiś czas temu, jest językiem kompilowanym, dzięki czemu wydajność aplikacji stoi na bardzo wysokim poziomie. Ponadto pozwala na zarządzanie zasobami, pamięć możemy alokować manualnie i sami musimy zadbać o brak błędów związanych z wyciekami pamięci. Inną zaletą jego zapewnia nam C++ jest niezależność od parametrów sprzętowych urządzenia, kod jest kompilowany z przeznaczeniem na konkretny procesor. Znajduje swoje zastosowanie w projektowaniu systemów operacyjnych oraz aplikacji.
Witaj Świecie!
Wprowadzenie do tego jakże wspaniałego języka zaczniemy od tradycyjnego już Witaj Świecie:
#include <iostream>
int main()
{
std::cout << "Hello World" << std::endl;
return 0;
}
Przeanalizujmy kod linijka po linijce:
#include <iostream>
Powyżej znajduje się polecenie zwane również dyrektywą preprocesora. Preprocesora, ponieważ to wywołanie wykonuje się przed samym tworzeniem kodu maszynowego. Wszystkie dyrektywy rozpoczynają się od znaku kluczowego #. #include jest dyrektywą, której zapewne każdy programista używa najczęściej. Jej celem jest dołączenie pliku źródłowego, w tej sytuacji jest to biblioteka iostream, zapewnia nam operacje wejścia / wyjścia.
int main()
W taki sposób definiujmy w języku C++ funkcje. Ważnym elementem jest na początku typ danych int. C++ wymaga zdefiniowania typu jaki będzie zwracany z funkcji. main to po prostu nazwa funkcji, z której nasz kod rusza. W każdym projekcie taka funkcja musi się znaleźć, po to żeby komputer mógł wiedzieć skąd ma zacząć. Natomiast nawiasy () definiują przyjmowane parametry do funkcji. W tej sytuacji takowych nie ma.
{
/* ciało funkcji */
}
Nawiasy klamrowe definiują miejsca w których będzie znajdowały się prze różne operacje. Przy definiowaniu funkcji są to obowiązkowe elementy.
std::cout << "Hello World" << std::endl;
Zacznę od tego czym jest std. Jest to przestrzeń nazw, w której znajdują się co niektóre elementy biblioteki standardowej. Ona definiuje takie moduły jak cout (Console Output) czy endl (End Line).
Przestrzenie nazw mają swoje zastosowanie podczas pracy nad większymi projektami, gdzie może być problem z odpowiednim nazewnictwem klas, czy funkcji. Wiele programistów może nazwać instancje w ten sam sposób co spowoduje wiele różnych błędów.
Dlatego zrezygnowałem z korzystania z using namespace std;. Programista powinien celowo skorzystania z tego dobrodziejstwa licząc się ze wspomnianymi konsekwencjami. Jednak w sytuacji przestrzeni std nie ma takiej potrzeby i warto korzystać z takiego rozwiązania.
Do elementu przestrzeni nazw zwracamy się korzystając z ::. Zadaniem std::cout jest wydrukowanie poleconej zmiennej. << to operator wysyłania danych do strumienia. On przekazuje element do std::cout, dzięki czemu możemy obserwować wspomniane Witaj Świecie. std::endl tworzy nowy akapit.
Warto wspomnieć, że wszystkie polecenia wykonują się od lewej do prawej. Zatem najpierw maszyna będzie chciała wydrukować cokolwiek w terminalu, następnie zostaje przekazany strumień „Hello World”, a na końcu std::endl co powoduje utworzenie nowej linii.
return 0;
Komenda zwraca 0 jako rezultat wykonania danej funkcji.
Kompilacja
Powyższy kod należy skompilować przy użyciu narzędzia zwanego kompilatorem (gcc albo g++) oraz uruchamiamy:
gcc hw.cpp -o hw -lstdc++
./hw
Output:
Hello World
gcc oraz g++ wchodzą w skład zestawu kompilatorów o otwartym kodzie źródłowym (GNU Compiler Collection). Jest jednym z najważniejszych narzędzi w system uniksopodobnych.
- hw.cpp to nazwa pliku, który ma być skompilowany.
- -o hw mówi, że skompilowany kod ma znajdować się w pliku wykonywalnym nazwanym hw.
- -lstdc++ to dodanie biblioteki standardowej do projektu.
Natomiast same pliki wykonywalne w systemach GNU Linux uruchamiamy poprzedzając nazwę pliku „./”.
Podstawowe typy danych
C++ posiada kilka standardowych typów danych, które są wykorzystywane najczęściej:
- liczby stałe (integer), które możemy podzielić zależnie od rozmiaru w bajtach
- liczby zmiennoprzecinkowe (float), również taki sam podział
- pojedyncze znaki reprezentowane w kodzie ASCII (char)
- łańcuchy znaków (string)
- zmienne logiczne (bool)
Zmienną w C++ tworzymy korzystając z danego wzoru:
typ_zmiennej nazwa_zmiennej;
typ_zmiennej nazwa_zmiennej = przypisywana_wartość;
Poniżej znajdują się wszystkie typy danych z jakimi możemy się spotkać. Osobiście polecam samodzielnie skompilować kod i przekonać się o wynikach programu.
#include <iostream>
#include <typeinfo>
int main()
{
short var_short = 2;
int var_int = 128;
long int var_long_int = 2048;
long long int var_long_long_int = 12288;
float var_float = 3.14;
double var_double = 3.14159;
long double var_long_double = 3.14159265359;
char var_char = 'a';
bool var_bool = true;
std::cout << "This is: " << typeid(var_short).name() << " = " << var_short <<
" Size: " << sizeof(var_short) << " bytes" << std::endl; // s means short
std::cout << "This is: " << typeid(var_int).name() << " = " << var_int <<
" Size: " << sizeof(var_int) << " bytes" << std::endl; // i means integer
std::cout << "This is: " << typeid(var_long_int).name() << " = " << var_long_int <<
" Size: " << sizeof(var_long_int) << " bytes" << std::endl; // l means long int
std::cout << "This is: " << typeid(var_long_long_int).name() << " = " << var_long_long_int <<
" Size: " << sizeof(var_long_long_int) << " bytes" << std::endl; // x means long long int
std::cout << "This is: " << typeid(var_float).name() << " = " << var_float <<
" Size: " << sizeof(var_float) << " bytes" << std::endl; // f means float
std::cout << "This is: " << typeid(var_double).name() << " = " << var_double <<
" Size: " << sizeof(var_double) << " bytes" << std::endl; // d means double
std::cout << "This is: " << typeid(var_long_double).name() << " = " << var_long_double <<
" Size: " << sizeof(var_long_double) << " bytes" << std::endl; // e means long double
std::cout << "This is: " << typeid(var_char).name() << " = " << var_char <<
" Size: " << sizeof(var_char) << " bytes" << std::endl; // c means char
std::cout << "This is: " << typeid(var_bool).name() << " = " << var_bool <<
" Size: " << sizeof(var_bool) << " bytes" << std::endl; // b means bool
}
/* Outputs:
This is: s = 2 Size: 2 bytes
This is: i = 128 Size: 4 bytes
This is: l = 2048 Size: 8 bytes
This is: x = 12288 Size: 8 bytes
This is: f = 3.14 Size: 4 bytes
This is: d = 3.14159 Size: 8 bytes
This is: e = 3.14159 Size: 16 bytes
This is: c = a Size: 1 bytes
This is: b = 1 Size: 1 bytes
*/
Jak możesz zauważyć, na początku zdefiniowałem zmienne o typach danych dostępnych w C++. Ponadto znaczącą różnicą jest dodanie linijki:
#include <typeinfo>
W tym pliku nagłówkowym znajduję się funkcja typeid(variable).name() z której korzystamy, zwraca ona typ zadanej jako parametr zmiennej. Ponadto nowością jest sizeof(variable), ta funkcja zwraca rozmiar w bajtach danej zmiennej.
Limity podstawowych typów danych
Jeśli chodzi o same zmienne warto wiedzieć, że wszystkie posiadają swój zakres, którego przekroczenie może spowodować nieoczekiwaną sytuację w kodzie.
#include <iostream>
#include <limits>
int main()
{
short s_min = std::numeric_limits<short>::min();
short s_max = std::numeric_limits<short>::max();
int i_min = std::numeric_limits<int>::min();
int i_max = std::numeric_limits<int>::max();
long int li_min = std::numeric_limits<long int>::min();
long int li_max = std::numeric_limits<long int>::max();
long long int lli_min = std::numeric_limits<long long int>::min();
long long int lli_max = std::numeric_limits<long long int>::max();
float f_min = std::numeric_limits<float>::min();
float f_max = std::numeric_limits<float>::max();
double d_min = std::numeric_limits<double>::min();
double d_max = std::numeric_limits<double>::max();
long double ld_min = std::numeric_limits<long double>::min();
long double ld_max = std::numeric_limits<long double>::max();
std::cout << "short range: " << s_min << " ... " << s_max << std::endl;
std::cout << "int range: " << i_min << " ... " << i_max << std::endl;
std::cout << "long int range: " << li_min << " ... " << li_max << std::endl;
std::cout << "long long int range: " << lli_min << " ... " << lli_max << std::endl;
std::cout << "float range: " << f_min << " ... " << f_max << std::endl;
std::cout << "double range: " << d_min << " ... " << d_max << std::endl;
std::cout << "long double range: " << ld_min << " ... " << ld_max << std::endl;
}
/* Outputs:
short range: -32768 ... 32767
int range: -2147483648 ... 2147483647
long int range: -9223372036854775808 ... 9223372036854775807
long long int range: -9223372036854775808 ... 9223372036854775807
float range: 1.17549e-38 ... 3.40282e+38
double range: 2.22507e-308 ... 1.79769e+308
long double range: 3.3621e-4932 ... 1.18973e+4932
*/
Jako zadanie domowe pozostawiam Tobie sprawdzenie, co się stanie gdy przekroczysz limit danego typu danych. Zgodnie z Learn by doing!
C++ udostępnia świetny sposób na sprawdzenie minimalnej jak i maksymalnej wartości danego typu z którego powyżej korzystamy. Znowu obserwujemy nową bibliotekę jaką jest limits, a w niej znajduje się funkcja biblioteki standardowej std::numeric_limits<type_name>. Korzystając z kolejno funkcji min() jak i max() zostają zwracane wartości minimalne oraz maksymalne.
Zdaję sobie sprawę, że na sam początek może to być kod trudny i wymagający, jednak uważam, że taki powinniśmy analizować i przyzwyczajać się. Na początku wszystko jest niejasne, trzeba poświęcić odrobinę czasu i pozwolić sobie na spokojnie przyswoić zdobytą wiedzę.
Słowa kluczowe signed i unsigned
Tutaj należy wiedzieć, że najlepiej przetłumaczyć to sobie z języka angielskiego:
- signed / ze znakiem
- unsigned / bez znaku
Oczywiście mamy na myśli tutaj typ danych, który określa liczby mogące posiadać znak bądź nie. Domyślnie, gdy deklarujemy jakąkolwiek zmienną C++ uznaje, że jest to zmienna typu signed, dlatego jest to słowo pomijane. Gdy chcemy korzystać tylko z liczb dodatnich należy dopisać słowo kluczowe unsigned. Poniżej znajduje się prosty przykład:
#include <iostream>
#include <limits>
int main()
{
int i_min = std::numeric_limits<int>::min();
int i_max = std::numeric_limits<int>::max();
signed int si_min = std::numeric_limits<signed int>::min();
signed int si_max = std::numeric_limits<signed int>::max();
unsigned int usi_min = std::numeric_limits<unsigned int>::min();
unsigned int usi_max = std::numeric_limits<unsigned int>::max();
std::cout << "int range: " << i_min << " ... " << i_max << std::endl;
std::cout << "signed int range: " << si_min << " ... " << si_max << std::endl;
std::cout << "unsigned int range: " << usi_min << " ... " << usi_max << std::endl;
}
/* Outputs:
int range: -2147483648 ... 2147483647
signed int range: -2147483648 ... 2147483647
unsigned int range: 0 ... 4294967295
*/
Jak pokazuje rezultat int to jest to samo co signed int. Różni się natomiast unsigned int, które może zawierać tylko liczby dodatnie włącznie z zerem. Polecam Ci samodzielnie sprawdzić rozmiar typu z słowem unsigned oraz sprawdzić wartości min i max pozostałych typów, którymi są: short, long int, long long int, char.
Łańcuchy znaków (string)
Dane string w języku C składał się z tablicy zmiennych typu char co sprawiało wiele problemów programistom. Dlatego też w bibliotece standardowej została zaimplementowana klasa std::string, które znacznie ułatwia pracę. Uruchamiając poniższy kod możesz przeanalizować właściwości wspomnianego obiektu.
#include <iostream>
#include <string>
#include <typeinfo>
int main()
{
std::string text = "Ania ma kota";
std::cout << "This is: " << typeid(text).name() << " = " << text <<
" Size: " << sizeof(text) << " bytes" << std::endl;
}
/* Outputs:
This is: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE = Ania ma kota Size: 32 bytes
*/
Tutaj z kolei jest nam potrzebna biblioteka string. Sam obiekt std::string definiujemy według wzoru typ_zmiennej nazwa_zmiennej = przypisywana_wartość;.
Może się pojawić pytanie, czemu taki długi ciąg wyświetlił się po ukończeniu programu. Otóż tak nazywa się typ danych z jakiego korzystają kompilatory gcc oraz g++, co pokrótce oznacza wspomniany std::string. Zwracana nazwa przez typeid(variable).name() ma z założenia być nieczytelna dla użytkownika, natomiast zrozumiała dla maszyny.
Operacje arytmetyczne
Tak jak każdy język programowania C++ pozwala na wykonywanie obliczeń numerycznych. Przykłady zastosowanych rozwiązań znajdują się poniżej:
#include <iostream>
int main()
{
int a = 5;
int b = 2;
std::cout << "addition: " << a + b << std::endl;
std::cout << "subtraction: " << a - b << std::endl;
std::cout << "multiplication:" << a * b << std::endl;
std::cout << "division: " << a / b << std::endl;
std::cout << "modulo: " << a % b << std::endl;
std::cout << "unary plus: " << +a << std::endl;
std::cout << "unary minus: " << -a << std::endl;
std::cout << "bitwise NOT: " << ~a << std::endl;
std::cout << "bitwise AND: " << (a & b) << std::endl;
std::cout << "bitwise OR: " << (a | b) << std::endl;
std::cout << "bitwise XOR: " << (a ^ b) << std::endl;
std::cout << "bitwise left shift: " << (a << b) << std::endl;
std::cout << "bitwise right shift: " << (a >> b) << std::endl;
}
/* Outputs:
addition: 7
subtraction: 3
multiplication:10
division: 2
modulo: 1
unary plus: 5
unary minus: -5
bitwise NOT: -6
bitwise AND: 0
bitwise OR: 7
bitwise XOR: 7
bitwise left shift: 20
bitwise right shift: 1
*/
Podsumowanie
W wpisie udało nam się poznać składnie języka C++, napisać tradycyjną na początku aplikację Witaj Świecie, skompilować kod w środowisku GNU Linux oraz zaczęliśmy wykorzystywać C++ do naszych własnych zastosowań. W następnych wpisie poruszę tematy tablic, podstawowych kontenerów na dane oraz manualne alokacje pamięci (rozwiązanie C++98, C+11 oraz C++14).
Linki referencyjne:
What is type NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE ?
Jeden komentarz do “C++ – Wprowadzenie do języka”
Możliwość komentowania została wyłączona.