Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++_шпоры(1).DOC
Скачиваний:
7
Добавлен:
08.08.2019
Размер:
200.19 Кб
Скачать

1

Язык программирования С++.

Происхождение С ++.

Язык программирования С++ является объектно - ориентированным. С++ - это расширение языка С. С гибкий и мощный язык, однако в зависимости от проекта программы размером от 25 000 до 100000 операторов оказываются трудными для разработки и управления потому, что их трудно охватывать целиком. Бьярн Страуструп и сотрудник Bell Laboratory Nurray Hill добавили несколько расширений с целью решить эту проблему и назвали язык С с классами. В 1983 г. это название было заменено на С++. Большинство сделанных Страуструпом добавлений поддерживают объектно - ориентированное программирование, которое иногда сокращенно называют ООП. Ряд объектно – ориентированных концепций был добавлен в С++, ориентируясь на язык «Симула – 67». Поэтому С++ представляет собой смесь двух мощных программных методов. С момента возникновения С++ подвергся серьёзным изменениям трижды. Первый раз в 1985г., второй в 1985г., третий пересмотр произошел в связи с работой над стандартом ANSI для С++. Первая версия предложенного стандарта была создана к 25 январю 1994г. В настоящее время работа продолжается и некоторые его характеристики могут быть модифицированы. Версия языка С++, реализованная корпорацией Borland, является достаточно устоявшейся и в общих чертах соответствует текущей форме проекта ANSI для С++. С++ сохраняет философию языка С, включая эффективность и гибкость, а также добавляет эффект объекта. Согласно Страуструпу С++ позволяет добиться ясности, расширяемости и легкости сопровождения за счет структуризации, причем без потери эффективности. Объектно – ориентированные атрибуты языка С++ могут быть эффективно применены к любой задаче программирования (не только для больших программ).

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

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

Объект

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

2

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

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

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

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

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

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

3

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

Без использования классификации каждый элемент должен определять все свои характеристики явным образом. На основе классификации объект нуждается только в определении таких качеств, которые отличают его от других объектов данного класса. Благодаря механизму наследования объект может охарактеризоваться в рамках классификации общего и частного. По терминологии ООП окно, меню, редактор, и т.д. – это классы. Классы образуют (описывают) иерархию, которая и определяет наследование свойств между ними. Так класс меню является производной от класса окно, которое является для него базовым. Сами по себе классы – это не объекты, а шаблоны для создания объектов. При необходимости создается экземпляр класса (объект), который используется в программе. Отношение между классом и экземпляром класса такое же, как между типами данных и переменной. Свойство, называемое многократностью использования, позволяет не только избежать многократного дублирования программных кодов, но и гарантирует, что если функция запрограммирована корректно, то она будет правильно работать со всеми объектами, Входящими в иерархию. В некоторых объектно -

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

Полиморфизм

Объектно – ориентированные языки программирования полиморфизм, который характеризуется фразой: “Один интерфейс, множество методов”, полиморфизм представляет собой атрибут, который позволяет использовать один и тот же интерфейс при реализации целого ряда различных действий. Выбор, того, какое именно действие будет совершено, определяется конкретной ситуацией. Пример полиморфизма – это регулятор температуры. Независимо от типа нагревательного прибора регулятор температуры работает одинаково. Этот принцип применяется и к программированию.

Пример:

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

Благодаря полиморфизму можно задать два набора функций, имеющих одинаковое имя. Например: push – поместить, pop – извлечь по одной для каждого типа данных. Общая концепция (интерфейс) заключается в том, что бы вставлять и извлекать данные из списка. Функции определяют специфические способы, с помощью которых эти операции выполняются для каждого типа данных. Когда данные вставляются в список, автоматически выбирается та

4

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

Объекты и классы.

Объект С++ - это абстрактное описание какой либо сущности. Сущность может быть запись о студенте или экранное окно. Объект включает в себя все данные, которые необходимы, что бы описать сущность, функции или методы, манипулирующие этими данными. Идеальный объект знает о себе все данные, которые необходимы, что бы описать сущность и функции или методы, которые манипулируют этими данными. Идеальный объект знает о себе всё, включая, что как ввести, вывести и обработать хранящиеся в нём данные. В Borland C++ объекты вводятся через понятия классов.

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

