DNK_C_C++_Go_Rust
45 subscribers
14 photos
45 links
DNK - дневник кодера С и С++
Download Telegram
#458_C_NTWK_LIB

Функция connect() в С.

Функция connect() в языке программирования C предназначена для установления соединения с удаленным сервером по определенному адресу и порту.
Эта функция активно применяется при разработке клиентов для различных сетевых протоколов, таких как HTTP, FTP, SSH и др., работающих поверх протокола TCP.

Прототип функции:
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


Где:
sockfd — дескриптор ранее открытого сокета (полученный функцией socket()).
addr — структура типа sockaddr, содержащая адрес и порт удаленного узла, к которому устанавливается соединение.
addrlen — размер структуры sockaddr в байтах.


Структура sockaddr — типичная структура для хранения адреса имеет вид:
struct sockaddr_in {
sa_family_t sin_family; // Семейство адресов (AF_INET)
uint16_t sin_port; // Номер порта
struct in_addr sin_addr; // Адрес IP
};


Чтобы заполнить поля структуры, нужно задать семейство адресов, преобразовать номер порта в сетевой порядок байтов с помощью функции htons() и установить нужный IP-адрес.


Как работает connect()?
Процесс подключения осуществляется следующим образом:
Клиент создает сокет с помощью функции socket().
Заполняет структуру sockaddr_in нужными значениями (адресом и номером порта).
Вызывает функцию connect(), передавая ей созданный сокет и заполненную структуру.
Ожидает успешного завершения процесса установки соединения.


Пример использования:
Предположим, необходимо подключиться к веб-серверу по адресу example.com на стандартный порт HTTP (порт 80):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERVER_IP "74.125.239.113"
#define SERVER_PORT 80

int main(void) {
int sockfd;
struct sockaddr_in serv_addr;

// Создаем сокет
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Ошибка при создании сокета");
exit(EXIT_FAILURE);
}

memset(&serv_addr, 0, sizeof(serv_addr)); // Обнуляем структуру
serv_addr.sin_family = AF_INET; // Используем семейство IPv4
serv_addr.sin_port = htons(SERVER_PORT); // Устанавливаем порт (преобразуя в сетевой порядок)
inet_pton(AF_INET, SERVER_IP, &(serv_addr.sin_addr)); // Конвертируем IP-адрес в двоичный формат

// Подключаемся к серверу
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Ошибка подключения к серверу");
close(sockfd);
exit(EXIT_FAILURE);
}

printf("Подключение установлено!\n");

// Теперь можно отправлять запросы и получать ответы...

close(sockfd);
return 0;
}


Возвращаемые значения:
0: Соединение успешно установлено.
-1: Произошла ошибка, код ошибки доступен через переменную errno.

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


Важные моменты:
После успешного вызова connect() можно начинать передачу данных через сокет с использованием функций send() и recv().
Если сервер временно недоступен или занят, функция connect() блокирует выполнение программы до тех пор, пока соединение не установится или не произойдет таймаут.
Можно настроить неблокирующий режим сокета перед вызовом connect(), используя опцию fcntl() или флаг O_NONBLOCK, чтобы избежать блокировки потока исполнения.


Функция connect() является ключевой частью механизма взаимодействия с удалёнными серверами и лежит в основе большинства сетевых приложений на языке C.
#459_C_NTWK_LIB

Функции getaddrinfo() и tcp_connect() в С.

Функции getaddrinfo() и tcp_connect() широко применяются в программах на языке С для облегчения процедуры подключения к удалённому узлу по имени домена или IP-адресу.
Они решают ключевые задачи сетевого программирования, обеспечивая гибкое и переносимое решение для инициализации TCP-соединений.


Что делает каждая из функций?

getaddrinfo() — служит для перевода символьного имени узла (доменное имя или IP-адрес) вместе с именем сервиса (номер порта или название службы) в список структур, содержащих всю необходимую информацию для установки сетевого соединения.

Пример прототипа:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(
const char* node, // Имя узла (IP или DNS)
const char* service, // Порт или сервис
const struct addrinfo* hints, // Дополнительные опции
struct addrinfo** res // Результат (список адресов)
);


Параметры:
node: имя узла (либо IP-адрес, либо DNS-имя).
service: порт или название услуги (может быть задан как числовой порт или строка вида "http").
hints: дополнительные настройки, такие как тип сокета, протокол и желаемое семейство адресов.
res: выходная переменная, куда помещается связанный список найденных адресов.

Возвращает:
0, если операция выполнена успешно,
Код ошибки, если возникла проблема (например, неверный узел или служба).

Получив информацию через getaddrinfo(), программа выбирает подходящий адрес и устанавливает соединение.


tcp_connect()вспомогательная функция, реализующая подключение к серверу с помощью стандартного интерфейса BSD Socket API (такого как socket(), connect() и др.).
Обычно она помогает упростить процедуру создания сокета и установки TCP-соединения.

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

Пример реализации простой версии tcp_connect():
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int tcp_connect(const char* hostname, const char* port) {
struct addrinfo hints, *result, *rp;
int sfd, errcode;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // Разрешить оба семейства (IPv4 и IPv6)
hints.ai_socktype = SOCK_STREAM;// Тип сокета TCP
hints.ai_protocol = IPPROTO_TCP;// Протокол TCP

// Получаем список возможных адресов
errcode = getaddrinfo(hostname, port, &hints, &result);
if (errcode != 0) {
fprintf(stderr, "Ошибка getaddrinfo(): %s\n", gai_strerror(errcode));
return -1;
}

// Пробуем каждый полученный адрес
for (rp = result; rp != NULL; rp = rp->ai_next) {
// Создаем сокет
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) continue; // Перейти к следующему адресу

// Подключаемся к выбранному адресу
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; // Успешное подключение!

close(sfd); // Неудача с данным адресом, закрываем сокет
}

freeaddrinfo(result); // Освобождаем память

if (rp == NULL) { // Ни один адрес не подошел
fprintf(stderr, "Не удалось подключиться ни к одному адресу\n");
return -1;
}

return sfd; // Вернуть дескриптор сокета
}


