Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Korekta: Paweł Król
Korekta merytoryczna: 'pekfos'
Inne artykuły

Porównanie C++ i Python - różnice w składni i podejściu programistycznym

[artykuł] W artykule porównano języki C++ (wersja C++23) i Python (wersja 3.11), prezentując ich różnice w formie komentowanego "diffa". Skoncentrowano się na aspektach składniowych, uwypuklając różne podejścia w programowaniu, aby pomóc w zrozumieniu tych języków osobom uczącym się jednego z nich, zwłaszcza w kontekście wykorzystania C++ w kluczowych projektach i Pythona w tworzeniu skryptów w dużych firmach.
Programiści bardzo rzadko są w stanie utrzymać się znając tylko jedną technologię. Języki oprogramowania realizują podobne zadania w różnyh sposób. W polskim internecie brakuje kompleksowego zestawienia różnic pomiędzy różnymi językami w formie znanego programistom "diffa" z komentarzem.

W niniejszym artykule omówię różnice między językiem C++ (wersja C++23) a językiem Python (wersja 3.11) w formie "diffa" z komentarzem. Podkreślam jednak, będą to najczęstsze/najpopularniejsze aspekty. Porównuję odmienne podejścia w programowaniu - język wieloparadygmatowy kompilowalny z językiem skryptowym. Zatem takie zestawienie nie odwzorowuje stopnia zaawansowania między językami.

Celem pracy jest zestawienie różnic składniowych w programowaniu w tych językach. Realia w wielu firmach są takie, że kluczowe projekty powstają w C++, a w Pythonie przygotowuje się mniejsze narzędzia w formie skryptów. W dużych firmach osoby uczące się jednego z tych języków mogą nie znać drugiego, takie zestawienie im pomoże.

Podstawy

1. Pierwszy program wyświetlający tekst wraz z komentarzem:

C++ python3
C/C++
#include<iostream>
using namespace std;

int main()
{
   
cout << "Witaj C++" << endl;
   
// wydruk: Witaj C++
}
Python
print("Witaj Python")
# wydruk: Witaj Python
bardziej profesjonalne podejście bardziej profesjonalne podejście
C/C++
#include<iostream>

int main()
{
   
std::cout << "Witaj C++" << std::endl;
   
// wydruk: Witaj C++
}
Python
if __name__ == '__main__':
   
print("Witaj Python")
   
# wydruk: Witaj Python

W C++ każdy program musi mieć funkcję
main
 aby był programem. Jednakże dla prostoty dalszych przykładów będę czasami pomijał nagłówki i jawne napisanie funkcji
main
. W Python każdy poprawny program będzie działać od początku.


2. Rozszerzenia plików

C++ python3
podstawowe rozszerzenia
  • pliki nagłówkowe:
    .h
     lub
    .hpp
     (różnią się tylko konwencją np. konwencjonalnie
    .hpp
     jest do szablonów)
  • pliki źródłowe:
    .cpp
podstawowe && zalecane rozszerzenie
wszystkie pliki języka python:
.py
inne możliwe rozszerzenia
Zasadniczo rozszerzenie pliku może być dowolne, ale wygodniej jest stosować rozszerzenia rozpoznawalne przez różne narzędzia, do takich rozszerzeń należą:
  • pliki nagłówkowe:
    .h
    ,
    .hpp
    ,
    .hxx
    ,
    .hh
    ,
    .h++
  • pliki źródłowe:
    .C
    ,
    .cc
    ,
    .cpp
    ,
    .CPP
    ,
    .c++
    ,
    .cp
    , lub
    .cxx
Na stronie wytycznych do programowania z C++ polecają stosować rozszerzenia wpierw za konwencją, a jak jej nie ma to stosowane przez środowisko programistyczne.
inne możliwe rozszerzenia (są one bardzo rzadko używane):
.py3
, a także wg zestawienia również:
.pyw
,
.pyx
,
.pyd
,
.pxd
,
.pxi
,
.pyi
,
.pth

Na systemach linuxowych jest możliwe posiadanie dowolnego rozszerzenia pliku tekstowego, pod warunkiem, że jest on plikiem wykonywalnym i posiada tzw. "shebang" w pierwszej linijce wskazujący na interpreter pythona np.:
Python
#!/usr/bin/env python3

3. Wykonanie programu

C++ python3
C++ jest językiem kompilowalnym, więc musimy najpierw skompilować przygotowany program. Do tego celu służy program nazywany kompilatorem do wersji binarnej dedykowanej dla konkretnego systemu i konkretnej architektury. Dopiero skompilowane programy możemy uruchomić. Tak skompilowany program można uruchomić przez dwuklik lub z wiersza poleceń/konsoli.

Do kompilacji służą różne narzędzia. Nie ma uniwersalnego narzędzia kompilującego program jedną komendą na różnych systemach operacyjnych. Można użyć środowiska programistycznego, które załatwi kwestie kompilacji i uruchomienia naszego programu i wiele więcej. Z poziomu wiersza poleceń można np. skonfigurować plik CMake, które przy podaniu odpowiednich argumentów załatwi kompilacje i uruchomienie naszego programu niezależnie od systemu operacyjnego.
Python jest językiem interpretowanym, przez co plik tekstowy z kodem języka python jest uruchamiany bez pełnej kompilacji. Dla optymalizacji interpreter po dokonaniu analizy składni kompiluje kod do bytekodu, który jest ciut szybszy. Używane moduły (inne pliki języka python) w formie bytekodu są zapisywane do plików o rozszerzeniu
.pyc
. Kompilacja ta odbywa się automatycznie w sytuacji gdy plik z kodem jest nowszy niż plik skompilowany lub nie ma pliku skompilowanego.

Kompilacja ta nie jest tak dokładna jak w języku C++, dlatego wykrywane jest znacznie mniej błędów. Poza tym programy skompilowane w języku Python najczęściej są wolniejsze.

Przykładowe komendy do kompilacji:
  • Linux/MacOS:
    -
    g++ plik.cpp

    -
    clang++ plik.cpp

  • Windows - mimo iż można korzystać z komendy kompilatora np.
    vs.exe plik.cpp
     to aby to zrobić trzeba dodać ścieżkę do kompilatora do zmiennej
    path
     środowiskowej, czego się w tej sytuacji nie robi, tylko korzysta z narzędzi programistycznych

Uruchomienie interpretacji programu niezależnie od systemu:
python plik.py

Należy pamiętać na jednym komputerze może być kilka różnych w wersji python. W tym przypadku warto wskazać wersję interpretera w poleceniu, np.
python3.11 plik.py

W języku Python stosuje się dedykowane środowiska uruchomieniowe, tzw. venv

4. Typy wbudowane:

co C++ python3
typ znakowy
char

ten typ ma zawsze jeden bajt
char
typ całkowitoliczbowy
char <= short <= int <= long <= long long

typy różnią się rozmiarem, standard nie precyzuje rozmiarów wszystkich typów całkowitoliczbowych
int
- jest to typ dynamiczny, który w razie potrzeby może przechowywać bardzo duże liczby
zmienne typów dodatnich
unsigned char <= unsigned short <= unsigned int <= unsigned long <= unsigned long long

rozmiar w bajtach jak odpowiadające im typy całkowitoliczbowe
typ
int
 dostosowywuje się do zawartości, nie ma typów dodatnich wbudowanych w język.
Na upartego można jednak użyć typów języka C:
Python
import ctypes

print(ctypes.c_uint(20), ctypes.c_ushort(1),
     
ctypes.c_ulong(1), ctypes.c_longlong(22))
# wydruk: c_uint(20) c_ushort(1) c_ulong(1) c_long(22)
typ zmiennoprzecinkowy
float < double <= long double
float
- typ dynamiczny, który może przechowywać zaróbno bardzo duże jak i bardzo małe liczby
typ logiczny
bool
bool
przykładowe zmienne dla powyższych typów, wraz z wyświetleniem: Występuje statyczne typowanie - zmienna musi mieć sprecyzowany typ:
C/C++
#include<iostream>

int main()
{
   
int a = 4;
   
float b = 3.14;
   
bool c = false; //  = true
   
std::cout << a << ' ' << b << ' ' << c << std::endl;
}
nie deklaruje się typów zmiennych:
Python
if __name__ == '__main__':
   
a = 4
   
b = 3.14
   
c = False # = True
   
print(a, b, c)
ale można dla czytelności kodu je zadeklarować:
Python
if __name__ == '__main__':
   
a: int = 4
   
b: float = 3.14
   
c: bool = True
   
print(a, b, c)
wykrywanie typu na podstawie wartości przy użyciu słówka kluczowego:
auto
 np.:
C/C++
#include <iostream>
#include <typeinfo>

int main()
{
   
auto a = 4; // int
   
auto b = 3.14; // double
   
auto c = true; // bool
   
auto d = 3.14f; // float
   
auto e = 44l; // long int
   
auto f = 44LL; // long long int
   
std::cout << a << ' ' << typeid( a ).name() << ", "
   
<< b << ' ' << typeid( b ).name() << ", "
   
<< c << ' ' << typeid( c ).name() << ", "
   
<< d << ' ' << typeid( d ).name() << ", "
   
<< e << ' ' << typeid( e ).name() << ", "
   
<< f << ' ' << typeid( f ).name()
    <<
std::endl;
   
// wydruk przykładowy: 4 i, 3.14 d, 1 b, 3.14 f, 44 l, 44 x
}
jak widać nazwy typów zależą od kompilatora, powyższe testowane na
g++
dynamiczne typowanie - odbywa się to zawsze:
Python
a = 4
b = 3.14
c = True
d = 3.14

print(f"{a} {type(a).__name__}, {b} {type(b).__name__}, "
      f"{
c} {type(c).__name__}, {d} {type(d).__name__}")
# wydruk: 4 int, 3.14 float, True bool, 3.14 float

5. Czas życia zmiennych:

C++ python3
W C++ możemy zdefiniować zmienną w różnych obszarach pamięci:
  • zmienne globalne - żyją przez cały program
  • zmienne lokalne - żyją od momentu zdefiniowania do końca funkcji (lub bloku ograniczonego nawiasami wąsatymi, w którym zostały zdefiniowane)
  • zmienne statyczne w funkcjach - są inicjalizowane przed pierwszym użyciem i żyją przez cały program
  • zmienne w pamięci dynamicznej (zwanej stertą) - żyją od momentu zaalokowania do czasu zwolnienia (lub do końca programu jak sobie zapomnimy o zwolnieniu pamięci, wtedy mamy do czynienia z tzw. WYCIEKIEM PAMIĘCI).
Przykład demonstrujący zakres życia zmiennych:
C/C++
#include <iostream>

int globalnaZmienna = 10;
int a = 0;

void f()
{
   
static int statycznaZmienna = 0; // inicjalizowane tylko raz
   
int lokalnaZmienna = 44; // inicjalizowane za kazdym wejsciem do funkcji
   
std::cout << ++statycznaZmienna << ") Wywolanie funkcji: " << __FUNCTION__
    << ", zmienna lokalna=" << lokalnaZmienna << std::endl;
}

int main() {
   
int lokalnaZmienna = 5;
   
   
std::cout << "Lokalna zmienna: " << lokalnaZmienna << std::endl;
   
std::cout << "Globalna zmienna: " << globalnaZmienna << std::endl;
   
   
std::cout << a << " w linii: " << __LINE__ << ", a globalne a=" <<::a << std::endl;
   
{
       
int a = 1;
       
std::cout << a << " w linii: " << __LINE__ << ", a globalne a=" <<::a << std::endl;
       
{
           
int a = 2;
           
std::cout << a << " w linii: " << __LINE__ << ", a globalne a=" <<::a << std::endl;
       
}
       
std::cout << a << " w linii: " << __LINE__ << ", a globalne a=" <<::a << std::endl;
   
}
   
std::cout << a << " w linii: " << __LINE__ << ", a globalne a=" <<::a << std::endl;
   
   
f();
   
f();
   
f();
   
   
if( 0 == a )
   
{
       
std::string message = "a sie nie zmienilo";
   
}
   
else
   
{
       
std::string message = "a sie zmienilo";
   
}
   
//    std::cout << message << '\n'; // blad: message nie zadeklarowane!
}
Wydruk z programu:
Lokalna zmienna: 5
Globalna zmienna: 10
0 w linii: 20, a globalne a=0
1 w linii: 23, a globalne a=0
2 w linii: 26, a globalne a=0
1 w linii: 28, a globalne a=0
0 w linii: 30, a globalne a=0
1) Wywolanie funkcji: f, zmienna lokalna=44
2) Wywolanie funkcji: f, zmienna lokalna=44
3) Wywolanie funkcji: f, zmienna lokalna=44
Tak prawdę powiedziawszy mamy jeszcze pamięć stałą, oraz lokalną dla wątku.
Język Python3 zupełnie inaczej traktuje definiowanie zmiennych. W momencie przypisania jest tworzona nowa zmienna i ona istnieje o tej nazwie od tego momentu do końca programu (lub do jej usunięcia słówkiem
del
). Jeśli mamy zmienną globalną a wewnątrz funkcji przypiszemy jej wartość to zostanie utworzona zmienna lokalna (przysłanianie), która po wyjściu z funkcji przestanie zasłaniać zmienną globalną, aby tego uniknąć i faktycznie zmienić zmienną globalną musimy użyć słówka
global
. Analogiczna sytuacja jest jeśli mamy funkcje zdefiniowaną wewnątrz funkcji, wtedy możemy użyć słówka
nonlocal
.
W Python3 nie ma zmiennych statycznych funkcji, jeśli chcemy zasymulować takie zachowanie możemy albo utworzyć zmienną o zasięgu globalnym z wnętrza funkcji (słówko
global
), albo przypisać atrybut do funkcji. Jak to wygląda demonstruje poniższy kod:
Python
import sys

