Статическое и динамическое представление данных. — КиберПедия 

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой...

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

Статическое и динамическое представление данных.

2017-07-09 309
Статическое и динамическое представление данных. 0.00 из 5.00 0 оценок
Заказать работу

С помощью определения

struct mixture

{

int ii;

long ll;

char cc [8];

};

задается состав структурного типа с названием struct mixture.

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

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

struct имя_структурного_типа

{

элементы_структуры; /* данные */

struct имя_структурного_типа * указатель;

};

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

 

10.5. Объединения и битовые поля

 

Объединения. Со структурами в «близком родстве» находятся объединения, которые вводятся (описываются, определяются) с помощью служебного слова union.

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

Пример:

union

{

char hh [2];

int ii;

} CC;

Здесь:

- union – служебное слово, вводящее тип данных «объединение» или объединяющий тип данных;

- CC – имя конкретного объединения;

- символьный массив char hh [2] и целая переменная int ii – элементы (компоненты) объединения.

Схема размещения объединения CC в памяти ЭВМ приведена на рисунке.

Для обращения к элементу объединения используются те же конструкции, что и для обращения к элементу структуры:

имя_объединения.имя_элемента

(* указатель_на_объединение).имя_элемента

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

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

Пример:

CC.ii = 15;

записывает значение 15 в объединение, а с помощью конструкций CC.hh[0], CC.hh[1] можно получить отдельные байты внутреннего представления целого числа 15.

 

Как и данные других типов, объединение – это конкретный объект, которому выделено место в памяти. Размеры участка памяти, выделяемого для объединения, должны быть достаточны для размещения самого большого элемента объединения. В нашем примере элементы int ii и char hh[2] имеют одинаковую длину, но это не обязательно. Основное свойство объединения состоит в том, что все его элементы размещаются он начала одного и того же участка памяти. А размеры участка памяти, отводимого для объединения, определяются размером самого большого из элементов. Например, для объединения

union

{

double dd;

float aa;

int jj;

} uu;

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

sizeof(uu) == sizeof(double)

Объединяющий тип. Именованный объединяющий тип вводится с помощью определения такого вида:

union имя_объединяющего_типа

{

определения_элементов

};

где union –спецификатор типа;

имя_объединяющего_типа – выбираемый программистом идентификатор;

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

Конструкция union имя_объединяющего_типа играет роль имени типа, т.е. с ее помощью можно вводить объединения-объекты.

Как и для структурных типов, с помощью typedef можно вводить обозначения объединяющих типов, не требующие применения union:

typedef union имя_объединяющего_типа

{

определения_элементов

} обозначение_объединяющего_типа;

Пример:

typedef union uni

{

double dou;

int in [4];

char ch[8];

} uni_name;

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

union uni snow, ray;

uni_name yet, zz4;

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


 

Глава 11. Ввод и вывод

 

11.1. Потоковый ввод-вывод

 

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

Поток – это файл вместе с предоставляемыми средствами буферизации.

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

· открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами);

· вводить и выводить символ, строку, форматированные данные, порцию данных произвольной длины;

· анализировать ошибки потокового ввода-вывода и условие достижения конца потока (конца файла);

· управлять буферизацией потока и размером буфера;получать и устанавливать указатель (индикатор) текущей позиции в потоке.

 

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

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

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

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

#include <stdio.h>

FILE *fp;

Указатель на поток приобретает значение в результате выполнения функции открытия потока:

fp = fopen(имя_файла, режим_открытия);

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

fp = fopen(“t.txt”, “r”);

где t.txt – имя некоторого файла, связанного с потоком;

r – обозначение одного из режимов работы с файлом (тип доступа к потоку).

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

“w” - новый текстовый файл открывается для записи. Если файл уже существовал, то предыдущее содержимое стирается, файл создается заново;

“r” - существующий текстовый файл открывается только для чтения;

“a” - текстовый файл открывается (или создается, если файла нет) для добавления в него новой порции информации (добавление в конец файла). В отличие от режима “w” этот режим позволяет открывать уже существующий файл, не уничтожая его предыдущей версии, и писать в продолжение файла;