Использование такой функции облегчает написание простых TCP-клиентов, скрывая рутинные шаги обработки адресов и создания сокетов.


getaddrinfo() полезна для трансляции имен узлов и сервисов в готовые сетевые адреса, облегчая доступ к удалённым ресурсам.

tcp_connect() представляет собой удобный способ создания и подключения TCP-сокета, упрощая последовательность действий и обработку ошибок.

Эти функции совместно помогают быстро и надёжно реализовать сетевую коммуникацию в приложениях на языке С.
#460_Cpp_NTWK_LIB

Популярные библиотеки для разработки сетевых приложений на С++.

При создании сетевых приложений на современном C++ наиболее популярными и эффективными являются следующие библиотеки:

Boost.Asio () — одна из самых распространенных библиотек для асинхронного ввода-вывода и сетевого взаимодействия.
Поддерживает низкоуровневые сетевые сокеты,
HTTP, WebSocket и прочие высокоуровневые абстракции.

Пример простого TCP-клиента на Boost.Asio:
#include <boost/asio.hpp>

int main() {
try {
boost::asio::ip::tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
boost::asio::write(socket, boost::asio::buffer("GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"));
boost::asio::read_until(socket, response_buffer, "\r\n");
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}



Poco Net () — легкая и простая в освоении библиотека для сетевого взаимодействия.
Предоставляет удобные классы для работы с HTTP, SMTP, POP3, FTP и SSL/TLS.

Простой HTTP-сервер:
#include <Poco/Net/HTTPServer.h>

void requestHandler(const HTTPRequest&, HTTPResponse&) {...}

int main() {
HTTPServer srv(new RequestHandlerFactory(), 8080);
srv.start();
}



Qt Network Module — мощный фреймворк, подходящий для GUI-проектов и мобильных приложений.
Предлагает элегантные классы для HTTP, FTP, TCP/IP и даже WebSocket.
Имеет интуитивные сигналы и слоты для реактивного стиля программирования.

Пример:
QTcpSocket socket;
connect(&socket, SIGNAL(connected()), SLOT(onConnected()));
socket.connectToHost("example.com", 80);



libuv () — известна благодаря использованию в Node.js, однако доступна и отдельно.
Очень быстрая и стабильная библиотека для многопоточности и асинхронного ввода-вывода.
Подходит для высоконагруженных серверов и игровых движков.



ZMQ (ZeroMQ) () — библиотека для асинхронного обмена сообщениями между компонентами программы.
Используется для построения масштабируемых и отказоустойчивых архитектур.
Идеальна для микросервисов и систем реального времени.



Google gRPC () — современный фреймворк для удалённых вызовов процедур (RPC).
Позволяет легко строить сервисы с автоматической генерацией клиентов и серверов на множестве языков.
Широко применяется для создания быстрых и надежных микросервисных архитектур.


Эти библиотеки обеспечивают надёжную основу для построения современных и производительных сетевых приложений на C++
Выбор конкретной библиотеки зависит от специфики проекта и поставленных целей.
Например, для простых сетей лучше подойдут легковесные решения вроде Poco или Boost.Asio, а для крупных корпоративных сервисов оптимально использовать ZMQ или gRPC.
#461_Cpp_LIB

Метод std::cin.peek в С++.

Метод std::cin.peek() позволяет заглянуть («подглядывать») в буфер потока ввода, не извлекая символ из него.
Другими словами, он возвращает следующий доступный символ из входящего потока, не удаляя его оттуда.

Метод полезен, когда нужно заранее посмотреть, какой символ придет следующим, перед тем как принять решение о дальнейших действиях.


Основные особенности метода peek():
Возвращает тип int, представляющий следующий символ из потока ввода.
Символ остается в потоке и доступен для последующего чтения методами, такими как
getchar() или getline().
Метод ничего не меняет в состоянии потока, кроме возвращения следующего символа.
Может вернуть специальный признак конца файла
(EOF), если поток достиг своего окончания.


Пример использования std::cin.peek().
Допустим, нужно пропустить пустые символы (пробелы, табуляции, переводы строки) и прочитать первое непустое значение:
#include <iostream>

int main() {
while(std::cin.peek() == ' ') { // пока впереди пробельный символ
std::cin.ignore(); // пропускаем его
}

// теперь считываем первый непробельный символ
char firstNonSpace;
std::cin.get(firstNonSpace);

std::cout << "Первый непробельный символ: " << firstNonSpace << std::endl;

return 0;
}


Что здесь происходит:
std::cin.peek() проверяет следующий символ в потоке ввода.
Если это пробел
(' '), то он игнорируется методом ignore().
Цикл продолжается, пока встречаются пробельные символы.
После завершения цикла первым символом станет тот, который не является пробелом, и он сохраняется в переменной firstNonSpace.


Еще один пример: проверка конца файла.
Можно использовать peek() для проверки конца файла (например, при чтении данных из файла):
#include <fstream>
#include <iostream>

int main() {
std::ifstream file("example.txt");

if (!file.is_open()) {
std::cerr << "Ошибка открытия файла." << std::endl;
return 1;
}

while(file.peek() != EOF) { // пока не конец файла
char current_char;
file.get(current_char); // берем очередной символ
std::cout << current_char;
}

file.close();
return 0;
}


Здесь:
Сначала проверяется наличие символа в файле через peek().
Если следующий символ равен EOF, значит достигнут конец файла, и цикл останавливается.
Иначе считывается символ с помощью get() и печатается на экран.


Полезные моменты:
Помните, что использование peek() позволяет лишь подглядеть следующий символ, не извлекая его из потока.
Если вам нужно считать символ и удалить его из потока, лучше воспользоваться методами вроде
get() или getline().


std::cin.peek() — удобный инструмент для предварительного анализа содержимого потоков ввода без непосредственного извлечения символов.
#462_Cpp_LIB

Назначение и использование типов ptrdiff_t, size_t, intptr_t, uintptr_t в С++.

Типы ptrdiff_t, size_t, intptr_t и uintptr_t являются стандартизированными типами в C++, специально предназначенными для работы с размерами объектов, адресами памяти и результатами арифметики указателей.


Рассмотрим каждый из них подробнее:

ptrdiff_t используется для хранения разности между двумя указателями (pointer difference type), возвращаемой операциями типа p2 - p1.
Например, если требуется определить расстояние между элементами массива, используя указатели, результатом операции должен быть именно этот тип:
#include <cstddef> // std::ptrdiff_t

int arr[] = {1, 2, 3};
auto diff = &arr[2] - &arr[0]; // Результат имеет тип ptrdiff_t


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

size_tиспользуется для представления размеров объектов и результатов функций вроде sizeof(), длины строки (strlen()), размера буферов и количества элементов в массивах. Этот тип гарантирует, что размер любого объекта или количество элементов массива гарантированно поместится в переменную типа size_t.

Пример использования:
#include <cstddef> // std::size_t

char str[] = "Привет";
size_t len = strlen(str); // Результат имеет тип size_t


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

intptr_tцелое число знакового типа, которое достаточно велико, чтобы вместить значение указателя. Используется, когда нужно сохранить адрес в виде целого числа, сохраняя возможность обратного преобразования обратно в указатель:
#include <cinttypes> // std::intptr_t

void* ptr = malloc(10);
intptr_t ip = reinterpret_cast<intptr_t>(ptr); // Преобразование указателя в целое число
free(reinterpret_cast<void*>(ip)); // Обратное преобразование назад в указатель


Стоит отметить, что типы intptr_t и uintptr_t необязательны и зависят от реализации компилятора, однако большинство современных компиляторов поддерживают их.

uintptr_t аналогичен типу intptr_t, но является беззнаковым типом. Часто применяется там, где важен диапазон положительных значений (например, хеширование):
#include <cinttypes> // std::uintptr_t

void* data = new char[10];
uintptr_t addr = reinterpret_cast<uintptr_t>(data); // Сохранение адреса в беззнаковом целом числе
delete [] reinterpret_cast<char*>(addr); // Обратное преобразование


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


Итоги и рекомендации по использованию:
Используйте ptrdiff_t, когда речь идет о разнице двух указателей.
Применяйте size_t, если вам нужен правильный тип для хранения размеров объектов или массивов.
Используйте intptr_t и uintptr_t, если действительно нужно представить указатель в виде целого числа (например, при передаче данных в библиотеку).


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

Существуют ли средства std в С++ позволяющие определить, что строка представляет корректный IP-адрес?

Стандартная библиотека C++ (std) сама по себе не предоставляет функций для проверки правильности IP-адреса. Однако можно легко реализовать проверку вручную, используя стандартные возможности библиотеки <string> и регулярных выражений из заголовка <regex>.


Пример простого способа проверить IPv4 адрес с использованием регулярного выражения:
#include <iostream>
#include <string>
#include <regex>

bool isValidIPv4(const std::string& ip) {
// Регулярное выражение для валидной строки IPv4 адреса:
std::regex ipv4_regex(R"(^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)");

return std::regex_match(ip, ipv4_regex);
}

int main() {
std::string test_ip = "192.168.1.1";
if(isValidIPv4(test_ip))
std::cout << "IP-адрес корректен." << std::endl;
else
std::cout << "IP-адрес некорректен." << std::endl;

return 0;
}


Как работает регулярное выражение?
25[0-5] — диапазон от 250 до 255,
2[0-4][0-9] — числа между 200 и 249 включительно,
[01]?[0-9][0-9]? — допустимые значения между 0 и 199 включительно.


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

Для IPv6 проверка немного сложнее из-за формата шестнадцатеричных чисел и возможности пропускать нули. Но аналогично можно написать отдельное регулярное выражение для проверки IPv6 адреса.


Стандартная библиотека позволяет эффективно проверять такие вещи, хотя готового решения непосредственно в пространстве имен std нет.
#467_C_CMPL_IF_TP

Можно ли вызвать рекурсивно функцию main() в С?

Вопрос скорее теоретический, не уверен, что это кто-либо где-то применял на практике.

При чтении книги увидел в исходнике, что функция main() предварительно объявляется (declaration), ну т.е.:
int main(int argc, char** argv):
// и далеее
int main(int argc, char** argv) {
//Тело функции main
return 0;
}


Cтало интересно, если функция main объявляется как обычная функция, то ведет ли она себя так же как обычная функция и в других аспектах, например, можно ли вызвать ее рекурсивно?

Решил провести эксперимент, набросал вот такой код:
#include <stdio.h>

int main(int argc, char** argv) {
int returnValue;
if (argc == 2) {
sscanf(argv[1], "%d", &returnValue);
}
if (returnValue == 0) {
return returnValue;
}
--returnValue;
argv[1][0] = '0' + returnValue;
printf("argv[1] = %s, returnValue = %d\n", argv[1], returnValue);
return main(argc, argv);
}


И как не удивительно это работает. 😁👍.

В результате работы программы получаем вот такой вывод:
compukter@barracol:~/Programs/C/edu_task$ ./a.out 9
argv[1] = 8, returnValue = 8
argv[1] = 7, returnValue = 7
argv[1] = 6, returnValue = 6
argv[1] = 5, returnValue = 5
argv[1] = 4, returnValue = 4
argv[1] = 3, returnValue = 3
argv[1] = 2, returnValue = 2
argv[1] = 1, returnValue = 1
argv[1] = 0, returnValue = 0

.
#469_C_Cpp_LIB_NTWK

libcurl4-openssl-dev для чего используется данная библиотека

Библиотека libcurl4-openssl-dev является пакетом разработки (development package) для библиотеки cURL, предназначенного для работы с протоколами передачи данных в Linux-системах.
Она включает заголовочные файлы, статические библиотеки и документацию, необходимые разработчикам для интеграции поддержки HTTP(S), FTP, SFTP, SMTP и многих других сетевых протоколов в свои приложения.


cURL (или libcurl) — широко используемая библиотека для работы с различными протоколами передачи данных, такими как HTTP/HTTPS, FTP/FTPS, IMAP, POP3, SMTP и другие.


Основные причины её популярности среди разработчиков:

Простота использованияAPI библиотеки прост и понятен даже начинающим программистам.
Кросс-платформенность — поддерживает работу практически во всех популярных ОС, включая Windows, macOS и различные дистрибутивы Linux.
Широкий набор поддерживаемых протоколов — позволяет работать с множеством различных протоколов передачи данных.
Безопасность — библиотека поддерживает SSL/TLS шифрование благодаря использованию OpenSSL или других криптографических библиотек.


Что делает пакет "-dev"?
Пакет libcurl4-openssl-dev содержит инструменты, необходимые именно для разработки приложений, использующих библиотеку curl.

Сюда входят:
Заголовочные файлы (header files) — необходимы компилятору C/C++ для понимания структуры данных и функций библиотеки.
Статическая версия библиотеки (.a-файлы).
Документация и вспомогательные утилиты для сборки и отладки проектов.


Без установки пакета "-dev" невозможно создать программу, зависящую от libcurl, так как отсутствует информация о структуре данных и функциях библиотеки.


Как устанавливать?
Обычно этот пакет устанавливается с помощью менеджера пакетов системы. Например, в Ubuntu или Debian это делается командой:
sudo apt-get install libcurl4-openssl-dev



Типичный сценарий использования выглядит примерно так:
// Подключение библиотеки:
#include <curl/curl.h>

/* Инициализация и выполнение простого GET-запроса*/
CURL* curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
res = curl_easy_perform(curl); // Выполняем запрос
curl_easy_cleanup(curl); // Освобождаем ресурсы
}


