Давыдов В.Г. - Программирование и основы алгоритмизации - 2003
.pdf12.3. Модификаторы const и volatile
Модификатор const, как и в языке Си, запрещает изменение значений данных. Разумеется, константа должна быть инициализи рована при описании, ведь в дальнейшем ей ничего нельзя присваи вать. Кроме того, в языке C+-I- данные, определенные как const, ста новятся недоступными в других файлах программного проекта, по добно статическим переменным:
Файл |
|
Р37,СРР |
|
делает |
|
данное |
недоступным |
в других |
фай |
||||
Модификатор |
const |
|
|||||||||||
лах |
программного |
|
проекта. |
|
|
|
|
|
|
|
|||
Состав |
проекта: |
|
Р37.СРР |
|
|
|
|
|
|
|
|||
*/ |
|
|
|
|
|
CONST,СРР |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
^include |
|
<stdio,h> |
|
|
|
|
|
|
|
|
|||
ехЬезпл |
floatt |
PI; |
|
|
|
|
|
|
|
|
|
||
±nt |
main ( |
void |
) |
|
// |
Возвращает |
0 при |
успехе |
|
|
|||
{ |
printfi |
"\n |
PI=%f'\ PI ) |
; |
|
|
|
|
|
||||
|
|
|
|
|
|
||||||||
|
return |
0; |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
Файл |
CONST. CPP. |
Используется |
в программном |
проекте |
P3 7.PRJ |
||||||||
const |
float |
|
PI |
= |
3.14159; |
|
|
|
|
|
|
|
|
|
Раздельная |
компиляция |
файлов |
из |
приведенного |
примера |
пройдет успешно, но компоновщик сообщит, что в файле Р37.СРР имеется не разрешенная внешняя ссылка.
В большинстве случаев компилятор языка C++ трактует описанное как const данное, не локальное ни в одном блоке (областью действия его является файл), точно так же, как и макроопределение, созданное директивой препроцессора Udefine, т.е. просто подставляет в соответствующих местах величину, которой данное инициализировано. Однако const обладает тем преимуществом перед Udefine, что обеспечивает контроль типов, поэтому его использование может уберечь от многих ошибок.
Модификатор volatile, напротив, сообщает компилятору, что значение данного может быть изменено каким-либо фоновым про цессом - например, при обработке прерывания. С точки зрения ком-
200
пилятора это означает, что, вычисляя значение выражения, в кото рое вход;ит такое данное, он должен брать его значение только из памяти (а не использовать копию, находящуюся в регистре, что до пустимо в других случаях).
12.4.Ссылки
вбольшинстве языков программирования параметры переда ются в подпрограмму (функцию) либо по ссылке, либо по значению.
Впервом случае подпрограмма (функция) работает непосредственно
саргументом, переданным ей, а во втором случае - с копией аргу мента. Различие здесь очевидно: аргумент, переданный по ссылке, подпрограмма (функция) может модифицировать, а переданный по значению - нет.
Как уже отмечалось выше, в языке Си аргументы передаются в функцию только по значению и общепринятый способ обеспечить функции непосредственный доступ к какому-либо данному из вы звавшей программы состоит в том, что вместо самого данного в ка честве аргумента передается его адрес. При работе на языке C++ нет необходимости прибегать к таким ухищрениям - в языке C++ реали зованы оба способа передачи параметров. Многочисленные приме ры, иллюстрирующие сказанное рассмотрены выше.
Ссылки в языке C++ можно использовать не только для пере дачи параметров в функции, но и для создания псевдонимов данных:
int |
X ^ |
1; |
int |
&xr |
= X |
xr = 2;
ХГ+ + ;
Однако,
/ / |
Ссылка хг |
становится псевдонимом |
|
// |
к |
равно, |
что к = 2; |
// |
Все |
||
// |
Все |
равно, |
что х++; |
int |
Так |
X |
= |
1; |
не |
совпадают, |
то компилятор создает |
||
// |
как типы х |
и хг |
|||||||
// |
переменную |
типа char, |
для которой |
хг |
будет псевдонимом, |
||||
// |
и |
присваивает |
ей |
(char)х |
|
|
|
||
char |
&ХГ = |
х; |
// Значение |
х |
не |
изменяется |
|||
хг |
= 2; |
|
|
|
|
201
12.5.Подставляемые функции
вязыке Си для уменьшения расходов на вызовы небольших, часто вызываемых функций, принято использовать макроопределе ния с параметрами. Однако их применение сильно запутывает про грамму и служит неиссякаемым источником трудноуловимых оши бок.
Что же предлагает взамен язык C++? Достаточно описать функцию как inline и компилятор, если это возможно, будет под ставлять в соответствующих местах тело функции, вместо того, что бы осуществлять ее вызов. Конечно же, определение подставляемой функции должно находиться перед ее первым вызовом:
inline |
int |
InlineFunctionCube( |
int |
x ) |
||
{ |
return |
x*x*x/ |
|
|
|
|
|
|
|
|
|
||
} |
|
|
|
|
|
|
int |
|
b |
= |
InlineFunctionCube( |
a ) ; |
|
int |
|
с |
= |
InlineFunctionCube( |
a++ ) / |
Вот теперь можно повысить эффективность программы, поль зуясь при этом всеми преимуществами контроля типов и не опасаясь побочных эффектов. Невозможна подстановка функций, содержа щих операторы case, for, while, do-while, goto. Если для данной функции, определенной как inline, компилятор не может осущест вить подстановку, то он трактует такую функцию как статическую, выдавая, как правило, соответствующее предупреждение.
12.6. Операции динамического распределения памяти
Так как занятие и освобождение блоков памяти является очень распространенной операцией, в языке C++ введены два "интеллек туальных" оператора new и delete, освобождающих программиста от необходимости явно использовать библиотечные функции malloc, calloc и free. Примеры использования этих операторов приведены выше. Остается добавить, что в программе, использующей new и delete, не запрещается применять также функции библиотеки языка Си malloc, calloc, free и им подобные.
202
12.7. Перегрузка функций
Предположим, что по ходу программы часто необходимо печа тать значения типа ш/, double и char *. Почему бы не создать для этой цели специальные функции?
/ / в |
языке |
Си для |
вывода |
значений |
разного |
типа каждой из |
|||||||
// |
функций |
придется |
1 |
дать |
особое |
имя |
|
||||||
void. |
print_int |
|
( ±xit |
) |
|
|
|
|
|
|
|||
|
printf( |
|
"%d", i ; / z-etuxn/ |
|
|
||||||||
voxd |
print_double |
( dovible |
|
x |
) |
|
|
||||||
printf( |
|
"%lg"r |
X |
|
) ; ) ; x-etum/ |
|
|
||||||
•sroxdL |
print_str±ng |
( |
chsir |
|
"^s |
) |
|
|
|||||
printf( |
|
"%s", |
s |
) |
; |
) |
; |
x-etixm/ |
|
|
|||
±nt |
|
|
|
j |
=5/ |
|
|
|
|
|
|
|
|
print_lnt |
( |
J |
) ; |
|
|
) |
; |
|
|
|
|
|
|
print_double( |
|
3,14159 |
|
|
|
|
|
|
|||||
print_string( |
|
"Hello" |
|
) |
; |
|
|
|
|
|
В стандартном языке Си потребовалось дать этим трем функ циям различные имена, а вот в языке C++ можно написать "умную" функцию print, существующую как бы в трех ипостасях:
i^include |
<stdio. |
h> |
|
|
|
|||
void, |
( |
Int |
i |
) |
|
|
|
|
|
pr±ntf( |
|
"%d", |
i |
) |
; |
return; |
|
void |
( |
double |
x |
) |
|
|||
|
printf( |
|
"%lg", |
X |
) |
; |
return; |
|
void |
( |
сЪа.г |
*s |
) |
|
|
||
|
printf( |
|
"%s", |
s |
) |
; |
retuim; |
|
int |
mam ( |
void |
) |
|
|
|
|
203
±nt |
j = 5; |
|
print( |
J ); |
|
print( |
3.14159 |
) ; |
print( |
"Hello" |
) ; |
iretuxTi |
0; |
|
} |
|
|
Компилятор |
сам выбирает, какую из трех перегруженных |
функций с именем print вызвать в каждом случае. Критерием выбора служат количество и типы аргументов, с которыми функция вызыва ется, причем, если не удается найти точного совпадения, компиля тор выбирает ту функцию, при вызове которой "наиболее легко" вы полнить для аргументов преобразование типа.
Обратите внимание на два существенных обстоятельства.
•Перегруженные функции не могут различаться только по типу возвращаемого значения:
void. f( |
±nt^ int |
); |
izit f( |
int^ int ); |
// Ошибка! |
•Перегрузка функций не должна приводить к конфликту с аргу ментами, заданными по умолчанию:
void |
f ( |
int |
= о ) ; |
void |
f( |
void |
) ; |
f ( ) ; |
|
|
// Какую функцию вызвать? |
|
Компилятор языка C++ позволяет давать различным функциям |
одинаковые имена. Поэтому, помещая имена функций в объектный файл - результат компиляции, он должен их каким-то образом мо дифицировать, чтобы сделать уникальными. Модифицированные компилятором имена содержат информацию о количестве и типе па раметров, так как именно по этому признаку перегруженные функ ции различаются между собой. Такая модификация получила назва ние "декорирование имен".
В некоторых ситуациях, например, при необходимости ском поновать программу на языке C++ с объектными файлами или биб лиотеками, созданными "обычным" Си-компилятором, декорирова ние имен нежелательно. Чтобы сообщить компилятору языка C++,
что |
имена тех или иных |
функций не должны декорироваться, их |
следует объявить как extern |
"С": |
|
/ / |
Отдельная функция |
|
204
exteim |
"С" |
±nt |
|
fund |
( ±nt |
) ; |
|
extern |
"C" |
|
|
|
// |
Несколько |
функций |
{ |
|
|
|
|
|
|
|
void. func2 |
( |
±nt |
) ; |
|
|
||
±nt |
funcS |
( |
void. |
) ; |
|
|
|
double |
fun |
с 4( double ) |
; |
|
}
Модификатор extern "C" можно использовать не только при объявлении, но и при описании функций. Естественно, что функции с модификатором extern "С" не могут быть перегруженными.
12.8. Шаблоны функций
При написании программ на языке C++ часто приходится соз давать множество почти одинаковых функций для обработки дан ных разных типов. Используя служебное слово template (шаблон), можно задать компилятору образец, по которому он сам сгенерирует код, необходимый для конкретных типов:
1 ^^
Файл Р38.СРР, Шаблоны функций
""/
^include |
|
<stdio.h> |
|
|
|
|
|
|
|
|
|
|
|
|
||||
iinclude |
|
<string.h> |
|
|
|
|
|
|
|
|
|
|
|
|||||
// |
Замена |
|
местами |
переменных |
|
"а |
<~> Ь". |
|
Компилятор |
создаст |
||||||||
// |
подходящую |
функцию, |
когда |
"узнает", |
|
какой |
тип |
аргументов |
||||||||||
// |
Т подходит |
в |
конкретном |
случае |
|
|
|
|
|
|
||||||||
template |
< class |
|
Т > |
|
|
|
|
|
|
|
|
|
|
|
||||
void |
swap( |
Т |
&а, |
Т |
&Ь ) |
|
|
|
|
|
|
|
|
|
|
|||
( |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Т |
|
|
|
с; |
|
|
|
// |
Для |
обмена |
|
|
|
|
|
||
|
с = Ь; |
b |
= а; |
а |
= |
с/ |
|
|
|
|
|
|
|
|
|
|
||
|
return/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int |
mam |
( |
void |
) |
|
|
|
// |
Возвращает |
О при |
|
успехе |
|
|||||
{ |
Int |
|
|
|
i |
= |
О, |
J |
= |
1; |
|
|
|
|
|
|
|
|
|
|
|
|
1.0/ |
|
|
|
|
|
|
|
|||||||
|
double |
|
|
X |
= |
0.0, |
|
у = |
|
|
|
|
|
|
|
|||
|
char |
|
|
|
*sl |
= "Строка!", |
|
*s2 |
= |
"Строка2" |
/ |
|
||||||
|
f |
( |
"\n |
Перед |
обменом: |
\n i = %d j = %d |
\n |
x=%lg |
" |
|||||||||
|
swap |
( |
i , |
"y==%lg \n |
sl=%s |
s2=%s", |
1, |
J, |
X, |
y, |
si, |
s2 ) / |
||||||
|
J |
) / |
swap |
( |
x, |
у |
) / |
swap |
( si, |
s2 |
) |
/ |
" |
|||||
|
printf |
|
( |
"\n |
После |
обмена: |
\n |
i = %d j = %d \n |
x=%lg |
205
"у=%1д \п sl = %s s2^%s", i , j , x, y , si, s2 ) ;
rebvim 0;
}
Аргументы, помещаемые в угловые скобки после служебного слова template, называют параметрами настройки шаблона. Пара метры настройки шаблонов функций обязательно должны быть именами типов.
12.9. Перегрузка операций
Если в языке C++ можно самому определять новые типы дан ных, например, структуры, то почему бы не заставить привычные операторы выполнять те же действия над определенными нами ти пами, которые мы хотим? И такая возможность есть.
Пусть @ есть некоторый оператор языка C++, кроме следую щих операторов:
,. * :: ?: sizeof
Тогда достаточно определить функцию с именем operator@ с требуемым числом и типами аргументов так, чтобы эта функция вы полняла необходимые действия:
|
Файл |
Р39.СРР. |
Перегрузка |
|
операторов |
|
|
|
|
||
^include |
<stdlo.h> |
|
|
|
|
|
|
|
|
||
^include |
<string.h> |
|
|
|
|
|
|
|
|
||
// |
Максимальная |
длина |
строки |
|
+1 |
|
|
|
|
|
|
const ±nt |
MAX_STR_LEN = |
80; |
|
|
|
|
|
||||
stiract |
STRING |
|
// |
Структурный |
тип |
для |
строки |
||||
{ |
chsr |
|
s[ MAX_STR_LEN |
]; |
|
|
|
|
|
||
|
|
|
|
|
|
|
|||||
|
±nt |
|
|
// |
Строка |
|
|
|
|
||
|
|
str_len; |
// |
Текущая длина |
строки |
|
|
||||
} |
; |
|
|
|
|
|
|
|
|
|
|
// |
Переопределение |
("перегрузка") |
оператора |
сложения |
для |
||||||
// |
строк - выполняет |
сцепление |
(конкатенацию) |
строк |
|||||||
STRING |
орега,Ьог+ |
( |
// |
Возвращает |
конкатенацию |
строк |
|||||
|
STRING |
&sl, |
// |
Первый |
операнд |
|
|
|
|||
|
STRING |
&s2) |
// |
Второй |
операнд |
|
|
|
|||
{ |
STRING |
TmpStr; |
// |
Для временного |
хранения |
|
|||||
|
|
206
// Длина |
строки |
результата |
|
равна |
сумме |
длин |
не |
складываемых |
|||||||||||||
// |
строк. |
Позаботимся |
также |
о |
том, |
чтобы |
выйти за |
||||||||||||||
// |
границу |
|
|
массива-суммы |
|
|
|
+ s2. |
str__len |
) |
>= |
||||||||||
±f( |
( |
TmpStr. |
str_len |
|
= |
si |
. str_len |
|
|||||||||||||
{ |
MAX_STR_LEN |
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
TmpStr, |
s[ |
0 |
] = |
'\xO'/ |
|
TmpStr. |
Str__len |
= |
0; |
|
|
||||||||||
|
|
|
|
||||||||||||||||||
|
re |
turn |
|
TmpStr; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Выполним |
конкатенацию |
|
(сложение) |
|
строк |
|
|
|
|
|
|||||||||||
strcpy( |
|
TmpStr.sг |
sl.s |
|
) ; |
strcat |
|
( |
TmpStr.s, |
|
|
s2.s |
) ; |
||||||||
rebvLrn |
|
TmpStr; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main |
( |
void |
) |
|
|
|
// |
Возвращает |
0 |
при |
|
успехе |
|
||||||||
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STRING |
|
|
strl, |
str2, |
|
str3; |
|
|
|
|
|
|
|
|
|
|
|||||
strcpy |
|
( |
strl.Sr |
= |
"Перегрузка |
|
операторов |
|
- |
" |
) |
; |
|
|
|||||||
strl.str_len |
str2.Sr |
strlen |
|
( |
strl.s |
|
) |
; |
|
) |
; |
|
|
|
|
|
|||||
strcpy( |
|
= |
"это |
очень |
|
здорово!" |
|
|
|
|
|
|
|||||||||
str2. |
str__len |
|
strlen |
|
( |
str2.s |
|
) |
; |
|
|
coдepжимoe=%s", |
|||||||||
printf( |
|
|
"\n |
Первая |
строка: |
|
длинa=%d^ |
|
|||||||||||||
printf( |
|
|
strl |
. str__len |
^ strl.s |
|
) |
; |
|
|
|
coдepжимoe=%s", |
|||||||||
|
|
"\n |
Вторая |
строка: |
|
длинa=%d, |
|
||||||||||||||
str3 |
|
|
str2. |
|
str_len,^ |
|
str2.s |
) |
; |
|
|
|
|
|
|
|
|
|
|||
= strl |
+ |
str2; |
|
|
строк: |
|
длина |
= %d,. содержимое^%s ", |
|||||||||||||
f( |
|
"\n |
Конкатенация |
; |
|||||||||||||||||
|
|
|
str3. |
|
str__len, |
|
strJ.s |
) |
|
|
|
|
|
|
|
|
|
||||
rebvLrii. |
|
0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13. ТЕХНОЛОГИЯ СОЗДАНИЯ ПРОГРАММ [5]
К настоящему моменту рассмотрен весь спектр средств языка С+-ь, кроме технологии объектно-ориентированного программиро вания (ООП) и стандартной библиотеки языка C++. Рассмотрим те перь, какими же принципами нужно руководствоваться, чтобы соз дать красивую, понятную и надежную программу.
13.1. Кодирование и документирование программы
С течением времени в процессе работы каждый программист вырабатывает собственные правила и стиль программирования. При этом полезно учиться не только на собственном опыте, но и разумно следовать приведенным ниже рекомендациям, основанным на дос тижениях ведущих программистов, которые, де-факто, стали не гласным стандартом программирования. Это поможет избежать многих распространенных ошибок и неоправданно больших затрат времени на проектирование программных продуктов. Вместе с тем отметим, что, конечно же, что на все случаи жизни советы дать не возможно - ведь не зря программирование, особенно на заре его развития, считалось искусством.
Главная цель, к которой нуэюно стремиться, - получить легко читаемую программу возможно более простой структуры [5]. В конечном итоге, все технологии программирования направлены на достижение именно этой цели, поскольку только таким путем можно добиться надежности и простоты модификации программы. В соот ветствии со сказанным, предпочтение при программировании следу ет отдавать не наиболее компактному и даже не наиболее эффектив ному способу программирования, а такому способу, который легче для понимания. Особенно важно это в случае, когда программу пи шут одни программисты, а сопровождают другие, что является ши роко распространенной практикой [5].
Первый шаг в написании программь/ - запись ее в так назы ваемой текстуальной форме, возможно, с применением блок-схем. Текстуальная форма должна показать, что именно и как программа должна делать. Если же не можете записать алгоритм решения зада чи в текстуальной форме, то велика вероятность того, что алгоритм плохо продуман. Текстуальная запись алгоритма полезна по не скольким причинам — она позволяет детально продумать алгоритм, обнаружить на самой ранней стадии некоторые ошибки, разбить программу на логическую последовательность функционально за-
208
конченных фрагментов, а также обеспечить комментарии к про грамме.
Каждый функционально законченный фрагмент алгоритма в соответствии с технологией модульного программирования следует оформить в виде функции. Каждая функция должна решать только одну задачу (не надо объединять два коротких независимых фраг мента в одну функцию). Предельные параметры функции (количест во строк исходного текста и число параметров) определяются рас смотренным ранее правилом "семь плюс-минус два".
Если некоторые действия встречаются в программе хотя бы дважды, их также нужно оформить в виде функции. Однотипные действия оформляются в виде перегруженных функций или функций с параметрами. Короткие, простые функции следует оформлять как подставляемые функции.
Необходимо тщательно выбирать имена объектов (пере менных, функций и т.п.). Рационально выбранные имена могут сде лать программу в некоторой степени самодокументированной. Не удачные имена, наоборот, служат источником проблем. Не увлекай тесь сокращениями - они ухудшают читаемость текста. Общая тен денция состоит в том, что чем больше область видимости объекта, тем более длинным именем его надо снабжать. Перед таким именем часто ставится префикс типа (одна или несколько букв, по которым можно определить тип объекта). Для управляющих переменных ко ротких циклов, напротив, лучше использовать однобуквенными именами типа /, у, или к. Имена макросов предпочтительнее записы вать прописными буквами, чтобы отличать их от других объектов программ. Не рекомендуется использовать имена, начинающиеся с одного или двух символов подчеркивания, имена типов, оканчи вающиеся на "_/" и т.п.
Переменные желательно инициализировать при их опреде лении^ а определять как можно ближе к месту их непосредственного использования. Но нет правил без исключений. Поэтому, с другой стороны, все определения локальных переменных блока лучше рас полагать в начале блока, чтобы их легко можно было найти.
Локальные переменные предпочтительнее глобальных. Если глабальная переменная все же необходима, то лучше определить ее статической. Это ограничит область действия такой переменной од ним исходным файлом.
Всю необходимую |
функции |
информацию |
нужно стремиться |
||||
передавать через список |
параметров, |
а не через глобальные пере |
|||||
менные, изменение которых трудно отследить. |
|
|
|||||
Входные |
параметры |
функции, |
которые |
не |
дол:и€ны в ней |
||
изменяться, |
следует |
передавать |
по |
ссылке |
с |
модификатором |
|
const, а не по значению. |
Кроме улучшения читаемости программы и |
209