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

Содержание

Слайд 2

Массивы Классы и объекты ООП Если в классе определен конструктор с

Массивы

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

ООП

Если в классе определен конструктор с параметрами, каждый объект

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

#include
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 вызывается явно. Разумеется, короткая форма инициализации используется чаще. В основе этого способа лежит возможность автоматического преобразования типа, если конструктор имеет только один аргумент. Таким образом, сокращенную форму инициализации можно использовать, только если массив состоит из объектов, конструкторы которых имеют лишь один аргумент.

Слайд 3

Массивы Классы и объекты ООП Если конструктор объекта имеет несколько аргументов,

Массивы

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

ООП

Если конструктор объекта имеет несколько аргументов, следует применять полную

форму инициализации. Рассмотрим пример:

#include
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 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; }

Здесь конструктор массива cl имеет два аргумента. Следовательно, следует применять не сокращенную, а полную форму инициализации.

Слайд 4

Массивы Классы и объекты ООП Инициализированные и неинициализированные массивы class cl

Массивы

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

ООП

Инициализированные и неинициализированные массивы

class cl {
int i ;

public:
cl (int j) // Конструктор
{ i=j; }
int get_i() { return i; }
};

cl а[9]; // Ошибка, конструктору необходим список инициализации
Этот оператор не работает, поскольку предполагается, что класс cl не имеет параметров, так как в объявлении массива не указан список инициализации. Однако класс cl не содержит конструкторов, не имеющих параметров. Итак, поскольку данному объявлению не соответствует ни один конструктор, компилятор выдаст сообщение об ошибке. Чтобы решить эту проблему, необходимо перегрузить конструктор, добавив вариант, не имеющий параметров, как показано ниже. Теперь в программе можно объявить оба вида массива:

Особая ситуация возникает, когда необходимо создать как инициализированные, так и неинициализированные массивы объектов. Рассмотрим следующий класс:

Здесь в классе cl определен конструктор с одним параметром. Это значит, что любой массив такого типа должен быть инициализирован.
Т.о. массив нельзя объявить обычным образом:

class cl {
int i; public:
cl() {i=0;} // для неинициализированных массивов
cl(int j) { i=j; } // для инициализированных массивов
int get_i() { return i; }
};

В таком варианте программы допускаются следующие операторы:
// Инициализированный массив
cl a1[3] = {3, 5, 6};
// Неинициализированный массив
cl а2[34];

Слайд 5

Массивы Классы и объекты ООП Пример: создание двумерного массива объектов и

Массивы

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

ООП

Пример: создание двумерного массива объектов и инициализация его:

//

Создание двумерного массива объектов
#include
using namespace std;
class samp {
int a; public:
samp(int n) { a = n; }
int get_a() { return a; }
};
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;
}

программа выводит на экран:
1 2
3 4
5 6
7 8

Слайд 6

Указатели. Указатели на объекты Классы и объекты ООП #include using namespace

Указатели. Указатели на объекты

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

ООП

#include
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;
}

Указатели могут ссылаться не только на переменные встроенных типов, но и на объекты. Для доступа к членам класса через указатель на объект используется оператор "->", а не ".".
Пример:

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

Слайд 7

Указатели. Указатели на объекты Классы и объекты ООП #include using namespace

Указатели. Указатели на объекты

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

ООП

#include
using namespace

std;
class cl {
int i ; public:
cl() { i=0; } cl(int j) { i=j; }
int get_i() { return i; }
};
int main()
{
cl ob[3] = {1, 2, 3}; cl *p; int i ;
p = ob; // Установить указатель на первый элемент массива
for(i=0; i<3; i++)
{ cout << p->get_i() << "\n";
p++; // Указатель на следующий объект
}
return 0;
}

Пример: присваивание указателю адреса открытого члена объекта и ссылка на этот член с его помощью.

#include
using namespace std;
class cl
{ public: int i ;
cl(int j) { i=j; }
};
int main() {
cl ob(1); int *p;
р = &ob.i; // Получить адрес члена ob.i
cout << *р; // Обращение к члену ob.i // через указатель р
return 0;
}
Указатель р ссылается на целое число, он имеет тип int. В данном случае не имеет значения, что переменная i является членом объекта ob.

Пример: использование указателя для доступа ко всем трем элементам массива ob.

Слайд 8

Указатели. Проверка типа указателей Классы и объекты ООП С++ содержит специальный

Указатели. Проверка типа указателей

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

ООП

С++ содержит специальный указатель this.

Это указатель, который автоматически передается любой функции-члену при ее вызове и указывает на объект, генерирующий вызов.
Например:
ob fl(); // предположим, что ob ‒ это объект, функции fl() автоматически передается указатель на объект ob. Этот указатель и называется this.
Важно понимать, что указатель this передается только функциям-членам. Дружественным функциям указатель this не передается.

Работая с указателями, следует иметь в виду: присваивать можно лишь указатели совместимых типов.
int *pi;
float *pf;
Следующий оператор неверен:
pi = pf; // Ошибка ‒ несовместимость типов.
Разумеется, любую несовместимость можно преодолеть, используя приведение типов, однако это нарушает принципы строгой проверки типов.

Указатель this

Слайд 9

Указатели. Указатель this Классы и объекты ООП При вызове функции-члена ей

Указатели. Указатель this

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

ООП

При вызове функции-члена ей неявно

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

#include
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.

Слайд 10

Указатели. Указатель this Классы и объекты ООП На самом деле обычно