Данные члены класса могут иметь любой тип как определённый в С++, например int или doubl, так и определённый пользователем, в том числе другие классы. Функции члены манипулируют данными членами, создают и уничтожают переменный класса и даже переопределяют стандартный операторы С++ для обработки объектов данного класса.

Члены класса

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

ПРИМЕР:

Для описания сферы нужно задать её радиус и координаты центра. Класс, описывающий сферу:

Class sphere

{ public:

float r; // Радиус сферы

float x, y, z : // координаты сферы

данные члены класса сфера r, x, y, z.

Функции члены – это функции , определённые в рамках класса, которые работают с данными членами этого класса. Использование функции членов отличает класс от stract.

5

ПРИМЕР:

Класс сфера содержащая функции членов

Class sphere

{public;

float r; // радиус сферы

float x, y, z;

sphere (float, x coord, float y coord, float z coord, float radius)

{x = x coord ; y = y coord; z = z coord, z = radius;}

~ Sphere () {}

float volume ()

{ return (r*r*r*4*MPI/3);}

float surface ared()

{

return (r*r*r*4*MPI);

}

};

Функции члены добавленные в сферу: sphere, ~ sphere, float volume(), surfaceared().

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

Пример объявления переменной класса сфера: sphere sphere variable;

Конструкторы и деструкторы

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

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

Рекомендации по программированию

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

6

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

Borland C++ выделяет память для переменной, типа класс, когда вы ее создаете.

Пример: программа демонстрирует создание и инициализации конструктора переменной s класса sphere.

#include <iostream.h>

#include <math.h>

void main (void)

{sphere s(1.0, 2.0, 3.0, 4.0);

cout <<”x=”<<s.x

<<”y=”<<s.y

<<”z=”<<s.z

<<”R=”<<s.r

<<”\n””;

}

РЕЗУЛЬТАТЫ РАБОТЫ: x=1, y=2, z=3, R=4

В С++ роль оператора << (сдвиг влево) расширено. Это не только побитовый сдвиг влево, но и оператор вывода данных в случае, если он записан в указанной в примере форме. Стандартным устройством вывода является экран (если не переназначено стандартное устройство).

Формат оператора: cout << data [<< data];

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

Пример:

|

|

|

cout <<”Изучаем С++”;

cout <<”Конспект читаем каждый день!”;

|

|

|

РЕЗУЛЬТАТЫ РАБОТЫ: Изучаем С++ Конспект читаем каждый день (все в одной строке).

Оператор cout не выполняет автоматический возврат каретки, т.е. курсор остается за последним, напечатанным, символом. Последующие операторы cout будут выводить информацию сразу после этого символа. Чтобы избежать этого, необходимо использовать управляющие символьные константы (\n).

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

Формат оператора: cin [>> values];

Где values – переменные, в которые будут помещаться данные.

Использование функции членов

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

7

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

Обобщенная форма доступа: имя_переменной_класса.имя_члена(описание аргументов);

Пример: программа вызывает функции – члены volume и surface_ared для отображения объема и площади поверхности определенной сферы.

#include <iostream.h>

#include <math.h>

(класс Sphere 1)

void main (void)

{sphere s(1.0, 2.0, 3.0, 4.0);

cout <<”x=”<<s.x;

cout <<”y=”<<s.y;

cout <<”z=”<<s.z;

cout <<”R=“ <<s.r<<”\n”;

cout <<”Объем”<<s.volume()<<”\n;

cout <<”Площадь”<<s.surface_ared()<<”\n;

}

РЕЗУЛЬТАТЫ: x=1, y=2, z=3, R=4.

Объем 268.083.

Площадь 201.062.

Встраиваемые функции - члены

Большинство функций – членов можно сделать с заменой вызова или встраиваемыми in – line. В этом случае код функции подставляется вместо ее вызова.

Пример: класс sphere со встраиваемыми функциями – членами volume и surface_area.

class sphere

{public:

float r;

float x, y, z;

sphere (float x coord, float y coord, float z coord, float radius)

{x=xcoord; y=ycoord, z=zcoord, r=radius;}

~sphere (){}

inline float volume()

{return (r*r*r*4*M_PI/3);

}

inline float surface_area();

{returne (r*r*4*M_PI);

}

}

При таком определении класса оператор вывода cout <<”Объем”<<s.volume() <<”\n”;

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

cout <<”Объем”<<(s.r*s.r*s.r*4*M_PI/3)<<”\n”;

Это расширение выполняется автоматически при компиляции программы.

8

Рекомендации по программированию

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

Аргументы функций – членов по умолчанию

Функции – члены могут принимать аргументы по умолчанию.

Пример: пусть в классе sphere принимается, что по умолчанию y=2.0, z=2.5, radius=1.0. Тогда конструктор выглядит следующим образом:

Sphere (float xcoord, float ycoord=2.0, float zcoord=2.5, float radius=1.0)

{x=xcoord; y=ycoord; z=zcoord; r=radius;

}

Тогда можно создать сферу любой из команд: sphere s (1,0); // использование всех возможных аргументов по умолчанию

Sphere t (1.0, 1.1); // явно указывается координата y

Sphere u (1.0, 1.1, 1.2); // явно указываются координаты y и z

Sphere v (1.0, 1.1, 1.2, 1.3); // явно указываются координаты y, z, r; отменяются все параметры по умолчанию.

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

Отличительные особенности конструктора и деструктора

Конструктор:

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

  • может определяться пользователем либо создателем С++ по умолчанию.

  • не может быть вызван явно из пределов программы (не может быть вызван как обычная функция(метод)). Он вызывается явно компилятором при создании объекта и не явно при выполнении оператора new (этот оператор используется рои динамическом распределении памяти под объект) для выделения памяти объекту или при компилировании объекта.

  • имеет всегда тоже имя, что и класс, в котором он определен.

  • никогда не должен возвращать значение (не может быть описан с каким либо типом возврата, даже void). Он всегда вызывается при объявлении экземпляра некоторого класса, то есть при создании объекта.

  • не наследуется.

  • не может быть объявлен как const, virtual, static.

Деструктор:

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

  • не может иметь аргументы или

  • не перегружается.

9

Перегрузка (переназначение) функций

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

Пример:

#include <iostream.h>

//функция sqr_it перегружается 3 раза

long sqr_it (int i)

int sqr_it (double d);

long scr_it (long e);

int main ()

{cout <<sqr_it (10)<<”\n”;

cout <<sqr_it (11.0)<<”\n”;

cout <<sqr_it (9L)<<”\n”;

return 0;

} ,

int sqr_it (int i)

{cout <<”Inside the sqr_it () function that uses”;

cout <<”an integer argument.\n”;

return i*i;

}

double sqr_it (double d)

{cout <<”Inside the sqr_it () function that uses”;

cout <<”a double argument.\n”;

return d*d;

}

long sqr_it (long e)

{cout <<”Inside the sqr_it () function that uses”;

cout <<”a long argument.\n”;

return e*e;

}

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

Как показывает эта программа компилятор знает какую функцию использовать благодаря типу аргумента.

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

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

Пример: стандартная библиотека С++ содержит функции itoa(), etoa(), utoa(), преобразующие числа типа: целые, длинные целые и без знаковые строковые эквиваленты.

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

10

запоминать и работать с каждой функцией отдельно. В противоположность этому в С++ можно использовать одно и то же имя, например num toa(), для каждой из 3 функций. Num toa() выражает общие действия, которые необходимо выполнить. Задача выбора специфической функции возлагается на компилятор. Этот выбор зависит от специфической ситуации. Программисту необходимо запомнить только общие действия, которые необходимо выполнить. Таким образом, применение полиморфизма в данном примере позволяет сократить число используемых функций с трех до одной.

Рекомендации по программированию

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

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

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

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

Например в С++ можно использовать операторы побитового сдвига влево и вправо << >> для выполнения оператора ввода вывода. Это оказывается в озможным благодаря тому, что в заголовочном файле iostream.h эти операторы перегружены. Когда оператор перегружен, то он обретает дополнительное значение в зависимости от своего использования. Тем не менее он сохраняет возможность своего использования в определенном ранее смысле. В общем случае можно перегружать операторы С++ определяя, что они означают применительно к специфическому классу.

Скрытие данных и общий интерфейс

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

11

Видимость членов класса.

Все функции члены (методы) и все данные члены класса, имеющие атрибуты ограничения на доступ, называются видимостью и определяются ключевыми словами: private, protected, public.

В С++ класс определяемый посредством ключевых слов struct, union, class включает в себя функции и данные, создавая новый тип объекта. Для ключевого слова class по умолчанию все компоненты будут private. Это означает, что их имена будут не доступны для использования вне компонентов класса. Ограничение доступа по умолчанию для некоторого компонента можно изменить, записав перед ним атрибут модификации доступа: private, protected, public u(:). Обычно ограничение на уровень доступа в С++ касается элементов данных. Данные имеют атрибут private или protected, а методы public. Смысл доступа следующий:

  • private - член класса с атрибутом private может использоваться только методами собственного класса и функциями «друзьями» этого же класса. По умолчанию все члены класса, объявленного с ключевым словом «класс» имеют атрибут доступа private.

  • protected – это то же самое, что и private, но дополнительно член класса может использоваться методами и функциями «друзьями» производного класса, для которого данный класс является базовым.

  • public – член класса, который может использоваться любой функцией программы, то есть защита на доступ снимается. В С++ элементы класса типа структуры (struct) и объединения (union) в отличие от типа «класс» (по умолчанию private) по умолчанию принимаются как public. Для ключевого слова struct атрибут можно явно переопределять на private или protected. Для ключевого слова union переопределение (явное) атрибута доступа не возможно.

Пример:

class sphere

{ public

float r;

float x, y,z;

sphere (float xcoord, float ycoord, float zcoord, float radius);

~sphere();

inline float volume();

float surfase_area();

}

Пример:

class sphere

{ public

sphere (float xcoord, float ycoord, float zcoord, float radius);

~sphere;

inline float volume();

float surface_area();

privare;

float r;

float x, y, z;

float cube();

float square();

12

}

К частным членам класса имеют доступ только функции – члены этого класса. Любые другие функции к таким данным доступа не имеют, то есть данные скрыты от внешнего мира. В данном примере объявлены как частные функции cube и square, следовательно они не могут быть вызваны за пределами класса sphere. То есть использование видимости необходимо для того, что бы предотвратить прямой доступ к данным класса со стороны функций, не являющихся членами данного класса. В классе могут быть и общие, и частные члены. Общие члены определяют то, как функции, не являющиеся членами этого класса, могут использовать данный класс. Эти члены класса называются интерфейсом класса. Хорошо разработанный интерфейс обычно включает в себя функции – члены, которые выполняют следующие действия:

  • инициализируют переменную типа данного класса.

  • освобождают память, используемую переменной класса

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

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

  • выполняют реальную работу объекта.

Class sphere

{public

sphere (float xcoord, float ycoord, float zcoord, float radius);

~sphere

float volume();

float surface_area();

void set x (float new x);

void set y (float new y);

void set z (float new z);

void set Radius (float new Radius);

float get x();

float get y();

float get z();

float get Radius();

private:

float r;

float x, y, z;

float cube();

float square();

}

В этом классе 12 функций образуют общий интерфейс. Функции volume и surface являются выполнителями реальной работа, для которой классс и был разработан. Функции set x – Radius позволяют функциям, не являющимся членами данного класса управлять данными членами класса (то есть описывать и изменять значения частных переменных).

Рекомендации по программированию

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

13

открытыми облегчает отладку, так как проблемы с манипуляциями данных локализуются в рамках либо функций элементов класса, либо «друзей» класса.

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

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

Пример:

Class sphere {

|

|

public:

friend void frd();

|

|

}

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

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

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

Пример наследования:

Член БГУИР

| \

сотрудник студент

| \

факультет вспомогательный персонал

| \

| преподаватель

администратор

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

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

Class road_vehicle

{

14

int pasengers;

public:

void set_wheels (int num);

int get_weels();

void set_pass (int num);

int get_pass();

} Теперь можно использовать это определение дорожного средства передвижения для того, что бы определить конкретные типы, например приведем класс truck (грузовик).

Определяем класс truck, используя класс «дорожное средство передвижения»:

class truck

public roud_vehicle

{

int cargo;

public:

void set_cargo (int size);

int get_cargo();

void show();

}

Общая форма записи наследования имеет следующий вид:

class имя_нового_класса

//тело нового класса

}

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

