Правила программирования на Си и Си++ Ален И. Голуб




Название Правила программирования на Си и Си++ Ален И. Голуб
страница 6/19
Дата публикации 19.06.2014
Размер 3 Mb.
Тип Реферат
literature-edu.ru > Авто-ремонт > Реферат
1   2   3   4   5   6   7   8   9   ...   19

static int glob;

static void recursive_function( void )

{

int y = glob;

// ...

recursive_function();

}

int do_recursive( int init_val )

{

glob = init_val;

recursive_function();

return glob;

}

Ни к глобальной переменной, ни к рекурсивной функции нельзя получить доступ прямо снаружи модуля из-за статического выделения памяти. Вы должны получить доступ к рекурсивной функции посредством функции доступа do_recursive(), которая гарантирует, что все инициализировано правильно перед тем, как выполнить вызов рекурсивной функции.

56.2.1. Делайте локальные переменные статическими в рекурсивных функциях, если их значения не участвуют в рекурсивном вызове

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

f()

{

static int i;

// ...

for( i = 10; --i >= 0; )

// ...

f();

for( i = 10; --i >= 0; ) // переменная i вновь инициализиру–

// ется после рекурсивного вызова,

// поэтому она может быть статичес–

} // кой.

Вот другой:

int f()

{

static int depth = 0;

static int depth_max = 0;

++depth; depth_max = max( depth, depth_max );

if( depth > 10 )

return -1; // уровень рекурсии слишком глубок.

f();

--depth;

return depth_max;

}

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

56.3. Используйте счетчик экземпляров объектов вместо инициализирующих функций

Инициализирующие функции, с очевидным исключением в виде конструкторов Си++, не должны использоваться просто потому, что слишком просто забыть их вызвать. Многие системы с оконным интерфейсом, например, требуют, чтобы вы вызывали функцию инициализации окна перед его созданием (и другую — закрытия — после удаления последнего окна). Это плохая идея. Уладьте эту проблему при помощи счетчика экземпляров, который обычно в Си должен быть глобальной переменной (объявленной статической для ограничения области ее видимости). Сделайте это так:

static int num_windows = 0; // ограничьте доступ к текущему

// модулю

create_window()

{

if( ++num_windows == 1 ) // только что создано первое окно

initialize_video_system();

// ...

}

destroy_window()

{

// ...

if( --num_windows == 0 ) // только что уничтожено

shutdown_video_system(); // последнее окно

}

В Си++ вы можете для этой цели использовать статический член класса.

56.4. Если оператор if завершается оператором return, то не используйте else

Вместо:

if( условие )

return xxx;

else

{

делать_массу_вещей();

}

обычно лучше записать:

if ( условие )

return xxx;

делать_массу_вещей();

Лучше сделать так, чтобы последним оператором return был аварийный возврат по ошибке, так чтобы вы получили сообщение об ошибке, если нечаянно заблудились.

Условный оператор также может решать эту проблему в простых ситуациях и делать код более читаемым для новичка. Вместо:

f()

{

// ...

if( x )

return 123;

else if ( y )

return 456;

else

return ERROR;

}

используйте

f()

{

// ...

return x ? 123 :

y ? 456 :

ERROR ;

}

Заметьте, насколько форматирование улучшает читаемость предыдущего кода.

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

if( A )

return FAIL;

else if( B )

return SUCCESS;

else

{

// Масса кода

return SUCCESS; // Подозрительны два одинаковых

// возвращаемых значения.

}

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

if( A )

return FAIL;

else if( B )

;

else

{

// Масса кода

}

return SUCCESS;

Затем освободитесь от предложения if, связанного с пустым оператором:

if( A )

return FAIL;

else if( B )

{

// Масса кода

}

return SUCCESS;

57. Помещайте более короткий блок условного оператора if/else первым

Часто бывает, что у оператора if/else одно предложение (или внутренний блок) короткое (обычно оператор для обработки ошибки), а другое, выполняющее собственно работу, — большое:

if( некая_ошибка() )

error( "ААААхххх!!!!" );

else

{

// Здесь следуют 30 строк кода

}

Всегда помещайте короткое предложение в начале. То есть, не делайте так:

if( !некая_ошибка() )

{

// Здесь следуют 30 строк кода

}

else

error( "ААААхххх!!!!" );

