692.50K
Категория: ПрограммированиеПрограммирование

Классы и объекты. Массивы

1.

Классы и объекты
ООП
Массивы
В языке С++ массивы могут состоять из объектов. С синтаксической
точки зрения объявление массива объектов ничем не отличается от объявления
массива встроенного типа, использование массивов объектов тоже не отличается.
Вот как выглядит программа, в которой используется массив из трех элементов:
#include <iostream>
using namespace std;
class cl
{
int i ;
public:
void set_i(int j) { i=j; }
int get_i() { return i; }
};
int main()
{
cl ob[3];
int l ;
for(i=0; i<3; i++) ob[i].set_i(i+1) ;
for(i=0; i<3; i++) cout << ob[i].get_i() << "\n";
return 0;
}
Эта программа выводит на экран числа 1, 2 и 3.
1

2.

Классы и объекты
ООП
Массивы
Если в классе определен конструктор с параметрами, каждый объект в массиве
инициализируется с помощью списка инициализации, как это принято для
массивов любого типа. Однако точный вид списка инициализации зависит от
количества параметров конструктора. Если конструктор имеет лишь один
параметр, можно просто задать список начальных значений, используя обычные
синтаксические конструкции, предназначенные для инициализации массивов. По
мере создания элементов массива эти значения поочередно присваиваются
параметру конструктора.
Вот как выглядит слегка измененная версия предыдущей программы:
#include <iostream>
using namespace std;
class cl
{
int i ;
public:
cl(int j) { i=j; } // Конструктор int
get_i() { return i; }
};
int main() {
// Список инициализации
cl ob[3] = {1, 2, 3};
int i ;
for(i=0; i<3; i++)
cout << ob[i].get_i() <<"\n";
return 0; }
// программа выводит числа 1, 2 и 3.
Фактически список инициализации,
показанный в этой программе, является
сокращенным вариантом более сложной
формы:
cl ob[3] = { cl(1), cl(2), сl(3) };
Здесь конструктор cl вызывается явно.
Разумеется,
короткая
форма
инициализации используется чаще. В
основе этого способа лежит возможность
автоматического преобразования типа,
если конструктор имеет только один
аргумент. Таким образом, сокращенную
форму
инициализации
можно
использовать, только если массив
состоит из объектов, конструкторы
которых имеют лишь один аргумент.
2

3.

Классы и объекты
ООП
Массивы
Если конструктор объекта имеет несколько аргументов, следует применять
полную форму инициализации. Рассмотрим пример:
#include <iostream>
using namespace std;
class cl
{
int h, i;
public:
// Конструктор с двумя параметрами
cl (int j, int k) { h=j; i=k; }
int get_i() { return i; }
int get_h() { return h; }
};
int main() {
Здесь конструктор массива cl имеет
два аргумента. Следовательно, следует
применять не сокращенную, а полную
форму инициализации.
// Инициализация
cl ob[3] = {cl(l, 2), cl(3, 4), cl(5, 6) };
int i ;
for(i=0; i<3; i++)
{ cout << ob[i].get_h();
cout << ", ";
cout << ob[i].get_i() << "\n";
}
return 0; }
3

4.

Классы и объекты
ООП
Массивы
Инициализированные и неинициализированные массивы
Особая ситуация возникает, когда необходимо создать как инициализированные,
так и неинициализированные массивы объектов. Рассмотрим следующий класс:
class cl {
int i ; public:
cl (int j) // Конструктор
{ i=j; }
int get_i() { return i; }
};
Здесь
в
классе
cl
определен
конструктор с одним параметром. Это
значит, что любой массив такого типа
должен быть инициализирован.
Т.о. массив нельзя объявить обычным
образом:
cl а[9]; // Ошибка, конструктору
необходим список инициализации
Этот оператор не работает, поскольку предполагается, что класс cl не имеет
параметров, так как в объявлении массива не указан список инициализации.
Однако класс cl не содержит конструкторов, не имеющих параметров. Итак,
поскольку данному объявлению не соответствует ни один конструктор,
компилятор выдаст сообщение об ошибке. Чтобы решить эту проблему,
необходимо перегрузить конструктор, добавив вариант, не имеющий параметров,
как показано ниже. Теперь в программе можно объявить оба вида массива:
class cl {
В таком варианте программы
int i; public:
допускаются следующие операторы:
cl() {i=0;} // для неинициализированных массивов // Инициализированный массив
cl a1[3] = {3, 5, 6};
cl(int j) { i=j; } // для инициализированных
массивов
// Неинициализированный массив
int get_i() { return i; }
cl а2[34];
};
4

