Горячее
Лучшее
Свежее
Подписки
Сообщества
Блоги
Эксперты
Войти
Забыли пароль?
или продолжите с
Создать аккаунт
Регистрируясь, я даю согласие на обработку данных и условия почтовых рассылок.
или
Восстановление пароля
Восстановление пароля
Получить код в Telegram
Войти с Яндекс ID Войти через VK ID
ПромокодыРаботаКурсыРекламаИгрыПополнение Steam
Пикабу Игры +1000 бесплатных онлайн игр В Битве героев вас ждут захватывающие приключения: сражайтесь с ордами монстров, исследуйте десятки уникальных локаций и собирайте мощное снаряжение. Объединяйтесь с кланом, чтобы вместе преодолеть испытания и победить самых грозных врагов. Ведите своего героя к славе и триумфу!

Битва Героев

Ролевые, Приключения, Мидкорные

Играть

Топ прошлой недели

  • solenakrivetka solenakrivetka 7 постов
  • Animalrescueed Animalrescueed 53 поста
  • ia.panorama ia.panorama 12 постов
Посмотреть весь топ

Лучшие посты недели

Рассылка Пикабу: отправляем самые рейтинговые материалы за 7 дней 🔥

Нажимая «Подписаться», я даю согласие на обработку данных и условия почтовых рассылок.

Спасибо, что подписались!
Пожалуйста, проверьте почту 😊

Помощь Кодекс Пикабу Команда Пикабу Моб. приложение
Правила соцсети О рекомендациях О компании
Промокоды Биг Гик Промокоды Lamoda Промокоды МВидео Промокоды Яндекс Маркет Промокоды Пятерочка Промокоды Aroma Butik Промокоды Яндекс Путешествия Промокоды Яндекс Еда Постила Футбол сегодня
0 просмотренных постов скрыто
5
mcnikirikitiki
Лига программистов

Основы программирования на C++: Шаблонные классы⁠⁠

9 месяцев назад

Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.

Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.

Здравствуйте, мои маленькие любители программирования!

Шаблонные классы

Классы, как и функции, могут быть параметризованы типами или константами. Такие классы называются шаблонными . Примерами шаблонов классов являются все контейнеры стандартной библиотеки. В этом параграфе мы напишем шаблонный класс «Матрица». Мы также рассмотрим на его примере не связанные с шаблонами вещи: перегрузку по константности и итерацию в цикле range-based for.


Матрица — это таблица чисел

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


Выбор шаблонных параметров

Наш класс должен поддерживать работу с разными типами элементов. Поэтому вынесем тип элемента в шаблонные параметры:

template <typename T>

class Matrix;

Теперь решим, будут ли размеры матрицы известны во время компиляции. Если да, их можно сделать шаблонными параметрами, а хранить матрицу можно в двумерном контейнере std::array:

#include <array>

template <typename T, size_t Rows, size_t Columns>

class Matrix {

private:

std::array<std::array<T, Columns>, Rows> data;

};

Пример использования:

int main() {

Matrix<int, 3, 4> m; // матрица размера 3 x 4

}

Однако чаще размеры матрицы становятся известными только во время выполнения программы. Тогда шаблонные размеры не подойдут, так как аргументы шаблона должны быть известны в момент компиляции. В этом случае размеры должны содержаться в данных самой матрицы, а не в её типе. Хранить матрицу будем в двумерном векторе:

#include <vector>

template <typename T>

class Matrix {

private:

std::vector<std::vector<T>> data;

public:

size_t GetRows() const {

return data.size();

}

size_t GetColumns() const {

if (data.empty()) {

return 0;

}

return data[0].size();

}

};


Конструкторы

Конструктор из вектора векторов

Напишем конструктор, который принимает двумерный вектор и соблюдает инвариант «в строках матрицы одинаковое количество элементов»:

template <typename T>

class Matrix {

private:

std::vector<std::vector<T>> data;

void MakeRectangle() {

size_t maxSize = 0;

for (const auto& row : data) {

if (row.size() > maxSize) {

maxSize = row.size();

}

}

for (auto& row : data) {

row.resize(maxSize);

}

}

public:

Matrix(const std::vector<std::vector<T>>& d) : data(d) {

MakeRectangle();

}

};

Пример использования:

int main() {

Matrix<int> m({

{1, 2, 3},

{4, 5, 6},

});

std::cout << m.GetRows() << "\n"; // 2

std::cout << m.GetColumns() << "\n"; // 3

}

Конструктор для нулевой матрицы

Добавим конструктор для создания нулевой матрицы заданных размеров:

template <typename T>

class Matrix {

private:

std::vector<std::vector<T>> data;

public:

Matrix(size_t rows, size_t columns) {

data.resize(rows);

for (auto& row : data) {

row.resize(columns);

}

}

};

Пример использования:

int main() {

Matrix<int> m(3, 4); // создаём нулевую матрицу из 3 строк и 4 столбцов

}


Обращение к элементам и перегрузка по константности

Хочется обращаться к элементам матрицы так же, как и с двумерным массивом:

int main() {

Matrix<int> m(3, 4);

int element = m[0][0];

m[1][1] = 1;

m[2][3] = 5;

}

Для этого перегрузим оператор []. Однако нужно учесть два случая: чтение и запись. Для этого перегрузим оператор по константности:

template <typename T>

class Matrix {

private:

std::vector<std::vector<T>> data;

public:

const std::vector<T>& operator[](size_t i) const {

return data[i];

}

std::vector<T>& operator[](size_t i) {

return data[i];

}

};

Теперь первая версия будет применяться к константным матрицам, а вторая — к неконстантным.


Итерация по матрице

Чтобы можно было писать цикл range-based for по строкам матрицы, добавим функции begin и end:

template <typename T>

class Matrix {

private:

std::vector<std::vector<T>> data;

public:

using const_iterator = typename std::vector<std::vector<T>>::const_iterator;

const_iterator begin() const {

return data.cbegin();

}

const_iterator end() const {

return data.cend();

}

};

Пример использования:

int main() {

Matrix<int> m(3, 4);

for (const auto& row : m) {

// обрабатываем строку row

}

}


Потоковый ввод и вывод

Перегрузим операторы << и >> для удобства работы с потоками:

template <typename T>

std::ostream& operator<<(std::ostream& out, const Matrix<T>& matrix) {

const size_t rows = matrix.GetRows();

const size_t columns = matrix.GetColumns();

for (size_t i = 0; i != rows; ++i) {

for (size_t j = 0; j != columns; ++j) {

if (j > 0) {

out << "\t";

}

out << matrix(i, j);

}

out << "\n";

}

return out;

}

template <typename T>

std::istream& operator>>(std::istream& in, Matrix<T>& matrix) {

const size_t rows = matrix.GetRows();

const size_t columns = matrix.GetColumns();

for (size_t i = 0; i != rows; ++i) {

for (size_t j = 0; j != columns; ++j) {

in >> matrix(i, j);

}

}

return in;

}

Пример использования:

int main() {

Matrix<int> m(3, 4);

std::cin >> m;

std::cout << m;

}


Арифметические операции

Оператор +=

Реализуем оператор += для сложения матриц одинакового размера:

template <typename T>

Matrix<T>& operator+=(const Matrix<T>& other) {

const size_t rows = GetRows();

const size_t columns = GetColumns();

if (rows != other.GetRows() || columns != other.GetColumns()) {

throw std::invalid_argument("Matrices have different size!");

}

for (size_t i = 0; i != rows; ++i) {

for (size_t j = 0; j != columns; ++j) {

data[i][j] += other.data[i][j];

}

}

return *this;

}

Оператор +

Реализуем оператор +, используя уже написанный оператор +=:

template <typename T>

Matrix<T> operator+(const Matrix<T>& m1, const Matrix<T>& m2) {

auto tmp = m1;

tmp += m2;

return tmp;

}


Сравнение матриц

Напишем операторы == и != для сравнения двух матриц:

template <typename T>

