Вы готовы присоединиться к величественному миру программирования C и C++? Хотите ли вы подвергнуть сомнению свое существование после нескольких простых строк C++?
Если ваш ответ "Yeh!", "Yep" или "Why not?" - добро пожаловать на проверку ваших знаний. Вам будет предложено несколько вопросов, связанных с C или C++.
Правильные ответы и пояснения вы найдете в конце истории. Удачи!
1. Самая маленькая программа
main;
Что произойдет, если вы попытаетесь скомпилировать эту программу с помощью компилятора C?
- не будет компилироваться
- скомпилируется, не будет компоноваться
- скомпилирует и свяжет
2. Вилка
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
Сколько строк напечатает эта программа?
- 1000
- менее 1000
- более 1000
3. Все, что вам нужно, это индексы.
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
Что напечатает эта программа?
- 1
- 2
- 3
- 4
- не будет компилироваться
- неопределенный
4. Регулярные выражения
#include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }
Сколько времени потребуется, чтобы это регулярное выражение сопоставило данную входную строку?
- 1 мс
- 1 сек.
- 1 мин.
- 1 час
- 1 год
- навсегда
5. Ходы и лямбды
#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } }; int main() { Foo f; auto a = [f = std::move(f)]() { return std::move(f); }; Foo f2(a()); return 0; }
Последняя строка, которую выведет эта программа, это…
-
Foo()
-
Foo(Foo&&)
-
Foo(const Foo&)
6. X и черта
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
Что напечатает эта программа?
-
0
-
1
-
0x0
- не будет компилироваться
- не будет ссылки
7. Конструкторы
#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } Foo(int) { std::cout << "Foo(int)\n"; } Foo(int, int) { std::cout << "Foo(int, int)\n"; } Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; } Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; } }; void f(Foo) {} struct Bar { int i, j; Bar() { f(Foo(i, j)); f(Foo(i)); Foo(i, j); Foo(i); Foo(i, j); } }; int main() { Bar(); }
Последняя строка, которую выведет эта программа, это…
-
Foo(int, int)
-
Foo(const Foo&, int)
-
Foo(int, const Foo&)
-
Foo(int)
Вместо заключения
Надеюсь, вы никогда не найдете ни одного из этих странных фрагментов в дикой природе.
Ответы
Самая маленькая программа
Это допустимый код C. Он успешно скомпилируется и линкуется. Он зависнет, если вы попытаетесь его запустить.
main;
- глобальная переменная.
В коде C можно опустить много вещей. Например, можно опустить тип глобальной переменной. По умолчанию компилятор будет считать, что этот тип —
int
. Также в C нет искажения имен (в отличие от C++), поэтому при связывании нет способа отличить переменнуюmain
от функцииmain
.
Таким образом, компилятор скомпилирует корректный код, а компоновщик найдет что-то с именем
main
в объектном файле, чтобы скомпоновать программу.
Вилка
Это больше функция POSIX, чем функция C или C++. Реализации операций ввода-вывода используют буферы для оптимизации производительности. Когда вы вызываете
fork
, ОС создаст копию-при-записи дубликата памяти процесса, буферы ввода-вывода, скорее всего, также будут дублироваться, а буферизованные строки , скорее всего, будут выведены более 1000 раз .
Все, что вам нужно, это индексы
Ответ 3.
Чтобы понять этот код, давайте подробнее рассмотрим, как работают индексы в C и C++:
array[index]
— то же самое, что*(array + index)
, то же самое, что(index + array)
и то же самое, чтоindex[array
.Вторая подсказка — оператор
,
. Это бинарный оператор, он отбрасывает левый аргумент и возвращает правый аргумент.
Регулярные выражения
Невозможно предсказать, что произойдет! Поведение зависит от реализации.
В моей среде эта программа вызывает исключение
The complexity of an attempted match against a regular expression exceeded a pre-set level.
Другие вероятные варианты на удивление долго работают или работают как и ожидалось. Это потому, что есть два возможных подхода к реализации регулярных выражений.
Первое - преобразовать регулярные выражения в конечные автоматы
O(n**2)
(n - длина шаблона), сопоставить строкуO(m)
(m - длина строки). Этот подход не поддерживает возврат.
Второй — жадный подход + DFS, поддерживает возврат, но склонен к экспоненциальному росту временной сложности в определенных шаблонах.
Движения и лямбды
Ответ:
Foo(const Foo&)
. Лямбды по умолчанию неизменяемы, все значения, захваченные в лямбду с помощью[]
неявно являютсяconst
. Это разблокирует идемпотентное поведение для лямбд.
При перемещении
f
вы создаетеconst Foo&&
.const Foo&&
— странный тип, поэтому компилятор просто копируетFoo
Есть два способа исправить это:
Создать изменяемую лямбду
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Объявить конструктор
Foo(const Foo&&)
X и полоса
Программа выведет
1
.
int bar(int(x));
— странный способ объявления функции, он эквивалентенint bar(int x);
.Если вы перепутали с приведением типа,
int bar((int(x)));
- это приведение типа.
Затем мы пытаемся неявно привести адрес функции к
bool
, результат такого приведения всегда будетtrue
.
Функция
bar()
никогда не использовалась, что позволяет нам избежать ошибки неиспользуемого символа при компоновке.
Конструкторы
Последняя строка —
Foo(const Foo&, int)
.
Foo(i)
— это объявление переменной, то же самое, что иFoo i
. Таким образом, член класса под именемi
скрыт в этой области видимости.