У цій відповіді я припускаю, що ви читаєте та інтерпретуєте рядки тексту . Можливо, ви спонукаєте користувача, який щось набирає та натискає кнопку ВІДРАТИ. Або, можливо, ви читаєте рядки структурованого тексту з якогось файлу даних.
Оскільки ви читаєте рядки тексту, має сенс організувати свій код навколо функції бібліотеки, яка читає, ну, рядок тексту. Функція Standard є fgets()
, хоча є й інші (в тому числі getline
). А далі наступний крок - якось інтерпретувати цей рядок тексту.
Ось основний рецепт заклику fgets
прочитати рядок тексту:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Це просто читається в одному рядку тексту і виводить його назад. Як написано, у нього є кілька обмежень, до яких ми дістанемося за хвилину. Він також має дуже чудову особливість: це число 512, яке ми передали як другий аргумент, fgets
- це розмір масиву, який
line
ми просимо fgets
прочитати. Цей факт - що ми можемо сказати, fgets
скільки дозволено читати - означає, що ми можемо бути впевнені, що fgets
він не переповнить масив, прочитавши в нього занадто багато.
Отже, тепер ми знаємо, як читати рядок тексту, але що робити, якщо ми дійсно хотіли прочитати ціле число, або число з плаваючою комою, або один символ, або одне слово? (Тобто, що робити , якщо
scanf
виклик ми намагаємося поліпшити був використанням специфікатора формату , як %d
, %f
, %c
або %s
?)
Легко переосмислити рядок тексту - рядок - як будь-який із цих речей. Для перетворення рядка в ціле число найпростіший (хоча і недосконалий) спосіб це зробити - зателефонувати atoi()
. Для того, щоб перетворити в число з плаваючою точкою, є atof()
. (І є й кращі способи, як ми побачимо через хвилину.) Ось дуже простий приклад:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
Якщо ви хотіли, щоб користувач ввів один символ (можливо, y
або
n
як відповідь "так / ні"), ви можете буквально просто схопити перший символ рядка, наприклад:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Це, звичайно, ігнорує можливість того, що користувач набрав відповідь з кількома символами; він спокійно ігнорує будь-які додаткові символи, які були введені.)
Нарешті, якщо ви хотіли, щоб користувач вводив рядок, який точно не містить пробілів, якщо ви хочете обробити рядок введення
hello world!
як рядок, що "hello"
слідує за чимось іншим (а це був би scanf
формат, який %s
би зробив), ну, в такому випадку я трохи перерізав, все-таки не так просто переосмислити рядок, зрештою, тому відповідь на це частина питання доведеться трохи почекати.
Але спершу я хочу повернутися до трьох речей, які я пропустив.
(1) Ми телефонували
fgets(line, 512, stdin);
читати в масив line
, і де 512 є розмір масиву line
так fgets
не знає переповнення його. Але для того, щоб переконатися, що 512 - це правильне число (особливо, щоб перевірити, чи можливо хтось налаштував програму, щоб змінити розмір), вам доведеться прочитати туди, де line
було оголошено. Це неприємність, тому є два набагато кращі способи синхронізації розмірів. Ви можете (a) використовувати препроцесор, щоб створити ім'я для розміру:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Або (b) використовувати sizeof
оператор C :
fgets(line, sizeof(line), stdin);
(2) Друга проблема полягає в тому, що ми не перевіряли наявність помилок. Читаючи дані, ви завжди повинні перевірити можливість помилки. Якщо з будь-якої причини fgets
не вдається прочитати рядок тексту, про який ви його просили, це вказує, повертаючи нульовий покажчик. Тож ми повинні були робити такі речі
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Нарешті, проблема полягає в тому, що для того, щоб прочитати рядок тексту, він
fgets
зчитує символи та заповнює їх у свій масив, поки не знайде \n
символ, який закінчує рядок, і він також заповнить \n
символ у своєму масиві . Ви можете побачити це, якщо трохи змінити наш попередній приклад:
printf("you typed: \"%s\"\n", line);
Якщо я запускаю це і набираю "Стів", коли це підказує, воно виводиться на друк
you typed: "Steve
"
Це "
на другому рядку - це те, що рядок, яку він читав і друкував назад, був насправді "Steve\n"
.
Іноді цей додатковий рядок не має значення (наприклад, коли ми телефонували
atoi
або atof
, оскільки вони обидва ігнорують будь-який додатковий нечисловий ввід після числа), але іноді це має велике значення. Тому часто ми хочемо зняти цей новий рядок. Є кілька способів зробити це, до якого я дістанусь за хвилину. (Я знаю, що я говорив це багато. Але я повернусь до всіх тих речей, обіцяю.)
У цей момент ви можете задуматися: "Я думав, що ви сказали, що scanf
це не добре, а цей інший спосіб буде набагато кращим. Але fgets
починає виглядати як неприємність. Виклик scanf
був таким простим ! Чи не можу я продовжувати його використовувати? "
Звичайно, ви можете продовжувати використовувати scanf
, якщо хочете. (А для справді
простих речей, певним чином, це простіше.) Але, будь ласка, не плачте до мене, коли він не дає вам ладу через одну з 17 його примх і байок, або переходить у нескінченну петлю через введення вашої не сподівався, або коли ви не можете зрозуміти, як за допомогою нього зробити щось складніше. А давайте подивимось на fgets
фактичні неприємності:
Ви завжди повинні вказати розмір масиву. Ну, звичайно, це зовсім неприємно - це особливість, адже переповнення буфера - це справді погана річ.
Ви повинні перевірити повернене значення. Насправді це миття, тому що для scanf
правильного використання ви також повинні перевірити його повернене значення.
Ви повинні зняти \n
спинку. Це, я визнаю, справжня неприємність. Мені б хотілося, щоб існувала стандартна функція, я можу вас вказати на те, що не мала цієї малої проблеми. (Будь ласка, ніхто не виховує gets
.) Але порівняно з scanf's
17 різними неприємностями, я прийму цю одну неприємність у fgets
будь-який день.
Так як же ви видалите цей рядок? Три способи:
(a) Очевидний спосіб:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) Хитрий та компактний спосіб:
strtok(line, "\n");
На жаль, це не завжди працює.
(c) Ще один компактний і м'яко незрозумілий спосіб:
line[strcspn(line, "\n")] = '\0';
І тепер, коли це не в змозі, ми можемо повернутися до іншої речі, яку я пропустив: недосконалості atoi()
та atof()
. Проблема в тому, що вони не дають вам жодної корисної вказівки на успіх чи невдачу: вони спокійно ігнорують проміжок нечислового введення, і вони спокійно повертають 0, якщо числового введення взагалі немає. Кращі альтернативи - які також мають певні інші переваги - є strtol
і strtod
.
strtol
також дозволяє використовувати базу, відмінну від 10, тобто ви можете отримати ефект (серед іншого) %o
або за %x
допомогоюscanf
. Але показати, як правильно використовувати ці функції, - це історія сама по собі, і це було б занадто відволіканням від того, що вже перетворюється на досить фрагментарну розповідь, тому я нічого більше про них зараз не збираюся говорити.
Решта основних розповідей стосується введення, яке ви, можливо, намагаєтеся розібрати, що є складнішим, ніж просто одне число чи символ. Що робити, якщо ви хочете прочитати рядок, що містить два числа, або кілька розділених пробілами слів, або конкретні обрамлювальні знаки? Ось де речі стають цікавими, і де, ймовірно, ускладнюються речі, якщо ви намагалися робити речі за допомогою scanf
, і де набагато більше варіантів тепер, коли ви чисто читали один рядок тексту, використовуючи fgets
, хоча повний розповідь про всі ці варіанти Можливо, можна було б заповнити книгу, тож ми лише зможемо тут подряпати поверхню.
Моя улюблена техніка - розбити рядок на "слова", розділені пробілом, а потім зробити щось далі з кожним "словом". Однією з основних стандартних функцій для цього є
strtok
(яка також має свої проблеми, і яка також оцінює цілу окрему дискусію). Моє власне уподобання - це виділена функція для побудови масиву покажчиків до кожного розбитого "слова", функції, яку я описую в
цих конспектах курсу . У будь-якому випадку, щойно ви отримаєте "слова", ви можете додатково обробити кожне з них, можливо, з тими ж atoi
/ atof
/ strtol
/ strtod
функціями, які ми вже розглянули.
Як не парадоксально, хоча ми витратили тут чимало часу і зусиль, з'ясовуючи, як відійти від цього scanf
, ще один прекрасний спосіб поводження з рядком тексту, який ми щойно прочитали,
fgets
- це передати його sscanf
. Таким чином ви отримуєте більшість переваг scanf
, але без більшості недоліків.
Якщо ваш синтаксис введення особливо складний, може бути доцільним використовувати бібліотеку "regexp" для його розбору.
Нарешті, ви можете використовувати будь-які спеціальні рішення для аналізу. Ви можете переміщати рядок символу одночасно за допомогою
char *
вказівника, перевіряючи очікувані символи. Або ви можете здійснити пошук конкретних символів , використовуючи функції , такі як strchr
або strrchr
, або , strspn
або strcspn
, або strpbrk
. Або ви можете проаналізувати / перетворити та пропустити групи знакових символів за допомогою функцій strtol
або,
strtod
які ми пропустили раніше.
Очевидно, що можна сказати набагато більше, але, сподіваємось, це вступ вас почне.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2
не визначає, як поганий текст, що не вводиться цифрами.