4599

Основы объектно-ориентированного программирования. Конспект лекций

Конспект

Информатика, кибернетика и программирование

Основы объектно-ориентированного программирования Введение Язык С++ был создан как объектно-ориентированное продолжение одного из самых популярных в мире языков для разработки коммерческих программ. Язык С был разработан как нечто среднее между язык...

Русский

2012-11-23

294.5 KB

8 чел.

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

Введение

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

Однако такие языки, как Smalltalk и CLU, проложили новое направление – ориентацию на объекты, что позволило объединить содержащиеся в структурах данные с процедурами, способными их обрабатывать. Такой единый блок называется объектом (object).

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

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

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

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

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

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

Объектно-ориетированные языки основаны на «трех китах»: инкапсуляции, наследовании и полиморфизме.

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

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

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

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

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

Функции в классе обычно выполняют действия над переменными-членами. Они называются функциями-членами, или методами класса. К методам класса Car можно отнести Start() (разгоняться) и Break() (тормозить). Класс Cat (кот) может иметь такие данные-члены, как Age, Weight (возраст и вес), а его методами могут быть Sleep(), Meow() и ChaseMice() (спать, мяукать и ловить мышей). Подобно переменным-членам, функции-члены являются составной частью класса. Именно они определяют, что данный класс может сделать.

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

Class Cat

{

unsigned int itsAge;

unsigned int itsWeight;

void Meow();

};

При объявлении класса Cat память не резервируется. Это объявление просто сообщает компилятору о существовании класса Cat, о том, какие данные он содержит (переменные itsAge и itsWeight), а также о том, что он умеет делать (метод Meow)). Кроме того, объявление сообщает компилятору о размере класса Cat, т.е. сколько места должен зарезервировать компилятор для каждого объекта класса Cat. Поскольку в приведенном примере для целого значения требуются четыре байта, размер объекта Cat составит восемь байтов (четыре байта для переменной itsAge и четыре – для itsWeight). Метод Meow() не требует выделения памяти, поскольку для функций-членов (методов) объекта пространство в памяти не резервируется.

Объект нового типа определяется точно так же, как и любая целочисленная переменная:

unsigned int GrossWeight;

Cat Frisky;

В этом коде определяется переменная GrossWeight, которая имеет тип unsigned int, а также определяется объект Frisky класса (или типа) Cat.

Кот – это разновидность домашнего животного, но никто не заводит дома разновидность, обычно покупают конкретного живого котенка. То есть, существует различие между абстрактным котом как понятием и конкретным котенком Фриски. Точно так же в языке С++ существует различие между классом Cat, который является концепцией кота, и каждым конкретным объектом класса Cat, который гуляет сам по себе. Таким образом, Frisky – это объект типа Cat, точно так же, как и GrossWeight является переменной типа unsigned int.

Объект – это конкретный экземпляр абстрактного класса. После определения реального объекта класса Cat, например Cat Frisky, возникает необходимость получить доступ к членам этого объекта. Для этого используется точечный оператор (.), который позволяет обратиться к элементам объекта непосредственно. Следовательно, чтобы присвоить число 50 переменной-члену Weight объекта Frisky, можно написать:

Frisky.itsWeight=50;

Аналогично для вызова метода Meow() достаточно использовать следующую запись:

Frisky.Meow();

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

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

int =5; // неверно!!!

Компилятор пометит эту строку как ошибочную, поскольку нельзя присваивать число 5 типу int. Вместо этого нужно определить целочисленную переменную и присвоить ей число 5. Например:

int x;  

x=5;  

Точно также недопустима следующая строка:

Cat.itsAge=5 //  неверно!!!

Компилятор снова пометит эту строку как ошибочную, поскольку нельзя присвоить число 5 части класса Cat, ведь это всего лишь декларация. Сначала необходимо создать объект класса Cat, а затем его переменной itsAge присвоить значение 5. Например:

Cat Fricky;  // то же, что и int x;

Frisky.itsAge=5; // то же, что и x=5;

  1.  Закрытые и открытые члены класса

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

Class Cat

{

unsigned int itsAge;

unsigned int itsWeight;

void Meow();

};

Здесь объявлены закрытые переменные itsAge и itsWeight, а также закрытый метод Meow(), поскольку по умолчанию все члены класса являются закрытыми. Это значит, что если член класса не указан как открытый явно, то он считается закрытым. Например, если в функции main() написать нижеследующее, то компилятор пометит эту строку как ошибку:

int main()

