- •Лекция 1 Создание консольного приложения
- •2. Консоль. Построение консольного проекта
- •3. Запуск приложения
- •4. Сохранение и редактирование проекта
- •Лекция 2
- •4. Функции форматированного ввода и вывода
- •4.1. Функция форматированного ввода с клавиатуры
- •4.2. Функция форматированного вывода на экран
- •5. Математические функции
- •Лекция 3 Линейные вычислительные процессы
- •1. Алгоритм. Управляющие структуры
- •2. Линейные вычислительные алгоритмы
- •2.1. Условный оператор if()
- •2.2. Условное выражение
- •2.3. Оператор выбора switch()
- •Лекция 5 Программирование разветвляющихся вычислительных процессов
- •Лекция 6 Циклические вычислительные процессы.
- •1. Типы циклов
- •3. Операторы безусловного перехода
- •Лекция 7 Вычисление последовательностей
- •4. Примеры вычисления последовательностей
- •5. Структура алгоритмов вычисления рекуррентных последовательностей
- •Лекция 8 Одномерные массивы
- •1. Массивы
- •1.1. Примеры программ обработки одномерных массивов
- •1.2. Сортировка выбором
- •1.3. Сортировка простыми вставками
- •Лекция 10 Двухмерные массивы
- •1. Двухмерные массивы
- •Лекция 11 Алгоритмы матричной алгебры
- •1. Алгоритмы матричной алгебры
- •Лекция 12 Динамические массивы
- •1. Память компьютера. Адресное пространство
- •2. Динамическая память
- •3. Адреса и указатели
- •4. Указатели и массивы. Динамические массивы
- •5. Проблемы, связанные с указателями
- •6. Поразрядные операции
- •1.2. Способы объявления и обращения к элементам двухмерных массивов
- •Лекция 14 Символы и строки
- •1. Символьный тип данных
- •2. Строки
- •Лекция 15 Структуры
- •1. Понятие структуры
- •2. Определение нового имени типа
- •3. Массивы структур. Указатели на структуры
- •3.1. Определение статического массива структур
- •3.1. Определение динамического массива из n структур
- •Лекция 16 Файлы
- •1. Потоковый ввод-вывод данных
- •3. Понятие файла. Функции работы с файлами
- •Лекция 17 Файлы
- •Лекция 18 Функции пользователя
- •I. Приёмы построения алгоритмов
- •2. Понятие функции
- •2.1. Определение функции
- •2.2. Область видимости переменных
- •2.3. Параметры функции
- •2.4. Описание функции
- •2.5. Организация вызова функции
- •2.5. Передача параметров в функцию
- •3. Рекурсия
- •Лекция 20 Нахождение приближенного значения корня нелинейного уравнения
- •На отрезке [a;b] с заданной точностью eps
- •1.1. Метод дихотомии (половинного деления)
- •1.2. Метод хорд
- •1.3. Метод касательных (Ньютона)
- •Лекция 22 Объектно-ориентированное программирование
- •Полиморфизм – это свойство класса, позволяющее определить одно и то же по имени, но разное по смыслу действие. Основные этапы ооп:
- •Уточнённое имя принадлежит классу (т.Е. Компонентной) функции
- •Лекция 23 Объектно-ориентированное программирование
- •1. Конструкторы и деструкторы
- •1.2. Определение компонентных функций
- •Лекция 25 Объектно-ориентированное программирование
- •1. Свойства классов
- •1.1. Наследование классов
- •1.2. Полиморфизм
- •Библиографический список
1.2. Полиморфизм
Полиморфизм - свойство класса, позволяющее использовать одно и то же имя для обозначения различных по сути действий для одного или нескольких классов.
Формы полиморфизма:
-
Перегрузка стандартных функций и стандартных операций.
-
Реализация виртуальных функций.
-
Использование шаблонов.
1.2.1) Перегрузка стандартных операций.
Одной из привлекательных особенностей языка Си++ является возможность распространения действия стандартных операций на операнды, для которых эти операции первоначально в языке не предполагались. Например, если S1 и S2 – символьные строки, то их конкатенацию (соединение) удобно было бы обозначить как S1+ S2. Однако бинарная операция + в обычном контексте языка Си++ предназначена для арифметических операндов и не предусматривает строковых операндов. Никакой возможности распространить действие стандартной операции + на строки в виде символьных массивов или строковых констант в языке Си++ нет. Однако, если определить S1 и S2 как объекты некоторого класса, например, введённого в классе stroka, то для них можно ввести операцию +, выполняемую по таким правилам, которые заранее выбрал программист. Для этих целей язык Си++ позволяет распространить действие любой стандартной операции на новые типы данных - механизм перегрузки стандартных операций.
Чтобы появилась возможность использовать стандартную для языка Си++ операцию (например, «+» или «*») с необычными для неё данными, необходимо специальным образом определить её новое поведение. Это возможно, если хотя бы один из операндов является объектом некоторого класса, то есть введённого пользователем типа. В этом случае применяется механизм, во многом схожий с механизмом определения функций. Для распространения действия операции на новые пользовательские типы данных программист определяет специальную функцию, называемую «операция – функция». Формат определения операции – функции:
<тип возвращаемого значения> operator <знак операции>(<спецификация параметров операции – функции>)
{
<операторы тела операции – функции>
}
При необходимости может добавляться и прототип операции – функции с таким форматом:
<тип возвращаемого значения> operator <знак операции>(<спецификация параметров операции – функции>)
И в прототипе, и в заголовке определения операции – функции используется ключевое слово operator, вслед за которым помещён знак операции.
Например, для распространения действия бинарной операции «*» на объекты класса Т может быть введена функция с заголовком
Т operator * (Тx, Тy)
Определённая таким образом операция (в нашем примере операция «звёздочка») называется перегруженной, а сам механизм – перегрузкой или расширение действия стандартной операций языка Си++.
Помимо операций перегружать можно ещё и функции. Функция считается перегруженной, если сигнатуры и типы возвращаемых результатов нескольких одноимённых функций различны. Функция называется переобъявленной, если она объявляется с именем другой функции, но с другим типом возвращаемого результата и другой сигнатурой. Сигнатурой в функции называется множество, включающее: состав параметров функции, последовательность параметров функции и типы параметров функции.
При вызове таких функций происходит обращение к той функции, у которой список формальных параметров совпадает со списком фактических параметров по смыслу и по содержанию. Ля разработки перегруженных функций описывается несколько функций с одним и тем же именем, но разными сигнатурами.
Рассмотрим конкретный пример по перегрузке стандартных операций.
Описать класс одномерных массивов, содержащих 10 элементов.
Свойства класса – массив;
Методы – ввод-вывод массива;
Перегружаемые операции – сложение массивов, операция присваивания
Опишем класс в головном файле mas.h
class m10
{
int a[10];
public:
void get_a ();
void print ();
m10 operator + (m10 v1);
);
};
void m10 : : get_a ()
{
int i;
for (i=0; i<=10-1; i++)
{ printf (“?”); scanf ( “ %i”, &a[i]); }
}
void m10 : : print ()
{
int i;
for (i=0; i<=10-1; i++)
{ printf (“ %i ”, a[i]); }
}
m10 m10 : : operator + (m10 v1)
{
int i; m10 t;
for (i=0; i<=10-1; i++)
t. a[i]= a[i] + v1. a[i]; // t будет содержать сумму двух массивов
return t;
}
m10 m10 : : operator = (m10 v)
{
int i;
for (i=0; i<=10-1; i++)
a[i] = v. a[i];
return *this;
}
где this – это зарезервированное слово, обозначающее указатель на объект, предшествующий знаку перегружаемой операции. В данном случае «возвращается» *this. Это значение по адресу указателя this, то есть возвращается объект, предшествующий знаку операции.
Программа
#include “mas.h”
int main ()
{
m10 A, B, C;
A.get_a();
B.get_a();
C=A+B;
C.print ();
return 1;
}
1.2.2) Виртуальные функции.
К механизму виртуальных функций обращаются в тех случаях, когда в базовый класс необходимо поместить функцию, которая должна по-разному выполняться в производных классах. Точнее, по-разному должна выполняться не единственная функция из базового класса, а в каждом производном классе требуется свой вариант этой функции.
Например, базовый класс может описывать фигуру на экране без конкретизации её вида, а производные классы (треугольник, эллипс и т.п.) однозначно определяют её формы и размеры. Если в базовом классе ввести функцию для изображения фигуры на экране, то выполнение этой функции будет возможно только для объектов каждого из производных классов, определяющих конкретные изображения.
Классы, включающие такие функции, называются полиморфными.
1.2.3) Классы и шаблоны.
Шаблоны, которые иногда называют родовыми или параметризованными типами, позволяют создавать (конструировать) семейства родственных функций и классов.
Шаблон семейства функций определяет потенциально неограниченное множество родственных функций. Он имеет следующий вид:
template <список параметров шаблона> определение функции
Здесь угловые скобки являются неотъемлемым элементом определения. Список параметров шаблона должен быть заключён именно в угловые скобки.
Аналогично определяется шаблон семейства классов:
template <список параметров шаблона> определение класса
Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов.
Определение шаблона может быть только глобальным.
Рассмотрим векторный класс ( в число данных входит одномерный массив). Какой бы тип ни имели элементы массива (целый, вещественный, с двойной точностью и т.д.), в этом классе должны быть определены одни и те же базовые операции, например доступ к элементу по индексу и т.д. Если тип элементов вектора задавать как параметр шаблона класса, то система будет формировать вектор нужного типа (и соответствующий класс) при каждом определении конкретного объекта.
Следующий шаблон позволяет автоматически формировать классы векторов с указанными свойствами:
// TEMPLATE.VEC - шаблон векторов
template <class T> // Т – параметр шаблона
class Vector
{
T *data; // Начало одномерного массива
int size; // Количество элементов в массиве
public:
Vector (int); // Конструктор класса vector
~Vector () { delete [ ] data; } // Деструктор
// Расширение действия (перегрузка) операции “[ ]”:
T& operator [ ] ( int i ) { return data [i];}
};
// Внешнее определение конструктора класса:
template <class T>
Vector <T>: : Vector (int n)
{
data=new T[n];
size=n;
};
Когда шаблон введён, у программиста появляется возможность определять конкретные объекты конкретных классов, каждый из которых параметрически порождён из шаблона. Формат определения объекта одного из классов, порождаемых шаблоном классов:
<имя параметризованного класса> <фактические параметры шаблона> <имя объекта> (<параметры конструктора>);
В нашем случае определить вектор, имеющий восемь вещественных координат типа double, можно следующим образом:
Vector <double> Z(8);
Проиллюстрируем сказанное следующей программой:
// P11.CPP - формирование классов с помощью шаблона
#include “template.vec” // Шаблон классов «вектор»:
#include <iostream.h>
main ()
{ // Создаём объект класса «целочисленный вектор»:
Vector <int> X (5);
// Создаём объект класса «символьный вектор»:
Vector <char> C (5);
// Определяем компоненты векторов:
for (int i=0; i<5; i++)
{ X[i]=i; C[i]= ‘A’ + i; }
for (int i=0; i<5; i++)
cout << “ “ << X[i] << ‘ ‘ << C[i];
}
Результат выполнения программы:
0 А 1 В 2 С 3 D 4 E
В программе шаблон семейства классов с общим именем Vector используется для формирования двух классов с массивами целого и символьного типов. В соответствии с требованиями синтаксиса имя параметризованного класса, определённое в шаблоне (в примере Vector), используется в программе только с последующим конкретным фактическим параметром (аргументом), заключённым в угловые скобки. Параметром может быть имя стандартного или определённого пользователем типа. В данном примере использованы стандартные типы int и char. Использовать имя Vector без указания фактического параметра шаблона нельзя – никакое умалчиваемое значение при этом не предусматривается.
В списке параметров шаблона могут присутствовать формальные параметры, не определяющие тип, точнее – это параметры, для которых тип фиксирован:
//P12.CPP
#include <iostream.h>
template <class T, int size = 64>
class row
{
T *data;
int length;
public : row ()
{
length = size;
data = new T[size];
}
~ row () { delete [ ] data;}
T& operator { } (int i)
{ return data [i];}
};
void main ()
{
row <float,8> rf;
row <int,8> ri;
for (int i = 0; i<8; i++)
{ rf [i] = i; ri [i] = i * i; }
for (I=0; I<8; i++)
cout << “ “ << rf[i] << ‘ ‘ << ri [i];
}
Результат выполнения программы:
0 0 1 1 2 4 3 9 4 16 5 25 6 36 7 49
В качестве аргумента, замеряющего при обращении к шаблону параметр, взята константа. В общем случае может быть использовано константное выражение, однако выражения, содержащие переменные, использовать в качестве фактических параметров шаблонов нельзя.