std::set в C++: уникальные элементы, поиск и сортировка на практике
std::set в C++ — это ассоциативный контейнер из стандартной библиотеки STL. Он полезен, когда нужно хранить набор уникальных значений, быстро проверять наличие элемента и получать данные в отсортированном виде без ручной сортировки.
Типичный поисковый запрос по этой теме — «std::set C++ примеры» или «как хранить уникальные элементы в C++». Именно под такую практическую задачу и разберём контейнер set.
Что такое std::set
std::set находится в заголовочном файле <set>. Внутри он обычно реализован как сбалансированное бинарное дерево, поэтому основные операции работают за O(log n):
Главные свойства std::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 << "Есть такой IDn";
}
Если вы пишете код под 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 хранит элементы отсортированно, а не в порядке вставки.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, когда нужны уникальность и сортировка одновременно.contains, а в C++17 — find.Если вы хотите уверенно разобраться не только с std::set, но и со всей базой C++: типами, функциями, ООП, STL и практическими задачами, посмотрите курс «Программирование на C++ с Нуля до Гуру» — пошаговый путь от первых программ до уверенной разработки.
Вывод
std::set в C++ — удобный контейнер для уникальных отсортированных данных. Он не заменяет все остальные контейнеры, но отлично решает задачи, где нужно быстро проверять наличие элемента и поддерживать порядок. Главное — помнить, что set не хранит порядок вставки и определяет уникальность через правило сравнения.