Проблема заключается в том, что проверка в операторе if управляет else в той же степени, что и if. Если большой блок следует первым, то вероятность того, что вычисляться будет предложение else, расположенное на следующем экране или странице, довольно велика. И если я допустил в нем ошибку, то мне придется поломать голову над тем, как добраться до else. Если в это время if в поле зрения, то я знаю, как туда попасть.

58. Старайтесь сдвинуть ошибки с этапа выполнения на этап компиляции

Неинициализированные переменные — по сути ошибки, ждущие своего часа. Вы всегда должны инициализировать переменную при ее объявлении. В Си++ инициализация во время объявления возможна всегда, потому что объявление может проводиться везде, где можно поместить оператор; просто откладывайте объявление до тех пор, пока у вас не будет достаточно информации для объявления в произвольном месте с инициализацией переменной. Таким образом, если вы попытаетесь использовать эту переменную преждевременно, то получите ошибку на этапе компиляции ("переменная не найдена") вместо ошибки во время выполнения.

В Си вы можете объявлять переменную после любой открытой фигурной скобки, поэтому вы можете часто откладывать объявление на некоторое время, но при этом у вас в распоряжении нет гибкости Си++. В самом крайнем случае инициализируйте переменную таким значением, которое в ответ на него понятным образом вызовет в подпрограмме аварию; не присваивайте переменной произвольное значение — оно может быть принято в программе за имеющее смысл. Например, указатель, инициализированный значением NULL, более надежен, чем имеющий произвольное значение, которое может оказаться существующим адресом.

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

f()

{

// код, который не использует переменную i

int i = init_val;

// код, который использует переменную i

}

может быть разделена следующим образом:

f()

{

// код, который не использует переменную i

g( init_val );

}

g( int init_val )

{

int i = init_val;

// код, который использует переменную i

}

Переменная-счетчик общего назначения, которая инициализируется в начале цикла for, является очевидным исключением из этого правила§. Иногда использование слишком большого количества подпрограмм может вызвать больше проблем, чем решить их, и в этом случае лучше выбрать внутренние объявления. Используйте свою голову.

59. Применяйте указатели на функции Си в качестве селекторов

Это правило строго для программистов на Си. (Программирующие на Си++ должны использовать виртуальные функции). В Си заменяйте подобный код:

typedef enum shape_type { CIRCLE, LINE, TEXT };

typedef struct

{

shape_type type;

union shape_data

{ // здесь данные для различных форм.

} data;

} shape;

extern void print_circle( shape *p );

extern void print_line ( shape *p );

extern void print_text ( shape *p );

shape a_circle = { CIRCLE, ... };

print_shape( shape *p )

{

switch( p->type )

{

case CIRCLE: print_circle( p );

case LINE: print_line ( p );

case TEXT: print_text ( p );

}

}

на следующий:

typedef struct

{

void (*print)( struct *shape );

union shape_data;

{ // здесь данные для различных фигур.

}

}

shape;

extern void print_circle( shape *p );

extern void print_line ( shape *p );

extern void print_text ( shape *p );

shape a_circle = { print_circle, ... };

print_shape( shape *p )

{

( p->type )( p );

}

