Структуры - введение в объектно-ориентированное программирование

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

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

Для этого используются специальные типы данных, которые в языке C называются структурами, а в языке C++ классами. Например, структура Point, задающая точку на плоскости и содержащая два действительных числа x и y может быть задана следующим образом:

struct Point
{
    double x;
    double y;
};

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

После этого мы можем работать с Point, как с новым типом данных, содержащим два поля: x и y. Примеры создания переменной и массива переменных типа Point:

     Point P, Arr[10];

Чтобы обратиться к полю какой-либо структуры, используется оператор "точка". Его левый операнд – идентификатор переменной типа структура, правый операнд – имя поля. Например, P.x или Arr[i].y.

Например, считать координаты точки, сохранить их в переменной P и вывести на экран расстояние от начала координат до этой точки можно следующим образом:

     cin >> P.x >> P.y;
     cout << sqrt(P.x * P.x + P.y * P.y);

По сути, величины P.x и P.y являются независимыми переменными, скомбинированными в один объект. С этим объектом можно работать, как с единым целым, например, можно выполнять присваивания вроде Arr[i]=P, можно сохранить набор точек в одном массиве и т.д.

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

     struct Person
     {
         string FirstName; // Имя
         string LastName;  // Фамилия
         int    BirthYear; // Год рождения
     };

Теперь можем работать с данными типа Person:

     Person Vasya;
     Vasya.FirstName = "Василий";
     Vasya.LastName = "Пупкин";
     Vasya.BirthYear = 1990;
     cout << Vasya.FirstName << " " << Vasya.LastName <<
         " родился в " << Vasya.BirthYear << " году" << endl;

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

Например, пусть мы хотим определить структуру Circle для определения окружности. Окружность задается центром и радиусом. Радиус – это действительное число (поле Radius типа double), а центр – это, конечно же, точка, то есть поле Center имеет тип Point . Получили:

     struct Circle
     {
         Point Center;
         double Radius;
     };

Дальше с такими "вложенными" структурами можно работать так:

     Circle A;
     A.Radius = 10;
     A.Center.x = -3;
     A.Center.y = 15;

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

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

     struct Person
     {
         string FirstName; // Имя
         string LastName;  // Фамилия
         int    BirthYear; // Год рождения
         Person(string FirstN, string LastN, int BirthY); 
     };

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

     
Person::Person(string FirstN, string LastN, int BirthY)
{
    FirstName = FirstN;
    LastName = LastN;
    BirthYear = BirthY;
}
Для того, чтобы создавать объекты как прежде Person p;, нужно добавить конструктор по умолчанию, то есть написать еще одну функцию Person::Person().

Cтруктура “Point”

Создайте структуру Point, определите для нее конструктор, вывод на экран. У структуры Point два поля: x и y.

Добавьте в структуру Point арифметические операции: прибавление другой точки ( void Point::add(Point &p)) и других.

A: Самая дальняя точка

Программа получает на вход число N, далее координаты N точек. Выведите координаты точки, наиболее удаленной от начала координат.

Для решения этой задачи напишите метод dist, который возвращает расстояние от точки до начала координат.

Ввод Вывод
2
1 2
2 3
2 3

B: Центр масс

Выведите координаты центра масс данного множества точек (учтите, что это —два действительных числа).

Используйте для вычислений метод add()

Ввод Вывод
2
1 2
2 3
1.5 2.5

C: Диаметр множества

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

Ввод Вывод
3
1 1
1 0
0 0
1.4142135623731

D: Сортировка

Определите для точек оператор сравнения “<”, сравнивающую точки по значению расстояния от начала координат. Отсортируйте данные точки в порядке возрастания расстояния от начала координат используя функцию sort из файла algorithm.

Ввод Вывод
3
1 0
-1 -1
0 0
0 0
1 0
-1 -1

E: Максимальный периметр

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

Для нахождения периметра треугольника напишите отдельную функцию double Perimeter(Point A, Point B, Point C).

Ввод Вывод
4
0 0
0 1
1 0
1 1
3.41421356237309

F: Максимальная площадь

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

Для нахождения площади треугольника напишите отдельную функцию double Area(Point A, Point B, Point C).

Ввод Вывод
4
0 0
0 1
1 0
1 1
0.5

Cтруктура “Fraction”

Структура Fraction должна иметь два поля: числитель a и знаменатель b. Оба поля должны быть типа int.