globalna_zmienna1 = 10
globalna_zmienna2 = 20
# global a
a = 0

def f():
   
if not hasattr(f, "atrybut_funkcji"):
       
f.atrybut_funkcji = 0  # it doesn't exist yet, so initialize it
   
f.atrybut_funkcji += 1

   
global globalna_zmienna
    if 'globalna_zmienna' not in globals():
       
globalna_zmienna = 0

   
a = 44  # inicjalizowane za każdym wejściem do funkcji

   
print(f"{f.atrybut_funkcji} vs {globalna_zmienna} wywolanie funkcji: {f.__name__}, zmienna lokalna={a}")

def use_nonlocal():
 
nielokalna_zmienna1 = "nielokalna1"
 
nielokalna_zmienna2 = "nielokalna2"
 
def use_nonlocal_impl():
   
nonlocal nielokalna_zmienna1
    nielokalna_zmienna1 = "nielokalna1_impl"
   
nielokalna_zmienna2 = "nielokalna2_impl"
 
use_nonlocal_impl()
 
return nielokalna_zmienna1, nielokalna_zmienna2


def init_variables():
   
print('inicjalizacja zmiennych: globalna_zmienna1, globalna_zmienna2, globalna_zmienna3:')
   
global globalna_zmienna1  # informujemy, ze jest to zmienna globalna
   
global globalna_zmienna3  # informujemy, ze jest to zmienna globalna
   
globalna_zmienna1 = 1  # zmieniamy zmienna globalna
   
globalna_zmienna2 = 2  # tworzymy zmienna lokalna
   
globalna_zmienna3 = 3  # ustawiamy wartosc zmiennej globalnej


if __name__ == "__main__":
   
f()
   
f()
   
f()
   
print(f"{a} w linii: {sys._getframe().f_lineno}")

   
print()

   
print(f"globalna_zmienna1={globalna_zmienna1}")
   
print(f"globalna_zmienna2={globalna_zmienna2}")

   
init_variables()

   
print(f"globalna_zmienna1={globalna_zmienna1}")
   
print(f"globalna_zmienna2={globalna_zmienna2}")
   
print(f"globalna_zmienna3={globalna_zmienna3}")

   
print()

   
if a == 0:
       
message = "a sie nie zmienilo"
   
else:
       
message = "a sie zmienilo"

   
# zmienna jest zadeklarowana, mimo iz wewnatrz wciecia:
   
print(f'Zmienna zadeklarowana wewnatrz wciecia: {message}')

   
# demonstracja uzycia slowka nonlocal:
   
print(f'Uzycie slowka nonlocal: {use_nonlocal()}')

   
print()

   
# mozna usunac recznie zmienna:
   
del a
    try:
       
print('Proba odwolania do zmiennej usunietej:', a)
   
except NameError:
       
print('!Zmienna a nie istnieje')
A oto wydruk:
1 vs 0 wywolanie funkcji: f, zmienna lokalna=44
2 vs 0 wywolanie funkcji: f, zmienna lokalna=44
3 vs 0 wywolanie funkcji: f, zmienna lokalna=44
0 w linii: 45

globalna_zmienna1=10
globalna_zmienna2=20
inicjalizacja zmiennych: globalna_zmienna1, globalna_zmienna2, globalna_zmienna3:
globalna_zmienna1=1
globalna_zmienna2=20
globalna_zmienna3=3

Zmienna zadeklarowana wewnatrz wciecia: a sie nie zmienilo
Uzycie slowka nonlocal: ('nielokalna1_impl', 'nielokalna2')

!Zmienna a nie istnieje

6. Wskaźniki i zarządzanie pamięcią

C++ python3
C++ umożliwia bezpośredni dostęp do pamięci po adresie. Jest to mechanizm niskopoziomowy, który jest ryzykowny, gdyż można odnieść się do pamięci, która nie należy do danego programu.
C/C++
#include <iostream>
int main()
{
   
int i = 4;
   
int * ptr1 = nullptr;
   
int * ptr2; // niezainicjalizowany wskaźnik
   
int * ptr3 = & i;
   
   
std::cout << i << std::endl; // wydruk: 4
   
std::cout << * ptr3 << std::endl; // wydruk: 4
   
   
* ptr3 = 6;
   
   
std::cout << i << std::endl; // wydruk: 6
   
std::cout << * ptr3 << std::endl; // wydruk: 6
   
    // *ptr1 = 3; // crash programu
    // *ptr2 = 3; // nie wiemy co się stanie - gdzię spiszemy wartość
   
   
ptr2 = ptr3;
   
* ptr2 = 7;
   
std::cout << i << endl; // wydruk: 7
   
std::cout << * ptr2 << std::endl; // wydruk: 7
   
std::cout << * ptr3 << std::endl; // wydruk: 7
}
Temat wskaźników jest na tylko trudny (dla osób, które mają z nim pierwszy raz do czynienia), że mocno zachęcam aby przeczytać na jego temat tam, gdzie jest to solidnie opisane czyli zapewne w dobrej książce do C++.
Tam nie ma mechanizmu wskaźników. Jednakże każde przypisanie jest współdzieleniem referencji do tego samego obiektu (nawet typu wbudowanego), referencje są przypisane do czasu przypisania nowego obiektu:
Python
if __name__ == '__main__':
   
a = 4
   
b = a  # nie odbywa sie tutaj kopiowanie
   
print(a, id(a), ';', b, id(b))
   
b = 5
   
print(a, id(a), ';', b, id(b))

   
# wydruk przykładowy:
    # 4 140439207675272 ; 4 140439207675272
    # 4 140439207675272 ; 5 140439207675304
W C++ jest możliwe dynamiczne zarządzanie pamięcią, czyli zarówno pojedyńczy obiekt a nawet typ wbudowany lub grupę obiektów (zwaną tablicą) możemy utworzyć w pamięci dynamicznej (zwanej stertą).
C/C++
#include <iostream>
int main()
{
   
int i = 4;
   
int * j = new int { 4 };
   
int * k = new int;
   
* k = 5;
   
std::cout << * j << std::endl; // wydruk: 4
   
std::cout << * k << std::endl; // wydruk: 5
   
delete j; // pamięć trzeba zwolnić ręcznie w C++
   
delete k;
}
Ręczne zarządzanie pamięcią nie jest zalacane, dlatego używa się inteligentnych wskaźników. Na ich temat trzeba poczytać w innym miejscu, ale po zrozumienia zagadnienia związanego z wskaźnikami.
W języku python wszystkie obiekty są tworzone dynamicznie, interpreter sam zarządza pamięcią.


7. Operatory:

Mimo pozorów występują tutaj rozbieżności, warto to przejrzeć szczegółowo.
C++ python3
C/C++
#include <iostream>
#include <cmath>  
// std::pow()
using namespace std;
// nawiasy są ze względu na priorytety operatorów
int main()
{
   
int i = 2, j = 3;
   
// arytmetyczne:
   
cout <<( i + j ) << endl;
   
cout <<( i - j ) << endl;
   
cout <<( i * j ) << endl;
   
cout <<( i / j ) << endl; // w python operator "//"
   
cout <<( i % j ) << endl;
   
   
// arytmetyczne, których nie ma w C++:
   
cout <<( static_cast < double >( i ) / j ) << endl; // w python "/"
   
cout << std::pow( i, j ) << endl; // w python "**"
   
    // inkrementacji i dekrementacji:
   
cout << ++i << endl;
   
cout << i++ << endl;
   
cout << --i << endl;
   
cout << i-- << endl;
   
   
// porównania:
   
cout <<( i > j ) << endl;
   
cout <<( i >= j ) << endl;
   
cout <<( i < j ) << endl;
   
cout <<( i <= j ) << endl;
   
cout <<( i == j ) << endl;
   
cout <<( i != j ) << endl;
   
   
// logiczne:
   
cout <<( i > j && j < i ) << endl;
   
cout <<( i > j || j < i ) << endl;
   
cout << !i << endl;
   
   
// bitowe:
   
i = 1, j = 2;
   
cout <<( i & j ) << endl;
   
cout <<( i | j ) << endl;
   
cout <<( i ^ j ) << endl; // xor
   
cout <<( ~i ) << endl;
   
cout <<( i << j ) << endl;
   
cout <<( i >> j ) << endl;
   
   
// rzutowania:
   
cout << static_cast < double >( i ) << endl;
   
cout << reinterpret_cast < char * >( & i ) << endl; // rzutowanie wskaźników
    /* jeszcze dostępne są operatory:
       dynamic_cast<...>(...), const_cast<...>(...) */
}
Python
if __name__ == '__main__':
   
i, j = 2, 3
   
# Arytmetyczne operacje:
   
print(i + j)
   
print(i - j)
   
print(i * j)
   