В данном примере члены класса truct имеют доступ к функциям - членам класса roud_vechicle, так как если бы функции члены были бы объявлены внутри класса truck, однако функции члены класса truck не имеют доступа к частным членам класса road_vechicle. Наибольшим достоинством наследования служит возможность создания базовой классификации, которая может быть затем включена в конкретные классы. Таким образом каждый объект выражает в точности все свои черты, которые опредеяют его место в классификации.

Множественное наследование

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

Общая форма множественного наследования:

Class имя_порожденного_класса: список базовых классов

{

}

Пример: класс z наследует оба класса x и y. При этом классы x, y и z содержат конструкторы.

#include <iostream.h>

class x{

protected:

int a;

15

public:

x(){

a=10;

cout <<”Инициализация x\n”;

}

};

class y{

protected: int b;

public:

y()

{

cout <<”Инициализация y\n”;

b=20;

}

};

class z : public x, public y

{

public:

z(){

cout <<”Инициализация z\n”;

}

int make_ab() {

return a*b;}

};

int main()

{ z.i;

cout <<i.make_abc);

return 0;

}

Результаты: инициализация x

инициализация y

инициализация z

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

Замещение функций – членов базового класса.

Наследование используется в основном, когда необходимо превратить объект точно такой, как нам нужен. Что бы сделать это, необходимо заместить некоторые функции базового класса. Это позволяет или полностью заменить член базового класса, или использовать функции – члены базового класса, добавив дополнительные, функциональные возможности с помощью новых функций членов. В Borland С++ можно заместить базовые функции – члены, определив новые функции в производном классе. При этом новые функции - члены должны иметь такие же имена, список аргументов и тип возвращаемого значения, как и функции базового класса, которые они замещают. Пример: класс shape содержит новую функцию с именем isa(). Эта функция возвращает

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

