CHAP5 (1018805), страница 4

Файл №1018805 CHAP5 (Сборник литературы - С и С++) 4 страницаCHAP5 (1018805) страница 42017-07-08СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 4)

{

switch( i )

{

case 1: сделать_нечто(); break;

case 2: сделать_нечто_другое(); break;

default:

fprintf(stderr, "Внутренняя ошибка в f(): неверное значение i (%d)", i );

exit( -1 );

}

}

То же самое относится к блокам if/else, работающим в манере, схожей с оператором switch.

В цикле также нужна проверка на невероятное. Следующий фрагмент работает, даже если i первоначально равно 0 - чего по идее быть не должно:

f( int i ) // переменная i должна быть положительной

{

while ( --i >= 0 )

сделать_нечто();

}

Конструкция while(--i) менее надежна, так как она дает ужасный сбой в случае, если i сначала равно 0.

66.2. Всегда проверяйте код, вызывающий ошибку.

Это должно быть очевидно, но комитет ISO/ANSI по С++ потребовал, чтобы оператор new вызывал исключение, если он не смог выделить память, потому что было установлено, что удивительное множество ошибок во время выполнения в реальных программах вызвано тем, что люди не потрудились проверить, не возвратил ли new значение NULL.

Мне также довелось видеть множество программ, в которых люди не позаботились посмотреть, сработала ли функция fopen(), перед тем как начать пользоваться указателем FILE.

67. Избегайте явно временных переменных.

Большинство переменных, используемых лишь один раз, попадают в эту категорию. Например, вместо:

int x = *p++;

f( x );

должно быть:

f( *p++ );

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

f( Coefficient_of_lift * (0.5 * RHO * square(v)) ); // передать функции f() образующуюся

// подъемную силу

f( Коэффициент_подъемной_силы * (0.5 * RHO * квадрат(v)) ); // передать функции f()

// образующуюся подъемную

// силу

на:

double lift = Coefficient_of_lift * (0.5 * RHO * square(v));

f( lift );

double подъемная_сила = Коэффициент_подъемной_силы * (0.5 * RHO * квадрат(v));

f( подъемная_сила );

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

68. Не нужно магических чисел.

