Структурное и объектно-ориентированное программирование — КиберПедия 

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

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

2018-01-03 294
Структурное и объектно-ориентированное программирование 0.00 из 5.00 0 оценок
Заказать работу

ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ

Классы и потоки ввода-вывода

 

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

Основополагающим базовым классом является класс ios. Производными от него являются классы istream – поддерживает базовые операции ввода,
os­tream – поддерживает базовые операции вывода. Двунаправленный поток под­держивается классом iostream, производным от istream и ostream, что можно представить схемой:

ios

____________|_________

| |

istream ostream

|_____________________|

|

iostream

 

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

#include<iostream.h>

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

 

Стандартный ввод-вывод

 

С++ поддерживает четыре предопределенных потоковых объекта:

1. cin – стандартный ввод, как правило с клавиатуры, аналог stdin в С (объект класса istream).

2. cout – стандартный вывод, как правило на экран, аналог stdout в C (объект класса ostream).

3. cerr– стандартный небуферизованный вывод ошибок, как правило на экран, аналог stderr в С (объект класса ostream).

4. clog– буферизованный вывод ошибок, нет аналога в С (объект класса ostream).

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

Для каждого потока "перегружены" (переопределены) два оператора:

>>– оператор извлечения (чтения) из потока (для ввода с клавиатуры: "считать из");

<<– оператор записи (вставки) в поток (для вывода на экран: "вывести на").

Замещающие функции способны воспринимать аргументы любых основ­ных типов данных (char, char*(string), int, long, float, double) и могут быть расширены для того, чтобы воспринимать аргументы типа class.

 

Ввод для встроенных типов

Файл заголовков iostream.h содержит объявление объекта вида:

istream cin;

Класс iostream определяет оператор ввода >> для набора стандартных ти­пов:

class istream

{ // …

public: // общедоступные функции

istream & operator >> (char&); // символ

istream & operator >> (char*); // символьная строка

istream & operator >> (short&); // короткий целый

istream & operator >> (int&); // целый

istream & operator >> (long&); // длинный целый

istream & operator >> (float&); // действительный

istream & operator >> (double&); // двойной действительный

}

 

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

Для ввода-вывода можно строить цепочки операций слева направо. Например, так можно ввести несколько данных в С++:

int i; float f;

cin >> I >> f;

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

Для целых типов short, int, long действие операции >> по умолчанию заклю­чается в пропуске пробельных символов и преобразовании целого значения пу­тем чтения символов ввода до символа, который не является допустимым для данного типа.

Для типов с плавающей точкой float иdouble действие операции >> со­стоит в пропуске пробельных символов и преобразовании значения с плаваю­щей точкой до символа, который не может быть частью числа с плавающей точ­кой.

Вывод для встроенных типов

 

Файл заголовков iostream.h содержит объявление объекта вида:

ostream cout;

Класс ostream определяет оператор вывода << для встроенных типов:

class ostream

{ // …

public: // общедоступные функции

ostream & operator << (char); // символ

ostream & operator << (char*); // символьная строка

ostream & operator << (int i); // целый

ostream & operator << (long);// длинный целый

ostream & operator << (double); // двойной действительный

ostream & put(char);// символ

}

 

Операция <<, называемая вставкой, заменяет функцию вывода printf(). Левый операнд – это объект cout типа класса ostream. Правый операнд может иметь любой тип, для которо­го определен вывод потоком для встроенных типов.

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

Например:

int i, k=234; double d; char ch='A';

cout<<”Значение k=” << k <<’\n’; // в конце символ перевода строки

cin>> i >> d; // ввод данных i, d

cout<<”i=”<<i<<” d=”<<d<<’\n’; // вывод значений i, d

cout<<”ch=”<<ch; // вывод символа A

 

Пример 1.

Потоки ввода-вывода.

 

#include<iostream.h> // подключение библиотек ввода-вывода

void main()

{ int i;

char str[ ]=”Пример вывода в С++\n”;

cout<<”Вывод строки”<<str;

cout<<”Введите целое и длинное целое число: “;

long l; // переменная описывается перед первым использованием

cin>>i>>l; // ввод данных

cout<<”i=”<<i<<” l=”<<l<<’\n;

cout<<”Введите строку: ”;

cin>>str;

cout<<”Введена строка: ”<<str;

}

