Сообщений 59    Оценка 306 [+2/-10]         Оценить  
Система Orphus

Занимательный C++

Автор: Старостин Василий Викторович
Источник: RSDN Magazine #2-2010
Опубликовано: 19.03.2011
Исправлено: 10.12.2016
Версия текста: 1.1
Список литературы

Мальвина дала Буратино 3 яблока, потом забрала назад одно. 
Вопрос: Сколько яблок осталось у Буратино?
Ответ: Да кто его знает. Неизвестно, сколько яблок у него было до этого!

Может ли C++ быть веселым? Давайте проверим! :) Я знаю несколько примеров, которые радуют программистский взгляд. И предлагаю взглянуть!

Разомнемся на кошках. Представьте, что вы видите следующий код:

      int i = 5;
int j = i++ + ++i;

Чему будет равно j? Вы хорошо подумали? Дело в том, что правильный ответ – где-то от 10 до 14! Мой любимый GCC выдает 12, но это еще не предел. Стандарт C++ не обязывает, чтобы результат оператора ++ был вычислен до операции присвоения, поэтому ответ может меняться от компилятора к компилятору, и даже зависеть от ключей оптимизации. Ведь результат операции i = i++, и тот не определен! Желающие могут погуглить "C++ Sequence point", а мы пойдем дальше. :)

Как вы думаете, возможен ли такой код:

      int x[x];
int y = y;
j j;
int i::i = j.j;

Как ни странно, вполне возможен, если сделать так:

      const
      int x = 1;
constint y = 2;
struct j
{
   int j;
};
namespace i
{
   int x[x];
   int y = y;
   j j;
   int i::i = j.j;
};

Перекрытие видимостей делает свое черное дело! :) Впрочем, MS Visual C++ такой код не берет: объявление “int j” он считает объявлением конструктора, а тот, как известно, не может возвращать значение. А вложенная переменная “y”, несмотря на «инициализацию», будет иметь произвольное значение (если окажется автоматической). И это хороший признак того, что с подобными «приемами» баловаться не стоит!

Возьмем еще один интересный синтаксический пример. С этого примера началась моя коллекция приколов, и его я люблю больше всего. Ведь он, помимо всего прочего, содержит еще и психологический трюк. Смотрите сами:

T t1;
T t2(t1);
T t3 = t1;
T t4();

Какой конструктор вызовется в каждом из четырех случаев? Если вы решили, что это:

то вы ошиблись... дважды! Первым, конечно, идет конструктор по умолчанию, а вторым – конструктор копирования, что очевидно следует из синтаксиса. Но в третьем случае, несмотря на знак "=", у нас все равно срабатывает старый добрый конструктор копирования: ведь мы создаемновый объект, а не инициализируем существующий! В последнем же случае это вообще не создание объекта, а... объявление функции, которая не принимает параметров и возвращает тип T, сравните:

      int foo();

Если вы ни разу не ошиблись – я вас поздравляю. Если все-таки разок да ошиблись и теперь жаждете реванша – то вот вам еще одна возможность! Пускай у вас есть такой класс:

      class A 
{   
public:
  A()                    { printf("default\n"); }
  A(long)                { printf("long\n"); }
  explicit A(int)        { printf("int\n"); }
  A(const A&)            { printf("copy\n"); }
  A &operator=(const A&) { printf("op=\n"); }
};

Есть и использующий его код:

      void prn(int n)
{
   cout << n << ": ";
}
int main()
{
   constint i = 0;
   prn(1); A a1();
   prn(2); A a2 = i;
   prn(3); A a3(i);
   prn(4); A a4 = A(i);
   prn(5); A a5 = (A) i;
   prn(6); A a6 = static_cast<A>(i);
   return 0;
}

Что в данном случае выведется на экран? Скажу честно, я сам здесь ошибся, причем жестоко и не один раз. Поэтому думайте внимательнее. Прикинули? Правильный ответ таков:

1: 2: long
3: int    
4: int    
5: int    
6: int    

Единица, понятное дело, пуста – как вы помните, это объявление функции. Двойка не смогла построить класс из int’а – он ведь explicit, но компилятор услужливо конвертнул int в long – а там, гляди – конструктор уже имеется. Остальные варианты – это вариации на тему A(i), просто по-разному оформленные. Можно только заценить грамотность транслятора, который ни разу не использовал лишние конструктор копирования или оператор присваивания, и даже конструктор по умолчанию. На то он и С++! :)

И последний пример, может быть, самый «практичный» из приведенных. Пускай вам надо сделать цикл, в котором значение переменной стремится к нулю. Конечно, можно воспользоваться оператором for. Но ведь можно сделать и так:

      int i = 10;
while (i --> 0)
{
   cout << i << endl;
}

А называется эта конструкция – оператор "стрелка". :)

Вот такие штуки возможны в нашем родном :) языке. А там, глядишь, подоспеет C++0x (скорей бы уж!), где, я думаю, нас ожидает еще немало подобных приколов. А пока давайте помнить, что этот код все-таки шутка, а не руководство к действию! :) (от редакции – когда будете вставлять смайлики в код, не забудьте предварять их знаками комментария!) :)

Список литературы

  1. Стандарт C++
  2. Герб Саттер, «Решение сложных задач на C++»
  3. Блуждание по просторам интернета


Эта статья опубликована в журнале RSDN Magazine #2-2010. Информацию о журнале можно найти здесь
    Сообщений 59    Оценка 306 [+2/-10]         Оценить