ного положения вещей, но все же оно достаточно близко к практическим целям. От-
ветственность за то, чтобы при выполнении программы не возникали подобные бес-
смысленные значения, возлагается на программиста.
102
В программе 2.1 демонстрируется, как в исходном тексте на Си++ располага-
ются описание типа структуры, объявление переменных и обращение к компонентам
структуры.
struct SimpleStructure {
char c;
int i;
float f;
double d;
};
void main()
{
SimpleStructure s1, s2;
s1.c = 'a';
s1.i = 1;
s1.f = 3.14f; // Буква 'f' в конце вещественной константы
// означает, что это константа типа float,
// а не double
s1.d = 0.00093;
s2.c = 'b';
s2.i = 2;
s2.f = 6.28f;
s2.d = 0.15;
}
Программа 2.1.
В программе 2.1 в функции "main()" создаются две переменные типа
"SimpleStructure" с именами "s1" и "s2". У каждой из этих переменных есть соб-
ственный набор компонент с именами "c", "i", "f", и "d". Т.е. "s1" и "s2" являются
наборами независимых переменных. Для выбора компонент внутри "s1" или "s2"
применяется _______операция "." (точка). Подобная запись применялась в предыдущих лек-
циях для обращения к функциям-членам объектов "cin" и "сout". Обращения к ком-
понентам классов в объектно-ориентированном Си++ очень похожи на обращения к
компонентам структур.
Переменные типа структуры можно присваивать, передавать, как параметры
функции, и возвращать из функции в качестве результата. Например:
Person current;
Person set_current_person( Person& p )
{
Person prev = current;
current = p;
return prev;
}
Остальные операции, такие, как сравнение ("==" и "!="), для структур по умол-
чанию не определены, но программист может их определить при необходимости.
Имя типа структуры можно использовать еще до того, как этот тип будет опре-
делен, вплоть до момента, когда потребуется, чтобы стал известен размер структуры.
Например, допустимы следующие прототипы функций:
103
struct S; // S - имя некоторого типа
S f();
void g(S v1);
Но эти функции нельзя вызывать, если тип "S" не определен:
void h()
{
S a; // ошибка: S не объявлено
f(); // ошибка: S не объявлено
g(a); // ошибка: S не объявлено
}
3. Доступ к компонентам структуры через указатель
Во всех предыдущих примерах компоненты структур использовались в выра-
жениях подобно обычным переменным. Аналогично обычным переменным, можно
создавать указатели на переменные-структуры. Для доступа к компонентам структу-
ры через указатель применяется операция "->". Например:
void print_person( Person* p )
{
cout << p->name << '\n';
cout << p->birthdate.day << '\n';
cout << p->birthdate.month << '\n';
cout << p->birthdate.year << '\n';
cout << p->salary << '\n\n';
}
Функцию "print_person()" можно переписать в эквивалентном виде с по-
мощью операции разыменования указателя "*" и операции доступа к компонентам
структуры ".". Обратите внимание на скобки в записи "(*p).", которые необходимы,
поскольку приоритет операции "." выше, чем у "*":
void print_person( Person* p )
{
cout << (*p).name << '\n';
cout << (*p).birthdate.day << '\n';
cout << (*p).birthdate.month << '\n';
cout << (*p).birthdate.year << '\n';
cout << (*p).salary << '\n\n';
}
Использование указателей на структуры показано в программе 3.1. В функции
"main()" этой программы указателю "sp" cначала присваивается адрес "s1", и затем
с помощью операции "->" компонентам "s1" присваиваются начальные значения. За-
тем указателю "sp" присваивается адрес "s2", и компоненты этой структуры также
инициализируются. Возможность динамического перенаправления на различные объ-
екты является одним из важнейших свойств указателей, которые, в частности, полез-
ны для реализации динамических структур данных (например, связного списка или
стека).
struct ExStruct {
104
char c;
int i;
float f;
double d;
};
void main()
{
ExStruct s1, s2;
ExStruct* sp = &s1;
sp->c = 'a';
sp->i = 1;
sp->f = 3.14f;
sp->d = 0.00093;
sp = &s2;
sp->c = 'b';
sp->i = 2;
sp->f = 6.28f;
sp->d = 2.5;
}
Программа 3.1.
В 7-й лекции (для реализации связного списка) и в 8-й лекции уже рассматри-
валось понятие рекурсивных структур данных. Для создания в структуре ссылки на
структуру такого же типа необходимо пользоваться указателем. В программе 3.2 соз-
даются две структуры, содержащие ссылки друг на друга.
struct SelfReferential {
int i;
SelfReferential* sr;
};
void main()
{
SelfReferential sr1, sr2;
sr1.sr = &sr2;
sr2.sr = &sr1;
sr1.i = 47;
sr2.i = 1024;
}
Программа 3.2.
Описание без указателя является недопустимым, т.к. в нем при описании ком-
поненты структуры тип структуры используется в тот момент, когда этот тип еще не
определен полностью:
struct SelfReferential {
int i;
SelfReferential sr; // Недопустимое описание компоненты
};
4. Массивы и структуры
У массива и структуры есть общее свойство: оба этих типа данных являются
типами с произвольным доступом. Но структура более универсальна, поскольку не
105
требуется, чтобы типы всех ее компонент были одинаковы. С другой стороны, в неко-
торых случаях массив предоставляет бoльшие возможности, т.к. индексы его элемен-
тов могут вычисляться, а имена компонент структуры –это фиксированные иденти-
фикаторы, задаваемые в описании типа.
Массивы и структуры могут комбинироваться различными способами. Напри-
мер, i-я компонента массива "a", который является компонентой структуры "r", обо-
значается как
r.a[i]
Напротив, компонента с именем "s", входящая в i-ю компоненту-структуру
массива структур "a" обозначается как
a[i].s
В качестве примера далее приведено описание переменной "screen", которая
является двумерным массивом структур типа "Cell". Этот массив предназначен для
хранения содержимого текстового экрана размером 80х25 знакомест:
struct Cell {
unsigned char character; // Символ
int foreground; // Цвет символа
int background; // Цвет фона
bool blink; // Мигание включено/выключено
};
void main()
{
Cell screen[25][80];
...
}
5. Перегрузка операторов
В 3-й лекции (п. 4) были описаны средства перегрузки функций. Си++ допус-
кает перегрузку не только функций, но и операторов, таких, как _______"+", "-", "*" (и боль-
шинство других – "+=", "->" и даже "()"). Средства перегрузки операторов полезны
тогда, когда желательно, чтобы пользовательские типы данных в исходном тексте
программ выглядели в выражениях подобно встроенным типам Си++. Поясним это на
нескольких примерах.
Для сложения комплексных чисел (описание структуры см. п.2) можно приме-
нить функцию:
Complex C_add( const Complex& x, const Complex& y )
{
Complex t;
t.re = x.re + y.re;
t.im = x.im + y.im;
return t;
}
Параметры функции "C_add(...)" передаются по ссылке, и, кроме того, опи-
саны как константы, чтобы запретить изменения параметров внутри функции. Пере-
дача по ссылке обеспечивает эффективный вызов функции, без копирования парамет-
ров, а константное описание защищает параметры от изменений.
106
Допустим, в программе реализована также функция "C_mult(...)" для умно-
жения двух комплексных чисел. Ниже приведен пример использования этих функ-