Замечание. Можно также использовать любые библиотечные функции ввода-вывода языка С (printf(), scanf() и др.). Кроме того, в другом контексте преопределенные операторы << и >> задают операции сдвига.

 

Пример 2.

Встроенные типы вставок (вывода данных). Целые типы преобразуются по правилам (по умолчанию) для printf (если эти правила не изменены путем установки флагов ios).

#include<iostream.h> // подключение библиотек ввода-вывода

#include<stdio.h> // для printf

#include<conio.h> // для консольных функций

void main()

{ int i=5; int *pi=&i; long l=12345678; double d=345.6789; char ch='A';

clrscr(); // чистка экрана результатов

cout<<”Вывод с помощью функции printf():\n”);

printf(“i=%d адрес i pi=%#x l=%ld d=%.4f ch=%c\n”, i,pi,l,d,ch);

cout<<”Вывод с помощью потока cout:\n”;

cout << ”i=”<< i << ” адрес i=” << &I << ” l =” << l << ” d=” <<d;

cout<<” ch=”<<ch<<'\n';

getch(); // задержка экрана результатов

}

Результаты программы:

Вывод с помощью функции printf():

i=5 адрес i pi=0xfff4 l=12345678 d=345.6789 ch=A

Вывод с помощью потока cout:

i=5 адрес i=0x8f61fff4 l=12345678 d=345.6789 ch=A

Управление вводом-выводом

Символьные извлечения.

Для типа char действие операции >> состоит в пропуске пробельных символов и чтения следующего (непробельного) символа. Если требуется про­честь следующий символ (любой), то можно использовать одну из функций-эле­ментов get() класса istream:

сhar ch;

cin.get(ch); // ch устанавливается на следующий символ потока,

// даже если это пробельный символ.

Следующий вариант get позволяет управлять числом извлекаемых симво­лов, их размещением и оконечным символом:

get(char* buf, int max, int term=’\n’).

Эта функция считывает символы из входного потока в символьный мас­сив buf до тех пор, пока не будет считано max -1символов, либо пока не встре­тится символ, заданный term, в зависимости от того, что будет раньше. Завер­шающий пустой символ (нуль-байт – ‘\0’) добавляется автоматически. По умолчанию око­нечным символом (терминатором), который не требуется задавать, является ‘\n’. Сам терминатор в массив buf не считывается и из входного потока istream не удаляется. Массив buf должен иметь размер не менее max символов.

 

Пример 3.

Ввод строки цифр и преобразование их в целое число типа int с помощью функции atoi(s) и вывод числа в 10-ом, 16-ом, 8-ом форматах с использованием идентификаторов dec, hex, oct, вставленных в поток вывода для управления форматом выходного потока, endlаналог'\n'. (см. Манипуляторы).

 

#include<iostream.h> // подключение библиотек ввода-вывода

#include<conio.h> // для консольных функций

#include<stdlib.h> // для функции atoi()

const int size=35; // размер буфера строки

void main()

{ clrscr();

int value; // переменная для числа

char s[size]; // буфер строки

cout<<"Value= ";

cin.get(s, size,'\n'); // ввод строки числа

value=atoi(s); // преобразование строки в число

cout<<"Decimal="<<dec<<value<<" Hexadecimal=0x"<<hex<<value

<<" Octal=0"<<oct<<value<<endl;

getch();

}

Результаты программы:

Value=123

Decimal=123 Hexadecimal=0x7b Octal=0173

Замечание. Для ввода строки вместо функции cin.get(s, size,'\n');можно использовать cin>>s. Этот оператор сработает, но если пользователь введет бо­лее 34 символов, оператор ввода продолжит запись за пределами буфера s, при этом, возможно, данные и код, располагающиеся за буфером, будут уничтоже­ны. Такая операция может привести к краху системы!

Пример 4.

Использование функции get для безопасного чтения строк.

#include<iostream.h>

void main()