{

Cat Boots;

Boots.itsAge=5; /* Ошибка! Нельзя обращаться к закрытым данным!*/

Чтобы разрешить доступ извне к переменным-членам объектов созданных на базе класса Cat, необходимо сделать их открытыми:

Class Cat

{

public:

unsigned int itsAge;

unsigned int itsWeight;

void Meow();

};

Теперь все члены itsAge, itsWeight и Meow() стали открытыми, а строка Boots.itsAge=5; больше не вызывает у компилятора проблем.

В объявлении ключевое слово public применяется ко всем расположенным ниже членам до тех пор, пока не встретится ключевое слово private, и наоборот. Согласно общей стратегии использования классов, его переменные-члены следует оставлять закрытыми. Благодаря этому обеспечивается инкапсуляция (скрытность) данных внутри класса. Следовательно, чтобы передавать и возвращать значения закрытых переменных, необходимо создать открытые функции, известные как методы доступа (accessor method).

Открытый метод доступа – это функция-член класса, предназначенная для установки и получения значений закрытых переменных-членов класса.

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

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

Объявление методов или данных закрытыми позволяет компилятору обнаруживать в программе ошибки прежде, чем они станут причиной неприятностей. Закрытые переменные не гарантируют «секретности» данных, поскольку любой достаточно опытный программист при желании сможет обойти запрет и получить к ним доступ. По этому поводу Страуструп (Stroustrup), автор языка С++, сказал: «Механизмы управления доступом в С++ обеспечивают защиту от несчастного случая, но не от мошенника».

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

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

Листинг 11.1. Реализация методов класса

#include <iostream>

class Cat {   //начало объявления класса

public:

 int GetAge() const;

void SetAge(int age);

void Meow();

private:

int itsAge;

};

//Реализация открытой функции доступа GetAge()

//возвращающей значение элемента itsAge

int Cat::GetAge() const {

 return itsAge;

}

//Реализация открытой функции доступа SetAge()

//устанавливающей значение элемента itsAge

void Cat::SetAge(int age) {

 itsAge=age;

}

//Реализация метода Meow()

void Cat::Meow() {

std::cout<<"Meow.\n";

}

int main()

{

Cat Frisky;

Frisky.SetAge(5);

Frisky.Meow();

std::cout<<"Frisky is a cat who is ";

std::cout<<Frisky.GetAge()<<" years old.\n";

Frisky.Meow();

return 0;

}

__________________________________________________________________

РЕЗУЛЬТАТ

Meow.

Frisky is a cat who is 5 years old.

Meow.

АНАЛИЗ

Метод GetAge() не принимает никаких аргументов и возвращает целочисленное значение. Реализация функции GetAge() занимает только одну строку, в которой она возвращает значение переменной-члена itsAge. Функция main() не может обратиться к этой переменной, поскольку она объявлена закрытой. Но из функции main() можно обратиться к открытому методу GetAge(), а поскольку он является функцией-членом класса Cat, то имеет все права доступа к переменной-члену itsAge. В результате функция GetAge() возвращает значение переменной itsAge в функцию main().

Функция-член SetAge() получает целочисленный параметр и присваивает его переменной itsAge. Являясь членом класса Cat, функция SetAge() имеет прямой доступ к переменной-члену itsAge.

Как можно заметить метод Meow() объявлен точно так же, как и функции доступа.

В теле основной программы main() переменной-члену itsAge с помощью метода доступа SetAge() присваивается значение 5. Обратите внимание, что при вызове метода указывается имя объекта Frisky, за которым следуют оператор прямого доступа (.) и имя самого метода SetAge(). Таким же образом можно вызывать и другие методы класса.

Если объявить метод класса как const (константа), то он не сможет изменить значение ни одного члена класса. Чтобы объявить метод класса как постоянный, нужно поместить ключевое слово const после круглых скобок, но перед точкой с запятой. Например, объявление постоянной функции-члена SomeFunction(), не получающей никаких аргументов и ничего не возвращающей, выглядит так:  void SomeFunction() const;

Класс  имеет две функции доступа SetAge() и GetAge(). Функция  SetAge() не может быть объявлена как постоянная, поскольку она изменяет значение переменной члена itsAge. А вот функция GetAge() вполне может и даже должна быть объявлена как постоянная, поскольку она ничего не изменяет в классе. Использовать ключевое слово const в объявлениях методов, не изменяющих объект, считается хорошим тоном в программировании.

Лекция № 12. КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ

  1.  Определение

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

int Weight;

Weight = 7;

Или, определив переменную, можно сразу инициализировать ее, например:

int Weight = 7;

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

Как же инициализировать переменные-члены класса? Для этого в классе используется специальная функция-член, называемая конструктором (constructor). При необходимости конструктор может получать параметры, но не может возвращать значения, даже типа void. Конструктор – это метод класса, имя которого совпадает с именем самого класса.

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

~Cat();

Доступны несколько типов конструкторов, одним из них передают параметры, другим – нет. Те, которые аргументов не получают, называются стандартными (default). Однако деструктор может быть только один. Никакие аргументы он не принимает.

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

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

Например, если для создания объекта класса Cat следует передать два параметра, то его определение будет выглядеть следующим образом:

Cat Frisky (5, 7);

Первым параметром в этом примере мог бы быть возраст кота, а вторым – его вес. Допустим, вместо предыдущей записи мы напишем:

Cat Frisky;

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

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

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

Листинг 12.1. Использование конструкторов и деструкторов

1: #include <iostream>  //для cout

2:

3: class Cat    //начало объявления класса

4: {

5:      public:    //начало раздела public

6:  Cat(int initialAge); //конструктор

7:  ~Cat();    //деструктор

8:  int GetAge() const; //функция доступа

9:  void SetAge(int age);//функция доступа

10:  void Meow();   //обычная функция

11:      private:    //начало раздела private

12:  int itsAge;   //переменные-члены

13: };

14:

15: // Конструктор класса Cat

16: Cat::Cat(int initialAge)

17: {

18:  itsAge = initialAge;

19: }

20:

21: Cat:: ~Cat()   //деструктор, не делает ничего

22: {

23: }

24: // Реализация открытой функции доступа GetAge(),

25: // возвращающей значение элемента itsAge

26: int Cat::GetAge() const   

27: {

28: return itsAge;

29: }

30:

31: // Реализация открытой функции

32: // доступа SetAge, устанавливающей значение

33: // элемента itsAge

34: void Cat::SetAge (int age)

35: {

36:  // присвоить переменной-члену itsAge значение,

37:  // переданное через параметр age

38:  itsAge = age;

39: }

40: // Реализация метода Meow()

41: // Возвращает: ничего (void)

42: // Параметры: нет

43: // Действия: выводит на экран «Мяу» (Meow)

44: void Cat::Meow()

45: {

46:  std::cout << “Meow.\n”;

47: }

48:

49: // Создать кота, установить его возраст, мяукнуть,

50: // сообщить его возраст, затем мяукнуть снова.

51: int main()

52: {

53:  Cat Frisky (5);

54:   Frisky.Meow();

55:  std::cout << “Frisky is a cat who is “;

56:  std::cout<<Frisky.GetAge()<< “ years old.\n”;

57:  Frisky.Meow();

58:  Frisky.SetAge(7);

59:  std::cout << “Now Frisky is “;

60:  std::cout<<Frisky.GetAge()<< “ years old.\n”;

61:  return 0;   

62: }

__________________________________________________________________

РЕЗУЛЬТАТ

Meow.

Frisky is a cat who is 5 years old.

Meow.

Now Frisky is 7 years old.

  1.  Файлы заголовков

Объявления классов можно поместить в один файл с программой, но это не считается хорошим тоном. В соглашении, которого придерживаются многие программисты, принято помещать объявления в файл заголовка (header file), имя которого обычно совпадает с именем файла программы и имеет расширение .h, .hp  или .hpp.

Например, можно поместить объявление класса Cat в файл cf2.h, а определения методов класса – в файл cf2.cpp. Затем файл заголовка необходимо подключить в файл кода с расширением .cpp. Для этого в файле cf2.cpp перед началом программного кода используется уже известная директива:

#includecf2.h

Это заставит компилятор внести содержимое файла cf2.h в соответствующее место программы.

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

  1.  Встраиваемые функции

Если функция объявлена с ключевым словом inline (т.е. встраиваемая), компилятор не создает функцию в памяти компьютера, а копирует ее строки непосредственно в код программы по месту вызова. Встраиваемая реализация функции GetWeight(), например, выглядит так:

inline int Cat::GetWeight() { return itsWeight; }

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

class Cat    

{

public:    

int GetWeight() {return itsWeight;}  //встраиваемая

void SetWeight(int aWeight);

};

В листингах 12.2 и 12.3 вновь создается класс Cat, но теперь объявление класса содержится в файле  cf2.h, а реализация – в файле cf2.cpp. Кроме того, в листинге 12.3 методы доступа к данным класса и метод Meow() являются встраиваемыми.

Листинг 12.2. Объявление класса Cat в файле cf2.h

#include <iostream>

class Cat

{

public:

Cat(int initialAge);

~Cat();

int GetAge() const { return itsAge; }

void SetAge(int age) { itsAge=age; }

void Meow() const { std::cout<<"Meow.\n"; }

private:

int itsAge;

};

Листинг 12.3. Реализация класса  Cat в файле cf2.cpp

#include "cf2.h"

Cat::Cat(int initialAge) //Конструктор

{

itsAge=initialAge;   

}

Cat::~Cat() { }   //Деструктор

int main()

{

Cat Frisky(5);

Frisky.Meow();

std::cout<<"Frisky is a cat who is ";

std::cout<<Frisky.GetAge()<<" years old.\n";

Frisky.Meow();

Frisky.SetAge(7);

std::cout<<"Now Frisky is ";

std::cout<<Frisky.GetAge()<<" years old.\n";

char Res;

std::cin>>Res;

return 0;

}

Лекция № 13. КЛАССЫ, СОДЕРЖАЩИЕ ДРУГИЕ КЛАССЫ КАК ДАННЫЕ-ЧЛЕНЫ

  1.  Сложные классы

Создав сложный класс, можно объявить в нем ряд более простых классов. Например, можно объявить класс «колесо», класс «мотор» и класс «коробка передач», а затем объединить их в класс «автомобиль».

Рассмотрим следующий пример. Прямоугольник состоит из линий. Линия определяется двумя точками. Каждая точка определяется координатами x и y. В листинге 13.1 показано объявление класса Rectangle, содержащееся в файле  Rectangle.h. Поскольку прямоугольник определяется четырьмя линиями, соединяющими четыре точки, и каждая точка определяется координатами на плоскости, сначала будет объявлен класс Point, предназначенный для хранения координат x и y каждой точки. Листинг 13.2 содержит реализацию обоих классов.

Листинг 13.1. Объявление класса Rectangle в файле Rectangle.h

#include <iostream>

class Point

{

public:

void SetX(int x) { itsX=x; }

void SetY(int y) { itsY=y; }

int GetX() const { return itsX; }

int GetY() const { return itsY; }

private:

int itsX;

int itsY;

};

class Rectangle

{

public:

Rectangle(int top, int left, int bottom, int right);

~Rectangle() { }

int GetTop() const { return itsTop; }

int GetLeft() const { return itsLeft; }

int GetBottom()  const { return itsBottom; }

int GetRight() const { return itsRight; }

Point GetUpperLeft() const {return itsUpperLeft; }

Point GetLowerLeft() const {return itsLowerLeft; }

Point GetUpperRight() const {return itsUpperRight; }

Point GetLowerRight() const {return itsLowerRight; }

void SetUpperLeft(Point Location)

  { itsUpperLeft=Location; }

void SetLowerLeft(Point Location)

  { itsLowerLeft=Location; }

void SetUpperRight(Point Location)

  { itsUpperRight=Location; }

void SetLowerRight(Point Location)

  { itsLowerRight=Location; }

void SetTop(int top)   { itsTop=top;}

void SetLeft(int left)  { itsLeft=left;}

void SetBottom(int bottom) { itsBottom=bottom;}

void SetRight(int right)  { itsRight=right;}

int GetArea() const;

private:

Point itsUpperLeft;

Point itsUpperRight;

Point itsLowerLeft;

Point itsLowerRight;

int itsTop;

int itsLeft;

int itsBottom;

int itsRight;

};

Листинг 13.2. Реализация класса Rectangle в файле Rectangle.cpp

#include "Rectangle.h"

Rectangle::Rectangle(int top, int left, int bottom, int right)

{

itsTop=top;

itsLeft=left;

itsBottom=bottom;

itsRight=right;

itsUpperLeft.SetX(left);

itsUpperLeft.SetY(top);

itsUpperRight.SetX(right);

itsUpperRight.SetY(top);

itsLowerLeft.SetX(left);

itsLowerLeft.SetY(bottom);

itsLowerRight.SetX(right);

itsLowerRight.SetY(bottom);

}

/*Найти по точкам ширину и высоту, а затем,

перемножив их, вычислить площадь прямоугольника*/

int Rectangle::GetArea() const

{

int Width=itsRight-itsLeft;

int Height=itsTop-itsBottom;

return (Width*Height);

}

int main()

{

//Инициализировать объект MyRectangle класса Rectangle

Rectangle MyRectangle(100, 20, 50, 80);

int Area=MyRectangle.GetArea();

std::cout<<"Area: "<<Area<<std::endl;

std::cout<<"Upper Left X Coordinate: ";

std::cout<<MyRectangle.GetUpperLeft().GetX();

char Res;

std::cin>>Res;

return 0;

}

Лекция № 14. ВВОД И ВЫВОД В ФАЙЛ

  1.  Объекты ifstream и ofstream

Чтобы открыть и закрыть файл, необходимо создать объекты ifstream и ofstream. Они происходят от уже знакомого класса iostream. Использование этих объектов требует включения в программу файла заголовка fstream.h.

Объекты iostream содержат флаги, отражающие состояние ввода и вывода. Значение каждого из этих флагов можно проверить с помощью функций, возвращающих значение True или False: eof(), bad(), fail()и good(). Функция eof() возвращает значение True, если объект iostream встретил конец файла (EOFend of file – конец файла). Функция bad() возвращает значение True при попытке выполнить неверную операцию. Функция fail()возвращает значение True каждый раз, когда это значение возвращает функция bad(), а также в тех случаях, когда операция невыполнима в данных условиях. Наконец, функция good() возвращает значение True, когда все идет хорошо, т.е. все остальные функции возвращают значение False.

  1.  Как открыть файл для ввода и вывода

Чтобы открыть для вывода файл myfile.cpp с помощью объекта ofstream, необходимо создать экземпляр объекта класса ofstream и передать ему имя файла в качестве параметра:

ofstream fout(“myfile.cpp”);

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

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

ifstream fin(“myfile.cpp”);

Необходимо обратить внимание на то, что fout и fin не более чем имена объектов; объект fout использовался для вывода в файл подобно тому, как объект cout используется для вывода на экран; объект fin аналогичен объекту  сin.

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

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

Листинг 14.1. Открытие файла для чтения и записи

#include <fstream>

#include <iostream>

using namespace std;

int main()

{

char fileName[80];

char buffer[255];  //для ввода пользователя

cout<<"File name: ";

cin>>fileName;

ofstream fout(fileName);  //открыть для записи

fout<<"This line written directly to the file...\n";

cout<<"Enter text for the file: ";

 cin.ignore(1, '\n');     //убрать символ новой

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

cin.getline(buffer, 255);//получить данные от

      //пользователя

fout<<buffer<<endl;      //и записать их в файл

fout.close();   //закрыть файл, но  

                         //остаться готовым

      //открыть его вновь

ifstream fin(fileName); //открыть файл для чтения

 cout<<"Here's the contents of the file:\n";

char ch;

while (fin.get(ch))

 cout<<ch;

cout<<"\n***End of file contents.***\n";

 fin.close();    //уходя гасите свет

char Res;

cin>>Res;

return 0;

}

  1.  Изменение поведения объекта ofstream

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

Допустимыми аргументами являются:

  •  ios::app – добавляет данные в конец файла, не усекая прежнее его содержимое;
  •  ios::ate – осуществляет переход в конец файла, но запись допускает в любом месте файла;
  •  ios::trunc – усекает существующий файл полностью (задано по умолчанию);
  •  ios::nocreate – открывает существующий файл;
  •  ios::noreplace – открывает несуществующий файл.

App – это сокращенное от append – добавить в конец, ate – от at end – перейти в конец, trunc – от truncate – усечение, nocreate – не создавать,  noreplace – не замещать.

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

ofstream fout(“myfile.cpp”, ios::app);

  1.  Двоичные и текстовые файлы

Некоторые операционные системы, например, DOS и Windows, различают текстовые и двоичные файлы. Язык программирования С++ обладает флагом ios::binary, который помогает файловой системе отличить текстовый формат файла от двоичного. Во многих системах этот флаг игнорируется, поскольку все данные хранятся в двоичном формате. А в некоторых закрытых системах этот флаг вообще запрещен и не поддается компиляции.

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

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

Лекция № 15. ПРИМЕНЕНИЕ УКАЗАТЕЛЕЙ В ОБЪЕКТНО-ОРИЕНТИРОВАННОМ ПРОГРАММИРОВАНИИ

  1.  Размещение объектов в динамической памяти

Наиболее часто указатели применяются в следующих случаях:

  •  для манипулирования данными в динамически распределяемой памяти;
  •  для доступа к переменным-членам и функциям класса;

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

Листинг 15.1. Размещение и удаление объектов в области динамической памяти

#include <iostream.h>

class SimpleCat

{

public:

SimpleCat();

~ SimpleCat();

private:

int itsAge;

};

SimpleCat()::SimpleCat()

{

cout<<”Constructor called.”<<endl;

itsAge=1;

}

SimpleCat()::~SimpleCat()

{

cout<<”Destructor called.”<<endl;

}

 

int main()

{

cout<<”SimpleCat Frisky…”<<endl;

SimpleCat Frisky;

cout<<”SimpleCat *pRags=new SimpleCat…”<<endl;

SimpleCat *pRags=new SimpleCat;

cout<<”delete pRags…”<<endl;

delete pRags;

cout<<”Exiting, watch Frisky go…”<<endl;

return 0;

}

__________________________________________________________________

РЕЗУЛЬТАТ

SimpleCat Frisky…

Constructor called.

SimpleCat *pRags=new SimpleCat…

Constructor called.

delete pRags…

Destructor called.

Exiting, watch Frisky go…

Destructor called.

  1.  Доступ к переменным-членам

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

(*pRags).GetAge();

Круглые скобки используются для того, чтобы сначала обратиться по адресу в указателе к объекту, а только затем – к его функции GetAge().

Такой синтаксис слишком громоздок, поэтому язык С++ укомплектован специальным сокращенным оператором косвенного доступа: «указатель на» (–>), который состоит из символов «минус» (–) и «больше» (>). Компилятор воспринимает его как единый оператор.

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

#include <iostream.h>

class SimpleCat

{

public:

SimpleCat() {itsAge=2;}

~ SimpleCat();

int GetAge() const {return itsAge;}

void SetAge(int age) {itsAge=age;}

private:

int itsAge;

};

int main()

{

SimpleCat *Frisky=new SimpleCat;

cout<<”Frisky is ”<<Frisky–>GetAge()

<<” years old”<<endl;

Frisky–>SetAge(5);

cout<<”Frisky is ”<<Frisky–>GetAge()

<<” years old”<<endl;

delete Frisky;

return 0;

}

__________________________________________________________________

РЕЗУЛЬТАТ

Frisky is 2 years old

Frisky is 5 years old

Лекция № 16. ШАБЛОНЫ

  1.  Введение

Мощнейшим инструментом программирования на языке С++ являются «параметрические типы», или шаблоны (templates). Шаблоны оказались настолько эффективными, что стандартная библиотека шаблонов (STLStandard Templates Library) была включена в определение языка С++.

Предположим, что мы имеем объект класса PartsList, являющийся списком деталей какой-либо машины (самолета или автомобиля). Если мы захотим воспользоваться этим объектом для создания, например, списка котов, то возникнет проблема: коты и детали относятся к разным классам (типам).

Создание и использование шаблонов позволяет решить эти проблемы. Шаблоны позволяют обучить компилятор тому, как создать список для любого типа элементов, вместо того, чтобы каждый раз создавать специфический набор для каждого типа списков: для PartsList – список деталей, для CatsList – список котов. Единственное отличие между ними – это тип элементов списка. При использовании шаблонов тип элементов списка становится параметром для определения класса.

Стандартная библиотека шаблонов предоставляет стандартный набор контейнерных (container) классов, включая массивы, списки и т.д. Можно самому создать собственный контейнерный класс. Однако в коммерческих программах обычно используют классы стандартной библиотеки STL, а не собственного изготовления.

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

template <class T>

В данном случае template и class – это ключевые слова, а T – это знакоместо, предназначенное для имени переменной. Это может быть любое имя, однако, как правило, используется буква «T» или название  «Type». Значением T является тип данных.

Поскольку в данном контексте ключевое слово class может выглядеть сомнительно, в качестве альтернативы можно использовать ключевое слово typename:

template <typename T>

Предположим мы хотим создать шаблон для массива. Это можно сделать следующим образом.

template <class T> //Объявление шаблона и параметра

class Array   //Параметрический класс    {

 public:

  Array();

   //Здесь должно быть полное объявление класса

};