bool operator==(const Matrix<T>& other) const {

const size_t rows = GetRows();

const size_t columns = GetColumns();

if (rows != other.GetRows() || columns != other.GetColumns()) {

return false;

}

for (size_t i = 0; i != rows; ++i) {

for (size_t j = 0; j != columns; ++j) {

if (!((*this)(i, j) == other(i, j))) {

return false;

}

}

}

return true;

}

template <typename T>

bool operator!=(const Matrix<T>& m1, const Matrix<T>& m2) {

return !(m1 == m2);

}


Таким образом, шаблонные классы позволяют создавать универсальные и гибкие структуры данных, которые легко адаптировать под разные задачи.

Показать полностью
[моё] IT C++ Программирование Программист Длиннопост
5
6
mcnikirikitiki
Лига программистов

Основы программирования на C++: Классы⁠⁠

9 месяцев назад

Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.

Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.

Здравствуйте, мои маленькие любители программирования!

Классы и их особенности

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

  • Структуры (struct) используются, когда нам не требуется сложная логика для работы с данными. Это просто набор полей без каких-либо ограничений. Пример: структура Point, которая хранит координаты.

  • Классы (class) применяются, когда необходимо контролировать данные, обеспечивать их корректность и выполнять определённые действия при инициализации или изменении. Например, класс Time гарантирует, что время всегда находится в допустимых пределах.

Классы не только задают тип данных, но и определяют их поведение. Переменные такого типа называются объектами.


Объявление класса

Рассмотрим пример простой структуры, которая хранит время в часах, минутах и секундах:

struct Time {

int hours = 0;

int minutes = 0;

int seconds = 0;

};

Эта структура удобна, но она не проверяет корректность значений. Например, можно присвоить часам значение 42, а минутам — -5. Чтобы избежать таких ошибок, объявим класс Time.

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

class Time {

private:

int hours;

int minutes;

int seconds;

public:

Time(int h, int m, int s); // Конструктор

int GetHours() const; // Метод для получения часов

int GetMinutes() const; // Метод для получения минут

int GetSeconds() const; // Метод для получения секунд

};

Здесь конструктор отвечает за начальную инициализацию объекта, а методы GetHours, GetMinutes и GetSeconds объявлены как константные (с пометкой const), что означает их невозможность изменять состояние объекта.


Реализация функций

Теперь реализуем объявленные функции. Для этого можно написать их тела внутри класса или отдельно. При внешнем определении используется префикс с именем класса и двоеточием:

Time::Time(int h, int m, int s) {

if (s < 0 || s > 59) {

// Обработка ошибочных секунд

}

if (m < 0 || m > 59) {

// Обработка ошибочных минут

}

if (h < 0 || h > 23) {

// Обработка ошибочных часов

}

hours = h;

minutes = m;

seconds = s;

}

int Time::GetHours() const {

return hours;

}

int Time::GetMinutes() const {

return minutes;

}

int Time::GetSeconds() const {

return seconds;

}

Обратите внимание, что каждая функция класса работает с текущим объектом, который передаётся неявно. Этот объект доступен через указатель this.


Обработка ошибок в конструкторе

Если конструктор получает некорректные значения, он может либо сгенерировать исключение, либо скорректировать данные. Рассмотрим второй вариант:

Time::Time(int h, int m, int s) {

m += s / 60;

s %= 60;

if (s < 0) {

m -= 1;

s += 60;

}

h += m / 60;

m %= 60;

if (m < 0) {

h -= 1;

m += 60;

}

h %= 24;

if (h < 0) {

h += 24;

}

hours = h;

minutes = m;

seconds = s;

}

Теперь объекты класса Time всегда будут содержать корректное время.


Перегрузка конструкторов

Можно добавить несколько конструкторов для удобства. Например, конструктор без параметров и конструктор, принимающий общее количество секунд:

class Time {

private:

int hours = 0;

int minutes = 0;

int seconds = 0;

public:

Time() = default; // Конструктор по умолчанию

Time(int h, int m, int s); // Основной конструктор

Time(int s): Time(0, 0, s) {} // Делегирующий конструктор

};


Изменение состояния объекта

Чтобы позволить изменять объект после создания, добавим метод AddSeconds:

void AddSeconds(int s) {

seconds += s;

Normalize(); // Корректировка времени

}

Этот метод нельзя вызвать для константного объекта, так как он изменяет состояние.


Перегрузка операторов

Для удобства можно перегрузить операторы, такие как += и +. Например:

Time& operator += (int s) {

seconds += s;

Normalize();

return *this;

}

Time operator + (int s) const {

return Time(hours, minutes, seconds + s);

}

Теперь можно писать t += 40 или t + 20.


Ввод и вывод

Операторы << и >> можно перегрузить для работы с потоками:

std::ostream& operator << (std::ostream& out, const Time& t) {

out << t.GetHours() << ":" << t.GetMinutes() << ":" << t.GetSeconds();

return out;

}

std::istream& operator >> (std::istream& in, Time& t) {

int h, m, s;

char dummy;

in >> h >> dummy >> m >> dummy >> s;

t = Time(h, m, s);

return in;

}

Теперь можно легко читать и выводить время.


Модификация реализации

Интерфейс класса остаётся неизменным, даже если внутренняя реализация меняется. Например, вместо трёх полей (hours, minutes, seconds) можно хранить только одно поле totalSeconds:

class Time {

private:

int totalSeconds;

void Normalize() {

const int secondsInDay = 24 * 60 * 60;

totalSeconds %= secondsInDay;

if (totalSeconds < 0) {

totalSeconds += secondsInDay;

}

}

public:

Time(int h, int m, int s) {

totalSeconds = h * 60 * 60 + m * 60 + s;

Normalize();

}

int GetHours() const {

return totalSeconds / (60 * 60);

}

int GetMinutes() const {

return (totalSeconds / 60) % 60;

}

int GetSeconds() const {

return totalSeconds % 60;

}

};

Это упрощает код, сохраняя совместимость с существующими программами.


Таким образом, классы позволяют создавать надёжные и гибкие типы данных, которые легко модифицировать и расширять.

Задание 1: Класс Point

Создайте класс Point, который представляет точку в двумерном пространстве с координатами (x, y). Реализуйте следующие функциональности:

  1. Приватные поля x и y.

  2. Конструкторы :

    • Конструктор без параметров (инициализирует координаты нулями).

    • Конструктор с двумя параметрами для установки значений x и y.

  3. Методы доступа :

    • GetX() и GetY() для получения значений координат.

    • SetX(int x) и SetY(int y) для изменения значений координат.

  4. Перегрузка операторов :

    • Перегрузите оператор + для сложения двух точек (координаты складываются поэлементно).

    • Перегрузите оператор - для вычитания двух точек.

  5. Дополнительно : добавьте метод DistanceTo(const Point& other), который вычисляет расстояние между текущей точкой и другой точкой.


Задание 2: Класс Fraction

Реализуйте класс Fraction, представляющий дробь вида a/b (где a — числитель, b — знаменатель). Добавьте следующие возможности:

  1. Приватные поля numerator и denominator.

  2. Конструкторы :

    • Конструктор с двумя параметрами для инициализации числителя и знаменателя.

    • Конструктор с одним параметром (знаменатель по умолчанию равен 1).

  3. Методы доступа :

    • GetNumerator() и GetDenominator().

    • Метод Simplify(), который сокращает дробь до несократимой формы (например, 4/8 → 1/2).

  4. Перегрузка операторов :

    • Перегрузите операторы +, -, *, / для выполнения арифметических операций с дробями.

    • Перегрузите операторы сравнения (==, !=, <, >, <=, >=).

  5. Дополнительно : добавьте метод ToDouble(), который возвращает значение дроби как число типа double.


Задание 3: Класс BankAccount