{ char s[35]; // буфер строки

char c; // переменная символа

cout<<"Введите строку не более 34 символов:\n";

cin.get(s, 35); // пропущен 3-й аргумент по умолчанию '\n'

cout<<"Вы ввели строку: "<<s<<endl;

if(cin.get(c) && c!='\n') // если следующий символ не '\n',то вывод

cout<<"Достигнута максимальная длина строки\n";

}

При вводе строки длиной более 34 символов она будет усечена и ввод бу­дет безопасным. Одна проблема все же остается. Символ '\n' или другой сим­вол, завершающий ввод, остается в потоке и должен быть прочитан еще одним оператором cin.get(), как показано последним оператором if. Если cin.get()не читает символ '\n',ввод будет усечен. Этот факт необходимо учитывать при на­писании программ.

Проблему непрочитанного символа '\n' можно решить с помощью метода cin.getline()с тремя параметрами (совпадет с get()), при этом символ раздели­тель '\n' также помещается в принимающую строку символов (s).

Для ввода неформатированных и непреобразованных двоичных данных используется функция cin.read((char*)&x, sizeof(x)).

 

Форматирование ввода/вывода.

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

 

Ширина вывода.

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

int ios::width(int w); // устанавливает поле шириной w символов и воз­вращает предыдущую ширину,

int ios::width(); // возвращает предыдущую ширину, не внося из­менений.

По умолчанию width=0, то есть вывод выполняется без заполнителей. При w≠0, если длина числа меньше w используются заполнители, иначе выводится фактическое число без усечения.

Например:

int i=123;

int oldw=cout.width(6);

cout<< i; // вывод будет 123 (перед числом 3 пробела), затем width=0

cout.width(oldw); // восстановление предыдущей ширины width=6

 

После каждой форматной вставки ширина обнуляется, например:

int i, j;

cout.width(4);

cout<< i <<" "<< j <<;

Здесь i будет выведено четырьмя символами, однако пробел и j будут иметь минимально необходимое число символов.

 

Заполнители и дополнение вправо и влево.

Символ-заполнитель и направление дополнения зависят от установок вну­тренних флагов, отвечающих за эти параметры.

По умолчанию символом-заполнителем является пробел. Изменить данное умолчание позволяет функция fill:

int i=123;

cout.fill ("*");

cout.width(6);

cout<< i; // на экран будет выведено ***123

 

По умолчанию устанавливается выравнивание по правому краю (дополне­ние символами-заполнителями). Это можно изменить функциями setf и unsetf:

int i=56;

cout.width(6);

cout.fill ("#");

cout.setf (ios::left, ios::adjustfield);

cout<< i; // на экране: 56####

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

Можно также использовать манипуляторы setfill, setiosflags, resetiosflags.

 

Манипуляторы.

Это специальные операции (похожие на функции) для более простого из­менения ширины и других параметров форматирования. Для их использования программа должна иметь строку:

#include<iomanip.h>

 

Манипуляторы принимают в качестве аргумента ссылку на поток и воз­вращают ссылку на тот же поток. Поэтому манипуляторы могут объединяться в цепочку вставок (или извлечений из потока) для того, чтобы изменять состоя­ние потока в виде побочного эффекта, без фактического выполнения каких-либо вставок или извлечений. В таблице ниже в столбце синтаксис задано направле­ние потока: входной – ins >>, выходной – outs <<.

Таблица

Манипулятор Синтаксис Действие

Установка флага форматирования с преобразованиями:

dec outs << dec 10-ым ins >> dec

hex outs << hex 16-ым ins >> hex

oct outs << oct 8-ым ins >> oct

ws ins >> ws Извлечение пробелов

endl outs << endl Вставка символа новой строки

и очистка потока

ends outs << ends Вставка конечного нулевого

символа в строку

flush outs << flush Очистка ostream

setbase(int) outs << setbase(n) Установка системы счисления

(0, 8, 10, 16). 0 это по умолча-

нию 10-ая с.с. при выводе и

правила С для литералов це-

лых чисел при вводе

resetiosflags(long) ins>>resetiosflags(1) Очистка битов в ins или outs

outs<<resetiosflags(1) аргументом 1