Это основа кода для объявления класса шаблона по имени Array. Указание типа шаблона аналогично объявлению переменной, которая будет его экземпляром. Это можно сделать, используя следующий синтаксис.

имяКласса <тип> экземпляр;

Например:

Array<int> anIntArray;

Array<Animal> anAnimalArray;

Таким образом, объект anIntArray представляет собой массив целых чисел, а объект anAnimalArrayмассив объектов класса Animal.

Полное объявление шаблона Array содержится в листинге 16.1.

Листинг 16.1. Шаблон класса Array

#include <iostream>

using namespace std;

const int DefaultSize = 10;

template <class T> // Объявление шаблона и параметра

class Array   // Параметрический класс    {

 public:

 // Конструкторы

Array(int itsSize = DefaultSize);

Array(const Array &rhs);

~Array() { delete [] pType; }

// Операторы

Array& operator=(const Array&);

T& operator[](int offset) { return

pType[offset]; }

// Методы доступа

int getSize() { return itsSize; }

private:

T *pType;

Int itsSize;

};

 

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

Внутри объявления класса слово Array можно использовать без уточнения типа. В другом месте программы этот класс должен упоминаться как Array<T>. Например, если не поместить конструктор в объявление класса, то придется написать следующее:

template <class T>

Array<T>::Array(int size):

 itsSize = size