“w+” - новый текстовый файл открывается для записи и последующих многократных исправлений. Если файл уже существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из него допустимы в любом месте файла, в том числе запись разрешена и в конец файла, т.е. файл может увеличиваться («расти»);

“r+” - существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла; однако в этом режиме невозможна запись в конец файла, т.е. недопустимо увеличение размеров файла;

“a+” - текстовый файл открывается или создается (если файла нет) и становится доступным для изменений; т.е. для записи и для чтения в любом месте. При этом в отличие от режима “w+” можно открыть существующий файл и не уничтожать его содержимого, в отличие от режима “r+” в режиме “a+” можно вести запись в конец файла, т.е. увеличивать его размеры.

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

В текстовом режиме прочитанная из потока комбинация символов CR (13) и LF (10) (возврат каретки и перевод строки) преобразуются в один символ новой строки “\n”. При записи в поток в текстовом режиме осуществляется обратное преобразование, т.е. символ новой строки “\n” заменяется последовательностью CR LF.

Если файл, связанный с потоком, хранит не текстовую, а произвольную двоичную информацию, то указанные преобразования не нужны и могут быть даже вредными. Обмен без такого преобразования выполняется при выборе двоичного или бинарного режима, который обозначается буквой b. Например, “r+b”, “wb”.

Если поток открыт для изменений, т.е. в параметре режима присутствует символ “+”, то разрешены как вывод в поток, так и чтение из него. Однако смена режима (переход от записи к чтению и обратно) должна происходить только после установки указателя потока в нужную позицию.

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

Пример:

if ((fp = fopen(“t.txt”, “w”)) == NULL)

{

perror(“ошибка при открытии файла t.txt \n”);

exit(0);

}

NULL – нулевой указатель, определенный в файле stdio.h

perror() – стандартная библиотечная функция для вывода на экран сообщения об ошибке при открытии потока. Текст сообщения об ошибке выбирается функцией на основании номера ошибки. Номер ошибки заносится в переменную int errno (определенную в заголовочном файле errno.h).

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

int fclose (указатель_на_поток);

 

11.2. Стандартные файлы и функции для работы с ними

 

Когда программа начинает выполняться, автоматически открываются пять потоков, из которых основными являются:

§ стандартный поток ввода (на него ссылаются, используя предопределенный указатель на поток stdin;

§ стандартный поток вывода (stdout);

§ стандартный поток вывода сообщений об ошибках (stderr).

По умолчанию стандартному потоку ввода sdtin ставится в соответствие клавиатура, а потокам stdout, stderr соответствует экран дисплея.

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

§ getchar() / putchar() – ввод-вывод отдельного символа;

§ gets() / puts() – ввод-вывод строки;

§ scanf() / printf() – ввод-вывод в режиме форматирования данных.

Пример:

#include <stdio.h>

int main()

{

printf(“a”);

getchar(); /* 1 */

printf(“b”);

getchar(); /* 2*/

printf(“c”);

getchar(); /* 3 */

return 0;

}

Сначала на экран выводится символ «а», и работа программы приостанавливается до ввода очередного символа. Если, как это делается обычно, нажать, например, клавишу <g> и затем Enter, то на следующей строке появятся символы bc, и программа завершит свою работу. Первая (в программе) функция getchar() прочитала из внутреннего буфера код символа и следующая за ней функция printf() вывела на экран символ ‘b’. Остановки работы программы не произошло, потому что вторая функция getchar()прочитала код клавиши Enter из внутреннего буфера, а не очередной символ с клавиатуры. Произошло это потому, что к моменту вызова функции getchar() внутренний буфер не был пуст.

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

#include <stdio.h>

int main()

{

printf("a");

getchar(); /* 1 */

printf("b");

while (getchar()!= '\n') /* очистка буфера */

;

getchar(); /* 2*/

printf("c");

while (getchar()!= '\n') /* очистка буфера */

;

getchar(); /* 3 */

return 0;

}

 

11.3. Форматный ввод-вывод

 

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

printf()

scanf()

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

Список аргументов (с предшествующей запятой) может отсутствовать.

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

% флаги ширина_поля.точность модификатор спецификатор

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

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

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

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

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

# если этот флаг используется с форматами «0», «х» или «Х», то любое ненулевое значение выводится с предшествующим 0, 0х или 0Х соответственно. При использовании флага # с форматами “f”, “g”, “G” десятичная точка будет выводиться, даже если в числе нет дробной части.

Примеры:

“%+d” - вывод знака «+» перед положительным целым десятичным числом;

“% d” - добавление (вставка) пробела на месте знака перед положительным числом (использован флаг пробел после символа %);

“%#0” - печать ведущих нулей в изображениях восьмеричных чисел.

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

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

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

Любые символы, которые появляются в форматной строке и не входят в спецификации преобразования, модифицируются в выходной поток. Этим обстоятельством можно воспользоваться для вывода явных разделителей между столбцами таблицы. Например, для этой цели можно использовать символ «*» или «||». В этом случае форматная строка в функции printf() будет выглядеть так: “%-3d | %5d | %-20s | %8.3fn”

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

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

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

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

Функция scanf() завершает работу, если исчерпана форматная строка. При успешном завершении scanf() возвращает количество преобразованных и введенных полей (точнее, количество объектов, получивших значения при вводе). Значение EOF возвращается при возникновении ситуации «конец файла»; значение –1 – при возникновении ошибки преобразования данных.

Форматная строка ограничена двойными кавычками и может включать:

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

- обычные символы, отличные от пробельных и символы %. Обработка обычного символа из форматной строки сводится к чтению очередного символа из входного потока. Если прочитанный символ отличается от обрабатываемого символа форматной строки, функция завершается с ошибкой. Несовпавший символ и следующие за ним входные символы остаются непрочитанными;

- спецификации преобразования.

 

11.4. Работа с файлами на диске

 

Для этой цели в библиотеку языка Си включены следующие функции:

fgetc(), getc() – ввод (чтение) одного символа из файла;

fputc(), putc() – запись одного символа в файл;

fprintf() – форматированный вывод в файл;

fscanf() – форматированный ввод (чтение) из файла;

fgets() – ввод (чтение) строки из файла;

fputs() – запись строки в файл.

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

c = getc(fc);

putc(c, fp);

где fp – указатель на поток;

с – переменная типа int для приема очередного символа из файла или для записи ее значения в файл.

Строковый обмен с файлами. Для работы с текстовыми файлами удобно использовать функции fgets() и fputs().

Прототипы этих функций:

int fputs(const *s, FILE *streem);

char * fgets(char *s, int n, FILE *streem);

Функция fputs() записывает ограниченную символом ‘0\’ строку (на которую указывает s) в файл, определенный указателем streem на поток, и возвращает неотрицательно целое. При ошибках функция возвращает EOF. Символ ‘0\’ в файл не переносится, и символ ‘n\’ не записывается в конце строки вместе ‘0\’.

Функция fgets() читает из определенного указателем streem файла не более (n-1) символов и записывает их в строку, на которую указывает s. Функция прекращает чтение, как только прочтет (n-1) символов или встретит символ новой строки ‘\n’, который переносится в строку s. Дополнительно в конец каждой строки записывается признак окончания строки ‘0\’. В случае успешного завершения функции возвращает указатель s. При ошибке или при достижении конца файла, пи условии, что из файла не прочитан ни один символ, возвращается значение NULL. В этом случае содержимое массива, который адресуется указателем s, остается без изменений.

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

int fprintf(указатель_на_поток, форматная_строка, список_переменных);

int fscanf(указатель_на_поток, форматная_строка, список_адресов_переменных);

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

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

Пример: программа, создающая файл int.dat и записывающая в него символьные изображения числе от 1 до 10.

 


#include <stdio.h>

int main()

{

FILE *fp; /* указатель на поток */

int n;

if ((fp = fopen(“int.dat”, “w”)) == NULL)

{

perror (“int.dat”);

return 1;

}

for (n = 1; n < 11; n ++)

fprintf(fp, “%d %d\n”, n);

fclose(fp);

return 0;

}

Позиционирование в потоке.

Операции чтения (или записи) для потока всегда производятся, начиная с текущей позиции в потоке. Начальная позиция чтения/записи в потоке устанавливается при открытии потока и может соответствовать начальному или конченому байту потока в зависимости от режима открытия потока. При открытии потока в режимах ‘r’, 'w' указатель текущей позиции чтения/записи в потоке устанавливается на начальный байт потока, а при открытии в режиме ‘a’ - конец файла (за конечным байтом). При выполнении каждой операции ввода-вывода указатель текущей позиции в потоке перемещается на новую ткущую позицию в соответствии с числом прочитанных (записанных) байтов.

В библиотеку языка Си включена функция fseek() для перемещения (установки) указателя текущей позиции в потоке на нужный байт потока (файла). Она имеет следующий прототип:

int fseek(указатель_на_поток, смещение, начало_отсчета);

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

SEEK_SET (имеет значение 0) – начало файла;

SEEK_CUR (имеет значение 1) – текущее позиция;

SEEK_END (имеет значение 2) – конец файла.

Функция fseek() возвращает 0, если перемещение в потоке (файле) выполнено успешно, в противном случае возвращается ненулевое значение.

Пример: перемещение к началу потока (файла) из произвольной позиции

fseek(fp, 0L, SEEK_SET);

 

11.5. Ввод-вывод нижнего уровня

 

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

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

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

В библиотеку языка Си включены следующие основные функции ввода-вывода нижнего уровня:

§ open() / close() – открыть / закрыть файл;

§ creat() – создать файл;

§ read() / write() – читать / писать данные;

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

§ eof() – проверить достижение конца файла;

§ lseek() – изменить текущую позицию в файле;

§ tell() – получить значение текущей позиции в файле.

Функции нижнего уровня в отличие от функций для работы с потоком не требуют включения в программу заголовочного файла stdio.h. Однако, этот файл содержит определения ряда констант (например, признак файл EOF), которые могут сказаться полезными. В случае применения этих констант файл stdio.h должен быть включен в программу.


 

Глава 12. Объектно-ориентированное программирование

 

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

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

 

12.1. Теоретические основы ООП

 

12.1.1. Процедурные и объектные методы программирования

 

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

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

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

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

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

 

Рис. 27. Архитектура программы, использующая глобальную область данных.

 

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

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

 

 

Рис. 28. Архитектура программы, использующая подпрограммы с локальными данными.

 

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

Также возникла проблема согласования интерфейса при ведении разработки несколькими программистами.

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

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

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

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

2. Структурное программирование, рекомендующее определённые структуры алгоритмов и стиль программирования (чем нагляднее текст программы, тем меньше вероятность ошибки).

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

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

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

 

12.1.2. Появление модульного программирования.

 

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

Достоинства:

1. Упрощает разработку ПО несколькими программистами. (Внутренняя структура модуля скрыта от остальных и, следовательно, может меняться независимо).

2. Взаимодействие осуществляется через специальные интерфейсы.

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

Недостатки:

1. Ошибка в интерфейсе при вызове подпрограммы появится только при выполнении программы (из-за раздельной компиляции модулей эти ошибки не обнаруживаются раньше).

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

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

 

Рис. 29. Модули с локальными данными. Архитектура модульной программы.

 

12.2. Основные принципы и этапы ООП

 

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

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

Такое представление впервые используется на языке имитационного моделирования сложных систем Simula (60-е годы), далее SmallTalk (70-е годы). Затем Pascal, C++, Ada, Modula.

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

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

 

Рис. 30. Архитектура программы при ООП.

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

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

2) Ограничение доступа - сокрытие отдельных элементов реализации абстракции, не затрагивающих существенных характеристик ее как целого.

Необходимость ограничения доступа предполагает разграничение двух частей в описании абстракции:

a) интерфейс – совокупность доступных извне элементов реализации абстракции (основные характеристики состояния и поведения);

b) реализация – совокупность недоступных извне элеме


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

Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...

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

Автоматическое растормаживание колес: Тормозные устройства колес предназначены для уменьше­ния длины пробега и улучшения маневрирования ВС при...

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



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

0.178 с.