Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основы разработки программного обеспечения на примере языка С. Учебное пособие для СПО

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
{ i++; } } return i; } /****************************************************** * Name : processString * Purpose : encode words in string * leaving delimiters unchanged * Input : str (string), * Output : str (encoded string) * Return : none ******************************************************/ void processString(char *str) { int i; i = 0; while( *(str+i) != '\0' ) { if (isDelimeter(*(str+i))) { i++; } else { i = change231(str, i); } } } int main() { /* max length 80 + 1 for ending symbol + 1 to test >80 entered symbols */ char str[82]; printf("Введите строку для кодирования\n"); readString(str); while(*str != '\0') { if (testString(str) == 0)
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
111
{ printf("Ошибка во входной строке\n"); } else { processString(str); printf("%s\n", str); } printf("Введите строку для кодирования\n"); readString(str); } printf("Работа закончена"); return 0; }
Проверка корректности строки выделена в функцию testString, которая последовательно рассматривает каждый символ строки и проверяет, принадлежит ли он английскому алфавиту и/или русскому и/ или является цифрой и/или разделителем. В начале допускаются в качестве разделителей только пробелы, поэтому используется дополнительная переменная start. Исходно она равна нулю, и только после появления в строке символа алфавита и/или цифры она становится равна единице. Пока start равна нулю, допускается только пробел в качестве разделителя:
if ( (*str) == ' ' ) { goodSymb = 1; }
а когда start равна единице, допускается любой разделитель (точка, запятая, пробел):
if ( (isDelimeter(*str)) && start ) { goodSymb = 1; }
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
112
Кодирование строки выполнено в один проход. В данной задаче сложность алгоритма определяется количеством проходов по строке, поэтому их желательно минимизировать. Разделители остаются без изменения. Когда встречается слово, оно кодируется в функции
change231, в которую передается вся строка и позиция начала слова.
После кодирования слова функция change231 возвращает позицию конца слова (номер следующего за концом слова символа).
Сама перестановка символов выполнена последовательно попарно. Требуемый результат - 231 получается, если сначала переставить первый символ со вторым (получив 213) и далее второй с третьим (получив как раз 231). Такой подход оказывается справедлив и в случае, когда остается два символа (12 - 21), т.е. алгоритм перестановки сводится к работе лишь с двумя рядом стоящими символами (перемене их мест). Однако требуется перестановка только в рамках тройки символов, поэтому на каждом третьем шаге следует пропустить одну позицию:
if (((i-startPos)%3 == 0) && (!isDelimeter(str[i]))) i++;
Алгоритм основной программы уже был определен ранее.
1. Ввести входную строку.
2. Если строка пустая, то закончить работу.
3. Преобразовать входную строку.
4. Вывести преобразованную строку.
5. Ввести очередную входную строку и вернуться к действию (2).
Соответственно будет выглядеть и реализация.
1. Ввести входную строку:
printf("Введите строку для кодирования\n"); readString(str);
2. Если строка пустая, то закончить работу:
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
113
while(*str != '\0') { . . . } printf("Работа закончена"); return 0;
3. Преобразовать входную строку:
if (testString(str) == 0) { printf("Ошибка во входной строке\n"); } else { processString(str); printf("%s\n", str); }
4. Вывести преобразованную строку:
processString(str); printf("%s\n", str);
5. Ввести очередную входную строку и вернуться к действию (2):
while(*str != '\0') { ... printf("Введите строку для кодирования\n"); readString(str); }
Вопросы и задачи для самостоятельного решения
Как можно задать числовую константу в Си?
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
114
Сколько необходимо создать файлов, чтобы реализовать модуль в Си? Зачем нужны заголовочные файлы в Си? В каких случаях возможно переполнение буфера при вводе строки? Какими способами можно этого избежать? Напишите программу перевода десятичного числа в шестнадцатеричное. Входное число должно считываться из стандартного потока ввода (клавиатура). Перевод должен работать для чисел длиной не менее 100 цифр. Напишите программу кодирования слова методом перестановки символов 3-1-2. Входное слово должно считываться из стандартного потока ввода (клавиатура). Кодирование должно работать для слов длиной не менее 300.
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
115
Абстрактный тип данных
Все должно быть изложено так просто, как только возможно, но не проще.
Альберт Эйнштейн
Абстракция данных является мощным подходом при разработке программ, позволяющим объединить тип данных и множество операций, которые могут над ним выполняться. Этот подход дает возможность использовать тип данных не задумываясь о его реализации, зависящей от выбранных средств программирования и целевой машины. Более того, абстракция является одной из важнейших составных частей объектно-ориентированного программирования.
5.1. Понятие абстрактного типа данных
В основу самой абстракции положено выделение некоторых важных свойств исследуемого объекта и игнорирование несущественных.
Любой программный проект можно рассматривать с двух точек зрения:
1. абстрагируясь от деталей реализации, программа соответствует некоторой решаемой задаче реального мира;
2. с точки зрения реализации программа представляет собой последовательность операторов, предназначенных для решения задачи.
Можно говорить об абстракции уже на уровне объявления простых структур программы. Она включает в себя использование "говорящих" идентификаторов, таких как константы, пользовательские типы (структуры) данных, перечислимые типы с "говорящими" значениями констант. Правильный подход к наименованиям повышает читаемость программы, упрощает разработку, делает программу более надежной (уменьшает потенциальную возможность сделать ошибку), уменьшает время разработки и последующей модификации. Следующий код написан без использования "говорящих" имен:
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
116
A = B; C = do1(A.b); D = do1(A.a); if (C + D) == B.a return(A.b); else return("");
В случае применения более значимых имен код приобретает более понятную форму:
TextCRC = CountFieldCRC(tempMessage.Text); TimeCRC = CountFieldCRC(tempMessage.Time); if (NameCRC + TimeCRC) == tempMessage.Time) return(tempMessage.Text) ; else return("");
При проектировании и кодировании модули могут использоваться для скрытия представления внутренней структуры объектов. Такое обособление предоставляет возможность для реализации абстрактных типов, что приводит к значительному упрощению при написании программы, т.е. делает программу более простой, понятной и надежной. В дополнение к этому обеспечивается гибкость (и переносимость), поскольку части объектов, их представления и операции общего пользования могут быть изменены или заменены другим набором частей.
Суть такого абстрактного типа данных заключается в определении имени типа, некого множества допустимых для него значений и операций для манипулирования этими значениями. Абстрактный тип можно рассматривать как расширение языка программирования. Любой абстрактный тип обязательно включает в себя два компонента:
сам тип данных (способ представления и множество значений); набор операций, определенных для этого типа.
Таким образом, абстрактный тип является спецификацией нового, ранее не существовавшего типа. При импорте этот тип может использоваться для создания новых типов данных, которые будут использовать его в качестве шаблона.
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
117
Все типы языка программирования описываются теми же двумя компонентами.
Например, для типа BOOL множество допустимых значений имеет вид: {TRUE, FALSE}.
И над ними могут быть определены операции (функции):
AND, OR, NOT, EQ.
И мы уже рассматривали возможность определения подобных понятий средствами препроцессора языка Си. Однако для более сложных структур способом реализации абстрактного типа является модуль, "экспортирующий" определение типа и заголовки процедур для работы с ним в формате соответствующего h-файла.
Программу, которая использует абстрактный тип данных, называют клиентской, пользовательской или импортирующей программой. Такая программа может использовать тип, операции над абстрактным типом и, в свою очередь, описывать новые типы на его основе. При этом ей совсем не обязательно знать внутреннюю структуру типа или особенности его реализации, так как ей доступно множество операций над типом. Таким образом, достигается сокрытие подробностей, разделение использования типа от его реализации и внутреннего представления. Этот подход отделяет то, ЧТО представляет определенный тип, от того, КАК он это делает.
Вышеописанный подход позволяет разделить разработку клиентской программы и абстрактного типа данных и сделать их независимыми. В дополнение упрощается дальнейшая модификация - изменение реализации какой-либо операции абстрактного типа не требует переделки клиентской программы. Более того, мы можем менять представление внутренней структуры данных абстрактного типа, не меняя клиентскую программу.
Рассмотрим, например, в качестве абстрактного типа простую дробь. Дело в том, что многие процессоры специального назначения не реализуют операции над числами с плавающей точкой. Простой заменой им является определение типа "Дробь" (Fraction) - записи с
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
118
полями "Числитель" (Numerator) и "Знаменатель" (Denominator). При этом для простоты положим, что числитель будет хранить знак дроби, а знаменатель будет рассматриваться как беззнаковое целое:
typedef struct { int Numerator; unsigned int Denominator; } FRACTION;
Пока можно не уточнять, проводится ли сокращение дроби или дробь 4/8 для нас так же хороша, как 1/2.
Абстрактный тип данных является компонентом, который может быть повторно использован. "Хорошая" абстракция скрывает все незначительные детали, отражая лишь важнейшие свойства объекта. Кроме того, абстракция не требует знаний по ее использованию. Т.е. абстрактный тип не зависит от клиентских программ, которые его используют. В свою очередь клиентская программа не обязательно должна знать внутреннюю реализацию абстрактного типа. Таким об­разом, абстрактный тип можно рассматривать как "черный ящик". Эта его особенность упрощает построение больших и сложных программных систем, позволяя выносить пользовательские типы в библиотеки модулей и не "изобретать колесо" по нескольку раз.
5.2. Операции абстрактного типа данных
При проектировании абстрактного типа данных всегда необходимо решить две задачи:
1. определить представление и множество значений;
2. определить множество операций типа.
Абстрактный тип может описывать достаточно сложный объект реального мира, и все его значения представляют собой домен множеств возможных значений, который проще задавать в терминах отдельных элементов. Например, для абстрактного типа "Студент"
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
119
необходимо задать множество возможных значений его имени, его фамилии, номера группы, номера зачетки. Здесь имя, фамилия, номер группы и номер зачетки представляют собой элементы объекта "Студент". Задав длины элементов и наборы символов, из которых эти элементы могут составляться, можно говорить о том, что задано множество значений абстрактного типа "Студент".
Следующий шаг - это определение операций над типом. Можно определить достаточно много различных операций, однако их большое число будет загромождать и усложнять абстракцию. С другой стороны, необходимо предусмотреть достаточный набор операций для поддержания выбранного уровня абстракции.
Операции, которые выполняются над абстрактным типом данных, могут быть логически разделены на несколько групп:
операции-конструкторы (порождают все множество возможных значений типа); операции инициализации (начальные конструкторы, которым безразлично исходное значение объекта); операции создания, удаления (позволяют занять или освободить память объекта); операции копирования значения типа; операции-селекторы (обеспечивают доступ к частям объекта); операции сравнения значений; операции преобразования типов; операции ввода/вывода.
Любой абстрактный тип должен иметь операции, позволяющие строить значения этого типа. Эти операции называются операциями­конструкторами. Их должно быть достаточно для порождения всего множества значений типа. Простейшим примером является операция
x++. Как правило, подобные операции используют исходное состояние
объекта для порождения его нового состояния.
Среди конструкторов часто необходимо выделить начальные конструкторы - операции инициализации. Им безразлично, в каком состоянии находился объект. Они сами полностью определяют его значение.
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
120
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]