{

 pType = new T[size];

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

 pType[i] = 0;

}

Здесь именем шаблона будет Array<T>, а именем функции Array(int size).

  1.  Реализация шаблона

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

Помимо стандартных конструктора и деструктора, компилятор способен предоставить конструктор копий, вызов которого осуществляется всякий раз, когда необходимо создать копию объекта. При возвращении и передаче объекта в функцию по значению всегда создается его временная копия. Все конструкторы копий получают один параметр – ссылку на объект своего класса. В качестве параметра конструктора копий принято использовать объект rhs, название которого является сокращением от right-hand side (стоящий справа). Параметр rhs, передаваемый в конструктор копий в качестве постоянной ссылки, соответствует объекту используемого класса.

Для работы со встроенными типами данных (int, real, char и т.д.) используются встроенные операторы, например, сложение (+), равенство (=), умножение (*) и т.д. Кроме того, язык С++ позволяет добавлять и перегружать подобные операторы для пользовательских классов.

Листинг 16.2 содержит простую программу, в которой реализован шаблон класса.

Листинг 16.2. Реализация шаблона массива

#include <iostream>

using namespace std;

const int DefaultSize = 10;

// Объявить простой класс Animal, чтобы получить

// возможность создать массив животных

class Animal

{

public:

 Animal (int);

Animal ();

~Animal () {}

int GetWeight() const { return itsWeight; }

void Display() const { cout << itsWeight; }