print(i // j)  # w C++ "/"
   
print(i % j)

   
# arytmetyczne, których nie ma w C++:
   
print(i / j)  # w C++ trzeba rzutować na typ zmiennoprzecinkowy
   
print(i ** j) # w C++ konieczna oddzielna funkcja

    # // inkrementacji i dekrementacji:
   
i += 1; print(i)  # ++i
   
print(i); i += 1 # i++
   
i -= 1; print(i)  # --i
   
print(i); i -= 1  # i--

    # Operatory porównania:
   
print(i > j)
   
print(i >= j)
   
print(i < j)
   
print(i <= j)
   
print(i == j)
   
print(i != j)

   
# Operatory logiczne:
   
print(i > j and j < i)
   
print(i > j or j < i)
   
print(not i)

   
# Bitowe operacje:
   
i, j = 1, 2
   
print(i & j)
   
print(i | j)
   
print(i ^ j)  # XOR
   
print(~i)  # NOT
   
print(i << j)  # Przesunięcie bitowe w lewo
   
print(i >> j)  # Przesunięcie bitowe w prawo

    # Rzutowania:
   
print(float(i))  # Rzutowanie na float
    # Rzutowanie dynamiczne nie jest dostępne w Pythonie, ponieważ Python jest językiem dynamicznie typowanym
    # Nie ma potrzeby korzystania z const_cast ani reinterpret_cast, ponieważ Python nie ma tych koncepcji
Jak widać występuje duża rozbieżność w operatorach rzutowania, osoby nie znające ich powinny poczytać na ich temat w zewnętrznym źródle np. dobrej książce.
Język python zawiera jeszcze operatory do operowania na listach
"kot" in ["ale", "ma", "kota"]
. Ich opis znajduje się w dalszej części artkułu.


8. Instrukcje sterujące:

C++ python3
C/C++
long variable = 123;
if( variable > 100 )
   
 cout << "variable is greater than 100" << endl;
else if( variable < 100 )
{
   
cout << "variable is less than 100" << endl;
   
if( variable < 0 )
       
 cout << "variable is negative" << endl;
   
}
else
   
 cout << "variable == 100" << endl;
Zastosowanie klamerek
{
 jest opcjonalne, bez nich brana jest tylko następna instrukcja, z nimi cała zawartość klamerek.
Od C++17 można zdefiniować zmienną w instrukcji warunkowej w następujący sposób:
if( long variable = 123; variable > 100 )
Python
variable = 123
if variable > 100:
   
print("variable is greater than 100")
elif variable < 100:
   
print("variable is less than 100")
   
if variable < 0:
       
print("variable is negative")
else:
   
print("variable == 100")
Nie stosuje się klamerek, jedynie wcięcia. Jest brane tyle instrukcji, ile jest wewnątrz wcięcia.
C/C++
char c = 'A';
switch( c )
{
case 'A':
   
cout << "A" << endl;
   
break;
case 'B':
   
cout << "B" << endl;
   
break;
case 'C':
   
cout << "C" << endl;
   
//break;
case 'C':
   
cout << "C" << endl;
default:
   
cout << "?" << endl;
};
Python
c = 'A'
match c:
   
case 'A':
       
print("A")
   
case 'B':
       
print("B")
   
case 'C':
       
print("C")
   
case 'C':
       
print("C")
   
case _:
       
print("?")
Powyższy kod wymaga Python 3.10, we wcześniejszych wersjach nie występuje
match
, zamiast tego można użyć
if-else
C/C++
for( int i = 0; i < 10; ++i )
   
 cout << i << ", ";

cout << endl;
Python
for i in range(10):
   
print(i, end=", ")
print()
C/C++
short s = 0;
while( s < 10 )
   
 cout <<( s++ ) << "\n";
Python
s = 0
while s < 10:
   
print(s)
   
s += 1
C/C++
long long ll = 41;
do
cout <<( ll++ ) << ", ";
while( ll < 10 );
Brak odpowiednika w pythonie, można to emulować:
Python
ll = 41
while True:
   
print(ll)
   
ll += 1
   
if not (ll < 10):
       
break
C/C++
int values[ ] = { 1, 2, 3, 4, 5 };
for( auto v: values )
   
 cout << v << ", ";
Python
values = [1, 2, 3, 4, 5]
for v in values:
   
print(v, end=", ")

9. Operowanie na tekście:

kryterium C++ python3
typ do obsługi tekstu obsługa tekstu nie jest wbudowana w język, za to jest elementem biblioteki standardowej
std::string
. Do jego użycia konieczny jest nagłówek
#include <string>
jest to typ wbudowany
str
obsługa znaków narodowych (polskie znaki) typ
std::string
 używa znaków z zakresu
char
, jest to zakres jedno-bajtowy, z założenia pozbawiony znaków narodowych (poza ASCII). Jednakże jeśli umieścimy polskie znaki np.
C/C++
std::string text = "Żółta łąka. ĘÓĄŚŁŻŹĆŃ ęóąśłżźćń. Zażółć gęślą jaźń";
std::cout << text << '\t' << text.size() << endl;
to zostaną one zakodowane jako większa liczba znaków, a jaka to już zależy od kodowania aktualnego pliku.
Aby polskie znaki działały w konsoli jej kodowanie musi być takie same jak kodowanie pliku źródłowego z kodem programu. Systemy Linuxowe i MacOS domyślnie koduje pliki i tekst w konsoli jako UFT-8, więc powyższy kod zadziała nam od strzała na tych systemach. Niestety na systemie Windows zapewne będziemy się musieli nagimnastykować aby uzyskać polskie znaki w konsoli. Kodowanie pliku źródłowego można sprawdzić w ustawieniach środowiska programistycznego (w razie czego mamy jeszcze Notepad++), kodowanie w konsoli można sprawdzić komendą
chcp
, przy pomocy tej komendy można zmienić kodowanie np.
chcp 65001
 ustawi kodowanie UTF-8 w konsoli, ale na starszych wersjach Windowsa zapewne będziemy musieli się trochę więcej nagimnastykować. Temat ten został szczegółowo omówiony w tutorialu https://miroslawzelent.pl/kurs-c++/polskie-znaki-konsola-terminal-windows-linux-macos/
Domyślnym kodowaniem kodu źródłowego Pythona jest UTF-8. Możesz użyć prawie dowolnego kodowania, jeśli zadeklarujesz używane kodowanie. Odbywa się to poprzez dodanie specjalnego komentarza w pierwszej lub drugiej linii pliku źródłowego poniższej deklaracji:
Python
# -*- coding: UTF-8 -*-
Jeśli nie dołączysz takiego komentarza, domyślnym zastosowanym kodowaniem będzie UTF-8. Komentarz umieszcza się w drugiej lini, gdy pierwszą jest deklaracja interpretera języka python np.:
Python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
Kodowanie w python opisano szczegółowo na stronie https://docs.python.org/3/howto/unicode.html
przykłady użycia:
C/C++
#include <iostream>
#include <string>
using namespace std;

int main()
{
   
// Inicjalizacja i konkatenacja ciągów znaków
   
string s1 = "Hello";
   
string s2( "C++" );
   
   
string s3 = s1 + s2; // Konkatenacja s1 i s2
   
string s4 = s1 + ' ' + s2; // Konkatenacja s1, spacji i s2
   
    // Wyświetlenie wyników konkatenacji
   
cout << "s3: " << s3 << endl; // Wynik: "HelloC++"
   
cout << "s4: " << s4 << endl; // Wynik: "Hello C++"
   
    // Dostęp do znaków w ciągu znaków
   
cout << "Pierwszy znak s2: " << s2[ 0 ] << endl; // Wynik: "C"
   
    // Rozmiar ciągu znaków
   
cout << "Rozmiar s3: " << s3.size() << endl; // Wynik: 8 (liczba znaków w "HelloC++")
   
    // Wyszukiwanie podciągu
   
if( auto position = s4.find( "C++" ); position != string::npos )
   
{
       
cout << "Znaleziono 'C++' w s4 na pozycji: " << position << endl;
   
}
   
else
   
{
       
cout << "Nie znaleziono 'C++' w s4" << endl;
   
}
   
   
cout << "Pierwszy znak: " << s2.front() << endl; // Wynik: "H"
   
cout << "Ostatni znak: " << s2.back() << endl; // Wynik: "!"
   
   
cout << "Czy ciąg jest pusty? " <<( s2.empty() ? "Tak"
       
: "Nie" ) << endl; // Wynik: "Nie"
   
   
string substring = "++";
   
cout << "Czy ciąg zawiera '" << substring << "'? "
   
<<( s4.contains( substring ) ? "Tak"
       
: "Nie" ) << endl; // Wynik: "Tak"
   
   
string prefix = "He";
   
cout << "Czy ciąg rozpoczyna się od '" << prefix << "'? "
   
<<( s3.starts_with( prefix ) ? "Tak"
       
: "Nie" ) << endl; // Wynik: "Tak"
   
    // ends_with() - sprawdza, czy ciąg kończy się określonym sufiksem
   
string suffix = "++";
   
cout << "Czy ciąg kończy się '" << suffix << "'? "
   
<<( s4.ends_with( suffix ) ? "Tak"
       
: "Nie" ) << endl; // Wynik: "Tak"
   
    // replace() - zastępuje podciąg innym ciągiem znaków
   
string replacedString = s4;
   
string replacement = "Best Language ";
   
replacedString.replace( 6, 0, replacement ); // zamien od pozycji 6, ilosc znakow do zamiany: 0
   
cout << "Zamiana '" << s1 << "' na '" << replacedString << "': " << replacedString << endl; // Wynik: "Hello Best Language C++"
   
    // substr() - wybiera podciąg z ciągu
   
string sub = s4.substr( 6, 3 ); // Wybieranie od indeksu 7 (W) kolejnych 5 znaków
   
cout << "Podciąg od indeksu 7 o długości 5 znaków: " << sub << endl; // Wynik: "World"
}
Pewne z użytych funkcji zostały dodane do C++ w nowych wersjach C++, są to:
  • starts_with
     w C++20
  • ends_with
    w C++20
  • contains
    w C++23
w starszych wersjach mogą nie działać.
Python
if __name__ == "__main__":
   
# Inicjalizacja
   
s1 = "Hello"
   
s2 = "Python"

   
s3 = s1 + s2  # Konkatenacja s1 i s2
   
s4 = s1 + ' ' + s2  # Konkatenacja s1, spacji i s2

    # Wyświetlenie wyników konkatenacji
   
print("s3:", s3)  # Wynik: "HelloPython"
   
print("s4:", s4)  # Wynik: "Hello Python"

    # Dostęp do znaków w ciągu znaków
   
print("Pierwszy znak s2:", s2[0])  # Wynik: "P"

    # Rozmiar ciągu znaków
   
print("Rozmiar s3:", len(s3))  # Wynik: 11 (liczba znaków w "HelloPython")

    # Wyszukiwanie podciągu
   
if "Python" in s4:
       
position = s4.index("Python")
       
print("Znaleziono 'Python' w s4 na pozycji:", position)
   
else:
       
print("Nie znaleziono 'Python' w s4")

   
print("Pierwszy znak:", s2[0])  # Wynik: "P"
   
print("Ostatni znak:", s2[-1])  # Wynik: "n"

   
print("Czy ciąg jest pusty?", "Tak" if not s2 else "Nie")  # Wynik: "Nie"

   
substring = "Py"
   
print("Czy ciąg zawiera '", substring, "'?", "Tak" if substring in s4 else "Nie")  # Wynik: "Tak"

   
prefix = "He"
   
print("Czy ciąg rozpoczyna się od '", prefix, "'?", "Tak" if s3.startswith(prefix) else "Nie")  # Wynik: "Nie"

   
suffix = "on"
   
print("Czy ciąg kończy się '", suffix, "'?", "Tak" if s4.endswith(suffix) else "Nie")  # Wynik: "Tak"

    # replace() - zastępuje podciąg innym ciągiem znaków
   
replacedString = s4.replace("Python", "Best Language")
   
print("Zamiana 'Python' na 'Best Language':", replacedString)  # Wynik: "Hello Best Language"

    # substr() - wybiera podciąg z ciągu
   
start_index = 6
   
length = 5
   
sub = s4[start_index:start_index + length]
   
print("Podciąg od indeksu", start_index, "o długości", length, "znaków:", sub)  # Wynik: "Python"
ciekawy przypadek - metoda
replace
std::string::replace
 zamienia jedynie pierwsze wystąpienie
str.replace
zamienia wszystkie wystąpienia
konwersja między tekstem a liczbami
C/C++
#include <iostream>
#include <string>
using namespace std;
using namespace std::literals::string_literals; // ""s

int main()
{
   
cout << to_string( 112 ) << ' ' << to_string( 3.14f ) << ' '
   
<< to_string( 2.71 ) << ' ' << to_string( 10'000'000'000LL ) << ' ' << endl;
   
cout << stoi( "112"s ) << ' ' << stof( "3.14"s ) << ' '
   
<< stod( "2.71"s ) << ' ' << stoll( "2304"s ) << endl;
}
Dla typu użytkownika można zdefiniować operator konwersji na
std::string

Python
if __name__ == "__main__":
   
print(str(112), str(3.14), end=' ')
   
print(str(2.71), str(10_000_000_000))

   
print(int("112"), float("3.14"), end=' ')
   
print(float("2.71"), int("2304"))
Dla typu użytkownika można zdefiniować metodę
__str__
 lub
__repr__
 zamieniającą obiekt użytkownika na tekst.
zmiana wielkości liter nie jest to wbudowane w
std::string
, trzeba użyć tricku:
C/C++
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>  
// std::toupper
using namespace std;

int main()
{
   
string text { "Musicie od siebie wymagac, nawet gdyby inni od Was nie wymagali!" };
   
ranges::transform( text, begin( text ), static_cast < int( * )( int ) >(::toupper ) );
   
cout << text << endl;
   
ranges::transform( text, begin( text ), static_cast < int( * )( int ) >(::tolower ) );
   
cout << text << endl;
}
wbudowane w język:
Python
if __name__ == "__main__":
   
text = "Musicie od siebie wymagac, nawet gdyby inni od Was nie wymagali!"
   
text_upper = text.upper()
   
print(text_upper)

   
text_lower = text.lower()
   
print(text_lower)
użycie funkcji do podziału tekstu wg słowa,
złączenia słów wg tekstu,
przycięcie białych znaków
C++ nie posiada wbudowanych funkcji do tego celu. Można to obejść używając biblioteki boost, więcej informacji w artykule » Inne artykułyPraca z tekstem przy użyciu dostępnych narzędzi w C++ artykuł, przykładowy kod używający biblioteki:
C/C++
#include <iostream>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>  
// Wymagana biblioteka Boost

int main() {
   
std::string sentence = "To jest przykładowe zdanie do podziału na słowa.";
   
std::vector < std::string > words;
   
boost::split( words, sentence, boost::is_any_of( " " ) ); // Dzielenie na słowa według spacji
   
   
std::cout << "\nOryginalne zdanie: " << sentence << std::endl;
   
std::cout << "Podzielone słowa:";
   
for( const std::string & word: words )
       
 std::cout << " " << word;
   
   
std::cout << std::endl;
   
   
// Strip
   
std::string text_with_spaces = "   Tekst z dodatkowymi spacjami   ";
   
boost::trim( text_with_spaces ); // Usunięcie zbędnych spacji z początku i końca
   
   
std::cout << "\nOryginalny tekst z spacjami: " << text_with_spaces << std::endl;
   
   
// Join
   
std::vector < std::string > list_of_words = { "To", "jest", "lista", "słów" };
   
std::string joined_text = boost::algorithm::join( list_of_words, " " ); // Połączenie listy słów w jeden tekst
   
   
std::cout << "Lista słów:";
   
for( const std::string & word: list_of_words )
       
 std::cout << " " << word;
   
   
std::cout << "\nPołączony tekst: " << joined_text << std::endl;
}
Python
sentence = "To jest przykładowe zdanie do podziału na słowa."
words = sentence.split()  # Dzielenie na słowa (domyślnie według spacji)

print("\nOryginalne zdanie:", sentence)
print("Podzielone słowa:", words)

# Strip
text_with_spaces = "   Tekst z dodatkowymi spacjami   "
stripped_text = text_with_spaces.strip()  # Usunięcie zbędnych spacji z początku i końca

print("\nOryginalny tekst z spacjami:", text_with_spaces)
print("Tekst po usunięciu zbędnych spacji:", stripped_text)

# Join
list_of_words = ["To", "jest", "lista", "słów"]
joined_text = " ".join(list_of_words)  # Połączenie listy słów w jeden tekst

print("\nLista słów:", list_of_words)
print("Połączony tekst:", joined_text)
klasyfikacja tekstu (np. czy zawiera tylko liczby) Można tylko sprawdzić pojedyńczy znak, zatem aby sprawdzić cały tekst należy użyć pewnego tricku:
C/C++
#include <iostream>
#include <algorithm>
#include <cctype>  
// Biblioteka do funkcji isdigit i isspace
using namespace std;

int main() {
   
// Sprawdzanie czy znak jest cyfrą (isdigit)
   
string characters = "123456";
   
if( std::ranges::all_of( characters,[ ]( auto c ) { return std::isdigit( c ); } ) ) ) {
       
cout << characters << " to cyfra" << endl;
   
} else {
       
cout << characters << " nie jest cyfrą" << endl;
   
}
   
   
// Sprawdzanie czy znak jest białym znakiem (isspace)
   
characters = " \t\n";
   
if( std::ranges::all_of( characters,[ ]( auto c ) { return std::isspace( c ); } ) ) ) {
       
cout << "'" << characters << "' to biały znak" << endl;
   
} else {
       
cout << "'" << characters << "' nie jest białym znakiem" << endl;
   
}
}
Python
# Sprawdzanie czy znak jest cyfrą (isdigit)
characters = "123456"
if characters.isdigit():
   
print(f"'{characters}' to cyfra")
else:
   
print(f"'{characters}' nie jest cyfrą")

# Sprawdzanie czy znak jest białym znakiem (isspace)
characters = " \t\n"
if characters.isspace():
   
print(f"'{characters}' to biały znak")
else:
   
print(f"'{characters}' nie jest białym znakiem")
wygodne formatowanie tekstu zawierającego zawartość innych zmiennych
C/C++
#include <iostream>
#include <format>  
// std::format
using namespace std;

int main() {
   
string name = "John";
   
int age = 30;
   
double number = 12345.6789;
   
   
string formatted = std::format( "Hello, {}! You are {} years old.", name, age );
   
std::string formatted_number = std::format( "{:.1f}", number ); // liczby do 1 miejsc po przecinku
   
   
cout << formatted << endl; // wydruk: Hello, John! You are 30 years old.
   
cout << formatted_number << endl; // wydruk: 12345.7
}
std::format
zostało dodane do C++ w wersji C++20. Jeśli potrzebujesz tej funkcjonalności we wcześniejszych wersjach możesz użyć biblioteki fmt.
W C++23 została dodana funkcja znana dotychczas z Javy:
std::println()
, której można wygodnie używać do formatowania tak jak przy użyciu
std::format
.
Python
if __name__ == '__main__':
   
name = "John"
   
age = 30
   
number = 12345.6789

   
formatted = f"Hello, {name}! You are {age} years old."
   
formatted_number = "{:.1f}".format(number)  # liczby do 1 miejsca po przecinku

   
print(formatted)  # Hello, John! You are 30 years old.
   
print(formatted_number)  # 12345.7

10. Podstawowe kontenery na dane: tablice (C++) vs listy i tuple (Python):

Jest to aspekt, który ciężko jest porównać w takiej formie jak do tej pory.

kryterium C++ python3
podstawowa różnica W C++ mamy lepszą kontrolę nad pamięcią, dlatego możemy tworzyć tablice o rozmiarze znanym w trakcie kompilacji, jak również przy pomocy wskaźników alokować (a potem pamiętać o jej zwolnieniu) pamięć wg potrzeb. Istotne jest, że tablice zawierają elementy tego samego typu. Co prawda możemy mieć tablice specjalnego typu
std::any
 ale jest to rzadko praktykowane.
Nie mamy aż takich możliwości zarządzania pamięcią.
podstawowa struktura danych do przechowywania wielu elementów
std::vector
, który jest elementem biblioteki standardowej, ale nie jest wbudowany w język:
C/C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
   
// Tworzenie pustego wektora liczb całkowitych
   
vector numbers = { 6, 24, 496 }; // alternatynie można podać typ: vector<int> numbers
   
    // Dodawanie elementów do wektora
   
numbers.push_back( 8124 );
   
   
// Rozmiar wektora
   
cout << "Rozmiar wektora: " << numbers.size() << endl;
   
   
// Dostęp do elementów wektora (w komentarzu alternatywny sposób)
   
cout << "Pierwszy element: " << numbers[ 0 ] << endl; // numbers.front()
   
cout << "Ostatni element: " << numbers[ numbers.size() - 1 ] << endl; // numbers.back()
   
    // Modyfikacja elementu wektora
   
numbers[ 1 ] = 28;
   
   
// Iterowanie przez wektor
   
cout << "Elementy wektora:";
   
for( auto i: numbers ) {
       
cout << " " << i;
   
}
   
cout << endl;
   
   
// Usuwanie ostatniego elementu
   
numbers.pop_back();
   
   
// Czyszczenie zawartosci wectora
   
numbers.clear();
   
   
// Sprawdzenie, czy wektor jest pusty
   
if( numbers.empty() )
       
 cout << "Wektor jest pusty." << endl;
   
else
       
 cout << "Wektor nie jest pusty." << endl;
   
}
Podstawową strukturą jest lista, która  nie jest implementowana jako lista tylko tablica
Python
if __name__ == '__main__':
   
