Last modified: April 03, 2023
This article is written in: 🇵🇱
C i C++ to dwa języki programowania o wspólnych korzeniach, które odgrywają kluczowe role w dziedzinie informatyki. Chociaż C++ jest często określany jako rozszerzenie C, różnice między nimi są na tyle znaczące, że warto je szczegółowo omówić. W poniższym tekście przedstawimy dogłębną analizę obu języków, zwracając uwagę na ich historię, zgodność, składnię, funkcjonalności oraz zastosowania, z naciskiem na rygor matematyczny i techniczny.
Język C został opracowany na początku lat 70. XX wieku przez Dennisa Ritchiego w Bell Laboratories. Był on ewolucją wcześniejszych języków, takich jak B i BCPL. C powstał w celu stworzenia języka uniwersalnego, który umożliwiłby pisanie systemów operacyjnych oraz programów narzędziowych z wydajnością zbliżoną do asemblera, ale z wyższym poziomem abstrakcji.
Kluczowym momentem w historii C było napisanie systemu operacyjnego UNIX w tym języku, co przyczyniło się do jego szerokiej adopcji. Standaryzacja języka nastąpiła w 1989 roku wraz z publikacją standardu ANSI C (znanego również jako C89), a później w standardach ISO C90, C99, C11 i C18, które wprowadzały kolejne usprawnienia i funkcjonalności.
C++ został stworzony przez Bjarne Stroustrupa w latach 80. XX wieku, również w Bell Laboratories. Początkowo nazywany "C z klasami", C++ miał na celu wprowadzenie do języka C mechanizmów programowania obiektowego, takich jak klasy, dziedziczenie i polimorfizm. Nazwa "C++" jest odniesieniem do operatora inkrementacji w C, sugerując, że C++ jest "następną wersją" C.
C++ przeszedł wiele etapów standaryzacji, począwszy od standardu C++98, poprzez C++03, C++11, C++14, C++17, aż po C++20 i C++23. Każda kolejna wersja wprowadzała nowe funkcjonalności, takie jak szablony (templates), inteligentne wskaźniki, wyrażenia lambda, koncepty (concepts) i wiele innych, czyniąc język coraz bardziej złożonym, ale jednocześnie potężnym narzędziem do tworzenia skomplikowanych systemów.
C++ został zaprojektowany tak, aby był w dużej mierze zgodny wstecz z C. Oznacza to, że wiele kodu napisanego w C można skompilować jako kod C++. Jednakże ta zgodność nie jest absolutna. Istnieją subtelne różnice w semantyce niektórych konstrukcji, co może prowadzić do nieprzewidywalnych zachowań podczas kompilacji kodu C jako C++.
Na przykład, w C deklaracje zmiennych muszą znajdować się na początku bloku kodu, podczas gdy w C++ można deklarować zmienne w dowolnym miejscu. Ponadto, C++ wprowadza nowe słowa kluczowe, które mogą kolidować z identyfikatorami używanymi w kodzie C.
C++ wprowadza silniejsze mechanizmy typowania w porównaniu z C. Na przykład, w C istnieje większa swoboda w konwersji wskaźników między różnymi typami, co może prowadzić do błędów. C++ wymaga jawnych rzutowań i wprowadza zestaw operatorów rzutowania (static_cast, dynamic_cast, reinterpret_cast, const_cast), co zwiększa bezpieczeństwo typów.
W C zarządzanie pamięcią odbywa się za pomocą funkcji malloc, calloc, realloc i free. W C++ wprowadzono operatory new i delete, które nie tylko przydzielają i zwalniają pamięć, ale także wywołują odpowiednie konstruktory i destruktory obiektów. Dodatkowo, C++ oferuje inteligentne wskaźniki (std::unique_ptr, std::shared_ptr), które automatyzują zarządzanie zasobami i pomagają uniknąć wycieków pamięci.
W obu językach używa się dyrektywy #include do załączania plików nagłówkowych. Różnice pojawiają się jednak w sposobie organizacji bibliotek standardowych.
W języku C pliki nagłówkowe standardowych bibliotek mają rozszerzenie .h. Przykłady:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
W C++ standardowe biblioteki są załączane bez rozszerzenia .h, a ich nazwy są zdefiniowane w przestrzeni nazw std. Dodatkowo, biblioteki C są dostępne z prefiksem c i bez rozszerzenia, np.:
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
// Biblioteki z C w C++
#include <cstdio>
#include <cstdlib>
#include <cstring>
Używając bibliotek C w C++, należy pamiętać, że ich funkcje są również umieszczone w przestrzeni nazw std.
Oba języki dzielą wiele słów kluczowych, które służą do sterowania przepływem programu, deklaracji zmiennych i typów danych:
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
C++ wprowadza szereg nowych słów kluczowych, które wspierają nowe funkcjonalności języka:
alignas
alignof
asm
bool
catch
class
const_cast
constexpr
decltype
delete
dynamic_cast
explicit
export
false
friend
inline
mutable
namespace
new
noexcept
nullptr
operator
private
protected
public
reinterpret_cast
static_assert
static_cast
template
this
thread_local
throw
true
try
typeid
typename
using
virtual
wchar_t
Każde z tych słów kluczowych ma określoną rolę w języku, np. class i struct służą do definiowania nowych typów danych, virtual umożliwia polimorfizm, a template wprowadza programowanie ogólne.
C++ jest językiem wieloparadygmatowym, który wspiera programowanie obiektowe (OOP). OOP wprowadza koncepcje takie jak:
private, protected i public.Przykład definicji klasy w C++:
class Punkt {
private:
double x;
double y;
public:
Punkt(double x = 0.0, double y = 0.0) : x(x), y(y) {}
double odlegloscOdPoczatku() const {
return sqrt(x * x + y * y);
}
void przesun(double dx, double dy) {
x += dx;
y += dy;
}
};
Szablony umożliwiają tworzenie funkcji i klas generycznych, które mogą działać z różnymi typami danych. Są one podstawą programowania ogólnego w C++.
Przykład szablonu funkcji:
template<typename T>
T maksimum(T a, T b) {
return (a > b) ? a : b;
}
Szablon klasy:
template<typename T>
class Wektor {
private:
std::vector<T> dane;
public:
void dodaj(const T& element) {
dane.push_back(element);
}
T pobierz(int indeks) const {
return dane[indeks];
}
};
C++ wprowadza mechanizm obsługi wyjątków, który pozwala na kontrolowane zarządzanie błędami podczas wykonywania programu.
Przykład użycia wyjątków:
double dziel(double a, double b) {
if (b == 0.0) {
throw std::runtime_error("Dzielenie przez zero!");
}
return a / b;
}
int main() {
try {
double wynik = dziel(10.0, 0.0);
} catch (const std::exception& e) {
std::cerr << "Wystąpił błąd: " << e.what() << std::endl;
}
return 0;
}
C++ pozwala na definiowanie wielu funkcji o tej samej nazwie, ale różnych sygnaturach (typach i liczbie argumentów). Przeciążanie operatorów umożliwia definiowanie zachowania operatorów dla obiektów własnych klas.
Przykład przeciążania funkcji:
int suma(int a, int b) {
return a + b;
}
double suma(double a, double b) {
return a + b;
}
Przykład przeciążania operatora:
class Liczba {
private:
int wartosc;
public:
Liczba(int wartosc) : wartosc(wartosc) {}
Liczba operator+(const Liczba& inna) const {
return Liczba(this->wartosc + inna.wartosc);
}
};
C++ wprowadza referencje, które są alternatywą dla wskaźników i pozwalają na przekazywanie argumentów do funkcji przez referencję, umożliwiając modyfikację oryginalnych danych bez konieczności użycia wskaźników.
Przykład:
void zwieksz(int& liczba) {
liczba++;
}
int main() {
int x = 5;
zwieksz(x);
// x == 6
return 0;
}
Ogólnie rzecz biorąc, zarówno C, jak i C++ są językami kompilowanymi do kodu maszynowego, co zapewnia wysoką wydajność. Jednakże:
| Standard | Rok | Nowości |
| C89/C90 | 1989 | Pierwszy standard języka, ustanawiający podstawy. |
| C99 | 1999 | Nowe typy całkowite (stdint.h), pętle for o zmiennym zakresie, funkcje zmiennowymiarowe, komentarze stylu //. |
| C11 | 2011 | Dodaje obsługę wielowątkowości, anonimowe struktury i unie. |
| C18 | 2018 | Głównie poprawki do C11. |
| Standard | Rok | Nowości |
| C++98/C++03 | 1998/2003 | Pierwsze standardy, wprowadzające szablony, przestrzenie nazw, wyjątki. |
| C++11 | 2011 | Znaczące rozszerzenia: wyrażenia lambda, auto, nullptr, inteligentne wskaźniki. |
| C++14 | 2014 | Drobne usprawnienia C++11. |
| C++17 | 2017 | Nowe biblioteki, std::optional, std::variant, składnia if constexpr. |
| C++20 | 2020 | Koncepty, moduły, zakresy (ranges), korutyny. |
Oba języki oferują podstawowe typy danych, takie jak int, float, double, char. Jednak C++ wprowadza dodatkowe typy, np. bool (w C typ logiczny jest symulowany za pomocą int).
Arytmetyka w obu językach podlega tym samym regułom, ale C++ umożliwia przeciążanie operatorów, co pozwala na definiowanie własnych typów numerycznych z zachowaniem naturalnej składni.
Przykład przeciążania operatorów dla liczb zespolonych:
class Zespolona {
private:
double re, im;
public:
Zespolona(double re = 0.0, double im = 0.0) : re(re), im(im) {}
Zespolona operator+(const Zespolona& z) const {
return Zespolona(re + z.re, im + z.im);
}
Zespolona operator*(const Zespolona& z) const {
return Zespolona(re * z.re - im * z.im, re * z.im + im * z.re);
}
};
C++ umożliwia tworzenie algorytmów generycznych za pomocą szablonów, co jest zbliżone do parametrów typów w matematyce. Wprowadzenie konceptów w C++20 pozwala na definiowanie wymagań dla parametrów szablonów, co zwiększa bezpieczeństwo i czytelność kodu.
Przykład konceptu:
template<typename T>
concept Liczba = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
{ a - b } -> std::convertible_to<T>;
{ a * b } -> std::convertible_to<T>;
{ a / b } -> std::convertible_to<T>;
};
template<Liczba T>
T suma(T a, T b) {
return a + b;
}