Главные преимущества такого подхода заключаются в следующем:

  • Вам больше не нужен перечислитель shape_type.

  • Функцию print_shape() теперь написать гораздо проще.

  • print_shape() будет продолжать работать без модификации, когда вы добавите новые фигуры в эту систему.

    60. Избегайте циклов do/while

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

    if( !проверить_нечто )

    return ERROR;

    do

    {

    начинка();

    } while( проверить_нечто );

    Вам гораздо лучше сделать так:

    while( проверить_нечто )

    начинка();

    Похожий случай:

    if( некое_условие() )

    do

    // масса материала

    while( некое_условие() && другой_материал() );

    легче трактовать следующим образом:

    while( некое_условие() )

    {

    // масса материала

    if( !другой_материал() )

    break;

    }

    Я профессионально занимаюсь программированием с 1979 года и за это время использовал цикл do/while всего два раза.

    60.1. Никогда не используйте do/while для бесконечного цикла

    Код, похожий на следующий:

    do

    {

    // здесь следует несколько страниц кода

    } while( 1 );

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

    61. В цикле со счетчиком его значение должно по возможности уменьшаться

    Циклы являются одним из тех мест, где малое повышение эффективности значительно улучшает выполнение программы, потому что их код выполняется многократно. Так как сравнение с нулем обычно более эффективно, чем сравнение с определенным числом, то цикл с уменьшающимся счетчиком, как правило, выполняется быстрее. Используйте

    for( i = max; --i >= 0; )

    ;

    вместо:

    for( i = 0; i < max; ++i )

    ;

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

    62. Не делайте одно и то же двумя способами одновременно

    В качестве контрапункта к предыдущему правилу рассмотрим следующий фрагмент (содержащий в себе ошибку):

    int array[ARRAY_SIZE];

    int *p = array;

    for( i = 1; i < ARRAY_SIZE ; ++i )

    *p++ = 0;

    Проблема состоит в том, что счетчик не совпадает по фазе с указателем (i имеет значение 1, когда указатель указывает на элемент array[0]), и последний элемент массива не будет инициализирован.

    Я обычно предпочитаю для простых перемещений по массивам указатели (вместо индексов массива), потому что указатели, как правило, более эффективны, устраняя неявную операцию умножения в выражении a[i], интерпретируемом как:

    ( a + ( i* sizeof(a[0])))

    Я бы переписал это код таким образом:

    int array[ARRAY_SIZE];

    int *current = array;

    int *const end = array + (SIZE-1);

    while( current <= end )

    *current++ = 0;

    Так же надежно (хотя и менее эффективно) сделать следующее:

    int array[ARRAY_SIZE];

    int i;

    for( i = 0; i < ARRAY_SIZE ; ++i )

    array[i] = 0;

    Кстати, если вы используете указатели, то вам придется извлекать индекс при помощи арифметики указателей, а не за счет сохранения второй переменной. У вас могут возникнуть проблемы, если вы передадите i функции в предыдущем примере с ошибкой. Воспользуйтесь подобным кодом:

    for( current = array; current <= end; ++current )

    {

    // ...

    f( current - array ); // передать функции f() текущий

    // индекс массива

    }

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

    while( (current - array) < ARRAY_SIZE )

    // ...

    63. Используйте оператор for, если имеются любые два из инициализурующего, условного или инкрементирующего выражений

    Иначе используйте while. Такой код:
1   2   3   4   5   6   7   8   9   ...   19

Похожие:

Правила программирования на Си и Си++ Ален И. Голуб icon Основы информатики и вычислительной техники системы программирования
Рассматриваются основные понятия языков программирования. Излагаются процедурный и объектный подходы в программировании. Более подробно...
Правила программирования на Си и Си++ Ален И. Голуб icon Рабочая программа по курсу «основы Программирования на языке ассемблер»
Программа предназначена для обучения основам программирования на языке низкого уровня Ассемблере учащихся средних школ, учреждений...
Правила программирования на Си и Си++ Ален И. Голуб icon Конспект лекций доцента и. А. Волковой по курсу «системы программирования»
Система программирования – комплекс программных инструментов и библиотек, который поддерживает создание и существование программного...
Правила программирования на Си и Си++ Ален И. Голуб icon Практикум на ЭВМ технология программирования в среде С++
Трунов К. В., Рыков В. И. Методы и технологии С++. Технология программирования в среде С++. /Издание Башкирского ун-та. Уфа 2007....
Правила программирования на Си и Си++ Ален И. Голуб icon Гигиенические требования к условиям обучения в общеобразовательных...
Настоящие Санитарно-эпидемиологические правила(далее — Санитарные правила) направлены на предотвращение неблагоприятного воздействия...
Правила программирования на Си и Си++ Ален И. Голуб icon Среда программирования Visual C++ 0 Общий вид окна
Совокупность средств и правил для представления алгоритма в виде пригодном для выполнения вычислительной машиной называется языком...
Правила программирования на Си и Си++ Ален И. Голуб icon Обоснование выбора средств и методов разработки
На данный момент существует огромное множество языков программирования с помощью которых можно написать данную дипломную работу....
Правила программирования на Си и Си++ Ален И. Голуб icon Учебно-методический комплекс по дисциплине «Технологии программирования»
Техническое задание по разработке дизайнерского проекта приложения «умк – учебно-методический комплекс по дисциплине «Технологии...
Правила программирования на Си и Си++ Ален И. Голуб icon Создание диалектов языков программирования с использованием грамматических аспектов
Такие языки называют диалектами. Диалекты языков программирования встречаются довольно часто: практически каждая субд использует...
Правила программирования на Си и Си++ Ален И. Голуб icon Календарно тематическое планирование по литературному чтению 2 класс
...
Литература


При копировании материала укажите ссылку © 2015
контакты
literature-edu.ru
Поиск на сайте

Главная страница  Литература  Доклады  Рефераты  Курсовая работа  Лекции