# Tworzenie pustej listy liczb całkowitych
   
numbers = [6, 24, 496]

   
# Dodawanie elementu do listy
   
numbers.append(8124)

   
# Rozmiar listy
   
print("Rozmiar listy:", len(numbers))

   
# Dostęp do elementów listy
   
print("Pierwszy element:", numbers[0])
   
print("Ostatni element:", numbers[-1])

   
# Modyfikacja elementu listy
   
numbers[1] = 28

   
# Iterowanie przez listę
   
print("Elementy listy:", end=" ")
   
for i in numbers:
       
print(i, end=" ")
   
print()

   
# Usuwanie ostatniego elementu
   
numbers.pop()

   
# Czyszczenie zawartości listy
   
numbers.clear()

   
# Sprawdzenie, czy lista jest pusta
   
if not numbers:
       
print("Lista jest pusta.")
   
else:
       
print("Lista nie jest pusta.")
struktura danych o stałym rozmiarze do przechowywania wielu elementów W C++ można używać zwykłych tablic znanych z języka C, natomiast zalecanym jest używanie
std::array
. Jest to opakowanie na tablicę o stałym rozmiarze, czyli nie można elementów dodawać i usuwać, ale można je zmieniać (chyba, że użyjemy słówka
const
), przykładowo:
C/C++
#include <iostream>
#include <array>
using namespace std;

int main() {
   
// Tworzenie pustej tablicy liczb całkowitych
   
array numbers = { 6, 24, 496 };
   
// alternatynie do powyzszego (podajac jawnie typ i rozmiar) array<int, 3> numbers;
   
const array numbers2 = numbers; // kopia
   
    // nie mozna zmieniac liczby elementow:
    // numbers.push_back(8124); // - nie można dodawać elementów
    // numbers.pop_back();
    // numbers.clear();
   
numbers[ 1 ] = 28; // mozna modyfikowac
    // numbers2[1] = 28; - tak nie mozna, bo to stala
   
   
cout << "Rozmiar tablicy: " << numbers.size() << endl;
   
   
cout << "Pierwszy element: " << numbers[ 0 ] << endl; // alternatywnie numbers.front()
   
cout << "Ostatni element: " << numbers[ numbers.size() - 1 ] << endl; // alternatywnie numbers.back()
   
    // Iterowanie przez tablice
   
cout << "Elementy tablicy:";
   
for( auto i: numbers ) {
       
cout << " " << i;
   
}
   
cout << endl;
   
   
if( numbers.empty() )
       
 cout << "Tablica jest pusta." << endl;
   
else
       
 cout << "Tablica nie jest pusta." << endl;
   
//    numbers = {1, 2, 3, 4}; - nie mozna zwiekszyc liczby elementow
   
numbers = { 1, 3, 5 }; // mozna przypisac tyle samo elementow
   
numbers = { 44 }; // lub mniej, reszta bedzie zerami
}
Występuje tupla, która ma operacje jak lista, z tymże nie można w niej dodać/usunąć elementów ani ich zmienić, przykładowy kod:
Python
if __name__ == '__main__':
   
# tworzenie tupli - uzywamy nawiasow OKRAGLYCH!
   
numbers = (6, 24, 496)

   
# numbers[0] = 5 # nie dziala
    # numbers.append(1024) # nie dziala

   
print("Rozmiar tupli:", len(numbers))

   
print("Pierwszy element:", numbers[0])
   
print("Ostatni element:", numbers[-1])

   
print("Elementy tupli:", end=" ")
   
for i in numbers:
       
print(i, end=" ")
   
print()

   
if not numbers:
       
print("Tupla jest pusta.")
   
else:
       
print("Tupla nie jest pusta.")

   
numbers = (2, 4, 6, 8, 10)  # mozna przypisac cokolwiek
W języku Python3 występuje struktura danych
array
, która przechowuje elementy tego samego typu:
Python
import array

my_array = array.array('i', [1, 2, 3, 4, 5])  # 'i' oznacza typ danych (liczby całkowite)
print(len(my_array))
my_array[3] = 44  # mozna zmieniac wartosci
# my_array[1] = "nie zadziala"
przechowywanie elementów różnych typów C++ jest językiem o silnym typowaniu. Wskutek tego przechowywanie elementów różnych typów nie jest wspierane. Aby przypisać konkretnej zmiennej wartości różnych typów stosuje się
str::any
, które można umieścić w kontenerze np.:
C/C++
#include <iostream>
#include <vector>
#include <any>

int main() {
   
// Tworzenie wektora z różnymi typami elementów za pomocą std::any
   
std::vector < std::any > mixedVector;
   
   
// Dodawanie różnych typów elementów do wektora
   
mixedVector.push_back( 42 ); // int
   
mixedVector.push_back( 3.14 ); // double
   
mixedVector.push_back( std::string( "Hello" ) ); // std::string
   
    // Iterowanie przez wektor i wypisywanie zawartości
   
for( const auto & item: mixedVector )
   
if( item.type() == typeid( int ) )
       
 std::cout << "int: " << std::any_cast < int >( item ) << std::endl;
   
else if( item.type() == typeid( double ) )
       
 std::cout << "double: " << std::any_cast < double >( item ) << std::endl;
   
else if( item.type() == typeid( std::string ) )
       
 std::cout << "std::string: " << std::any_cast < std::string >( item ) << std::endl;
   
}
Jednakże raczej się nie praktykuje takiego postępowania w C++ (jak ktoś takie rzeczy chce zrobić to wybiera inny język programowania, albo musi się douczyć).
Python nie ma silnego typowania, więc można przypisać dowolne typy:
Python
if __name__ == '__main__':
   
# Tworzenie listy z różnymi typami elementów za pomocą Any
   
mixed_list = []

   
# Dodawanie różnych typów elementów do listy
   
mixed_list.append(42)                    # int
   
mixed_list.append(3.14)                  # float
   
mixed_list.append("Hello")               # str

    # Iterowanie przez listę i wypisywanie zawartości
   
for item in mixed_list:
       
print(f'{type(item).__name__}: {item}')
wyjście poza zakres Niezdefiniowane zachowanie programu, czyli może się program wykonywać nadal, a błąd nie zostanie od razu zauważony, może też program się wykrzaczyć/wywalić zwane segmentation fault. Dlatego w C++ zamiast używając
operator[ ]
 można zastosować metodę
at()
, która w razie wyjścia poza zakres tablicy wyrzuci wyjątek, przykładowo:
C/C++
#include <iostream>
#include <vector>

int main() {
   
std::vector < int > numbers = { 1, 2, 3 };
   
   
// Próba dostępu do elementu poza zakresem
    // numbers[3] = 55; --- wykonanie operacji jest możliwe, ale spowoduje niezdefiniowane zachowanie
   
try {
       
auto value = numbers.at( numbers.size() ); // Wywoła wyjątek std::out_of_range
       
std::cout << "Wartość: " << value << std::endl;
   
} catch( const std::out_of_range & e ) {
       
std::cerr << "Błąd: Przekroczono zakres wektora." << std::endl;
   
}
}
Język python zawsze sprawdza i w razie czego wyrzuci wyjątek:
Python
numbers = [1, 2, 3]