Создайте класс BankAccount, моделирующий банковский счёт. Реализуйте следующие функциональности:

  1. Приватные поля :

    • balance (баланс счёта).

    • accountNumber (номер счёта, строка или целое число).

  2. Конструкторы :

    • Конструктор с начальным балансом и номером счёта.

    • Конструктор без параметров (начальный баланс = 0, номер счёта генерируется автоматически).

  3. Методы :

    • Deposit(double amount) для пополнения счёта.

    • Withdraw(double amount) для снятия денег (с проверкой на достаточность средств).

    • GetBalance() для получения текущего баланса.

  4. Перегрузка операторов :

    • Перегрузите оператор += для пополнения счёта.

    • Перегрузите оператор -= для снятия денег.

  5. Дополнительно : добавьте метод Transfer(BankAccount& other, double amount), который переводит деньги с одного счёта на другой.


Задание 4: Класс Date

Реализуйте класс Date, представляющий дату в формате день-месяц-год. Добавьте следующие возможности:

  1. Приватные поля :

    • day, month, year.

  2. Конструкторы :

    • Конструктор с тремя параметрами для инициализации дня, месяца и года.

    • Конструктор без параметров (текущая дата по умолчанию).

  3. Методы :

    • GetDay(), GetMonth(), GetYear() для получения значений.

    • SetDate(int day, int month, int year) для изменения даты.

    • IsValid() для проверки корректности даты (например, 31 февраля недопустимо).

  4. Перегрузка операторов :

    • Перегрузите оператор ++ для увеличения даты на один день.

    • Перегрузите оператор -- для уменьшения даты на один день.

  5. Дополнительно : добавьте метод DaysBetween(const Date& other), который возвращает количество дней между текущей датой и другой датой.


Задание 5: Класс Matrix

Создайте класс Matrix, представляющий матрицу чисел. Реализуйте следующие функциональности:

  1. Приватные поля :

    • Двумерный массив для хранения элементов матрицы.

    • Размеры матрицы (rows, columns).

  2. Конструкторы :

    • Конструктор с размерами матрицы и начальными значениями элементов.

    • Конструктор без параметров (матрица размером 1x1 с нулевым элементом).

  3. Методы :

    • GetElement(int row, int col) и SetElement(int row, int col, int value) для доступа к элементам.

    • Print() для вывода матрицы на экран.

  4. Перегрузка операторов :

    • Перегрузите оператор + для сложения двух матриц.

    • Перегрузите оператор * для умножения матриц (проверьте совместимость размеров).

  5. Дополнительно : добавьте метод Transpose(), который возвращает транспонированную матрицу.


Задание 6: Класс Student

Создайте класс Student, представляющий студента с информацией о его имени, возрасте и оценках. Реализуйте следующие функциональности:

  1. Приватные поля :

    • name (имя студента).

    • age (возраст студента).

    • grades (массив оценок).

  2. Конструкторы :

    • Конструктор с именем, возрастом и массивом оценок.

    • Конструктор только с именем (остальные поля по умолчанию).

  3. Методы :

    • GetName(), GetAge(), GetGrades() для получения данных.

    • AddGrade(int grade) для добавления новой оценки.

    • AverageGrade() для вычисления средней оценки.

  4. Перегрузка операторов :

    • Перегрузите оператор << для вывода информации о студенте в поток.

    • Перегрузите оператор >> для ввода данных о студенте из потока.

  5. Дополнительно : добавьте метод IsExcellentStudent(), который возвращает true, если средняя оценка >= 4.5.


Задание 7: Класс String

Реализуйте свой собственный класс String, аналогичный стандартному std::string. Добавьте следующие возможности:

  1. Приватные поля :

    • Указатель на массив символов (char*).

    • Длина строки.

  2. Конструкторы :

    • Конструктор с C-style строкой (например, "Hello").

    • Конструктор без параметров (пустая строка).

  3. Методы :

    • Length() для получения длины строки.

    • Concat(const String& other) для конкатенации строк.

    • Substring(int start, int length) для получения подстроки.

  4. Перегрузка операторов :

    • Перегрузите оператор + для конкатенации строк.

    • Перегрузите оператор == для сравнения строк.

  5. Дополнительно : реализуйте метод Find(char c), который возвращает индекс первого вхождения символа в строку.

Показать полностью
[моё] IT Программирование C++ Windows Программист Длиннопост
2
10
mcnikirikitiki
Лига программистов

Основы программирования на C++: Адаптеры и представления⁠⁠

9 месяцев назад

Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.

Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.

Здравствуйте, мои маленькие любители программирования!

Адаптеры и представления — это инструменты, которые позволяют работать с данными более удобным и эффективным способом, не создавая новых копий данных. Они не являются самостоятельными контейнерами, а используют существующие структуры для хранения данных, предоставляя при этом специализированный интерфейс.


Адаптеры

1. std::stack

Стек — это структура данных, которая работает по принципу LIFO (Last In, First Out). Это означает, что последний добавленный элемент будет первым извлеченным.

  • Основные операции:

    • push: Добавляет элемент на вершину стека.

    • pop: Удаляет элемент с вершины стека.

    • top: Возвращает элемент на вершине стека без его удаления.

    • empty: Проверяет, пуст ли стек.

#include <iostream>

#include <stack>

int main() {

std::stack<int> s;

s.push(1);

s.push(13);

s.pop(); // Удаляем элемент с вершины (останется только 1)

std::cout << s.top() << "\n"; // Выводим элемент на вершине (1)

if (s.empty()) {

std::cout << "Stack is empty\n";

}

}

Особенности:

  • По умолчанию std::stack использует std::deque для хранения данных, но можно заменить его на другой контейнер, например, std::list.


2. std::queue

Очередь реализует принцип FIFO (First In, First Out). Элементы добавляются в конец очереди, а извлекаются из начала.

  • Основные операции:

    • push: Добавляет элемент в конец очереди.

    • pop: Удаляет элемент из начала очереди.

    • front: Возвращает первый элемент очереди.

    • back: Возвращает последний элемент очереди.

    • empty: Проверяет, пуста ли очередь.

#include <iostream>

#include <queue>

int main() {

std::queue<int> q;

q.push(1);

q.push(13);

std::cout << q.front() << "\n"; // Выводит первый элемент (1)

std::cout << q.back() << "\n"; // Выводит последний элемент (13)

q.pop(); // Удаляем первый элемент (останется только 13)

if (q.empty()) {

std::cout << "Queue is empty\n";

}

}

Особенности:

  • По умолчанию используется std::deque, но можно заменить его на другой контейнер, поддерживающий операции push_back, pop_front и т. д.


3. std::priority_queue

Очередь с приоритетами позволяет быстро получать максимальный (или минимальный) элемент. Она основана на структуре данных "куча".

  • Основные операции:

    • push: Добавляет элемент в очередь.

    • pop: Удаляет максимальный элемент.

    • top: Возвращает максимальный элемент без его удаления.

    • empty: Проверяет, пуста ли очередь.

#include <iostream>

#include <queue>

int main() {

std::priority_queue<int> pq;

for (int x : {3, 14, 15, 92, 6, 0, 1, 10}) {

pq.push(x);

}

while (!pq.empty()) {

std::cout << pq.top() << "\n"; // Выводит элементы в порядке убывания

pq.pop();

}

}

Особенности:

  • По умолчанию используется std::vector для хранения данных.

  • Можно изменить порядок сортировки, передав третий параметр, например, std::greater<int> для получения минимального элемента.


Представления

1. std::string_view

std::string_view — это легковесная обертка над строкой, которая позволяет работать с подстроками без создания их копий. Она не владеет памятью, а лишь ссылается на существующую строку.

  • Преимущества:

    • Быстрее, чем создание новой строки (std::string).

    • Экономит память, так как не копирует данные.

#include <iostream>

#include <string>

#include <string_view>

int main() {

std::string s = "Hello, world! How do you do?";

std::string_view sv = s; // Создаем string_view для строки s

auto sub = sv.substr(7, 5); // Выделяем подстроку "world"

std::cout << sub << "\n"; // Выводит "world"

std::cout << s << "\n"; // Исходная строка не изменилась

}