setiosflags(long) ins >> setiosflags(1) Установка битов в ins или outs

outs << setiosflags(1) аргументом 1

setfill(int) ins >> setfill(n) Установка символа

outs >> setfill(n) заполнителя в n

setprecision(int) ins >> setprecision(n) Установка точности представле-

outs>>setprecision(n) ния чисел с плавающей точкой,

равной n разрядам

setw(int) ins >> setw(n) Установка ширины поля

outs << setw(n) в значение n

 

Пример 5.

Применение функций и манипуляторов форматирования вывода в про­грамме.

 

#include<iostream.h>

#include<conio.h>

#include<iomanip.h>

void main()

{ clrscr();

int i=36, j=45;

cout<<"Вывод с установленной шириной поля 5 числа i:\n";

int oldw=cout.width(5); // установка ширины поля вывода

cout<< i <<'\n';

cout<<"После каждой вставки формата ширина поля=0:\n";

cout<< " oldw=" << oldw << '\n';

cout<< i << '\n';

cout<< "Установка поля 5 влияет только на 1-ю переменную:\n";

cout.width(5);

cout<< i << " " << j << '\n';

cout<< "Манипулятор setw(5) упрощает вывод переменных:\n";

cout<< setw(5) << i << setw(5) << j << '\n';

cout<< "Манипуляторы dec, hex, oct изменяют сист. счисл. \n";

cout<< "(оставляя эти изменения в силе) — вывод i:\n";

cout<< dec << i << " " << hex << i << " " << oct << i <<endl;

cout<< "Манипулятор endl аналог '\n' очищает поток"<<endl;

cout<< "i= " << i <<endl;

cout<< "Заполнитель пробел можно заменить на *"<<endl;

cout.fill ('*'); // задание символа заполнения функцией fill

cout<< setw(6) << i << endl; // установка ширины поля 6 числа i

cout<< "Выравнивание можно изменить функцией setf:"<<endl;

cout.width(6);

cout.setf(ios::left, ios::adjustfield);

cout<< "i= " << i <<endl;

getch();

}

 

Вывод чисел с плавающей точкой.

Большие и малые числа выводятся в экспоненциальном формате (е, Е), а не фиксированном. Например, число 1234567.8 печатается 1.2345678Е+07.

Числа с нулевой дробной частью печатаются как целые. Например, число 95.0 печатается 95.

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

cout.setf(ios::fixed, ios::floated);

cout.setf(ios::showpoint);

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

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

cout<< setprecision(2) << x;

например, вместо числа 16.38567 печатается 16.39.

 

Пример 6.

Если отсутствует переназначение, то ввод с клавиатуры отображается на экран. Следующая программа копирует cin в cout.

 

#include<iostream.h>

void main()

{ char ch;

while (cin >> ch)

cout << ch;

}

 

Обратите внимание, что cin >> ch может рассматриваться как булевское выражение. Этот эффект стал возможен благодаря определениям в классе ios. А именно, такие выражения как (cout) или (cin >> ch) рассматриваются как ука­затель на переменную, значение которой определяется кодом ошибки пото­ка. Нулевой указатель (трактуемый как "ложь") индицирует ошибку в потоке, а не­нулевой указатель ("истина") означает ее отсутствие. Можно использовать опе­рацию отрицания (!), в этом случае (!cout) будет "истина" при возникнове­нии ошибки в потоке cout и "ложь", если они отсутствуют:

if (!cout) errmsg("Output error!");

cin — это входной поток, подключенный к стандартному выводу. Он мо­жет правильно обрабатывать все стандартные типы данных. Как известно, в С вывод подсказки (приглашения) без символа новой строки ('\n') в стандартный выходной поток stdout требует обращения к fflush(stdout), чтобы эта подсказка могла появиться. В С++ обращение к cin автоматически очищает cout.

Рассмотрим примеры простых программ на С++ с отличиями от С-про­грамм.

 

Пример 7.

Применение констант и встроенных функций (inline).

 

#include<iostream.h>

const float pi=3.14159;

inline float area (const float r) { return pi*(r)*(r); }

void main()