16

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

Class shape

{

protected:

float x, y, z;

public:

shape (float xcoord, float ycoord, float zcoord);

~shape();

char * isa();

void set x (float new x);

void set y (float new y);

void set z (float new z);

float get x ();

float get y();

float get z();

};

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

Пример:

class sphere: public shape

{

private:

float r;

float cube();

float square ();

public:

shape (float xcoord, float ycoord, float zcoord, float radius);

~shape();

char *isa();

float volume();

float surfase_area();

void set radius (float new radius);

float get radius();

}

Если создать переменную s объект sphere можно написать такую строку:

cout <<”объект s это” <<s.isa()

<<”порожденный от”

<<”s.shape::isa()<<”\n”;

При выполнении программы, последняя строка выдаст сообщение:

объект s это sphere порожденный от shape.

То есть при вызове isa возвращается строка shape. Однако используя область действия s.shape::isa() мы можем все еще получить доступ к базовым функциям – членам замещенным в производном классе. Это может быть полезным для вызова члена базового класса, когда он делает почти все то, что вы хотите

17

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

Полиморфизм

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

Пример: используя классы shape и sphere можно описать какую – либо переменную следующим образом:

sphere sphere1 (1,2,3,4);

sphere * sphere_ptr;

shape * shape_ptr;

Эту переменную можно использовать например так:

Sphere_ptr = & sphere;

Shape_ptr = & sphere_ptr;

Можно использовать присвоение значения указателя на объект sphere указателю на объект shape без преобразования типов. Это возможно так как любой указатель на объект sphere является указателем на объект shape.

Преимущества использования полиморфизма

  1. Упрощение программ (отпадает необходимость проверки типа объекта с которым мы работаем).

  2. Облегчается процесс включения в программу новых типов объектов.

  3. Упрощается отладка, так как программная реализация особого типа объекта локализуется к методам этого объекта.

Виртуальные функции

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

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

Пример объявления виртуальной функции классе shape:

Class shape

{

protected:

float x,y,z; //координаты фигуры

public:

shape (float x coord, float y coord, float z coord);

virtual ~shape();

virtual char * isa();

void set x (float new x);

void set y (float new y);

void set z (float new z);

float get x();

float get y();

18

float get z();

virtual float volume();

virtual float surface_area(); }

Здесь функции ~shape(), char * isa(), float volume(), float surface_area() объявлены как виртуальные.

Эти функции проявят полиморфное поведение, когда они будут вызваны через указатель на объект shape. Виртуальные функции являются особыми функциями, так как при вызове объекта производного класса с помощью указателя или ссылки на него в С++ определяется во время исполнения программы какую функцию вызвать, основываясь на типе объекта. Для разных объектов вызываются разные версии одной и той же виртуальной функции. Класс, содержащий одну или более виртуальных функций, называется полиморфным классом. Виртуальная функция объявляется в базовом классе с использованием ключевого слова virtual. При ее переопределении в производном классе повторять ключевое слово нет необходимости, хотя и в случае его повторного использования ошибки не возникает.

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

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

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

  • Конструктор не может быть виртуальной функцией, хотя деструктор можно объявить со спецификатором virtual.