libcurl4-openssl-dev предназначен для разработчиков, которым необходимо включить поддержку сетевого взаимодействия в своё приложение.
Пакет обеспечивает необходимую инфраструктуру для использования возможностей cURL, обеспечивая удобную и надёжную интеграцию множества сетевых протоколов.
#470_C_Cpp_LIB_NTWK

Функции SSL_library_init(), SSL_load_error_strings(), curl_easy_init()

Функция SSL_library_init() — использовалась ранее в библиотеке OpenSSL для инициализации внутренних структур и компонентов библиотеки.
Однако начиная с версии OpenSSL 1.1.0 эта функция была объявлена устаревшей и заменена функцией OPENSSL_init_ssl().
Если необходима совместимость с современными версиями OpenSSL, рекомендуется использовать новый интерфейс.

Функция SSL_library_init() выполняла следующие операции:
Инициализировала внутренние структуры OpenSSL.
Регистрировала алгоритмы шифрования и хеширования.
Настраивала внутреннее состояние
OpenSSL.


Использование:
#include <openssl/ssl.h>

void init_openssl() {
SSL_library_init();
}


Однако правильнее будет заменить её на современный эквивалент:
#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
#include <openssl/ssl.h>

#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
#define USE_OPENSSL_INIT_API
#endif

#ifdef USE_OPENSSL_INIT_API
void init_openssl() {
OPENSSL_init_ssl(0, NULL);
}
#else
void init_openssl() {
SSL_library_init();
}
#endif


