ТОП 10 трюков на C++, которые облегчат вам жизнь
Мы подобрали приёмы, которые сделают программирование на С++ легче или, как минимум, интереснее. Освежите свои знания!
Изучение С++ – дорога длиною в жизнь. Сегодня этот язык является рекордсменом по количеству подводных камней и способов выстрелить себе в ногу. При этом он нереально востребован и является одним из самых быстрых. Мы подготовили несколько советов, которые сделают ваш код более читаемым и оптимизированным.
Автовыведение типа
Несмотря на то, что ключевое слово auto
было введено еще в C++ 11, многие программисты продолжают его игнорировать. А ведь автовыведение позволяет экономить время и делает код лаконичным.
Увидеть преимущества можно даже на примере стандартных типов STL:
//создадим простенький вектор с координатами городов std::map<std::string, std::pair<float, float>> coordinatesRus; std::map<std::string, std::pair<float, float>> coordinatesUsa; coordinatesRus["Moscow"] = std::make_pair(45.2376, 37.2236); coordinatesRus["Sankt Petersburg"] = std::make_pair(59.9386, 30.3141); coordinatesUsa["New York"] = std::make_pair(40.7142, -74.0059); coordinatesUsa["Chikago"] = std::make_pair(41.8500, -87.6500); std::vector<std::map<std::string, std::pair<float, float>>> coordinatesWorld; coordinatesWorld.push_back(coordinatesRus); coordinatesWorld.push_back(coordinatesUsa); //теперь сделаем обход с двумя разными for for (std::map<std::string, std::pair<float, float>> country : coordinatesWorld) { for (std::map<std::string, std::pair<float, float>>::iterator it = country.begin(); it != country.end(); ++it) { std::cout << it->first << " " << it->second.first << " " << it->second.second << std::endl; } } //и с auto for (auto country : coordinatesWorld) { for (auto it = country.begin(); it != country.end(); ++it) { std::cout << it->first << " " << it->second.first << " " << it->second.second << std::endl; } }
Сложный расчет констант
Использование констант – хороший тон. Это позволяет компилятору лучше оптимизировать код и делает его более явным. Но если вычисление слишком громоздкое, от модификатора const
приходится отказываться. На помощь приходят лямбда-функции:
const int myVariable = [&] { if (contidionA) return conditionB ? computeFunc(param) : 0; else return param * 2; }();
Трюки с битовыми операциями
Эти приёмы не так полезны в повседневном программировании, но помогут козырнуть на собеседовании или соревнованиях:
//изящное определение четности if ((n & 1) != 0) { //нечётный } else { //чётный } //свап значений x и y без создания третьей переменной при помощи ксора x ^= y; y ^= x; x ^= y; //умножение и деление на при помощи битового сдвига y = x << 2; //x будет умножен на 4 y = x >> 3; //x будет разделен на 8
Трюки с логарифмом
Функцию log
тоже можно использовать для ряда изящных решений:
//определение старшей цифры числа n, если его разрядность неизвестна double k = log10(n); k = k - floor(k); int result = pow(10, k); //подсчет количества цифр в целом числе без цикла int digitСount = floor(log10(n)) + 1
Встроенные алгоритмы
Удобно использовать встроенные функции all_of
, any_of
и none_of
для быстрой проверки элементов коллекции на соответствие условию.
//стандартный массив if (std::all_of(arr, arr + 5, [](int elem) { return elem > 0; })) std::cout << "Все элементы массива положительны" << std::endl; //работа с коллекциями if (std::any_of(someVector.begin(), someVector.end(), [](int elem) { return elem == 0; })) std::cout << "В векторе присутствует 0" << std::endl;
Трюки с шаблонами
Шаблоны сложнее для понимания, чем наши предыдущие советы, и могут показаться трудночитаемыми, но однозначно стоят потраченного времени.
Проверка специализации типа
Иногда встает задача проверить, является ли конкретный тип данных специализацией определенного шаблона. Для этого можно использовать синтаксис шаблонов template
:
//проверка на пустые параметры template <template <typename...> class T, typename U> struct is_specialization_of : std::false_type {}; //создаем шаблон, принимающий в виде аргументов другой шаблон и шаблонный тип данных с неопределенным числом аргументов template <template <typename...> class T, typename... Us> struct is_specialization_of<T, T<Us...>> : std::true_type {}; int main(){ //тестируем std::cout << is_specialization_of<std::tuple, std::tuple<int>>::value; std::cout << is_specialization_of<std::tuple, std::map<int,std::string>>::value; }
Проверка возможности конструирования элемента с набором конкретных параметров
Когда шаблонный класс задан в виде template<class T, typename... Args>
, бывает трудно понять, какие аргументы можно использовать. Метод is_constructible
из библиотеки type_traits
даёт неполный ответ: он показывает, существует ли конструктор под конкретные аргументы. Для более полной картины можно использовать еще один шаблон:
template <typename...> struct identity {}; //проверка на пустые параметры template <typename, typename, typename = std::void_t<>> struct is_braces_constructible_impl : std::false_type {}; //определяем соответствие типов template <typename T, typename... Args> struct is_braces_constructible_impl < T, identity<Args...>, std::void_t<decltype(T{ std::declval<Args>()... }) >> : std::true_type {}; template <typename T, typename... Args> struct is_braces_constructible : is_braces_constructible_impl<T, identity<Args...>> {}; int main(){ std::cout << is_braces_constructible<std::tuple<int, int>, int, int>::value; std::cout << is_braces_constructible<std::tuple<int, int, int>, int, int>::value; }
Вызов функции через кортеж
Креативное применение шаблонов, которое позволяет вызвать любую функцию, передав в неё параметры в виде кортежа:
//основной шаблон, позволяющий вызвать функцию по ссылке и передать в неё кортеж template <typename F, size_t... Idx, typename... Ts> constexpr auto call_via_tuple_impl(F f, std::index_sequence<Idx...>, const std::tuple<Ts...>& args) { return f(std::get<Idx>(args)...); } //дополнительный шаблон, позволяющий не заботиться о размере кортежа template <typename F, typename... Ts> constexpr auto call_via_tuple(F f, const std::tuple<Ts...>& args) { return call_via_tuple_impl(f, std::make_index_sequence<sizeof...(Ts)>(), args); } int main(){ //создаем кортеж constexpr auto tup = std::make_tuple(6, 33, 1); //тестируем auto func = [](int a, int b, int c) -> auto { return a + b + c; }; //можно сделать ссылку на функцию с неопределенным числом параметров std::cout << call_via_tuple(func, tup); }
Поиск первого элемента указанного типа в кортеже
Кортежи, в силу своей способности хранить разные типы данных, часто бывают полезной коллекцией. Но эта же особенность может доставлять неудобства. Наш последний шаблон позволит без циклов находить в кортеже элементы определенного типа:
template <typename T, std::size_t N, typename... Ts> struct index_of; //проходим кортеж и определяем типы данных template <typename T, std::size_t N, typename... Ts> struct index_of<T, N, T, Ts...> : std::integral_constant<int, N> {} template <typename T, std::size_t N, typename U, typename... Ts> struct index_of<T, N, U, Ts...> : std::integral_constant<int, index_of<T, N + 1, Ts...>::value> {};template <typename T, std::size_t N> struct index_of<T, N> : std::integral_constant<int, -1> {}; //находим первые совпадения и возвращаем template <typename T, typename... Ts> constexpr T get_first(const std::tuple<Ts...>& tup) { return std::move(std::get<index_of<T, 0, Ts...>::value>(tup)); } int main(){ constexpr auto tup = std::make_tuple('x', 10, 'y', 1.0f); std::cout << get_first<int>(tup); std::cout << get_first<char>(tup); std::cout << get_first<float>(tup); }