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

Штерн В. - Основы C++. Методы программной инженерии - 2003

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

щ пби I Часть I ^ Введение в про;

С о в е т у ем при любых операциях со строками нужно убедиться, что вводимые данные не переполняют символьный массивов и не портят

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

Звучит пугающе? Так и было задумано. Программисты, применяющие C+ + ,— фигуры влиятельные. Они пишут программы, затрагивающие интересы многих. Сам язык — великий и могучий. Но он опасен в неопытных руках, подобно револь­ веру или автомобилю. Поэтому нужно сделать все возможное, чтобы правильно использовать его потенциал.

Двумерные символьные массивы

Символьные массивы могут иметь несколько измерений. Для многих задач обработки текста удобно организовать массив в виде строк и столбцов, создать "массив массивов" — двумерный массив типа char. Рассмотрим, например, мас­ сив из дней недели. Требуется определить, содержат ли введенные данные в мас­ сиве day[] слово "Sunday", "Monday", "Tuesday" и т.д. (воскресенье, понедельник, вторник...). Хотя это слова разной длины, мы представим их как двумерный массив символов из семи строк (для 7 дней недели). Строки должны быть одной длины. Максимальное название ("Wednesday") — 9 символов. Учитывая завершающий символ, это будет 10 столбцов.

char days[7][10], day[10];

Если нужно проверить, содержит ли массив day[ ] допустимый день недели, доста­ точно сравнить этот массив с каждой строкой массива days[][]. Можно сделать это в лоб, сопоставляя каждый элемент в i-той строке (i меняется от О до 6). Если символы в массиве day[] те же, что в i-той строке массива days[][], то итерация заканчивается на индексе i, так как введенные данные найдены в масси­ ве. Следовательно, во внешнем цикле нужно знать, что происходит во внутреннем цикле. Обычно для этого используется управляющий флаг (здесь переменная found). Перед началом внутреннего цикла она устанавливается в 1 (true). Если внутренний цикл обнаруживает разные символы, то данная переменная устанав­ ливается в О (false), показывая, что слово не найдено. Если все символы в масси­ ве day[] и в i-той строке массива days[][] совпадают, то присваивание found = О не выполняется — флаг остается равным 1, оператор break завершает внешний цикл:

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

{found = 1; j = 0; do {

i f (days[j]

!=

d a y s [ i ] [ j ] )

/ /

слово

не найдено

 

{ found = 0;

break;

}

/ /

стоп,

сделать для следующего i

}

while

(day[j]

!=

' \ 0 ' ) ;

 

 

 

 

i f (found

== 1)

break;

}

 

/ /

прервать внешний

цикл

Некоторые программисты, знакомые с C+ + , могут написать последние строки более кратко:

do

// сравнение и инкремент

{ if (day[j] !=days[i][j++])

{ found = 0; break; }

// стоп, сделать для следующего i

} while (dayCj] != '\0');

// отдельного j++ нетребуется

if (found) break; }

// любое ненулевое значение - true

Глава 5 • Агрегирование с помощью типов, опреАеляе1У1Ь1х програг^^истом

161

Применение такой формы оператора условия будет вполне безопасным. Однако комбинировать сравнение и инкремент безрассудно. Здесь j используется в двух подвыражениях, а в главе 3 говорилось, что неизвестно, в каком порядке они вычисляются. На моей машине данный код выполняется некорректно. На вашей может быть иначе. Как бы то ни было, предусмотрительный программист должен знать об этом и избегать подобного стиля программирования.

 

 

 

 

Часто обработку такого рода упрощают с помощью библиотечной функции

 

 

 

 

strcmpO. В листинге 5.9 показана программа, запрашивающая у пользователя

 

 

 

 

день недели, выполняющая поиск в двумерном массиве символов и выводящая

 

 

 

 

на экран номер найденного дня. Обратите внимание, что после завершения цикла

 

 

 

 

поиска

программа

должна определить,

 

почему это произошло: найдено слово