В основном тексте вашей программы не должно быть чисел в явном виде. Используйте перечислитель или константу для того, чтобы дать числу символическое имя. (Я уже объяснял, почему для этого не очень хорошо применять #define). Тут есть два преимущества:

  • Символическое имя делает величину самодокументируемой, устраняя необходимость в комментарии.

  • Если число используется более чем в одном месте, то менять нужно лишь одно место - определение константы.

Я иногда делаю исключение из этого правила для локальных переменных. Например, в следующем фрагменте используется магическое число (128):

f()

{

char buf[128]

...

fgets( buf, sizeof (buf) / sizeof(*buf), stdin );

}

Я использовал sizeof() в вызове fgets(), поэтому изменения размера массива автоматически отражаются в программе. Добавление дополнительного идентификатора для хранения размера добавит излишнюю сложность.

69. Не делайте предположений о размерах.

Классической проблемой является код, исходящий из того, что тип int имеет размер 32 бита. Следующий фрагмент не работает, если у вас 32-битный указатель и 16-битный тип int (что может быть при архитектуре Intel 80x86):

double a[1000], *p = a;

// ...

dist_from_start_of_array_in_bytes = (int)p - (int)a;

расстояние_от_начала_массива_в_байтах = (int)p - (int)a;

Более трудно уловима такая проблема в С (но не в С++):

g()

{

не_работает( 0 );

}

не_работает( char *p )

{

if( !p ) // вероятно не работает

// ...

}

Компилятор соглашается с этим вызовом, потому что в С разрешены ссылки вперед (и не разрешены в С++, так что там это не проблема). 0 это тип int, поэтому в стек помещается 16-битовый объект. Но функция ожидает 32-битный указатель, поэтому она использует 16 бит из стека и добавляет к ним еще 16 бит всякого мусора для создания 32-битного указателя. Вероятнее всего, что if( !p ) даст ложный результат, так как только 16 бит из 32 будут равны 0.

Традиционное решение состоит в использовании typedef :

typedef int word; // всегда 16 бит

typedef long dword; // всегда 32 бита.

После чего вы можете поменять операторы typedef в новой операционной среде, чтобы гарантировать, что word по прежнему имеет размер 16 бит, а dword - 32 бита. Для 32-разрядной системы предыдущее может быть переопределено как:

typedef short word; // всегда 16 бит

typedef int dword; // всегда 32 бита.

Другая связанная с размерностью часовая бомба спрятана в том способе, которым в ANSI С обеспечивается работа с иностранными языками. ANSI С определяет тип wchar_t для работы с расширенными наборами символов типа Unicode - нового 16-битного многонационального набора символов. Стандарт ANSI С также утверждает, что перед строкой с расширенными символами должен стоять символ L. Micrisoft и другие поставщики компиляторов стараются помочь вам писать переносимые программы, предусматривая макросы типа:

#ifdef _UNICODE

typedef wchar_t _TCHAR

# define _T(x) L##x

#else

typedef char _TCHAR

# define _T(x) x

#endif

Если константа _UNICODE не определена, то оператор:

_TCHAR *p = _T("делай_что_нужно");

имеет значение:

char *p = "делай_что_нужно";

Если константа _UNICODE определена, тот же самый оператор получает значение:

wchar_t *p = L"делай_что_нужно";

Пока все хорошо. Вы теперь можете попробовать перенести вашу старую программу в среду Unicode, просто используя свой редактор для замены всех экземпляров char на _TCHAR и помещения всех строковых констант в скобки _T(). Проблема состоит в том, что такой код, как ниже (в котором все _TCHAR первоначально были типа char), более не работает:

_TCHAR str[4];

// ...

int max_chars = sizeof(str); // предполагает, что тип char имеет размер 1 байт

Тип _TCHAR будет иметь размер 2 байта при определенной константе _UNICODE, поэтому число символов у вас будет определено в два раза большим, чем есть на самом деле. Для исправления ситуации вы должны воспользоваться следующим вариантом:

int max_chars = sizeof(str) / sizeof(*str) ;

70. Опасайтесь приведения типов (спорные вопросы С).

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

Первое место, где неверное понимание приведения типов может навлечь на вас неприятности, находится в С, где не требуются прототипы функций. Когда компилятор находит вызов функции без предшествующего прототипа, то полагает, что эта функция возвращает тип int. В следующем фрагменте не говориться "malloc() на самом деле возвращает указатель, а не тип int":

int *p = (int *) malloc( sizeof(int) );

а скорее код говорит "я полагаю, что malloc() возвращает тип int, так как тут нет предшествующего прототипа, и преобразую этот int в указатель для присваивания его значения p). Если тип int имеет размер 16 бит, а указатель 32-битовый, то вы теперь в глубокой луже. Вызов malloc() может вернуть и 32-битовый указатель, но так как компилятор полагает, что malloc() возвращает 16-битовый int, то он игнорирует остальные 16 бит. Затем компилятор округляет возвращенное значение до 16-бит и преобразует его в 32-битовый тип int принятым у него способом, обычно заполняя старшие 16 бит нулями. Если указатель содержал адрес больше, чем 0xffff, что вероятно для большинства компьютеров, то вы просто теряете старшие биты. Единственным способом урегулирования этой проблемы является указание для malloc() соответствующего прототипа, который подскажет, что malloc() возвращает указатель (обычно путем включения файла <stdlib.h>).

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

f( int x );

// ...

unsigned y;

f( y );

и многие программисты заглушат такой компилятор при помощи f((int)y). Несмотря на это, приведение типа не изменит того факта, что тип unsigned int может содержать такое значение, которое не поместится в int со знаком, поэтому результирующий вызов может не сработать.

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

some_object array[ size ];

int my_cmp( some_object *p1, some_object *p2 );

qsort( array, size, sizeof(some_object), ((*)(void*, void*)) my_cmp );

Следующий похожий код просто печально отказывается работать без предупреждающего сообщения:

some_object array[ size ];

void foo( int x );

qsort( array, size, sizeof(some_object), ((*)(void*, void*)) foo );

Функция qsort() передает аргументы-указатели в foo(), но foo() ждет в качестве аргумента int, поэтому будет использовать значение указателя в качестве int. Дальше еще хуже - foo() вернет мусор, который будет использован qsort(), так как она ожидает в качестве возвращаемого значения int.

Выравнивание также связано с затруднениями. Многие компьютеры требуют, чтобы объекты определенных типов располагались по особым адресам. Например, несмотря на то, что 1-байтоый тип char может располагаться в памяти по любому адресу, 2-байтовый short должен будет иметь четный адрес, а 4-байтовый long - четный и кратный четырем. Следующий код вновь не выдаст предупреждений, но может вызвать зависание компьютера во время выполнения:

short x;

long *lp = (long*)( &x );

*lp = 0;

Эта ошибка особенно опасна, потому что *lp = 0 не сработает лишь тогда, когда x окажется по нечетному или не кратному четырем адресу. Может оказаться, что этот код будет работать до тех пор, пока вы не добавите объявление второй переменной типа short сразу перед x, после чего эта программа зависнет.

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

p = (char *)(long *);

71. Немедленно обрабатывайте особые случаи.

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

print( const char *str )

{

if ( !*str ) // ничего не делать, строка пуста

return;

while ( *str )

putchar( *str++ );

}

Оператор if тут не нужен, потому что этот случай хорошо обрабатывается циклом while.

Листинги 2 и 3 демонстрируют более реалистический сценарий. Листинг 2 определяет умышленно наивный заголовок связанного списка и функцию для удаления из него элемента.

Листинг 2. Связанный список: Вариант 1.

  1. typedef struct node

  2. {

  3. struct node *next, *prev;

  4. //...

  5. } node;

  6. node *head;

  7. remove( node **headp, node *remove )

  8. {

  9. // Удалить элемент, на который указывает "remove", из списка, на начало

  10. // которого указывает *headp.

  11. if ( *headp == remove ) // Этот элемент в начале списка.

  12. {

  13. if ( remove->next ) // Если это не единственный элемент в списке,

  14. remove->next->prev = NULL; // то поместите следующий за ним элемент

  15. // первым в списке.

  16. *headp = remove->next;

  17. }

  18. else // Элемент находится в середине списка

  19. {

  20. remove->prev->next = remove->next;

  21. if ( remove->next )

  22. remove->next->prev = remove->prev;

  23. }

  24. }

Листинг 3 делает то же самое, но я модифицировал указатель на предыдущий элемент в структуре node, поместив туда адрес поля next предыдущего элемента вместо указателя на всю структуру. Это простое изменение означает, что первый элемент больше не является особым случаем, поэтому функция remove становится заметно проще.

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

Листинг 3. Связанный список: Вариант 2.

  1. typedef struct node

  2. {

  3. struct node *next, **prev; // <=== К prev добавлен символ *

  4. // ...

  5. } node;

  6. node *head;

  7. remove( node **headp, node *remove )

  8. {

  9. if ( *(remove->prev) = remove->next ) // если не в конце списка,

  10. remove->next->prev = remove->prev; // то уточнить следующий элемент

  11. }

72. Не старайтесь порадовать lint.

Lint является программой проверки синтаксиса для языка С. (Также имеется версия для С++ в среде MS-DOS/Windows. Она выпускается фирмой Gimple Software). Хотя эти программы неоценимы при использовании время от времени, они выводят такую кучу сообщений об ошибках и предупреждений, что текст вашей программы будет почти невозможно прочитать, если вы попробуете избавиться от всех них. Оказывается, нужно избегать кода, подобного следующему:

Характеристики

Тип файла
Документ
Размер
87,5 Kb
Тип материала
Высшее учебное заведение

Список файлов книги

С и С++ - сборник литературы
C++ Бархатный путь - Марченко А
cpp_001.shtml
cpp_002.shtml
cpp_003.shtml
cpp_004.shtml
cpp_005.shtml
cpp_006.shtml
cpp_007.shtml
cpp_008.shtml
cpp_009.shtml
cpp_010.shtml
cpp_011.shtml
cpp_012.shtml
cpp_013.shtml
cpp_014.shtml
cpp_015.shtml
cpp_016.shtml
cpp_017.shtml
cpp_018.shtml
cpp_019.shtml
cpp_020.shtml
cpp_021.shtml
cpp_022.shtml
cpp_023.shtml
cpp_024.shtml
cpp_025.shtml
cpp_026.shtml
cpp_027.shtml
cpp_030.shtml
cpp_034.shtml
Свежие статьи
Популярно сейчас
Как Вы думаете, сколько людей до Вас делали точно такое же задание? 99% студентов выполняют точно такие же задания, как и их предшественники год назад. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
7027
Авторов
на СтудИзбе
260
Средний доход
с одного платного файла
Обучение Подробнее