 private:

  int itsWeight;

};

Animal::Animal(int weight):

 itsWeight(weight) {}

Animal::Animal():

 itsWeight(0) {}

template <class T> // Объявление шаблона и параметра

class Array   // Параметрический класс    {

 public:

 // Конструкторы

Array(int itsSize = DefaultSize);

Array(const Array &rhs);

~Array() { delete [] pType; }

// Операторы

Array& operator=(const Array&);

T& operator[](int offset)

{ return pType[offset]; }

 const T& operator[] (int offset) const

{ return pType[offset]; }

// Методы доступа

int GetSize() const { return itsSize; }

private:

T *pType;

int itsSize;

};

// Далее следует реализация…

// Реализация конструктора

template <class T>

Array<T>::Array(int size):

itsSize(size)

{

pType = new T[size];

// Создаваемые конструкторы типов должны задавать

// значения по умолчанию

 }

// Конструктор копий

template <class T>

Array<T>::Array(const Array &rhs)

{

itsSize = rhs.GetSize();

pType = new T[itsSize];

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

 pType[i] = rhs[i];

}

// operator=

template <class T>

Array<T>& Array<T>::operator=(const Array &rhs)

{

if (this == &rhs)

 return *this;

delete [] pType;

itsSize = ths.GetSize();

pType = new T[itsSize];

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

 pType[i] = rhs[i];

return *this;

}

// Основная программа

int main()

{

 Array<int> theArray;  // Массив целых чисел

 Array<Animal> theZoo;  // Массив животных

Animal *pAnimal;

// Заполнить массивы

for (int i=0; i<theArray.GetSize(); i++)

{

 theArray[i] = i*2;

 pAnimal = new Animal(i*3);

 theZoo[i] = *pAnimal;

 delete pAnimal;

}

// Вывести на экран содержимое массивов

 for (int j=0; j<theArray.GetSize(); j++)

{

 cout << “theArray[“ << j << “]:\t”;

 cout << theArray[j] << “\t\t”;

 cout << “theZoo[“ << j << “]:\t”;

 theZoo[j].Display();

 cout << std::endl;

}

char Res;

cin >> Res;

return 0;

}