{ float radius;

cout << "Введите радиус круга: ";

cin >> radius;

cout << "Площадь круга= " << area (radius) << '\n';

}

 

Идентификатор константы ведет себя как обычная переменная (т.е. ее об­ластью действия является блок, в котором она определена) за исключением того, что она не может находиться в левой части оператора присваивания. Дирек­тива #define является морально устаревшей для С++.

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

 

Ссылки и указатели как параметры функции.

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

 

Пример 8.

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

 

void swap1 (int *a, int *b)

{ int tmp;

tmp=*a; *a= * b; *b=tmp;

}

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

swap1(&i, &j); // i, j — аргументы функции

 

В С++ можно использовать в функции ссылки (&) на изменяемые парамет­ры, а в теле функции и при вызове применяются имена параметров и аргумен­тов:

void swap2 (int &a, int &b)

{ int tmp=a; a=b; b=tmp; }

Вызов функции:

swap2 (i, j);

 

Список элементов класса

Список элементов представляет собой последовательность объявлений данных (любого типа, включая нумераторы, битовые поля и другие классы) и объявлений и определений функций, каждое из которых может иметь необяза­тельные модификаторы доступа. Определенные таким образом объекты называ­ют элементами (членами) класса (class members). Специфика­торы класса памяти auto, register, extern недопустимы. Элементы могут быть объявлены со спецификатором static.

Функция-элемент класса называется методом класса, а функция с моди­фикатором friend называется дружественной функцией. Данные класса – это то, что класс "знает " (пассивная часть класса), а методы – это то, что класс "умеет делать" (активная часть).

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

 

Существует два способа включения метода в класс.

Первый способ – метод может быть определен внутри класса как встраи­ваемая функция с одним-двумя операторами. Компилятор осуществляет макро­расширение таких функций, снижая накладные расходы, связанные с обращени­ем к функции и выходом из нее, например:

class Point

{ int X, Y;

int Getx() { return X; } // встроенная функция

}

 

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

class Point

{ int X, Y;

int Getx(); // объявление метода

}

int Point:: Getx() // определение метода

{ return X; } // вне класса

 

Имя Point:: Getx называется полным или квалифицированным именем метода класса. В С++ разные функции могут иметь функции с одинаковыми именами. Операция:: и имя класса позволяют компилятору определить принад­лежность функции конкретному классу.

Определение метода внутри класса (способ 1) не требует модификатора Point::, так как и так очевидно, что Getx принадлежит классу Point. Кроме того, модификатор Point:: перед Getx служит и другой цели. Он определяет, что переменная Х в операторе return X является собственным элементом класса Point, к которому можно обращаться непосредственно по имени.

Вывод. Тело метода Point::Getx() находится внутри области действия класса Point вне зависимости от физического расположения. Метод Getx() имеет доступ ко всем внутренним переменным класса Point.

 

Конструктор по умолчанию

Конструктор, не имеющий параметров, называется конструктором по умолчанию. Если отсутствует определенный пользователем конструктор по умолчанию, компилятор С++ генерирует собственный конструктор по умолча­нию для описанного класса – это конструктор, например, класса Х, который не принимает никаких аргументов X:: X().

Конструктор, как и любая функция, может иметь любое количество пара­метров, в том числе и параметры по умолчанию. Например, конструктор

X:: X (int, int=0)

может принимать один или два аргумента. Если будет только один аргумент, то недостающий второй аргумент будет принят как 0. Аналогично, конструктор

X:: X (int=5, int=6)

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

Однако конструктор по умолчанию X:: X() не принимает аргументов во­обще, и его не следует путать с конструктором, например, X:: X (int=0), кото­рый может либо принимать один аргумент, либо не принимать аргументов. При вызове конструкторов следует избегать неоднозначностей. В следующем приме­ре возможна неоднозначная трактовка компилятором конструктора по умолча­нию и конструктора, принимающего целый параметр.

Пример 15.

Неоднозначность конструкторов.

class X

{ public:

X();

X(int = 0);

};

void main()

{ X one (10); // так МОЖНО: используется конструктор X::X(int)

X two; // так НЕЛЬЗЯ: X::X() или X::X(int=0)?

}