5.

Классы и объекты
ООП
Массивы
Пример: создание двумерного массива объектов и инициализация его:
// Создание двумерного массива объектов
#include <iostream>
using namespace std;
class samp {
int a; public:
samp(int n) { a = n; }
int get_a() { return a; }
};
программа выводит на экран:
1
3
5
7
2
4
6
8
int main ()
{
samp ob[4][2] = {1,2,3,4,5,6,7, 8 };
int i;
for(i=0; i<4; i++)
{
cout << ob[i][0].get_a() << ' ';
cout << ob[i][1].get_a() << "\n";
}
cout << "\n"; return 0;
}
5

6.

Классы и объекты
Указатели.
ООП
Указатели на объекты
Указатели могут ссылаться не только на переменные встроенных типов, но и на
объекты. Для доступа к членам класса через указатель на объект используется
оператор "->", а не ".".
Пример:
#include <iostream>
using namespace std;
class cl
{
int i ;
public:
cl(int j) { i=j; }
int get_i() { return i; }
};
int main()
{
cl ob(88), *p;
р = &ob; // Получаем адрес объекта ob
cout << p->get_i();
// для вызова функции get_i() применяется
При увеличении указателя на
единицу он перемещается на
следующий элемент того же типа.
Например,
целочисленный
указатель будет ссылаться на
следующее целое число. Как
правило, адресная арифметика
зависит от типа указателя, т.е.
она зависит от типа данных, на
который ссылается указатель.
Это
правило
касается
и
указателей на объекты.
// оператор ->
return 0;
}
6

7.

Классы и объекты
ООП
Указатели. Указатели на объекты
Пример: использование указателя
Пример: присваивание указателю
для доступа ко всем трем
адреса открытого члена объекта и
элементам массива ob.
ссылка на этот член с его помощью.
#include <iostream>
#include <iostream>
using namespace std;
using namespace std;
class cl {
class cl
int i ; public:
{ public: int i ;
cl() { i=0; } cl(int j) { i=j; }
cl(int j) { i=j; }
int get_i() { return i; }
};
};
int main() {
int main()
cl ob(1); int *p;
{
р = &ob.i; // Получить адрес члена ob.i
cl ob[3] = {1, 2, 3}; cl *p; int i ;
cout << *р; // Обращение к члену ob.i
// через указатель р
p = ob; // Установить указатель на первый
элемент массива
return 0;
for(i=0; i<3; i++)
}
{ cout << p->get_i() << "\n";
Указатель р ссылается на целое число,
p++; // Указатель на следующий объект он имеет тип int. В данном случае не
}
имеет значения, что переменная i
return 0;
является членом объекта ob.
}
7

8.

Классы и объекты
ООП
Указатели. Проверка типа указателей
Работая с указателями, следует иметь в виду: присваивать можно лишь
указатели совместимых типов.
int *pi;
float *pf;
Следующий оператор неверен:
pi = pf; // Ошибка ‒ несовместимость типов.
Разумеется, любую несовместимость можно преодолеть, используя
приведение типов, однако это нарушает принципы строгой проверки
типов.
Указатель this
С++ содержит специальный указатель this. Это указатель, который
автоматически передается любой функции-члену при ее вызове и
указывает на объект, генерирующий вызов.
Например:
ob fl();
// предположим, что ob ‒ это объект, функции fl()
автоматически передается указатель на объект ob. Этот указатель и
называется this.
Важно понимать, что указатель this передается только функциямчленам. Дружественным функциям указатель this не передается.
8

9.

