osn_progr_final
.pdfprintf(“номер вісімковий:”); printf(“%o\n”,c-‘0’); printf(“номер шістнадцятковий:”); printf(“%x\n”,c-‘0’); printf(“код десятковий:”); printf(“%d\n”,c);
printf(“код вісімковий:”); printf(“%o\n”,c);
printf(“код шістнадцятковий:”); printf(“%x\n”,c);
}
Приклад 2 Підрахувати кількість символів вхідного потоку
Розглянемо наступну програму:
#include <stdio.h> main()
{
long nc; nc = 0;
while (getchar() != EOF) ++nc;
printf("%1d\n", nc);
}
Програма підрахунку символів накопичує їхню кількість у змінну типу long, а не int (максимальне число32767), і якщо описати лічильник як int, те він буде переповнятися навіть при порівняно малому файлі . Специфікація перетворення %1d указує printf, що відповідний аргумент є цілим типу long .
Щоб справитися ще з більшими числами, ви можете використовувати тип double . Ми також використовуємо оператор for замість while для того, щоб проілюструвати інший спосіб запису циклу.
main()
{ /* count characters in * input */
double nc;
for (nc = 0; getchar() != EOF; ++nc); printf("%.0f\n", nc);
}
191
Функція printf використовує специфікацію %f як для float, так і для double; специфікація %.0f дозволяє не друкувати дробову частину. Тіло оператора циклу for тут порожнє, тому що вся робота виконується в перевірочній і ініціалізуючій частинах. Відзначимо, що якщо файл вводу не містить ніяких символів, то умова в while чи for не виконається при найпершому звертанні до getchar, і, отже, програма видасть нуль.
Приклад 3 Підрахувати кількість рядків у файлі .
Будемо вважати, що рядок закінчується \n.
main() |
|
{ |
/* count lines in input */ |
int s, nl; nl = 0;
while ((s= getchar()) != EOF) if (s== '\n')
++nl; printf("%d\n", nl);
}
Тіло while тепер містить оператор if, що у свою чергу, керує оператором інкремента ++nl. Оператор if перевіряє вкладену в круглі дужки умову і, якщо вона істинна, виконує наступний за ним оператор
Приклад 4 Написати програму, що копіює символи вхідного файлу у вихідний
Загальна схема програми має вигляд:
ввести символ
while (символ не є ознакою кінця файлу) вивести тільки що прочитаний символ ввести новий символ
Ось текст програми:
#include <stdio.h> |
|
main() |
|
{ |
/* copy input tо output; |
192
* 1st version */
int c;
c = getchar(); while (c != EOF) {
putchar(c);
c = getchar();
}
}
Основна проблема полягає в тому, щоб зафіксувати кінець файлу . Звичайно, коли функція getchar досягає кінця файлу, вона повертає значення, що не є дійсним символом. Але яке значення є ознакою кінця файлу? (Це може бути –1 чи 0). Щоб універсалізувати підхід в таких випадках використовується символічне ім”я (макровизначення) EOF, яке визначається як
#define EOF -1
або
#define EOF 0
В прикладі також описана змінна 'с' як int, а не char, для того щоб вона могла зберігати значення, що повертається getchar .
Ця програма, звичайно, могла б бути написана більш стисло. У мові C будь-яке присвоювання, як наприклад
c = getchar();
може бути використане у виразі; його значенням є значення, що присвоюється лівій частині. Тоді програма копіювання файлу запишеться у вигляді:
main()
{ /* copy input -> output; * 2nd version */
int c;
while ((c = getchar()) != EOF) putchar(c);
}
Програма отримує символ з стандартнго вводу, присвоює його змінній 'с' і потім перевіряє, чи не є цей символ ознакою кінця файлу. Якщо не є - виконується тіло оператора while, що виводить цей символ. Потім цикл while повторюється. Коли, нарешті, буде досягнутий кінець файлу введення, оператор while завершується, а разом з ним закінчується виконання і функції main.
193
Важливо зрозуміти, що круглі дужки навколо присвоювання в умовному виразі дійсно необхідні. Пріоритет операції != вищий, ніж операції присвоювання =, а це означає, що під час відсутності круглих дужок перевірка умови != буде виконана до присвоювання =. Таким чином, оператор
c = getchar() != EOF
еквівалентний оператору c = (getchar() != EOF)
Приклад 5 Напишіть програму з використанням циклів, котра малює трикутник:
*
***
*****
*******
*********
/* Трикутник із зірочок */
#include <stdio.h>
/* Друк n символів c */ printn(c, n){
while( --n >= 0 ) putchar(c);
}
int lines = 10; /* кількість рядків трикутника */ void main(argc, argv)
char *argv[];
{
register int nline; /* номер рядка */
register int naster; /* кількість зірочок в рядку */ register int i;
if( argc > 1 )
lines = atoi( argv[1] );
for( nline=0; nline < lines ; nline++ ){ naster = 1 + 2 * nline;
/* лідируючі пропуски */ printn(' ', lines-1 - nline);
/* зірочки */ printn('*', naster);
/* перевід рядка */ putchar( '\n' );
} |
|
exit(0); |
/* завершення програми */ |
194
}
Приклад 6 Написати програму, що підраховує кількість слів у файлі
Розглянемо програму, що підраховує кількість рядків, слів і символів. Будемо вважати, що словом є будь-яка послідовність символів, яка не містить пропусків чи символів табуляції.
#define yes 1 #define no 0
main()
{
int c, nl, nw, inword;
inword = no;
nl = nw = nc = 0;
while ((c = getchar()) != EOF) { ++nc;
if (з== '\n') ++nl;
if (c == ' ' || c == '\n' || c == '\t') inword = no;
else if (inword == no) { inword = yes; ++nw;
}
}
printf("%d %d %d\n", nl, nw, nc);
}
Щоразу, коли програма зустрічає перший символ слова, вона збільшує лічильник числа слів на одиницю. Змінна inword стежить за тим, чи знаходиться програма в даний момент всередині слова чи ні; спочатку цій змінній присвоюється " не в слові", чому відповідає значення no. Ми віддаємо перевагу символічним константам yes і no перед літерними значенням 1 і 0, тому що вони роблять програму більш зручною для читання. Також істотні зміни набагато легше вносити в ті програми, де числа фігурують тільки як константи.
Рядок
nl = nw = nc = 0;
195
дозволяє всім трьом змінним присвоїти нулі. операція || означає ”або”, так що рядок
if (c == ' ' || c == '\n' || c == '\t')
означає "якщо c - пропуск, чи c - символ нового рядка, чи c -табуляція
...". (Умовна послідовність \t є зображенням символу табуляції). Оператор, що слідує за else, є опертором if, що керує двома операторами у фігурних дужках.
Приклад 7 Підрахувати кількість входжень кожної з літер алфавіта в файл
#include <stdio.h> #include <ctype.h>
long bcnt[8]; |
|
|
|
char masks[8] = { |
/* маски битов */ |
||
1, 2, 4, 8, 16, 32, 64, 128 }; |
|
||
long cnt[256]; |
/* счетчики для каждой из 256 |
||
букв */ |
|
|
|
/* роздруковка лытер */ |
|
|
|
char *pr( c ){ |
|
|
|
static char buf[ 20 ]; |
|
|
|
switch( c ){ |
|
|
|
case '\n': return |
" |
\\n " |
; |
case '\r': return |
" |
\\r " |
; |
case '\t': return |
" |
\\t " |
; |
case '\b': return |
" |
\\b " |
; |
case '\f': return |
" |
\\f " |
; |
case '\033': return " |
ESC" |
; |
|
case '\0': return |
" |
\\0 " |
; |
case 0177: return |
" ^? " |
; |
|
} |
|
|
|
if( c < ' ' ){
sprintf( buf, " ^%c ", c + 'A' - 1 ); }else if( isspace(c)){
sprintf( buf, " '%c'", c ); }else if( ! isprint( c ))
sprintf( buf, "\\%3o", c ); else sprintf( buf, " %c ", c ); return buf;
}
196
main( argc, argv ) char **argv; { FILE *fp;
if( argc == 1 ) process( stdin ); else{ argv++; argc--;
while( *argv ){
printf( "----- FILE %s -----\n", *argv ); if((fp = fopen( *argv, "r" )) == NULL ){
printf( "Can not open\n" );
}else{ process( fp ); fclose( fp ); } argv++; argc--;
}
}
exit(0);
}
/* обробити файл з вказівником fp */ process( fp ) FILE *fp;
{register i; int c; int n;
/* зачистка лічильників */
for( i=0; i < 256; i++ ) cnt[i] = 0L; for( i=0; i < 8 ; i++ ) bcnt[i] = 0;
while( ( c=getc(fp)) != EOF ){ c &= 0377;
/* по букву */ cnt[ c ] ++;
/* підрахунок бітів */ for( i=0; i < 8; i++ )
if( c & masks[i] ) bcnt[ i ] ++;
}
/* видача результатів в COL колонок */
#define COL 4
printf( "\tASCII map\n" ); for( n=i=0; i < 256; i++ ){
/* if( cnt[i] == 0l ) continue; */
printf( "%s %5ld |", pr(i), cnt[i] );
if( ++n == COL ){ n = 0; putchar('\n'); }
/* чи if((i % COL) == (COL-1)) putchar('\n'); */
}
printf( "\n\tBITS map\n" );
197
for( i=7; i >=0 ; i-- ) printf( "%6d ", i ); putchar( '\n' );
for( i=7; i >=0 ; i-- )
printf( "%6ld ", bcnt[i] ); putchar( '\n' ); putchar( '\n' );
}
Приклад 8 Напишемо програму, що зчитує рядки і друкує найдовший з них.
Схема програми наступна: while (є ще рядок)
if (цей рядок довший самого довшого з попередніх) запам'ятати цей рядок і його довжину надрукувати самий довгий рядок
За цією схемою ясно, що програма природньо розпадається на кілька частин. Одна частина читає новий рядок, інша перевіряє його, третя запам'ятовує, а інші частини програми керують цим процесом.
#define maxline 1000 /* довжина максимального рядка*/ main()
{int len; /* довжина біжучого рядка */
int |
max; /* максимальна довжина */ |
char |
line[maxline]; /* біжучий рядок */ |
char |
save[maxline];/*найдовший збережений рядок */ |
max = 0;
while ((len = getline(line, maxline)) > 0) if (len > max) {
max = len; copy(line, save);
}
if (max > 0) printf("%s", save);
}
getline(s, lim) /* отримує рядок та записує його в * s,повертає довжину рядка */
char s[]; int lim;
{
int c, i; for (i = 0;
i < lim - 1 && (c = getchar()) != EOF && c != '\n';
198
++i)
s[i] = c; if (c == '\n') {
s[i] = c; ++i;
}
s[i] = '\0'; return (i);
} |
|
|
copy(s1, s2) |
/* копіює s1 в s2; */ |
|
char |
s1[], s2[]; |
|
{ |
|
|
int |
i; |
|
i = 0;
while ((s2[i] = s1[i]) != '\0') ++i;
}
Функції main і getline “спілкуються” як через пару аргументів, так і через значення, що повертається. Аргументи getline описані в рядках
char |
s[]; |
int |
lim; |
які вказують, що перший аргумент є масивом, а другий - цілим. Довжина масиву s не зазначена, тому що вона визначена в main . Функція getline використовує оператор return для передачі значення назад, точно так само, як це робила функція power . Щоб позначити кінець рядка символів, функція getline поміщає в кінець створюваного нею масиву символ ‘\0’.
Специфікація формату %s вказує, що printf очікує рядок, що закінчується нуль-символом. Проаналізувавши функцію copy, бачимо , що і її вхідний аргумент повинен закінчуватись символом ‘\0’, вона копіює цей символ у вихідний аргумент s2.
Відмітимо, що функція getline при заповненні масиву припиняє подальше отримання символів, якщо рядок перевищив максимальнодопустиму довжину, навіть якщо не зустрічає символу нового рядка. Перевіривши отриману довжину й останній символ, функція main може встановити, чи не був цей рядок занадто довгим.
Користувач функції getline ніяк не може заздалегідь довідатися, наскільки довгим виявиться рядок, що вводиться. Тому в getline включений контроль переповнення. У той же час користувач функції copy вже знає /чи може довідатися/, який розмір рядків, тому додаткового контролю не потрібно.
199
Приклад 9 Написати програму, що друкує слова в рядках файла в зворотному порядку
#include <stdio.h> #include <ctype.h> #include <string.h> #include <locale.h>
#define MAXL 255 /* макс. довжина рядка */
/* Якби мы не включили ctype.h, то повинні були б визначити:
* #define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\f') */
main ( argc, argv ) char **argv;
{
setlocale(LC_ALL, ""); if( argc == 1 ){
/* программа викликани без аргументов */ munch( "" );
} else{
/* аргументы программи - імена файлів */ while( argv[ 1 ] ){
munch( argv[1] ); argv++;
argc--;
}
}
total();
exit(0);
}
/* обробити файл з іменем name */ munch( name ) char *name;
{ |
|
|
char |
l[MAXL]; |
/* буфер для чергового рядка */ |
int len; |
|
/* довжиною цього рядка */ |
char *words[50]; |
/* таблиця полів рядка */ |
|
char **s; |
/* службова */ |
|
int nwords; |
/* число слів в рядку */ |
FILE *fp;
if( name == NULL || !*name )
fp = stdin; /* стандартний ввід */
200