Массивы объектов
Массивы объектов можно создавать точно так же, как создаются массивы значений других типов. Например, в следующей программе создается класс display, который содержит значения разрешения для различных режимов видеомонитора. В функции main() создается массив для хранения трех объектов типа display, а доступ к объектам, которые являются элементами этого массива, осуществляется с помощью обычной процедуры индексирования массива
// Пример использования массива объектов.
#include <iostream>
using namespace std;
enum resolution {low, medium, high}
class display {
int width;
int height;
resolution
int i, w, h;
display_mode[0].set_res(low);
display_mode[0].set_dim(640, 480);
display_mode[1].set_res(medium);
display_mode[1].set_dim(800, 600);
display_mode[2].set_res(high);
display_mode[2].set_dim(1600, 1200);
cout << "Возможные режимы отображения данных:\n\n";
for(i=0; i < 3; i++) {
cout << names[display_mode[i].get_res()] << ":";
display_mode[i].get_dim(w, h);
cout << w << " x " << h << "\n";
}
return 0;
}
При выполнении эта программа генерирует такие результаты.
Возможные режимы отображения данных:
низкий: 640 х 480
средний: 800 х 600
высокий: 1600 х 1200
Обратите внимание на использование двумерного символьного массива names для преобразования перечислимого значения в эквивалентную символьную строку. Во всех перечислениях, которые не содержат явно заданной инициализации, первая константа имеет значение 0, вторая — значение 1 и т.д. Следовательно, значение, возвращаемое функцией get_res(), можно использовать для индексации массива names, что позволяет вывести на экран соответствующее название режима отображения. Многомерные массивы объектов индексируются точно так же, как многомерные массивы значений других типов.
Инициализация массивов объектов
Если класс включает параметризованный конструктор, то массив объектов такого класса можно инициализировать. Например, в следующей программе используется параметризованный класс samp и инициализируемый массив sampArray объектов этого класса.
// Инициализация массива объектов.
#include <iostream>
using namespace std;
class samp {
int a;
public:
samp(int n) { a = n; }
int get_a() { return a; }
};
int main()
{
samp sampArray[4] = { -1, -2, -3, -4 };
int i;
for(i=0; i < 4; i++) cout << sampArray[i].get_a() << ' ';
cout << "\n";
return 0;
}
Результаты выполнения этой программы
-1 -2 -3 -4
подтверждают, что конструктору samp действительно были переданы значения от -1 до
-4.
В действительности синтаксис инициализации массива, выраженный строкой
samp sampArray[4] = { -1, -2, -3, -4 };
представляет собой сокращенный вариант следующего (более длинного) формата:
samp sampArray[4] = { samp(-1), samp(-2), samp(-3), samp(-4) };
Формат инициализации, представленный в программе, используется программистами чаще, чем его более длинный эквивалент, однако следует помнить, что он работает для массивов таких объектов, конструкторы которых принимают только один аргумент. При инициализации массива объектов, конструкторы которых принимают несколько аргументов, необходимо использовать более длинный формат инициализации. Рассмотрим пример.
#include <iostream>
using namespace std;
class samp {
int a, b;
public:
samp(int n, int m) { a = n; b = m; }
int get_a() { return a; }
int get_b() { return b; }
};
int main()
{
samp sampArray[4][2] = {
samp(1, 2),
samp(3, 4),
samp(5, 6),
samp(7, 8),
samp(9, 10),
samp(11, 12),
samp(13, 14),
samp(15, 16)
};
int i;
for(i=0; i < 4; i++) {
cout << sampArray[i][0].get_a() << ' ';
cout << sampArray[i][0].get_b() << "\n";
cout << sampArray[i][1].get_a() << ' ';
cout << sampArray[i][1].get_b() << "\n";
}
cout << "\n";
return 0;
}
В этом примере конструктор класса samp принимает два аргумента. В функции main()
объявляется и инициализируется массив sampArray путем непосредственных вызовов
конструктора samp(). Инициализируя массивы, можно всегда использовать длинный формат
инициализации, даже если объект принимает только один аргумент (короткая форма просто
более удобна для применения). Нетрудно проверить, что при выполнении эта программа
отображает такие результаты.
1 2
3 4
5 6
7 8
9 10
11 12
13 14
15 16
Указатели на объекты
Как было показано в предыдущей главе, доступ к структуре можно получить напрямую или через указатель на эту структуру. Аналогично можно обращаться и к объекту: непосредственно (как во всех предыдущих примерах) или с помощью указателя на объект. Чтобы получить доступ к отдельному члену объекта исключительно "силами" самого объекта, используется оператор "точка". А если для этого служит указатель на этот объект, необходимо использовать оператор "стрелка". (Применение операторов "точка" и "стрелка" для объектов соответствует их применению для структур и объединений.)
Чтобы объявить указатель на объект, используется тот же синтаксис, как и в случае объявления указателей на значения других типов. В следующей программе создается простой класс Р_ехample, определяется объект этого класса ob и объявляется указатель на объект типа Р_ехample с именем р. В этом примере показано, как можно напрямую получить доступ к объекту ob и как использовать для этого указатель (в этом случае мы имеем дело с косвенным доступом)
// Простой пример использования указателя на объект.
#include <iostream>
using namespace std;
class P_example {
int num;
public:
void set_num(int val) {num = val;}
void show_num();
};
void P_example::show_num()
{
cout << num << "\n";
}
int main()
{
P_example ob, *p; // Объявляем объект и указатель на него.
ob.set_num(1); // Получаем прямой доступ к объекту ob.
ob.show_num();
р = &ob; // Присваиваем указателю р адрес объекта ob.
p->show_num(); // Получаем доступ к объекту ob с помощью
указателя.
return 0;
}
Обратите внимание на то, что адрес объекта ob получается путем использования оператора что соответствует получению адреса для переменных любого другого типа
Как вы знаете, при инкрементации или декрементации указателя он инкрементируется или декрементируется так, чтобы всегда указывать на следующий или предыдущий элемент базового типа. То же самое происходит и при инкрементации или декрементации указателя на объект: он будет указывать на следующий или предыдущий объект. Чтобы проиллюстрировать этот механизм, модифицируем предыдущую программу. Теперь вместо одного объекта ob объявим двухэлементный массив ob типа P_example. Обратите внимание на то, как инкрементируется и декрементируется указатель р для доступа к двум элементам этого массива.
// Инкрементация и декрементация указателя на объект.
#include <iostream>
using namespace std;
class P_example {
int num;
public:
void set_num(int val) {num = val;}
void show_num();
};
void P_example::show_num()
{
cout << num << "\n";
}
int main()
{
P_example ob[2], *p;
ob[0].set_num(10); // прямой доступ к объектам
ob[1].set_num(20);
p = &ob[0]; // Получаем указатель на первый элемент.
p->show_num(); // Отображаем значение элемента ob[0] с помощью
указателя.
p++; // Переходим к следующему объекту.
p->show_num(); // Отображаем значение элемента ob[1] с помощью
указателя.
p--; // Возвращаемся к предыдущему объекту.
p->show_num(); // Снова отображаем значение элемента ob[0].
return 0;
}
Вот как выглядят результаты выполнения этой программы.
10
20
10
Как будет показано ниже в этой книге, указатели на объекты играют главную роль в реализации одного из важнейших принципов C++: полиморфизма.