std::set в C++: уникальные элементы, поиск и сортировка на практике
std::set в C++ — это ассоциативный контейнер из стандартной библиотеки STL. Он полезен, когда нужно хранить набор уникальных значений, быстро проверять наличие элемента и получать данные в отсортированном виде без ручной сортировки.
Типичный поисковый запрос по этой теме — «std::set C++ примеры» или «как хранить уникальные элементы в C++». Именно под такую практическую задачу и разберём контейнер set.
Что такое std::set
std::set находится в заголовочном файле <set>. Внутри он обычно реализован как сбалансированное бинарное дерево, поэтому основные операции работают за O(log n):
- добавление элемента;
- поиск элемента;
- удаление элемента.
Главные свойства std::set:
- элементы не повторяются;
- значения автоматически сортируются;
- нельзя изменить элемент прямо внутри set так, чтобы нарушился порядок;
- итерация идёт по возрастанию, если не задан другой компаратор.
Простой пример std::set
Допустим, пользователь вводит числа, а нам нужно вывести только уникальные значения по возрастанию.
#include <iostream>
#include <set>
int main() {
std::set<int> numbers;
numbers.insert(5);
numbers.insert(2);
numbers.insert(10);
numbers.insert(2); // повтор, не будет добавлен
for (int value : numbers) {
std::cout << value << ' ';
}
return 0;
}
Результат:
2 5 10
Обратите внимание: мы добавили 2 два раза, но в контейнере он остался только один раз. Кроме того, значения вывелись не в порядке добавления, а в отсортированном порядке.
Как понять, был ли элемент добавлен
Метод insert возвращает пару: итератор и логическое значение. Второе значение равно true, если элемент действительно добавлен, и false, если такой элемент уже был.
#include <iostream>
#include <set>
int main() {
std::set<std::string> names;
auto result = names.insert("Anna");
if (result.second) {
std::cout << "Имя добавлено\n";
} else {
std::cout << "Такое имя уже есть\n";
}
result = names.insert("Anna");
if (!result.second) {
std::cout << "Повторное добавление не сработало\n";
}
return 0;
}
Это удобно, например, при регистрации логинов, тегов, идентификаторов или уникальных кодов.
Поиск элемента в std::set
Для поиска используется метод find. Если элемент найден, возвращается итератор на него. Если нет — end().
#include <iostream>
#include <set>
int main() {
std::set<int> ids = {101, 205, 310, 404};
int target = 205;
if (ids.find(target) != ids.end()) {
std::cout << "ID найден\n";
} else {
std::cout << "ID не найден\n";
}
return 0;
}
В C++20 можно использовать более читаемый метод contains:
if (ids.contains(205)) {
std::cout << "Есть такой ID\n";
}
Если вы пишете код под C++17 или более старый стандарт, используйте find.
Удаление элементов
Удалять элементы можно по значению или по итератору. Самый простой вариант — erase(value).
#include <iostream>
#include <set>
int main() {
std::set<int> numbers = {1, 2, 3, 4, 5};
numbers.erase(3);
for (int number : numbers) {
std::cout << number << ' ';
}
return 0;
}
Результат:
1 2 4 5
Метод erase возвращает количество удалённых элементов. Для std::set это будет либо 0, либо 1, потому что дубликатов нет.
Сортировка по убыванию
По умолчанию std::set сортирует значения по возрастанию. Чтобы хранить элементы по убыванию, можно передать компаратор std::greater.
#include <iostream>
#include <set>
#include <functional>
int main() {
std::set<int, std::greater<int>> numbers = {4, 1, 7, 2};
for (int number : numbers) {
std::cout << number << ' ';
}
return 0;
}
Результат:
7 4 2 1
std::set со своими структурами
Чтобы хранить в set объекты пользовательского типа, нужно объяснить контейнеру, как их сравнивать. Например, отсортируем пользователей по возрасту, а при равном возрасте — по имени.
#include <iostream>
#include <set>
#include <string>
struct User {
std::string name;
int age;
};
struct UserCompare {
bool operator()(const User& a, const User& b) const {
if (a.age != b.age) {
return a.age < b.age;
}
return a.name < b.name;
}
};
int main() {
std::set<User, UserCompare> users;
users.insert({"Ivan", 25});
users.insert({"Anna", 20});
users.insert({"Petr", 25});
for (const User& user : users) {
std::cout << user.name << ": " << user.age << '\n';
}
return 0;
}
Важно: для std::set два элемента считаются одинаковыми не через ==, а через компаратор. Если ни один объект не меньше другого, set считает их эквивалентными.
Когда использовать std::set
std::set хорошо подходит, если вам нужно:
- хранить только уникальные элементы;
- часто проверять наличие значения;
- получать элементы сразу в отсортированном порядке;
- быстро находить ближайшие элементы через
lower_boundиupper_bound.
Пример с lower_bound: найдём первое число, которое не меньше заданного.
std::set<int> numbers = {10, 20, 30, 40};
auto it = numbers.lower_bound(25);
if (it != numbers.end()) {
std::cout << "Первое подходящее число: " << *it << '\n';
}
Здесь будет найдено число 30.
std::set, std::unordered_set или vector?
Новички часто выбирают std::set «на всякий случай», но это не всегда лучший вариант.
std::set— элементы уникальны и отсортированы, операции обычноO(log n).std::unordered_set— элементы уникальны, но порядок не гарантируется, поиск в среднемO(1).std::vector— хорош, если данных мало или важен порядок добавления; для уникальности придётся писать дополнительную проверку.
Если сортировка не нужна, а важна максимальная скорость поиска, часто лучше подойдёт std::unordered_set. Если элементов немного, простой vector может оказаться понятнее и быстрее из-за меньших накладных расходов.
Частые ошибки новичков
- Ожидать порядок добавления.
std::setхранит элементы отсортированно, а не в порядке вставки. - Пытаться изменить элемент через итератор. Это запрещено, потому что изменение может сломать внутренний порядок дерева.
- Забывать про компаратор. Для своих типов нужно явно определить логику сравнения.
- Использовать set там, где нужен multiset. Если дубликаты должны сохраняться, берите
std::multiset.
Практический пример: уникальные слова из текста
Ниже программа считывает слова до конца ввода и выводит уникальные слова в алфавитном порядке.
#include <iostream>
#include <set>
#include <string>
int main() {
std::set<std::string> words;
std::string word;
while (std::cin >> word) {
words.insert(word);
}
std::cout << "Уникальные слова:\n";
for (const std::string& item : words) {
std::cout << item << '\n';
}
return 0;
}
Такой код можно использовать как основу для простого анализа текста, списка тегов или проверки повторяющихся значений.
Рекомендации
- Используйте
std::set, когда нужны уникальность и сортировка одновременно. - Для проверки наличия в C++20 предпочитайте
contains, а в C++17 —find. - Не храните в set изменяемые данные, по которым выполняется сравнение.
- Для пользовательских структур всегда тщательно продумывайте компаратор.
Если вы хотите уверенно разобраться не только с std::set, но и со всей базой C++: типами, функциями, ООП, STL и практическими задачами, посмотрите курс «Программирование на C++ с Нуля до Гуру» — пошаговый путь от первых программ до уверенной разработки.
Вывод
std::set в C++ — удобный контейнер для уникальных отсортированных данных. Он не заменяет все остальные контейнеры, но отлично решает задачи, где нужно быстро проверять наличие элемента и поддерживать порядок. Главное — помнить, что set не хранит порядок вставки и определяет уникальность через правило сравнения.
-
Создано 26.08.2026 17:00:56
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.