# Próba dostępu do elementu poza zakresem
try:
   
value = numbers[len(numbers)]  # Wywoła wyjątek IndexError
   
print(f'Wartość: {value}')
except IndexError:
   
print('Błąd: Przekroczono zakres listy.')

11. Inne struktury danych (inne kontenery na dane)


Często używanymi kontenerami są słowniki i zbiory, które mają swoje odpowiedniki w porównywanych językach, poza tym jest wiele kontenerów, które można uznawać za równoważne. Przykładowo C++ ma około 20 kontenerów standardowych, z kolei Python3 ma kilka kontenerów wbudowanych w język, oraz wiele innych w ramach modułu modułu collections. W poniższej tabeli zastosowałem porównanie odpowiadających sobie kontenerów demonstrując przykłady użycia jedynie dla najpopularniejszych:

kontener C++ python3
słownik/mapa: kontener, który kluczom przyporządkowuje wartości
C/C++
#include <iostream>
#include <map>

int main() {
   
// Tworzenie mapy int -> std::string
   
std::map < int, std::string > myMap = { { 1, "Jeden" }, { 2, "Dwa" } };
   
   
// Dodawanie elementów do mapy
   
myMap[ 3 ] = "Trzy";
   
myMap[ 4 ] = "Cztery";
   
   
// Sprawdzanie, czy klucz 2 istnieje w mapie
   
int keyToCheck = 2;
   
if( myMap.contains( keyToCheck ) )
       
 std::cout << "Klucz " << keyToCheck << " jest w mapie." << std::endl;
   
else
       
 std::cout << "Klucz " << keyToCheck << " nie istnieje w mapie." << std::endl;
   
   
// Pobieranie wartości po kluczu (UWAGA, w razie jej braku UTWORZY!)
   
std::cout << "Wartość dla klucza 2: " << myMap[ 2 ] << std::endl;
   
   
// Iterowanie przez mapę
   
std::cout << "Elementy mapy:" << std::endl;
   
for( const auto &[ key, value ]: myMap )
       
 std::cout << key << ": " << value << std::endl;
   
}
W powyższym kodzie użyłem metody
contains
, która została wprowadzona dopiero w C++23, we wcześniejszych wersjach trzeba by sprawdzić to tak:
if( myMap.find( keyToCheck ) != myMap.end() )
W C++ mamy również dostępny kontener
std::multimap
 (nagłówek
#include <map>
), który umożliwia trzymanie par klucz-wartość, ale z możliwością powtórzeń par o tym samym kluczu.
Porównując C++ z Python jeśli chodzi o mapę należy dodać, że wg StackOverflow słowniki w języku Python są zaimplementowane ze stałym średnim dostępem do składowych, czyli tak naprawdę odpowiednikiem słownika dla języka Python są
std::unordered_map
 oraz  
std::unordered_multimap
dostępne po załączeniu
#include <unordered_map>
, jednakże ich użycie jest znacznie trudniejsze.
Python
# Tworzenie słownika int -> str
myMap = {1: "Jeden", 2: "Dwa"}

# Dodawanie elementów do słownika
myMap[3] = "Trzy"
myMap[4] = "Cztery"

# Sprawdzanie, czy klucz 2 istnieje w słowniku
keyToCheck = 2
if keyToCheck in myMap:
   
print(f"Klucz {keyToCheck} jest w słowniku.")
else:
   
print(f"Klucz {keyToCheck} nie istnieje w słowniku.")

# Pobieranie wartości po kluczu
print("Wartość dla klucza 2:", myMap.get(2, "Klucz nie istnieje")) # w razie braku zwróci "Klucz ..."
print("Wartość dla klucza 3:", myMap.get(3)) # w razie braku wyrzuci wyjątek

# Iterowanie przez słownik
print("Elementy słownika:")
for key, value in myMap.items():
   
print(f"{key}: {value}")
W języku Python istnieją też ciekawe wersje słowników:
from collections import defaultdict
 - który w razie brakującej wartości dla danego klucza ją tworzy, a także
from collections import OrderedDict
- która trzyma kolejność dodania elementów.
zbiór
C/C++
#include <iostream>
#include <unordered_set>

int main() {
   
// Tworzenie zbioru
   
std::unordered_set < int > mySet = { 1, 2, 3, 5 };
   
   
// Dodawanie elementów do zbioru
   
mySet.insert( 8 );
   
mySet.insert( 1 ); // element juz jest, nic sie nie stanie
   
auto[ position, wasSuccess ] = mySet.insert( 2 );
   
std::cout << "Dodawanie elementu " << 2 << " " <<( wasSuccess ? "udane": "nieudane" ) << std::endl;
   
   
// Sprawdzanie, czy element istnieje w zbiorze
   
int elementToCheck = 2;
   
std::cout << "Element " << elementToCheck << " w zbiorze: "
   
<<( mySet.contains( elementToCheck ) ? "istnieje"
       
: "nie istnieje" ) << std::endl;
   
   
   
// Iterowanie przez zbiór
   
std::cout << "Elementy zbioru:";
   
for( const auto & element: mySet )
       
 std::cout << " " << element;
   
   
std::cout << std::endl;
}
W powyższym kodzie użyłem metody
contains
, która została wprowadzona dopiero w C++23, we wcześniejszych wersjach trzeba by sprawdzić to tak:
mySet.find( keyToCheck ) != mySet.end()
W C++ mamy również dostępny kontener
std::unordered_multiset
 (nagłówek
#include <unordered_set>
), który umożliwia trzymanie wielu elementów o tej samej wartości klucza.
C++ posiada również kontenery o analogicznym interfejsie do powyższych:
std::set
 oraz
std::multiset
 dostępne po załączeniu
#include <set>
, które różnią się implementacją dającą dostęp do składowych w czasie logarytmicznym.
Python
# Tworzenie zbioru
my_set = {1, 2, 3, 5}

# Dodawanie elementów do zbioru
my_set.add(8)
my_set.add(1)  # Element już istnieje, nic się nie stanie
was_success = my_set.add(2)
print(f"Dodawanie elementu 2 {'udane' if was_success else 'nieudane'}")

# Sprawdzanie, czy element istnieje w zbiorze
element_to_check = 2
print(f"Element {element_to_check} w zbiorze: {'istnieje' if element_to_check in my_set else 'nie istnieje'}")

# Iterowanie przez zbiór
print("Elementy zbioru:", end=" ")
for element in my_set:
   
print(element, end=" ")
print()
kolejka o dwóch końcach, w której można dodawać z obydwu stron w sposób wydajny
std::deque
dostępne w
#include <deque>
dostępne w
from collections import deque
wielozbiór (brak pełnych odpowiedników między językami) W języku C++ mamy dostępny wielozbiór
std::multiset
, dostępny w nagłówku
#include <set>
, podobny jak
std::set
 ale zezawalający na powtórzenia elementów
Kontener
from collections importCounter
, który pozwala na zliczanie liczby wystąpień elementów o takiej samej wartości.
Poza powyższymi kontenerami warto uwzględnić kontenery dostępne tylko w jednym z języków, bez bezpośrednich odpowiedników w drugim języku, dostępne w standardzie obydwu języków (czyli bez konieczności doinstalowywania czegokolwiek):
C++ python3
Listy:
std::list
 i
std::forward_list
.
Adaptery kontenerów:
std::stack
,
std::queue
,
std::priority_queue
, co prawda są one adapterami, więc można je zaimplementować przez
from collections import deque
, a kolejkę priorytetową za pomocą
import heapq
from collections import namedtuple
 jest to szybki sposób na tworzenie klas (o nich później)
from collections import ChainMap
 - typ tworzący jeden widok do wielu słowników.
from collections import UserDict, UserList, UserString
 - są to klasy bazowe, gdy chcemy tworzyć własne odpowiednio: słowniki, listy, obsługę tekstu

12. Funkcje


kontener C++ python3
deklaracja i definicja funkcji funkcja musi być co najmniej zadeklarowana przed jej użyciem. Robi się to przykadowo tak:
void print( int number );
. Przykład deklaracji i użycia:
C/C++
#include <iostream>

// Deklaracja funkcji
void print( double value );

int main() {
   
print( 3.14 );
}

void print( double value )
{
   
std::cout << "--->>" << value << "<<---" << std::endl;
}
funkcji się nie deklaruje, tylko definiuje. Aby jej użyć powinna  być w załączonym module, lub być zdefiniowana w tym samym pliku. Nie musi to być przed jej użyciem ale w tej sytuacji musi interpreter "zauważyć jej definicje", co widać w poniższym przykładzie:
Python
def print_text(value):
   
# 3. ponizsza funkcja jest znana bo interpreter doszedl do konca pliku
   
print_text_impl("--->>" + value + "<<---")

def print_text_impl(value):
   
print("--->>", value, "<<---")

if __name__ == "__main__":
   
# 1. aby znalezc to miejsce interpreter musial przeczytac caly plik
    # 2. tym sposobem wszystkie powyzsze definicje sa znane
   
print_text(3.14)
definicja funkcji w C++ definiujemy funkcje przez jej nazwę, typ zwracany i argumenty z podaniem typu, przykładowo:
C/C++
#include <iostream>

// Deklaracja funkcji
int add( int a, int b );

int main() {
   
// Wywołanie funkcji
   
int result = add( 5, 3 );
   
std::cout << "Wynik dodawania: " << result << std::endl;
}

// Definicja funkcji
int add( int a, int b ) {
   
return a + b;
}
Funkcje najczęściej się deklaruje w jednym pliku, a w innym je definiuje. Plik z deklaracjami się załącza w miejscach gdzie funkcje są wywoływane, natomiast same definicje można dostarczyć w formie skompilowanej.
Typy argumentów są wiążące, ale można użyć tzw. szablonów, aby generować funkcje dla różnych typów argumentów. W C++20 została wprowadzona skrócona forma szablonów np.:
C/C++
#include <iostream>

// Deklaracja szablonu funkcji

// dla C++20
void print( auto value );

// dla starszych wersji C++
template < typename T >
void print_old( T value ) { print( value ); }

int main() {
   
print( 3.14 );
   
print( std::string( "Text" ) );
   
print( 44u );
}

void print( auto value )
{
   
std::cout << "--->>" << value << "<<---" << std::endl;
}
definicje funkcji się zaczyna słówkiem kluczowym
def
 następnie nazwa funkcji, argumenty i dwukropek. Nie trzeba precyzować ani typów argumentów, ani typu zwracanego aczkolwiek można dla czytelności (program się nie wywali z powodu podania innego typu). Przykład:
Python
# Definicja funkcji
def add(a, b):
   
return a + b

# deklarujemy zarówno typy argumentów, jak i typ zwracany
def multiply(a: float, b: float) -> float:
   
return a * b

# Wywołanie funkcji
print("Wynik dodawania:", add(5, 3))
print("Wynik mnozenia:", multiply(5, 3))
wartości domyślne i ich przypisywanie można używać domyślnych wartości argumentów, jednakże musi się to odbywać od końca argumentów. Korzystając z argumentów nie można wskazać który argument się chce przypisać ręcznie, trzeba iść od lewej do prawej, przykładowo:
C/C++
#include <iostream>

// Funkcja z trzema domyślnymi argumentami
void exampleFunc( int a = 1, int b = 2, int c = 3 ) {
   
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}

int main() {
   
exampleFunc(); // Poprawne - wszystkie argumenty zostaną ustawione na domyślne wartości
   
exampleFunc( 5 ); // Poprawne - a = 5, reszta domyślna
   
exampleFunc( 5, 10 ); // Poprawne - a = 5, b = 10, c domyślne
    // exampleFunc(5, , 15);  // Błąd kompilacji - nie można pominąć środkowego argumentu
}
można używać domyślnych wartości argumentów, jednakże musi się to odbywać od końca argumentów. Korzystając z argumentów można wskazać który argument chce się przypisać ręcznie, wtedy nie trzeba iść od lewej do prawej wręcz można przypisać w innej kolejności niż w definicji funkcji:
Python
# Funkcja z trzema domyślnymi argumentami
def example_func(a=1, b=2, c=3):
   
print(f"a: {a}, b: {b}, c: {c}")

# Funkcja z trzema domyślnymi argumentami, z tymże argumenty b i c można podać tylko po nazwie:
def example_func2(a, *, b=4, c=5): # argumenty b i c można podawać tylko po nazwie
   
print(f"a: {a}, b: {b}, c: {c}")

# Wywołanie funkcji
example_func()       # Poprawne - wszystkie argumenty zostaną ustawione na domyślne wartości
example_func(5)      # Poprawne - a = 5, reszta domyślna
example_func(5, 10)  # Poprawne - a = 5, b = 10, c domyślne