Ограничения:

  • std::string_view не позволяет изменять строку.

  • Ссылается на исходную строку, поэтому если строка уничтожается, string_view становится невалидным.


2. Пример некорректного использования std::string_view

Если исходная строка выходит из области видимости, string_view теряет доступ к данным:

#include <iostream>

#include <string>

#include <string_view>

#include <vector>

int main() {

std::vector<std::string_view> lines;

for (int i = 0; i < 5; ++i) {

std::string line;

std::getline(std::cin, line);

lines.push_back(line); // string_view ссылается на временную строку

}

for (auto item : lines) {

std::cout << item << "\n"; // Ошибка! Строки уже уничтожены

}

}

Решение: Используйте std::string, если данные должны сохраняться после выхода из области видимости.


3. std::span

std::span — аналог std::string_view, но для массивов или векторов. Он предоставляет доступ к непрерывной последовательности элементов в памяти.

#include <iostream>

#include <vector>

#include <span>

int main() {

std::vector<int> v = {1, 2, 3, 4, 5};

std::span<int> span(v); // Создаем span для вектора

for (int x : span) {

std::cout << x << " "; // Выводит элементы вектора

}

}

Особенности:

  • Не владеет данными.

  • Может использоваться для работы с подмассивами.


Заключение

Адаптеры и представления позволяют работать с данными более эффективно:

  • Адаптеры (std::stack, std::queue, std::priority_queue) предоставляют специализированный интерфейс для работы с контейнерами.

  • Представления (std::string_view, std::span) позволяют избежать ненужных копирований данных, но требуют осторожности при работе с временными объектами.

Задачи на std::stack

  1. Обратный порядок слов
    Напишите программу, которая принимает строку текста и выводит слова в обратном порядке, используя std::stack.
    Пример:
    Ввод: "Hello world from C++"
    Вывод: "C++ from world Hello"

  2. Проверка правильности скобок
    Напишите программу, которая проверяет, правильно ли расставлены скобки в строке (круглые, квадратные и фигурные). Используйте std::stack.
    Пример:
    Ввод: "{[()()]}" → Вывод: "Правильно"
    Ввод: "{[(])}" → Вывод: "Неправильно"

  3. Калькулятор с обратной польской записью
    Реализуйте калькулятор, который вычисляет выражения в обратной польской записи (RPN). Например:
    Ввод: "3 4 + 2 * 7 /" → Вывод: 2


Задачи на std::queue

  1. Симуляция очереди в банке
    Напишите программу, которая моделирует очередь клиентов в банке. Каждый клиент имеет номер. Программа должна позволять добавлять новых клиентов в очередь и обслуживать их по принципу FIFO.
    Пример:
    Ввод:

    add 1

    add 2

    serve

    Вывод: "Client 1 served"

  2. Поиск минимального элемента в очереди
    Напишите программу, которая находит минимальный элемент в очереди за один проход. Используйте только операции push, pop и front.

  3. Симуляция круговой очереди
    Реализуйте круговую очередь (например, карусель), где после обслуживания последнего клиента очередь начинается заново. Программа должна позволять добавлять новых клиентов и обслуживать их.


Задачи на std::priority_queue

  1. Топ-N самых больших чисел
    Напишите программу, которая находит N самых больших чисел из потока данных. Используйте std::priority_queue.
    Пример:
    Ввод: N = 3, данные = {5, 1, 9, 2, 8, 3} → Вывод: {9, 8, 5}

  2. Сортировка задач по приоритету
    У вас есть список задач с приоритетами. Напишите программу, которая обрабатывает задачи в порядке убывания приоритета.
    Пример:
    Ввод:

    Task1 Priority=3

    Task2 Priority=1

    Task3 Priority=2

    Вывод:

    Task1

    Task3

    Task2

  3. Минимальная куча
    Реализуйте минимальную кучу с помощью std::priority_queue и напишите программу, которая выводит элементы в порядке возрастания.


Задачи на std::string_view

  1. Подсчет подстрок
    Напишите программу, которая подсчитывает количество вхождений заданной подстроки в строку, используя std::string_view.
    Пример:
    Ввод: "Hello world", "o" → Вывод: 2

  2. Разбиение строки на слова
    Напишите программу, которая разбивает строку на слова, используя std::string_view.
    Пример:
    Ввод: "Hello world from C++" → Вывод:

    Hello

    world

    from

    C++

  3. Проверка префикса и суффикса
    Напишите программу, которая проверяет, начинается ли строка с заданного префикса и заканчивается ли заданным суффиксом, используя std::string_view.
    Пример:
    Ввод: "Hello world", prefix="Hello", suffix="world" → Вывод: "Да"


Задачи на std::span

  1. Сумма элементов массива
    Напишите программу, которая вычисляет сумму элементов массива или вектора, используя std::span.
    Пример:
    Ввод: {1, 2, 3, 4, 5} → Вывод: 15

  2. Поиск максимального элемента
    Напишите программу, которая находит максимальный элемент в массиве или векторе, используя std::span.

  3. Реверс части массива
    Напишите программу, которая переворачивает часть массива, используя std::span.
    Пример:
    Ввод: {1, 2, 3, 4, 5}, start=1, end=4 → Вывод: {1, 4, 3, 2, 5}


Комбинированные задачи

  1. Очередь с приоритетом задач
    Реализуйте систему управления задачами, где каждая задача имеет приоритет и описание. Используйте std::priority_queue для хранения задач и std::string_view для работы с описанием.

  2. Стек с поддержкой минимума
    Реализуйте стек, который поддерживает операцию получения минимального элемента за O(1). Используйте два std::stack.

  3. Анализ логов
    Напишите программу, которая анализирует лог-файл и выводит топ-N самых частых сообщений. Используйте std::unordered_map для подсчета частот и std::priority_queue для выбора топ-N.

  4. Работа с подстроками
    Напишите программу, которая находит самую длинную подстроку без повторяющихся символов, используя std::string_view.

  5. Многомерные массивы
    Реализуйте функцию, которая работает с двумерным массивом через std::span. Например, найдите сумму всех элементов или максимальный элемент.

Показать полностью
[моё] Программа Гайд C++ IT Программирование Длиннопост
6
1
mcnikirikitiki
Лига программистов

Основы программирования на C++: Алгоритмы⁠⁠

9 месяцев назад

Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.

Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.

Здравствуйте, мои маленькие любители программирования!

1. Что такое алгоритмы в C++?

Алгоритмы — это готовые функции, которые помогают выполнять типичные задачи с последовательностями элементов, такими как:

  • Подсчет элементов.

  • Поиск элементов.

  • Сортировка данных.

  • Модификация последовательностей.

Эти алгоритмы работают с итераторами , которые представляют собой указатели на элементы контейнера (например, vector, list, deque и т.д.).


2. Как работают итераторы?

Итераторы — это объекты, которые позволяют перебирать элементы контейнера. Они похожи на указатели, но могут быть адаптированы для разных типов контейнеров.

Основные правила работы с итераторами:

  1. Два итератора задают диапазон :

    • Первый итератор указывает на начало диапазона.

    • Второй итератор указывает за последний элемент диапазона (так называемый "полуинтервал").

    • Например: [first, last) означает, что first включается, а last исключается.

  2. Итераторы одного контейнера :

    • Итераторы должны принадлежать одному и тому же контейнеру.

  3. Операции с итераторами :

    • Можно двигаться вперед с помощью оператора ++.

    • Можно сравнивать итераторы (iter1 == iter2 или iter1 != iter2).

    • Можно разыменовывать итераторы (*iter) для доступа к значению.


3. Примеры работы с алгоритмами

Подсчет элементов

Функция std::count подсчитывает, сколько раз встречается определенное значение в последовательности.

#include <iostream>

#include <vector>

#include <algorithm>

int main() {

std::vector<int> v = {2, 7, 1, 8, 2, 8};

int count = std::count(v.begin(), v.end(), 8); // Сколько раз встречается число 8

std::cout << "Число 8 встречается " << count << " раз(а).\n";

}