Функция SSL_load_error_strings() — функция также связана с OpenSSL и предназначена для загрузки сообщений об ошибках в память, чтобы потом выводить осмысленные описания ошибок, возникающих при работе с SSL-криптографическими функциями.


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

Использование:
#include <openssl/err.h>

void load_errors() {
SSL_load_error_strings();
}


Важно: Начиная с OpenSSL 1.1.0, аналогично SSL_library_init(), эта функция устарела и заменяется функцией ERR_load_crypto_strings(), которая автоматически загружается при вызове OPENSSL_init_ssl().

Правильный вариант для современных версий OpenSSL:
#include <openssl/err.h>

void load_errors() {
ERR_load_crypto_strings();
}


Или лучше всего:
#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
#include <openssl/ssl.h>

#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
#define USE_OPENSSL_INIT_API
#endif

#ifdef USE_OPENSSL_INIT_API
void load_errors() {
OPENSSL_init_ssl(0, NULL);
}
#else
void load_errors() {
SSL_load_error_strings();
}
#endif



Функция curl_easy_init() — функция библиотеки cURL, которая создаёт новый объект сессии для последующего выполнения запросов.
Эта сессия управляет всеми параметрами и настройками конкретного
HTTP-запроса.

Назначение функции — функция создает структуру данных типа CURL, представляющую собой handle (дескриптор) сеанса, через которую будут выполняться запросы.

Использование:
#include <curl/curl.h>

int main(void) {
CURL *curl_handle = curl_easy_init();
if (!curl_handle) {
fprintf(stderr, "Ошибка инициализации cURL.\n");
return 1;
}

// Далее можно настроить опции, такие как URL, HTTP-метод и др.
curl_easy_setopt(curl_handle, CURLOPT_URL, "http://example.com");

// Отправляем запрос
CURLcode result = curl_easy_perform(curl_handle);

// Очищаем ресурсы
curl_easy_cleanup(curl_handle);

return 0;
}


SSL_library_init() и SSL_load_error_strings() являются устаревшими функциями, которые были заменены на новые интерфейсы в новых версиях OpenSSL.

curl_easy_init() — стандартная функция библиотеки cURL, необходимая для инициализации объекта сессии перед отправкой HTTP-запросов.

Используйте современные аналоги этих функций, чтобы поддерживать совместимость с новыми версиями библиотек.
#505_C_Cpp_GO_RUST_NTWK

Веб сервер, что это такое, для чего он нужен, какие задачи должен выполнять?
Примеры веб - серверов на С, С++, Rust, Golang.


Веб-сервер — что это?

Веб-серверпрограмма или система программного обеспечения, которая принимает запросы от клиентов (обычно браузеров), обрабатывает их и возвращает запрошенные ресурсы (например, HTML-документы, изображения, файлы JavaScript и CSS).

Основная задача веб-сервера заключается в обеспечении обмена информацией между клиентом и сервером посредством протокола HTTP/HTTPS.


