Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП (С++) Лекции Бобин.doc
Скачиваний:
57
Добавлен:
08.02.2015
Размер:
625.66 Кб
Скачать

Объектно-ориентированное программирование

на языке С++

Глава 1. Объектно-ориентированный подход.

п.1.1. Уровни Абстракции

  1. Машинный язык  Assembler

Недостаток: необходимо переводить свое логическое понимание задачи в термины, понимаемые машиной.

  1. Процедурно-ориентированный подход

Предназначен для описания алгоритмов (последовательности действий). Ключевым моментом является действие, в то время как объект, над которым выполняются действия, пассивен и служит, например, для хранения данных.

  1. Объектно-ориентированный подход

Объект становится активным. ООП предполагает наличие множества объектов, взаимодействующих между собой.

п.1.2. Концепция ООП

  1. Любая моделируемая система состоит из объектов.

  2. Каждый объект характеризуется своим внутренним состоянием и элементами поведения.

  3. Для описания внутреннего состояния объекта используются поля данных (переменные, принадлежащие объекту).

  4. Элементы поведения описываются методами (функциями, которые принадлежат данному объекту).

п.1.3. Понятия объект и класс объектов

Объект– некоторая сущность, обладающая конкретным внутренним состоянием и поведением.

Каждый объект описывает нечто конкретное в реальном мире.

Класс объектов– абстрактное описание, которое объединяет схожие существенные черты разных объектов.

п.1.4. Основные принципы ООП

В ООП заложены 4 принципа:

  1. Абстракция

  2. Инкапсуляция

  3. Наследование

  4. Полиморфизм

п.1.4.1. Абстракция

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

п.1.4.2. Инкапсуляция

Принцип, согласно которому любой класс объектов должен рассматриваться, как «черный ящик» (черным ящиком называется система, принцип действия которой неизвестен или сложен для изучения; черный ящик обладает двумя характеристиками – вход и выход; принцип преобразования неизвестен). Класс должен быть самодостаточен, все поля и методы, присущие рассматриваемым объектам должны регламентироваться в классе объектов.

Достоинства:

  1. Компоненты класса защищены от внешнего вмешательства других программистов.

  2. Разработчик класса в любой момент может изменить его внутренние механизмы, и это не отразится на пользователях класса.

п.1.4.3. Наследование

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

Пример:

От базового класса «фигура» можно создать производные классы «круг» и «квадрат», команду «вычисли свою площадь», но у каждого класса будет своя формула.

п.1.4.4. Полиморфизм

Явление, когда один и тот же программный код выполняется по-разному в зависимости от контекста.

п.1.5. Интерфейс и реализация

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

Совокупность конкретных наборов команд, определяющих поведение класса при выполнении этих действий, называется его реализацией.

ООП предполагает возможность отделения интерфейса от реализации.

П

Определение класса «Источник света».

Фактически класс задает шаблон построения объекта.

ример на С++:

class Light

{

bool isOn;

public:

void turnOn()

{

isOn = true;

}

void turnOff()

{

isOff = false;

}

};

i

Использование класса.

nt main()

{

Light projector;

projector.turnOn();

...

projector.turnOff();

return 0;

}

п.1.6. Основная идея ООП

Если для решения задачи не хватает какого-то типа данных, то создайте его.

Глава 2. Знакомство с С++

п.2.1. Украшение имен (name decoration)

Для обеспечения безопасности кода в процессе компиляции программы на С++ к использующимся в ней именам добавляются специальные префиксы, содержащие дополнительную информацию о принадлежности этих имен к классам. То есть функция func(), расположенная внутри классаexampleclassдолжна отличаться от глобальной функцииfunc(), которая не принадлежит никакому классу. Добавление префиксов – украшение имен.

Пример:

my_program.c

void func(int x)

{

...

}

Компилятор добавлял префикс «_» _func.

Для файла .cppкомпилятор может переназвать функцию, например так:_@1F3fg4$func.

Чтобы принудительно отключить украшение имен (например: вызывается программа на С++ в модуле на языке С) соответствующую функцию или глобальную переменную следует объявить с конструкцией extern «C»:

extern «C» void func(int x);

п.2.2. Особенности функций

п.2.2.1. Пустой список параметров

