Рейтинг@Mail.ru

 

 

 

 

 

 

.: Учебник по практическому программированию ( Бейсик, Си, Паскаль ) :.

<< НазадСодержаниеВперед >>

маркированный список Глава 3. Обработка текстовой информации
маркированный список Символьные данные и их внутреннее представление
маркированный список Ввод и вывод текстовой информации
маркированный список Обработка фрагментов строк
маркированный список Сравнение и сортировка текстовых данных
маркированный список Управление цветом в текстовом режиме
маркированный список Задачи, советы и ответы

Глава 3.

Обработка текстовой информации

Символьные данные и их внутреннее представление

Символьная (текстовая) информация — самый простой тип данных с точки зрения его представления в памяти ЭВМ. Каждому символу текста в памяти соответствует байт с 8-разрядным кодом этого символа в том или ином стандарте. Буквам латинского алфавита, цифрам, знакам операций и различным разделителям (скобки, точки, запятые и т. п.) в некотором смысле повезло больше, т. к. их кодировка практически универсальна. Она предложена фирмой IBM и составляет первую половину большинства 8-разрядных кодировочных таблиц, используемых в разных странах. В терминологии IBM PC такие таблицы принято называть кодовыми страницами. Вторая половина кодовых страниц отведена под национальную символику.

В отечественной практике чаще всего приходится иметь дело либо с символикой MS-DOS в стандарте ASCII (American Standard Code for Information Interchange), либо с одной из кодовых страниц ANSI (American National Standard Institute), применяемых в среде Windows. Между этими таблицами существует принципиальное различие, связанное с кодировкой букв русского алфавита.

В таблице ASCII (кодовая страница 866) заглавные русские буквы начинаются со 128-й позиции. Вплотную вслед за ними располагается цепочка строчных букв от буквы а (код — 160) до буквы п (код — 175). Далее следуют символы псевдографики, используемые для формирования таблиц с одинарными и двойными линиями (диапазон кодов от 176 до 223). Начиная с 224-й позиции располагаются остальные буквы от р до я. И наконец, вслед за ними, выбиваясь из алфавитного порядка, размещены буквы Ё (код -240) и ё (код — 241).

В таблице ANSI (кодовая страница 1251) коды русских букв начинаются с позиции 192 (код буквы А) и расположены сплошным интервалом до 255-й позиции (код буквы я). Символы псевдографики в графической оболочке Windows смысла не имеют и поэтому в таблице ANSI отсутствуют. Буквы Ё и ё здесь тоже не включены в общепринятую алфавитную последовательность и имеют, соответственно, коды 168 и 184. Более того, их обозначение на большинстве русифицированных клавиатур отсутствует, и для включения таких букв в набираемый текст приходится нажимать самую левую клавишу в верхнем ряду (на латинском регистре этой клавише соответствует символ "~").

Все достаточно развитые алгоритмические языки включают серию процедур по обработке символьных (каждый объект — отдельный символ) и строковых (цепочки символов) данных. В системе QBasic текстовые данные находятся в символьных переменных, имена которых либо заканчиваются знаком $, либо начинаются с одной из букв, упомянутых в объявлении вида DEFSTR c-J, либо описаны в операторе DIM сочетанием ...AS STRING. Базовым текстовым элементом в Си являются объекты типа char, значением каждого их которых является единственный символ. Объединение таких объектов в одномерный массив позволяет работать с цепочками символов. Паскаль обеспечивает работу как с одиночными символами (данные типа char), так и с их последовательностями (данные типа string).

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

В Си это вытекает из способа представления текстовой информации. Начальное присвоение (char name [5]="Вася";) или копирование одной строки в другую (strcpy (name, "Вася");) эквивалентно 5 обычным операторам присваивания:

name[0] = 'B' ;

name[1]='а';

name[ 2 ] ='с' ;

name[ 3 ] ='я' ;

name[4]='\0';

Паскаль позволяет одновременно обращаться и к переменной типа string целиком и к отдельным ее символам как к значениям элементов одномерного массива:

const

name : string[4]='Вася';

writeln(name[l], name);

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

С точки зрения внутреннего представления текстовых данных в памяти ЭВМ в языках программирования преобладают два похода. В первом случае перед цепочкой символов располагается один (Паскаль) или два (QBasic) байта, в которых хранится общее количество символов — длина цепочки. Для неинициированных данных она обычно равна 0. Во втором случае (Си) вслед за цепочкой символов располагается байт с признаком конца текста (обычно это — нулевой код). Первый подход более прост с точки зрения программирования соответствующих процедур обработки строк. Однако он накладывает ограничение на максимальную длину строкового объекта, например, в Паскале длина строки не должна превышать 255 символов. Для преодоления этого недостатка и обеспечения совместимости по данным с другими системами программирования в последних версиях Паскаля были введены объекты типа pchar.

Ввод и вывод текстовой информации

Наиболее простые средства ввода символьных и строковых данных предлагают Паскаль и QBasic. Здесь достаточно указать в списке ввода имя переменной соответствующего типа:

INPUT A$

ИЛИ

var

c1:char;

s1:string;

readln(cl);

readln(s1);

В Си имеется довольно много возможностей для ввода одиночных символов и цепочек строк. Форматный ввод с помощью функции scanf использует для этой цели два спецификатора:

"%s" - при вводе строковых значений в массив типа char; "%с" - при вводе одиночных символов в переменную типа char. Например:

char c1,s1l[80];

scanf("%c",&c1l); // не забывайте указывать адрес переменной

scanf("%s",s1); // имя массива одновременно является адресом

Ввод символьных и строковых данных завершается после нажатия клавиши <Enter>, однако в переменную d поступит только один символ, а в массив si при этом будет введена начальная цепочка символов до первого пробела. В операторе форматного ввода пробел или несколько подряд идущих пробелов рассматриваются как признак конца информации, считываемой из очередного поля. Конечно, при наборе вы можете набрать в одной строке несколько символов или несколько слов, разделенных пробелами. Все они будут введены в системный буфер, выделяемый для работы функции scant (стандартный размер этого буфера позволяет набрать строку длиной до 128 символов, включая и разделители-пробелы). При последующих обращениях к функции ввода данные из буфера продолжают извлекаться до тех пор, пока не будут исчерпаны, после чего вновь придется продолжить набор на клавиатуре.

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

int c1;

c1=getch(); //Ввод кода нажатой клавиши без отображения

//соответствующего символа на экране

c1=getche(); //Ввод кода нажатой клавиши с соответствующего

//символа на экране

c1=getchar(); //Ввод кода нажатой клавиши вслед за нажатием

//клавиши Enter

Обратите внимание на то, что вводимый символ передается не в переменную типа char, а в двухбайтовую целочисленную переменную. Именно такие значения возвращают указанные выше функции. Вторая особенность их применения связана с разбиением клавиатуры на две категории клавиш — отображаемые и управляющие. Окраска клавиш не совсем точно передает принадлежность клавиши той или иной группе. Например, функциональные клавиши <F1>, <F2> ... <F12> имеют разный цвет, но все они относятся к категории управляющих. А клавиша <Enter>, несмотря на серую окраску, причислена к разряду обычных.

Дело в том, что при нажатии обычной клавиши в буфер входного потока (stdin) поступает единственный байт с ненулевым кодом. Именно он и извлекается при первом же обращении к одной из функций getch, getchar или getche. От нажатия управляющих клавиш в буфер stdin поступают два байта, первый из которых содержит нулевой код, а второй представляет уже собственно числовой код, соответствующий выбранной клавише. Для извлечения второго байта к этим функциям приходится обращаться повторно, и таким образом программа может проанализировать сложившуюся ситуацию.

Есть некоторые нюансы и при нажатии клавиши <Enter>. Так, функция getch сообщает, что нажата клавиша с числовым кодом 13, а функция getchar считает, что последним введен символ с кодом 10. На самом деле она перекодирует код клавиши <Enter> в символ LF (line feed -- строка заполнена), действительно имеющий код 10 (0Х0А) в таблице ASCII. По-разному воспринимают эти функции и комбинацию <Ctrl>+<z>, сообщая в