Основные задачи, выполняемые веб-сервером:
Приём запросовполучение входящего запроса от клиента (браузера).
Обработка запросовпроверка формата запроса, авторизация пользователя, определение запрашиваемого ресурса.
Генерация ответовформирование содержимого страницы, обработка динамических страниц, отправка статического контента (HTML-файлы, изображения, скрипты).
Отправка ответов клиентупередача сформированного ответа обратно клиенту согласно протоколу HTTP/HTTPS.
Безопасностьобеспечение защиты сервера и передаваемых данных, аутентификация пользователей, шифрование передачи данных (SSL/TLS).
Логированиезапись действий и ошибок в журнал для последующего анализа и устранения проблем.
Управление нагрузкойраспределение нагрузки среди нескольких серверов (балансировка нагрузки), кэширование часто используемых ресурсов для повышения производительности.
Оптимизация скорости сжатие файлов перед передачей, использование асинхронности обработки запросов, кеширование результатов запросов.


Пример простого веб-сервера на C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

void handle_client(int client_socket) {
char buffer[1024];
ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer)-1, 0);
if(bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("Received request:\n%s\n", buffer);

// Send a simple response back to the client
const char* response =
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
"<html><body><h1>Hello from C Web Server!</h1></body></html>";
send(client_socket, response, strlen(response), 0);
}

close(client_socket); // Close connection after sending response
}

int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);

// Create socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}

// Forcefully attaching socket to port 8080
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

// Bind the socket to the specified IP and port
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0 ) {
perror("bind failed");
exit(EXIT_FAILURE);
}

// Listen for incoming connections
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}

while(1) { // Accept multiple clients
if ((new_socket = accept(server_fd, (struct sockaddr *)NULL, NULL)) < 0) {
perror("accept");
continue;
}

handle_client(new_socket);
}

return 0;
}


Этот пример демонстрирует простой веб-сервер на языке C, который слушает порт 8080, принимает входящие соединения и возвращает простейшую страницу с текстом "Hello from C Web Server!".
Пример простого веб-сервера на C++:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string>
#include <arpa/inet.h>

using namespace std;

const string default_response =
"RFC 2616 compliant HTTP Response\r\n"
"HTTP/1.1 200 OK\r\n"
"Server: Simple C++ Web Server\r\n"
"Content-Length: 26\r\n"
"\r\n"
"<h1>Hello from C++!</h1>\r\n";

// Handle incoming requests
void handleRequest(int sockfd) {
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t n = read(sockfd, buffer, sizeof(buffer)-1);
cout << "Client Request:" << endl << buffer << endl;

write(sockfd, default_response.c_str(), default_response.length());
close(sockfd);
}

int main() {
int listenfd, connfd;
struct sockaddr_in servaddr;

listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8080);

bind(listenfd, reinterpret_cast<const sockaddr*>(&servaddr), sizeof(servaddr));
listen(listenfd, 10);

while(true) {
connfd = accept(listenfd, nullptr, nullptr);
handleRequest(connfd);
}

return 0;
}


Здесь представлен веб-сервер на C++, также возвращающий простую HTML-страницу с сообщением "Hello from C++!". Сервер работает на порте 8080.


Пример простого веб-сервера на Rust:
Rust является популярным языком для разработки высокопроизводительных приложений благодаря своей безопасности памяти и эффективности исполнения.
Для написания веб-сервера в Rust удобно воспользоваться библиотекой actix-web:
use actix_web::{web, App, HttpServer, Responder};