В Си пустой список параметров при объявлении функции означает, что при ее вызове проверка корректности аргументов не производится, т.е. функцию можно вызвать с любым количеством параметров.

В С++ это означает, что при вызове функции у нее не должно быть фактических параметров.

В С++ void func(void);иvoid func();равносильны.

п.2.2.2. Безымянные параметры

В С++ при определении функции допускается создавать параметры без имени:

void func(int a, int, int b)

{

...

}

При вызове такой функции безымянному параметру должен соответствовать какой-то фактический параметр.

п.2.2.3. Значения аргументов по умолчанию

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

Умалчиваемые значения задаются только при объявлении функции, причем, если какой-то параметр имеет умалчиваемое значение, то все записанные справа от него должны иметь умалчиваемые значения.

void func(int a, int b, int c = 6);

Вызов:

  1. func(1, 2, 3); /* a = 1; b = 2; c = 3; */

  2. func(1, 2); /* a = 1; b = 2; c = 6; */

п.2.2.4. Тип возвращаемого значения

В Си действовало правило:

Если программист не указал тип, то он принимается как int.

Пример:

intfunc(int x)

{

...

}

В С++ тип всегда должен указываться явно.

Не скомпилируется в С++:

void func(const x)

{

...

}

п.2.2.5. Подставляемые функции.

int sum(int a, int b)

{

return a + b;

}

...

x = sum(6, 3);

y = sum(x, 7);

После компиляции этой программы:

0100

push bp

mov bp, sp

...

mov ax, [a]

add ax, [b]

...

ret

0200

push 3

push 6

call 0100

add sp, 4

1)

Но выгодней схема:

2

push ax

mov ax, 6

add ax, 3

)

Если есть простая функция, во многих ситуациях выгодней подставить в точку вызова ее программный код вместо выполнения полноценной подпрограммы.

Достоинства второго способа: экономим на обращении к памяти и отсутствие переходов. Недостаток: раздувается программный код. Для реализации второго способа в С++ используются подставляемые функции.

Чтобы создать подставляемую функцию, ее следует определить с ключевым словом inline:

inline int sum(int a, int b)

{

return a + b;

}

Функция перестает считаться подставляемой, если выполняется хотя бы одно условие:

  1. Отсутствует тело функции;

  2. Внутри функции присутствует хотя бы одна управляющая конструкция (if,for, и т.п.);

  3. В программе явно или косвенно берется адрес функции

  4. Подстановка производится в нескольких программных модулях и теоретически может вызвать ошибку трансляции.

п.2.2.6. Полиморфизм функций (перегрузка)

Суть перегрузки: создание нескольких одноименных функций с разными списками аргументов, причем списки должны отличаться либо количеством либо типом аргументов. С точки зрения С++ имеет место следующая ситуация: одной функции соответствует несколько вариантов ее исполнения, причем нужный вариант определяется конкретным набором фактических параметров.

Пример:

void print(int x)

{

printf(“%d\n”, x);

}

void print(int x, int y)

{

printf(“%d, %d\n”, x, y);

}

void print(float x)

{

printf(“Число: %f\n”, x);

}

int main()

{

print(5); /* 5*/

print(5.5); /* ошибка, т.к. тип – double */

print(6, 7); /*  6, 7  */

return 0;

}

Замечание: Рассмотрим пример программного кода:

void print(int x)

{

printf(“%d\n”, x);

}

void print(int x, int y = 6)

{

prrintf(“%d, %d\n”, x, y);

}

int main

{

print(3);

return 0;

}

Перегрузка может конфликтовать с умалчиваемыми параметрами.

Замечание: Перегрузки по типу возвращаемого значения не существует.

Так делать нельзя:

int print()

{

...

}

float print()

{

...

}

п.2.3. Типы данных

В С++ поддерживаются все встроенные типы данных языка Си: char, short, int, long (+ вариант с unsigned), float, double, longdouble,enum.

В С++ вводится новый тип данных логичесого типа bool, который может принимать 2 значения:true,false.

В С++ существует автоматическое приведение типа между boolиint.

Немного изменены логические операторы (вместо целого числа возвращается типа bool).

п.2.3.1 Указатели и ссылки

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

Пример:

void swap(int *a, int *b)

{

int c;

c = *a;

*a = *b;

*b = c;

}

int main()

{

int x = 6, y = 7;

swap(&x, &y);

return 0;

}