Как это работает?

  • Алгоритм проходит по всем элементам в диапазоне [v.begin(), v.end()).

  • Если элемент равен заданному значению (в данном случае 8), счетчик увеличивается.


Поиск элемента

Функция std::find ищет первый элемент, равный заданному значению.

#include <iostream>

#include <deque>

#include <algorithm>

int main() {

std::deque<int> d = {3, 14, 15, 92, 6};

auto it = std::find(d.begin(), d.end(), 15); // Ищем число 15

if (it != d.end()) {

std::cout << "Найдено число: " << *it << "\n";

} else {

std::cout << "Число не найдено.\n";

}

}

Как это работает?

  • Алгоритм проходит по всем элементам в диапазоне [d.begin(), d.end()).

  • Если элемент найден, возвращается итератор на него.

  • Если элемент не найден, возвращается d.end().


Сортировка

Функция std::sort сортирует элементы в порядке возрастания.

#include <iostream>

#include <vector>

#include <algorithm>

int main() {

std::vector<int> data = {100, 42, 17, 80, 20, 0};

std::sort(data.begin(), data.end()); // Сортируем все элементы

for (int x : data) {

std::cout << x << " "; // 0 17 20 42 80 100

}

}

Как это работает?

  • Алгоритм использует быструю сортировку (quicksort) или другую эффективную стратегию.

  • Элементы переставляются так, чтобы они шли в порядке возрастания.


4. Алгоритмы с предикатами

Предикат — это функция, которая возвращает true или false. Алгоритмы с суффиксом _if используют предикаты для выполнения условий.

Пример: std::count_if

Подсчитаем, сколько заглавных букв в строке.

#include <iostream>

#include <string>

#include <algorithm>

int main() {

std::string s = "iPhone SE";

int count = std::count_if(s.begin(), s.end(), [](char c) {

return ('A' <= c && c <= 'Z'); // Проверяем, является ли символ заглавной буквой

});

std::cout << "Заглавных букв: " << count << "\n"; // 3

}

Как это работает?

  • Лямбда-функция проверяет, является ли символ заглавной буквой.

  • Алгоритм применяет эту функцию ко всем элементам строки.


5. Модификация последовательности

Алгоритмы могут изменять порядок или значения элементов.

Пример: std::reverse

Переворачивает порядок элементов.

#include <iostream>

#include <string>

#include <algorithm>

int main() {

std::string s = "No lemon, no melon!";

std::reverse(s.begin(), s.end()); // Переворачиваем строку

std::cout << s << "\n"; // !nolem on ,nomel oN

}

Как это работает?

  • Алгоритм меняет местами первый и последний элементы, второй и предпоследний, и так далее.


6. Копирование данных

Функция std::copy копирует элементы из одной последовательности в другую.

#include <iostream>

#include <vector>

#include <list>

#include <algorithm>

int main() {

std::vector<int> v = {3, 14, 15, 92, 6};

std::list<int> l(v.size()); // Создаем список того же размера

std::copy(v.begin(), v.end(), l.begin()); // Копируем данные

for (int x : l) {

std::cout << x << " "; // 3 14 15 92 6

}

}

Как это работает?

  • Алгоритм берет каждый элемент из входной последовательности и записывает его в выходную.


7. Теоретико-множественные операции

Если последовательности отсортированы, можно выполнять операции над множествами.

Пример: std::set_union

Объединяет два множества.

#include <iostream>

#include <vector>

#include <algorithm>

int main() {

std::vector<int> a = {1, 3, 5, 7};

std::vector<int> b = {2, 3, 6};

std::vector<int> result;

std::set_union(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(result));

for (int x : result) {

std::cout << x << " "; // 1 2 3 5 6 7

}

}

Как это работает?

  • Алгоритм объединяет два отсортированных множества, сохраняя порядок.


8. Бинарный поиск

Для отсортированных последовательностей можно использовать бинарный поиск.

Пример: std::binary_search

Проверяет, есть ли элемент в последовательности.

#include <iostream>

#include <vector>

#include <algorithm>

int main() {

std::vector<int> data = {1, 4, 5, 9, 9, 13, 47};

bool found = std::binary_search(data.begin(), data.end(), 5); // Ищем число 5

std::cout << (found ? "Найдено" : "Не найдено") << "\n";

}

Как это работает?

  • Алгоритм делит последовательность пополам и проверяет, находится ли элемент в левой или правой части.


Заключение

Алгоритмы стандартной библиотеки C++ — это мощный инструмент для работы с данными. Они универсальны, эффективны и легко интегрируются в код. Чтобы использовать их правильно:

  1. Убедитесь, что вы понимаете, как работают итераторы.

  2. Выбирайте подходящий алгоритм для вашей задачи.

  3. Используйте предикаты для сложных условий.

Теперь вы знаете основы работы с алгоритмами и можете применять их в своих программах!

Вот несколько задач для закрепления материала по алгоритмам C++:

Подсчет и поиск:

  • Напишите программу, которая считает количество четных чисел в векторе

  • Найдите первое отрицательное число в списке или выведите сообщение, что их нет

    Сортировка:

  • Отсортируйте массив строк по длине (от коротких к длинным)

  • Создайте вектор чисел и отсортируйте его в обратном порядке

    Алгоритмы с предикатами:

  • Посчитайте количество гласных букв в строке

  • Найдите первое слово в векторе строк, начинающееся на заданную букву

    Модификация последовательностей:

  • Переверните порядок слов в строке

  • Удалите все повторяющиеся элементы из вектора

    Копирование данных:

  • Скопируйте только положительные числа из одного вектора в другой

  • Создайте копию строки, преобразовав все буквы в верхний регистр

    Теоретико-множественные операции:

  • Найдите общие элементы двух отсортированных массивов

  • Объедините два отсортированных списка уникальных чисел

    Бинарный поиск:

  • Проверьте, содержит ли отсортированный массив заданное число

  • Найдите первое число, большее заданного, в отсортированном векторе

    Комбинированные задачи:

  • Отсортируйте список слов и подсчитайте количество уникальных

  • Найдите все числа в диапазоне [a, b] в отсортированном массиве

  • Удалите все четные числа из вектора и отсортируйте оставшиеся

    Работа с пользовательским вводом:

  • Запросите у пользователя набор чисел, отсортируйте их и найдите медиану

  • Создайте телефонный справочник (вектор пар имя-телефон) и реализуйте поиск по имени

    Обработка текста:

  • Посчитайте частоту встречаемости каждой буквы в тексте

  • Разбейте текст на слова и выведите только те, что длиннее 5 символов

Показать полностью
[моё] IT Программирование Гайд C++ Длиннопост
3
georgiyozhegov
georgiyozhegov
Программисты шутят

Про Rust⁠⁠

9 месяцев назад
Про Rust
Показать полностью 1
[моё] IT юмор Rust C++ Программирование IT Юмор Грустный юмор Мемы Боль
0
105
terebiata
terebiata
Юмор для всех и каждого

Шутка просто бомба⁠⁠

10 месяцев назад
Шутка просто бомба

Телеграм - https://t.me/roflemem/4572

Картинка с текстом Юмор Программирование C++ Арабы IT юмор Telegram (ссылка)
8
4
mcnikirikitiki
Лига программистов

Основы программирования на C++: Последовательные контейнеры⁠⁠

10 месяцев назад

Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.

Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.

Здравствуйте, мои маленькие любители программирования!

Последовательные контейнеры

Стандартная библиотека C++ предлагает набор шаблонных контейнеров, с некоторыми из которых вы уже знакомы: это std::vector и std::string. Эти два контейнера гарантируют, что элементы (или символы в случае строки) будут храниться в непрерывном участке памяти. Они эффективно добавляют элементы в конец, при необходимости делая реаллокацию, но не могут обеспечить эффективную вставку или удаление элементов в других местах.

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

Контейнер std::array

