Основы Программирования

Хвастунов Александр Павлович

Всего 11 лекций
Лекции .pdf Контекст для LLM
Отказ от ответственности
Конспекты предоставляются «как есть» — это студенческие записи, а не официальные материалы. Они могут содержать неточности, пропуски и субъективные интерпретации. Перед экзаменом сверяйтесь с первоисточниками и лекциями преподавателя.

Лекция 10

Лекция 10. ООП. Полиморфизм Полиморфизм Свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта. Динамический полиморфизм Позднее (dynamic) и раннее (static) связывание. Реализуется через виртуальные функции. Виртуальные функции Без знания реального типа класса позволяют вызывать метод того класса, чем переменная фактически является. Ключевое слово virtual — говорит о том, что в классах-потомках функция может быть переопределена. Ключевое слово override (необязательно, но рекомендуется) — явно указывает, что метод переопределяет функцию родителя (помогает на уровне компиляции отслеживать ошибки изменения сигнатуры). Ключевое слово final (у виртуальной функции) — переопределять метод в классах-потомках нельзя. Если метод не виртуальный — берётся метод того типа, через который вызываем (статический тип). Дёшево и сердито! class Base { public: virtual void foo(); virtual ~Base() = default; }; class Derived : public Base { public: void foo() override; // переопределение }; Таблица виртуальных функций (vtable) Таблица заводится для любого класса с виртуальной функцией. Вызов виртуального метода — это вызов метода по адресу из таблицы (немного долговато из-за дополнительной косвенности). Каждый объект класса с виртуальными функциями хранит указатель на vtable (vptr). Стандарт не определяет механизм реализации виртуальных функций, однако большинство компиляторов реализуют именно таблицу виртуальных функций.

05 сент.
salt-caramel

Лекция 11

Лекция 11. Шаблоны классов и функций Шаблоны «Способ объяснить компилятору, как мы хотим решить ту или иную шаблонную задачу.»

05 сент.
salt-caramel

Лекция 2

Лекция 2. Указатели, массивы и строки Указатель Указатель (pointer) — переменная, диапазон значений которой состоит из адресов ячеек памяти и специального значения — нулевого адреса. Значение нулевого адреса используется только для обозначения того, что указатель в данный момент не указывает ни на какую ячейку памяти. Зануляем либо nullptr (предпочтительно, C++11+), либо 0 / NULL (устаревший C-стиль; NULL — это 0, что одновременно и int, и указатель — отсюда возможные неоднозначности при перегрузке функций). void* void* — указатель на любой тип. Может быть явно приведён к указателю на другой тип (в C-style: (int*)ptr, в C++: static_cast<int*>(ptr)). Нельзя разыменовать напрямую — сначала нужно привести к конкретному типу. Представление в виде байтов Любой указатель может быть представлен в виде массива байт! #include <iostream> #include <format> void printBytes(void* ptr, size_t size) { uint8_t* bytes = (uint8_t*)ptr; for (size_t i = 0; i < size; ++i) { std::cout << std::format("{:08b} ", *bytes); ++bytes; // увеличивает адрес на 1, смещая на след. байт } std::cout << std::endl; } std::format — ещё один способ организовать форматированный вывод (C++20).

05 сент.
salt-caramel

Лекция 3

Лекция 3. Структуры, объединения Структура (struct) Структура — это одна или несколько переменных (возможно, различных типов), которые для удобства работы с ними сгруппированы под одним именем. В C++ структура практически идентична классу (отличие — модификатор доступа по умолчанию: в struct — public, в class — private). Объединение (union) Объединение — это переменная, которая может содержать (в разные моменты времени) объекты различных типов и размеров. Все требования относительно размеров и выравнивания выполняет компилятор. Объединения позволяют хранить разнородные данные в одной и той же области памяти без включения в программу машинно-зависимой информации. Размер объединения равен размеру самого большого его поля. union Data { int i; float f; char str[4]; }; // sizeof(Data) == 4

05 сент.
salt-caramel

Лекция 4

Лекция 4. Работа с памятью Архитектура: фон Неймана vs Гарвардская 2 потока: поток данных и поток команд-вычислений. В архитектуре фон Неймана они идут через одну шину памяти. В Гарвардской — данные и команды разделены. 2 блока: блок процессора и блоки, отвечающие за память: SSD (сотни ГБ) и HDD (ГБ–ТБ) — жёсткие диски. Оперативная память (~16 ГБ). Кэш процессора (3 уровня: L1 — ~24 КБ, L2 — ~112 КБ, L3 — ~4 МБ) — сверхоперативная память, используемая микропроцессором для уменьшения среднего времени доступа к компьютерной памяти. “Процесс” Под каждый процесс отводится своя память. Своё адресное пространство. Свои потоки для каждого процесса. ОС для каждого процесса создаёт иллюзию того, что он может пользоваться всей виртуальной памятью, хотя на самом деле она разделена между процессами. ОС хранит взаимно-однозначное соответствие между виртуальной памятью и физической памятью с помощью Page Table (выдаёт страницы памяти, забирает, отдаёт дальше). Для каждого процесса создаётся своя виртуальная таблица — одним и тем же виртуальным адресам в разных процессах могут соответствовать разные места физической памяти. ОС может swap-ать часть информации на жёсткий диск, если она используется редко. Сегменты памяти Сегмент ОС (виртуальная таблица) — доступа у пользователя нет. 3 сегмента фиксированного размера: Text — исполняемый код. Data — инициализированные глобальные/статические переменные. BSS — неинициализированные глобальные/статические переменные. 3 сегмента, меняющих размер: Stack — стек вызовов. Heap — куча. Memory mapping segment — для маппинга файлов. Process id: 18636 Data segment: 00007ff6cc079000 BSS segment: 00007ff6cc078000 Text segment: 00007ff6cc079004 Code segment: 00007ff6cc071430 Stack segment: 0000007 8c0bff884 Первые сегменты расположены близко друг к другу, а Stack — где-то далеко (растёт в обратном направлении).

05 сент.
salt-caramel

Лекция 5

Лекция 5. Компиляция Раздельная компиляция — зачем Уменьшение количества строк в одном файле. Ускорение компиляции (пересобираются только изменённые модули). Разделение программы на логические модули, которые можно переиспользовать. Проще читать код. Разделение на .h- и .cpp-файлы нужно для разных этапов компиляции. Этапы трансляции (на самом деле их около 9) Препроцессор — создаёт блоки (единицы трансляции), включая .h-файлы в .cpp-файлы. Компиляция — получает из каждой единицы трансляции объектные файлы (.o) — скомпилированный код, независимый для каждой единицы трансляции. Линковщик (Linker) — собирает объектные файлы в одну программу. a.hpp b.hpp c.hpp d.hpp ↓ ↓ ↓ a.cpp b.cpp c.cpp ↓ ↓ ↓ ─── Preprocessor ─── ↓ ↓ ↓ d.hpp c.hpp d.hpp b.hpp b.hpp c.hpp a.hpp a.cpp b.cpp a.cpp b.cpp c.cpp ↓ ↓ ↓ ──── Compiler ──── ↓ ↓ ↓ a.o b.o c.o ↓ Linker ↓ a.exe Раздельная компиляция позволяет перекомпилировать только изменённые части. Запуск этапов трансляции отдельно (на примере clang) Флаг Действие clang++ -E только препроцессор clang++ -S препроцессор + компиляция clang++ -c препроцессор + компиляция + ассемблирование clang++ --Xlinker запуск линковщика Declaration vs Definition Declaration — задаёт имя и прочие атрибуты для сущностей (например, сигнатуру функции). Может встречаться сколько угодно раз. Definition — полностью определяет сущность; является одновременно и объявлением. Должно быть ровно одно (ODR — One Definition Rule). Заголовочные файлы (.h / .hpp) Содержат:

05 сент.
salt-caramel

Лекция 6

Лекция 6. Ссылки, инициализация, перегрузка функций и именованные скоупы Ссылки (lvalue ref) «Псевдоним» для уже существующего объекта. Обязательно инициализирована при объявлении. Не занимает дополнительную память (формально; на практике может реализовываться через указатель). Нельзя сделать указатель на ссылку, нельзя сделать массив ссылок. Продлевают «жизнь» временным переменным (если ссылка константная и инициализируется временным объектом). Скорее всего, компилятор сам передаёт адреса при передаче аргументов по ссылке, но нас это не заботит — на уровне языка это просто другое имя. Опасности и правила Возвращать ссылку на локальную переменную при выходе из функции — ошибка (объект уничтожен, ссылка «висячая»). Можно возвращать ссылку, которую мы изначально передали в аргументе функции (переменная не локальна для функции). В случае больших объектов нужно передавать по ссылке (предпочтительно const&), чтобы не забивать стек вызова функции лишними копиями. Перегрузка функций Функции с одинаковым именем, но разными списками параметров. Нельзя перегружать функции с одинаковыми аргументами, но разными возвращаемыми типами — компилятор не сможет однозначно выбрать функцию. Инициализация — виды Вид Пример Default initialization std::string s1; Value initialization int l{}; Direct initialization std::string s4("hello"); Copy initialization std::string s3 = "hello"; List initialization std::string str{'a', 'b', 'c'}; Aggregate initialization char a[3] = {'a', 'b'}; Namespace Предотвращают конфликт имён. Могут состоять из нескольких блоков (могут быть «раскрыты» в разных местах). Упрощают читабельность кода. Unnamed namespace:

05 сент.
salt-caramel

Лекция 7

Лекция 7. ООП. Абстракция. Инкапсуляция Абстракция Абстракция — придание объекту характеристик, которые чётко определяют его концептуальные границы, отличая от всех других объектов. Основная идея: отделить способ использования составных объектов данных от деталей их реализации в виде более простых объектов. Инвариант Инвариант в ООП — выражение, определяющее непротиворечивое внутреннее состояние объекта. Должен сохраняться между вызовами публичных методов класса. Class Класс — универсальный, комплексный тип данных, состоящий из тематически единого набора «полей» (переменных) и «методов» (функций для работы с этими полями). Является моделью информационной сущности с внутренним и внешним интерфейсами для оперирования своим содержимым. Инкапсуляция Механизм языка, позволяющий ограничить доступ одних компонентов программы к другим. Языковая конструкция, позволяющая связать данные с методами, предназначенными для обработки этих данных. Access Modifiers (модификаторы доступа) Public — доступно всем. Protected — доступно классу и его наследникам. Private — доступно только классу. Constructor (конструктор) Специальный, не статический метод, используемый для инициализации объекта (обеспечивает инвариант). Название этого метода совпадает с именем класса. Не имеет возвращаемого типа. Виды конструкторов:

05 сент.
salt-caramel

Лекция 8

Лекция 8. Перегрузка функций, методов и операторов Операторы (классификация) 1. Арифметические:

05 сент.
salt-caramel

Лекция 9

Лекция 9. ООП. Наследование Наследование Позволяет описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым, родительским или суперклассом. Новый класс — потомком, наследником, дочерним или производным классом. Позволяет использовать полиморфизм. Is-a relationship «Является объектом типа» — при public-наследовании объект производного класса является также и объектом базового класса. Возможность манипулирования объектами по ссылкам/указателям на базовые классы. Наследник Хранит в себе родителя (физически содержит подобъект базового класса). Сохраняет методы родителя (за исключением некоторых случаев с переопределением). Возможно приведение к базовому классу (slicing — при копировании по значению теряется «лишняя» часть наследника). Учитывает модификаторы доступа при наследовании. Порядок вызова конструкторов и деструкторов Base constructor — конструктор базового класса. Derived constructor — конструктор производного класса. Derived destructor — деструктор производного класса. Base destructor — деструктор базового класса. То есть деструкторы вызываются в обратном порядке относительно конструкторов.

05 сент.
salt-caramel

Лекция 1

Лекция 1. Типы данных, идентификаторы, операторы, операторы ветвления, циклы, функции Идентификаторы Идентификаторы — это имена, используемые для обозначения переменных, типов, функций, шаблонов и т.д. Должны начинаться с буквы или подчёркивания, могут содержать цифры. Зарезервированные слова языка использовать нельзя. Типы данных Для определения границ типа удобно использовать numeric_limits<type>::param (заголовок <limits>). Например: std::numeric_limits<int>::max(), std::numeric_limits<int>::min(). Соотношение размеров целочисленных типов: 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) Стандарт не фиксирует конкретные размеры в байтах, только их соотношение. Целочисленные литералы Префикс Система счисления (нет) десятичная (dec) 0 восьмеричная (oct) 0x шестнадцатеричная (hex) 0b двоичная (bin) Вещественные литералы Суффикс Тип (пусто) double f / F float l / L long double Также используется экспоненциальная запись: 1.5e10, 2.3E-4.

09 сент.
salt-caramel