Конструктор копирования

Это особый тип конструктора. Конструктор копирования для класса Х – это конструктор, который имеет один аргумент ссылочного типа X& и, возмож­но, параметры по умолчанию. Ссылочный тип тесно связан с типом указатель (X*) и позволяет передавать в функцию не сам объект, а только ссылку на него.

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

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

class X

{ public:

X() {...} // конструктор по умолчанию

X(const X&) {...} // конструктор копирования

X(const X&, int i=4) {...} // конструктор копирования с параметром // по умолчанию

};

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

X one; // вызов конструктора по умолчанию

X two = one; // вызов конструктора копирования

X two (one); // вызов конструктора копирования с параметром

По умолчанию, копия объекта класса содержит копию каждого члена, т.е. объект two будет полной копией объекта one. Аналогично, объекты класса мо­гут по умолчанию почленно копироваться при помощи операции присваивания:

X three;

three = one;

 

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

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

 

В рассмотренном выше примере классы X и Y по сути равноправны, но при использовании простого наследования класс Y отличается от класса X чис­лом параметров конструктора. Избавится от неравноправия классов можно, ис­пользуя множественное наследование. Суть его в том, что производный класс может быть наследником нескольких базовых классов, например, для классов X, Y, Z по схеме наследования (X, Y) <- Z, которая в программе может принимать вид:

class Z: public X, public Y

{ тело класса Z };

 

Пример 30.

Изменим программу примера 29 в соответствии со схемой множественно­го наследования (X, Y) <- Z.

 

#include<iostream.h>

#include<conio.h>

class X // базовый класс X для класса Z

{ protected: int x; // защищенный элемент х, доступный в Z

public: // прототипы открытых методов класса X:

X(int i); // конструктор с параметром класса X

~X(); // деструктор класса X

int getx () { return x; } // встроенная функция возврата x

void putx (int i) { x=i; } // встроенная функция задания x

void show(); // функция вывода на экран для класса Х

};

class Y // базовый класс Y для класса Z

{ protected: int y; // защищенная переменная, доступная в Z

public: // прототипы открытых методов класса Y:

Y(int i); // конструктор с параметром класса Y

~Y(); // деструктор класса Y

int gety () { return y; } // встроенная функция возврата y

void puty (int i) { y=i; } // встроенная функция задания y

void show(); // функция вывода на экран для класса Y

};

// Класс Z производный от Х и Y имеет доступ к переменным Х::x и Y::у:

class Z: public Х, public Y

{ protected: int z; // защищенная переменная z

public: // прототипы открытых методов класса Z:

Z(int i, int j); // конструктор с параметрами класса Z

~Z(); // деструктор класса Z

void makez (); // функция вычисления z

void show(); // функция вывода на экран для класса Z

};

// Описание методов:

X::X(int i) // конструктор с параметром класса X

{ x = i; cout<<"Конструктор X\n"; } // инициализация элемента х

X::~X() // деструктор класса X

{ cout<<"Деструктор X\n"; }

void X::show() // функция вывода на экран класса Х

{ cout<<" x = "<< x <<endl; }

Y::Y(int i): // конструктор с параметром класса Y

{ y = i; cout<<"Конструктор Y\n"; } // инициализация элемента у

Y::~Y() // деструктор класса Y

{ cout<<"Деструктор Y\n"; }

void Y::show() // функция вывода на экран класса Y

{ cout<<" y = "<< y <<endl; }

Z::Z(int i, int j):X(i), Y(j) // конструктор Z с параметрами i, j для классов X, Y

{ cout<<"Конструктор Z\n"; }

Z::~Z() // деструктор для класса Z

{ cout<<"Деструктор Z\n"; }

void Z::show() // функция вывода на экран класса Z

{ cout<<" z = "<< z <<endl; }

void Z::makez() // функция вычисления переменной z

{ z=x * y; } // с использованием переменных из X,Y

void main() // главная функция

