вторник, 26 мая 2009 г.

деликатный switch

Не редко программисты забывают закрывать case ветку оператором break; или любым другим, который бы прекратил исполнение структуры switch. Но так же, достаточно часто программисты намеренно не закрывают case, как, например, в нижеприведенном коде:
// из исходника ls.c
case 'a':
    fts_oprions |= FTS-SEEDOT;
case 'A':
    f_listdot = 1;
    break;

Возникает некоторая двойственность, для человека читающего код может быть не очевидно: ошиблись вы или намеренно оставили case открытым. Для внесения ясности принято во всех случаях когда case необходимо оставить открытым, писать комментарий /* FALLTHROUGH */ . таким образом вышеупомянутый пример приводится к виду:
case 'a':
    fts_oprions |= FTS-SEEDOT;
    /* FALLTHROUGH */
case 'A':
    f_listdot = 1;
    break;

На комментарий так же реагируют некоторые компиляторы и не предупреждают, о, возможно по ошибке, открытом case.

степень двойки


Для возведения числа в степень k = 2^n используется логический оператор побитового смещения "<<". Таким образом, конструкцию вида a << n следует читать как a * k, где k = 2^n.
Аналогичным образом определяется операция деления числа на k = 2^n с использованием логического оператора побитового смещения ">>". Конструкцию a >> n следует читать как a / k, где k = 2^n.
Не стоит забывать о том, что оператор побитового сдвига ">>>" для реализации конструкции деления числа на степень двойки в JAVA не работает с отрицательными числами.

остаток от деления

результатом выполнения кода 65 & 7 будет остаток от деления 65 на 8 = 1. (& - побитовое логическое "И"). В общем случае результат выполнения конструкции a&b равен a%(b+1), где b = 2^n - 1. (% - оператор нахождения остатка от деления левого операнда на правый).
Теоретическая причина по которой конструкция a&b более популярна для нахождения остатка, в том что она исполняется быстрее чем её более понятный для чтения аналог. На практике, последние версии популярных компиляторов в состоянии проводить подобную оптимизацию кода самостоятельно, не говоря уже о том что разница в эффективности исполнения этих двух конструкций вообще не существенна на современных процессорах. В идеале, вы должны знать об этом и других подобных трюках для того чтобы понимать код написанный несколькими годами ранее, но случаи в которых их использование будет оправдано вами в настоящее время действительно встречаются редко.

if statements [ C / C++ ]

Исключительно в целях улучшения читабельности кода рекомендуется вместо структур вида: if ((p = q)) [1] писать: if ((p = q) != NULL) [2]. Это главным образом связано с тем, что программисты часто ошибаются используя '=' вместо '=='.
В случае когда проверка на NULL проводится явным образом[2] у человека читающего код, мысли о том что вы что-то перепутали не возникает, чего нельзя сказать о коде [1].

UPD:
инструкцией if((p = q) != NULL) имеет смысл пользоваться только, если q - это вызов функции, возвращаемое значение которой мы хотим сохранить. Например:
if((r = foo(...)) != 0) err(1, "foo");

thx2 ivaxer
.

обработка ошибок вывода [ C / C++ / Java ]

printf() в случае успеха возвращает число записанных байт.
putchar() возвращает EOF в случае если не получилось записать символ.
С одной стороны хорошо бы каждый раз при вызове любой системной функции проверять возвращаемое значение, но делать это бывает чересчур утомительно, в случае если подобных вызовов достаточно много.
Вместо этого можно проверять были ли ошибки на стандартном потоке вывода перед завершением программы.
В Java для этого пользуются методом checkError(); в С++ для этого пользуются методами fail(), good() или bad(); и в коде на C пользуется функция ferror: if (ferror (stdout )) err (1, "stdout");

сравнение строк [ C / C++ ]

strcmp() возвращает 0 в случае если две строчки равны, что не очень показательно.
Исключительно в целях повышения читабильности кода для проверки на эквивалентность двух строчек принято определять макрос:
#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0)