одном случае, что поступил признак конца файла (end-of-file) с кодом 26, а в другом — что встретился признак EOF, которому соответствует числовой код — 1. Функция getchar не позволяет ввести код клавиши <Esc>, тогда как функции getch и getche успешно справляются с этой клавишей.

Зато с функций gets, осуществляющей ввод строки, у вас никаких проблем не возникнет:

gets (s);

Вы можете набирать любые предложения, содержащие любое количество пробелов, и все они будут введены в строку s. Однако длина вводимой строки ограничена емкостью буфера (128 байт).

Дополнительные возможности по вводу текстовых данных связаны с использованием потоков:

#include <iostream.h>

char c1, s1[80];

cin >> c1;

cin >> s1;

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

PRINT А$,"Вася"

или

var

cl:char;

si:string;

writeln (c1, s1, .'Вася') ;

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

printf("%с Вася %s",c1,s1);

Вывод отдельного символа или одиночной строки в Си можно выполнить и c помощью

putchar (c1)

и

puts (s1)

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

LOCATE col, row 'Так это выглядит на QBasicgotoxy(col,row);

// А так делается на Си и Паскале

Параметр col задает номер колонки в диапазоне от 1 до 80, а второй аргумент (row) определяет номер строки в диапазоне от 1 до 25.

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

writelnt'Вася':10,c1:k+5,s1:7);

В QBasic для указания ширины поля используется оператор PRINT USING:

PRINT USING "##### ### #######";"Вася",А$,В$