Если вам нужен массив фиксированного размера, известного на этапе компиляции, используйте std::array. Вот пример объявления array из трёх элементов:

#include <array>

int main() {

std::array<int, 3> point = {1, 2, -3};

}

std::array является обёрткой над низкоуровневым массивом T[N], предоставляя интерфейс стандартного контейнера: он знает свой размер, умеет присваиваться, предоставляет итераторы и т.д. Как и у вектора, элементы array располагаются в памяти непрерывно, но хранятся не в динамической памяти, а на стеке. Размер array должен быть задан на этапе компиляции и не может изменяться во время выполнения программы.

Контейнер std::deque

Deque расшифровывается как double-ended queue (двусторонняя очередь). В отличие от вектора, который размещает элементы в памяти непрерывно, std::deque размещает их кусочно-непрерывно, в отдельных страницах (непрерывных блоках) памяти фиксированного размера. Даже для одного элемента в деке будет выделена целая страница. Сами страницы не обязательно расположены в памяти подряд. Отдельно поддерживается список указателей на начала страниц. Размеры страниц зависят от sizeof(T) и от конкретной реализации дека.

Дек эффективно добавляет и удаляет элементы в начале и в конце без необходимости реаллокации и копирования старых элементов. Вставка по краям в деке эффективнее, чем в векторе. Однако вставка в середину и удаление из неё требуют сдвига элементов. Обращение к элементу по индексу происходит за O(1), но требует двух разыменований указателей, в то время как вектору достаточно одного.

Пример использования deque:

#include <deque>

#include <iostream>

int main() {

std::deque<int> d = {1, 2, 3, 4};

d.push_back(5); // добавление в конец

d.push_back(6);

d.pop_back(); // удаление из конца

d.push_front(0); // добавление в начало

d.push_front(-1);

d.pop_front(); // удаление из начала

for (size_t i = 0; i != d.size(); ++i) {

std::cout << d[i] << "\n";

}

for (int x : d) {

std::cout << x << "\n";

}

}

Контейнер std::list

Двусвязный список std::list хранит элементы в отдельных узлах, которые могут располагаться в разных местах памяти. Узлы содержат указатели на предыдущий и следующий узлы. Пройтись по списку можно только с помощью range-based for или итераторов.

Пример использования list:

#include <list>

#include <iostream>

int main() {

std::list<int> l = {10, 15, 20};

l.push_front(5);

l.push_front(0);

l.push_back(25);

l.push_back(30);

l.pop_front();

l.pop_back();

for (int x : l) {

std::cout << x << "\n"; // 5 10 15 20 25

}

}

Итераторы списка

Итераторы — это объекты для навигации по контейнеру. Они позволяют обращаться к текущему элементу и сдвигаться к соседним. Функция begin возвращает итератор, указывающий на начальный элемент контейнера.

Пример использования итераторов:

#include <list>

#include <iostream>

int main() {

std::list<int> l = {10, 15, 20};

auto iter = l.begin();

std::cout << *iter << "\n"; // печатаем начальный элемент

++iter; // сдвигаемся к следующему элементу

--iter; // возвращаемся назад

}

Функция end возвращает итератор, указывающий за последний элемент контейнера. Этот итератор нельзя разыменовывать, но можно сравнивать с ним.

Пример прохода по списку:

#include <list>

#include <iostream>

int main() {

std::list<int> l = {10, 15, 20};

for (auto iter = l.begin(); iter != l.end(); ++iter) {

std::cout << *iter << "\n"; // печатаем элементы списка через итератор

}

for (auto iter = l.rbegin(); iter != l.rend(); ++iter) {

std::cout << *iter << "\n"; // проход по списку в обратном порядке

}

}

С итераторами можно вставлять или удалять элементы в любом месте списка.

Пример вставки и удаления элементов:

#include <list>

#include <iostream>

int main() {

std::list<int> l = {0, 10, 15, 20};

auto iter = l.begin();

++iter;

l.insert(iter, 5); // вставляем на эту позицию элемент

for (auto iter = l.begin(); iter != l.end(); ) {

if (*iter % 2 == 0) {

iter = l.erase(iter); // возвращается итератор на элемент, следующий за удалённым

} else {

++iter;

}

}

}

Контейнер std::forward_list

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

Пример использования forward_list:

#include <forward_list>

#include <iostream>

int main() {

std::forward_list<int> fl = {3, 42, 5};

fl.push_front(2);

auto iter = std::next(fl.begin());

iter = fl.erase_after(iter);

fl.insert_after(iter, 4);

for (int x : fl) {

std::cout << x << "\n"; // 2 3 5 4

}

}

Инвалидация итераторов и ссылок

При изменении контейнера итераторы и ссылки на элементы могут стать невалидными. Например, если у вектора произошла реаллокация, все итераторы и ссылки инвалидируются. В std::array ничего вставить нельзя, его размер фиксирован. В std::deque инвалидируются итераторы, но не инвалидируются ссылки и указатели. В std::list и std::forward_list ни итераторы, ни ссылки не инвалидируются.

Пример инвалидации итераторов:

#include <vector>

#include <iostream>

int main() {

std::vector<int> v = {1, 2, 3, 4};

auto iter = v.begin(); // итератор

int* ptr = &v.front(); // указатель

int& ref = v.front(); // ссылка

std::cout << *iter << " " << *ptr << " " << ref << "\n"; // 1 1 1

v.push_back(5); // происходит реаллокация

// обращаться к старым итераторам, указателям и ссылкам больше нельзя:

std::cout << *iter << " " << *ptr << " " << ref << "\n"; // неопределённое поведение!

}

Ассоциативные контейнеры в C++

Ассоциативные контейнеры в C++ позволяют хранить данные в виде пар "ключ-значение". Они обеспечивают быстрый доступ к значениям по ключу. В стандартной библиотеке C++ есть два основных типа ассоциативных контейнеров:

1. Контейнеры на основе сбалансированных деревьев:

- `std::map` — хранит пары "ключ-значение" в отсортированном порядке.

- `std::set` — хранит только уникальные ключи в отсортированном порядке.

2. Контейнеры на основе хеш-таблиц:

- `std::unordered_map` — хранит пары "ключ-значение" без гарантии порядка.

- `std::unordered_set` — хранит только уникальные ключи без гарантии порядка.

Также существуют "мульти" версии этих контейнеров (`multimap`, `multiset`, `unordered_multimap`, `unordered_multiset`), которые позволяют хранить несколько значений для одного ключа.

---

Контейнер `std::map`

`std::map` — это ассоциативный контейнер, который хранит пары "ключ-значение" в отсортированном порядке. Он реализован как красно-чёрное дерево, что обеспечивает логарифмическое время выполнения операций поиска, вставки и удаления.

Пример использования `std::map`:

#include <iostream>

#include <map>

#include <string>

int main() {

std::map<std::string, int> years = {

{"Moscow", 1147},

{"Rome", -753},

{"London", 47},

};

// Добавление элемента

years["Paris"] = 52;

// Итерация по элементам

for (const auto& [city, year] : years) {

std::cout << city << ": " << year << "\n";

}

// Поиск элемента

if (auto it = years.find("Rome"); it != years.end()) {

std::cout << "Found: " << it->first << " -> " << it->second << "\n";

}

// Удаление элемента

years.erase("London");

}

Основные операции:

- Вставка: `years[key] = value` или `years.insert({key, value})`.

- Поиск: `years.find(key)`.

- Удаление: `years.erase(key)`.

Контейнер `std::unordered_map`

`std::unordered_map` — это ассоциативный контейнер, основанный на хеш-таблицах. Он обеспечивает среднее время выполнения операций O(1), но не гарантирует порядок элементов.

Пример использования `std::unordered_map`:

#include <iostream>

#include <unordered_map>

#include <string>

int main() {

std::unordered_map<std::string, int> freqs;

std::string word;

// Подсчёт частот слов

while (std::cin >> word) {

++freqs[word];

}

// Вывод результатов

for (const auto& [word, freq] : freqs) {

std::cout << word << ": " << freq << "\n";

}

}