_________________________________________________________________

РЕЗУЛЬТАТ

 В данной программе иллюстрируется создание и использование шаблона. Шаблон Array определен, а затем использован для создания экземпляра объектов Array типа int и Animal. Целочисленный массив заполняется целыми числами, представляющими собой удвоенное значение их индекса в массиве. Массив объектов Animal называется theZoo. Он заполняется значениями, представляющими собой утроенное значение их индекса в массиве.

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

Класс Array содержит два конструктора, причем первому передают в качестве параметра размер целого числа, устанавливаемого по умолчанию константой DefaultSize (размер по умолчанию).

Указатель this содержит адрес текущего объекта.

Затем объявляются операторы присвоения и индексирования, причем объявляются постоянная и непостоянная версии оператора индекса. Единственным методом доступа является функция GetSize(), которая возвращает размер массива.

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

При реализации функций-членов класса шаблона, поскольку они расположены вне первичного определения класса, необходимо еще раз указать, что они являются частью шаблона (Array<T>). Этот процесс повторяется при объявлении конструктора копий и перегрузке оператора равенства.

Фактически шаблон класса Array используется в строках основной программы. Он применяется для создания экземпляра объекта theArray, который использует шаблон типа int, и экземпляра theZoo, представляющий собой массив объектов типа Animal.

  1.  Передача объектов шаблона в функции

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

void Некоторая функция (Array<Тип>&);

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

void SomeFunction(Array<int>&); // Правильно

Однако следующая запись неверна, поскольку отсюда не ясно, что представляет собой выражение T&.

void SomeFunction(Array<T>&); // Ошибка!!!

Так тоже нельзя написать, поскольку нет никакого класса Array, есть только шаблон и его экземпляры.

void SomeFunction(Array &); // Ошибка!!!

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

template <class T>

void MyTemplateFunction(Array<T>&); // Правильно

Здесь MyTemplateFunction()объявлена как функция шаблона, на что указывает первая строка объявления. Функции шаблонов, подобно другим функциям, могут иметь любые имена.

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

template <class T>

void MyTemplateFunction(Array<T>&, Array<int>&);

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

Лекция № 17. СТАНДАРТНАЯ БИБЛИОТЕКА ШАБЛОНОВ

  1.  Введение

Отличительной чертой языка С++ является наличие стандартной библиотеки шаблонов (STLStandard Templates Library).  Все основные разработчики компиляторов предлагают библиотеку STL как составную часть своих программных продуктов. STL – это библиотека классов контейнеров, базирующихся на шаблонах.

Контейнер (container) – это объект, который содержит другие объекты. Различают два типа классов контейнеров библиотеки STL: последовательные и ассоциативные.

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

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

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

Стандартная библиотека С++ предоставляет три вида последовательных контейнеров: векторы (vector), списки (list) и двухсторонние очереди (deque).

  1.  Вектор

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

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

Вот как выглядит определение класса vector:

template <class T, class A = allocator<T>> class vector

{

// члены класса

};

Первый аргумент (class T) указывает тип элементов вектора. Второй аргумент (class A) – класс распределения (allocator class), который берет на себя функции диспетчера памяти, ответственного за выделение и освобождение памяти элементов контейнера.

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

Определить вектор для содержания целых и вещественных чисел можно следующим образом:

vector<int>     vInt;

vector<float>  vFloat;

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

vector<Student>  MathClass(50);

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

Количество элементов в векторе можно узнать с помощью функции-члена size() (размер). В данном случае функция-член vStudent.size() возвратит значение 50.

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

Вектор называется пустым (empty), если не содержит ни одного элемента, т.е. если его размер равен нулю. Чтобы определить, не является ли вектор пустым, в классе вектора предусмотрена функция-член  empty(), которая возвращает значение true, если вектор пуст.

Чтобы присвоить объект Harry класса Student шестому элементу массива MathClass (т.е. зачислить Гарри на курс прикладной математики), можно использовать оператор индекса [ ]:

MathClass[5]=Harry;

Индексы начинаются с нуля. Как можно заметить, чтобы назначить Harry шестым элементом массива MathClass, здесь использован перегруженный оператор присвоения класса Student. Для доступа к возрасту объекта Harry можно воспользоваться следующим выражением:

MathClass[5].GetAge();

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

MathClass.push_back(Sally);

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

В векторе из библиотеки STL максимальное количество элементов не задается, т.к. это решение лучше переложить на плечи создателей компиляторов. Векторный класс предоставляет функцию-член max_size(), которая способна сообщить максимальное число элементов вектора, определенное в компиляторе.

Листинг 17.1 демонстрирует использование векторного класса. Для упрощения обработки строк в этом листинге использован стандартный класс String.

Листинг 17.1. Создание вектора и доступ к его элементам

#include <iostream>

#include <string>

#include <vector>

using namespace std;

class Student

{

public:

Student();

Student(const string& name, const int age);

Student(const Student& rhs);

~Student();

void SetName(const string& name);

string GetName() const;

void SetAge(const int age);

int  GetAge() const;

Student& operator = (const Student& rhs);

private:

string itsName;

int itsAge;

};

Student::Student():

itsName(“New Student”), itsAge(16)

{}

Student::Student(const string& name, const int age):

itsName(name), itsAge(age)

{}

Student::Student(const Student& rhs):

itsName(rhs.GetName()), itsAge(rhs.GetAge())

{}

Student::~Student()

{}

void Student::SetName(const string& name)

{

itsName=name;

}

string Student::GetName() const

{

return itsName;

}

void Student::SetAge(const int age)

{

itsAge=age;

}

int Student::GetAge() const

{

return itsAge;

}

Student& Student::operator=(const Student& rhs)

{

itsName = rhs.GetName();

itsAge = rhs.GetAge();

return *this;

}

ostream& operator<<(ostream& os, const Student& rhs)

{

os << rhs.GetName() << “ is “ << rhs.GetAge()

       << “ years old”;

return os;

}

template<class T>

// отобразить свойства вектора

void ShowVector(const vector<T>& v);

