Мы разбили вводимые символы на двенадцать категорий, и нам удобнее использовать массив для хранения числа появлений каждой цифры, а не десять отдельных переменных. Вот один из вариантов программы:
// Подсчет всех цифр,
// промежутков (пробел, табуляции, новая строка)
// и всех остальных символов
main()
{
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother = 0;
for (i = 0; i < 10; ++i)
ndigit[i] = 0;
while ((c = getchar()) != eof)
if (c >= '0' && c <= '9')
++ndigit[c-'0'];
else if(c== ' ' || c== '\n' || c== '\t')
++nwhite;
else
++nother;
printf("digits =");
for (i = 0; i < 10; ++i)
printf(" %d", ndigit[i]);
printf("\nwhite space = %d, other = %d\n",
nwhite, nother);
}
Описание:
int ndigit[10];
объявляет, что ndigit является массивом из десяти целых. В языке «C» индексы массива всегда начинаются с нуля (а не с 1, как в ФОРТРАНЕ или PL/1/, так что элементами массива являются:
ndigit[0], ndigit[1], ..., ndigit[9].
Эта особенность отражена в циклах for , которые инициализируют и печатают массив.
Индекс может быть любым целым выражением, которое, конечно, может включать целые переменные, такие как I , и целые константы.
Эта конкретная программа сильно опирается на свойства символьного представления цифр. Так, например, в программе проверка:
if( c >= '0' && c <= '9')...
определяет, является ли символ в символьной переменной c цифрой, и если это так, то численное значение этой цифры определяется по формуле:
c - '0' .
Такой способ работает только в том случае, если значения символьных констант '0', '1' и т.д. Положительны, расположены в порядке возрастания и нет ничего, кроме цифр, между константами '0' и '9'. К счастью, это верно для всех общепринятых наборов символов.
По определению перед проведением арифметических операций, вовлекающих переменные типа char и int, все они преобразуются к типу int, Tак что в арифметических выражениях переменные типа CHAR по существу идентичны переменным типа int. Это вполне естественно и удобно. Например:
c - '0'
- это целое выражение со значением между 0 и 9 в соответствии с тем, какой символ от '0' до '9' хранится в переменной c, и, следовательно, оно является подходящим индексом для массива ndigit.
Выяснение вопроса, является ли данный символ цифрой, символом пустого промежутка или чем-либо еще, осуществляется последовательностью операторов
if (c >= '0' && c <= '9')
++ndigit[c-'0'];
else if(c == ' ' || c == '\n' || c == '\t')
++nwhite;
else
++nother;
Конструкция
if (условие)
оператор-1;
else if (условие)
оператор-2;
else
оператор-3;
часто встречаются в программах как средство выражения ситуаций, в которых осуществляется выбор одного из нескольких возможных решений.
Программа просто движется сверху вниз до тех пор, пока не удовлетворится какое-нибудь условие; тогда выполняется соответствующий «оператор»*), и вся конструкция завершается.
Если ни одно из условий не удовлетворяется, то выполняется «оператор», стоящий после заключительного else, если оно присутствует. Если последнее else и соответствующий «оператор» опущены (как в программе подсчета слов), то никаких действий не производится. Между начальным if и конечным else может помещаться произвольное количество групп:
else if (условие)
оператор .
С точки зрения стиля целесообразно записывать эту конструкцию так, как мы показали, с тем, чтобы длинные выражения не залезали за правый край страницы.
Оператор switch (переключатель), который рассматривается в главе 4, представляет другую возможность для записи разветвления на несколько вариантов, этот оператор особенно удобен, когда проверяемое выражение является либо просто некоторым целым, либо символьным выражением, совпадающим с одной из некоторого набора констант. Версия этой программы, использующая оператор switch, будет для сравнения приведена также в главе 4.
Упражнение 2-12. Напишите программу, печатающую гистограмму длин слов из файла ввода. Самое легкое – это начертить гистограмму горизонтально; вертикальная ориентация требует больших усилий.
В языке «C» функции эквивалентны подпрограммам или функциям в ФОРТРАНЕ или процедурам в PL/1, ПАСКАЛЕ и т.д. Функции дают удобный способ заключения некоторой части вычислений в черный ящик, который в дальнейшем можно использовать, не интересуясь его внутренним содержанием. Использование функций является фактически единственным способом справиться с потенциальной сложностью больших программ. Если функции организованы должным образом, то можно игнорировать то, как делается работа; достаточно знание того, что делается. Язык «C» разработан таким образом, чтобы сделать использование функций легким, удобным и эффективным. Вам будут часто встречаться функции длиной всего в несколько строчек, вызываемые только один раз, и они используются только потому, что это проясняет некоторую часть программы.
До сих пор мы использовали только предоставленные нам функции типа printf, getchar и putchar; теперь пора написать несколько наших собственных. так как в «C» нет операции возведения в степень, подобной операции ** в ФОРТРАНЕ или PL/1.
Давайте проиллюстрируем механику определения функции на примере функции power(m,n), возводящей целое m в целую положительную степень n. Так значение power(2,5) равно 32. Конечно, эта функция не выполняет всей работы операции **, поскольку она действует только с положительными степенями небольших чисел, но лучше не создавать дополнительных затруднений, смешивая несколько различных вопросов.
Пример 2-11. Ниже приводится функция power и использующая ее основная программа, так что вы можете видеть целиком всю структуру.
main() // Испытание функции возведения в степень
{
int i;
for(i = 0; i < 10; ++i)
printf("%d %d %d\n",i,power(2,i),power(-3,i));
}
power(int x, int n) // Возведение x в степень n, n>0
{
int i, p;
p = 1;
for (i =1; i <= n; ++i)
p = p * x;
return (p);
}
Все функции имеют одинаковый вид:
имя (список аргументов, если они имеются)
описание аргументов, если они имеются
{
описания
операторы
}
Эти функции могут быть записаны в любом порядке и находиться в одном или двух исходных файлах. Конечно, если исходная программа размещается в двух файлах, вам придется дать больше указаний при компиляции и загрузке, чем если бы она находилась в одном, но это дело операционной системы, а не атрибут языка. В данный момент, для того чтобы все полученные сведения о прогоне «C»- программ, не изменились в дальнейшем, мы будем предполагать, что обе функции находятся в одном и том же файле.
Функция power вызывается дважды в строке
printf("%d %d %d\n",i,power(2,i),power(-3,i));
при каждом обращении функция power, получив два аргумента, возвращает целое значение, которое печатается в заданном формате. В выражениях power(2,i) является точно таким же целым, как 2 и i. (Однако не все функции выдают целое значение; мы займемся этим вопросом в главе 5).
Аргументы функции power должны быть описаны соответствующим образом, так как их типы известны. Это сделано в строке
int x,n;
которая следует за именем функции.
Описания аргументов помещаются между списком аргументов и открывающейся левой фигурной скобкой; каждое описание заканчивается точкой с запятой. Имена, использованные для аргументов функции power, являются чисто локальными и недоступны никаким другим функциям: другие процедуры могут использовать те же самые имена без возникновения конфликта.
Это верно и для переменных i и p; i в функции power никак не связано с i в функции main.
Значение, вычисленное функцией power, передаются в main с помощью оператора return, точно такого же, как в PL/1. Внутри круглых скобок можно написать любое выражение. Функция не обязана возвращать какое-либо значение; оператор return, не содержащий никакого выражения, приводит к такой же передаче управления, как «сваливание на конец» функции при достижении конечной правой фигурной скобки, но при этом в вызывающую функцию не возвращается никакого полезного значения.
Упражнение 2-13. Напишите программу преобразования прописных букв из файла ввода в строчные, используя при этом функцию ower(c), которая возвращает значение – переменную c, если символ в c – не буква, и значение соответствующей строчной буквы, если c – это буква.
2.8. Аргументы: вызов по значению
Один аспект в «C» может оказаться непривычным для программистов, которые использовали другие языки, в частности, ФОРТРАН и PL/1. В языке «C» все аргументы функций передаются «по значению». Это означает, что вызванная функция получает значения своих аргументов с помощью временных переменных (фактически через стек), а не их адреса. Это приводит к некоторым особенностям, отличным от тех, с которыми мы сталкивались в языках типа ФОРТРАНА и PL/1, использующих (вызов по ссылке), где вызванная процедура работает с адресом аргумента, а не с его значением.
Главное отличие состоит в том, что в «C» вызванная функция не может изменить переменную из вызывающей функции; она может менять только свою собственную временную копию.
Вызов по значению, однако, не помеха, а весьма ценное качество. Оно обычно приводит к более компактным программам, содержащим меньше не относящихся к делу переменных, потому что с аргументами можно обращаться как с удобно инициализированными локальными перемнными вызванной процедуры.
Пример 2-12. Рассмотрим вариант функции power, использующей данное обстоятельство
power(int x, int n)
{
int p;
for (p = 1; n > 0; --n)
p = p * x;
return (p);
}
Аргумент n используется как временная переменная; из него вычитается единица до тех пор, пока он не станет нулем. Переменная i здесь больше не нужна. чтобы ни происходило с n внутри power это никак не влияет на аргумент, с которым первоначально обратились к функции power.