async fn index() -> impl Responder {
"Hello from Rust!"
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
println!("Starting web server on http://localhost:8080");
HttpServer::new(|| {
App::new().route("/", web::get().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}


Этот код создает простое приложение с использованием фреймворка Actix Web, которое прослушивает адрес localhost:8080 и выводит сообщение "Hello from Rust!" при обращении по адресу /.


Пример простого веб-сервера на Go (Golang):
Go отлично подходит для быстрого запуска простых и эффективных веб-сервисов.

Простой пример на Go выглядит следующим образом:

package main
import (
"fmt"
"log"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go!")
}

func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}


Данный скрипт запускает простой веб-сервер, доступный по адресу localhost:8080. При заходе на главную страницу возвращается простая строка "Hello from Go!".


Веб-серверы являются основой современных интернет-приложений и обеспечивают взаимодействие клиентских устройств с ресурсами, размещёнными на серверах.
Выбор языка зависит от требований проекта: C/C++ подойдут для низкоуровневых решений, Rust обеспечит безопасность памяти и высокую производительность, Go удобен для быстрой разработки и масштабирования.
#506_C_PRGM

Программка для перевода строки представляющей шестнадцатиричное число в строковом представлении в строку двоичных кодов. Т.е. нам задается шестнадцатиричное число в виде 0x1234567890abcdf или 0X12fde... или fffde4532a1eb. Длина входной строки до 100 символов, а на выходе мы имеем двоичное представление данного числа.

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

// hex_to_bin.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

void digit_to_bin(const char digit) {
int cp_digit;
if (digit > 47 && digit < 58) {
cp_digit = digit - 48;
} else if (digit > 96 && digit < 103) {
cp_digit = digit - 87;
} else {
perror("Incorrect input");
exit(0);
}

for (int i = 3; i >= 0; --i) {
printf("%d", (cp_digit >> i) & 1);
}
}

void hex_to_bin(const char* hex_str_num) {
for (int i = 0; i != strlen(hex_str_num); ++i) {
digit_to_bin(tolower(hex_str_num[i]));
}
putchar('\n');
}

void read_line(char* buffer, size_t buffer_size) {
const char* input = fgets(buffer, buffer_size, stdin);

if (NULL != input) {
char* end_ptr = strchr(buffer, '\n');
if (NULL != end_ptr) {
*end_ptr = '\0';
}
}
}

char* prefix_ignore(char* input) {
char* result = NULL;
if (input[0] == '0' && tolower(input[1]) == 'x') {
result = &input[2];
}
return result;
}

int main(void) {
enum {SIZE = 101};
char input[SIZE] = {0};

read_line(input, SIZE);
//puts(input);

if (strlen(input)) {
const char* hex_ptr = prefix_ignore(input);
if (NULL != hex_ptr) {
hex_to_bin(prefix_ignore(input));
} else {
hex_to_bin(input);
}
}

return 0;
}

Если у кого есть идеи по оптимизации с радостью выслушаю.
#538_Cpp_LIB_SDS_STL

Что делает std::cin.tie(NULL); в C++?

Операция std::cin.tie(NULL) — отменяет привязку (tie) потока ввода (std::cin) к потоку вывода (std::cout).


Суть привязки потоков (tie).
Потоки ввода и вывода связаны между собой по умолчанию в C++.
Это означает, что всякий раз, когда производится операция вывода (например, std::cout << something;), система синхронизирует потоки ввода и вывода, гарантируя, что ввод и вывод происходят синхронно.
То есть перед выполнением следующего шага чтения (std::cin) операционная система гарантирует, что предыдущий вывод (std::cout) завершён.

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


Зачем разрывать связь (tie(NULL))?

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

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


Пример использования:
Допустим, есть следующая программа:
#include <iostream>

int main() {
std::cin.tie(NULL); // Отменяем привязку cin к cout

std::cout << "Enter your name: ";
std::string name;
std::cin >> name;

std::cout << "Hello, " << name << "!\n";
return 0;
}


До вызова std::cin.tie(NULL) вывод на экран происходил синхронно с вводом, то есть строка приглашения выводилась только после завершения предыдущего ввода.
После разрыва связи вывод немедленно появляется на экране, не дожидаясь завершения последующих операций ввода.


Важно помнить:
Разрыв связи между потоками может повлиять на визуальное представление приложений, работающих в режиме реального времени.
Будьте осторожны при применении этого трюка в ситуациях, где необходим аккуратный контроль взаимодействия с пользователем.
1🤡1
#539_Cpp_PY_LIB_SDS_STL_IdPt

Как в С++, организовать конструкцию подобную switch в Go или match в Rust и Python, которая позволит в качестве меток case использовать не только целочисленные и символьные значения?

Интересный не очень очевидный прием, в нем нет ничего очень умного или хитрого, но таким образом можно заменить конструкцию switch в С++ и при этом мы сможем оперировать не только целочисленными типами, но и строками выражениями и другими объектами (подобная же конструкция с использованием словаря вместо std::map в С++, способна заменить конструкцию math (конструкции switch или подобной ей в Python не было вплоть до версии 3.10)).

Давайте создадим простейший калькулятор:
#include <iostream>
#include <map>
#include <cmath>

int main() {
auto opd1 = 0., opd2 = 0.;
char operation = '\0';
std::cin >> opd1;
std::cin.ignore();
std::cin >> operation >> opd2;

if ('/' == operation && 0 == opd2) {
std::cerr << "Error. Division by zero.\n";
return 0;
}

std::map<char, int> calculate {
{'+', opd1 + opd2},
{'-', opd1 - opd2},
{'*', opd1 * opd2},
{'/', opd1 / opd2},
{'^', pow(opd1, opd2)},
{'%', static_cast<int>(opd1) % static_cast<int>(opd2)}
};

std::cout << calculate[operation];

return 0;
}


Таким образом просто добавляя новые пары ключ-значение в map — calculate, можно легко расширить функциональность, добавляя новые операции в наш калькулятор и получать результат вычислений просто обращаясь по ключу.


Все довольно просто, но, по моему мнению, не совсем очевидно, что в качестве значений std::map в С++ или словарей в Python, мы можем использовать выражения.
👀2🤡1
#542_C_Cpp_IdPt_IF

Я тут увидел в исходнике в одной книжке что функция main() предварительно объявляется, ну т.е.:
int main(int argc, char** argv):
// и далеее
int main(int argc, char** argv) {
//Тело функции main
return 0;
}


Мне и стало интересно, если она объявляется как обычная функция, то можно ли вызвать ее рекурсивно.

Решил провести эксперимент, набросал вот такой код:
#include <stdio.h>

int main(int argc, char** argv) {
int returnValue;
if (argc == 2) {
sscanf(argv[1], "%d", &returnValue);
}
if (returnValue == 0) {
return returnValue;
}
--returnValue;
argv[1][0] = '0' + returnValue;
printf("argv[1] = %s, returnValue = %d\n", argv[1], returnValue);
return main(argc, argv);
}


И как не удивительно это работает. 😁👍.

В результате работы программы получаем вот такой вывод:
compukter@barracol:~/Programs/C/edu_task$ ./a.out 9
argv[1] = 8, returnValue = 8
argv[1] = 7, returnValue = 7
argv[1] = 6, returnValue = 6
argv[1] = 5, returnValue = 5
argv[1] = 4, returnValue = 4
argv[1] = 3, returnValue = 3
argv[1] = 2, returnValue = 2
argv[1] = 1, returnValue = 1
argv[1] = 0, returnValue = 0

.
🤡1
#543_C_Cpp_ALG_IdPt

Алгоритмы. "Эталонная" реализация Bubble Sort (пузырьковой сортировки) с досрочным завершением, если вектор (массив) уже отсортирован, на С/С++:
#include <iostream>
#include <vector>
#include <utility> // std::swap
#include <cstddef> // std::size_t

/**
* @brief Пузырьковая сортировка с ранним выходом.
*
* Алгоритм сортирует контейнер по возрастанию.
* Оптимизирован: если за проход не было обменов — массив уже отсортирован.
*
* @tparam Container тип контейнера (например, std::vector<int>)
* @param arr ссылка на контейнер, который будет отсортирован
*/
template<typename Container>
void bubbleSort(Container& arr) {
const std::size_t n = arr.size();
if (n <= 1) return; // Пустой или одноэлементный контейнер уже отсортирован

bool swapped;
for (std::size_t i = 0; i < n - 1; ++i) {
swapped = false;
// Последние i элементов уже на месте
for (std::size_t j = 0; j < n - 1 - i; ++j) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) break; // Ранний выход — массив отсортирован
}
}

// Специализация для ввода: читаем std::vector<int>
bool readVector(std::vector<int>& numbers) {
std::size_t size = 0;
if (!(std::cin >> size)) {
std::cerr << "Error: invalid or missing input size.\n";
return false;
}

numbers.resize(size);
for (auto& num : numbers) {
if (!(std::cin >> num)) {
std::cerr << "Error: failed to read a number from input.\n";
return false;
}
}
return true;
}

int main() {
std::vector<int> numbers;
if (!readVector(numbers)) {
return EXIT_FAILURE;
}

bubbleSort(numbers);

// Вывод без завершающего пробела
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) {
std::cout << ' ';
}
std::cout << numbers[i];
}
std::cout << '\n';