В Си ширина поля включается в спецификатор формата ("%3с %10s"). Однако здесь имеется дополнительная возможность указать, что выводимое значение требуется прижать к левой границе выделенного поля ("%-Зс" %-10s").

Обработка фрагментов строк

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

В QBasic для выделения фрагментов используют системные функции LEFT? (выделение левой подстроки), RIGHTS (выделение правой подстроки) и MID$ (выделение внутренней подстроки). Последняя функция может выступать и в роли оператора, заменяющего старый фрагмент новым значением. Например:

LЕFТ$("Вася",1) 'выделяет "В"

LЕFТ$("Вася",2) 'выделяет "Ва"

RIGHT$("Вася",1) 'выделяет "я"

RIGHT?("Вася",2) 'выделяет "ся"

MID$("Вася",2,2) 'выделяет "ас"

MID$("Вася",3)="илиса" 'заменяет на "Василиса"

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

В Паскале для выделения подстроки используется функция сору, аналогичная функции мю$:

s2 := copy('Вася',2,3); {выделяется 'ася'}

Для удаления или вставки фрагмента здесь используются процедуры delete

И insert: delete(s1,start,len);{удаляется len символов, начиная с позиции start} insert(s1,s2,start);{в строку s2, начиная с позиции start, вставляется строка s1}

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

strcpy(s1,s2); //копирует строку s2 в строку s1

strncpy(s1,s2,n); //копирует первые п символов из строки s2 в s1

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

strcpy(s1, &s2[k]); //копирует правую подстроку из s2 в s1

strncpy(s1, &s[2],n); //копирует среднюю подстроку из s2 в s1

Длина строк в рассматриваемых системах программирования определяется одной из системных функций LEN (QBasic), Length (Паскаль) или strlen (Си). Единственным аргументом у каждой из них является анализируемая строка.

Для конкатенации (объединения) строк в Паскале и QBasic используется довольно естественная операция сложения:

А$="Здравствуй, "+NAME$+"!" s1:='3дравствуй, '+nаmе+'!'

В Си эта операция реализуется с помощью одной из следующих функций:

strcat(s1, s2); //добавляет s2 к s1

strncat(s1, s2, n); //добавляет и первых символов из s2 к s1

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

Функция, определяющая в QBasic, входит ли значение строки А2$ в строку А1$, имеет вид:
INTSR(A1$,A2$) или INSTR(k,Al$,А2$)
В первом случае анализ вхождения ведется с начала строки AI$, во втором случае — начиная с k-й позиции строки AI$. Последний вариант позволяет последовательно определить все вхождения искомого образца.
Примерно такими же возможностями обладает функция pos(si,s2) в Паскале. Для поиска повторного вхождения можно удалить уже исследованный фрагмент и снова обратиться к функции роз.
Гораздо более разнообразные варианты поиска вхождений предлагает Си:

strstr (s1,s2); //ищет вхождение строки s2 в s1

strchr(s1,с); //ищет вхождение символа "с" с начала строки s1

strrchr(s1,с); //ищет вхождение символа "с" с конца строки s1

strpbrk(s1,s2); //ищет вхождение любого символа из s2 в s1

strspn(s1,s2); //ищет вхождение любого фрагмента, составленного

//из символов s2 в s1

Сравнение и сортировка текстовых данных


Операции сравнения отдельных символов или строк основаны на последовательном анализе отношений числовых значений соответствующих кодов. В кодовых страницах символы букв упорядочены в соответствии с их расположением в латинском или национальном алфавитах. Поэтому код буквы А меньше кода буквы F, код буквы г меньше кода буквы ю и т. д.
Некоторое неудобство вызывает тот факт, что одноименные заглавные и строчные буквы имеют разные коды — в одной клетке кодировочной таблицы можно разместить только один символ, кроме того, заглавные и строчные буквы имеют разный смысл. Это не позволяет напрямую упорядочить слова в соответствии с их лексикографическим расположением в словарях. Поэтому приходится предварительно заменять коды всех малых букв в тексте на коды больших (или наоборот) и только после этого выполнять операцию сравнения. Такая замена для букв латинского алфавита особых проблем не представляет, т. к. смещение между кодами соответствующих заглавных и строчных букв — величина постоянная. А ьот с русскими буквами приходится поооэнтьсл — D кодировке ASCII цепочка строчной буко между пир разорвана символами псевдографики, а буквы вне вообще находятся "не на своих местах.

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

QBasic позволяет преобразовывать содержимое символьных строк к верхнему (UCASE$(A$>) или нижнему (LCASE$(A$)) регистру. В Паскале имеется только одна функция upCase (с), заменяющая строчную букву заглавной. Но коды символов, не принадлежащих множеству букв латинского алфавита, она оставляет без изменения. Такая же оговорка распространяется на функции strupr(s) Hstrlwr(s) В Си.

В Паскале и QBasic операции сравнения символьных данных ничем не отличаются от аналогичных операций над числовыми величинами:

RЕМ Упорядочение слов по "возрастанию"

IF A$>B$ THEN

ТМР$=А$ : A$=BS : В$=ТМР$

END IF

или

{Упорядочение слов по "убыванию"}

if s1<s2 then

begin

tmp:=sl;

sl:=s2;

s2:=tmp;

end;

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

strcmp(s1,s2); //сравнивает строки s1 и s2

strcmpi(s1,s2); //сравнивает s1 и s2 с игнорированием

//разницы между большими и малыми буквами

stricmp(s1,s2); //эквивалентна функции strcmpi

strncmp(s1,s2,k); //сравнивает первые k символов в s1 и s2

strncmpi(s1,s2,k); //сравнивает первые k символов в s1 и s2

//с игнорированием разницы между большими

//и малыми буквами

strnicmp(s1,s2,k); //эквивалентна функции strncmpi

Управление цветом в текстовом режиме

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

  • существует несколько текстовых режимов работы монитора, различающихся по цветовой гамме, а также по числу строк и столбцов, отображаемых на экране;
  • содержимое экрана отражает состояние активной страницы видеопамяти;
  • каждой символьной позиции экрана (так называемому знакоместу) в видеопамяти соответствуют 2 байта, в первом из которых находится код ASCII отображаемого символа, а во втором — атрибуты, управляющие цветом пикселов переднего плана (контуры буквы) и фоновых пикселов.

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

В любом руководстве приводится следующее описание формата байта цветовых атрибутов:

7
6
5
4
3
2
1
0
В
b
b
b
I
f
f
f

Самый старший бит управляет режимом мерцания символа, которое осуществляется аппаратным способом примерно с частотой 1 раз в секунду при B=1. Три следующих бита (ььь) представляют код цветности фона, который должен принадлежать интервалу [0, 7]. Четверка младших битов (Ifff) определяет код цветности переднего плана. Среди них особо выделяют бит I, единичное значение которого обычно соответствует повышенной яркости, т. е. цвету, сопровождаемому приставкой "ярко" или "светло".

Из сказанного выше следует, что стандартный текстовый режим на цветном мониторе позволяет задавать для каждого символа один из 8-ми фоновых оттенков и один из 16-ти цветов, выделяющих контур символа. А если вы хотите вывести мерцающий текст, то байты атрибутов соответствующих символов должны быть увеличены на 128.

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

В Си и Паскале код цветности символов задается с помощью процедуры textcoior (fc). Значение фонового цвета изменяется процедурой textbackground(bc). В этих же системах программирования существует и более универсальная процедура textattr, позволяющая за одно обращение изменить оба цвета и установить признак мерцания:

textattr(В + bc*16 + fc);

Следует отметить, что при работе с мониторами SVGA сравнительно давно разработанные системы Borland C++ 3.1 и Turbo Pascal 7.0 не очень точно следуют ограничениям, вытекающим из структуры байта цветовых атрибутов. В указанных выше процедурах вы можете задавать цвет фона из диапазона [0, 15], а эффект мерцания символов может и не наблюдаться.

Кроме того, в Си для вывода разноцветного текста следует использовать не стандартную функцию вывода printf, а функцию cprintf, использующую "прямое" обращение к видеопамяти.

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

COLOR fc, be

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

Задачи, советы и ответы

Задание 3.01. Формирование таблицы ASCII

Сформировать на экране таблицу ASCII таким образом, чтобы в ней были представлены все отображаемые символы 866-й кодовой страницы и их числовые коды. Один из возможных вариантов заполнения экрана такой таблицей представлен на рис. 3.1.

Совет 1 (общий)

Приведенная выше таблица содержит 21 кодовую строку, и смещение начальных кодов в смежных колонках равно 21. Поэтому внешний цикл повторяется 21 раз, начинаясь с первого отображаемого кода (32 — код пробела). Так как заголовок отделен от таблицы одной пустой строкой, вывод начинается с 3-й строки (3 = 32 - 29). А дальше остается только перебирать все коды отображаемого диапазона (от 32 до 255) и выводить пары "символ-код" по фиксированному формату — одна позиция под символ, разделяющий пробел, три позиции — под числовой код и пара пробелов между колонками.

Рис. 3.1. Таблица ASCII

Программа 3_01.bas

RЕМ Вывод таблицы ASCII

CLS : PRINT TAB(31); "Таблица ASCII"

FOR I=32 TO 52: LOCATE I-29,1

FOR J=I TO 255 STEP 21

PRINT USING "! ### ";CHR$(J);J;

NEXT J

NEXT I

Программа 3_01.с

/* Вывод таблицы ASCII */

#include <stdio.h>

main() {

int i,j;

clrscr ();

gotoxy(31,1);

printf("Таблица ASCII");

for (i=32; i<=52; i++) {

gotoxy(1,i —2 9) ;

for (j=i; j<=255; j+=21)

printf("%c %3d ",j,j);

} getch();

}

Программа 3_01.pas

program ASCII; {Вывод таблицы ASCII} uses Crt; var

i,j:word; begin clrscr;

gotoxy(31,1);

write('Таблица ASCII');

for i:=32 to 52 do

begin

gotoxy(l,i-29);

j:=i;

repeat

write(chr(j) :l,j:4, ' ');

j:=j+21;

until j>255;

end;

readln;

end.

Задание 3.02. Преобразование строк к верхнему регистру

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

Совет 1 (общий)

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

  • [97,122] — строчные латинские буквы от "а" до "z";
  • [160,175] — строчные русские буквы от "а" до "п";
  • [224,239] — строчные русские буквы от "р" до "я";
  • [241,241] — буква "ё".

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

Совет 2 (QBasic)

Для выделения и изменения j-гo символа строки s удобнее всего воспользоваться функцией (оператором) MID$(S,J, 1). Анализ на принадлежность одному из кодовых интервалов, подлежащему замене, целесообразно оформить в виде переключателя SELECT CASE — END SELECT. Наконец, функции ASC и CHR$ понадобятся, соответственно, для преобразования символа в код ASCII и модифицированного кода в символ эквивалентной большой буквы.

В приводимых ниже двух вариантах программы преобразование к верхнему регистру выполнено в виде подпрограммы UP и функции UP$. Чтобы не портить аргумент функции и другие глобальные переменные головной программы, в теле функции объявлены свои локальные переменные в и j.

Совет 3 (Си)

Обратите внимание на то, что локальный массив ь, объявленный в теле функции up, имеет тип unsigned char, а не char. Объясняется это тем, что проверяемые коды (а они в теле функции выступают как числа) принадлежат диапазону [0,255].

Совет 4 (Паскаль)

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

Программа 3_02.bas

REM Замена малых русских букв большими

DECLARE SUB UP (A$)

PRINT "Введите строку, содержащую малые и большие буквы"

INPUT "",A$

UP A$

PRINT A$ END

SUB UP(A$)

FOR j=l TO LEN(A$)

SELECT CASE MID$(A$,j,l)

CASE "a" TO "z": MID$(A$,j,1)=CHR$(ASC(MID$(A$, j , 1) )-32)

CASE "a" TO "n": MID$(A$,j,l)=CHR$(ASC(MID$(A$,j,l))-32)

CASE "p" TO "я": MID$(A$,j,1)=CHR$(ASC(MID$(A$,j,1))-90)

CASE "e": MID$(A$,j,l)="E"

END SELECT

NEXT j

END SUB

Программа 3_02a.bas

RЕМ Замена малых русских букв большими

DECLARE FUNCTION UP$(A$)

PRINT "Введите строку, содержащую малые и большие буквы"

INPUT "",A$

B$=UP$(A$)

PRINT A$

PRINT B$

END

FUNCTION UP$(A$)

'Чтобы не испортить аргумент А$, введена локальная переменная

DIM В AS STRING

В=А$

FOR J=l TO LEN(A$)

SELECT CASE MID$(В,J,1)

CASE "a" TO "z": MID$ (B, J, 1) =CHR$ (ASC (MID$ (B, J, 1) )-32)

CASE "a" TO "n": MID$ (B, J, 1)=CHR$ (ASC (MID$ (B, J, 1) ) -32)

CASE "p" TO "я": MID$(B,J,1)=CHR$(ASC(MID$(B,J,1))-80)

CASE "e": MID$(B,J,1)="E"

END SELECT

NEXT J

UP$=B

END FUNCTION

Программа 3_02.с

/* Замена малых русских букв большими */

# include <stdio.h>

#include <conio.h>

#include <string.h>

char *up(char *a);

void main() {

char a[80],*b;

printf("\n Введите строку, содержащую малые и большие буквы\n");

gets(a);

b=up(а);

printf("\na=%s",a);

printf("\nb=%s",b);

getch (); }

char *up(char *a) {

unsigned char b[80]; int j ;

strcpy(b,a);

for(j=0; j<strlen(a); j++) {

if ( 97<=b[j] && b[j]<=122) b[j]-=32;

if (160<=b[j] && b[j]<=175) b[j]-=32;

if (224<=b[j] && b[j]<=239) b[j]-=80;

if (241==b[j]) b[j]— ; }

return b; }

Программа 3_02.pas

program UperCase;

{ Замена малых русских букв большими }

var

a,b>:string[80] ; function up(a:string):string; var

j:integer;

begin

for j:=1 to length(a) do

case a[j] of

'a1..'z': a[j] :=chr (ord (a [ j ] )-32) ;

'a'.,'n'; a[j]:=chr(ord(a[j])-32);

'р'.'я': a[j]:=chr(ord(a[j])-80);

'ё': a[j]:='Ё'; end;

up:=a; end; begin

writeln('Введите строку, содержащую малые и большие буквы ');

readln(a);

b:=uр(а);

writeln(a);

writeln(b);

readln;

end.

Задание 3.03. Сортировка-фамилий

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

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

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

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

Совет 1 (общий)

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

Совет 2 (QBasic)

Для перевода курсора в позицию, соответствующую очередной строке списка, надо воспользоваться оператором LOCATE row, col (row — номер строки, col — номер столбца).

Совет 3 (Си)

В программе 3_03а.с демонстрируется одна из уловок, повышающая эффективность работы любой программы сортировки достаточно длинных документов. С подобного рода приемами вы неоднократно сталкивались, но, возможно, не обращали на них внимания. Например, на панелях Norton Commander'a можно выбрать один из нескольких способов упорядочения оглавления — по именам файлов, по их расширениям, по размерам, по датам создания, в порядке появления файлов на диске. Естественно, что такого рода сортировки не производят никаких перемещений файлов на диске. Вместо этого происходят перестановки каких-то указателей в оперативной памяти.

В программе 3_03а.с для размещения фамилий использованы динамически запрашиваемые строки, на начало которых "смотрят" элементы массива указателей типа char (см. обращение к функции malloc). Вместо перестановки фамилий, расположенных не в алфавитном порядке, меняются местами соответствующие 4-байтовые указатели.

Перевод курсора в начало очередной строки списка осуществляется функцией gotoxy (col, row), последовательность параметров которой прямо противоположна соответствующему оператору QBasic.

Программа 3_03b!с построена менее замысловато. В ней использован двумерный массив для хранения фамилий, да и вывод результатов оформлен попроще.

Программа 3_03.bas

RЕМ Сортировка фамилий DIM NAMES(10)

N=10

CLS

FOR J=l TO 10

INPUT "Введи очередную фамилию - ", NAMES(J)

NEXT J CLS

PRINT "Фамилии до упорядочения : " FOR J=l TO N

LOCATE J+2,1: PRINT NAMES(J)

NEXT J

FOR J=l TO N-l

FOR K=J+1 TO N

IF NAME$(J)>NAME$(K) THEN

TMP$=NAME$(J)

NAME$(J)=NAME$(K)

NAMES(K)=TMP$

END IF

NEXT К

NEXT J

LOCATE 1,40

PRINT "Фамилии после упорядочения :"

FOR J=l TO N

LOCATE J+2,40: PRINT NAME$(J)

NEXT J

END

Программа 3_03а.с

/* Сортировка фамилий */

#include <stdio.h>

#include <alloc.h>

#include <conio.h>

#include <string.h>

main()

{

#define n_max 10

#define len_max 20

int j,k;

char tmp[len_max], *names[n_max], *p;

clrscr();

for(j=0; j< n_max; j++) {

printf("\n Введи очередную фамилию - ");

scanf("%s",tmp);

names[j]=(char *)malloc(len_max);

strcpy(names[j],tmp); }

clrscr () ;

printf("Фамилии до упорядочения :");

for(j=0; j<n_max; j++) {

gotoxyd, j+2) ;

printf("%s",names[j]); }

for(j=0; j<n_max-l; j++)

for(k=j+l; k<n_max; k++) {

if (strcmp(names[j],names[k]) > 0} {

p=names[j];

names[j]=names[k] ;

names[k]=p; } }

gotoxy(40,1);

printf("Фамилии после упорядочения :");

for(j=0; j<n_max; j++) {

gotoxy(40,j+2);

printf("%s",names[j]) ; }

getch(); }

Программа 3_03b.c

/* Сортировка фамилий */

#include <stdio.h>

#include <conio.h>

#include <string.h>

#define N 10

main () {

cnar a[N][20],tmp[20];

int i,j; Clrscr () ;

puts("Введите 10 фамилий по одной в строке");

for(i=0; i<N; i++)

gets(&a[i][0]);

for(1=0; i<N-l; 1++)

for(j=i+l; j<N; j++)

if(strcmp(&a[i] [0], &a[j] [0])>0) {

strcpy(tmp,&a[i][0]);

strcpy(&a[i][0],&a[j][0]);

strcpy(&a[j][0],tmp); }

puts("А теперь они же по алфавиту");

for (1=0; i<N; i++)

puts(&a[i][0]);

getch(); }

Программа 3_03.pas

program sort;

{ Сортировка фамилий }

uses crt;

const

n=10; var

j,k:integer; tmp:string[20];

name:array [l..n] of string[20];

begin cirscr;

for j:=1 to n do begin

writeln('Введи очередную фамилию');

readln(name[j]); end;

clrscr;

writeln('Фамилии до упорядочения :');

for j :=1 to n do

begin

gotoxy(1,j+2);

write(name[j]);

end;

for j:=1 to n-1 do

{

for k:=j+l to n do

begin

if name[j]>name[k] then

begin

tmp:=name [j];

name[j]:=name[k];

name[k]:=tmp;

end

end;

gotoxy(40,l);

writeln('Фамилии после упорядочения :');

for j:=1 to n do

begin

gotoxy (40,j+2);

write(name[j]);

end;

readln;

end.

Задание 3.04. Подсчет числа слов в строке

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

Совет 1 (общий)

В начале следует проверить длину введенной строки и, если она равна 0, сообщить, что число слов тоже равно 0. Затем имеет смысл проверить первый символ строки и, если он отличен от пробела, зафиксировать начало первого слова. Дальнейший анализ можно оформить в виде цикла, в котором проверяется очередной символ строки с учетом одной из двух возможных ситуаций - ему предшествовал пробел или символ, отличный от пробела. При переходе с пробела на непробел счетчик слов следует увеличить на 1. Переменная space хрэиит информацию о предшествующем символе — она равна 1 млм true, если предшествовавшим символом был пробел. Ее значение меняется на противоположное, если только один из двух смежных символов является пробелом.

Программа 3_04.bas

RЕМ Подсчет числа слов в строке

CLS

SPACE=1: WORDS=0

PRINT "Введите строку"

INPUT "",S$

LENS=LEN(S$)

IF LENS=0 THEN GOTO RET

IF LEFT$(S$,1)<>" " THEN SPACE=0: WORDS=1

FOR J=2 TO LENS

IF SPACE=1 AND MID$(S$,J,1)<>" " THEN SPACE=0: WORDS=WORDS+1

IF SPACE=0 AND MID$(S$,J,1)=" " THEN SPACE=1

NEXT J

RET:

PRINT "Число слов в строке = "; WORDS

END

Программа 3_04.с

/* Подсчет числа слов в строке */

#include <stdio.h>

#include <conio.h>

#include <string.h>

main() {

char s[81], space=l, words=0, i,len;

clrscr ();

puts("Введите строку");

gets (s) ;

len=strlen(s);

if (len=0) goto ret;

if (s[0]!=' ') {

space=0;

words=l; }

for(i=l; i<len; i++) (

if (space==l &&, s[i]! = ' ') {

space=0; words++; }

if (space==0 && s[i]==' ')space=l; } ret:

printf("\n Число слов в строке = %d",words);

getch () ; }

Программа 3_04.pas

program num_words;

{ Подсчет числа слов в строке }

label ret;

var

s:string[81];

space:boolean;

words:byte;

i,len:byte; begin

writeln('Введите строку');

readln(s);

len:=length(s);

if len=0 then goto ret;

space:=(s[1]=' ');

if not space then words:=1;

for i:=2 to len do

begin

if (not space)and(s[i]=' ') then space:=true;

if (space)and(s[i]<>' ') then

Degin

space;=false; inc(words);

end;

end;

ret:

writeln('Число слов в строке = ',words);

readln;

end.

Задание 3.05. Анализ нажатой клавиши

Составить программу, которая анализирует код нажатой клавиши и выводит его на экран. Программа должна завершать свою работу после нажатия клавиши <Esc> (код клавиши равен 27).

Совет 1 (общий)

В программе необходимо организовать бесконечный цикл, прерываемый после приема кода клавиши <Esc>. К сожалению, обработку клавиши <Enter> не удается включить в цикл обработки обычных клавиш. В операторах вывода попытка отобразить символ с кодом 13 приводит либо к возврату курсора в начало строки и затиранию ранее напечатанного текста последующим сообщением (Си, Паскаль), либо к-переводу курсора в начало следующей строки (QBasic).

Совет 2 (QBasic)

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

Совет 3 (Си)

Для ввода кода нажатой клавиши необходимо один (обычная клавиша) или два раза (функциональная клавиша) обратиться к функции getch.

Совет 4 (Паскаль)

Для ввода кода нажатой клавиши необходимо один (обычная клавиша) или два раза (функциональная клавиша) обратиться к функции readkey.

Программа 3_05.bas

RЕМ Анализ кода нажатой клавиши CLS

GETKEY: A$=INKEY$: IF A$="" THEN GOTO GETKEY

IF ASC(A$)=27 THEN STOP

IF LEN(A$)=1 THEN

IF ASC(A$)=13 THEN

PRINT "Нажата клавиша Enter с кодом = 13": GOTO GETKEY

END IF

PRINT "Нажата обычная клавиша ' "; A$; " ' с кодом ="; ASC(A$)

ELSE

PRINT "Нажата управляющая клавиша с кодом ";ASC(RIGHT$(A$,1))

END IF

GOTO GETKEY

Программа 3_05.с

/* Анализ кода нажатой клавиши */ #include <stdio.h>

main() { unsigned char ch;

clrscr(); getkey:

ch=getch();

if(ch==27) exit(0);

if(ch==13) {

printf("\n Нажата клавиша Enter с кодом = 13");

goto getkey; }

if(ch==0) {

ch=getch();

printf("\n Нажата управляющая клавиша с кодом = %d",ch); }

else printf("\n Нажата обычная клавиша %с с кодом=%d",ch,ch);

goto getkey;

}

Программа 3_05.pas

program keyboard;

( Анализ кода нажатой клавиши }

uses Crt;

label getkey;

var

ch:char;

begin

clrscr; getkey:

ch:=readkey;

if ord(ch)=27 then exit;

if ord(ch)=13 then

begin

writeln('Нажата обычная клавиша Enter с кодом = 13');

goto getkey;

end;

if ord(ch)=0 then

begin

ch:=readkey;

writeln('Нажата управляющая клавиша с кодом = ',ord(ch));

end else

writeln('Нажата обычная клавиша "',ch,'" с кодом=',ord(ch));

goto getkey; end.

Программа 3_05a.pas

program keyboard;

{ Анализ кода нажатой клавиши }

uses Crt;

var

ch:char; begin clrscr; repeat

ch:=readkey;

if ord(ch)=13 then

writeln('Нажата обычная клавиша Enter с кодом =13') else

if ord(ch)=0 then

begin

ch:=readkey;

writeln { 'Нажата управляющая клавиша с кодом = ' ,ord(ch) } ;

end else

writeln('Нажата обычная клавиша "',ch,'" с кодом = ',ord(ch});

until ord(ch)=27;

end.

Задание 3.06. Три цвета радуги

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

Совет 1 (общий)

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

Программа 3_06.bas

REM Упорядочение цветов радуги

DATA "красный","оранжевый","желтый","зеленый"

DATA "голубой","синий","фиолетовый"

DIM А$ (7) , В$ (3)

FOR I=0 ТО 6: READ A$(I): NEXT I

PRINT "Введите по одному в строке 3 цвета радуги"

FOR I=0 ТО 2: INPUT B$ (I): NEXT I

PRINT "В радуге эти цвета следуют в таком порядке:"

FOR J=0 TO 6: FOR I=0 TO 2

IF A$(J)=B$(I) THEN PRINT A$(J)

NEXT I: NEXT J

END

Программа 3_06.c

/* Упорядочение цветов радуги */

#include <stdio.h>

#include <conio.h>

#include <string.h> main()

{

char a[7][11]={"красный","оранжевый",

"желтый","зеленый","голубой","синий","фиолетовый"};

char b[3][11]; int i,j;

printf("\n Введите по одному в строке 3 цвета радуги\n");

for (i=0; КЗ; i++) gets (&b[i] [0] ) ;

printf("\nB радуге эти цвета следуют в таком порядке:\n");

for(j=0; j<7; j++) for(i=0; i<3; i++)

if(strcmp(&a[j][0],&b[i][0])==0)

puts(&a[j][0]); getch(); }

Программа 3_06.pas

program raduga;

{ Упорядочение цветов радуги }

const

a:array [1..7] of string[10]=('красный','оранжевый',

' желтый', ' зеленый', ' голубой', ' синий', ' фиолетовый') ;

var

b:array [1..3] of string[10];

i,j:byte; begin

writeln('Введите по одному в строке 3 цвета радуги');

for i:=l to 3 do readln(b[i]);

writeln('В радуге эти цвета следуют в таком порядке:');

for j:=1 to 7 do

for i:=l to 3 do

if a[j]=b[i] then writeln(a[j]);

readln;

end.

Задание 3.07. Левый и правый прижим, центрирование текста при выводе

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

Совет 1 (QBasic)

Для смещения текста в нужную позицию строки можно воспользоваться либо функцией TAB, либо переводом курсора с помощью оператора LOCATE.

Совет 2 (Си, Паскаль)

Для перевода курсора в нужную позицию текущей строки можно воспользоваться процедурой gotoxy, у которой в качестве первого аргумента задан номер начальной колонки, а в качестве второго — значение функции wherey, соответствующее номеру текущей строки.

Программа 3_07.bas

CLS

PRINT "Введите строку, содержащую не более 20 символов"

FOR I = 1 ТО 8: PRINT "1234567890"; : NEXT I

INPUT "", A$

PRINT "Вывод, начиная с 10-й позиции :"

FOR I = 1 ТО 8: PRINT "1234567890"; : NEXT I

PRINT TAB(10); A$

PRINT "Вывод по центру между 10-й и 50-й позициями :"

FOR I = 1 ТО 8: PRINT "1234567890"; : NEXT I

PRINT TAB(10 + (50 - 10) / 2 - LEN(A$) / 2); A$

PRINT "Вывод с прижимом к 50-й позиции :"

FOR I = 1 ТО 8: PRINT "1234567890"; : NEXT I

PRINT TAB(51 - LEN(A$)); A$

END

Программа 3_07.с

#include <stdio.h>

#include <conio.h>

#include <string. h>

main()

{

char str[20], rule [] ="1234567890";

int i;

clrscr () ;

puts("Введите строку, содержащую не более 20 символов");

for(i=0; i<8; i++)

printf("%s",rule);

gets (str) ;

puts("Вывод, начиная с 10-й позиции :");

for(i=0; i<8; i++) printf("%s",rule);

gotoxy(10,wherey()); puts (str) ;

puts("Вывод по центру между 10-й и 50-й позициями :");

for(i=0; i<8; i++) printf("%s",rule);

gotoxy(10+(50-10)/2-strlen(str)/2,wherey());

puts(str);

puts("Вывод с прижимом к 50-й позиции :");

for (i=0; i<8; i++) printf("%s", rule);

gotoxy(51-strlen(str),wherey());

puts(str);

getch(); }

Программа 3_07.pas

program text_justify;

uses Crt;

const

rule:string='1234567890'; var

s:string[20];

i:integer; begin

clrscr;

writeln('Введите строку, содержащую не более 20 символов');

for i:=0 to 7 do write(rule);

readln(s);

writeln('Вывод, начиная с 10-й позиции :');

for i:=0 to 7 do write(rule);

gotoxy(10,wherey); writeln(s);

writeln('Вывод по центру между 10-й и 50-й позициями :');

for i:=0 to 7 do write(rule);

#include <string.h>

main() {

char str[20], rule[]="1234567890";

int i ;

clrscr();

puts("Введите строку, содержащую не более 20 символов");

for(i=0; i<8; i++)

printf("%s",rule);

gets (str) ;

puts("Вывод, начиная с 10-й позиции :");

for(i=0; i<8; i++) printf("%s",rule);

gotoxy(10,wherey()); puts (str) ;

puts("Вывод по центру между 10-й и 50-й позициями :");

for (i=0; i<8; i++) printf("%s",rule);

gotoxy(10+(50-10)/2-strlen(str)/2,wherey());

puts(str); puts("Вывод с прижимом к 50-й позиции :");

for (i=0; i<8; i++)

printf("%s",rule);

gotoxy(51-strlen(str),wherey());

puts(str);

getch(); }

Программа 3_07.pas

program text justify;

uses Crt;

const

rule:string='1234567890'; var

s:string[20];

i:integer; begin

clrscr;

writeln('Введите строку, содержащую не более 20 символов');

for i:=0 to 7 do write(rule);

readln(s);

writeln('Вывод, начиная с 10-й позиции :');

for i:=0 to 7 do write(rule);

gotoxy(10,wherey); writeln(s);

writeln('Вывод по центру между 10-й и 50-й позициями :');

for i:=0 to 7 do write(rule);

gotoxy(10+(50-10) div 2 - length(s) div 2,wherey);

writeln(s); writeln{'Вывод с прижимом к 50-й позиции :');

for i:=0 to 7 do write(rule);

gotoxy(51-length(s),wherey);

writeln(s);

readln; end.

Задание 3.08. Сравнение строк с игнорированием пробелов

Написать подпрограмму-функцию compare, имеющую своими аргументами две строки, которая сравнивает их, игнорируя пробелы между словами и разницу между кодами больших и малых букв. Функция должна возвращать следующие значения: 1, если первая строка "больше" второй; -1, если первая строка "меньше" второй; о, если обе строки равны.

Совет 1 (общий)

Самым простым по реализации является алгоритм, в котором сначала коды всех малых букв заменяются кодами их больших эквивалентов, а затем из строк удаляются пробелы. Для выполнения первой процедуры можно воспользоваться функцией up (str) — см. программу 3_02.

Программа 3_08.bas

REM Сравнение строк с игнорированием пробелов

DECLARE SUB UP(A$)

DECLARE FUNCTION COMPARE(B$, C$)

PRINT "Введите первую строку"

INPUT "",A1$

PRINT "Введите вторую строку"

INPUT "", A2$

K=COMPARE(A1$,A2$)

IF K=l THEN PRINT "Первая строка 'больше'"

IF K=-l THEN PRINT "Первая строка 'меньше'"

IF K=G THEN PRINT "Обе строки равны"

END

FUNCTION COMPARE(B$,C$) DIM B1$,C1$

UP B$: UP C$

FOR J=l TO LEN(B$)

IF MID$ (B$, J, 1)<>" " THEN B1$=B1$+MID$ (B$,J, 1 )

NEXT J

FOR J=l TO LEN(C$)

IF MID$(C$,J,1)<>" " THEN C1$=C1$+MID$(C$,J,1) NEXT J

IF B1$>C1$ THEN COMPARE=1 IF B1$=C1$ THEN COMPARE=0

IF B1$<C1$ THEN COMPARE=-1 END FUNCTION

SUB UP(A$)

FOR J=l TO LEN(A$)

SELECT CASE MID$(A$,J,1)

CASE "a" TO "z": MID$ (A$, J, 1) =CHR$ (ASC (MID$ (A$, J, 1) )-32)

CASE "a" TO "n": MID$(A$,J,1)=CHR$(ASC(MID$(A$,J,1))-32)

CASE "p" TO "я": MID$(A$,J,1)=CHR$(ASC(MID$(A$,J,1))-80)

CASE "ё": MID$(A$,J,1)="Ё" END SELECT

NEXT J

END SUB

Программа 3_08.с

/* Сравнение строк с игнорированием пробелов */

#include <stdio.h>

#include <conio.h>

#include <string.h>

char *up(char *a);

int compare(char *s1, char *s2);

void main() {

char s1[80],s2[80];

puts("Введите первую строку");

gets (s1);

puts("Введите вторую строку");

gets(s2);

switch(compare(s1,s2)) {

case 1: puts("Первая строка 'больше'");break;

case -1: puts("Первая строка 'меньше'");break;

case 0: puts("Обе строки равны");

}

getch(); }

char *up(char *a)

{

/* Замена кодов малых букв кодами больших */

static unsigned char b[80]; int j ;

strcpy (b, a) ;

for(j=0; j<strlen(a); j++) {

if(97<=b[j] && b[j]<=122) b[j]-=32;

if(160<=b[j] && b[j]<=175) b[j]-=32;

if (224<=b[j] && b[j]<=239) b[j]-=80;

if (241==b[j]) b[j]—; }

return b; }

int compare(char *s1,char *s2) {

char ssl[80],ss2[80]; char j,k;

strcpy(s1,up(s1)};

/* Замена малых букв большими */

strcpy (s2*, up (s2) );

/* с записью на то же место */

for(j=0,k=0; j<strlen(sl); j++)

if (sl[j] != ' ') ssl[k++]=sl[j];

/* Извлекаем непробелы */

ssl[k]=0x0; /* Добавили признак конца */

for(j=0,k=0;

j<strlen(s2); j++)

if(s2[j]!=' ') ss2[k++]=s2[j];

ss2[k]=0x0;

k=atrcmp(ss1.ss2) /* Cравнили преобразованные строки */

if(k>0)return 1;

if(k<0)return 1;

return 0;

}

Программа 3_08.pas

program comp_string;

{ Сравнение строк с игнорированием пробелов }

var

s1,s2:string;

function up (a:string):string; var

j:integer; begin

for j:=l to length(a) do

case a[j] of

'a'..'z': a[j] :=chr(ord(a[j])-32) ;

'a'..'n': a[j]:= chr(ord(a[j])-32} ;

'р'..'я': a[j]:= chr(ord(a[j])-80} ;

'ё': a[j]:='E'; end; up:=a; end;

function compare(s1,s2:string):integer;

var

ssl,ss2:string; j:byte;

begin

sl:=up(s1);

s2:=up(s2);

ssl:=' ';

for j:=l to length(s1) do

if sl[j]<>' ' then ssl:=ssl+sl[j];

ss2:=' ';

for j:=l to length(s2) do

if s2[j]<>' ' then ss2:=ss2+s2[j];

compare:=0;

if ssl>ss2 then compare:=1;

if ssl<ss2 then compare:=-!;

end;

begin

writeln('Введите первую строку');

readln(s1);

writeln('Введите вторую строку');

readln(s2);

case compare(s1,s2) of

1: writeln('Первая строка "больше"');

-1: writeln('Первая строка "меньше"');

0: writeln('06e строки равны');

end;

readln;

end.

Задание 3.09. Разноцветный текст

Написать программу, которая выводит на экран текст, меняя цвет букв и цвет фона.

Совет 1 (общий)

В приведенных ниже текстах программ в пределах одного экрана демонстрируются две возможности. Во-первых, выводится строка, все символы которой окрашены в разные цвета. Для этого приходится каждый символ выдавать на экран отдельным оператором, предваряя вывод переустановкой цвета переднего плана. Выбранное для этой цели слово "ПРОГРАММИРОВАНИЕ" содержит ровно 16 букв, но каждый раз при очередной смене цвета фона одна из букв отображаемого слова имеет такой же цвет и не будет видна. Во-вторых, выводится строка, все символы которой имеют одинаковый цвет, не совпадающий с цветом фона. С помощью такой программы можно поэкспериментировать в подборе приятных цветовых сочетаний.

Программа 3_09.bas

RЕМ Разноцветный текст А$ = "ПРОГРАММИРОВАНИЕ"

CLS

'Отображение разноцветных букв на допустимой фоновой гамме

FOR CF=0 TO 7

FOR CS=0 TO 15

COLOR CS,CF LOCATE CF+1,2*CF+CS+1

PRINT MID$(A$,CS+1,1)

NEXT CS

NEXT CF

'Отображение мигающих разноцветных букв

FOR CF=0 TO 7

FOR CS=0 TO 15

COLOR CS+16,CF

LOCATE CF+1,2*CF+CS+41

PRINT MID$(A$,CS+1,1)

NEXT CS

NEXT CF

Программа 3_09.с

/* Разноцветный текст */

#include <conio.h>

main() {

int i ;

textbackground(0);

clrscr () ;

for(i=0; i<24; i++) {

gotoxy(2*i+l,i+l);

textcolor(128+i);

textbackground(i+2);

cprintf("Цветовая гамма в текстовом режиме"); }

getch(); }

Программа 3_09а.с

/* Разноцветный текст */

#include <conio.h>

main() {

int i;

textbackground(0) ;

clrscr();

for(i=0; i<24; i++) {

gotoxy(2*i+l,i+1);

textattr(128+i + ((i+1) « 4));

cprintf("Цветовая гамма в текстовом режиме");

}

getch (); }

Программа 3_09.pas

program colorl;

{ Разноцветный текст }

uses crt;

var

i:integer; begin

textbackground(0); clrscr;

for i:=0 to 23 do

begin

gotoxy(2*i+l,i+l);

textcolor(128+i);

textbackground(i+l);

writeln('Тест цветовой гаммы ');

end;

readln;

end.

Задание 3.10. Преобразование обычной дроби в десятичную

Составить функцию символьного (строкового) типа, преобразующую два своих целочисленных аргумента — числитель m и знаменатель п правильной дроби (m < n < 100) в строку, представляющую запись десятичной дроби. Для бесконечной дроби период следует заключить в круглые скобки. Например:

m=3 n=5 значение функции - "0.6"

m=1 n=6 значение функции - "0.1(6)"

Совет 1 (общий)

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

Совет 2 (Си)

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

Программа 3_10.bas

RЕМ Преобразование обычной дроби в десятичную

DECLARE FUNCTION FRAC2STR$(M%, N%)

CLS : DEFINT A-Z

INPUT "Введите числитель и знаменатель дроби : ",M,N

PRINT M;"/";N;"= ";FRAC2STR$(M,N)

END

DEFSNG A-Z

FUNCTION FRAC2STR$(M AS INTEGER,N AS INTEGER)

DIM S AS STRING, REST(100) AS INTEGER

DEFINT A-Z

I=3: S="0."

DO

Q=M*10\N : P=M*10 MOD N: REST(I)=P

IF P=0 THEN

FRAC2STR=S+CHR$(Q+48) EXIT FUNCTION

END IF

FOR J=3 TO 1-1

IF REST(J)=P THEN

FRAC2STR=LEFT$(S, J-l}+"("+RIGHT$(S,LEN(S)-J+l)+")"

EXIT FUNCTION

END IF

NEXT J

S=S+CHR$(Q+48): I=1+1: M=P LOOP UNTIL P=0

END FUNCTION

Программа 3_10.с

/* Преобразование обычной дроби в десятичную */

#include <stdio.h>

#include <conio.h>

char *frac_to_str(char m,char n);

main() {

char m,n;

printf("\n Введите числитель и знаменатель дроби : ");

scanf("%d %d",sm,&n) ;

printf("\n%d/%d=%s",m,n,frac_to_str(m,n));

getch();

}

/ *-----------------------------------------------* /

char *frac_to_str(char m,char n) {

int i=2,j,p,q;

char rest[100];

static char s [100]="0.";

do {

q=m*10/n; p=m*10%n; rest[i]=p;

if(p==0) { s[i]=q+48;

s[i+l]=0x0;

return s; }

for(j=2; j<i; j++)

if(rest[j]==p) {

for(p=i; p>=j; p--)

s[p+l]=s[p];

s[j]='(';

s[i+l]=')';

s [i+2]=0x0 ;

return s ; }

s[i]=q+48;

i++;

m=p; }

while (p!=0); }

Программа 3_10.pas

program drobi;

{ Преобразование обычной дроби в десятичную }

var

m, n:byte;

function frac_to_str(m,n:byte):string;

var

i,j,p,q:word;

s:string;

rest:array [1..100] of byte;

begin

s:='0.';

i:=l;

repeat

q:=m*10 div n;

p:=m*10 mod n;

rest[i]:=p;

if p=0 then begin

frac_to_str:==s+chr (q+48) ;

exit;

end;

for j:=l to i-1 do

if p=rest[j] then

begin insert(' (',s,j+2); frac_to_str:=s+')';

exit;

end;

inc(i);

s:=s+chr(q+48);

m:=p;

until p=0;

end;

begin

writeln('Введите числитель и знаменатель дроби :');

readln(m,n);

writeln(m,'/',n, ' = ', frac_to_str(m,n));

readln; end.

Задание 3.11. Перевод чисел в римскую систему счисления

Составить функцию символьного (строкового) типа, аргументом которой является натуральное число из интервала [1, 3999]. Функция должна возвращать строку с эквивалентным значением в формате римской системы счисления. Напомним, что в римской системе счисления используются большие латинские буквы — M(1000), O (500), C(100), L (50), X(10), V(5) и I(1). Если меньшая "цифра" находится левее "большей", то она вычитается (например: IV == V-I = 4, IX= X- I= 9, XL = L - X- 40). Если меньшая "цифра" расположена правее, то она прибавляется (например: VI = = V+ I= б, XI = X+ I= I I, LX = L + X = 60). Для некоторых "цифр" римская система допускает наличие до трех идущих подряд одинаковых

цифр —

I I= I + I = 2,

I I I = I + I+ I= 3,

XXX = X + X + X = 30,

ССС == C+ C+ C= 300,

MMM = M+ M+ M + 3000.

Совет 1 (общий)

Заведем два массива — символьный с наиболее характерными сочетаниями римских "цифр" и числовой с соответствующими значениями этих сочетаний. Оба массива разумно расположить в порядке убывания числовых значений (табл. 3.1).

Таблица 3.1. Расположение массива в порядке убывания числовых названий

Индекс элемента
Символьный массив
Числовой массив
0
M
1000
1
CM
900
2
D
500
3
CD
400
4
С
100
5
ХС
90
6
L
50
7
XL
40
8
X
10
9
IX
9
10
V
5
11
IV
4
12
I
1

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

Программа 3_11.bas

RЕМ Перевод чисел в римскую систему

DECLARE FUNCTION torome$ (M%)

КЕМ Перевод арабских чисел в римскую систему счисления

DEFINT A-Z

COMMON SHARED ND()

COMMON SHARED SD$()

DATA 1,4,5,9,10,40,50., 90,100,400,500,900,1000

DIM ND(13)

FOR J=0 TO 12: READ ND(J): NEXT J

DATA I, IV, V, IX, X, XL, L, XC, C, CD, D, CM, M

DIM SD$(13)

FOR J=0 TO 12: READ SD$(J): NEXT J

INPUT "Введите целое число от 1 до 3999 : ", N

IF N<1 OR N>3999 THEN PRINT "Число вне диапазона":

END

PRINT "В римской системе счисления "; N;" = ";torome$(N)

END

FUNCTION torome$ (M)

SHARED ND(), SD$()

S$=""

FOR K=12 TO 0 STEP -1

WHILE ND(K)<=M

M=M-ND(K): S$=S$+SD$(K)

IF M=0 THEN EXIT FOR

WEND

NEXT К

torome$=S$

END FUNCTION

Программа 3_11.с

/* Перевод чисел в римскую систему счисления */

#include <stdio.h>.

#include <conio.h>

#include <string.h>

char *to_rome(int n) ;

main() {

int N;

printf("\n Введите целое число от 1 до 3999 : ");

scanf("%d",&N); if (N<0 | | N>3999) {

printf("\n Число вне диапазона");

getch();

exit(0); }

printf("\n B римской системе счисления %d = %s",N,to_rome(N));

getch();

}

/*-------------------------------*/

char *to_rome(int n) {

int k;

static char s[20]="";

int nd[13]={l,4,5,9,10,40,50,90,100,400,_500,900,1000};

char *sd[13]={"I","IV","V","IX","X","XL",

"L","XC","C","CD","D","CM","M"}; for(k=12; k>=0; k—) {

while(nd[k]<=n) {

n=n-nd[k]; strcat(s,sd[k]);

if(n==0) break;

} }

return s; }

Программа 3_11.pas

program in_rome;

{Перевод чисел в римскую систему счисления}

var

N:1..3999;

function to_rome(n:integer): string;

const

nd:array [1..13] of integer=

(1,4,5,9,10,40,50,90,100,400,500,900,1000) ;

sd:array [1..13] of string=

( 'I', 'IV, 'V, 'IХ', 'X', 'XL',

'L', 'XC', 'C','CD','D','CM', 'M');

var

k:integer;

s:string;

begin s : = " ;

for k:=13 downto 1 do

while nd[k]<=n do begin

n:=n-nd[k];

s:=s+sd[k];

if n=0 then break;

end;

to_rome:=s;

end; begin

write ('Введите целое число от 1 до 3999 : ' ).; readln(N);

writeln('B римской системе счисления ',N, ' = ', to_rome (N) ) ;

readln;

end.

Задание 3.12. Перевод чисел из римской системы счисления

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

Программа 3_12.bas

REM Перевод чисел из римской системы счисления в арабскую

DEFINT A-Z

DATA 1,4,5,9,10,40,50,90,100,400,500,900,1000

DIM ND(13)

FOR J=0 TO 12: READ ND(J): NEXT J

DATA I,IV,V,IX,X,XL,L,XC,C,CD,D,CM,M

DIM SD$(13)

FOR J=0 TO 12: READ SD$(J): NEXT J

INPUT "Введите число в римской системе счисления : ", R$

J=l: M=0

100 :

FOR К=12 ТО 0 STEP -1

N=LEN(SD$(К))

IF MID$(R$,J,N)=SD$(К) THEN M=M+ND(K): J=J+N: GOTO 100

END IF

IF J>LEN(R$) THEN EXIT FOR

NEXT К

PRINT "В арабской системе счисления ";R$;" = ";M

END

Программа 3_12a.bas

REM Перевод чисел из римской системы счисления в арабскую

DATA 1000,900,500,400,100,90,50,40,10,9,5,4,1

DIM ND(13):

FOR J=0 TO 12: READ ND(J): NEXT J

DATA M,CM,D,CD,C,XC,L,XL,X,IX,V,IV,I

DIM SD$(13): FOR J=0 TO 12: READ SD$(J): NEXT J

INPUT "Введите число в римской системе счисления : ",R$

FOR J=l TO LEN(R$)

FOR K=0 TO 12

N=LEN(SD$(K))

IF MID$(R$,J,N)=SD$(К) THEN M=M+ND(K): J=J+1: K=K-1

END IF NEXT К NEXT J

PRINT "В арабской системе счисления ";R$;" = ";M

END

Программа 3_12.с

/* Перевод чисел из римской системы счисления в арабскую */

#include <stdio.h>

#include <conio.h>

#include <string.h>

main() {

int nd[13]={1000,900,500,400,100,90,50,40,10,9,5,4,1};

char *sd[13]={"M","CM","D","CD","C","XC","L","XL","X",

"IX","V","IV","I"}; char r[20],s[3]; int j,k,n,m=0;

printf("ХпВведите число в римской системе счисления : ");

scanf("%s", r);

for(j=0; j<strlen(r); j++)

for(k=0; k<13; k++) {

n=strlen(sd[k]);

strncpyfs, &r.[j] ,n) ;

s[n]=0;

if(strcmp(s,sd[k])==0)

{ m += nd[k]; j++; k—; } }

printf("\nB арабской системе счисления %s = %d",r,m);

getch(); }

Программа 3_12.pas

{Перевод чисел из римской системы счисления в арабскую }

program from rome;

const

nd:array [1..13] of integer=(1000,900,500,400,100,

90,50,40,10,9,5,4,1);

sd:array [1..13] of string=('M','CM','D','CD','C',

'XC' , 'L', 'XL', 'X', 'IX1, 'V, 'IV, 'I' ) ; var

r,s:string;

j,k,n,m: integer; begin

m:=0;

write('Введите число в римской системе счисления : ');

readln(r);

J:=l;

while j<=length(r) do begin k:=l;

while k<=13 do begin

n:=length(sd[k]);

s:=copy(r,j,n);

if s=sd[k] then begin

inc(m,nd[k]); inc(j);

dec(k);

end;

inc(k);

end;

inc(j);

end;

writeln('B арабской системе счисления ',r,' = ',m);

readln;

end.

Задание 3.13. Вхождение строки с разрядкой

Строка S2 может входить в более длинную строку S1 в общепринятом смысле, когда непрерывная подпоследовательность символов строки S1совпадает с цепочкой символов S2. Однако возможна и другая ситуация, когда строка S2 входит в строку S1 с разрядкой. При этом символы S2 с равномерным шагом через 1, 2, 3 или более символов встречаются в строке S1.

Например, для строк S1="ABCBEEC" и S2="ABC" имеют место обычное (с нулевым шагом разрядки) вхождение (символы 1, 2 и 3 в строке si) и вхождение с разрядкой в 2 символа (символы 1, 4 и 7 в строке S1).

Составить программу, которая:

  • запрашивает и вводит анализируемые строки S1 и S2;
  • определяет, входит ли строка S2 в S1, выдает шаг разрядки и номера соответствующих позиций в строке si.

Задача программы — определить все возможные варианты вхождения S2 в S1.

Совет 1 (общий)

Идея поиска довольно проста. Обозначим через h шаг по индексу в строке S1, с которым выбираются символы, сравниваемые с символами S2. Для нулевой разрядки h=l, для выборки через символ h=2 и т. д. Очевидно, что начинать

сравнение надо с символа S1[I], совпадающего с первым символом S2. Однако реализация этой идеи может быть разной. Например, из строки si можно извлечь с заданной разрядкой нужное число символов и результат сравнить с S2. Более эффективно совместить эти две операции и сравнивать извлекаемый из S1 символ с очередным символом S2, прекращая выборку при первом несовпадении. Указанный подход был реализован победителем областной студенческой олимпиады В. Мартьяновым (апрель 2000 г.), на базе программы которого и построены приведенные ниже примеры. Наиболее оригинальной, на наш взгляд, является проверка условия (h=l) или (L2>1). Здесь L2—длина второй строки.

Программа 3_13.bas

RЕМ Поиск вхождения S2 в S1 с разрядкой

CLS

INPUT "Введите S1: ", Sl$: L1=LEN(S1$)

INPUT "Введите S2: ", 32$: L2=LEN(S2$)

FOR H=l TO L1

IF (H=l) OR (L2>1) THEN

FOR I=1 TO Ll-(L2-l)*H

IF MID$(S1$,I,1)=MID$(S2$,l,l) THEN

FOR J=2 TO L2,

IF MID$(S1$,I+(J-1)*H,1)<>MID$(32$,J,1) THEN GOTO ml

NEXT J

PRINT "Шаг: ";Н-1,"Позиции: ";I;

FOR К = 1 TO L2 - 1: PRINT "-"; I + К * H; : NEXT K: PRINT

К = 1 ml:

END IF

NEXT I

END IF

NEXT H

IF К = 0 THEN PRINT "Строка S2 не входит в S1"

END

Программа 3_13.с

/* Поиск вхождения S2 в S1 с разрядкой */

#include <stdio.h>

#include <conio.h>

#include <string.h> main{)

{

char s1 [80],s2[80];

int i,j,k=0,h,Ll,L2;

clrscr();

printf("Введите S1: ");

scanf("%s",s1);

Ll=strlen(s1);

printf("Введите S2: ");

scanf("%s",s2);

L2=strlen(s2);

for(h=l; h<=Ll; h++)

{

if(h==l || L2>1

}

{

for(i=0; i<Ll-(L2-l)*h; i++)

{

if (sl[i]=s2[0])

{

for(j=l; j<L2; j++)

if(si[i+j*h]!=s2[j]) goto ml;

printf("\n Шаг: %2d Позиции: %d",h-l,i+1);

for(k=l; k<L2; k++)

printf("-%d",i+i+k*h);

ml:;

}

} } }

if(k==0) printf("Строка S2 не входит в S1");

getch () ; }

Программа 3_13.pas

program red_str;

(Поиск вхождения S2 в S1 с разрядкой}

uses crt;

var

s1,s2:string;

i,j,k,h,L1,L2:integer;

label 1;

begin

clrscr;

write('Введите S1: ');

readln (si);

Ll:=length(sl);

write('Введите S2: ');

readln (s2);

L2:=length(s2);

k:=0;

for h:=l to L1 do

if (h=l)or(L2>1) then

for i:=l to L1-(L2-1)*h do

if s1[i]=s2[l] then begin

for j:=2 to L2 do

if s1[i+(j-l)*h]os2[j] then goto 1;

write ('Шаг: ',h-l:2,' Позиции: ',i);

for k:=l to L2-1 do

write('-',i+k*h);

writeln;

k:=l;

1:

end;

if k=0 then writeln('Строка 32 не входит в S1');

readln;

end.

 

<< НазадСодержаниеВперед >>

 

®Сайт разработал: Nek по вопросам пишите сюда NekSuper@yandex.ru
 
Hosted by uCoz