paint-brush
Скрытые жемчужины C и C++, о которых вы, вероятно, не знаетек@udalov
11,160 чтения
11,160 чтения

Скрытые жемчужины C и C++, о которых вы, вероятно, не знаете

к Ilia6m2024/08/09
Read on Terminal Reader

Слишком долго; Читать

C++ — отличный язык со слишком большим количеством функций. За свою карьеру разработчика C++ я пережил множество моментов «WTF». В этой истории вы найдете самые занимательные аспекты программирования на C и C++, сжатые в виде тестов. Вы ни за что не сможете ответить правильно больше, чем на пару вопросов.
featured image - Скрытые жемчужины C и C++, о которых вы, вероятно, не знаете
Ilia HackerNoon profile picture
0-item
1-item

Вы готовы присоединиться к величественному миру программирования C и C++? Хотите ли вы подвергнуть сомнению свое существование после нескольких простых строк C++?


Если ваш ответ "Yeh!", "Yep" или "Why not?" - добро пожаловать на проверку ваших знаний. Вам будет предложено несколько вопросов, связанных с C или C++.


Правильные ответы и пояснения вы найдете в конце истории. Удачи!

1. Самая маленькая программа

 main;


Что произойдет, если вы попытаетесь скомпилировать эту программу с помощью компилятора C?

  1. не будет компилироваться
  2. скомпилируется, не будет компоноваться
  3. скомпилирует и свяжет

2. Вилка

 #include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }


Сколько строк напечатает эта программа?

  1. 1000
  2. менее 1000
  3. более 1000

3. Все, что вам нужно, это индексы.

 #include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }


Что напечатает эта программа?

  1. 1
  2. 2
  3. 3
  4. 4
  5. не будет компилироваться
  6. неопределенный

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 мс
  2. 1 сек.
  3. 1 мин.
  4. 1 час
  5. 1 год
  6. навсегда

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; }


Последняя строка, которую выведет эта программа, это…


  1. Foo()
  2. Foo(Foo&&)
  3. Foo(const Foo&)

6. X и черта

 #include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }


Что напечатает эта программа?

  1. 0
  2. 1
  3. 0x0
  4. не будет компилироваться
  5. не будет ссылки

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(); }


Последняя строка, которую выведет эта программа, это…

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. Foo(int)

Вместо заключения

Надеюсь, вы никогда не найдете ни одного из этих странных фрагментов в дикой природе.

Ответы

  1. Самая маленькая программа


    Это допустимый код C. Он успешно скомпилируется и линкуется. Он зависнет, если вы попытаетесь его запустить. main; - глобальная переменная.


    В коде C можно опустить много вещей. Например, можно опустить тип глобальной переменной. По умолчанию компилятор будет считать, что этот тип — int . Также в C нет искажения имен (в отличие от C++), поэтому при связывании нет способа отличить переменную main от функции main .


    Таким образом, компилятор скомпилирует корректный код, а компоновщик найдет что-то с именем main в объектном файле, чтобы скомпоновать программу.


  2. Вилка


    Это больше функция POSIX, чем функция C или C++. Реализации операций ввода-вывода используют буферы для оптимизации производительности. Когда вы вызываете fork , ОС создаст копию-при-записи дубликата памяти процесса, буферы ввода-вывода, скорее всего, также будут дублироваться, а буферизованные строки , скорее всего, будут выведены более 1000 раз .


  3. Все, что вам нужно, это индексы


    Ответ 3.


    Чтобы понять этот код, давайте подробнее рассмотрим, как работают индексы в C и C++: array[index] — то же самое, что *(array + index) , то же самое, что (index + array) и то же самое, что index[array .

    Вторая подсказка — оператор , . Это бинарный оператор, он отбрасывает левый аргумент и возвращает правый аргумент.


  4. Регулярные выражения


    Невозможно предсказать, что произойдет! Поведение зависит от реализации.


    В моей среде эта программа вызывает исключение The complexity of an attempted match against a regular expression exceeded a pre-set level.


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


    Первое - преобразовать регулярные выражения в конечные автоматы O(n**2) (n - длина шаблона), сопоставить строку O(m) (m - длина строки). Этот подход не поддерживает возврат.


    Второй — жадный подход + DFS, поддерживает возврат, но склонен к экспоненциальному росту временной сложности в определенных шаблонах.


  5. Движения и лямбды


    Ответ: Foo(const Foo&) . Лямбды по умолчанию неизменяемы, все значения, захваченные в лямбду с помощью [] неявно являются const . Это разблокирует идемпотентное поведение для лямбд.


    При перемещении f вы создаете const Foo&& . const Foo&& — странный тип, поэтому компилятор просто копирует Foo


    Есть два способа исправить это:


    1. Создать изменяемую лямбду

       auto a = [f = std::move(f)]() mutable { return std::move(f); };


    2. Объявить конструктор Foo(const Foo&&)


  6. X и полоса

    Программа выведет 1 .


    int bar(int(x)); — странный способ объявления функции, он эквивалентен int bar(int x); .

    Если вы перепутали с приведением типа, int bar((int(x))); - это приведение типа.


    Затем мы пытаемся неявно привести адрес функции к bool , результат такого приведения всегда будет true .


    Функция bar() никогда не использовалась, что позволяет нам избежать ошибки неиспользуемого символа при компоновке.


  7. Конструкторы

    Последняя строка — Foo(const Foo&, int) .


    Foo(i) — это объявление переменной, то же самое, что и Foo i . Таким образом, член класса под именем i скрыт в этой области видимости.