return EXIT_SUCCESS;
}

.
🔥1🤡1
Функции и интерфейсы

1. Возвращай коды ошибок (или используй exit)
int create_list(List *l); // возвращает 0 при успехе, -1 при ошибке


Альтернатива — аварийное завершение (exit) при критических ошибках (например, malloc failed).


2. Инициализация через составной литерал или функцию
List l = {0}; // все поля = 0 / NULL
// или
List l = make_list();

{0} — стандартный способ обнулить структуру.



Макросы и препроцессор (осторожно!).

1. Защита от повторного включения
#ifndef MY_HEADER_H
#define MY_HEADER_H
// содержимое
#endif


Стандарт для заголовочных файлов.


2. Макросы для мин/макс (осторожно: avoid side effects!)
#define MIN(a, b) ((a) < (b) ? (a) : (b))


Но лучше использовать функции, если типы известны.



Безопасность и надёжность.

1. Избегай "магических чисел":
#define MAX_NODES 1000


Улучшает читаемость и поддержку.


2. Используй size_t для размеров и индексов:
for (size_t i = 0; i < n; i++) // n — размер массива


size_t —
беззнаковый тип, возвращаемый sizeof, strlen, malloc и т.д.

Но будь осторожен при сравнении с int — возможны неожиданные преобразования.


3. Проверяй границы массивов (если не используешь безопасные абстракции)
C не проверяет границыэто твоя забота.



Современные (C99/C11) идиомы.

1. Объявление переменных в заголовке цикла
for (int i = 0; i < n; i++) // i виден только в цикле


Чисто, локально, безопасно.


2. Designated initializers (C99)
struct Point p = {.x = 10, .y = 20};
List l = {.first = NULL, .last = NULL, .size = 0};


Читаемо и явно.


3. Compound literals (C99)
draw_point((struct Point){.x = 5, .y = 5});


Полезно для временных объектов.



Ошибки, которых стоит избегать (анти-идиомы):

void main()не стандартно. Используй
int main(void) {}

или
int main(int argc, char *argv[])




Приведение malloc:
int* pi = (int*)malloc(size * sizeof(int));

В C не нужно, может скрыть ошибку, если забыть
#include <stdlib.h>


Игнорирование возвращаемого значения scanf, fopen ведёт к UB или зацикливанию.


fflush(stdin)неопределённое поведение (только для outputStreams!).


Возврат указателя на локальную переменнуюуказывает в "никуда" после выхода из функции.



C idioms это не просто "как писать", а "как писать правильно в духе C":
— минимализм,
— явность,
— контроль над ресурсами,
— доверяй, но проверяй,
— компилятор — друг, но не замена внимательности.


Изучение и применение этих идиом делает код:
надёжным,
понятным другим C-программистам,
легко поддерживаемым.
👍3😁1🤡1
#548_Cpp_LIB_Boost


Boost.Program_options — библиотека для работы с параметрами командной строки и конфигурационными файлами в C++.


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

Установка Boost.Program_options

1. Загрузка Boost:
Скачайте последнюю версию Boost на официальном сайте https://www.boost.org

2. Распаковка архива:
— Распакуйте архив в удобное место на вашем компьютере.

3. Сборка Boost:
— Перейдите в папку с распакованным Boost.
— Запустите команду для сборки библиотеки:
./bootstrap.sh
./b2


Это создаст необходимые библиотеки и заголовочные файлы.

4. Установка Boost:
— После успешной сборки выполните команду для установки:
sudo ./b2 install


Это установит Boost в системные каталоги.

Подключение Boost.Program_options.

1. Добавление заголовочных файлов:
— Добавьте заголовочные файлы Boost.Program_options:

#include <boost/program_options.hpp>


2. Подключение библиотеки:
— При компиляции программы укажите путь к библиотекам Boost и добавьте библиотеку program_options:
g++ -o my_program my_program.cpp -lboost_program_options



Использование Boost.Program_options.

Рассмотрим пример использования Boost.Program_options для обработки параметров командной строки.

Пример программы:
#include <iostream>
#include <boost/program_options.hpp>

namespace po = boost::program_options;

int main(int argc, char* argv[]) {
// Описание опций
po::options_description desc("Allowed options");
desc.add_options()
("help,h", "produce help message")
("input-file,i", po::value<std::string>(), "input file")
("output-file,o", po::value<std::string>(), "output file")
;

// Переменные для хранения значений опций
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);

// Обработка опций
if (vm.count("help")) {
std::cout << desc << std::endl;
return 1;
}

if (vm.count("input-file")) {
std::cout << "Input file: " << vm["input-file"].as<std::string>() << std::endl;
}

if (vm.count("output-file")) {
std::cout << "Output file: " << vm["output-file"].as<std::string>() << std::endl;
}

return 0;
}


Объяснение кода:
1. Описание опций:
— Создаем объект options_description и добавляем к нему описания опций.

2. Переменные для хранения значений:
— Используем variables_map для хранения значений опций.

3. Обработка опций:
— Проверяем наличие опций и выводим соответствующие сообщения.


Запуск программы:
./my_program --input-file input.txt --output-file output.txt


Вывод:
Input file: input.txt
Output file: output.txt



Boost.Program_options предоставляет мощный и удобный способ обработки параметров командной строки и конфигурационных файлов в C++.
Следуя этим шагам, можно легко установить, подключить и использовать эту библиотеку в своих проектах.
👍21🔥1
#551_Cpp_ALG_IdPt_LIB_STL_TP


Идиома "remove — erase" в C++.



В ЯП C++ идиома "remove-erase" стандартная методика удаления элементов из контейнеров (таких как `std::vector`, `std::list`, `std::deque` и других), которая сочетает использование алгоритма std::remove (или `std::remove_if`) и метода erase соответствующего контейнера.


