Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

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

на языке С++

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

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

1) Машинный язык ® Assembler

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

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

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

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

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

п.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;

}

};

 

Использование класса.  
int 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.

Пример:

int func(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);

 

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

  push bp mov bp, sp ... mov ax, [a] add ax, [b] ... ret   push 3 push 6 call 0100 add sp, 4

1)

 

 

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

push ax mov ax, 6 add ax, 3
2)

 

 

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

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

Чтобы создать подставляемую функцию, ее следует определить с ключевым словом 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, long double, 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);

 

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

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

 

Пример:

p1.x;

 

 

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

 

Пример:

p1.init(3, 6);

 

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

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

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

 

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

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

 

Пример:

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. Конструкторы класса.

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

Пример:

Конструктор определен внутри класса.  
class 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) Конструктор умолчаний – конструктор без входных параметров, вызывается при определении объектов класса следующим образом:

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

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

 

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

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

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

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

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

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

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

 

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

имя_класса имя.объекта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))


Глава 4. Статические компоненты классов

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

В Си для этого использовались глобальные переменные. Но данных подход небезопасен, поскольку существует возможность изменения такой переменной в других частях программы; безопасная альтернатива – статические поля классов.

Статическое поле – поле класса, которое относится ко всему классу в целом и не тиражируется в его объектах.

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

Статические компоненты находятся в контексте класса (т.е. не тиражируются).

Статические поля обладают особенностями:

1) Допускают только внешнее определение;

2) К статическому полю можно обратиться даже не имея объекта класса;

3) На них не распространяется действие спецификатора доступа (public, protected, private).

п.4.1. Создание статических полей

Статическое поле должно быть объявлено в классе с ключевым словом static, и иметь внешнее определение без этого ключевого слова.

Пример:

class Point

{

static int counter;

double x, y;

 

public:

Point();

Point(const Point& p);

~Point();

}

 

//внешнее определение статического поля:

int Point::counter;

 

п.4.2. Обращение к статическому полю

К нему можно обратиться как к обычному полю класса:

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

2) указатель_на_объект->статическое_поле;

 

Учитывая принадлежность статического поля к классу, мы можем обратиться к нему:

3) имя_класса::статическое_поле;

Внутри методов класса к этому полю можно обратиться:

Point::Point()

{

x = 0.0;

y = 0.0;

 

++counter;

}

 

Point::Point(const Point &p)

{

x = px;

y = py;

++counter;

}

 

Point::~Point()

{

--counter;

}

 

п.4.3 Статические методы

По аналогии со статическими полями существуют статические метода (то есть к ним применимы те же свойства)

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

Пример:

class Point

{

static int counter;

double x, y;

 

public:

Point();

Point(const Point &);

~Point();

static void view();

};

 

int Point::counter = 0;

 

...

 

void Point::view()

{

cout << “В программе существует ” << counter << “точек\n”;

}


int main()

{

Point p1, p2;

{

Point p3 = p2;

Point::view(); // ® 3 (точки)

}

p1.view(); // ® 2 (точки)

 

return 0;

}

 

Глава 5. Друзья класса

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

п.5.1. Дружественные функции

Дружественная функция – внешняя по отношению к классу функция (возможно метод другого класса), которая имеет доступ к его закрытым и защищенным компонентам.

Чтобы внешняя функция стала дружественной классу, ее нужно объявить в теле класса с ключевым слово friend.

Замечание: Поскольку дружественная функция не является методом класса, на нее не действуют спецификаторы доступа.

class Point

{

...

 

private:

friend double RadiusVector(Point &p);

};

 

...

 

double RadiusVector(Point &p)

{

return sqrt(p.x*p.x + p.y*p.y);

}

 

____________________________________________

 

 

class Point;

 

class Editor

{

public:

void edit(Point &p);

};


class Point

{

...

friend void Editor::edit(Point &p);

};

 

...

 

void Editor::edit(Point &p)

{

p.x = 8;

p.y = p.x – 6;

}

 

п.5.2. Дружественные классы

Аналогично можно добавить в друзья целый класс. В этом случае все методы дружественного класса становятся дружественными функциями.

friend class Editor;

 

п.5.3. Вложенные классы

В С++ есть понятия вложенный и локальный классы.

1) Вложенный класс (определен внутри определения другого класса)

class outer

{

...

class inner

{

...

};

...

};

 

2) Локальный класс (определен внутри блока, например, функции)

void func()

{

class local

{

...

};

...

}

 

 

На вложенный класс распространяются ограничения доступа внешнего класса.

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

1) Объявить внутренний класс в теле внешнего;

2) Объявить внутренний класс в теле внешнего в качестве дружественного;

3) Определить внутренний класс в теле внешнего;

class outer

