Трюки C++

6 min

Я вижу, как многие программисты пишут код вот так:

pair<int, int> p;
vector<int> v;
p = make_pair(3, 4);
v.push_back(4); v.push_back(5);

Хотя вполне можно писать так:

pair<int, int> p;
vector<int> v;
p = {3, 4};
v = {4, 5};

1. Присваивание контейнерам с помощью фигурных скобок {}

Многие пишут так:

pair<int, int> p;
p = make_pair(3, 4);

Но можно так:

pair<int, int> p;
p = {3, 4};

И более сложные pair тоже без проблем:

pair<int, pair<char, long long>> p;
p = {3, {'a', 8ll}};

А что насчёт других контейнеров вроде vector, deque, set и т. д.? Точно так же работает.

Внимание: stack и queue не поддерживают такой синтаксис.

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

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

#define what_is(x) cerr << #x << " is " << x << endl;
int a_variable = 376;
what_is(a_variable);       // вывод "a_variable is 376"
what_is(a_variable * 2 + 1) // вывод "a_variable * 2 + 1 is 753"

3. Прощайте, горы #include!

Просто используйте:

#include <bits/stdc++.h>

Этот заголовок включает почти всё, что нужно на соревнованиях: algorithm, iostream, vector и т. д. Поверьте, вам больше не придётся подключать что-то отдельно!

4. Скрытые функции (на самом деле не скрытые, просто редко используются)

I) __gcd(value1, value2)

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

Пример: __gcd(18, 27) = 9

II) __builtin_ffs(x)

Возвращает индекс младшего установленного бита (самого правого 1) плюс 1. Если x == 0, возвращает 0. Тип аргумента — int, суффикс l принимает long, суффикс ll принимает long long.

Пример: __builtin_ffs(10) = 2, потому что двоичное представление 10 — ...1010, самый правый 1 стоит на позиции 1 (0-based), функция возвращает 1+1=2.

III) __builtin_clz(x)

Возвращает количество ведущих нулей от старшего значащего бита. Аргумент — unsigned int, суффиксы l/ll аналогично. При x==0 поведение не определено.

Пример: __builtin_clz(16) = 27, потому что 16 — это ...10000, а в unsigned int 32 бита, 32-5=27.

IV) __builtin_ctz(x)

Возвращает количество нулей на конце (от младших битов). Аргумент — unsigned int, при x==0 поведение не определено.

Пример: __builtin_ctz(16) = 4, потому что 16 — это ...10000, на конце 4 нуля.

V) __builtin_popcount(x)

Возвращает количество единиц в двоичном представлении x. Аргумент — unsigned int, при x==0 поведение не определено.

Пример: __builtin_popcount(14) = 3, потому что 14 — это ...1110, там три единицы.

Примечание: существуют и другие __builtin функции, но эти используются чаще всего. Если интересно — поищите самостоятельно.

5. Вариативные функции и макросы

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

В C++14 можно использовать auto sum(T a, Args... args), чтобы суммировать смешанные типы.

Вариативные макросы:

#define a_macro(args...) sum(args)

В сочетании с вариативными шаблонами можно сделать очень удобную отладочную функцию (спасибо Igorjan94):

#include <bits/stdc++.h>
using namespace std;

#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
    cerr << *it << " = " << a << endl;
    err(++it, args...);
}

int main() {
    int a = 4, b = 8, c = 9;
    error(a, b, c);
}

Вывод:

a = 4
b = 8
c = 9

Эта функция очень полезна при отладке.

6. CF уже поддерживает C++0x — зачем пользоваться старым C++?

Вариативные функции — это новая возможность C++11/C++0x. Ниже — несколько отличных фич C++11:

I) Цикл for по диапазону (Range-based for-loop)

Старый стиль:

set<int> s = {8, 2, 3, 1};
for (set<int>::iterator it = s.begin(); it != s.end(); ++it)
    cout << *it << ' ';

Новый стиль, намного короче:

set<int> s = {8, 2, 3, 1};
for (auto it: s)
    cout << it << ' ';

Если нужно изменять значения, используйте auto &:

vector<int> v = {8, 2, 3, 1};
for (auto &it: v)
    it *= 2;

II) Сила auto

Больше не нужно вручную писать сложные имена типов — компилятор выведет тип автоматически. Например, для итератора по set<pair<int, pair<int, int>>> раньше приходилось писать огромную конструкцию, а теперь достаточно auto it = s.begin().