# Możliwość pominięcia argumentu 'b' i podania wartości tylko dla 'a' i 'c' (w Python to jest możliwe)
example_func(5, c=15)  # a = 5, b domyślne, c = 15

# Możliwość podania argumentów w dowolnej kolejności za pomocą nazw (keyword arguments)
example_func(c=7, a=2)  # a = 2, b domyślne, c = 7


# example_func2(5, 3) # - nie mozna
example_func2(5, b=3)  # ok, podajemy po nazwie
uchwyty do funkcji (wskaźniki i referencje) C++ posiada zarówno wskaźniki, jak i referencje do funkcji. Wskaźnik do funkcji można zdefiniować w klasyczny sposób, jak i przy użyciu słówka
auto
. Referencji do funkcji nie można przestawić na inna funkcję. Przykładowo:
C/C++
#include <iostream>

// Przykładowe funkcje
void exampleFunction( int lineNumber ) {
   
std::cout << "Funkcja zawolana z linii: " << lineNumber << std::endl;
}
void exampleFunction2( int ) { // argument anonimowy
   
std::cout << "Funkcja2" << std::endl;
}

int main() {
   
// Deklaracja typu wskaźnika do funkcji
   
void( * functionPointer1 )( int ) = exampleFunction;
   
auto functionPointer2 = exampleFunction; // wygodniejsza forma powyzszego
   
void( * functionPointer3 )( int ) = & exampleFunction; // mozna uzyc operatora &
   
    // Deklaracja referencji do funkcji
   
void( & functionReference )( int ) = exampleFunction;
   
   
// Wywołanie funkcji za pomocą wskaźnika
   
functionPointer1( __LINE__ ); // przekazanie numeru linii
   
functionPointer2( __LINE__ );
   
   
functionPointer1 = exampleFunction2; // mozna przepisac wskaznik
    //    functionReference = exampleFunction2; // nie mozna przepisac referencji
   
functionPointer1( __LINE__ );
}
/* Wydruk z programu:
Funkcja zawolana z linii: 21
Funkcja zawolana z linii: 22
Funkcja2 */
W języku python mamy referencje do funkcji, które tworzymy jak zwykłe zmienne i do nich przypisujemy funkcje, następnie wywołujemy z argumentami jakie przyjmuje ta funkcja - tylko tyle, co widać w poniższym kodzie:
Python
import sys

# Przykładowa funkcja
def example_function(line_number):
   
print(f"Funkcja zawolana z linii: {line_number}")

# Przykład funkcji z argumentem anonimowym
def example_function2(_):
   
print("Funkcja2")

if __name__ == "__main__":
   
# Deklaracja zmiennej przechowującej referencję do funkcji
   
function_reference = example_function

    # Wywołanie funkcji za pomocą referencji
   
function_reference(sys._getframe().f_lineno)  # przekazujemy nr linii

    # Przypisanie innej funkcji do zmiennej
   
function_reference = example_function2
    function_reference(None)  # Argument anonimowy, można użyć None
# wydruk z programu:
# Funkcja zawolana z linii: 16
# Funkcja2
przeciążanie funkcji Można tworzyć wiele funkcji o tej samej nazwie, ale różniących się argumentami, ale nie wystarczy aby się dwie funkcje różniły tylko typem zwracanym:
C/C++
#include <iostream>

// Przykład funkcji przeciazonych:
int add( int a, int b ) {
   
return a + b;
}
double add( double a, double b ) {
   
return a + b;
}
// nie mozna kolejnej funkcji, ktora ma te sama nazwe i te same argumenty:
//float add(double a, double b) {
//    return a + b;
//}

int main() {
   
std::cout << "Wynik int: " << add( 5, 3 ) << std::endl;
   
std::cout << "Wynik double: " << add( 2.5, 3.7 ) << std::endl;
}
Nie ma przeciążania funkcji, może być jedna funkcja o takiej nazwie. Podobne zachowanie można osiągnąć przez fakt, że argumenty mogą mieć różne typy, oraz przez domyślne wartości argumentów, można też wykrywać z jakim typem mamy do czynienia np.:
Python
def add(a, b=None):
   
if b is None:
       
return a
    else:
       
if isinstance(a, str) or isinstance(b, str):
           
return str(a) + str(b)
       
else:
           
return a + b

print("Wynik int:", add(5))
print("Wynik double:", add(2.5, 3.7))
print("Wynik string+int:", add("Przyklad:", 3))
przeźroczyste funkcje, czyli takie, które przyjmują uchwyt do funkcji, oraz doskonale przekazują jej argumenty Mamy mechanizm perfect forwarding, który umożliwia przekazanie argumentów bez ich dodatkowego kopiowania, przykład użycia:
C/C++
#include <iostream>
#include <chrono>  
// std::chrono::high_resolution_clock
#include <thread>  // std::this_thread::sleep_for

// Funkcja do pomiaru czasu wykonania innej funkcji
template < typename Func, typename ... Args >
double measureExecutionTime( Func && func, Args && ... args ) {
   
auto start = std::chrono::high_resolution_clock::now(); // Początkowy czas
   
    // Przekazujemy argumenty używając perfect forwarding
   
func( std::forward < Args >( args ) ... );
   
   
auto end = std::chrono::high_resolution_clock::now(); // Końcowy czas
   
std::chrono::duration < double > duration = end - start;
   
   
return duration.count(); // Czas wykonania w sekundach
}

// Przykładowa funkcja oczekująca określonej liczby sekund
void sampleFunction( int seconds ) {
   
std::this_thread::sleep_for( std::chrono::seconds( seconds ) );
   
std::cout << "Funkcja wykonana po oczekiwaniu przez " << seconds << " sekundy." << std::endl;
}

int main() {
   
double executionTime = measureExecutionTime( sampleFunction, 1 ); // Przekazujemy liczbę sekund jako argument
   
std::cout << "Czas wykonania funkcji: " << executionTime << " sekundy." << std::endl;
}
Do przekazywania otrzymanych argumentów funkcji do innej funkcji możemy użyć jednej gwiazdki na przekazanie argumentów pozycyjnych, oraz dwóch gwiazdek na przekazanie argumentów z nazwami, konwencjonalnie nazywają się te zmienne
*args
 i
**kwargs
, ale to tylko konwencja, bo nazwy mogą być dowolne.A oto kod obudowujący wywołanie funkcji:
Python
import time

# Funkcja do pomiaru czasu wykonania innej funkcji
def measure_execution_time(func, *args, **kwargs):
   
start_time = time.time()  # Początkowy czas

    # Wywołujemy funkcję z przekazanymi argumentami
   
func(*args, **kwargs)

   
end_time = time.time()  # Końcowy czas
   
execution_time = end_time - start_time  # Czas wykonania w sekundach

   
return execution_time

# Przykładowa funkcja oczekująca określonej liczby sekund
def sample_function(seconds):
   
time.sleep(seconds)
   
print(f"Funkcja wykonana po oczekiwaniu przez {seconds} sekundy.")

if __name__ == "__main__":
   
execution_time = measure_execution_time(sample_function, 1)  # Przekazujemy liczbę sekund jako argument
   
print(f"Czas wykonania funkcji: {execution_time} sekundy.")

13. Typy użytkownika - klasy

Język C++ w przeciwieństwie do Pythona posiada stałe, których wartości po zdefiniowaniu nie można zmienić. Jeśli stałą jest obiekt (klasa) to nie można zawołać funkcji składowych klasy, która nie jest stała (czyli nie posiada
const
 z prawej strony). Dlatego w wielu przypadkach poniżej wiele funkcji składowych, które tylko zwracają wartość nie modyfikując składowych klasy są oznaczone jako stałe, a niektóre z metod mają dwie wersje, różniące się wizualnie tylko obecnością słówka
const
 z prawej strony.

kontener C++ python3
prosta klasa (agregat na dane bez enkapsulacji) Wszystkie składowe klasy muszą być zadeklarowane, wraz z typami
C/C++
#include <iostream>

// tworze prosta klase
class Fraction
{
public:
   
int numberator_, denominator_;
};

int main() {
   
// inicjuje jej pola za pomoca listy inicjalizacyjnej
   
Fraction f { 1, 2 };
   
// mozna tez posposrednio
   
f.numberator_ = 3;
   
   
// odnosze sie do pol klasy
   
std::cout << f.numberator_ << "/" << f.denominator_ << '\n';
}
W takich sytuacjach (gdy klasa jest agregatem na dane pozbawionym logiki) konwencjonalnie używa się struktur, wtedy kod wyglądałby tak:
C/C++
struct Fraction
{
   
int numberator_, denominator_;
};
Nie musimy deklarować składowych klasy:
Python

# Tworzę pusta klase
class Fraction:
   
pass

# Inicjuję jej pola za pomocą przypisania wartosci do pol
f = Fraction()
f.numerator = 1
f.denominator = 2

# Odwołuję się do pól klasy
print(f.numerator, "/", f.denominator)
metody klasy i enkapsulacja danych Klasy (oraz struktury) mogą mieć metody składowe. W klasach jest możliwe umieszczanie pól (zmiennych) klasy, oraz metod z różnym dostępem. W C++ mamy do wyboru dostęp
public
 (dla wszystkich),
private
 (tylko dla metod tejże klasy i przyjaciół),
protected
 (tylko dla metod tej klasy, klas pochodnych i przyjaciół).
C/C++
#include <iostream>

// tworze klase zawierajaca metody
class Fraction
{
private: // składowe prywatne (dostępne tylko dla metod klasy i przyjaciół):
   
int numberator_, denominator_;
public:
   
auto numberator() const
   
{
       
return numberator_;
   
}
   
void setNumberator( int newNumberator )
   
{
       
numberator_ = newNumberator;
   
}
   
   
auto calculateValue() const
   
{
       
return static_cast < double >( numberator() ) / denominator();
   
}
   
int denominator() const;
   
void setDenominator( int newDenominator );
};

int main() {
   
// inicjuje jej pola za pomoca getterow
   
Fraction f;
   
f.setNumberator( 1 );
   
f.setDenominator( 2 );
   
   
// odnosze sie do pol klasy (nie mozna do skladowych prywatnych)
    //    std::cout << f.numberator_ << "/" << f.denominator_ << '\n';
   
std::cout << f.numberator() << "/" << f.denominator() << '\n';
}

int Fraction::denominator() const
{
   
return denominator_;
}

void Fraction::setDenominator( int newDenominator )
{
   
denominator_ = newDenominator;
}
Klasy mogą mieć metody składowe, których pierwszym argumentem jest obiekt danej klasy (konwencjonalnie nazywany
self
). Klasy mogą mieć metody składowe. Python nie posiada nazwanych modyfikatorów dostępu, natomiast stosuje się pewne konwencje do dostępu do składowych. W przypadku składowych protected (dostępnych dla dzieci) stosuje się przed nazwą zmiennej jedno podkreślenie
_
, ale można tej zmiennej/metody użyć na zewnątrz. Inaczej wygląda sprawa z prywatnymi składowymi - wtedy stosujemy przed nazwą składowej dwa podkreślenia
__
 - wtedy już nie ma dostępu z zewnątrz.
Python
class Fraction:
   
def __init__(self, numerator=0, denominator=1):
       
self.__numerator = numerator  # Prywatna zmienna
       
self._denominator = denominator  # Zmienna protected

   
def numerator(self):
       
return self.__numerator

    def set_numerator(self, new_numerator):
       
self.__numerator = new_numerator

    def calculate_value(self):
       
return float(self.__numerator) / self._denominator

    def denominator(self):
       
return self._denominator

    def set_denominator(self, new_denominator):
       
self._denominator = new_denominator


if __name__ == "__main__":
   
# Inicjuje jej pola za pomocą konstruktora
   
f = Fraction(1, 2)

   
# Odnoszę się do metod klasy (nie powinno sie do składowych prywatnych)
   
print(f.numerator(), "/", f.denominator())
   
# print(f.__numerator) # blad wykonywania - zmienna prywatna
   
print(f._denominator)  # wykona sie, chociaz to niezgodne z konwencja
Do oznaczenia getterow i setterow stosuje sie specjalne atrybuty metod, wtedy kod wygladal by tak:
Python
class Fraction:
   
def __init__(self, numerator=0, denominator=1):
       
self.__numerator = numerator
        self.__denominator = denominator

    @property
    def numerator(self):
       
return self.__numerator

    @numerator.setter
    def numerator(self, new_numerator):
       
self.__numerator = new_numerator

    def calculate_value(self):
       
return float(self.__numerator) / self.__denominator

    @property
    def denominator(self):
       
return self.__denominator

    @denominator.setter
    def denominator(self, new_denominator):
       
self.__denominator = new_denominator


if __name__ == "__main__":
   
f = Fraction()

   
# Inicjuje jej pola za pomocą setterow
   