{

int x, y;

 

public:

class inner;

friend class inner;

 

outer();

outer(const outer &);

 

class inner

{

int a, b;

public:

inner();

inner(const inner &);

};

};

 

outer::outer()

{

...

}

 

outer::outer(const outer &o)

{

...

}

 

outer::inner::inner()

{

...

}

 

outer::inner::inner(const outer::inner &i)

{

...

}

 

п.5.4. Важное предупреждение!

Концепция дружественности классов – это добровольный отказ программиста от средств автоматического контроля доступа. Эта технология крайне небезопасна и пользоваться ей следует только в крайнем случае.

 

Point::Point(double x, double y)

{

this->x = x;

this->y = y;

}

 

 


Глава 6. Константность в языке С++

п.6.1. Переменная-константа

const тип имя; // Имя пременной не является леводопустимым выражением.

 

п.6.2. Константность в указателях

(1) тип (2) * (3) имя;

 

Необходимо читать данную запись слева-направо.

1) сonst тип * имя;

const int * x;

x – это указатель на const int, то есть указатель на константу.

x = y; - можно

*x = z; - нельзя

 

Можно изменять сам указатель, но нельзя изменять ячейку, на которую он настроен.

2) тип const * имя;

int const * x;

x – это указатель на const типа int.

3) тип * const имя;

int * const x;

x – это константный указатель на ячейку типа int

x = y; - нельзя

*x = z; - можно

 

Нельзя перенастраивать указатель.

4) const тип * const имя;

x – константный указатель на константу

Имя массива является константным указателем на первый элемент.

Пример:

int x;

int *px;

const int *pcx;

 

px = &x;

pcx = &x;

 

x = 5; // можно

*pcx = 5; // нельзя


const int x = 5;

int *px;

const int *pcx;

 

px = &x; // нельзя (открывает возможность изменять ячейки,

// которые запрещено изменять

pcx = & x; // можно

 

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

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

п.6.3. Константы в ссылках

int x = 5;

const int & rx = x;

 

Константная ссылка и ссылка на константу – одно и то же.

x++; // можно

rx++; // нельзя

 

п.6.4. Передача константы как параметра функции

void func(const int x)

{

++x; // нельзя

}

 