Если функция не описана (пропущена) в производном классе, то используется версия базового класса.

Использование виртуальных функций

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

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

Что бы использовать виртуальные функции, описанные в классе shape необходимо создать несколько производных классов от базового.

Пример:

- производный класс sphere

class sphere: public shape

{private:

float r;

float cube();

float square();

public:

sphere (float x coord, float y coord, float z coord, float radius);

virtual ~sphere();

19

virtual char * isa();

virtual float volume();

virtual float surface_area();

void set radius (float new radius);

float get radius();}

  • Производный класс cube

class cube: public shape

{public:

cube (float x coord, float y coord, float z coord);

virtual ~cube();

virtual char * isa();

virtual float vlume;

virtual float surface_area();

}

Здесь замещены все четыре виртуальные функции.

Пусть созданы следующие переменные:

Shape sphere var (1.0, 2.0, 3.0, 4.0);

Shape * shape ptr;

Тогда можно записать такой фрагмент программы:

shape ptr = & sphere var;

cout << “object shape ptr isa”

<< shape ptr isa()

<< “derived from a “

<<shape ptr shape::isa()

<<”\n”;

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

Результат выполнения:

object shape ptr isa sphere

derived from a shape

Чисто виртуальные функции и абстрактные классы

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

Рекомендации по программированию

  1. Вывод предупреждающего сообщения,

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

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

Формат чисто виртуальной функции:

20

Virtual тип имя_функции (список параметров) = 0;

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

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

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

Манипуляция с объектами класса.

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

Так если объявить:

class my – ob {….}

то возможны следующие действия:

void f1 (my – ob p) {….} //передача объектов функции my – ob mas [10]; появление массива из 10 элементов my – ob a, +b;

b=&a; // b является указателем на объект а

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

Если к – компонент объекта а, то после инициализации (b=&a) к нему можно обратиться следующим образом:

b k

Если объявить:

My – ob mas [10], *p;

и присвоить: p=& mas [10];

то (p+1) является указателем на следующий элемент mas [1], то есть выполняется смещение указателя на размер объекта.

Статические члены класса

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

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

21

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

Пример:

#include <iostream.h>

class counter

{

static in count;

public:

void set count (int i)

{count = i;

}; void show count ()

{count <<count <<””l;

}};

int counter :: count; // определение count

int main()

{counter a, b;

  1. show count(); // выводит 0

  2. show count(); // выводит 0

  1. set count(10); // установка статического count в 0

  1. show count(); // выводит 0

  2. show count(); // выводит 0

returne 0;

}