Основные операции:

- Вставка: `freqs[key] = value` или `freqs.insert({key, value})`.

- Поиск: `freqs.find(key)`.

- Удаление: `freqs.erase(key)`.

Контейнеры `std::set` и `std::unordered_set`

Эти контейнеры хранят только уникальные ключи. Они полезны, когда нужно проверить наличие элемента в коллекции.

Пример использования `std::set`:

#include <iostream>

#include <set>

#include <string>

int main() {

std::set<std::string> unique_words;

std::string word;

// Сбор уникальных слов

while (std::cin >> word) {

unique_words.insert(word);

}

// Вывод уникальных слов

for (const auto& word : unique_words) {

std::cout << word << "\n";

}

}

Мультиконтейнеры (`multimap`, `multiset`, `unordered_multimap`, `unordered_multiset`) позволяют хранить несколько значений для одного ключа.

Пример использования `std::multimap`:

#include <iostream>

#include <map>

#include <string>

int main() {

std::multimap<std::string, int> positions;

std::string word;

int position = 0;

// Сохранение позиций слов

while (std::cin >> word) {

positions.insert({word, position});

++position;

}

// Вывод всех позиций для слова "hello"

auto range = positions.equal_range("hello");

for (auto it = range.first; it != range.second; ++it) {

std::cout << "Position: " << it->second << "\n";

}

}

Итераторы ассоциативных контейнеров

Итераторы позволяют обходить элементы контейнера. Для `map` и `set` итераторы двусторонние, а для `unordered_map` и `unordered_set` — однонаправленные.

Пример работы с итераторами:

#include <iostream>

#include <map>

int main() {

std::map<int, std::string> numbers = {

{1, "one"},

{2, "two"},

{3, "three"},

};

auto it = numbers.find(2);

if (it != numbers.end()) {

std::cout << "Found: " << it->first << " -> " << it->second << "\n";

}

}

Ассоциативные контейнеры в C++ — это мощный инструмент для работы с данными, организованными по принципу "ключ-значение". Выбор между `map` и `unordered_map` зависит от требований к порядку элементов и производительности. Для хранения уникальных ключей используйте `set` или `unordered_set`, а для работы с дубликатами — мультиконтейнеры.

Показать полностью
[моё] Программирование C++ IT Программист Программа Текст Длиннопост
6
3
mcnikirikitiki
Лига программистов

Основы программирования на C++: функции, шаблоны⁠⁠

10 месяцев назад

Прежде чем читать мою статью - реши для себя, зачем ты это делаешь. Даже если ты просто нормальный человек, лишним не будет.

Если вы настоящий профессионал программирования, то зачем вы тут сидите и читайте статью на пикабу? Если вы ради интереса зашли почитать, то претензий ноль, но если вы просто захотели задушить нового пользователя Пикабу минусами, то немедленно покиньте статью, вам однозначно интересно не будет.

Здравствуйте, мои маленькие любители программирования!

Функции — это фундаментальный элемент программирования, который позволяет структурировать код, избегать повторений и упрощать его понимание. Они позволяют выделить часто используемый код в отдельные блоки, которые можно многократно использовать с разными входными данными.

Основные концепции функций

  1. Что такое функция? Функция — это блок кода, который выполняет определенную задачу. Она может принимать входные данные (аргументы), обрабатывать их и возвращать результат. В языке C++ каждая программа начинается с выполнения функции main(), которая является точкой входа в программу.

    int main() {

    // Код программы

    return 0;

    }

    • int перед main() указывает, что функция возвращает целое число.

    • return 0; означает успешное завершение программы.

  2. Создание простой функции

    Рассмотрим пример функции, которая складывает два числа:

    int Sum(int a, int b) {

    return a + b;

    }

    • int перед именем функции указывает, что функция возвращает значение типа int.

    • Sum — это имя функции.

    • (int a, int b) — это параметры функции, то есть значения, которые передаются в функцию при её вызове.

    Пример использования функции Sum:

    int main() {

    int x = 17, y = 42;

    int z = Sum(x, y); // Вызов функции Sum

    std::cout << "Сумма: " << z << "\n"; // Выведет 59

    }

  3. Функции без возвращаемого значения

    Если функция не должна возвращать результат, её можно объявить как void. Это означает, что функция просто выполняет какие-то действия, но не возвращает никаких данных.

    void DoSomething(double d, char c) {

    std::cout << "Число: " << d << ", Символ: " << c << "\n";

    }

    Пример использования:

    int main() {

    DoSomething(3.14, '@'); // Выведет: Число: 3.14, Символ: @

    }

  4. Рекурсивные функции

    Рекурсия — это когда функция вызывает саму себя. Это полезно для решения задач, которые могут быть разделены на подзадачи того же типа. Например, вычисление факториала числа:

    #include <cstdint> // Для uint64_t

    std::uint64_t Factorial(std::uint64_t n) {

    if (n == 0) {

    return 1; // Базовый случай

    }

    return n * Factorial(n - 1); // Рекурсивный вызов

    }

    Пример использования:

    int main() {

    std::cout << "Факториал 5: " << Factorial(5) << "\n"; // Выведет 120

    }

    Важно: При использовании рекурсии нужно быть осторожным, чтобы не создать бесконечный цикл вызовов, что может привести к переполнению стека.


Аргументы функций

  1. Передача аргументов по значению

    По умолчанию аргументы передаются "по значению", то есть функция работает с копиями переменных. Любые изменения внутри функции не влияют на исходные переменные.

    void f(int x, int y) {

    x = 10; // Изменение x не повлияет на внешнюю переменную

    y = 20; // Изменение y не повлияет на внешнюю переменную

    }

    int main() {

    int a = 1, b = 2;

    f(a, b);

    std::cout << "a: " << a << ", b: " << b << "\n"; // Выведет: a: 1, b: 2

    }

  2. Передача аргументов по ссылке

    Чтобы изменять исходные переменные внутри функции, используются ссылки (&). Это позволяет функции работать с оригинальными переменными, а не с их копиями.

    void Swap(int& x, int& y) { // Передача по ссылке

    int temp = x;

    x = y;

    y = temp;

    }

    int main() {

    int a = 1, b = 2;

    Swap(a, b);

    std::cout << "a: " << a << ", b: " << b << "\n"; // Выведет: a: 2, b: 1

    }

  3. Константные ссылки

    Для сложных типов данных (например, векторов или строк) лучше передавать аргументы по константной ссылке (const &). Это позволяет избежать лишнего копирования данных и защищает их от случайного изменения внутри функции.

    void PrintVector(const std::vector<int>& vec) {

    for (int num : vec) {

    std::cout << num << " ";

    }

    std::cout << "\n";

    }

    int main() {

    std::vector<int> numbers = {1, 2, 3, 4, 5};

    PrintVector(numbers); // Выведет: 1 2 3 4 5

    }


Возвращаемые значения функций

  1. Возврат простых типов

    Функция может возвращать любые типы данных, такие как int, double, char и т.д. Например:

    double Divide(double a, double b) {

    return a / b;

    }

    int main() {

    double result = Divide(10.0, 2.0);

    std::cout << "Результат деления: " << result << "\n"; // Выведет: 5.0

    }

  2. Возврат сложных типов

    Возврат сложных типов данных (например, векторов или строк) также возможен. Компилятор автоматически оптимизирует процесс копирования таких объектов.

    std::string Concatenate(const std::vector<std::string>& parts) {

    std::string result;

    for (const auto& part : parts) {

    result += part;

    }

    return result;

    }

    int main() {

    std::vector<std::string> words = {"Hello", " ", "World", "!"};

    std::cout << Concatenate(words) << "\n"; // Выведет: Hello World!

    }

  3. Опасность возврата ссылок на локальные переменные

    Нельзя возвращать ссылку на локальную переменную, так как она будет уничтожена после завершения функции.

    int& GetLocalVariable() {

    int x = 10;

    return x; // Ошибка! x будет уничтожен после завершения функции

    }