Указатели. Указатель this

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

ООП

На самом деле обычно конструктор

таким образом не пишут, поскольку сокращенная форма намного проще. Однако указатель this очень важен при перегрузке операторов, а также в ситуациях, когда функция-член должна использовать указатель на вызывающий объект.
Следует помнить, что указатель this автоматически передается всем функциям-членам. Следовательно, функцию get_pwr() можно переписать иначе:
double get_pwr() { return this->val; }
В этом случае, если функция get_pwr() вызывается с помощью оператора
y.get_pwr () ;
указатель 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;
}

Слайд 11

Указатели. Указатели на производные типы Классы и объекты ООП Как правило,

Указатели. Указатели на производные типы

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

ООП

Как правило, указатель одного

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

Указатели. Указатели на производные типы Классы и объекты ООП Рассмотрим программу:

Указатели. Указатели на производные типы

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

ООП

Рассмотрим программу: применение указателя

на базовый класс для доступа к объектам производного класса:

#include
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;
}

Как видим, указатель на базовый класс позволяет обращаться к объекту производного класса.

Слайд 13

Указатели. Указатели на члены класса Классы и объекты ООП В языке

Указатели. Указатели на члены класса

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

ООП

В языке С++ существует

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

Указатели. Указатели на члены класса Классы и объекты ООП Пример: #include

Указатели. Указатели на члены класса

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

ООП

Пример:
#include
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, являющуюся указателем на член класса. Обратите внимание на то, что для правильного выполнения оператора ".*" необходимы дополнительные скобки.

Слайд 15

Указатели. Указатели на члены класса Классы и объекты ООП #include using

Указатели. Указатели на члены класса

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

ООП


#include
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 () применяется оператор "->*".

Слайд 16

Указатели. Указатели на члены класса Классы и объекты ООП Указатели на

Указатели. Указатели на члены класса

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

ООП

Указатели на члены класса

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

Структуры данных Стек (stack) Стеки – последовательный список переменной длины, включение

Структуры данных

Стек (stack)

Стеки – последовательный список переменной длины, включение и

исключение элементов из которого выполняются только с одной стороны списка, называемого вершиной стека. Применяются и другие названия стека - магазин и очередь, функционирующая по принципу LIFO: Last – In, First- Out - "последним пришел - первым исключается (выбирается)".

Пример: принцип включения элементов в стек и исключения элементов из стека.
На рисунке изображены состояния стека:
а) пустого;
б-г) после последовательного включения в него элементов с именами 'A', 'B', 'C';
д, е) после последовательного удаления из стека элементов 'C' и 'B';
ж) после включения в стек элемента 'D'.

Основные операции над стеком:
- включение нового элемента (английское название push - заталкивать)
- исключение элемента из стека (англ. pop - выскакивать).
Полезными могут быть также вспомогательные операции:
определение текущего числа элементов в стеке;
очистка стека;
неразрушающее чтение элемента из вершины стека, которое может быть реализовано, как комбинация основных операций: x:=pop(stack); push(stack,x).

И+ПРГ

Слайд 18

Структуры данных C / С++ Стек (stack) int stack [MAX]; int

Структуры данных

C / С++

Стек (stack)

int stack [MAX];
int tos=0; //

вершина стека
void push (int i)
/* Занесение элемента в стек */
{
if (tos >= MAX)
{
printf (" Стек полон \n");
return 0;
}
// заносим элемент в стек
stack[tos] = i;
tos++;
return 0;
}

Через массив

И+ПРГ

int pop (void)
/* Выборка элемента из стека */
tos--;
if (tos < 0)
{
printf (" Стек пуст \n");
return 0;
}
return stack [tos];
}

Написать головную программу:
Занести в стек 5 элементов
Вывести содержимое стека на экран
Удалить верхний элемент
Удалить второй снизу элемент
Вывести оставшуюся часть стека

Слайд 19

Классы и объекты #include #define SIZE 100 // Определение класса stack

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

#include
#define SIZE 100
// Определение класса stack
class stack
{
int

stck[SIZE];
int tos;
public:
void init ();
void push (int i);
int pop ();
};
//-----------------------------------------------------
void stack :: init () /* :: - оператор разрешения области видимости (доступ к области видимости). Он определяет компилятору, что данная версия функции init() принадлежит классу stack, т.е. находится в области видимости этого класса */
{ tos = 0; }
//-----------------------------------------------------
void stack :: push (int i)
{ if (tos == SIZE)
{ cout << "Стек полон.\n"; return; }
stck[tos] = i;
tos++;
} // см. продолжение

// продолжение
into stack :: pop ()
{ if (tos == 0)
{ cout << "Стек пуст.\n"; return 0; }
tos--;
return stck[tos]; }
int main ()
{ stack stack1, stack2; /* создаем 2 объекта класса stack */
stack1.init (); // Вызов init для объекта stack1
stack2.init (); // Вызов init для объекта stack2
stack1.push (1);
stack2.push (2);
stack1.push (3);
stack2.push (4);
cout << stack1.pop() << " ";
cout << stack1.pop() << " ";
cout << stack2.pop() << " ";
cout << stack2.pop() << " \n ";
return 0;
}

Работа со СТКЕом в ООП через массив

Вывод на экран: 3 1 4 2

ООП

Слайд 20

#include struct stack // Описание элемента стека { int value; struct

#include
  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;
}

ООП

Работа со СТЕКом в ООП
через указатели и элементы односвязного списка

Структуры данных

C / С++