Фишки
1. Поменять местами значения переменных a и b:
swap(a, b);
2. Оператор goto:
goto AFTER_CYCLE;
AFTER_CYCLE:проверка на успешное чтение
3. Минимально возможное число int:
#include <climits>
int max = INT_MIN;
4. Пустое место
5. if (cin >> n);
- проверка на успешное чтение
if (cin >> n && n);
- проверка на успешное чтение и на n != 0
6. В С\С++ система ленивого сравнения:
Если первое условие не выполняется (а != 0), то второе даже не будет проверяться
(0 == a && x > 5)
Если первое условие выполняется (а == 0), то второе даже не будет проверяться
(0 == a || x > 5)
7. Операция присваивания (а не оператор, как во многих яп)
В Си вполне допустима такая конструкция:
x = (y=17) + 1; // = 18
a = b = c = d = e + 5; // результат e + 5 будет занесен в d, d в c, c в b, b в a
8. Блок - конструкция для объединения нескольких комманд в одну, так же у блока своя область видимости:
{
int k = 5;
i = k + 5;
}
// k за блоком не существует
9. for(;;){}
тоже самое что while(true){}
10. do{} while(false)
выполнится только 1 раз
11. cin.get(c);
считать 1 символ с iostream и положить значение в переменную с
12. Функция system() передает командному процессору операционной системы команду в строке и возвращает статус выхода команды:
system(ls); // тоже самое что написать ls в терминал
13. #include <cstddef>
- size_t
14. #include <cstring
- работа со C-style строками (strlen, strcpy)
15. Объединения - поля разделяют между собой одну и туже область памяти. Область памяти которую мы можем интерпретировать как int либо как char:
union Un
{
char c;
int i;
};
Un un;
un.c = 'a';
// i = 97
// c = 'a'
Поля перекрывают друг друга:
int | |||
Байт 1 | Байт 2 | Байт 3 | Байт 4 |
char |
16. Ключевое слово register (убрали в С++17)
Спецификатор register рекомендует компилятору сохранить переменную способом, который позволит осуществлять самый быстрый доступ к ней, чаще всего это в регистре процессора (вместо стандартного хранения в ОЗУ). Так как это только рекомендация, то компилятор может просто её проигнорировать!
register int a;
17. Препроцессорная склейка строк
int ma\
in()
{
retu\
rn 0;
}
Конечно можно и не использовать \, однако это является самым правильным способом переноса строки.
Чаще всего используется для красивого оформления макросов:
#define macro\
some\
code
18. Конкатенация строк во время компиляции
std::cout << "Hello" ", world";
склеится в одну строку "Hello, world"
Это удобно использовать когда нужно перенести длинную строку
19. Функции с переменным количеством параметров
В таких функциях хотя бы один параметр обязателен!
Можно было бы передать только один обязательный параметр - количество параметров и полагаясь на знания о том как располагаются параметры на стеке (друг за другом) вывести все остальные параметры:
int * ptr = &count;
std::cout << *ptr << "\n";
std::cout << *(ptr + 1) << "\n";
Однако размещение параметров зависит от компилятора, поэтому такой подход не везде будет работать.
Для упрощение были введены макрокоманды. Все они используют специальный тип va_list, который по сути выступает в качестве указателя на параметры, который перемещают макрокоманды:
- va_start(va_list param, обязательный_параметр_функции); - “нацеливает” va_list на первый параметр
- va_arg(va_list param, type); - получить значение текущего параметра и переместить va_list на следующий параметр
- va_end(va_list param); - закончить обработку параметров
#include <stdarg.h> // тут определены макрокоманды
void print(int count, ...)
{
va_list ptr;
va_start(ptr, count);
for (int i = 0; i != count; ++i)
std::cout << va_arg(ptr, int) << "\n";
va_end(ptr);
}
print(3, 8, 9, 2); // 8 9 2
20. Макросы с переменным количеством параметров
С макросамы все проще если вспомнить что макрос это просто подстановка текста препроцессором. Соответственно достаточно лишь использовать готовый __VA_ARGS__ что бы подставить все переданные параметры:
#define print(str, ...)\
printf(str, __VA_ARGS__);
print("%d %d %d \n", 2, 3, 4); // 2 3 4
Одно из мощных и одновременно бесполезных применений:
#define for(...) // "обезвредили" все циклы for
21. Порядок исходников при компиляции
Как оказалось порядок, в котором мы передаем исходники важен для компилятора, а точнее это больше важно для линковщика!
Допустим на проекте есть такие файлы:
- Библиотека SDL2
- Библиотека SDL2_image
- Framework, который зависит от библиотек SDL2 и SDL2_image
Собранный фреймворк (.os) лежит тут: ../Framework/tmp/
Заголовок фреймворка лежит тут: ../Framework/inc
Соответственно, команда компиляции должна выглядеть примерно так:
g++ -Wall -I../Framework/inc -L../Framework/tmp/ main.cpp -lFrameworkRelease -lSDL2 -lSDL2_image
Библиотеки (и другие файлы) нужно располагать справа налево, где самая правая менее зависимая от других.
Для красоты можно все ключи и подключаемые библиотеки вынести в отдельные переменные среды:
export FLAGS= -Wall -I../Framework/inc -L../Framework/tmp/
export LIBS= -lFrameworkRelease -lSDL2 -lSDL2_image
Итог:
g++ $(FLAGS) -o game main.cpp ($LIBS)
22. Двойные неравенства
Как известно, особых конструкций для двойных неравенств в Си нет и их нужно оформлять через логические операторы:
(a <= x && x <= b) // xє[a; b]
Однако, если все-таки написать a <= x <= b
, то это будет аналогично: (a <= x) <= b
. А теперь вспомним что true = 1, false = 0:
int x = 0;
-5 <= x < 1 // ожидаем true
(-5 <= 0) < 1
true < 1
1 < 1 // false!
23. Обмен значений переменных без использования дополнительной переменной
Такой обмен однотипных переменных можно реализовать используя XOR:
int x = 5; // 101
int y = 7; // 111
x = x ^ y; // x = 101 ^ 111 = 010
y = x ^ y; // y = 010 ^ 111 = 101 (5)
x = x ^ y; // x = 010 ^ 101 = 111 (7)
Более короткая запись:
y ^= (x ^= y);
x ^= y;
24. Stack overflow
Интересный пример: что произойдёт?
void f4(char z);
void f3(char x)
{
f4(x);
}
void f4(char z)
{
f3(z);
}
int main()
{
f3(126);
std::cout << "THE END" << std::endl;
}
На самом деле произойдёт переполнение стека, потому что он весь будет забит стековыми фреймами функций
25. static и инстанцирование шаблона
template<class T>
void Print(T Num)
{
static int i = 0;
std::cout << i++ << std::endl;
}
int main()
{
Print(20); // инстанцирование <int>, выведет 0, i = 1
Print(2 + 5); // вызов для уже существующего <int>, выведет 1, i = 2
Print(1.f); // инстанцирование <float>, выведет 0, i = 1
Print(1.0); // инстанцирование <double>, выведет 0, i = 1
return 0;
}