Лямбда-функции

Лямбда-функции — это анонимные функции, которые можно определять непосредственно в месте использования. Они особенно полезны для коротких операций, например, для сортировки.

#include <algorithm>

#include <vector>

int main() {

std::vector<int> numbers = {5, 2, 8, 1, 9};

// Сортировка в обратном порядке с помощью лямбда-функции

std::sort(numbers.begin(), numbers.end(), [](int a, int b) {

return a > b; // Сравнение в обратном порядке

});

for (int num : numbers) {

std::cout << num << " "; // Выведет: 9 8 5 2 1

}

}


Шаблоны

Шаблоны позволяют создавать универсальный код, работающий с различными типами данных.

  1. Шаблонные функции

    Шаблонная функция позволяет работать с разными типами данных без необходимости писать отдельные версии для каждого типа.

    template<typename T>

    T Max(T a, T b) {

    return (a > b) ? a : b;

    }

    int main() {

    std::cout << "Максимум: " << Max(10, 20) << "\n"; // Выведет: 20

    std::cout << "Максимум: " << Max(3.14, 2.71) << "\n"; // Выведет: 3.14

    std::cout << "Максимум: " << Max("apple", "banana") << "\n"; // Выведет: banana

    }

  2. Шаблонные структуры

    Шаблоны также можно использовать для создания универсальных структур данных. Например, структура Triple, которая хранит три значения разных типов:

    template<typename T1, typename T2, typename T3>

    struct Triple {

    T1 first;

    T2 second;

    T3 third;

    };

    int main() {

    Triple<int, double, std::string> data = {1, 3.14, "Hello"};

    std::cout << "First: " << data.first << ", Second: " << data.second

    << ", Third: " << data.third << "\n";

    }


Это основные концепции работы с функциями и шаблонами в C++. Теперь вы знаете, как создавать и использовать функции, передавать аргументы, возвращать значения и работать с шаблонами.

Задачи на функции

1. Простые функции

  1. Функция "Приветствие"
    Напишите функцию void Greet(const std::string& name), которая выводит приветствие в формате:
    "Привет, [имя]!".
    Пример:

    Greet("Анна"); // Вывод: Привет, Анна!

  2. Вычисление площади прямоугольника
    Напишите функцию double CalculateRectangleArea(double width, double height), которая принимает ширину и высоту прямоугольника и возвращает его площадь.
    Пример:

    double area = CalculateRectangleArea(5.0, 3.0); // Результат: 15.0

  3. Проверка числа на четность
    Напишите функцию bool IsEven(int number), которая возвращает true, если число четное, и false — если нечетное.
    Пример:

    bool result = IsEven(4); // Результат: true


2. Функции с передачей по ссылке

  1. Увеличение значения на 1
    Напишите функцию void Increment(int& value), которая увеличивает переданное значение на 1.
    Пример:

    int x = 5;

    Increment(x);

    std::cout << x; // Вывод: 6

  2. Обмен значений двух переменных
    Реализуйте функцию void Swap(int& a, int& b), которая меняет значения двух переменных местами.
    Пример:

    int x = 10, y = 20;

    Swap(x, y);

    std::cout << x << " " << y; // Вывод: 20 10


3. Рекурсивные функции

  1. Числа Фибоначчи
    Напишите рекурсивную функцию int Fibonacci(int n), которая возвращает n-е число Фибоначчи.
    Пример:

    int result = Fibonacci(7); // Результат: 13

  2. Степень числа
    Напишите рекурсивную функцию int Power(int base, int exponent), которая возводит число base в степень exponent.
    Пример:

    int result = Power(2, 3); // Результат: 8


Задачи на шаблоны

4. Шаблонные функции

  1. Минимум двух чисел
    Напишите шаблонную функцию T Min(T a, T b), которая возвращает минимальное из двух значений.
    Пример:

    int result1 = Min(10, 20); // Результат: 10

    double result2 = Min(3.14, 2.71); // Результат: 2.71

  2. Сравнение строк
    Напишите шаблонную функцию bool AreEqual(T a, T b), которая возвращает true, если два значения равны, и false — если нет.
    Пример:

    bool result1 = AreEqual(5, 5); // Результат: true

    bool result2 = AreEqual("hello", "world"); // Результат: false

  3. Сумма элементов массива
    Напишите шаблонную функцию T SumArray(const std::vector<T>& arr), которая вычисляет сумму всех элементов вектора.
    Пример:

    std::vector<int> numbers = {1, 2, 3, 4};

    int result = SumArray(numbers); // Результат: 10


5. Шаблонные структуры

  1. Шаблонная структура "Точка"
    Создайте шаблонную структуру Point<T>, которая хранит координаты точки в двумерном пространстве. Добавьте метод void Print(), который выводит координаты точки.
    Пример:

    Point<int> p1 = {3, 4};

    p1.Print(); // Вывод: (3, 4)

  2. Шаблонная структура "Пара"
    Создайте шаблонную структуру Pair<T1, T2>, которая хранит два значения разных типов. Добавьте метод void Print(), который выводит значения пары.
    Пример:

    Pair<int, std::string> pair = {42, "Ответ"};

    pair.Print(); // Вывод: (42, Ответ)


Задачи на лямбда-функции

6. Лямбда-выражения

  1. Фильтрация чисел
    Используя лямбда-функцию, напишите программу, которая фильтрует числа из вектора, оставляя только четные.
    Пример:

    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};

    std::vector<int> evenNumbers;

    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(evenNumbers), [](int x) {

    return x % 2 == 0;

    });

    // Результат: evenNumbers = {2, 4, 6}

  2. Сортировка строк по длине
    Используя лямбда-функцию, отсортируйте вектор строк по их длине.
    Пример:

    std::vector<std::string> words = {"apple", "banana", "kiwi"};

    std::sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) {

    return a.size() < b.size();

    });

    // Результат: words = {"kiwi", "apple", "banana"}


Дополнительные задачи

7. Комбинированные задачи

  1. Калькулятор
    Напишите шаблонную функцию T Calculator(T a, T b, char operation), которая выполняет одну из операций (+, -, *, /) над двумя числами.
    Пример:

    int result = Calculator(10, 5, '+'); // Результат: 15

    double result2 = Calculator(10.0, 2.0, '/'); // Результат: 5.0

  2. Поиск максимального элемента в массиве
    Напишите шаблонную функцию T FindMax(const std::vector<T>& arr), которая возвращает максимальный элемент вектора.
    Пример:

    std::vector<int> numbers = {3, 1, 4, 1, 5, 9};

    int max = FindMax(numbers); // Результат: 9

  3. Подсчет слов в строке
    Напишите функцию int CountWords(const std::string& text), которая подсчитывает количество слов в строке. Слова разделены пробелами.
    Пример:

    int count = CountWords("Hello world!"); // Результат: 2


Эти задачи помогут вам закрепить основные концепции работы с функциями, шаблонами и лямбда-выражениями в C++. Вы можете начать с простых задач и постепенно переходить к более сложным. Удачи!

Показать полностью
[моё] Программирование Гайд IT C++ Windows Длиннопост
48
Посты не найдены
О нас
О Пикабу Контакты Реклама Сообщить об ошибке Сообщить о нарушении законодательства Отзывы и предложения Новости Пикабу Мобильное приложение RSS
Информация
Помощь Кодекс Пикабу Команда Пикабу Конфиденциальность Правила соцсети О рекомендациях О компании
Наши проекты
Блоги Работа Промокоды Игры Курсы
Партнёры
Промокоды Биг Гик Промокоды Lamoda Промокоды Мвидео Промокоды Яндекс Маркет Промокоды Пятерочка Промокоды Aroma Butik Промокоды Яндекс Путешествия Промокоды Яндекс Еда Постила Футбол сегодня
На информационном ресурсе Pikabu.ru применяются рекомендательные технологии