вызов функции: (при вызове функции в нее передается копия объекта (в стеке)

 

int a = 5;

const int b = 6;

 

func(a);

func(b);

 

п.6.5. Возврат константы

Возврат константы для встроенных и пользовательских типов данных отличается.  

 


Пример1:

int func1()

{

return 5;

}

 

const int func2()

{

return 5;

}

 

int main()

{

int a1, a2;

const int b1, b2;

 

a1 = func1(); // Все 4 записи

b1 = func1(); // допустимы

 

a2 = func2(); // При работе со встроенными типами данных

b2 = func2(); // не требует точного соответсвия по константности

 

return 0;

}

 

class Point

{

...

};

 

Point func1() // Существенная особенность:

{ // При возврате на пользовательский тип данных

return Point(); // возвращаемое значение и переменная-приемник

} // должны юыть совместимы по константности

 

const Point func2()

{

return Point();

}

 

int main()

{

Point a1, a2;

const Point b1, b2;

 

a1 = func1(); // +

b1 = func1(); // -

 

a2 = func2(); // -

b2 = func2(); // +

 

return 0;

}

 

п.6.6. Временные объекты

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

 
 
(1) Функционально временный объект не отличается от обычного, за исключением того, что он является константой.  


Пример:

class Point

{

public:

void print();

};

 

void f(Print &x)

{

}

 

 

Point g()

{

return Point();

}

 

int main()

{

g().print(); // можно

f(g()); // нельзя (*)

 

return 0;

}

 

(*) Сначала вызывается функция g(), после этого вызывается функция f(), принимающая на вход результат функции g(). Функция g() возвращает объект класса Point, который представляет собой временный объект, подаваемый на вход функции f(). В силу утверждения (1), объект, подаваемый на вход функции f(), является константой.

g()->Point->const Point->f(Point &)

 

п.6.7. Константные поля классов

Обычное поле класса, объявленное с ключевым словом const, играет роль локальной константы, принадлежащей конкретному объекту данного класса. С такой константой обязательно связана конкретная ячейка памяти. Инициализировать можно только 1 раз при создании объекта (в конструкторе).

п.6.8. Список инициализации конструктора

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

имя_конструктора(параметры):список_инициализации

{

тело_конструктора

}

 

имя_компонента(параметры_конструктора_компонента)

Это элемент списка инициализации.

class Int

{

const int minVal;

const int maxVal;

int Val;

 

public:

Int(int v = 0, int min = 0, max = 25);

Int(const Int &other);

...

};

 

Int::Int(int v, int min, int max): val(v), minVal(min), maxVal(max)

{

}

 

 

Int::Int(const Int &other):minVal(other.minVal), maxVal(other.maxVal)

{

val = other.val;

}

 

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

п.6.9. Константы времени компиляции в классах

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

Статическое константное поле должно быть инициализировано непосредственно в теле класса. Оно может быть только целым.

class Stack

{

static const int size = 100;

int data[size];

int count;

 

public:

Stack();

void push(int value);

int pop();

};

 

Stack::Stack():count(0)

{

}

 

void Stack::push(int value)

{

if(count < size)

data[count++] = value;

}

 

int Stack::pop()

{

if(count > 0)

return data[--count];

else

return 0;

}

 

п.6.10. Константные методы класса

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

class Point

{

double x, y;

public:

...

void print();

};

int main()

{

...

const Point cp(0.1, 0.7);

cp.print(); // выдаст ошибку, т.к. для константного объекта

... // вызывается метод, который потенциально может

} // содержать программный код, изменяющий

// состояние объекта.

 

void Point::print()

{

cout << “x= “ << x << endl;

cout << “y= “ << y << endl;

}

 

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

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

Правильно:

void print() const;

...

void Point::print() const;

 

Ни конструкторы, ни деструкторы не могу быть константными методами.

Замечание 1: Перегрузка методов класса может осуществляться не только по списку параметров, но и по константности.

Замечание 2: Техническая реализация:

При вызове константного метода указатель this является указателем на константу.

const имя класса * const this;

...

p.print();

Point::print(&p);

 

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

п.6.11. Ключевое слово mutable

Иногда возникает потребность в изменении состояния объекта константным методом (например, подсчитать количество вызовов данного метода для объекта). Если определить поле класса с ключевым словом mutable, то это поле можно изменять внутри константного метода.

class Point

{

mutable unsigned int counter;

...

};

 

...

void Point::print() const

{

...

counter++;

}

 

 

Глава 7. Перегрузка стандартных операторов

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

п.7.1. Суть перегрузки операторов

y
y
Пусть есть квадрат на плоскости, заданный координатами левого верхнего угла и стороной. Мы хотим передвинуть квадрат (опорную точку на вектор (a; b)).

(x, y)

а

s2
s

x

x

 

class Square

{

double x, y;

double a;

...

};

 

class Vector

{

double alpha;

double beta;

...

};

 

       
 
Написали так: class Square { ... public: ... Square move(const Vector &v); };   Square s(0, 0, 1); Vector v(1, 6); Square s2 = s.move(v);
   
Хотим сделать так: Square s(0, 0, 1); Vector v(1, 6); Square s2 = s + v;
 

 

По своей сути два предложенных варианта являются равносильными, но вариант с использованием знака «+» более компактный и наглядный

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

Конструкцию s + v можно представить в префиксной форме +(s, v), что и делается программистом при перегрузке операторов.

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

п.7.2. Синтаксис перегрузки операторов

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

п.7.2.1. Перегрузка унарного оператора.

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

Предположим, есть класс Point, и мы хотим перегрузить оператор «-», который строит симметричную точку.

Point p, p2;

p2 = -p;

 

Технически это реализуется так:

p2 = p.operator –(); либо:

p2 = operator –(p);

 

Создание:

1)

class Point { public: Point operator –(); };

 

 
 
Point operator –(Point &p) { ... }  


2)


Пример:

class Point

{

double x, y;

 

public:

Point(double = 0.0, double = 0.0); // Комбинированный конструктор

// общего вида и умолчаний

void print() const;

Point operator –() const;

friend point operator +(const Point &); // Глобальная функция

};

 

Point::Point(double x, double y): x(x), y(y)

{

}

 

void Point::print()

{

cout << “(“ << x << “, “ << y << “)” << endl;

}

 

Point Point::operator –() const

{

return Point(-x, -y);

}

 

Point operator +(const Point &p)

{

return Point(fabs(p.x), fabs(p.y));

}

 

int main()

{

Point p1(-1, 1), p2, p3;

 

p2 = -p1; //~~ p2 = p1.operator –();

p3 = +p1; //~~ p3 = operator +(p1);

 

p2.print();

p3.print();

 

return 0;

}

 

п.7.2.2. Перегрузка бинарных операторов

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

Point p1, p2;

double distance;

 

distance = p1 – p2;

 

class Point { ...   double operator –(const Point & r); };   ...   distance = p1.operator –(p2);
Способы:

1)

 

 
 
double operator –(const Point & l, const Point & r);   ...   distance = operator –(p1, p2);


2)

 

 

Пример:

class Point

{

double x, y;

 

public:

Point(double = 0.0, double = 0.0);

void print() const;

double operator –(const Point & r) const;

friend Point operator +(const Point & l, const Point & r);

};

 

double Point::operator –(const Point & r) const