{ clrscr(); // чистка экрана

Z zobj(3, 5);; // создан и инициирован объект класса Z

zobj.makez(); // вычисление z=x*y

zobj.show(); // вызов функции вывода для класса Z

zobj. X::show(); // вызов функции вывода для класса X!

zobj. Y::show(); // вызов функции вывода для класса Y!

zobj.putx(7); // изменение переменной х в классе Х

zobj.puty(9); // изменение переменной у в классе Y

zobj.makez(); // вычисление z=x*y

zobj.show(); // вызов функции вывода для класса Z

zobj. X::show(); // вызов функции вывода для класса X!

zobj. Y::show(); // вызов функции вывода для класса Y!

getch(); // задержка экрана результатов

}

Результаты программы:

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

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

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

z=15

x=3

y=5

z=63

x=7

y=9

Деструктор Z

Деструктор Y

Деструктор X

 

ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ

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

Разработка программного обеспечения (ПО) ЭВМ в настоящее время осу­ществляется с использованием двух основных технологий — структурного (процедурного) программирования и объектно-ориентированного программиро­вания (ООП).

 

Структурное программирование.

Данная технология предполагает вы­полнение последовательности этапов разработки программ для решения задач с ис­пользованием ЭВМ.

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

2. Формализация задачи с использованием математического аппарата и по­лучение ее абстрактной математической модели в виде формул и уравне­ний.

3. Выбор численного метода из возможных вариантов с учетом требований по времени и точности решения и занимаемого объема памяти ЭВМ.

4. Алгоритмизация – построение общего плана решения, т. е. алгоритма за­дачи в виде логической последовательности этапов (шагов, действий, операций), приводящих от исходных данных к искомому результату за ко­нечное время на языке понятном человеку. Могут быть использованы раз­личные способы представления алгоритма – словесный, графический (схемы алгоритмов), алгоритмический язык высокого уровня (ЯВУ). ЯВУ – формализованный язык для описания данных и набор правил (инструкций, операторов) их обработки для реализации алгоритма задачи. Типичные ЯВУ структурного программирования – Си, Паскаль.

5. Программирование – перевод алгоритма задачи на язык ЭВМ (систему команд), т.е. кодирование алгоритма. Процесс разработки программы де­лится на следующие этапы: 1) запись алгоритма на ЯВУ в виде исходного файла в памяти (например, prog1.cpp); 2) компиляция и редактирование свя­зей (объектный файл – prog1.obj); 3) загрузка программы в оперативную память (исполняемый файл – prog1.exe); 4) исполнение программы; 5) получение результатов программы.

6. Отладка программы – поиск и исправление ошибок в программе. Этот процесс разбивается на два этапа: 1) синтаксическая отладка – исправле­ние формальных ошибок, связанных с нарушением норм языка програм­мирования, с помощью ЭВМ; 2) семантическая отладка – исправление логических (смысловых) ошибок с применением специальных тестовых данных.

7. Исполнение (эксплуатация) программы с любыми допустимыми данными и получение результатов решения задачи.

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

Существование программы можно разделить на три периода:
1) разработка (этапы 1– 4); 2) реализация (этапы 5, 6); 3) сопровождение (этапы 7, 8).

Функциональная декомпозиция.

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

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

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

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

Концепции объектно-ориентированного программирования (ООП).

Идеи ООП впервые были выдвинуты и реализованы в 60-е годы 20-го века авто­рами языков Simula-67 (Дал и Нигард) и Smaltalk.

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

ООП базируется на следующих ключевых понятиях: 1) объект (object);
2) класс (class); 3) инкапсуляция (encapsulation); 4) наследование (inharitance);
5) полиморфизм (polimorphism); 6) абстракция типов (abstraction).

 

Раскроем эти понятия в общем плане.

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

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

Инкапсуляция – это объединение набора данных и структур данных с группой методов (функций) манипулирования этими данными в единое целое, называемое классом, в котором также определены механизмы защиты данных и функций, которые могут быть частными (private), общими (public), или защи­щенными (protected) и установлены права доступа к данным.

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

Например, СОБАКА

____________________|____________________

| | |

сто


Поделиться с друзьями:

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

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

История развития хранилищ для нефти: Первые склады нефти появились в XVII веке. Они представляли собой землянные ямы-амбара глубиной 4…5 м...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.398 с.