Эквивалентная программа с использованием ссылок:

void swap(int &a, int &b)

{

int c;

c = a;

a = b;

b = c;

}

int main()

{

int x = 6, y = 7;

swap(x, y);

return 0;

}

Ссылка может выступать в роли возвращаемого функцией значения.

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

Пример:

int & func(int &x)

{

return x;

}

Благодаря ссылкам, используемым в качестве возвращаемого функцией значения, вызов функции становится леводопустимым выражением.

int main()

{

...

int a;

func(a) = 6; /* a = 6 */

...

}

Независимая ссылкаопределяется непосредственно в теле функции; ее необходимо сразу же инициализировать.

int main()

{

int x = 6;

int &y = x;

x = 8;

y = 9; /* x = 9 */

return 0;

}

Поскольку ссылка является псевдонимом объекта, то все операции, применимые к ней на самом деле применяются к объекту, на который «смотрит» ссылка.

В программе невозможно получить адрес ссылки; нет понятия «нулевая ссылка».

п.2.4. Заголовочные файлы стандартной библиотеки С++

Заголовочные файлы не имеют расширения.

Заголовочные файлы библиотеки Си сохраняют свои имена, но к ним добавляется префикс «c»:#include <cstdio>.

п.2.5. Пространство имен.

В Си все имена являлись глобальными и действуют во всей программе (либо внутри модуля). В С++ управление доступностью имен более развито. Вводится понятие «пространство имен».

Пространство имен– фрагмент программного кода, внутри которого видны объявленные в нем имена.

Создание пространства имен:

namespace имя

{

...

}

Пространства имен могут быть вложенными.

Чтобы обратиться к компоненту пространства имен, используется оператор разрешения (уточнения) области видимости. Оператор «::».

Пример:

namespace smth

{

int x;

}

int x;

int main()

{

x = 6;

smth::x = 8;

return 0;

}

Оператор «::» имеет ассоциативность слева направо: (groups::m32)::Nezhdanov

В качестве альтернативы компонентам с внутренними связями можно использовать безымянное пространство имен.

int x = 6;

int main()

{

int x = 8;

printf(“%d, %d\n”, x, ::x);

return 0;

}

Средства стандартной библиотеки С++ располагаются в пространстве имен std.

Чтобы указать компилятору на подключение стандартного пространства имен к глобальному, используется ключевое слово using

using namespace ns1;

После написания такой команды все компоненты пространства имен ns1 переходят в глобальное пространство имен, действующее в рамках текущего блока.

namespace ns1

{

int x = 6;

}

void func()

{

using namespace ns1;

x = 6;

}

int main()

{

printf(“%d”, ns1::x);

return 0;

}

Есть возможность подключить к глобальному пространству имен только отдельные компоненты какого-нибудь пространства имен.

namespace ns1

{

int x = 6;

int y = 8;

}

int main()

{

using ns1::x;

printf(“%d, %d”, x, ns1::y);

return 0;

}

using namespace std;

int main()

{

...

}

п.2.6 Создание переменных

В С++ переменную можно создать где угодно:

int main()

{

int x;

scanf(“%d”, &x);

int y = 8;

printf(“%d\n”, x + y);

for(int i = 0; i < 10; ++i)

x += i;

return 0;

}

п.2.7. Правила комментирования

1) /* .........

.......... */

2) // .........

Если необходимо включать или выключать большие фрагменты программного кода, рекомендуется следовать стилю комментирования:

Для отключения:

/*

...

...

...

/**/

Для включения:

//*

...

...

...

/**/

п.2.8. Ввод-вывод в С++

Для работы со стандартным потоком ввода используется переменная «cin». Для работы со стандартным потоком вывода используется переменная «cout». Для работы со стандартным потоком ошибок используется переменная «cerr» и «clog».

Вывод информации в поток:

cout << “Привет!”;

int x = 6;

cout << x;

Можно организовать «конвеер»:

int age = 25;

cout << “мне ” << age << “лет\n”;

Ввод данных из потока:

int x, y;

cin >> x;

cin >> x >> y;

Все эти средства подключаются заголовочным файлом iostream:

#include <iostream>

Глава 3. Классы в С++

п.3.1. Определение классов

ключ_класса имя_класса

{

компоненты_класса

};