f.numerator = 1
   
f.denominator = 2

   
# Odnoszę się do pól klasy przy pomocy getterow
   
print(f.numerator, "/", f.denominator)
składowe statyczne (wspólne dla wszystkich obiektów klas) W C++ jest możliwe tworzenie zarówno zmiennych statycznych jak i metod. Zmienne statyczne muszą być zainicjalizowane poza klasą:
C/C++
#include <iostream>

class MyClass {
public:
   
static int static_variable;
   
   
static void static_method() {
       
std::cout << "This is a static method." << std::endl;
       
++static_variable;
   
}
}
;

// Inicjalizacja statycznej zmiennej (musi sie odbyc poza klasa)
int MyClass::static_variable = 0;

int main() {
   
MyClass::static_variable = 10;
   
MyClass::static_method();
   
   
MyClass myClass;
   
myClass.static_method(); // z obiektu mozna wywolac
   
   
std::cout << "Static variable: " << MyClass::static_variable << std::endl;
}
W C++17 została dodana możliwość zdefiniowania zmiennej statycznej w klasie, przy pomocy słówka kluczowego, dla powyższego kodu wyglądało by to tak:
static inline int static_variable;

W Pythonie jest możliwe tworzenie zarówno zmiennych statycznych jak i metod:
Python
class MyClass:
   
static_variable = 0  # Deklaracja zmiennej statycznej

   
@staticmethod
    def static_method():
       
print("This is a static method.")
       
MyClass.static_variable += 1  # Zwiększenie wartości zmiennej statycznej

# Inicjalizacja zmiennej statycznej (musi się odbyć poza klasą)
MyClass.static_variable = 10
MyClass.static_method()

my_class = MyClass()
my_class.static_method()  # Możesz również wywołać metodę statyczną z instancji

print("Static variable:", MyClass.static_variable)
dziedziczenie składowych Aby wywołać konstruktor klasy bazowej należy użyć jego nazwy na liście inicjalizacyjnej konstruktora jeśli tego nie zrobimy to konstruktor bezargumentowy się wywoła.
C/C++
#include <iostream>

// Klasa bazowa
class Base {
public:
   
Base( int value )
        :
baseValue( value )
   
{
       
std::cout << "Base constructor called with value: " << baseValue << std::endl;
   
}
   
   
void showBaseValue() {
       
std::cout << "Base value: " << baseValue << std::endl;
   
}
   
private:
   
int baseValue;
};

// Klasa pochodna dziedzicząca po klasie bazowej
class Derived
    : public Base
{
public:
   
// Wywołujemy konstruktor klasy bazowej z przekazaniem argumentu
   
Derived( int derivedValue, int baseValue )
        :
Base( baseValue )
       
, derivedValue( derivedValue )
   
{
       
std::cout << "Derived constructor called with value: " << derivedValue << std::endl;
   
}
   
   
void showDerivedValue() {
       
std::cout << "Derived value: " << derivedValue << std::endl;
   
}
   
private:
   
int derivedValue;
};

int main() {
   
Derived derivedObj( 42, 10 ); // Tworzenie obiektu klasy pochodnej
   
derivedObj.showBaseValue(); // Wywołanie metody klasy bazowej
   
derivedObj.showDerivedValue(); // Wywołanie metody klasy pochodnej
}
Dziedziczenie w C++ jest bardziej skomplikowanym zagadnieniem, wchodzą takie zagadnienia jak m.in. kolejność wołania konstruktorów i destruktorów, wielodziedziczenie, dziedziczenie wirtualne, kolejność inicjalizacji składowych, dlatego zachęcam aby poczytać na ten temat w dobrej książce.
Aby wywołać konstruktor klasy bazowej używamy słówka
super
, przykładowy kod:
Python
class Base:
   
def __init__(self, value):
       
self.baseValue = value
        print(f"Base constructor called with value: {self.baseValue}")

   
def showBaseValue(self):
       
print(f"Base value: {self.baseValue}")

class Derived(Base):
   
def __init__(self, derivedValue, baseValue):
       
super().__init__(baseValue)  # Wywołanie konstruktora klasy bazowej z przekazaniem argumentu
       
self.derivedValue = derivedValue
        print(f"Derived constructor called with value: {self.derivedValue}")

   
def showDerivedValue(self):
       
print(f"Derived value: {self.derivedValue}")

# Tworzenie obiektu klasy pochodnej
derivedObj = Derived(42, 10)
derivedObj.showBaseValue()  # Wywołanie metody klasy bazowej
derivedObj.showDerivedValue()  # Wywołanie metody klasy pochodnej
polimorfizm W C++ w tym celu używa się dziedziczenia, funkcji wirtualnych i wskaźników/referencji.
Jeśli nasza klasa ma być klasą bazową przy polimorfiźmie, to raczej na pewno powinniśmy pamiętać o dodaniu wirtualnego destruktora w klasie bazowej, która ma służyć do wywołań polimorficznych. Bez tego może nie dojść do wywołania odpowiedniego destruktora z klasy pochodnej i w rezultacie nawet wycieku zasobów.
C/C++
#include <iostream>
#include <vector>

// Klasa abstrakcyjna (bazowa), nie mozna utworzyc jej instancji
class Shape {
public:
   
virtual void draw() const = 0; // Funkcja czysto wirtualna
   
virtual ~Shape() = default; // destruktor wirtualny
};

// Klasy pochodne
class Circle
    : public Shape
{
public:
   
void draw() const override { // slowko "override" nie jest obowiazkowe
       
std::cout << "Drawing a circle" << std::endl;
   
}
}
;

class Square
    : public Shape
{
public:
   
void draw() const override {
       
std::cout << "Drawing a square" << std::endl;
   
}
}
;

int main() {
   
std::vector < Shape * > shapes =
   
{
       
new Circle(),
       
new Square()
   
};
   
   
for( int i = 0; i < shapes.size(); ++i ) {
       
shapes[ i ]->draw(); // Polimorficzne wywołanie metody draw
       
delete shapes[ i ];
   
}
}
Jest to łatwiejsze w użyciu, gdyż polimorfizm nie wymaga klas w hierarchii dziedziczenia aby wywoływać odpowiednie metody. Operujemy na tylko na referencjach, więc nie trzeba "specjalnie" tworzyć obiektu, a metody są wirtualne automatycznie (nie trzeba dodawać specjalnych słówek kluczowych):
Python
# klasa bazowa
class Shape:
   
def draw(self):
       
pass

# klasy pochodne
class Circle(Shape):
   
def draw(self):
       
print("Drawing a circle")

class Square(Shape):
   
def draw(self):
       
print("Drawing a square")

shapes = [Circle(), Square()]

for shape in shapes:
   
shape.draw()  # Polimorficzne wywołanie metody draw
W powyższym przykładzie klasa
Shape
 nie jest abstrakcyjna (można utworzyć jej instancje), aby jednak ją zrobić abstrakcyjną należy użyć modułu
abc
, wtedy klasa bazowa wyglądała by w następujący sposób:
Python
from abc import ABC, abstractmethod

# Definiowanie abstrakcyjnej klasy bazowej
class Shape(ABC):

   
@abstractmethod
    def draw(self):
       
pass

14. Metody specjalne

Dzięki nim nasza klasa może się zachowywać jak typy wbudowane lub konwencjonalnie w danym języku programowania sposób. Poniżej przykładowe porównanie:
metoda C++ python3
wprowadzenie C++ umożliwia przeciążenie większości operatorów, dzięki którym zachowanie naszego typu może być podobne do typów wbudowanych. Poza tym możemy definiować operatory konwersji naszego typu do dowolnego innego typu. Przeciążanie operatorów jest definiowaniem zachowania operatora lub przesłanianiem zachowania wygenerowanego przez kompilator. Operatory można przeciążyć jako metoda klasy (wtedy pierwszym argumentem jest obiekt tej klasy
this
, albo jako funkcja globalna poza klasą, ale funkcje nie mają dostępu do składowych niepublicznych (za wyjątkiem funkcji zaprzyjaźnionych). Poniżej metody specjalne dla klasy
C/C++
class MyClass
{
   
std::string text_;
public:
};
W C++ aby zgłębić temat przeciążania operatorów trzeba poświęcić trochę czasu, dlatego zachęcam aby zapoznać się z tym zagadnieniem w dobrej książce, chociaż nie jest to zagadnienie, które trzeba koniecznie znać korzystając z C++, gdyż przeciążanie operatorów nie jest konieczne - to samo zachowanie (często czytelniej) osiąga się przy pomocy metod klasy.
W języku python również można ustawiać domyślne zachowanie dla naszego typu w momencie wołania operatorów. Do tego celu są specjalne metody zaczynające i kończące się od dwóch podkreśleń. Poniżej metody specjalne dla klasy
Python
class MyClass:
   
pass
konstruktory (metody wołane w momencie tworzenia obiektu) Można zdefiniować dowolną liczbę konstruktorów, byleby różniły się argumentami np.:
C/C++
// konstruktor bezargumentowy
MyClass()
    :
text_( "(empty1)" ) // inicjalizacja na liscie inicjalizacyjnej
{
   
// text_ = "(empty2)"; // inicjalizacja w ciele konstruktora
}
// konstruktor przyjmujacy argument(y)
MyClass( const std::string & text )
    :
text_( text )
{ }
}
;
Powyższy kod można zamknąć w jednym konstruktorze z wartościami domyślnymi:
C/C++
MyClass( const std::string & text = "empty" )
    :
text_( text )
{ }
Możemy też zdefiniować własny konstruktor kopiujący lub przenoszący, który wykona kopię tak jak chcemy (przydatne gdy mamy uchwyty do zasobów, jeśli nie mamy żadnych zasobów to zakładamy, że domyślnie wygenerowany konstruktor kopiujący załatwi sprawę kopiując składowe jedna za drugą):
C/C++
// konstruktor kopiujacy
MyClass( const MyClass & myClass )
    :
text_( myClass.text_ )
{ }
// konstruktor przenoszacy
MyClass( MyClass && myClass )
    :
text_( std::move( myClass.text_ ) )
{ }
}
;
Kompilator generuje pewne konstruktory, chyba, że działania programisty wyłączą tę opcje, przykładowo konstruktor bezargumentowy jest generowany, o ile nie utworzymy dowolnego innego konstruktora. Zachęcam aby się zapoznać z tymi zasadami.
Można zdefiniować jeden konstruktor. Konstruktora kopiującego się nie definiuje (python sam zarządza pamięcią)
Python
class MyClass:
   
def __init__(self, text=''):
       
self.text = text
Gdybyśmy potrzebowali zrobić głęboką kopię (czyli nie tylko referencje do obiektu, a cały obiekt) można użyć funkcji
deepcopy
 z modułu
copy
.
operatory przypisania - wywolywane gdy obiekt docelowy juz istnieje i chcemy mu przypisac nowa wartosc W C++ występuje semantyka przenoszenia (możemy zamiast kopiować przenieść zawartość):
C/C++
// operator przypisania kopiujacy
MyClass & operator =( const MyClass & myClass )
{
   
if( & myClass != this ) // czy nie kopiujemy na siebie
       
 text_ = myClass.text_;
   
   
return * this; // zwrocenie obiektu tej klasy
}

// operator przypisania przenoszacy
MyClass & operator =( MyClass && myClass )
{
   
if( & myClass != this ) // czy nie przypisujemy samego siebie
   
{
       
text_ = std::move( myClass.text_ );
   
}
   
return * this; // zwrocenie obiektu tej klasy
}
W języku python kopiowane są tylko referencje do obiektów, jeśli chcemy go skopiować dogłębnie możemy użyć
deepcopy
 z modułu
import copy
, przykładowo:
Python
from copy import deepcopy
class MyClass:
   
def __init__(self, text=''):
       
self.text = text

my_class1 = MyClass("text1")
my_class2 = MyClass("text2")

print(my_class1.text, id(my_class1))
print(my_class2.text, id(my_class2))

my_class1 = my_class2
my_class2.text += 'a'
print(my_class1.text, id(my_class1))
print(my_class2.text, id(my_class2))

my_class1 = deepcopy(my_class2)
my_class2.text += 'b'
print(my_class1.text, id(my_class1))
print(my_class2.text, id(my_class2))

# wydruk (przykładowy, gdyż id() zwraca adres):
# text1 140668414609296
# text2 140668414609424
# text2a 140668414609424
# text2a 140668414609424
# text2a 140668414611536
# text2ab 140668414609424
destruktor (metoda wołana przed usunięciem obiektu) W C++ jest to bardzo ważne zagadnienie. Kompilator dla każdego obiektu generuje destruktor, który jednak możemy zdefiniować. Jeśli nasz obiekt korzysta z zasobów (np. pamięci dynamicznej) to zapewne będziemy potrzebowali sami zdefiniować destruktor aby zwolnić pamięć. Przykład destruktora:
C/C++
#include <iostream>

