Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Давыдов В.Г. - Программирование и основы алгоритмизации - 2003

.pdf
Скачиваний:
839
Добавлен:
13.08.2013
Размер:
9.55 Mб
Скачать

12.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,

print

(

Int

i

)

 

 

 

 

pr±ntf(

 

"%d",

i

)

;

return;

void

print

(

double

x

)

 

 

printf(

 

"%lg",

X

)

;

return;

void

print

(

сЪа.г

*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"

/

 

 

print

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 ",

print

f(

 

"\n

Конкатенация

;

 

 

 

str3.

 

str__len,

 

strJ.s

)

 

 

 

 

 

 

 

 

 

rebvLrii.

 

0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

13. ТЕХНОЛОГИЯ СОЗДАНИЯ ПРОГРАММ [5]

К настоящему моменту рассмотрен весь спектр средств языка С+-ь, кроме технологии объектно-ориентированного программиро­ вания (ООП) и стандартной библиотеки языка C++. Рассмотрим те­ перь, какими же принципами нужно руководствоваться, чтобы соз­ дать красивую, понятную и надежную программу.

13.1. Кодирование и документирование программы

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

Главная цель, к которой нуэюно стремиться, - получить легко читаемую программу возможно более простой структуры [5]. В конечном итоге, все технологии программирования направлены на достижение именно этой цели, поскольку только таким путем можно добиться надежности и простоты модификации программы. В соот­ ветствии со сказанным, предпочтение при программировании следу­ ет отдавать не наиболее компактному и даже не наиболее эффектив­ ному способу программирования, а такому способу, который легче для понимания. Особенно важно это в случае, когда программу пи­ шут одни программисты, а сопровождают другие, что является ши­ роко распространенной практикой [5].

Первый шаг в написании программь/ - запись ее в так назы­ ваемой текстуальной форме, возможно, с применением блок-схем. Текстуальная форма должна показать, что именно и как программа должна делать. Если же не можете записать алгоритм решения зада­ чи в текстуальной форме, то велика вероятность того, что алгоритм плохо продуман. Текстуальная запись алгоритма полезна по не­ скольким причинам — она позволяет детально продумать алгоритм, обнаружить на самой ранней стадии некоторые ошибки, разбить программу на логическую последовательность функционально за-

208

конченных фрагментов, а также обеспечить комментарии к про­ грамме.

Каждый функционально законченный фрагмент алгоритма в соответствии с технологией модульного программирования следует оформить в виде функции. Каждая функция должна решать только одну задачу (не надо объединять два коротких независимых фраг­ мента в одну функцию). Предельные параметры функции (количест­ во строк исходного текста и число параметров) определяются рас­ смотренным ранее правилом "семь плюс-минус два".

Если некоторые действия встречаются в программе хотя бы дважды, их также нужно оформить в виде функции. Однотипные действия оформляются в виде перегруженных функций или функций с параметрами. Короткие, простые функции следует оформлять как подставляемые функции.

Необходимо тщательно выбирать имена объектов (пере­ менных, функций и т.п.). Рационально выбранные имена могут сде­ лать программу в некоторой степени самодокументированной. Не­ удачные имена, наоборот, служат источником проблем. Не увлекай­ тесь сокращениями - они ухудшают читаемость текста. Общая тен­ денция состоит в том, что чем больше область видимости объекта, тем более длинным именем его надо снабжать. Перед таким именем часто ставится префикс типа (одна или несколько букв, по которым можно определить тип объекта). Для управляющих переменных ко­ ротких циклов, напротив, лучше использовать однобуквенными именами типа /, у, или к. Имена макросов предпочтительнее записы­ вать прописными буквами, чтобы отличать их от других объектов программ. Не рекомендуется использовать имена, начинающиеся с одного или двух символов подчеркивания, имена типов, оканчи­ вающиеся на "_/" и т.п.

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

Локальные переменные предпочтительнее глобальных. Если глабальная переменная все же необходима, то лучше определить ее статической. Это ограничит область действия такой переменной од­ ним исходным файлом.

Всю необходимую

функции

информацию

нужно стремиться

передавать через список

параметров,

а не через глобальные пере­

менные, изменение которых трудно отследить.

 

 

Входные

параметры

функции,

которые

не

дол:и€ны в ней

изменяться,

следует

передавать

по

ссылке

с

модификатором

const, а не по значению.

Кроме улучшения читаемости программы и

209