typedef vector<Student> SchoolClass;

int main()

{

Student Harry;

Student Sally(“Sally”, 15);

Student Bill(“Bill”, 17);

Student Peter(“Peter”, 16);

SchoolClass EmptyClass;

Cout << “EmptyClass” << endl;

ShowVector(EmptyClass);

SchoolClass GrowingClass(3);

cout << “GrowingClass(3):” << endl;

ShowVector (GrowingClass);

GrowingClass[0] = Harry;

GrowingClass[1] = Sally;

GrowingClass[2] = Bill;

cout << “GrowingClass(3) after assigning students:\n”;

ShowVector(GrowingClass);

GrowingClass.push_back(Peter);

cout << “GrowingClass() after added 4th student:”

<< endl;

ShowVector(GrowingClass);

GrowingClass[0].SetName(“Harry”);

GrowingClass[0].SetAge(18);

cout << “GrowingClass() after Set\” << endl;

ShowVector(GrowingClass);

return 0;

}

// отобразить свойства вектора

template<class T>

void ShowVector(const vector<T>& v)

{

cout << “max_size()= “ << v.max_size();

cout << “\tsize()= “ << v.size();

cout << “\tcapacity()= “ << v.capacity();

cout << “\t“ << (v.empty()? “empty”: “not empty”);

cout << endl;

for (int i=0; i<v.size(); ++i)

 cout << v[i] << endl;

 cout << endl;

}

_________________________________________________________________

РЕЗУЛЬТАТ

 В начале этой программы определяется класс Student. Это класс простой структуры, дружественный по отношению к классу vector. По рассмотренным ранее причинам были определены стандартный конструктор, конструктор копий и перегруженный оператор присвоения.

Функция шаблона ShowVector() используется для вызова функций-членов вектора, отображающих его свойства max_size(), size() capacity(), empty().

  1.  Список

Список (list) – это контейнер, предназначенный для оптимального выполнения частых вставок и удалений элементов. Класс-контейнер библиотеки STL list определен в файле заголовка <list> в пространстве имен std. Класс list реализован как двунаправленный связанный список, в котором каждый блок содержит указатели как на предыдущий, так и на последующий блок списка.

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

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

Листинг 17.2. Проход по списку с помощью итераторов

#include <iostream>

#include <list>

using namespace std;

typedef list<int> IntegerList;

int main()

{

IntegerList intList;

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

intList.push_back(i*2);

  for (IntegerList::const_iterator ci=intList.begin();

 ci!=intList.end(); ++ci)

cout << *ci << “ “;

  return 0;

}

_________________________________________________________________

РЕЗУЛЬТАТ

 В приведенном листинге использован шаблон из библиотеки STL. Во второй строке программы директива #include подключает необходимый файл. В четвертой строке продемонстрировано использование команды typedef.  В данном случае, вместо определения list<int>, ключевое слово typedef позволяет использовать стандартный класс списка IntegerList.

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

intList:: iterator

 Функция-член begin() (начало) возвращает итератор к первому блоку списка. Оператор инкремента (++) позволяет перевести итератор на следующий блок. Функция-член end() (конец), как ни странно, возвращает итератор на блок, расположенный за последним блоком списка. Поэтому не следует позволять итератору достигать самого конца.

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

Итераторы можно использовать не только с классом list, но и с классами векторов. Кроме функций-членов, содержащихся в классе вектора, класс списка обладает двумя дополнительными функциями push_front() (добавить в начало) и pop_front() (удалить первый).

  1.  Стек

Стек является одной из самых распространенных структур данных в программировании. Но стек реализован не как независимый контейнерный класс, скорее его можно назвать оболочкой контейнера. Класс шаблона  stack определен в файле заголовка <stack> в пространстве имен std.

Стек (stack) – это непрерывный блок выделенной памяти, который может расширяться или сокращаться в хвостовой части, т.е. к элементам стека можно обращаться (или удалять их) только с одного конца. Подобными характеристиками обладают последовательные контейнеры, особенно в классах vector и deque. Фактически, для реализации стека можно использовать любой последовательный контейнер, который поддерживает функции back(), push_back() и  pop_back(). Другие методы контейнеров для работы стека не используются, поэтому они и не предоставляются классом stack.

Шаблон класса стека (stack) библиотеки STL может содержать любой тип объектов. Единственное ограничение состоит в том, что все элементы должны иметь одинаковый тип.

Стек представляет собой структуру типа LIFO (last in, first out – последним вошел, первым вышел). По соглашению, открытый конец стека называется вершиной стека (top of the stack), а операции, выполняемые со стеком, называют помещением (push) и выталкиванием (pop). Для класса stack эти общепринятые термины остаются в силе.

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

  1.  Двусторонняя очередь

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

  1.  Очередь

Очередь (queue) – еще одна распространенная в программировании структура данных. В очереди элементы добавляются с одного конца, а извлекаются с другого. Этот принцип известен как структура FIFO (first in, first out – первым пришел, первым ушел).

Подобно классу stack, класс queue реализован как класс оболочки контейнера. Контейнер должен поддерживать такие функции, как front(), back(), push_back() и pop_front().

17.7. Понятие ассоциативных контейнеров

Ассоциативные контейнеры (associative container)  обеспечивают быстрый произвольный доступ к элементам по ключам. Стандартная библиотека С++ содержит четыре ассоциативных контейнера: map (карту), multimap (мультикарту), set (набор или множество), multiset (мультимножество).

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

Класс-контейнер multimap – это класс карты, не ограниченный уникальностью ключей. Это значит, что одно и то же ключевое значение могут иметь несколько элементов.

Класс-контейнер set также подобен классу карты. Единственное отличие заключается в том, что его элементы представляют собой не пары (ключ/значение), а только ключи.

Наконец, класс-контейнер multiset – это класс set, который позволяет иметь несколько ключевых значений.

В следующем примере (листинг 17.3) для создания списка студентов используется карта.

Листинг 17.3. Класс-контейнер map

#include <iostream>

#include <string>

#include <map>

using namespace std;

class Student

{

public:

Student();

Student(const string& name, const int age);

Student(const Student& rhs);

~Student();

void SetName(const string& name);

string GetName() const;

void SetAge(const int age);

int  GetAge() const;

Student& operator = (const Student& rhs);

private:

string itsName;

int itsAge;

};

