Си или Си плюс плюс: чем отличаются C и C++ и что выбрать

Структура и отдельные функции в C рядом с классом в C++ Программирование и разработка

Если вы выбираете между Си и Си плюс плюс как первым языком программирования и хотите писать приложения, игры или сервисы, берите C++: в нём есть почти всё из C, а сверх того классы, шаблоны, исключения и большая стандартная библиотека. Язык Си лучше выбрать, когда цель прямо связана с ним: прошивки микроконтроллеров, драйверы, ядра операционных систем или желание понять, как программа работает с памятью.

C++ вырос из C, хотя это давно два отдельных языка программирования со своими стандартами. Код на C в большинстве случаев собирается компилятором C++, и всё же не всегда: бывает файл, который в C работает, а в C++ даже не компилируется, пример в разделе о совместимости. Все примеры собраны Clang 21.1 на Windows.

Что такое язык Си

Язык программирования C создал Деннис Ритчи в Bell Labs в 1972 году для работы над Unix. Уже через год большую часть ядра Unix переписали на нём, и с тех пор это главный язык системного программирования. Первый стандарт вышел в 1989 году (ANSI C), дальше были C99, C11, C17. Действующая версия называется C23, в ISO это документ 9899:2024.

Си называют языком низкого уровня среди языков высокого. В нём есть указатели, ручное управление памятью через malloc и free, структуры и функции, и почти ничего сверх этого. Классов, исключений и шаблонов нет. Простота языка позволяет по исходному коду легко понять, во что превратится программа и как данные лягут в память.

C статически типизированный язык: тип каждого выражения известен на этапе компиляции. Это верно и для C++.

Что такое C++

Бьёрн Страуструп начал делать «C с классами» в той же Bell Labs в 1979 году, а в 1983-м язык получил имя C++. Два плюса означают оператор инкремента: «C, увеличенный на единицу». Первый международный стандарт вышел в 1998 году, затем появились C++03, C++11, C++14, C++17 и C++20. Действует C++23 (ISO/IEC 14882:2024), а следующую версию, C++26, комитет по стандартизации закончил в марте 2026-го.

Возможности языка C++ шире, чем «C с классами»: объектно-ориентированное программирование (ООП) с наследованием и виртуальными функциями, шаблоны, обработка исключений, пространства имён, перегрузка функций и операторов. Стандартная библиотека предлагает контейнеры, строки и алгоритмы STL (стандартной библиотеки шаблонов), потоки, умные указатели. За эти возможности приходится платить сложностью: в стандарте C++23 2104 страницы против 758 у C23, а у многих конструкций есть несколько способов записи.

Главное отличие: процедурный и объектный подход

В языке C данные лежат в структуре, а функции существуют отдельно и получают структуру по адресу. Каждое выделение и освобождение видно в коде. Вот растущий стек целых чисел на C17:

#include <stdio.h>
#include <stdlib.h>

struct Stack {
    int *items;
    size_t len;
    size_t cap;
};

int push(struct Stack *s, int value) {
    if (s->len == s->cap) {
        size_t n = s->cap ? s->cap * 2 : 4;
        int *p = realloc(s->items, n * sizeof *p);
        if (p == NULL) {
            return -1;                 /* памяти нет: сообщаем кодом возврата */
        }
        s->items = p;
        s->cap = n;
    }
    s->items[s->len++] = value;
    return 0;
}

int main(void) {
    struct Stack s = {NULL, 0, 0};
    for (int i = 1; i <= 5; i++) {
        if (push(&s, i * 10) != 0) {
            free(s.items);
            return 1;
        }
    }
    printf("len = %zu, cap = %zu, top = %d\n", s.len, s.cap, s.items[s.len - 1]);
    free(s.items);                     /* забудете эту строку, получите утечку */
    return 0;
}

Команда clang -std=c17 -Wall stack.c -o stack, вывод программы:

len = 5, cap = 8, top = 50

Вместимость удвоилась с 4 до 8 на пятом элементе. Проверить результат realloc и освободить блок программист должен сам, и любая пропущенная строка превращается в утечку или падение. Поля структуры открыты, и другой код может записать туда что угодно.

Читайте также:  typeof в JavaScript: что возвращает оператор для каждого типа данных

Та же задача на C++20 с классом:

#include <iostream>
#include <vector>

class Stack {
public:
    void push(int value) { values.push_back(value); }  // нет памяти: std::bad_alloc
    int top() const { return values.back(); }
    auto count() const { return values.size(); }
private:
    std::vector<int> values;                           // снаружи класса поле не видно
};

int main() {
    Stack s;
    for (int i = 1; i <= 5; i++) {
        s.push(i * 10);
    }
    std::cout << "count = " << s.count() << ", top = " << s.top() << '\n';
}   // здесь деструктор vector сам вернёт память