Зачем нужна идиома "remove-erase"?
Алгоритм std::remove (или std::remove_if)перемещает элементы, подлежащие удалению, в конец контейнера, оставляя "дырки" в начале.
Сам по себе этот алгоритм не удаляет элементы физически — он только перемещает их и возвращает итератор на начало "недействительных" элементов.
Чтобы окончательно удалить ненужные элементы, нужно вызвать метод erase, который уберёт лишние элементы и зафиксирует новый размер контейнера.


Пример использования идиомы "remove-erase":
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 2, 6};

// Удаляем все элементы со значением 2
auto newEnd = std::remove(vec.begin(), vec.end(), 2); // move 2's to the end
vec.erase(newEnd, vec.end()); // удаляем хвост

// Печать оставшихся элементов
for (int item : vec) {
std::cout << item << " ";
}
std::cout << std::endl;

return 0;
}


📌Что происходит:
1. std::remove(vec.begin(), vec.end(), 2) алгоритм перемещает все элементы со значением `2` в конец контейнера и возвращает итератор на первый элемент после "хороших" элементов.
2. vec.erase(newEnd, vec.end()) метод erase удаляет все элементы, начиная с newEnd и до конца контейнера.

Вышеприведённый код выведет:
1 3 4 5 6



Удаление с условием (remove_if):
Иногда требуется удалить элементы, удовлетворяющие определённому условию (предикату). Для этого используется алгоритм std::remove_if:
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 2, 6};

// Удаляем все нечётные числа
auto newEnd = std::remove_if(vec.begin(), vec.end(), [](int x){ return x % 2 != 0; });
vec.erase(newEnd, vec.end());

// Печать оставшихся элементов
for (int item : vec) {
std::cout << item << " ";
}
std::cout << std::endl;

return 0;
}



Почему эта идиома популярна?

Эффективностьданная идиома имеет линейную сложность O(n), так как алгоритм std::remove совершает один проход по контейнеру.

Универсальностьподходит для широкого спектра контейнеров STL, таких как std::vector, std::deque, std::list и других.

Компактность удобно совмещает логику перемещения и физического удаления элементов в один короткий и понятный код.


Когда использовать идиому "remove-erase"?
Когда нужно быстро и эффективно удалить элементы из контейнера, сохраняя порядок оставшихся элементов.
🔥1
⚠️ Распространённые заблуждения:

Миф 1: «rvalue = правая часть присваивания»
int x = 42;  // 42 — prvalue (правая часть)
x = 42; // 42 — всё ещё prvalue, но теперь в середине выражения


Миф 2: «Именованные rvalue references — это rvalues»
void func(std::string&& s) {
// s — ИМЕНОВАННАЯ переменная!
// Несмотря на &&, s является LVALUE внутри функции!

std::string copy = s; // OK — копирование из lvalue
std::string moved = std::move(s); // Нужен std::move для перемещения
}

Правило: любая именованная переменная (даже с &&) является lvalue. Чтобы сделать её rvalue, нужно явно использовать std::move().


Миф 3: «Все временные объекты — prvalue»
std::string func();
const std::string& ref = func(); // Временный объект продлён
// ref — lvalue, хотя изначально был prvalue

Временные объекты могут быть «продлены» через константные ссылки и стать lvalue.


🧩 Продвинутые случаи

1. Битовые поля:
struct S {
int x : 4; // битовое поле
};

S s;
s.x = 5; // s.x — lvalue
// &s.x; // Ошибка! Битовые поля не имеют адреса

Битовые поля — особый случай lvalue без адреса.


2. Функции как выражения
void foo();
foo; // lvalue — функция имеет адрес
&foo; // OK — можно взять адрес функции

Имена функций без скобок — это lvalue.


3. Квалифицированные идентификаторы
struct Base {
int x;
};

struct Derived : Base {};

Derived d;
d.Base::x; // lvalue — квалифицированный доступ к члену базового класса


🔧 Практические рекомендации

1. Когда использовать std::move:
//  Правильно: перемещаем только когда уверены, что объект больше не нужен
std::vector<int> createVector() {
std::vector<int> temp = {1, 2, 3, 4, 5};
return std::move(temp); // НЕ НУЖНО! RVO сработает автоматически
}

// Правильно: в конструкторах перемещения
MyClass(MyClass&& other) : data(std::move(other.data)) {}

2. Перегрузка по категориям значений
class Container {
public:
// Для lvalue — копируем
void add(const std::string& str) {
data.push_back(str);
}

// Для rvalue — перемещаем
void add(std::string&& str) {
data.push_back(std::move(str));
}
};

Container c;
std::string s = "Hello";
c.add(s); // вызовет первую версию (lvalue)
c.add("World"); // вызовет вторую версию (prvalue)
c.add(std::move(s)); // вызовет вторую версию (xvalue)

3. Universal References и forwarding:
template<typename T>
void wrapper(T&& arg) { // T&& — universal reference
process(std::forward<T>(arg)); // сохраняем категорию
}

std::string s = "test";
wrapper(s); // T = std::string&, arg = std::string& (lvalue)
wrapper("temp"); // T = const char(&)[5], arg = const char(&)[5] (lvalue)
wrapper(std::string{}); // T = std::string, arg = std::string&& (rvalue)


📊 Диаграмма принятия решений для категории выражения
Выражение

├── Имеет имя и не является rvalue reference? ──→ lvalue

├── Является результатом std::move()? ────────→ xvalue

├── Является rvalue reference без имени? ─────→ xvalue

├── Создаёт временный объект? ────────────────→ prvalue

└── Является литералом (кроме строк)? ────────→ prvalue


🔍 Инструменты для отладки категорий

Макрос для определения категории:
#include <type_traits>

#define PRINT_CATEGORY(expr) \
do { \
if constexpr (std::is_lvalue_reference_v<decltype((expr))>) { \
std::cout << #expr " is lvalue\n"; \
} else if constexpr (requires { &expr; }) { \
std::cout << #expr " is xvalue\n"; \
} else { \
std::cout << #expr " is prvalue\n"; \
} \
} while(0)

// Использование:
int x = 42;
PRINT_CATEGORY(x); // lvalue
PRINT_CATEGORY(std::move(x)); // xvalue
PRINT_CATEGORY(42); // prvalue

.