1 Введите день

недели

или 'end

для

завершения ввода: Sunday

1

или кончились

элементы

массива,

а совпадение не обнаружено. Есть

1 Выввели: Sunday

 

 

 

 

 

 

 

 

 

несколько способов реализовать это.

1 Sunday имеет номер 1

 

 

завершения ввода: Thursday

 

Здесь

проверяется индекс

i. Если

1 Введите день

недели

или 'end' для

 

он равен числу элементов массива,

1 Выввели: Thursday

 

 

 

 

 

 

 

 

1 Thursday имеет номер 5

 

 

завершения ввода: Saturday

 

значит

поиск

был безуспешным.

1 Введите день

недели

или 'end' для

 

Если он меньше числа элементов,

Вы

ввели: Saturday

 

 

 

 

 

 

 

 

следовательно,

поиск

завершился

Ввод "Saturday"

некорректен

 

 

 

 

 

 

 

 

 

 

 

 

 

 

по оператору

break.

На

рис. 5.9

Введите день

недели

или 'end' для

завершения ввода: end

 

1

показаны результаты

выполнения.

Вы

ввели: end

 

 

 

 

 

 

 

 

 

Программа не

тестировалась на

Спасибо, что воспользовались программой

 

 

 

 

все возможные вводимые значения,

 

 

 

 

 

 

 

 

 

 

 

 

а это непредусмотрительно: в ини­

Рис.

5 . 9 . Неполное тестирование не выявляет,

ошибок

 

циализации массива слово "Friday"

 

набрано с опечаткой.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Листинг 5.9.

Применение двумерного массива символов для поиска

 

 

 

 

#inclucle

<iostream>

 

 

 

 

 

 

 

 

 

 

 

 

 

#inclucle

<cstring>

 

 

 

 

 

 

 

 

 

 

 

 

 

using namespace std;

 

 

 

 

 

 

 

 

 

 

 

 

 

«define NUM 7

 

 

 

 

 

 

/ / вдруг число дней в неделе изменится.

 

int mainO

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

int i; char day[10];

 

 

 

 

 

 

 

 

 

 

 

 

char days[NUM][10] = { "Sunday", "Monday", "Tuesday",

 

 

 

 

 

 

"Wednesday", "Thursday", "friday", "Saturday" };

 

 

 

 

 

 

do {

 

 

 

 

 

 

 

 

// пока пользователь не введет "end"

 

 

cout «

"Введите день недели или 'end' длязавершения ввода: ";

 

 

 

 

cin.get(day, 10);

cin. ingore(2000,

'\n');

//непредусмотрительно

 

 

 

 

cout «

"Выввели: " «

day «

endl;

 

 

 

 

 

 

 

 

 

if (strcmpCday,"end")==0) break;

 

 

 

 

 

 

 

 

 

for (i = 0; i < NUM;

i++)

 

 

// остановить, если найдено

 

 

 

 

 

