войти зарегистрироваться

PHP whois

индекс
169,28

Чудеса оператора ==

Как вы думаете, обладает ли оператор == в PHP свойством транзитивности? Транзитивность в данном случае означает следующее:
если $a == $b и $b == $c, то $a == $c
Первое, что приходит в голову: "это ж очевидно, конечно обладает!"
А вот и нет:
     '0' == false; // true
     false == '';  // true
     '0' == '';    // false :)))
Добро пожаловать в увлекательный мир PHP. Занавес...

P.S.: Вот тут (и ещё тут, спасибо посмотреть профиль Kupyc), по всей видимости, это дело задокументировано (в комментариях дополнительных много всего интересного).

P.P.S.: Пост не для холивара, а к сведению и чтобы улыбнуться. Сам активно пишу на PHP.

комментарии (120)

  • Да, это верхушка айсберга "Преобразование типов". Имхо, скорее дело закоментировано по http://ru2.php.net/manual/ru/language.ty…
    • О, точно!
      Спасибо, сейчас добавлю...
  • ну, в общем-то, и нет ничего удивительного.
    вот оператор === как раз таки обладает таким нужным и полезным свойством :)
    • Как раз === и хотел привести в качестве аргумента.
      Если нужны строгие определения переменных, то вам в C++.
      Конечно, к каждому большому удобству идёт в комплекте и своё маленькое неудобство. О нём достаточно просто не забывать.

      Кстати, оператор "==" в Perl работает примерно аналогично.
      • > perl -e "print '0' == ''"
        1

        Именно что "примерно" ;)
        • Просто в перле "==" — оператор сравнения чисел. Он всё приводит к числам. А для строк там "eq". Ну и конечно '0' eq '' не будет истинным.
  • Я для себя завёл негласное правило, всегда пользоваться только операторами === и !==. Наводит ясность и хорошо защищает от дальнейших проблем.
    • это уже паранойя
      • Да. Паранойя это моя особенность, представляющая определённую ценность. Испоьзование таких операторов является не самым ярким её проявлением.
      • НЛО прилетело и опубликовало эту надпись здесь.
    • Это не очень удобно, например, когда вы сравниваете число с ячейкой из БД , которая имеет тип string
      • имеет смысл приводить переменные к правильному типу. и безопасность выше и код логичней
    • производительность страдает. Самое умное - использовать это _когда_нужно_.
      • Откуда сведения?

        Эксперемент №1. Проверка оператора ===
        time echo "<?php \$a='0'; \$b=1; for (\$i = 0; 10000000 > \$i; ++\$i) \$c = (\$a === \$b); ?>" | php
        real 0m1.889s
        user 0m1.864s
        sys 0m0.012s

        Эксперемент №2. Проверка оператора ==
        time echo "<?php \$a='0'; \$b=1; for (\$i = 0; 10000000 > \$i; ++\$i) \$c = (\$a == \$b); ?>" | php
        real 0m3.132s
        user 0m3.120s
        sys 0m0.008s

        Очевидно, что оператор === почти в два раза быстрее чем ==. Для достоверности эксперемента, замеры произвёл несколько раз. Причина банальна: при операторе === интерпретатор не производит преобразование типов: если не совпали, то false.
        • Нет. Не в два раза быстрее, а примерно в пять раз. В выше приведённых примерах существенную часть занимает холостой ход цикла и оператор присваивания:

          time echo "<?php \$a='0'; \$b=1; for (\$i = 0; 10000000 > \$i; ++\$i) \$c = 0; ?>" | php
          real 0m1.662s
          user 0m1.652s
          sys 0m0.004s
    • Не нужно заводить правил, а нужно знать, как что работает и тогда не будет проблем.
      Операторы сравнения и тождественности, это разные операторы, каждый для своего случая.
      Если бы один из них был определенно лучше другого во всех случаях, то он бы один в языке и остался.
  • Это не занавес, а гибкость ;-)
    Для более строгого сравнения есть ===.
  • вообще все законно
    перед сравнением пхп должен привести все к одному типу
    '0' == false; // (bool)'0' вернет false
    false == ''; // тут тоже самоме по сути
    '0' == ''; // а тут уже сравнение строк
    • Собственно, да.

      ml@pivo ~ $ echo "<? echo 0 == '' ?>" | php && echo
      1
  • НЛО прилетело и опубликовало эту надпись здесь.
    • результат будет true
      но это не значит что '0' == ''
    • В Вашей записи и правда нет никакой тонкости, не поймите неправильно :). Автор же демонстрирует неплохой пример php-софизма, в чем, собственно, и заключается смысл топика, если я все верно понимаю - в софизме.
  • Просто программист не должен начинать с PHP. Он должен получить сперва классическое для этой области знаний образование. Любой писавший на Си, осторожно относится к приведению типов, даже если этим занимается движок интерпретатора. Каждый раз когда пишешь что-то вроде $string==false должен зазвонить колокольчик у виска - "верно ли я понимаю процесс сравнения здесь?"

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

    Так что ничего пародоксального в данном факте нет, я считаю. Оператор не обязан быть транзитивен при манипуляциях со столь далекими друг от друга типами.
    • Не надо писать $string == false, надо писать if !$string.

      А в том факте действительно ничего парадоксального нет, нужно просто понимать механизмы языка, с которым имеешь дело.
      • А я так никогда и не пишу. Это перефразированный пример из топика. И бывает что подобные конструкции чем-то оправданны... Нельзя лишь делать это автоматом и необдуманно.
      • Как раз таки следует писать (if $string == false).
        • А еще лучше if(false == $string). ы!
          • для работы со строками лучше пользоваться строковами операторами
            if(strlen($string))
            и самому спокойней и каждому кто будет твой код ковырять сразу бедут понятно что $string это строка
            • а по-моему если надо убедиться, что строка непустая, достаточно сравнить ее с пустой строкой. зачем заставлять интерпретатор считать кол-во символов в строке, если оно вам не надо?
              • вот именно, нужно писать просто: if($string==="")
              • А чего считать, он это число и так знает :-)
                • Ну так все равно, сперва нужно "преобразовать" строку в ее длину а потом привести целое число к булевому типу (если проверять if(strlen($string))). Вместо того чтобы просто сравнить две строки.
                  • Длина хранится в строке в одной из байтов. Не нужно ничего преобразовывать (:
                    Операция сравнения строк тоже тривиально, ага?
                    • Это не Паскаль, чтобы в foo[0] хранить размер. Здесь строки как в Си, т.е. в конце символ с кодом 0.
                  • Наверняка ведь при сравнении строк сначала сравнивается их длина.
          • так делать можно только новичкам, которые могут забыть вместо одного знака равно написать два.
            когда же человек уже навострился в программировании в C-подобных языках, это уже не нужно, и будет только мешать
            • Так следует делать, чтобы акцентировать свои намерения. конструкция if(!foo) априори хуже читается, особенно в сложных выражениях. Сравните
              if(foo && (!(a + f1(b, c)) || f2(c)))
              и
              if(foo == true && ((a + f1(b, c)) == false) || f2(c) == true))
              • это дело привычки. не могу сказать что лично мне нижняя на порядок легче
        • Ну-ну
    • Программист должен программировать. На подходящем ему языке.
      А классическое образование приводит к забитой теорией голове и неспособности решать практические задачи.
      У знакомых тут команда классиков пишет. По два высших у каждого. Умные алгоритмы знают, да. А вот о безопасности понятия ВООБЩЕ не имеют, SQL injection на каждом углу пролезает.
      • Как бы не умеют просто писать, вот и всё. Они не классики, они просто слабые программисты, а дипломы сейчас только ленивые не получили.
      • Я уверен, что php - инструмент для грамотных программистов, обязательно знакомых со строго типизированными языками. Тогда он "играет".

        В противном случае это сродни магии. "Ух-ты как себя ведет программа!" на каждом шагу возникает.
  • Несколько странно, что человек, «активно пишущий на PHP», удивляется, получая закономерное false при сравнении разных строк. ;-)
    • Я удивляюсь не этому, а отсутствию наличия транзитивности, которая, казалось бы, очевидно должна присутствовать :)
      • Она и присутствует ;)
        Исправьте пожалуйста весь пост
        • Лучше раскройте тему того, что при сравнении == производится попытка привести к одинаковым типам.
        • Не понял вас. О чём вы? Транзитивности нет, нечего исправлять...
          • А откуда она там должна взяться? Кто-нибудь вам говорил, что == транзитивен?

            Лучше распишите, что происходит, когда вы делаете ==, а не просто "первое, что приходит на ум". Так напишите, тем, кто не знает, почему ее тут нет.

            А то пока от топика у новичков создается впчатление, что в пыхе опять что-то не так, хотя на самом деле это в знаниях "не так" ;)
            • Сорри, в комменте имел ввиду транзитивность операции сравнения. В смысле ===
            • Ну кто-кто? не помню, возможно на уроках математики. Если A равно B и B равно С, то A равно С - согласитесь, логично.

              А зачем расписывать то, что в прекрасной документации php и так написано чёрным по белому? Ссылки я дал.

              А что новичков обманывать, в пыхпыхе действительно много что "не так", однако мне это не мешает его по своему "любить" и писать на нём много лет.

              Мои знания тут не причём. Почему транзитивности нет в данном случае - совершенно понятно - и я уверен, что большинство программистов знает принципы сравнения, однако я так же уверен, что почти никто не обращал внимания на отсутствие транзитивности.
              • == - это не "равно". Это что-то типа "А, при приведении типов, даст результат сходный с Б".
  • Так все же просто...

    '0' — это строка
    '' — это строка
    false — это булево

    (boolean) '0' — это булево
    (boolean) '' — это булево

    получается что: (boolean) '0' == (boolean) '' == false
  • Все логично и правльно.
    Свойство транзитивности не может оставаться строгим когда идет преобразование типов из-за возможных потерь информации.

    '0'== false // true
    false == '' // true
    '0' == '' // false Строка содержащая 1 символ, не равна пустой строке.
    Но
    0 == '' // true


    Потому что:
    $a='0';
    $b=false;
    $c='';

    to STRING:
    a=0
    b=
    c=
    to BOOL:
    a=
    b=
    c=
    to INT:
    a=0
    b=0
    c=0
  • Капец. Про неявное приведение типов вы никогда не слышали?
    • Конечно слышал. Это всего лишь забавное наблюдение по поводу транзитивности. Капец... :)
  • "Сам активно пишу на PHP."
    Не надо. Ни одному человеку который учился не по книгам "PHP за 24 часа" даже в голову не прийдет такое сравнивать ибо результат вполне предсказуем.
  • Занавес это переполнение памяти при использовании str_replace() в старых версиях. В новых не проверял, думаю поправили. Но там еще много таких багов.
  • Видимо, стоит поменять пример с '0' == '' на 0 == '', а то действительно получается сравнение двух строк.
    • угу, да только данные из $_ENV, $_SERVER, $_GET, $_POST etc. достаются строки (про массивы умолчим), и соответственно
      $_GET['here_is_my_zero'] == '' будет работать как в примере...
  • ух, какая полемика :)
    про Java или C# такой спор в принципе не мог произойти, ибо сравнивать в них можно только одинаковые типы (даже при сравнении short и long IDE будет намекать на ошибку, а String'и в Java через "==" не сравнивают)...

    а тут с одной стороны вроде как гибкий инструмент, а с другой "сдуру можно и хер сломать" :)

    >даже в голову не прийдет такое сравнивать
    прийдет или не прийдет дело десятое. это же иллюстрация :)
    • Что поделать, специфика слабо типизированного языка
    • Иллюстрация, простите, к чему? К тому что превосходно изложено в документации к языку или в Zend PHP5 Certification Study Guide? Мне сейчас лениво рыться в этом талмуде, но типизация в PHP там расписана довольно хорошо, при чем на первых страницах. Ссылки же на документацию были выше.

      Это я к тому, что учить язык надо по вот таким источникам. Тогда и иллюстрировать ничего не прийдется. Те кто действительно знает PHP прекрасно понимают в чем фишка, и почему работает именно так. Те же кто знаком с ним по упомянутым мною выше "... за 24 часа" прочитают, забудут через 15 минут и уж тем более не извлекут никакого для себя урока.
    • Сдуру можно поломать всё что угодно.
  • Зато ПХП это глобально и надежно! (с) l.o.r.
  • аналогично:
    'муха' == true
    'слон' == true
    'муха' == 'слон'
    • класс :)
      Ваш пример, в отличие от моего, можно размножить до бесконечности :)
      Однако вероятность ошибки по неосторожности в случае с нулём и пустой строкой больше.
      • По неосторожности ошибок можно нагородить где угодно. А в данном случае ошибка может возникнуть, когда вы работаете с переменными и точно неизвестно какого она типа. Тогда необходимо явно преобразовывать тип переменной:
        $x == false; // true
        false == $y; // true
        $x == $y //???

        тогда лучше так
        intval($x) == intval($y)

        или так
        strval($x) == strval($y)
        в зависимости от контекста
  • Хороший язык PHP, ничего не скажешь. Особенно то, что непустая строка может быть == false — это парни вообще отожгли.
    • это неизбежные последствия нетипизированности языка. главное об этом помнить и грамотно пользоваться.
      • Ладно, буду помнить (читай "буду пользоваться более разумными языками" :).
        • разумность разумности рознь. если вы не готовы платить аккуратностью за гибкость — значит, что PHP не для вас, но никак не то что он неразумен.
          • Нет, не готов. Я люблю гибкость забесплатно, без кучи неочевидных граблей.
            • там нет неочевидных граблей. всё четко расписно в мануале
        • Да пользуйтесь вы чем хотите.
          Только зачем публично определять свою позу - все пхпшники в г-не, а я один в белом?
    • Вы, видимо, думаете, что где-то иначе?

      mysql> SELECT '0'=FALSE FROM DUAL;
      +-----------+
      | '0'=FALSE |
      +-----------+
      | 1 |
      +-----------+
      1 row in set (0.00 sec)

      mysql> SELECT FALSE='' FROM DUAL;
      +----------+
      | FALSE='' |
      +----------+
      | 1 |
      +----------+
      1 row in set (0.00 sec)

      mysql> SELECT '0'='' FROM DUAL;
      +--------+
      | '0'='' |
      +--------+
      | 0 |
      +--------+
      1 row in set (0.00 sec)
  • Последнее вполне нормально: оба операнда — строки, никаких преобразований типов не будет, а строки между собой не равны.
  • Контекст, однако.
    Фича известная.

    И чего к PHP докопались? Тогда уж с Перла начните.
  • Как раз сегодня на работе обсуждали поднялась тема приведения типов в пыхе. По мотивам - написал в блоге. http://nordluf.blogspot.com/2007/10/string-to-int-voodoo.html
    • Кто вас вообще учил использовать Эхо для дебага???
      • *чисто для интереса*
        а сколько и какие именно средства дебага на PHP вы сможете сходу назвать?
        • var_dump() справится с задачей значительно эфективнее и несомненно поможет автору найти ответы на мучающие его вопросы. Вы так не считаете?
          • Вы ошибаетесь: результат мне известен - boolean. Я ведь делаю не вывод значения переменной, а демонстрирую результат сравнения.
            • > демонстрирую результат сравнения (пост выше)
              > displays structured information about one or more expressions (php.net)

              А это не одно ли и то же? Как пожно предположить, цитата относится далеко не к описанию echo.

              > результат мне известен
              так обычно пишут, когда знают(!) не только тип данных возвращаемых операцией сравнения, но и результат этого сравнения. О вот он, как мне показалось ( "Это обьяснить и понять уже сложнее" (с) ) для вас немного остался загадкой.

              П.С.: прошу извинить, если мой тон показался вам излишне резким. Ни в коем случае не хотел на вас "наезжать" ибо и сам на абсолютное знание не претендую.
              • Ну тип всегда - boolean. Проверить можно просто посмотрев что вернет gettype("0xff"==255), а так же все другие сравнения. Это будет string(7) "boolean".
                А явное преобразование boolean в integer у меня пока не вызывает сомнение. Поэтому я пишу echo.
                Кроме того, автоматически приписать в начале строки echo проще нежели писать более сложную регулярку для окружения результата var_dump'ом. ;)
                Я ведь, ествественно, не так смотрел всё это - это просто минималистический тестовый пример.
                • Ваша запись:
                  echo (int)("0xff"==0)."\n";
                  echo (int)((int)"0xff"==0)."\n";
                  echo (int)("0xff"==255)."\n";

                  Моя запись:
                  var_dump("0xff"==0, (int)"0xff"==0, "0xff"==255);

                  Какую-какую, простите, регулярку писать?
                  • Регулярку для обработки хлама который был, в вывод с var_dump ом.
          • да, вы правы, но мне просто интересно было, вдруг ещё что-то есть…
            • Ну есть еще print_r(), если тип данных не нужен. А вар_дамп даже Зендовцы используют в экзаменах, если мне память не изменяет. Все никак время не выкрою на ЗЦЕ себя попробовать ...
              • Как Вам, я уже понял. Лично мне удобнее пользоваться var_export'ом. Для меня он нагляднее + позволяет без мучений с ob_* функциями передавать эти дампы в переменных.
      • А это не дебаг: примеры написано исключительно для демонстрации.
        • Даже для демонстрации var_dump() подходит значительно лучше, поскольку показывает нам не только значение, но и тип. А ведь именно о приведении типов мы сейчас говорим.
    • Попробую вам объяснить то, что вам сейчас (судя по блогу) непонятно:

      (int)("0xff"==0)
      строка 0xff превосходно конвертируется в число в 16-ной форме. Далее приводится к десятичному 255. Это не равно нулю, а false при преобразовании в int становится нулем.

      (int)((int)"0xff"==0)
      Здесь интерпретатор не может преобразовать строку к 16-ричному виду, поскольку вы ему указываете на необходимость сразу преобразовать строку в интеджер. Используется явное преобразование, что дает нам 0, значит результат true, как следствие - 1

      (int)("0xff"==255)
      См. первое решение.
      • Мне непонятен тут исключительно один момент: почему преобразование в integer работает по разному. Когда сравнивается "0xff" и 255 - приведение в integer(255). В случае явного преобразования 0xff - в integer(0).
        • А вы воспользуйтесь intval() с указанием основания. И все будет замечательно.
          А приводит оно так, ибо сказано: the default is base 10
          • Интересная мысль, которая мне не приходила в голову. Видимо, из-за ощущения, что попытка приведения строки к числу должна быть реализована всегда одинаково. Откуда берется base 16? Видимо проверяется содержание строки. Почему тогда нет этого при явном приведении к инту? Лично для меня x16, x10, x8, x2 - всё тот же родной int. Я хорошо понимаю откуда что берется, меня удивляет именно разница в поведении в момент приведения.
            • Могу предположить, что происходит так из-за того, что неявное преобразование использует функцию strtod из stdlib на которую они и ссылаются в мануале. Она превосходно конвертирует и 10, 16, бесконечность и NAN. В то время как явное преобразование с помощью intval реализовано у них своими силами, на что довольно таки прозрачно намекает документация, где сказано, что основание указывается необязательным параметром функции, в то время как strtod определяет его автоматически.
              • Именно об этом я и написал. Чтоб узнать каким именно образом происходит обработка явного и неявного приведения я в исходники всё таки не полезу, но да не суть важно. Собственно, моё удивление связанно как раз с тем, что оба эти, на мой взгляд одинаковые, операции реализованный по разному. Ни в коей мере не умаляя достоинств разработчиков, я считаю что это неверное решение.
                • "Это не баг, это фича" (с)
                  Соглашусь с тем, что это несколько странно. Но как уже здесь писали, хотя несколько по другому поводу - для хорошего(!) разработчика главное такие тонкости знать и уметь использовать себе во благо.
                  • Угу. Как обычно и бывает - пришли к выводу что говорим об одном и том-же разными словами.

                    Кстати, одна из причин написать в блог развёрнуто и с примерами, была в том, что я хотел проиллюстрировать это поведение. Именно для того, о чём Вы написали выше (про разработчика). Собьсно тот пост вырос из развёрнутого обьяснения новенькому почему if ($rt=="0") {...} срабатывает, когда $rt==="all".
                    • Ошибся. Правильно конечно же if ($rt==0) {...}
        • Вот здесь многое изложено со ссылками на более детальные пояснения по темам и с указаниями куда именно смотреть при желании найти еще более подробную информацию.
    • для людей писавших на нормальных языках

      Так зачем вы вообще за PHP взялись, если он ненормальный?
      • Вы меня неверно поняли. Лично у меня нет никаких претензий к PHP5. Это один из немногих языков. на которых я продолжаю писать после знакомства с ним. Тем не менее, я об этом тоже немного писал в блоге, многие программисты на "нормальных" языках относятся к нему как к "недоязыку для дизайнеров". Я много обсуждал это со своими друзьями, но в результате мне остаётся только грустно иронизировать время от времени. Они всё понимают мозгом, но отказываются это признавать.
        • Тогда извините )
  • Ответ скорее не автору топика, а многим из комментировавших.

    Дело в том, что в PHP оператор == НЕ ЯВЛЯЕТСЯ оператором равенства. Он лишь означает equivalence операндов, что в переводе на русский означает "равнозначность", но отнюдь не "равенство". Вот так непонимание русского языка (не важно кем, переводчиком либо читателем) приводит к непониманию азбучных истин программирования.
    • На сколько мне не изменяет память, то при ТОЧНОМ сравнении нужно пользовать ===
      • Именно так. На удивление многим это тоже не оператор равенства. Это оператор "идентичности". И, вы будете смеяться, но мне попадалась на книжных полках макулатура (по другому назвать язык не поворачивается) в которой вообще об этом операторе и слова не было сказано.
        • Я учился по книге и там это было указано в «Золотых ошибках»
        • Я учился на слове "эквивалентность"
  • отношение == (eq1), определенное на множестве строк, и отношение == (eq2) на множестве строк и булевых значений очевидно отношения разные. Каким образом отсутствие импликации: (a eq2 b И b eq2 c) -> a eq1 c влияет на транзитивность отношения eq1 или eq2 мне не понятно. И кстати в C++: 12 == true; 13 == true; 12 !=13, какой ужас!
    • Знаете, как ни странно, но в PHP 12 так же != 13.
      Отношение == на множестве строк и булевых значений не определено, поэтому строка приводится к булевому значению и выполняется операция определенная на множестве булевых значений. Так же, как и в C++.
  • Весь секрет фокуса крутится вокруг _строковой константы_ '0', вводящей начинающего программиста в заблуждение.
    Остальное уже было сказано.
  • Если строка '0' вводит программиста в заблуждение, то такому программисту пора подумать о покупке какой-нибудь книги.
    А лучше выучить какой-нибудь типизированный язык для разнообразия.

    Вообще если человек начинает программировать с PHP (!) - велика вероятность не стать хорошим программистом.

    Этот топик во мне вызвал чувство деградации... сам пишу на PHP, т.к. работа вынуждает. Грустно. Эх... пойду что-ли Страуструпа почитаю...
    • Да, пыхпых породил немало быдлокодеров.
      Сам, сильно увлёкшись пхп, и забыв о типах, потом довольно болезненно начинал сишарп.
  • Первое, что приходит в голову лично мне: "Это ж очевидно: конечно, не обладает!"
  • Автоматическое преобразование типов хотя и радует нас такими сюрпризами, но немного почитав манов, да набравшись опыта, можно всё это предвидеть.

    Зато не нужно парится о типах в процессе кодинга. PHP позволяет мне излагать свои мысли прямо по ходу их поступления, не отвлекаясь на объявления переменных, размышления о том, какой тип рациональнее. Меня не ставят в строгие границы, как это, например, обожает делать C#.
  • И вообще, ситуация из приведённого примера абсолютно очевидна.

    В первой ситуации строку сравнивают с булевым типом. Пхп преобразует строку в bool и сравнивает 0 ? false. Конечно же, равны.
    Кстати, это популярный источник ошибок. Ошибки, основанные на этом равенстве я находил даже в IPB. А именно, когда программист хочет проверить, указана ли строка: if ( $string ) { .. } Очевидно, что если юзер напишет строку "0", то условие не выполнится. Благодаря этому, в недавней версии ИПБ нельзя было поставить личный статус "0".

    Вторая ситуация очевидна.

    В третьей ситуации сравниваются две строки. Никакого преобразования не происходит. Две строки нифига не одинаковые, следовательно false;
  • Никаких чудес, просто строка 0 не равна пустой строке.
Только авторизованные пользователи могут оставлять комментарии. Авторизуйтесь, пожалуйста.