class MyClass
{
   
std::string text_;
public:
   
MyClass() { std::cout << " + MyClass()\n"; } // konstruktor bezargumentowy
   
~MyClass() { std::cout << " - MyClass()\n"; } // destruktor
};

int main() {
   
std::cout << "Przed utworzeniem klasy\n";
   
{ MyClass myClass; }
   
std::cout << "Po usunieciu klasy\n";
}
Wydruk z programu:
Przed utworzeniem klasy
 + MyClass()
 - MyClass()
Po usunieciu klasy
W języku Python nie praktykuje się czegoś takiego, ze względu na automatyczne zarządzanie pamięcią z poziomu samego języka. Jednakże możemy zdefiniować metodę
__del__
, która będzie zawołana przed usunięciem obiektu (ale nie wiadomo kiedy - to zależy od odśmieczacza):
Python
class MyClass:
   
def __init__(self):
       
print(" + MyClass()")

   
def __del__(self):
       
print(" - MyClass()")

print("Przed utworzeniem klasy")
my_class = MyClass()
del my_class  # sami usuwamy obiekt
print("Po usunieciu klasy")
Aby mieć pewność, że dana metoda została zawołana przed wyjściem z programu stosuje się jeszcze
atexit.register
 - wtedy dana funkcja/metoda będzie zawołana w momencie wyłączania interpretera pythona:
Python
import atexit
import os

class MyClass:
   
def __init__(self):
       
print(" + MyClass()")
       
atexit.register(self.cleanup)

   
def __del__(self):
       
print(" - MyClass()")

   
def cleanup(self):
       
print("Running cleanup...")


print("Przed utworzeniem klasy")
my_class = MyClass()
del my_class  # sami usuwamy obiekt
print("Po usunieciu klasy")
Wyjście programu nas zaskoczy:
Przed utworzeniem klasy
 + MyClass()
Po usunieciu klasy
Running cleanup...
 - MyClass()
porównywanie obiektów W C++ można przy pomocy operatorów sprawdzać czy dwa obiekty mają tę samą zawartość (nie muszą to być obiekty tego samego typu):
C/C++
bool operator ==( const MyClass & myClass ) const
{
   
return text_ == myClass.text_;
}
bool operator !=( const MyClass & myClass ) const
{
   
return text_ != myClass.text_;
}
bool operator <( const MyClass & myClass ) const
{
   
return text_ < myClass.text_;
}
bool operator >( const MyClass & myClass ) const;
bool operator >=( const MyClass & myClass ) const;
bool operator <=( const MyClass & myClass ) const;
W C++20 został wprowadzony nowy sposób porównywania obiektów, który wszystkie te operatory generuje:
C/C++
auto operator <=>( const MyClass & myClass ) const
{
   
return text_ <=> myClass.text_;
}
W języku python definiujemy specjalne metody, wtedy możemy dwa obiekty porównywać jak typy arytmetyczne, dla naszej klasy będą to:
Python
    # Operator porównywania ==
   
def __eq__(self, other):
       
return self.text == other.text

    # Operator porównywania !=
   
def __ne__(self, other):
       
return self.text != other.text

    # Operator porównywania <
   
def __lt__(self, other):
       
return self.text < other.text

    # Operator porównywania <=
   
def __le__(self, other):
       
return self.text <= other.text

    # Operator porównywania >
   
def __gt__(self, other):
       
return self.text > other.text

    # Operator porównywania >=
   
def __ge__(self, other):
       
return self.text >= other.text
konwersja na typ logiczny (celem sprawdzenia warunku) W C++ definiuje się metodę będącą operatorem konwersji:
C/C++
operator bool() const {
   
return !text_.empty();
}
Python
    def __bool__(self):
       
return len(self.text) > 0
konwersja obiektu na tekst W C++ jest to rzadko praktykowane, gdyż wyświetlanie obiektu robi się inaczej niż poprzez konwersje na obiekt
std::string
. Można to zrobić definiując operator konwersji na typ
std::string
 w następujący sposób:
C/C++
operator std::string() const
{
   
return "MyClass(" + text_ + ")";
}
Aby potem skorzystać z tej konwersji wystarczy przypisać do zmiennej typu tekstowego:
C/C++
MyClass myClass( "Musicie od siebie wymagac nawet jakby inni od was nie wymagali!" );
std::string text = myClass;
std::cout << text << std::endl;
Warto aby konwersja nie odbywała się w sposób niejawny, dlatego można skorzystać ze słówka kluczowego
explicit
:
C/C++
explicit operator std::string() const
{
   
return "MyClass(" + text_ + ")";
}
dzięki temu, aby skorzystać z konwersje należy zrobić jawne rzutowanie np.:
C/C++
MyClass myClass( "Musicie od siebie wymagac nawet jakby inni od was nie wymagali!" );
std::string text = static_cast < std::string >( myClass );
W Python3 definiuje się metodę
__str__
 dla naszej klasy można ją zdefiniować następująco:
Python
    def __str__(self):
       
return "MyClass(" + self.text + ")"
Warto podkreślić, że funkcja
print()
 woła pod spodem metodę
__str__
, co widać w poniższym wywołaniu:
Python
my_class = MyClass("text1")
text = str(my_class)  # rzutowanie klasy na str

print(text)
print(my_class)  # print wola metode __str__
wyświetlanie na ekran W C++ jest to dość rzadko stososowane, ale jeśli chcemy wyświetlić nasz typ na ekran to konwencjonalnie robi się poprzez zdefiniowanie operatora strumienia dla naszego typu (jako funkcja globalna). Często funkcja ta musi mieć dostęp do składowych prywatnych, dlatego się ją deklaruje jako przyjaciela. Przykładowo dla naszej klasy może to wyglądać tak:
C/C++
// zdefiniowane wewnatrz klasy (ale nie jest to metoda klasy):
friend std::ostream & operator <<( std::ostream & outputStream, const MyClass & myClass )
{
   
return outputStream << "MyClass(" << myClass.text_ << ")";
}
Następnie możemy z tego skorzystać w następujący sposób:
C/C++
MyClass myClass( "Musicie od siebie wymagac nawet jakby inni od was nie wymagali!" );
std::cout << myClass << std::endl;
W języku python do wyświetlania stosuje się funkcję
print()
, która pod spodem woła metodę
__str__
, przykład w powyższym wierszu.
W aspekcie wyświetlania na ekran należy uwzględnić, że język python można wykonywać w trybie interaktywnym, aby w nim wyświetlić dany obiekt wystarczy jego nazwę napisać w linii np.
Python
>>> myclass = MyClass("Jest tylko jedno dobro: wiedza, i jedno zło: ignorancja")
>>>
myclass
<MyClass(Jest tylko jedno dobro: wiedza, i jedno zło: ignorancja)>
Aby jednak wydruk był jak powyżej należy zdefiniować w naszej klasie metodę:
Python
    def __repr__(self):
       
return "<MyClass(" + self.text + ")>"
Jeśli przy pomocy funkcji
print()
 wypisujemy całą tablicę naraz to również jest wołana metoda
__repr__
:
Python
my_classes = [MyClass("text1"), MyClass("text2"), MyClass("text3")]
print(my_classes)
iterowanie po obiekcie za pomocą pętli: gdy obiekt jest agregatem (bardziej zaawansowane) W C++ trzeba zdefiniować metody
begin()
 i
end()
, które będą zwracać obiekt iteratora. Który ma kilka metod koniecznych dla tego typu obiektu, co jest dość skomplikowane, na szczęście nasza klasa zawiera typ, który już ma iterator, dlatego metody te mogą wyglądać tak:
C/C++
auto begin()
{
   
return text_.begin();
}
auto end()
{
   
return text_.end();
}
Wywołanie z kolei:
C/C++
MyClass myClass( "Przyszlosc zaczyna sie dzisiaj, nie jutro" );
for( auto c: myClass )
   
 std::cout << '_' << c;

std::cout << '\n';
W Python3 również należy zdefiniować namiastkę iteratora przez zdefiniowanie metod
__iter__
  i
__next__
. Druga z nich po zakończeniu iterowania musi wyrzucić wyjątek
StopIteration
, przykładowa implementacja dla naszej klasy:
Python
    def __iter__(self):
       
self.index = 0
       
return self

    def __next__(self):
       
if self.index < len(self.text):
           
result = self.text[self.index]
           
self.index += 1
           
return result
        else:
           
raise StopIteration
wywołanie:
Python
my_class = MyClass("Przyszlosc zaczyna sie dzisiaj, nie jutro")

for c in my_class:
   
print('_' + c, end='')

print()  # Dodaj pusty wiersz na końcu
dodawanie obiektów
C/C++
MyClass operator +( const MyClass & myClass ) const
{
   
return MyClass( text_ + myClass.text_ );
}
Python
    def __add__(self, other):
       
if isinstance(other, MyClass):
           
return MyClass(self.text + other.text)
       
else:
           
raise TypeError(f"Unsupported operand type for +: MyClass and {type(other)}"
dostęp do elementów kontenera po indeksie
C/C++
// dwie wersje - stala i zwykla:
const auto & operator[ ]( int index ) const
{
   
if( index < 0 || index >= text_.size() )
       
 throw std::out_of_range( "Index out of range!" );
   
   
return text_[ index ];
}
auto & operator[ ]( int index )
{
   
if( index < 0 || index >= text_.size() )
       
 throw std::out_of_range( "Index out of range!" );
   
   
return text_[ index ];
}
Python
    def __getitem__(self, index):
       
return self.text[index]

   
def __setitem__(self, key, value):
       
# str jest niemodyfikowalny dlatego tworze nowy tekst
       
self.text = self.text[0:key] + value + self.text[key+1:]
nasz obiekt obiektem funkcyjnym
C/C++
auto operator()( const std::string & surroundingText ) const
{
   
return surroundingText + text_ + surroundingText;
}
Python
    def __call__(self, surrounding_text):
       
return surrounding_text + self.text + surrounding_text

Poza powyższymi jest wiele metod specjalnych, które się tworzy analogicznie (np. odejmowanie obiektów od siebie). Poza tym jest wiele metod specjalnych w obydwu językach, które nie mają swoich odpowiedników w metodach specjalnych drugiego języka (oczywiście można je zaimplementować dowolną metodą).

Zarzuty względem poszczególnych języków (z punktu widzenia drugiego porównywanego języka)

C++ python3
  • Język ten wymaga od programisty większej pracowitości, ze względu na swoją składnię, nowe standardy, możliwość ręcznego zarzadzania zasobami. Przekłada się to na wysoki próg wejścia aby dobrze posługiwać się językiem, a także większą potrzebę dokształcania się.
  • Na początku posługiwania się tym językiem istnieje duże ryzyko błędów, które mogą nie być widoczne (udefined behaviour).
  • Brak garbage collectora (dla wielu osób to zaleta), dlatego niezbyt profesjonalne programowanie może prowadzić do wycieków pamięci i innych problemów.
  • Stosunkowo mała biblioteka standardowa, doinstalowywanie bibliotek zewnętrznych może być kłopotliwe, nie jest to ustandaryzowane między różnymi systemami.
  • Wiele rzeczy nie jest ustandaryzowane, przez co zmieniając firmę trzeba zmieniać przyzwyczajenia (np. styl formatowania kodu).
  • Wydajność mniejsza niż języki kompilowalne
  • Mniejsza kontrola nad pamięcią
  • Global Interpreter Lock (GIL) - nie ma prawdziwej wielowątkowości, jeden wątek równocześnie wykonuje kod pythona
  • Wiele błędów wykrywa się dopiero w czasie wykonywania - potrzeba więcej testów

Podsumowanie

W opracowaniu skupiłem się na zestawieniu składni i mechaniźmie działania porównywanych języków, aby znawca jednego języka mógł użyć drugiego języka. Powyższe porównanie dwóch języków nie jest porównaniem pełnym. Wśród pominiętych aspektów są m.in. wyjątki, wyrażenia lambda, operacje wejścia/wyjścia na plikach, szablony, kuroutyny/generatory, obsługa argumentów uruchomienia programu, wczytywanie z klawiatury, obsluga stałych, typy wyliczeniowe, wielowątkowość.

Każdy język istnieje po coś i w tym przoduje, wygoda języka Python wydaje się być większa, ale w pewnych zastosowaniach bardziej cenimy szybkość, większą kontrolę nad programem i wykrywanie błędów na etapie kompilacji. Nie można powiedzieć, że któryś z języków jest w każdym aspekcie lepszy od drugiego.

Autorzy artykułu

Autor Zakres zmian Afiliacja
Bazior Grzegorz Utworzenie artykułu Pracownik AGH w Krakowie
Paweł Król Korekta Pracownik Politechniki Krakowskiej
pekfos Korekta merytoryczna wybitny użytkownik forum http://cpp0x.pl