Классы и объекты
ООП
Указатели. Указатель this
При вызове функции-члена ей неявно передается указатель на вызывающий
объект. Этот указатель называется this. Рассмотрим программу, в которой описан
класс pwr, предназначенный для вычисления степени некоторого числа:
#include <iostream>
using namespace std;
class pwr
{
double b;
int e ;
double val;
public:
pwr(double base, int exp);
double get_pwr() { return val; }
};
pwr::pwr(double base, int exp)
{
b = base;
e = exp;
val = 1;
if(exp==0) return;
for( ; exp>0;
exp -> val = val * b;
}
см.продолжение
int main()
продолжение
{ pwr x(4.0, 2), y(2.5, 1), z(5.7, 0);
cout << x.get_pwr() << " ";
cout << y.get_pwr() << " ";
cout << z.get_pwr() << "\n";
return 0;
}
Внутри класса к функции-члену можно обращаться
напрямую, не используя объекты и название класса.
Таким образом, внутри конструктора pwr() оператор b =
base; (сокращённая форма) означает, что переменной b,
принадлежащей вызывающему объекту, присваивается
значение переменной base.
Однако тот же самый оператор можно переписать иначе
(в расширенной форме):
this -> b = base;
Указатель this ссылается на объект, вызывающий
функцию pwr(). Т.о., выражение this->b ссылается на
переменную b, принадлежащую текущему объекту.
Например, если функция pwr() вызвана объектом х (в
объявлении х(4.0, 2)), то указатель this в предыдущем
операторе будет ссылаться на объект х. Впрочем, этот
оператор можно записать в сокращенном виде, не
используя указатель this.
9

10.

Классы и объекты
ООП
Указатели. Указатель this
Рассмотрим
полное
определение
конструктора
pwr(),
написанное
с
помощью указателя this:
pwr::pwr (double base, int exp)
{
this->b = base;
this->e = exp;
this->val = 1;
if(exp==0) return;
for( , exp>0; exp--)
this -> val = this -> val * this -> b;
}
Сокращённая форма
(с предыдущего слайда)
pwr::pwr(double base, int exp)
{
b = base;
e = exp;
val = 1;
if(exp==0) return;
for( ; exp>0;
exp -> val = val * b;
}
На самом деле обычно конструктор таким образом не пишут, поскольку
сокращенная форма намного проще. Однако указатель this очень важен
при перегрузке операторов, а также в ситуациях, когда функция-член
должна использовать указатель на вызывающий объект.
Следует помнить, что указатель this автоматически передается всем
функциям-членам. Следовательно, функцию get_pwr() можно переписать
иначе:
double get_pwr() { return this->val; }
В этом случае, если функция get_pwr() вызывается с помощью оператора
y.get_pwr () ;
указатель this будет ссылаться на объект у.
10

11.

Классы и объекты
ООП
Указатели. Указатели на производные типы
Как правило, указатель одного типа не может ссылаться на
объект другого типа.
Однако у этого правила есть важное исключение,
касающееся производных классов:
Предположим для начала, что в программе объявлены два
класса: B и D. Кроме того, допустим, что класс D является
производным от базового класса B. В этом случае указатель
типа B* может ссылаться и на объекты типа D.
В принципе, указатель на базовый класс можно
использовать как указатель на объект любого производного
класса.
Однако обратное утверждение неверно. Указатель типа D*
не может ссылаться на объекты класса B.
Кроме того, с помощью указателя на базовый класс можно
ссылаться только на наследуемые члены, но не на новые
члены производного класса. (Однако указатель на базовый класс
можно привести к типу указателя на производный класс и получить
доступ ко всем членам производного класса.)
11

12.

Классы и объекты
ООП
Указатели. Указатели на производные типы
Рассмотрим программу: применение указателя на базовый класс для
доступа к объектам производного класса:
продолжение
#include <iostream>
using namespace std;
class base
{
int i ;
public:
void set_i(int num) { i=num; }
int get_i() { return i; }
};
class derived: public base
{
int j ;
public:
void set_j(int num) { j=num; }
int get_j() { return j; }
};
см.продолжение
int main()
{
base *bp;
derived d;
bp = &d; /*
Базовый указатель ссылается
на объект производного класса */
// Доступ к объекту производного класса с
// помощью указателя на производный класс
bp -> set_i(10);
cout << bp ->get_i () << " ";
Следующий оператор не работает. На
элементы
производного
класса
нельзя
ссылаться с помощью указателя на базовый
класс
/*
bp -> set_j(88);
// Ошибка
cout << bp -> get_j(); // Ошибка
*/
return 0;
}
Как видим, указатель на базовый класс позволяет обращаться к объекту
производного класса.
12

13.

Классы и объекты
ООП
Указатели. Указатели на члены класса
В языке С++ существует особый тип указателя, который
ссылается на член класса вообще, а не на конкретный
экземпляр этого члена в объекте.
Указатель такого вида называется указателем на член
класса.
Этот необычный указатель задает смещение внутри объекта
соответствующего класса.
Поскольку указатели на члены класса не являются
указателями в обычном смысле слова, к ним нельзя
применять операторы "." и "->".
Чтобы обратиться к члену класса с помощью указателя на
него, следует применять особые операторы: ".*" и "->*".
13

14.

Классы и объекты
ООП
Указатели. Указатели на члены класса
Пример:
#include <iostream>
using namespace std;
class cl
{ public:
сl (int i)
{ val=i; }
int val;
int double_val()
{ return val+val; }
};
int main()
{ int cl::*data;
// Указатель на член класса
int(cl::*func)();
//Указатель на функцию-член
cl ob1(1), ob2(2);
// Создаем объекты
data = &cl::val;
// Определяем смещение члена val
func = &cl::double_val; // Определяем смещение
//функции double_val()
cout << "Значения: ";
cout << ob1.*data << " " << ob2.*data << "\n";
cout << "Удвоенные значения: ";
cout << (ob1.*func) () << " ";
cout << (ob2.*func)() <<"\n";
return 0;
}
Эта программа создает два указателя на члены класса: data и func.
Синтаксические особенности их объявлений: объявляя указатели на члены, следует
задавать имя класса и применять оператор разрешения области видимости.
Кроме того, программа создает два объекта класса cl: оb1 и оb2. Указатели на члены класса
могут ссылаться как на переменные, так и на члены. Затем вычисляются адреса членов val
и double_val (). Эти "адреса" представляют собой смещения соответствующих членов в
объекте класса cl. Значения, хранящиеся в переменной val в каждом из объектов, выводятся
на экран с помощью указателя data.
В заключение программа вызывает функцию double_func (), используя переменную func,
являющуюся указателем на член класса. Обратите внимание на то, что для правильного
14
выполнения оператора ".*" необходимы дополнительные скобки.

15.

Классы и объекты
ООП
Указатели. Указатели на члены класса
Для доступа к члену класса через объект или ссылку на него используется оператор ".*". Если
задан указатель на объект, для доступа к его членам необходимо применять оператор "->*".
Пример:
#include <iostream>
using namespace std;
class cl
{ public:
cl (int i)
{ val=i; }
int val;
int double_val()
{ return val+val; }
};
int main() {
int cl::*data; // Указатель на переменную-член int
(cl::*func)(); // Указатель на функцию-член cl ob1(1)
ob2(2);
// Создаем объекты cl *pl, *р2;
pl = &ob1; // Доступ к объекту через указатель
р2 = &ob2;
data = &cl::val; //Определяем смещение переменной val
func = &cl::double_val; // Определяем смещение
// функции double_val()
cout << "Значения: ";
cout << pl ->* data << " " << p2->*data << "\n";
cout << "Удвоенные значения: ";
cout << (pl->*func) << " ";
cout << (p2->*func)() << "\n";
return 0;
}
В этом варианте программы переменные p1 и р2 являются указателями
на объекты класса cl, поэтому для доступа к членам val и double_func ()
применяется оператор "->*".
15

16.

Классы и объекты
ООП
Указатели. Указатели на члены класса
Указатели на члены класса отличаются от указателей на
конкретные элементы объекта.
Рассмотрим фрагмент программы, полагая, что класс cl
объявлен, как показано выше:
int cl::*d;
int *p;
cl o;
p = &o.val
// Адрес конкретной переменной val
d = &cl::val // Смещение обобщенной переменной val
Здесь указатель р ссылается на целочисленную переменную,
принадлежащую конкретному объекту. В то же время
переменная d хранит смещение члена val внутри любого
объекта класса cl.
16

17.

Структуры данных
И+ПРГ
Стек (stack)
Стеки – последовательный список переменной длины, включение и исключение
элементов из которого выполняются только с одной стороны списка, называемого
вершиной стека. Применяются и другие названия стека - магазин и очередь,
функционирующая по принципу LIFO: Last – In, First- Out - "последним пришел первым исключается (выбирается)".
Основные операции над стеком:
- включение нового элемента (английское название push - заталкивать)
- исключение элемента из стека (англ. pop - выскакивать).
Полезными могут быть также вспомогательные операции:
• определение текущего числа элементов в стеке;
• очистка стека;
• неразрушающее чтение элемента из вершины стека, которое может быть реализовано, как
комбинация основных операций: x:=pop(stack); push(stack,x).
Пример: принцип включения
элементов в стек и исключения
элементов из стека.
На рисунке изображены состояния стека:
а) пустого;
б-г) после последовательного включения в
него элементов с именами 'A', 'B', 'C';
д, е) после последовательного удаления из
стека элементов 'C' и 'B';
ж) после включения в стек элемента 'D'.
17