Ключ класса задается с помощью одного из следующих ключевых слов: class,struct,union.

Компонентами класса могут быть переменные и функции.

Каждый объект этого класса содержит свой собственный набор этих переменных и функций (т.е. они тиражируются в объектах класса).

Переменные называются полями класса.

Функции называются методами класса.

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

Вывод: класс и тип данных – одно и то же.

Определение класса необходимо только компилятору и в конечной программе не занимает памяти.

п.3.2. Ограничение доступа к компонентам класса

Для того, чтобы гарантировать правильное функционирование класса и предотвратить случайное или умышленное нарушение логики его работы в С++ есть возможность ограничить доступ к компоненту класса.

Все компоненты класса можно разделить на 3 группы:

  1. Общедоступные: доступны в любом месте программы;

  2. Защищенные: доступны только в компонентах одного класса и в его потомках.

  3. Закрытые: доступны только в компонентах данного класса.

Каждая группа начинается с указания ключевого слова с двоеточием; группа заканчивается там, где начинается новая группа или в конце класса.

Пример:

class Point

{

private:

double x, y;

public:

void init(double xi, double yi)

{

x = xi;

y = yi;

}

void print()

{

cout << “x= ” << x << “, y= ” << y;

}

};

По умолчанию действуют следующие правила:

  1. Компонент класса, определенного с ключевым словом ‘class’ считаются закрытыми по умолчанию;

  2. Компонент класса, определенного с ключевым слово ‘struct’ или ‘union’ считаются открытыми.

п.3.3. Создание объектов класса

имя_класса имя_объекта;

Пример:

Point p1;

Point p2, p3;

п.3.4. Обращение к компонентам класса

  1. Полная форма прямого обращения:

имя_объекта.имя_класса::имя_поля;

Пример:

p1.Point::x;

имя_объекта.имя_класса::имя_метода(параметры);

Пример:

p1.Point::init(3, 6);

  1. Сокращенная форма прямого обращения:

имя_объекта.имя_поля;

Пример:

p1.x;

имя_объекта.имя_метода(параметры);

Пример:

p1.init(3, 6);

  1. Формы косвенного обращения:

указатель_на_объект->имя_класса::имя_поля;

указатель_на_объект->имя_класса::имя_метода(параметры);

указатель_на_объект->имя_поля;

указатель_на_объект->имя_метода(параметры);

Пример:

Point *p = &p1;

p->Point::init(3, 6);

p->print();

Пример:

int main()

{

Point p1, p2;

p1.Point::init(1.1, 2.2);

p2.init(3.3, 4.4);

Point *p = &p1;

p->Point::init(0.1, 0.5);

p->print();

Point &r = p2;

r.Point::print();

r.print();

Point arr[10];

arr[3] = *p;

arr[3].print();

return 0;

}

п.3.5. Внешнее и внутреннее определение методов класса

Тело метода класса можно указать как внутри самого класса так и вне его

Внешнееопределение метода:

  1. В теле класса объявлен данный метод

  2. За пределами класса дается определение с указанием полного имени

class Point

{

double x, y;

public:

//объявление метода класса

void init(double, double);

void print();

};

//определение метода класса

void Point::init(double xi, double yi)

{

x = xi;

y = yi;

}

void Point::print()

{

cout << “x = “ << x << “, y =” << y;

}

Методы, определенные внутри класса по умолчанию считаются подставляемыми функциями.

п.3.6. Конструкторы класса.

Имя конструктора должно в точности совпадать с именем класса. Конструктор не может иметь возвращаемого значения. В программе нельзя получить адрес конструктора. Параметром конструктора не может быть объект его класса.

Пример:

c

Конструктор определен внутри класса.

lass Point

{

double x, y;

public:

Point()

{

x = 0.0;

y = 0.0;

}

};

Конструктор объявлен внутри класса.

class Point

{

double x, y;

public:

Point();

};

Point::Point()

{

x = 0.0;

y = 0.0;

}

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

п.3.6.2 Стандартные виды конструкторов

В С++ принято выделять следующие виды конструкторов:

  1. Конструктор умолчаний – конструктор без входных параметров, вызывается при определении объектов класса следующим образом:

имя_класса имя_объекта;