Кроме того, x.begin() и x.end() теперь можно писать как begin(x) и end(x).

Дополнительные трюки из комментариев

Умный перенос строки

Дополнение из комментария Ximera:

Этот код:

for(i = 1; i <= n; i++) {
    for(j = 1; j <= m; j++)
        cout << a[i][j] << " ";
    cout << "\n";
}

Эквивалентен:

for(i = 1; i <= n; i++)
    for(j = 1; j <= m; j++)
        cout << a[i][j] << " \n"[j == m];

Идея: " \n" — это char*, " \n"[0] — пробел ' ', " \n"[1] — перевод строки '\n'.

Использование tie и emplace_back

Дополнение из комментария tubo28:

Почему emplace_back быстрее, чем push_back: emplace_back конструирует объект на месте прямо в конце vector; а push_back сначала конструирует где-то ещё, а затем перемещает внутрь.

tie также поддерживает ключевое слово ignore, чтобы пропустить некоторое значение:

tuple<int, int, int, char> t (3, 4, 5, 'g');
int a, b;
tie(b, ignore, a, ignore) = t;
cout << a << ' ' << b << '\n';
// вывод: 5 3

Автор также поделился макросом для двунаправленного перебора:

#define rep(i, begin, end) for (__typeof(end) i = (begin) - ((begin) > (end)); i != (end) - ((begin) > (end)); i += 1 - 2 * ((begin) > (end)))

Плюсы: не нужно указывать тип; автоматически выбирается прямой или обратный проход в зависимости от begin > end.

  • rep(i, 1, 10) → 1, 2, …, 9
  • rep(i, 10, 1) → 9, 8, …, 1

Можно применять и к итераторам:

vector<int> v = {4, 5, 6, 4, 8};
rep(it, end(v), begin(v))
    cout << *it << ' ';
// вывод "8 4 6 5 4"

Lambda-функции

В C++11 также появились lambda-функции, синтаксис такой:

[список захвата](список параметров) -> тип_возврата { тело функции }
  • Список захвата: если не нужен — []
  • Параметры: например int x, string s
  • Тип возврата: чаще всего можно опустить
  • Тело: как обычно

Пример:

auto f = [] (int a, int b) -> int { return a + b; };
cout << f(1, 2); // вывод "3"

Можно использовать в STL-функциях вроде sort, for_each:

vector<int> v = {3, 1, 2, 1, 8};
sort(begin(v), end(v), [] (int a, int b) { return a > b; });
// вывод: 8 3 2 1 1

Использование move

Дополнение из комментария Igorjan94:

При работе с STL-контейнерами можно использовать move, чтобы перемещать, а не копировать контейнер, что сильно экономит ресурсы:

vector<int> v = {1, 2, 3, 4};
vector<int> w = move(v);
// v теперь пуст, w содержит прежнее содержимое v

7. Строки C++0x

I) Сырые строки (Raw Strings) (из комментария IvayloS)

Определение сырой строки:

string s = R"(Hello, World!)";

Сырая строка игнорирует все управляющие последовательности, такие как \n, \". Также поддерживаются многострочные строки — достаточно добавить пользовательский разделитель:

string r_str = R"(Dear Programmers,
I'm using C++11
Regards, Swift!)";

II) Регулярные выражения (Regular Expressions)

В C++11 есть regex; рекомендуется сочетать с сырыми строками (потому что в регулярках часто встречаются обратные слэши и другие спецсимволы):

regex email_pattern(R"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)");

III) Пользовательские литералы (User-defined literals)

Вы можете определить собственные суффиксы литералов, например для преобразования единиц длины:

long long operator "" _m(unsigned long long literal) { return literal; }
long double operator "" _cm(unsigned long long literal) { return literal / 100.0; }
long long operator "" _km(unsigned long long literal) { return literal * 1000; }

cout << 250_m;   // 250
cout << 12_km;   // 12000
cout << 421_cm;  // 4.21

Имя пользовательского литерала должно начинаться с подчёркивания _, а тип параметра может быть только одним из следующих: const char *, unsigned long long int, long double, char и т. д.


Ссылка на оригинал: Codeforces - C++ Tricks

Автор: HosseinYousefi

Перевод: Подготовлено редакцией сайта

Теги: c++,c++0x,tricks

Лайки: +971