книги / Программирование на языке Си
..pdf192 |
Программирование на языке Си |
4.3. С и м вольн ая инф орм ация и строки
Для представления текстовой информации в языке Си ис пользуются символы (константы), символьные переменные и строки (строковые константы), для которых в языке Си не вве дено отдельного типа в отличие от некоторых других языков программирования.
Символьные константы были рассмотрены в главе 1. Для символьных данных введен базовый тип char. Описание сим вольных переменных имеет вид:
char список_именпеременных;
Например:
char a,z;
Ввод-вывод символьных данных. Для ввода и вывода сим вольных значений в форматных строках библиотечных функций printf() и scanf() используется спецификация преобразования %с. Некоторые особенности работы с символьными данными уже рассматривались выше. Смотрите, например, разные спосо бы "инвертирования" массива символов в §4.2. Продолжая эту тему, рассмотрим следующую задачу.
Ввести предложение, слова в котором разделены пробелами и в конце которого стоит точка. Удалить повторяющиеся пробе лы между отдельными словами (оставить по одному пробелу), вывести отредактированное предложение на экран.
Текст программы:
/* Удаление повторяющихся пробелов */ #include <stdio.h>
void main( )
{
char |
z, s; |
/* z — текущий вводимый символ */ |
|
printf("\n Напишите предложение с точкой VI |
|||
for |
"в конце:\п"); |
s=z) |
|
(z=s=' |
z!='.'; |
||
{ /* s - предыдущий символ */ |
|||
scanf("%c",&z); |
') continue; |
||
if |
(z==1 ' && s =' |
Глава 4. Указатели, массивы, строки |
193 |
printf("%c",z) ;
}/* Конец цикла обработки */
}/* Конец программы */
Впрограмме две символьные переменные: z - для чтения очередного символа и s - для хранения предыдущего. В заго ловке цикла переменные z и s получают значения "пробел". Очередной символ вводится как значение переменной z, и пара z, s анализируется. Если хотя бы один из символов значений па ры отличен от пробела, то значение z печатается. В заголовке цикла z сравнивается с символом "точка" и при несовпадении запоминается как значение s. Далее цикл повторяется до появ ления на входе точки, причем появление двух пробелов (z и s) приводит к пропуску оператора печати.
Пример работы программы:
Напишите предложение с точкой в конце:
YYYYYууууу hhhhh ttttt. YYYYY ууууу hhhhh ttttt.
Помимо scanf() и printf() для ввода и вывода символов в библиотеке предусмотрены специальные функции обмена:
getchar() - функция без параметров. Позволяет читать из входного потока (обычно с клавиатуры) по одному символу за обращение. Как и при использовании функции scanf(), чтение вводимых данных начинается после нажатия клавиши <Enter>. Это дает возможность исправлять вводимую информацию (стирая последние введенные символы с помощью клавиши <Backspace>) до начала ее чтения программой;
putchar(X) - выводит символьное значение X в стандартный выходной поток (обычно на экран дисплея).
Проиллюстрируем применение этих функций на следующей задаче: "Ввести предложение, в конце которого стоит точка, и подсчитать общее количество символов, отличных от пробела (не считая точки)".
/* Подсчет числа отличных от пробелов символов*/ #include <stdio.h>
void main( )
{
1 3 -3 1 2 4
194 |
Программирование на языке Си |
char z ; /* z — вводимый |
символ */ |
int к; /* к - количество значащих символов */ printf("Напишите предложение с точкой в "
|
"конце:\п"); |
|
for (k=0; (z-getchar( |
) |
|
if |
(z ?— 1 ') k++; |
символов=%<1" ,k) ; |
printf ("\n Количество |
||
} /* |
Конец программы |
*/ |
В заголовке цикла for выражение z=getchar() заключено в скобки, так как операция присваивания (см. табл. 1.4 на с. 34) имеет более низкий ранг, чем операции сравнения. Если скобки опустить, то последовательность операций будет такой: функ ция getchar() введет значение символа и выполнит его сравне ние с символом Результат сравнения присвоится переменной z. По смыслу же необходимо введенный символ присвоить пе ременной z и сравнить его с точкой.
Результат выполнения программы:
Напишите предложение с точкой в конце:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
0. |
Количество символов=10
Внутренние коды и упорядоченность символов. В языке принято соглашение, что везде, где синтаксис позволяет исполь зовать целые числа, можно использовать и символы, т.е. данные типа char, которые при этом представляются числовыми значе ниями своих внутренних кодов. Такое соглашение позволяет сравнительно просто упорядочивать символы, обращаясь с ни ми как с целочисленными величинами. Например, внутренние коды десятичных цифр в таблицах кодов ASCII упорядочены по числовым значениям, поэтому несложно перебирать символы десятичных цифр в нужном порядке.
Следующая программа печатает цифры от 0 до 9 и шестна дцатеричные представления их внутренних кодов.
/* Печать десятичных цифр: */ #include <stdio.h>
void main( )
Глааа 4. Указатели, массивы, строки |
195 |
{
char z;
for (z='0'; z<='9'; z++)
{
if (z=*'0’ || г=*='5') printf ("\n") ; printf(" %c-%x ", z, z) ;
>
} /* Конец программы */
Результат выполнения программы:
0-30 |
1-31 |
2-32 |
3-33 |
4-34 |
5-35 |
6-36 |
7-37 |
8-38 |
9-39 |
Обратите внимание на то, что символьная переменная z явля ется операндом арифметической операции выполняемой над числовым представлением ее внутреннего кода. Для изо бражения значения символов в форматной строке функции printf() используется спецификация %с. Шестнадцатеричные коды выводятся с помощью спецификации %х. В Приложении 1 можно посмотреть, что именно такими являются шестнадцате ричные коды символов. Для '0' - код 30, для Т - код 31 и т.д.
Воспользовавшись упорядоченностью внутренних кодов букв латинского алфавита, очень просто его напечатать:
/* Печать латинского алфавита: */ #include <stdio.h>
void main( )
{
char z;
for (z=»A'; z<='Z '; z++)
printf("%c",z); |
*/ |
} /* Конец•программы |
Результат выполнения программы:
ABCDEFGHIJKIWNOPQRSTUVWXYZ
Строки, или строковые константы. В программе строки, или строковые константы, представляются последовательно стью изображений символов, заключенной в кавычки (не в апо строфы), например "любые_символы". Среди символов строки
13*
196 |
Программирование на языке Си |
могут быть эскейп-последовательности, соответствующие ко дам неизображаемых (специальных) символьных констант.
Примеры строк:
"1234567890"
"\t Состав президиума"
" Начало строки \п и конец строки".
Строки уже многократно встречались в функциях printf() и scanf().
Однако у строк есть некоторые особенности. Транслятор от водит каждой строке отдельное место в памяти ЭВМ даже в тех случаях, когда несколько строк полностью совпадают (стандарт языка Си предполагает, что в конкретных реализациях это пра вило может не выполняться). Размещая строку в памяти, транс лятор автоматически добавляет в ее конце символ '\0', т.е. нулевой байт. В записи строки может быть и один символ: "А", однако в отличие от символьной константы 'А' (использованы апострофы) длина строки "А" равна двум байтам. В отличие от других языков (например, от Паскаля) в языке Си нет отдельно го типа для строк. Принято, что строка - это массив символов, т.е. она всегда имеет тип char[ ]. Таким образом, строка счита ется значением типа "массив символов". Количество элементов в таком массиве на 1 больше, чем в изображении соответст вующей строковой константы, так как в конец строки добав лен нулевой байт '\0'.
Присвоить значение массиву символов (т.е. строке) с помо щью обычного оператора присваивания нельзя. Поместить строку в массив можно либо с помощью инициализации (при определении символьного массива), либо с помощью функций ввода. В функции scanf() или printf() для символьных строк используется спецификация преобразования %s.
При определении массива типа char с одновременной ини циализацией можно не указывать пределы изменения индекса. Сказанное иллюстрирует следующая программа:
/* Печать символьной строки */ #include <stdio.h>
void main( )
Г/шва 4. Указатели, массивы, строки |
197 |
. {
char В[ ]="Сезам, откройся!"; printf("%s",В);
> /* Конец программы */
Результат выполнения программы:
Сезам, откройся!
В программе длина массива В - 17 элементов, т.е. длина строки, помещаемой в массив (16 символов), плюс нулевой байт окончания строки. Именно 17 байтов выделяется при инициали зации массива в приведенном примере. Инициализация массива символов с помощью строковой константы представляет собой сокращенный вариант инициализации массива и введена в язык для упрощения. Можно воспользоваться обычной инициализа цией, поместив начальные значения элементов массива в фи гурные скобки и не забыв при этом поместить в конце списка начальных значений специальный символ окончания строки '\0'. Таким образом, в программе была бы допустима такая инициа лизация массива В:
char В [ ]-{' С ,'е','з','а','м',',',' ,,,о','т',
•к','р','о','й','с','я',•!',’\0• >;
Соглашение о признаке окончания строки нужно соблюдать, формируя в программах строки из отдельных символов. В каче стве примера рассмотрим следующую задачу: "Ввести предло жение, заканчивающееся точкой, слова в котором отделены пробелами. Напечатать последнее слово предложения".
Анализ условия задачи позволяет выявить следующие оши бочные ситуации и особые случаи: отсутствие точки в конце предложения; пробел или пробелы перед первым словом пред ложения; несколько пробелов между словами; пробелы перед завершающей предложение точкой; отсутствие слов в предло жении (только точка). Чтобы не усложнять решение задачи, примем соглашение о том, что вводимое предложение всегда размещается на одной строке дисплея, т.е. длина его не пре вышает 80 символов. Это позволит легко выявлять отсутствие точки в конце предложения и ограничивает длину любого слова
198 |
Программирование на языке Си |
предложения. Чтобы учесть особые ситуации с пробелами, не обходимо при анализе очередного введенного символа (переменная s) рассматривать и предыдущий символ (переменная ss). Для выявления отсутствия слов в предложении будем вычислять длину к каждого очередного вводимого слова. Если к = 0 , а вводится символ то это признак пустого пред ложения.
Текст программы:
/* Напечатать последнее слово в предложении* */ #include <stdio.h>
void ma±n( )
{
char |
s,ss; |
/* s |
- вводимый символ */ |
/* ss — предыдущий введенный символ */ |
|||
char А [80]; |
/* Массив для слова */ |
||
int |
i,k; |
/* k |
- длина слова */ |
printf("Напишите |
предложение с точкой в " |
||
for |
"конце:\п"); |
||
(i=0,s=' ',k=0; i<=79; i++) |
{
ss=s; s=getchar( ); if (s=' ') continue; if (s=='.') break;
if (ss=' ') k=0; A[k]=s; k++;
}
/♦Выход no точке или по окончании ввода строки*/ if (i==80 || k=0)
printf(" Неверное предложение \n"); else
{
A[k]='\0'; /* Конец строки */
printf("Последнее |
слово: %s",A); |
} |
*/ |
} /* Конец программы |
Чтение вводимых данных выполняется посимвольно в цикле с параметром i. Если вводится пробел, то оператор continue вы зывает переход к следующей итерации. Если введена точка, то цикл прерывается. При этом в первых к элементах массива А[]
Глава 4. Указатели, массиаы, строки |
199 |
запоминается последнее слово предложения. Если введенный символ отличен от точки и пробела, то анализируется предыду щий символ. Если это пробел, то начинается ввод следующего слова и устанавливается нулевое значение к. В следующих опе раторах введенный символ записывается в к-й элемент массива А й к увеличивается на 1. Выход из цикла возможен при появ лении точки или после ввода 80 символов. Последнее выполня ется при отсутствии в предложении точки. В этом ошибочном случае i= 8 0 . Когда в предложении нет слов, к остается равным нулю. Если i<80 и к не равно 0, то в к-й элемент массива А за писывается признак конца строки (последнего слова предложе ния), и она как единое целое выводится на печать.
Результат выполнения программы:
Напишите предложение с точкой в конце: Орфографический словарь.
Последнее слово: словарь
Как еще раз иллюстрирует приведенная программа, работа с символьными строками - это работа с массивами типа char. При этом следует обращать внимание на обязательное присут ствие в конце строки (в последнем занятом элементе массива) символа '\0' и не допускать его случайного уничтожения при обработке.
В качестве следующей задачи рассмотрим проверку вводи мой цифровой информации: "Необходимо, введя строку, напе чатать порядковый номер (позицию) каждого символа, который отличен от пробела или цифры". Задачу решает следующая про грамма:
/* Проверка вводимых числовых данных: */ #include <std±o.h>
void ma±n( )
{
char |
z [ ]="0123456789 "; |
char |
s; |
int |
i ,j ; |
printf("Введите строку символов:\n ") ; for (i=l; (s=getchar( ))!='\n'; i++)
200 Программирование на языке Си
{
for (j=0; j<ll; j++)
if (s = 2 [j]) break; if (j = 11)
printf("Ошибка в символе %c с номером " "%d\n", s,i);
) /* Конец цикла ввода */
}/* Конец программы */
В программе (в цикле по i) выполняется ввод отдельных символов до появления неизображаемого символа '\п' - перевод строки. Каждый введенный символ s сравнивается в цикле по j с элементами массива z, содержащего все допустимые символы. Если выявлено совпадение, то при выходе из цикла j не равно 11. При естественном завершении цикла (не найдено совпаде ния) j оказывается равным 11, и печатается номер ошибочного символа.
Пример результата выполнения программы:
Введите строку символов: 124Е-22 16 Ошибка в символе Е с номером 4 Ошибка в символе - с номером 5
Строки и указатели. Мы уже рассмотрели взаимосвязь ука зателей с массивами, так что возможность связать указатель с каждой строкой нас уже не удивит. Рассмотрим следующие два определения:
char Л [20];
/* Массив, в который можно записать строку */
char *В;
/* Указатель, с которым можно связать строку */
Массив А получает память автоматически при обработке оп ределения. Строке, с которой мы хотим связать указатель В, па мять при обработке определения указателя не выделяется. Поэтому если далее следуют операторы
scanf("%s",А); /* Оператор верен */
scanf("%s",В); /* Оператор не корректен */
Глава 4. Указатели, массивы, строки |
201 |
то первый из них допустим, а второй приводит к ошибке во время выполнения программы - попытка ввода в неопределен ный участок памяти. До выполнения второго из этих операто ров ввода с указателем В нужно связать некоторый участок па мяти. Для этого существует несколько возможностей. Во-пер вых, переменной В можно присвоить адрес уже определенного символьного массива. Во-вторых, указатель В можно "на строить" на участок памяти, выделяемый с помощью средств динамического распределения памяти (см. табл. 4.1). Например, оператор
B=(char *) malloc(80);
выделяет 80 байт н связывает этот блок памяти с указателем В. Только теперь применение приведенного выше оператора ввода допустимо. Прототипы функции malloc() и других функций распределения памяти находятся в файле stdlib.h.
С помощью указателей типа char* удобно получать доступ к строкам в виде массивов символов. Типичная задача - обработ ка слов или предложений, каждое из которых представлено в массиве типа char в виде строки (т.е. в конце представления предложения или слова находится нуль-символ '\0'). Использо вание указателей, а не массивов с фиксированными размерами особенно целесообразно, когда предложения или слова должны быть разной длины. В следующей программе определен и при инициализации связан с набором строк одномерный массив point[ ] указателей типа char*. Функция printf() и специфика тор преобразования %s допускают использование в качестве параметра указатель на строку. При этом на дисплей (в выход ной поток) выводится не значение указателя point[i], а содержи мое адресуемой им строки.
Текст программы:
#±nclude <stdio.h> void main( )
{
char * point! ]={"thirteen","fourteen", "fifteen","sixteen","seventeen","eighteen", "ninteen"};