Student::Student():

itsName(“New Student”), itsAge(16)

{}

Student::Student(const string& name, const int age):

itsName(name), itsAge(age)

{}

Student::Student(const Student& rhs):

itsName(rhs.GetName()), itsAge(rhs.GetAge())

{}

Student::~Student()

{}

void Student::SetName(const string& name)

{

itsName=name;

}

string Student::GetName() const

{

return itsName;

}

void Student::SetAge(const int age)

{

itsAge=age;

}

int Student::GetAge() const

{

return itsAge;

}

Student& Student::operator=(const Student& rhs)

{

itsName = rhs.GetName();

itsAge = rhs.GetAge();

return *this;

}

ostream& operator<<(ostream& os, const Student& rhs)

{

os << rhs.GetName() << “ is “ << rhs.GetAge()

       << “ years old”;

return os;

}

// Внимание! Здесь начинаются изменения!

template<class T, class A>

// отобразить свойства карты

void ShowMap(const map<T, A>& v);

typedef map<string, Student> SchoolClass;

int main()

{

Student Harry(“Harry”, 18);

Student Sally(“Sally”, 15);

Student Bill(“Bill”, 17);

Student Peter(“Peter”, 16);

SchoolClass MathClass;

MathClass[Harry.GetName()] = Harry;

MathClass[Sally.GetName()] = Sally;

MathClass[Bill.GetName()] = Bill;

MathClass[Peter.GetName()] = Peter;

cout << “MathClass” << endl;

ShowMap(MathClass);

cout << “We know that “ << MathClass[“Bill”].GetName()

 << “is “ << MathClass[“Bill”].GetAge()

 << “ years old” << endl;

return 0;

}

// отобразить свойства карты

template<class T, class A>

void ShowMap(const map<T, A>& v)

{

for (map<T, A>::const_iterator ci=v.begin();

ci!=v.end(); ++ci)

 cout << ci->first <<”: “ << ci->second << endl;

 cout << endl;

}


 

А также другие работы, которые могут Вас заинтересовать

33197. Характеристика слабослышащих детей 14.71 KB
  Возникновение тугоухости после сформирования речи не ограничивает дальнейшего достаточно интенсивного ее развития не смотря на её недостатки: относительная бедность словарного запаса сочетается с неправильным усвоением и употреблением понятий. Характерные отклонения в устной речи отражаются и на письменной. На степень сохранности речи влияют время наступления глухоты условия развития и роста ребенка уровень остаточного слуха и качество работы по развитию речи ребенка. Без специального воспитания дети потерявшие слух в 45 лет к поступлению...
33198. ПРОБЛЕМА КОМПЕНСАЦИИ ГЛУХОТЫ 14.85 KB
  Компенсация – это возмещение недоразвитых или нарушенных психических функций путем использования сохранных или перестройки частично нарушенных функций. При компенсации психических функций возможно вовлечение в ее реализацию новых структур которые раньше не участвовали в осуществлении данных функций или выполняли при этом другую роль. Второй тип межсистемная компенсация которая осуществляется путем перестройки функциональных систем и включения в работу новых элементов из других структур выполнения ими несвойственных ранее функций....
33199. Закономерности психического развития детей с нарушением слуха 14.53 KB
  Общие закономерности: Закономерность соотношения биологических и социальных факторов в процессе психического развития ребенка. Процесс перехода от одной стадии психического развития к другой предполагает глубокое преобразование всех структурных компонентов психики. Неравномерность психического развития.
33200. МЕТОДЫ ИССЛЕДОВАНИЯ СЛУХА 19.41 KB
  Исследование нарушения слуха затруднено тем что: Ребенок не жалуется на отсутствие слуха. Родители не замечают отсутствие слуха и не принимают эффективных мер. Отсутствие унифицированных методов позволяющих получить достоверные сведения о снижении слуха в детском возрасте.
33201. Особенности зрительного восприятия детей с нарушением слуха 14.16 KB
  Особенности зрительного восприятия детей с нарушением слуха. Это связано с менее подробным анализом и синтезом предметов в прошлом опыте с замедленным формированием произвольности процесса восприятия. Для точного восприятия формы предмета важно выделить его контур. Особенности развития осмысленности восприятия отчетливо проявляются при анализе изображений и картин.
33202. Особенности двигательных ощущений. Осязание глухих и слабослышащих 15.25 KB
  Многие дети имеющие нарушения слуха отстают от нормально слышащих по развитию движений. На протяжении всего дошкольного возраста сохраняется некоторая неустойчивость трудность сохранения статичного и динамичного равновесия недостаточно точная координация неуверенность движений и относительно низкий уровень развития пространственной ориентировки. У большинства имеется отставание в развитии мелких движений пальцев рук артикуляционного аппарата. Замедленная по сравнению со слышащими скорость выполнения отдельных движений влияет на темп...
33203. Кожные ощущения и восприятия 13.83 KB
  Возникают при непосредственном контакте предмета с кожей подразделяются на 4 вида: тактильные вибрационные температурные и болевые. Наибольшее значение для компенсации слуха имеют вибрационные т. Вибрационные ощущения возникают при воздействии меньшей силы чем слуховые. Для того чтобы вибрационные ощущения смогли использовать как средство познания для детей необходимо проводить специальную работу.
33204. Особенности развития внимания 15.43 KB
  Устойчивость внимания с возрастом меняется. Для детей с нарушениями слуха характерно более позднее становление высшей формы внимания т. произвольного и опосредствованного что обусловлено более поздним формированием умений использовать средства организации внимания управление им а также отставанием в развитии речи способствующей организации и управлению собственным поведением.
33205. Память как хранитель информации 15.52 KB
  Глухие дети раньше познают в объектах специфическое чем особое и общее отмечают несущественные детали в ущерб главным но менее заметным. Значительно больше глухие отстают в запоминании слов обозначающие звуковые явления. Глухие запоминают больше слов обозначающих качество предметов воспринимаемых тактильно. Часто глухие школьники заменяют слова глаголы из области слуховых представлений словами глаголами связанными со зрительной вибрационной и тактильной сферами.