18.

Структуры данных
C / С++
Стек (stack)
Через массив
int stack [MAX];
int tos=0; // вершина стека
int pop (void)
/* Выборка элемента из стека */
tos--;
if (tos < 0)
{
printf (" Стек пуст \n");
return 0;
}
return stack [tos];
}
void push (int i)
/* Занесение элемента в стек */
{
if (tos >= MAX)
{
printf (" Стек полон \n");
return 0;
}
// заносим элемент в стек
stack[tos] = i;
tos++;
return 0;
}
И+ПРГ
Написать головную программу:
1.
2.
3.
4.
5.
Занести в стек 5 элементов
Вывести содержимое стека на экран
Удалить верхний элемент
Удалить второй снизу элемент
Вывести оставшуюся часть стека
18

19.

ООП
Классы и объекты
Работа со СТКЕом в ООП через массив
// продолжение
#include <iostream.h>
into stack :: pop ()
#define SIZE 100
{ if (tos == 0)
// Определение класса stack
{ cout << "Стек пуст.\n"; return 0; }
class stack
tos--;
{
return stck[tos]; }
int stck[SIZE];
int main ()
int tos;
public:
{ stack stack1, stack2; /* создаем 2 объекта
void init ();
класса stack */
void push (int i);
stack1.init (); // Вызов init для объекта
int pop ();
stack1
};
stack2.init (); // Вызов init для объекта
//----------------------------------------------------stack2
void stack :: init () /* :: - оператор разрешения
области видимости (доступ к области видимости). Он
определяет компилятору, что данная версия функции init()
принадлежит классу stack, т.е. находится в области
видимости этого класса */
stack1.push (1);
stack2.push (2);
stack1.push (3);
stack2.push (4);
{ tos = 0; }
//----------------------------------------------------void stack :: push (int i)
cout << stack1.pop() <<
{ if (tos == SIZE)
cout << stack1.pop() <<
{ cout << "Стек полон.\n"; return; }
cout << stack2.pop() <<
stck[tos] = i;
cout << stack2.pop() <<
tos++;
}
// см. продолжение
return 0;
}
" ";
" ";
" ";
" \n ";
Вывод на экран: 3 1 4 2
19

20.

C / С++
ООП
Структуры данных
Работа со СТЕКом в ООП
через указатели и элементы односвязного списка
#include <iostream>
struct stack
// Описание элемента стека
{ int value; struct stack *next; /* значение, следующий элемент */
};
void push(stack* &NEXT, const int VALUE) // Добавить элемент в стек
{ stack *MyStack = new stack;
MyStack->value = VALUE;
MyStack->next = NEXT;
NEXT = MyStack;
}
int pop(stack* &NEXT) // Удалить элемент из стека
{ int temp = NEXT->value;
stack *MyStack = NEXT;
NEXT = NEXT->next;
delete MyStack;
return temp;
}
int main()
{ stack *p=0;
push(p,100);
push(p,200);
cout << pop(p);
cout << pop(p);
return 0;
}
20

21.

Классы и объекты
ООП
Домашние задания на Классы
Индивидуальные домашние задания на Классы С++
21
English     Русский Правила