if (strcmp(day,days[i][==0) break;

 

 

 

 

if (i==NUM)

 

 

day «

 

 

// проверить, почему мы здесь

 

 

 

cout «

"Ввод \"" «

"\" некорректен\п";

 

 

 

 

 

 

 

else

 

 

day «

" имеет номер " «

i+l «

endl;

 

 

 

 

 

 

 

cout «

 

 

 

 

 

 

 

} while (1==1);

 

 

 

 

 

 

endl;

 

 

 

 

 

cout «

"Спасибо, что воспользовались программой" «

 

 

 

 

 

return 0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

I 162 I Часть I ^ ВвеАвние в: тв на C++

Переполнение массива в алгоритме вставки

Приведенный в листинге 5.9 алгоритм использует массив данных (дни недели) только для поиска, поэтому вопрос переполнения массива здесь не возникает. Размер массива совпадает с числом его элементов. Обычно массивы заполняются данными до того, как они используются для последующей обработки. Данные загружаются в начало массива, и каждый следуюнлий элемент добавляется после текущего последнего элемента. В каждый момент времени часть массива, занятая данными, представляет соболи непрерывный набор элементов, а вторая, свободная часть массива — доступные адреса, но она не содержит действительных данных.

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

В листинге 5.10 показан алгоритм для занесения данных транзакций. Это рас­ ширение примера, представленного в главе 4, В прежних примерах для анализа завершения конца ввода использовалось фиктивное отрицательное значение. Здесь это делается более цивилизованным путем — пользователя просят набрать "end" (конец). (Простое нажатие клавиши Enter — не очень хорошая идея, ведь ее можно нажать случайно.) Чтобы можно было обрабатывать вводимые данные как текст и числа, программа помещает их в массив buff[] и проверяет наличие контрольного значения "end". Если его там нет, то она преобразует строку в число с плавающей точкой при помощи библиотечной функции atof(), определенной в файле cstdlib (или stdlib. h). В имени функции 'а' означает ASCII, ' to' — "в", а ' f' — ... можно было бы сказать float, но на самом деле функция возвращает значение double. Существуют также функции atoi() (ASCII в int), atol() (ASCII в long), но функции atod() нет. Эти функции могут преобразовывать до 100 символов, поэтому с 20-символьным буфером они справятся. Если буфер не содержит числовых данных, atof() возвращает О и программа выводит преду­ преждение. Неплохо проверить, содержат ли вводимые данные что-либо, кроме числа, но это потребовало бы применения функций strtodO и strtolO (строка

вdouble и в long), а также использования указателей, к чему мы еще не готовы.

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

 

 

до достижения индексом значения count, а не числа элементов массива NUM.

 

 

Обратите внимание на применение библиотечной функции width(), которая

 

 

задает минимальное число позиций вывода, выделяемых для следующего значения.

 

 

По умолчанию это О, т. е. значение занимает столько позиций, сколько для него

 

 

 

 

 

требуется. Если число символов в выводимом

Введите сумму (или

'end' для завершения):

22

значении меньше, чем "ширина" (количество

Введите сумму (или

'end' для завершения):

33

знакомест), то остальная часть заполняется про­

Введите сумму (или

'end' для завершения):

44

белами (числа выравниваются вправо, строки —

Введите сумму (или

'end' для завершения):

55

влево). Если число символов превышает ширину

Введите сумму (или

'end' для завершения): end

Общая сумма 4 значений равна 154

 

widthO, то ширина игнорируется, и значение зай­

 

мет столько позиций, сколько необходимо.

Ном. опер.

Сумма

 

 

 

Присутствующие в программе отладочные опе­

1

22

 

 

 

раторы выводят на экран вводимые данные и дан­

 

 

 

ные, преобразованные в числовую форму. Ошибки

2

33

 

 

 

нередко происходят при некорректной интерпре­

3

44

 

 

 

4

55

 

 

 

тации в программе данных, но они остаются не­

 

 

 

 

 

замеченными, поэтому полезно проверять, как

Рис. 5.10. Использование

непрерывного

программа воспринимает ввод. Пример ее выпол-

нения показан на рис. 5.10.

 

массива для

сохранения

 

 

 

вводимых данных

 

 

Глава 5 • Агрегирование с помощью типов, определяемых орофоммистом

| 163 р

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

вводимых значений превысит длину массива.

Листинг 5.10. Заполнение непрерывного массива данными транзакций

#inclucle <iostream> #inclucle <cstring> #inclucle <cstcllib> using namespace std;

int main ()

 

 

 

 

{

 

 

 

 

// конечно, размер может изменяться

const int NUM = 100;

 

 

double total, amount, data[NUM]; int count;

 

char buff[20];

 

 

// инициализация текущих данных

total = 0.0; count = 0;

 

 

do {

 

 

 

 

// пока пользователь не введет "end"

cout «

"Введите сумму (или 'end' для завершения)

cin/get(buff.20); cin.ignore(2000,'\n');

// отладка

// cout «

"Вы ввели •" « buff « "'" «

endl;

if (strcmp(buff,"end")==0) break;

 

// преобразует вdouble до 100 знаков

amount =atof(buff);

amount « endl;

// cout «

"Сумма операции: " «

// отладка

if (amount <= 0)

 

как некорректное.\n";

cout

«

"Это значение отбрасывается

else

«

"Повторите ввод.\п";

 

 

 

 

 

 

// обработка текущих данных

{ total += amount;

 

 

data[count] = amount;

 

 

 

count++; }

 

 

 

} while (1 ==1);

 

 

 

cout «

"\п0бщая сумма " « count «

значений равна

«

total « endl;

 

 

 

if (count == 0) return 0;

 

 

 

cout «

"\nHoM. опер. Сумма\п\п";

 

 

for (int i = 0; i < count; i++)

 

 

{

cout.width(4); cout «

i+1;

 

}

 

cout.width(ll); cout «

data[i] « endl

return 0;

 

 

 

 

}

Чтобы избежать переполнения массива и порчи памяти, первый цикл должен проверять, указывает ли индекс count на допустимый элемент массива. Не забы­ вайте, что первым недопустимым значением индекса будет NUM, число элементов массива, поэтому, пока count меньше NUM, значение можно сохранять в массиве. В противном случае ввод нужно прекратить.

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

и не исключает ошибок. Версия программы с заш.итой от переполнения показана

влистинге 5.11. Чтобы избежать работы с большими наборами данных, размер массива уменьшен до 3 — полезный метод отладки.

164

Часть I ^ Введение в oporpaivir^npoBaHMe на С4>^-

Листинг 5.11. Ввод данных в массив с защитой от переполнения

#include

<iostream>

 

 

#include

<cstring>

 

 

#inclucle

<cstdlib>

 

/ / для поддержки atof()

using namespace std;

 

 

int main

()

 

 

// размер может изменяться

// {const int NUM = 100;

 

{

 

 

 

// только для отладки

const int NUM = 3;

 

const chat LAST{} = "end";

 

// литерал для завершения

double amount, total, data[NUM];

 

char buff[20]; int count;

 

// инициализация текущих данных

total = 0.0; count = 0;

 

do {

 

 

 

// пока пользователь не введет "end"

cout << "Введите сумму (или '" << LAST «"' для завершения): ";

cin/get(buff,20); cin.ignore(2000,'\n');

if (strcmp(buff,LAST)==0) break;

// конец ввода данных

amount = atof(buff);

 

// преобразует в double до 100 знаков

if (amount <= 0)

 

как некорректное.\п"

cout

«

"Это значение отбрасывается

 

«

"Повторите ввод.\п";

 

else if (count < NUM)

 

// обработка текущих данных

{ total += amount;

 

data[count] = amount;

 

 

count++; }

 

 

else

 

"He хватает памяти: ввод прекращен\п";

{ cout «

break; }

 

 

 

} while (1==1);

 

 

if (strcmp(buff,"end") != 0)

 

cout «

"Значение " « amount « " несохранено\п";

cout

«

"\п0бщая сумма " « count «

" значений равна "

 

«

total « endl;

 

 

if (count == 0) return 0;

 

 

cout «

"\nHoM. опер. Сумма\п\п";

 

for (int i = 0; i < count; i++)

 

{ cout.widht(4); cout «

i+1;

endl; }

cout.width(ll); cout «

data[i] «

return 0;

 

 

 

}

 

 

 

 

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

случае цикл безуспешно проверил все элементы. В данном примере подобный

подход не будет надежным на 100%,так как count может достигать значения NUM,

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

чет, что неверно. В листинге 5.11 проверяется,ввел ли пользователь "end". Если

нет, то цикл был завершен по причине переполнения массива. Результатвыполне­ ния показан на рис. 5.11.

Глава 5 • Агрегирование с по^^ощыо типов, определяег^ь1х nporpaivii^HCTOivi

[

165

|

1

Введите

сумму

(или

'end' для завершения): 22

Эта версия программы демонстрирует также

 

использование ключевого слова const с массивом

 

1

Введите

сумму

(или

'end' для завершения): 33

LAST[], что позволяет сопровождающему прило­

 

1

Введите

сумму

(или

'end' для завершения): 44

 

 

Введите

сумму

(или

'end' для завершения): 55

жение программисту легко заменить терминатор

 

 

Не хватает памяти: ввод прекращен

на "finish", пустую строку или на что-либо еще,

 

 

Значение 55 несохранено

не выискивая в программе все вхождения "end".

 

 

Общая сумма

3 значений

равна 99

 

 

Это же относится к символьному литералу NUM.

 

 

Ном. опер.

 

Сумма

 

 

Заменить константу в одном месте намного легче,

 

 

1

 

 

22

 

 

 

чем глобально править всю программу. Однажды,

 

 

 

 

 

 

 

когда мне понадобилось изменить размер массива

 

 

2

 

 

33

 

 

 

со 100 на 300, я воспользовался командой replace.

 

 

3

 

 

44

 

 

 

 

 

 

 

 

 

 

 

 

Это было финансовое приложение, где учитыва­

 

 

 

 

 

 

 

 

 

лось, что в долларе 100 центов. После такой гло­

 

Рис. 5 . 11 .

Использование

бальной замены в долларе оказалось 300 центов,

 

 

 

 

короткого

массива

что почему-то не понравилось моему начальнику.

 

 

 

 

для

проверки защиты

Обратите внимание, что если не включить заго­

 

 

 

 

от

переполнения

 

 

 

 

 

 

 

 

 

ловочный файл cstdlib (или stdlib. h) компилятор

 

 

 

 

 

 

 

 

 

посчитает вызов atof() синтаксической

ошибкой

 

 

 

 

 

 

 

и напишет, что не знает такой функции. На самом деле это не так. Компилятор

 

 

 

 

 

 

 

знает, что такое библиотечные функции и где они находятся. Что делать, если

 

 

 

 

 

 

 

вы не знаете, какой заголовочный файл нужен для функции atof()?

 

Спросите

 

компилятор. В UNIX можно написать man atof. В Windows — выделить atof в исходном коде и нажать F1 (Help). Появится страница диалогового справочника, рассказывающая о функции atof (), включая то, в каком заголовочном файле она находится.

Определение типов массивов

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

double data[NUM]; double tax[NUM];

Если эти определения находятся в разных местах программы, то сопровождаю­ щему ее программисту (или партнеру разработчика) нужно понимать, что данные объекты имеют одну структуру, т. е. опять встает вопрос написания программного кода так, чтобы знания разработчика можно было передать сопрово>вдающему программисту.

Для этого полезно определить для массива с NUM-компонентами типа double тип SalesData и использовать его затем для определения переменных:

SalesData data; SalesData tax;

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

typedef известный_тип имя_нового_типа;

После этого оператора (завершенного точкой с запятой) программа может исполь­ зовать известный_тип и имя_нового_типа как синонимы.

166

Часть I« Введение в программирование на С-^'^

Простым примером применения typedef является следующий фрагмент про­ граммы, обрабатывающий информацию инвентаризации:

i nt

idx,

quant, const MAX=30, qty[MAX];

for

(idx

= 0; idx < MAX; idx++)

{

Gin »

quant;

qty[idx] = quant; }

Здесь переменные idx, quant, MAX и элементы массива qty[] — целочисленные, однако это целые другого рода: одна переменная представляет собой индекс массива, другие описывают количество инвентарных единиц. Операторы вида qty[idx] = quant; имеют смысл. Операторы типа idx = quant; смысла не имеют: в вычислениях нельзя смешивать яблоки и апельсины. Что касается правил C+ + , то для него допустимы оба оператора. Единственный способ подчеркнуть разли­ чия — ввести новые имена типов:

typedef int

Index;

/ /

один вид целого

Index

idx;

 

 

 

const

Index MAX=30;

 

 

typedef int

Stock;

 

 

Stock

quant,

qty[MAX];

/ /

другой вид целого

for

(idx

= 0; idx < MAX; idx++)

/ /

сравнение в рамках одного типа

{

cin »

quant;

 

 

 

qty[idx]

= quant; }

/ /

присваивание в рамках одного типа

Здесь idx и МАХ имеют один тип — Index, так что их сравнение вполне законно. Переменные quant и qty[idx] также одного типа, поэтому допускается присваи­ вание. Если бы программист написал, к примеру, idx = quant;, то такое присваи­ вание было бы подозрительным, ведь переменные имеют разный тип.

В данном примере для определения нового имени существующего типа int используется typedef. Новый тип не вводится. Они различаются только ддя про­ граммиста. Для компилятора Index и Stock — псевдонимы одного имени типа.

Для определения типа массива используется нечто отличное от директивы typedef, определяющей новый тип:

int const MAX = 30;

typedef double SalesData[MAX];

В данном определении ключевое слово typedef предшествует синтаксически полному определению массива. При отсутствии ключевого слова typedef это определение привело бы к введению нового имени SalesData. Оно определялось бы через тип double и константу МАХ как имя переменной-массива. Поскольку определение typedef присутствует, то вводится имя нового типа SalesData. Со­ гласно тому, что следует за typedef, данный тип определяет массив компонентов МАХ типа double. (Конечно, МАХ может быть любой константой этапа компиляции, включая литеральное целочисленное значение.)

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

SalesData data; SalesData tax;

Глава 5 • Агрегирование с помощью типов, определяемых программистом

[ 167 щ

Переменные data и tax могут использоваться как любые переменные-массивы с применением тех же обозначений компонентов массива, как и во всех других случаях:

for

(int idx

= 0; idx < MAX; idx++)

{

tax[idx]

= data[idx] * 0.05; }

Форма typedef, с помощью которой определялись типы Index и Stock, отли­ чается от формы определения типа SalesData, но они работают одинаково. Опре­ деляется новое имя типа — единственный элемент typedef (Index и Stock — в первом случае, SalesData — во втором).

Структуры как неоднородные агрегаты

Существует еще один определяемый программистом тип данных — это струк­ туры. В C+-I- структуры представляют мощный инструмент для комбинирования связанных компонентов. Они могут определяться с помощью нескольких методов, и мы обсудим наиболее популярные. Все эти методы позволяют программисту определять состав компонентов структуры (поля или компоненты данных), т. е. перечислять их типы и имена.

Структуры, определяемые программистом

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

struct

Account {

/ /

теперь

'Account' - имя типа

long

number;

/ /

'number'

- имя поля

double

balance;

 

 

 

double

overdue; } ;

/ /

двоеточие за фигурной скобкой

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

struct

Account {

/ /

теперь 'Account' - имя типа

long

number;

/ /

'number' - имя поля

double balance, overdue;

} ;

 

C++ поддерживает еще и другой синтаксис определения структуры, унаследо­ ванный из С. В нем для определения нового типа используется typedef:

typedef struct tagAccount { long number;

double balance, overdue; } Account;

Такое использование typedef мы уже видели в предыдущем разделе для целых переменных.

typedef известный_тип имя_нового_типа;

168

Часть 1 ^ Введение в програтттровоишв

Здесь известный_тип представлен определением struct tagAccount, а Account — это имя_нового_типа (подобно всем предыдущим примерам с typedef, Account — единственное, еще не определенное здесь имя). Фактически struct tagAccount является также именем типа и может использоваться там, где применяется тип Account, однако это имя менее удобно, поскольку ключевое слово struct отличает его от встроенных имен типов. Такая форма определения имен типа для структур очень популярна в С, но в C++ она не нужна.

Создание и инициализация переменных-структур

При определении структуры память не выделяется. Такое определение задает шаблон для будущего распределения памяти: сколько именно памяти нужно выделить, как ее интерпретировать и какие имена использовать для доступа к значениям в памяти. Когда имя структуры используется для определения пере­ менных, это делается точно так же, как в случае встроенных примитивных типов int, double и т. д.

Account a1, а2;

/ / память для двух переменных Account

Здесь создаются две переменные типа Account. Каждая из них содержит поля с именами number, balance и overdue. Общий размер каждой переменной Account равен размеру одного значения long, плюс два значения double, плюс некоторое место для выравнивания (значение не может начинаться с произвольного адреса, а должно выравниваться на адрес, кратный 4 или 8).

Фигурные скобки в определениях типа структуры в предыдущем разделе сле­ дует принимать всерьез. Подобно другим областям действия, они обозначают блок со своей отдельной областью действия (об этом подробнее в следующей главе). Определяемые в данной области действия имена вне этой области неизвестны. Так как number — элемент данных типа long, а не переменная long, ее имя нельзя использовать без уточнения:

number = 800123456L;

/ / ошибка

Такое выражение неверно, поскольку не указано, к какому счету account принад­ лежит number. Высокоприоритетная операция точки в C+-I- (селектор) позволяет выбрать поля переменных-структур. Аналогично компонентам массива, доступным по индексам, к полям структуры можно обращаться как 1-значениям и г-значениям с помощью одной и той же формы записи с точкой:

a1. number = 800123456L;

/ /

поле используется как 1-значение

i f (a1.number == 800123456L)

/ /

поле используется как г-значение

а2. number = a1. number;

/ /

1-значение и г-значение

Число элементов структуры должно быть определено во время компиляции. Элементы массива имеют один тип, они не нуждаются в индивидуальных именах, но упорядочены. На элементы массива можно ссылаться с помощью имени пере­ менной-массива и индексов. Компоненты структуры не упорядочены. Они имеют индивидуальные имена и могут быть разных типов. Для ссылки на них использует­ ся имя переменной-структуры и имя компонента. Результатом будет значение, тип которого задан для данного компонента в определении структуры.

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

При создании переменных-структур их поля не содержат полезной информа­ ции. Как и в случае с массивами, С-Ь4- поддерживает инициализацию структур, где для каждого компонента структуры задается значение соответствующего типа:

Account a1 = { 800123456L, 532.84, О } ;

Глава 5 • Агрегирование с помощью типов, определяемых профоммистог^

[ 169 |

Такой синтаксис допускается только для структур с общедоступными полями (public), т. е. полями, к которым можно обращаться в клиентском коде (все струк­ туры, описываемые в данной главе, имеют такие поля). Позднее будет рассказано об инициализации структур с закрытыми для доступа полями при помощи конст­ рукторов и переменных-классов.

Как и для неагрегированных переменных, допускается инициализация пере­ менных-структур посредством другой, определенной ранее переменной-структуры:

Account аЗ=а1;

/ / будет содержать в полях 800123456L, 532.84, О

Иерархические структуры и их компоненты

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

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

Account carcls[500]; / / массив из 500 структур

При обращении к полям, принадлежащим к элементам такого массива, нужно использовать иерархическую запись. Операция индекса и операция точки (се­ лектора) имеют одинаковый приоритет и ассоциируются слева направо. Вот так можно обращаться к полю number компонента с индексом 75 массива cards (код читается справа налево):

carcls[75]. number = 800123456L;

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

struct Customer

{

char name[30] char aclclress[70]; Account acct;

} :

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

struct Customer

{char name[30], address[70]; Account acct; } ;

При создании переменной Customer отводимая для нее память содержит два символьных массива и переменную Account, состоящую из поля long и двух полей double. Для элементов массива и элементов Account также используется иерархи­ ческая запись:

Customer с;

strcpy(c.name, "Doe, John");

// с.name имеет тип char[]

Соседние файлы в предмете Программирование на C++