Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

1.2. Выражения

Итак, имена надо подбирать так, чтобы максимально облегчить жизнь читателю; точно так же и выражения следует писать так, чтобы их смысл был предельно ясен. Пишите самый ясный код, какой только возможен. Вставляйте пробелы вокруг операторов для логической группировки выра­жений; вообще, форматируйте выражения так, чтобы сделать их наиболее удобочитаемыми. Идея тривиальна, но очень полезна: совсем как мысль о том, что чем аккуратнее убран ваш рабочий стол, тем проще на нем найти необходимую вещь. Правда, в отличие от рабочего стола в вашей программе могут копаться и посторонние — и им тоже должно быть удобно.

Форматируйте код, подчеркивая его структуру. Форматирование кода логично выбранными отступами — самый простой способ добиться того, чтобы структура программы стала самоочевидна. Приведенный фраг­мент плохо отформатирован:

? for(n++; n<100; field[n++]='\0');

? *i = ‘\0’; return(‘\n');

Проведя простейшее форматирование, мы несколько улучшим его:

? for (n++; n < 100; field[n++] = '\0')

? ;

? *i = ‘\0’;

? return('\n');

Еще лучше — перенести оператор присваивания в тело цикла и выде­лить инкремент (приращение) счетчика в отдельный оператор, тогда цикл примет более традиционный вид и, следовательно, можно будет быстрее догадаться о его смысле.

for (n++; n < 100; n++)

field[n] = ‘\0’;

*i = ‘\0’;

return '\n';

Используйте естественную форму выражений. Записывайте выраже­ния в том виде, в котором вы произносили бы их вслух. Условные выра­жения, содержащие отрицания, всегда трудно воспринять:

? if (!(block_id < actblks) || !(block_id >= unblocks))

? .......

Здесь каждая проверка используется с отрицанием, однако необходимо­сти в этом нет никакой. Достаточно изменить знаки сравнения, и можно обойтись без отрицаний:

if ((block_id >= actblks) || (block_ld < unblocks))

Теперь код читается вполне естественно.

Используйте скобки для устранения неясностей. Скобки подчеркива­ют смысловую группировку кода, и их можно использовать для упроще­ния понимания его структуры даже тогда, когда с точки зрения грамма­тики в их применении нет необходимости. Так, в предыдущем примере внутренние скобки не нужны, однако их применение как минимум ниче­му не мешает. Бывалый программист мог бы и опустить их, поскольку операторы сравнения (< <= == ! = >= >) имеют более низкий приоритет, чем логические операторы (&& и || ).

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

while ((с = getchar()) != EOF)

Побитовые операции & и | имеют более низкий приоритет, чем "обыч­ные" операции сравнения типа ==, так что, несмотря на, казалось бы, очевидный порядок выполнения, выражение

? if (x&MASK == BITS)

? ...........

на самом деле выполнится как

? if (х & (MASK==BITS))

? .........

а это, очевидно, вовсе не то, что подразумевал программист. Поскольку используется комбинация операций сравнения и побитовых операций, скобки просто необходимы:

if ((x&MASK) == BITS)

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

? 1еар_уеаг = у % 4 == 0 && у % 100 != О | | у % 400 == 0;

но с ними фрагмент станет гораздо проще для понимания:

1еар_уеаг = ((у%4 == 0) && (у%100 != 0)) || (у%400 == 0);

Мы убрали здесь некоторые пробелы: группировка операций с самым высоким приоритетом помогает читателю быстрее разобраться в струк­туре программы.

Разбивайте сложные выражения. Языки С, C++ и Java имеют богатый и разнообразный синтаксис, поэтому многие увлекаются втискиванием всего подряд в одну конструкцию. Приведенное выражение весьма ком­пактно, однако в нем содержится слишком много операторов:

? *х += (*xp=(2*k < (n-m) ? c[k+1] : d[k--] ));

Если разбить это выражение на несколько, оно станет гораздо более удобочитаемым:

if (2*k < n-m)

*хр = c[k+1];

else

*xp = d[k--];

*x += *xp;

Будьте проще. Безудержная энергия программистов иногда направля­ется на то, чтобы написать наиболее краткий код или достичь результата самым хитрым и красивым способом. Иногда, впрочем, эти таланты тра­тятся впустую: настоящая задача в том, чтобы написать код попроще, а не похлеще. Вот, к примеру, что делает это замысловатое выражение?

? subkey = subkey >> (bitoff - ((bitoff >> 3) << 3));

Самое внутреннее выражение сдвигает bitoff на три бита вправо. Ре­зультат сдвигается обратно влево, при этом три сдвинутых бита замещаются нулями. Затем результат вычитается из первоначального значе­ния, оставляя три младших бита bitoff, которые используются для сдвига subkey вправо.

Таким образом, приведенная конструкция эквивалентна

subkey = subkey >> (bitoff & 0x7);

Над первой версией приходится гадать некоторое время, чтобы понять, что там происходит; со второй же все просто и ясно. Опытные програм­мисты записали бы ее еще короче:

subkey >>= bitoff & 0x7;

Некоторые конструкции кажутся специально созданными для того, чтобы сбить читателя с толку. Например, применение оператора ? : мо­жет привести к появлению абсолютно загадочного кода:

? child=(!LC&&!RC)?0:(!LC?RC:LC);

Согласитесь, что, только разобрав все возможные варианты сравнений, можно догадаться, что же делает этот фрагмент. Приведенная ниже кон­струкция длиннее, но значительно проще, потому что возможные вари­анты видны сразу:

if (LC == 0 && RC == 0)

child = 0;

else if (LC == 0)

child = RC;

else

child = LC;

Операция ? : хороша только для коротких и простых выражений-, где она может заменить четыре строки if-else одной, как в следующем примере:

max = (а > b) ? а : b;

или даже в таком случае:

printf("The list has %d item%s\n", n, n==1 ? “” : "s");

однако подобная замена все же не является распространенной.

Ясность и краткость — не одно и то же. Часто более ясный код будет и более коротким (как в примере со сдвигом битов), однако он может быть и, наоборот, более длинным (как в примере с if-else). Главным кри­терием выбора должна служить простота восприятия.

Будьте осторожны с побочными эффектами. Операции типа ++ имеют побочные эффекты: они не только возвращают значение, но еще и изме­няют операнд. Эти побочные эффекты иногда могут быть весьма удоб­ны, но они же могут вызвать трудности из-за того, что получение значе­ния и обновление переменной могут происходить не тогда, когда ожидается. В С и C++ порядок выполнения этих побочных эффектов вообще не определен, поэтому нижеприведенное множественное при-сва'ивание, скорее всего, будет выдавать неправильный ответ:

? str[i++] = str[i++] = ‘ ‘;

Смысл выражения — записать пробел в следующие две позиции строки str. Однако в зависимости от того, когда будет обновляться i, позиция в str может быть пропущена, и в результате переменная i будет увели­чена только на 1. Разбейте выражение на два:

str[i++] = ' ' ;

str[i++] = ' ';

Даже если в выражении содержится только один инкремент, резуль­тат может быть неоднозначным:

? array[i++] = i;

Если изначально i имеет значение 3, то элемент массива может принять значение 3 либо 4.

Побочные эффекты есть не только у инкремента и декремента; ввод-вывод — еще один потенциальный источник возникновения закулисных действий. В следующем примере осуществляется попытка прочитать два взаимосвязанных числа из стандартного ввода:

? scanf("%d” , &yr, &profit[yr] );

Выражение неверно, поскольку одна его часть изменяет yr, а другая ис­пользует ее. Значение profit[yr] будет правильным только в том случае, если новое значение уг будет таким же, как и старое. Вы, наверное, думае­те, что все дело в порядке вычисления аргументов, однако в действительности проблема здесь в том, что все аргументы scanf вычисляются до того, как эта операция вызывается на выполнение, так что &profit[yr] всегда будет вычисляться с использованием старого значения yr. Проблемы по­добного рода могут возникнуть в любом языке программирования. Для исправления достаточно опять же разбить выражение на две части:

scanf("%d", &yr);

scanf("%d", &profit[yr] );

Будьте осторожны с любым выражением, вызывающим побочные эффекты.

Упражнение 1-4

Улучшите каждый из-приведенных фрагментов:

? if ( !(с == 'у' || с == 'Y” ) )

? return;

? length = (length < BUFSIZE) ? length : BUFSIZE;

? flag = flag ? 0 : 1;

? quote = (*line =="") ? 1 : 0;

? if (val & 1)

? bit = 1;

? else

? bit = 0;

Упражнение 1-5

Найдите ошибку в приведенном фрагменте:

? int read(int *ip) {

? scanf("%d", ip);

? return *ip;

? }

? ........

? insert(&graph[vert], read(&val), read(&ch));

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]