Команда clang++ -std=c++20 -Wall stack.cpp -o stack, вывод:

count = 5, top = 50

Структура и отдельные функции в C рядом с классом в C++

Поле values закрыто, изменить его можно только через методы класса: это инкапсуляция, и Stack становится собственным типом со своими правилами. Деструктор освобождает ресурсы, когда объект выходит из области видимости. Такой приём называется RAII (получение ресурса есть инициализация), и так же в C++ устроено управление ресурсами вообще: файлами, мьютексами, сетевыми соединениями. Нехватка памяти приходит исключением, которое удобнее поймать в одном месте через try/catch, вместо проверки кода возврата после каждого вызова.

Совместимость: один файл, два языка

Возьмём небольшую программу на языке C:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *buf = malloc(3 * sizeof(int));
    if (buf == NULL) {
        return 1;
    }
    printf("sizeof('a') = %zu\n", sizeof('a'));
    free(buf);
    return 0;
}

Как код на C она собирается и печатает:

sizeof('a') = 4

Тот же файл под именем demo.cpp компилятор C++ не пропускает:

demo.cpp:5:10: error: cannot initialize a variable of type 'int *' with an rvalue of type 'void *'

В C void * сам превращается в адрес любого типа данных, в C++ нужно явное приведение. Добавляем (int *) перед malloc, пересобираем как C++ и получаем другой результат:

sizeof('a') = 1

Символьный литерал 'a' в C имеет тип int, а в C++ тип char. Другие места, где код на C ломается в C++:

  • переменная с именем new, class, delete или template в C законна, в C++ это ключевые слова;
  • массивы переменной длины (int a[n], где n приходит из ввода) есть в C99, в стандартном C++ их нет;
  • слова restrict в C++ нет.

С пустыми скобками всё наоборот: тут C подтянулся к C++. До C23 объявление void hello(); значило «аргументы неизвестны», и вызов hello(42) в режиме C17 проходил с предупреждением. В режиме C23 компилятор выдаёт ошибку too many arguments to function call, expected 0, have 1, как и в C++. Новая версия языка C получила из C++ и другие возможности: nullptr, bool, true и false как ключевые слова, constexpr и вывод типа через auto.

C и C++ в сравнении

C (C23) C++ (C++23)
Подход процедурное программирование процедурное, объектно-ориентированное и обобщённое программирование
Классы и объекты, наследование нет, только структуры есть, с виртуальными функциями
Управление памятью malloc и free вручную деструкторы, контейнеры, умные указатели
Ошибки коды возврата, errno исключения и коды возврата
Шаблоны нет, обходятся макросами и void * есть
Перегрузка функций нет есть
Стандартная библиотека ввод-вывод, строки char *, математика то же, а ещё контейнеры, алгоритмы, string, std::thread
Объём стандарта ISO 758 страниц 2104 страницы
Читайте также:  TRY CATCH в SQL Server: обработка ошибок в T-SQL на примерах

Преимущества и недостатки C и C++

Язык C маленький и предсказуемый. Программист держит под контролем каждый байт, и в коде нет скрытых вызовов: нет деструкторов, которые срабатывают незаметно, и перегруженных операторов. Недостатки вытекают из того же: структуры данных вроде списков и словарей, динамические строки приходится писать самому или брать из сторонней библиотеки, а за границами массивов и утечками следит только человек. Например, текст длиннее буфера молча затрёт соседние байты.

Возможности C++ позволяют писать на более высоком уровне и не терять скорость: структуры данных и алгоритмы STL готовы, шаблоны (обобщённое программирование) позволяют создавать один код для разных типов данных, RAII снимает большую часть ручной работы с ресурсами. Но C++ сложнее и дольше учить, сообщение об ошибке в шаблоне может занять несколько экранов, а время компиляции большого проекта на C++ обычно больше, чем у сопоставимого кода на C, из-за тяжёлых заголовков и шаблонов.

Что быстрее, C или C++

Скорость зависит от кода, а не от названия языка. Все основные компиляторы собирают оба языка одним и тем же оптимизатором, поэтому одинаковый цикл по массиву превратится в одинаковые машинные команды. C++ строится на принципе нулевых издержек: то, чем программа не пользуется, ничего не стоит.

Производительность различается в конкретных приёмах. Виртуальная функция обычно вызывается через таблицу адресов, и такой вызов не всегда удаётся встроить. Исключения и информация о типах (RTTI) увеличивают размер исполняемого файла, поэтому в прошивках их часто отключают флагами -fno-exceptions и -fno-rtti. Бывает и наоборот: qsort из C вызывает функцию сравнения через указатель на каждой паре элементов, а sort из STL получает сравнение шаблонным параметром, и его можно встроить прямо в тело сортировки.

