Четверта, з нашого набору корисних програм, лічить рядки, слова та знаки, з приблизним визначенням, що слово — це будь-яка послідовність знаків, що не містить пробілів, табуляції або символу нового рядка. Це спрощена версія Юнікс-програми wc.
#include <stdio.h>
#define IN 1 /* всередині слова */
#define OUT 0 /* зовні слова */
/* лічить рядки, слова та знаки вводу */
main()
{
int c, nl, nw, nc, state;
state = OUT;
nl = nw = nc = 0;
while ((c = getchar()) != EOF) {
++nc;
if (c == '\n')
++nl;
if (c == ' ' || c == '\n' || c = '\t')
state = OUT;
else if (state == OUT) {
state = IN;
++nw;
}
}
printf("%d %d %d\n", nl, nw, nc);
}
Кожний раз, як програма зустрічає перший знак слова, вона додає до рахунку ще одне слово. Змінна state занотовує, чи програма у дану мить знаходиться всередині слова, чи ні; початково вона не «у слові», маючи значення OUT. Ми надаємо перевагу символічним константам IN і OUTперед буквальними значеннями 1 і 0, оскільки перші роблять програму зрозумілішою. Якщо це маленька програма як от ця, різниця не відчутна, але в більших програмах, покращення прочитності варте цього невеличкого зусилля — написати саме так з самого початку. Ви також дійдете висновку, що набагато легше впроваджувати широкі зміни в програмах, де «магічні» числа з'являються тільки як символічні константи.
Рядочок
nl = nw = nc = 0;
встановлює всі три змінні у значення нуль. Це не є спеціальним випадком — скоріше наслідок того, що присвоєння є виразом із певним значенням, і присвоєння спрягаються з права на ліво. Це так, ніби ми написали б
nl = (nw = (nc = 0));
Оператор || означає АБО, тож рядок
if (c == ' ' || c == '\n' || c = '\t')
можна озвучити як «якщо c є пробілом АБО c є символом нового рядка АБО c є кроком табуляції». (Якщо пригадуєте, екранована послідовність \t є видимим представленням табуляції.) Існує відповідний оператор &&, що означає логічне I (ТА), його пріоритет вищий за ||. Вирази, поєднані && або || оцінюються з ліва на право і гарантовано, що оцінювання припиниться, як тільки істинність чи хибність стане відомою. Якщо c є пробілом, то необхідність перевіряти, чи cдорівнює символу нового рядка, чи табуляції відпадає, тож ці перевірки опущено. Це не настільки важливо тут, але суттєво в складніших випадках, як ми скоро побачимо.
Цей приклад також демонструє else, який описує альтернативну дію, якщо умовна частина твердження if виявиться хибною. Загальною формою буде
if (вираз)
твердження1
else
твердження2
Одне, і тільки одне, з двох тверджень, пов'язаних з if-else, буде виконано. Якщо вираз в дужках є істинним, буде виконано твердження1, якщо ні — твердження2. Твердження можуть бути як одним, так і багатьма, включеними у фігурні дужки. У програмі підрахунку слів, після else знаходиться if з двома твердженнями, включеними у фігурні дужки.
Вправа 1-11. Як би ви перевірили програму підрахунку слів? Які типи вводу ймовірно виявлять помилки, якщо такі є?
Вправа 1-12. Напишіть програму, яка би виводила свій ввід по одному слову на рядок.
Масиви
Тепер, напишімо програму, яка підрахує кількість кожної цифри, пропусків (пробіл, табуляція і знак нового рядка) і решти знаків. Це трохи штучно, але дозволяє проілюструвати декілька аспектів C в одній програмі.
У нас буде дванадцять категорій можливого вводу, тож має зміст використати масив для утримування числа повторень тієї самої цифри, замість десяти окремих змінних. Ось одна з можливих версій програми:
#include <stdio.h>
/* лічить цифри, пропуски та інші знаки */
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(", white space = %d, other = %d\n", nwhite, nother);
}
Вивід самої програми може виглядати як
digits = 9 3 0 0 0 0 0 0 0 1, white space = 123, other = 345
Оголошення
int ndigit[10];
описує ndigit, як масив з 10-и цілих. Індексація масивів завжди починається з нуля в C, тож елементами будуть ndigit[0], ndigit[1], ..., ndigit[9]. Це відображено у циклах for, які ініціалізують і виводять масив.
Індексом може бути будь-який вираз типу int, включаючи цілочисельну змінну, як от i, та цілочисельні константи.
Ця програма покладається на символьне представлення цифр. Так, наприклад, перевірка
if (c >= '0' && c <= '9')
визначає, чи символ, який міститься в c є цифрою. Якщо так, числовим значенням цієї цифри буде
c - '0'
Це працює тільки за умови, що символи '0', '1', ..., '9' мають послідовно-зростаючі значення. На щастя, це справджується в усіх наборах символів.
За означенням, char — це просто малі цілі, тож змінні і сталі типу 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)
твердження1
else if (умова2)
твердження2
...
...
else
твердження
зустрічається доволі часто в програмах, як один з способів виразити розгалуження рішень. Умови розглянуто по-порядку, починаючи зверху, до тих пір, доки одна з умов не справдиться, у разі чого буде виконано відповідне твердження, і ціла конструкція закінчить своє існування. Якщо жодна з умов не є істинною, тоді буде виконано твердження після останнього else, якщо таке існує. Якщо ж останнє else і відповідне твердження відсутні, як у випадку з програмою відліку слів, тоді жодної дії не відбудеться. Можна використати будь-яку кількість
else if (умова)
твердження
груп, між початковим if і кінцевим else.
Щодо стилю, то радимо форматувати цю конструкцію саме так, як ми показали; якби кожний ifвирівнювався з попереднім else, довга черга розгалужень змістилась би до правого боку сторінки.
Твердження switch, яке буде розглянуто у Розділі 4, — це інший спосіб написання розгалуження рішень, особливо корисне у випадку, коли умова складається з якогось цілого чи символьного виразу, який порівнюється з набором констант. Для контрасту, у Розділі 3.4 ми представимо switch-версію цієї програми.
Вправа 1-13. Напишіть програму, яка би виводила гістограму довжин слів вводу. Гістограму легко намалювати горизонтальними стрижнями; вертикальну орієнтацію гістограми втілити трохи складніше.
Вправа 1-14. Напишіть програму виводу гістограми частоти різних знаків вводу.