Для структуры Fraction определите конструкторы, которые могут принимать следующие виды параметров:

В конструкторе принимающем std::string можно воспользоваться такой конструкцией:

    // Пусть a, b - поля Fraction (числитель и знаменатель)
    // s - параметр типа std::string&
    int n = sscanf(s.c_str(), "%d/%d", &a, &b);
    if (n == 1)
         b = 1;

Этот код мы разберем в подробностях позже.

У каждой дроби существует единственное каноническое представление. Каноническое представление: это такое представление \(\frac{a}{b}\), что \(b>0\), \( (a, b) = 1\). Структура должен иметь метод reduce, который приводит дробь к каноническому представлению. После каждой операции с дробями (в том числе и в конструкторе) необходимо вызывать метод reduce для сокращения дроби.

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

Ввод и вывод объектов при помощи std::cin и std::cout

Разберемся как работает стандартный ввод и вывод в C++. Оказывается, этот не какой-то специальный синтаксис С++, а именно перегрузка операций. В языке C++ есть стандартные классы std::istream и std::ostream, экземлярами, то есть объектами которых являются std::cin и std::cout.

Запись std::cout << x; означает применение перегруженной операции << к объектам std::cout и x.

Для ввода и вывода в привычном виде нужно перегрузить операторы >> и << для классов std::istream и std::ostream. Функция должна быть внешней, потому что первым параметром операции являются std::cin и std::cout, а доступа к коду от классов std::istream и std::ostream у нас нет.

Напишем, например, функцию для вывода дроби

std::ostream &operator<<(std::ostream &out, const Fraction &f) {
    out << f.a << "/" << f.b;
    return out;
}

О слове const мы поговорим позже. В принципе, можно убрать его вместе с амперсандом std::ostream &operator<<(std::ostream &out, Fraction f)

Оператор << (да, << - это именно операция, такая же как + или *, принимает объект вывода (объект типа std::ostream) и дробь. В теле функции дробь выводится при помощи переданного ей объекта вывода.

Теперь мы можем выводить дробь так: std::cout << f.

Функция возвращает переданный ей объект out для того, чтобы можно было устраивать цепочки: std::cout << f1 << f2, эта конструкция выполняется так: (std::cout << f1) << f2.

Организация ввода при помощи std::cin делается аналогично.

std::istream &operator>>(std::istream &in, Fraction &f) {    
    // создаем string и читаем в него всю строку со ввода
    string s;
    std::getline(in, s); 
    // и формируем дробь из строки s как в конструкторе, показанном выше
    ...
    return in;
}

G: Метод reduce и вывод дроби

Необходимо реализовать следующую функциональность структуры:

  1. Метод reduce.
  2. Конструкторы Fraction(), Fraction(int), Fraction(int, int). Конструктор должен вызывать метод reduce.
  3. Вывод дроби.

Методика тестирования следующая. Проект должен содержать два файла: fraction.h, содержащий описание и реализацию структуры и fraction.cpp, подключающий fraction.h и содержающий функцию main. На проверку сдается только файл fraction.h, после чего проводится его тестирование на соответствие спецификации.

H: Считывание дроби

Реализовать считывание дроби из потока istream, перегрузив оператор считывания >>. При считывании дробь имеет один из двух форматов: либо целое число, либо два целых числа через дробную черту без пробелов, при этом только числитель может иметь знак “-”.

I: Сравнения

Определите операторы <, <=, >, >=, ==, !=. Сравнения необходимо реализовать для типов int, double, Fraction, при этом значение типа Fraction может быть как правым, так и левым операндом.

J: Сложение

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

K: Вычитание

Определите оператор вычитания.

L: Умножение и деление

Определите операторы умножения и деления.

M: Операторы присваивания

Определите операторы +=, -=, *=, /= для случая, когда правый операнд имеет тип Fraction или int.

Пример определения оператора:

Fraction & operator+= (Fraction &, Fraction)

ZA: Идеальный газ - ООП

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

Сделайте структуры Containter и Particle, в последней реализуйте методы void draw() и void move(Containter &containter).

Координаты контейнера и центр частицы храните вложенной структурой Point.

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

ZB: Вентилятор

Вентилятор представляет собой круг и три лопасти-треугольника с углом 30 градусов. Лопасти расположены под углами 120 градусов друг к другу. (см. рисунок).

Можно рисовать из линий (незакрашенный).

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

Нажатие на левую - уменьшает. Скорость может стать отрицательной.