Если нужно сравнить скорость на своей задаче, обе версии пишут одинаково, собирают одним компилятором с одними флагами (например, -O2) и замеряют на реальных данных.

Общая проблема: неопределённое поведение

Оба языка программирования доверяют программисту. Выход за границу массива, чтение освобождённой памяти, переполнение знакового int считаются неопределённым поведением: стандарт не говорит, что произойдёт, и программа может долго работать, пока на другом компьютере не упадёт. Поэтому в крупных проектах на C и C++, например в Windows и Chrome, большая часть уязвимостей связана с памятью.

В C++ есть возможности снизить риск: vector::at проверяет индекс и бросает исключение, умные указатели сами удаляют объект, string следит за длиной. Однако обычный доступ по индексу v[i] не проверяется, даже если использовать контейнер, и старые приёмы из C в C++ по-прежнему работают. Из отладочных инструментов для обоих языков удобнее всего флаг -fsanitize=address в GCC и Clang: он ловит выход за границы и обращение к освобождённому блоку в момент ошибки и печатает строку, где она случилась.

Читайте также:  Async/await в JavaScript: как работает и где ошибаются

Где используется Си и где C++

На языке C написаны ядро Linux (с версии 6.1 в него принимают и код на Rust), Git, SQLite, интерпретатор CPython. Он же используется для драйверов и прошивок: для некоторых микроконтроллеров компилятор C++ урезан или его нет совсем.

Язык C++ используется в больших проектах, где нужна скорость: браузер Chromium, инфраструктура компиляторов LLVM, движок Unreal Engine, графические редакторы, торговые системы. В разработке игр это основной язык популярных движков. Скетчи Arduino, кстати, тоже компилируются как C++.

Плата микроконтроллера и сцена игрового движка: где пишут на C и где на C++

Что выбрать новичку

Мы бы не советовали учить C только как ступеньку перед C++. Навыки C переносятся, но вместе с ними переносятся привычки, от которых в современном C++ отучивают: ручной malloc, массивы char вместо string, голые указатели для владения объектом. Если цель C++, начинайте сразу с него, с vector, string и умных указателей, и не пишите «C с классами».

C в роли первого языка подходит тому, кто идёт в разработку встраиваемых систем или хочет понять, что происходит с переменными на уровне адресов. С этой точки зрения C даже лучше: язык маленький, и на нём быстро видно, как структуры данных лежат в памяти.

Если ни игры, ни прошивки вас не тянут, а нужно просто освоить основы программирования, проще начать с Python. К C или C++ вы потом сможете вернуться с готовой базой: основные понятия вроде переменной, условия и цикла уже будут знакомы, и изучение нового языка сведётся к его особенностям.

Частые вопросы про C и C++

Почему C до сих пор используется, если есть C++?

Компилятор C есть почти для любого процессора, включая самые маленькие. Двоичный интерфейс функций C одинаков у всех компиляторов платформы, поэтому библиотеку на C может вызвать другой язык: Python, Java, Go. По той же причине библиотеки, написанные на C++, часто выставляют наружу интерфейс в стиле C.

Можно ли вызывать код на C из C++?

Да, это обычная практика в больших проектах. Заголовок C-библиотеки подключают внутри extern "C" { ... }, и компоновщик находит её функции по простым именам. Многие заголовки уже содержат такую обёртку под условием #ifdef __cplusplus.

Чем C# отличается от C и C++?

C# сделала Microsoft в 2000 году как язык программирования для платформы .NET. Программа на C# работает в среде выполнения .NET, ненужные объекты удаляет сборщик мусора, а синтаксис ближе к Java. Синтаксис у этого языка программирования похож на C++, но модель памяти другая: сборщик мусора вместо ручного освобождения, и с указателями разработчики на C# почти не сталкиваются. На C# пишут приложения для Windows, серверы и игры на Unity.

Нужно ли знать C, чтобы работать с C++?

Нет. Несмотря на историю, C++ учат как самостоятельный язык: базовый синтаксис у них общий, и его вы выучите в любом случае. Вызовы вроде printf или memcpy понадобятся при работе со старым кодом и интерфейсами операционной системы, их можно разобрать по мере надобности.

Какой стандарт ставить при компиляции?

Для учебных программ на C++ подойдёт C++20 (флаг -std=c++20): его основную часть поддерживают свежие версии трёх основных компиляторов. Для языка C разумный выбор C17, а C23 включайте, если нужны nullptr, constexpr и другие новинки. Поддержка C23 в компиляторах пока неполная: у MSVC, например, нет #embed.

Оцените статью
bestprogrammer.ru
Добавить комментарий