{

return sqrt((x – r.x)*(x – r.x) + (y – r.y)*(y – r.y));

}

 

Point operator +(const Point & l, const Point & r)

{

return Point(l.x + r.x, l.y + r.y);

}

 

int main()

{

Point p1(-1, 1), p2(2, 2), p3;

double distance;

 

distance = p1 – p2;

p3 = p1 + p2;

 

cout << “Distance: “ << distance << endl;

p3.print();

 

return 0;

}

 

 


п.7.2.3. Перегрузка операторов инкремента и декремента

Для операторов инкремента и декремента существует префиксный и постфиксный варианты. Перегрузка обоих операторов в префиксном варианте производится по правилу унарных операторов. Перегрузка в постфиксном варианте производится особым образом: добавляется фиктивный параметр типа int.

class Point

{

double x, y;

 

public:

...

const Point & operator ++();

const Point operator ++(int);

 

friend const Point & operator -–(Point &);

friend const Point operator -–(Point &, int);

};

 

const Point & Point::operator ++()

{

++x;

++y;

 

return *this;

}

 

const Point Point::operator ++(int)

{

Point old = *this;

 

++x;

++y;

 

return old;

}

 

const Point operator –(Point & p)

{

--p.x;

--p.y;

 

return p;

}

 

const Point operator –-(Point & p, int)

{

Point old = p;

 

--p.x;

--p.y;

 

return old;

}

 


int main()

{

...

 

Point A(1, 1), B(2, 2);

 

cout << (A – B) << endl;

cout << (A – (++B)) << endl;

cout << (A – (B++)) << endl; // A.operator – (B.operator ++(0));

cout << (A – (--B)) << endl;

cout << (A – (B--)) << endl;

 

return 0;

}

 

 

п.7.3. Особенности перегрузки операторов

1) Операторы присваивания (=), индексирования ([]) и косвенного обращения (->) к компоненту можно перегружать только в виде компонентных функций.

2) Если в классе перегружен оператор ‘>’ и оператор ‘=’, то это не значит, что будет перегружен составной оператор ‘>=’. При необходимости составной оператор нужно перегружать отдельно.

Общая схема перегрузки операторов
Выражение Метод Глобальная функция
@x x.operator@() operator@(x)
x@y x.operator@(y) operator@(x, y)
x@ x.operator@(0) operator@(x, 0)
x = y x.operator=(y)  
x[y] x.operator[](y)  
x->y x.operator->(y)  
x(y, z) x.operator()(y, z) operator()(x, y, z)

3)

 

4) При перегрузке операторов НЕЛЬЗЯ:

a) создавать новые операторы;

b) изменять приоритеты операторов;

c) изменять синтаксис оператора

d) перегружать операторы для стандартных типо

e) перегружать операторы:

- разрешения области видимости «::»

- прямого доступа «.»

- прямого доступа с разыменованием «. *»

- тернарный оператор «?:»

 


п.7.4. Оператор приведения

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

Пример:

class Point

{

...

 

public:

Point(double = 0.0, double = 0.0);

operator double() const; // преобразование «точки» в действит. число

};

 

Point::operator double() const

{

return sqrt(x*x + y*y);

}

 

int main()

{

Point A(1, 1);

doubre rv = A; // rv = (double) A;

 

cout << “Длина радиус-вектора: “ << rv << endl;

 

return 0;

}

 

Производится автоматическое преобразование типа «Point» в тип «double».

Замечание: Вместо «double» может быть указан любой тип, даже сложный, например: operator const double * ().

 


Глава 8. Классы ресурсоемких объектов.

п.8.1. Понятие ресурсоемкого объекта

Ресурсоемкий объект – объект, который в процессе своего функционирования осуществляется ручное управление внешними ресурсами (область динамической памяти, файл на диске, сетевой порт, другая программа и т.д.). Наиболее проблемными операциями являются процедуры выделения и освобождения внешних ресурсов.

п.8.2. Автоматически создаваемые компоненты класса

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

1) конструктор умолчаний;

2) конструктор копирования;

3) операторная функция присваивания;

4) деструктор.

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

Это не всегда соответсвует требованиям к классам ресурсоемких объектов.

 


Пример:

class Smth

{

int *p;

 

...

};

...

 

Smth s;

 

s.p =?

 

Автоматически создаваемый конструктор копирования последовательно вызывает конструкторы копирования для всех компонентов класса. При это копия внешнего ресурса не создается.

Пример:

Smth t = s;

 

У вновь созданного объекта указатель «смотрит» на тот ж

<== предыдущая лекция | следующая лекция ==>
Організація наукової діяльності в Україні | Сущность предпринимательства
Поделиться с друзьями:


Дата добавления: 2014-01-07; Просмотров: 637; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 1.372 сек.