имя_класса имя_объекта = имя класса();

  1. Конструктор копирования – конструктор служит для создания нового объекта класса на базе уже существующего. В качестве параметра ему передается константная ссылка на объект прототип.

Конструктор копирования вызывается при определении объекта с инициализацией.

имя_класса имя_объекта1 = имя_объекта2;

  1. Конструктор общего вида.

Все остальные конструкторы называются конструкторами общего вида.

имя_класса имя_объекта(параметры);

имя_класса имя_объекта = имя_класса(параметры);

Среди конструкторов общего вида важную роль играет конструктор с одним параметром.

имя_класса имя.объекта1 = имя_объекта2; //объекты должны быть из разных классов

Этот конструктор используется в операциях приведения типа и называется конструктором приведения.

Пример: пусть класс создается в виде двух файлов. Первый – заголовочный файл с определением класса, второй – исходный файл, в котором будут даны все реализации (тела методов этих классов).

Point.h

class Point

{

double x, y;

public:

Point();

Point(const Point &other);

Point(double xi, double yi);

Point(double i);

}

Point.cpp

Point::Point()

{

x = 0.0;

y = 0.0;

cout << “Конструктор умолчания\n”;

}

Point::Point(const Point & other)

{

x = other.x;

y = other.y;

cout << “Конструктор копирования”;

}

Point::Point(double xi, double yi)

{

x = xi;

y = yi;

cout << “Конструктор общего вида\n”;

}

Point::Point(double i)

{

x = i;

y = i;

cout << “Конструктор приведения”;

}

main.cpp

int main()

{

Point p1; //умолчаний

Point p2 = Point(3, 6); //общего вида

Point p3(7, 9); //общего вида

Point p4 = p2; //копирования

Point p5 = 5.0; //приведения

Point p6 = 5; //приведения

return 0;

}

При создании объекта p6, число 5 сначала преобразуется из типаintвdouble, а затем с помощью конструктора приведения приводится к классуPoint.

Все операции выполняются компилятором автоматически, таким образом мы сами, не желая того, можем создать в своей программе иерархию автоматического приведения типа (вместо числа 5 может стоять объект любого класса, который за некоторое количество преобразований можно автоматически привести к типу int.

В некоторых случаях такая иерархия автоматического приведения типа является нежелательной, поэтому необходимо ограничить автоматический вызов конструктора приведения. Для этого используется ключевое слово explicit.

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

explicit Point(double);

Point p5 = 5.0; Point p5 = Point(5.0);

Ключевое слово explicitприменяется только к конструкторам и запрещает их автоматический вызов.

п.3.6.3. Автоматически создаваемые конструкторы

Если программист в теле класса явно не объявил ни одного конструктора, то компилятор автоматически создаст их.

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

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

Если программист создал в классе хотя бы один конструктор, то компилятор не будет автоматически генерировать другие конструкторы.

п.3.7. Деструктор класса

Деструктором называется особая компонентная функция класса, которая отвечает за уничтожение объекта. Имя деструктора обязательно должно начинаться с тильды (~), за которой следует имя класса.

Деструктор не может иметь параметров и может быть только один в классе (в отличие от конструктора). Но, также как и конструктор, эта функция не имеет типа возвращаемого значения.

Стандарт С++ гарантирует, что при уничтожении объекта класса до освобождения занимаемой им памяти будет вызван деструктор.

Кроме того допускается явный вызов деструктора.

Деструктор должен обладать возможностью корректной обработки (так как возможна ситуация, при которой деструктор вызывается дважды).

Пример:

class Point

{

double x, y;

public:

Point();

Point(const Point &p);

~Point();

};

...

Point::~Point()

{

}

int main()

{

Point p1;

p1.~Point()

return 0;

}

Перед командой returnпроисходит принудительный вызов деструктора.

! Принципиальное отличие С от С++:

{ (1)

Point x;

(2) }

В С++ при выделении памяти (1) вызывается конструктор умолчаний, а при выходе (2) – деструктор.

п.3.8. Особенности встроенных типов

Встроенные типы в С++ (int,long,short,double, ...) также выступают как классы, то есть имеют фиктивные конструкторы и деструктор, этим можно воспользоваться. В числе прочего, встроенные типы имеют конструктор приведения:

int x;

int x = int();

int x();

double x = 6.0;

int y = int (x);

(как бы предполагается, что имеется int::int(double))