Здесь статическая переменная целого типа count объявляется в двух местах: в классе couner и затем как глобальная переменная.

В Borland C++ инициализируется count нулем. Поэтому первый вызов show count выводит 0. После этого оба объекта а и b выводят с помощью функции showcount одну и ту же величину равную \0, т.е. существует только одна копия count, используемая совместно с объектами a и b, и на экран выводится в обоих случаях \0.

Можно использовать и статистические функции – члены, но они не могут прямо ссылаться на нестатистические функции или нестатистические данные, объявленные в их классе. Причина: отсутствие для них указателя this т.е. нет способа указать с какими именно нестатистическими данными работать. Например, если есть два объекта класса, содержащих статистическую функцию f0, и если f0 пытается получить доступ к нестатической переменной var, определенной этим классом, то как можно определить какую именно переменную (var) нужно использовать. Компилятор не может решить эту проблему В этом и заключается причина того, что статические функции могут обращаться только с другими статическими функциями или статическими данными.

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

22

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

Динамические объекты

Распределение памяти

Память для хранения данных может выделяться статически или динамически.

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

Класс хранения задает место (сигмент данных) где эти данные будут располагаться.

Часто заранее не известно, сколько программа будет хранить объектов. В этом случае для хранения данных может использоваться специальная область памяти, называемая heap (куча). Объем «кучи» и ее местоположения зависят от модели памяти. Функции malloe() и free() библиотеки С, выполняют динамическое выделение и освобождение памяти соответственно.

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

При высокой активности

Работа с помощью оператора new и delete

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

В языке С, для динамического выделения и освобождения памяти используются функции malloc() и free(), а в С++ - new и delete, которые выполняют выделение и освобождение памяти эффективно и просто.

Формат оператора:

new

переменная_указатель = new тип (инициализирующее значение);

delete

delete переменная_указатель;

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

возвращает ее адрес. С помощью new могут быть размещены любые типы данных.

Оператор delete освобождает память на которую указывает переменная указатель.

Если операция выделения памяти не может быть выполнена, то оператор new генерирует исключениями типа xalloc.

Если программа не перехватит это исключение, то она будет снята с выполнения. Хотя для коротких программ такое поведение по умолчанию является удовлетворительным. Для реальных прикладных программ обычно требуется перехватить исключения и обработать его соответствующим образом. Для того, что бы отследить это сообщение необходимо включить дополнительный файл except.h.

Преимущества операторов new и delete

по сравнению с функциями в С.

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

2.Нет необходимости использовать операторы определения размера типа saze of

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

4.Допускается инициализация выделенного блока памяти.

5.Они гарантируют вызов конструкторов и деструкторов.

Пример:

Cirde * A cirde = new Cirde (120,80,50);

Здесь a cride на тип Cride присваивается адрес блока памяти, достаточного для размещения одного объекта типа Cirde.

Пример использования new и delete:

# include <iostream.h>

# include <except.h>

int main ()

{int * p;

try{

p=new int; //выделение памяти для int

} catch (xalloc xa)

{cout <<”Размещение не возможно\n”;

return 1;

}

*p=20; //Присвоение данному участку памяти значение 20.

сout <<*p; //демонстрация работы путем вывода значения

delete p; //освобождение памяти

return o;

}

Эта программа присваивает переменной p адрес блока памяти, имеющего достаточный размер для того, что бы содержать число целого типа. Далее этой памяти присваивается значение 20 и содержимое памяти выводится на экран и наконец динамически выделенная память освобождается.

Пример инициализации памяти с использованием указателя new:

# include <iostream.h>

# include <except.h>

int main()

{int * p;

try {

p = new int (99); //инициализация 99}

}catch (xalloc x){

cout <<”Размещение невозможно\n”;

return 1;

cout <<*p;

delete p;

returne 0;

}

Размещение массивов

С помощью new и delete можно размещать массивы.

Формат:

Переменная_указатель=new тип_переменной [размер];

Где размер определяет число элементов массива.