osn_progr_final
.pdfset(key, s); continue;
}
if((val = get( key )) == NULL) /* КЛЮЧ :знайти зна-
чення */
printf( "немає такого ключа\n");
else{ printf( "значення "); printf(VALFMT, val- >val);
putchar('\n');
}
}
/* роздруковка таблиці за допомогою ітератора */ for( cl = resetiter(&ci) ; cl ; cl = nextpair(&ci))
printcell(cl), putchar('\n');
}
Приклад 2 Складені описувачі Напишемо програму, яка:
а) підраховує кількість всіх допустимих в мові С складених описувачів заданої довжини б) генерує довільний складений описувач заданої довжини
в) друкує всі допустимі складені описувачі заданої довжини При цьому у допустимих описувачах в цій задачі будемо враховувати
лише тривіальні ознаки масивів та функцій-дужки [ ], ( ). Розглянути поняття довжини як кількість кроків інтерпретації.
Якщо позначити через V-ознаку вказівника, М-масива і F-функції, то з точки зору правил розшифровки довільна послідовність виду FFVVMFVV задає схему утворення описувача (інтерпретуємо її зліва направо). Причому описувач буде допустимим, якщо в цій послідовності немає комбінацій виду FM (масив функцій), FF (функція повертає функцію), MF. Схему утворення допустимого складеного опису-
вача можемо зобразити так: |
|
|
V- вказівник |
M-масив |
F-функція |
V-V |
M-V |
F-V |
V-M |
M-M |
|
V-F |
|
|
Бачимо, що якщо останньою була ознака вказівника, то вона може “утворити” в подальшому усі три ознаки, масиву - дві і, нарешті, ознака функції може утворити лише одну ознаку вказівника.
Тоді для підрахунку кількості всіх можливих описувачів можемо використати лише три змінних v, m, f типу long, які б зберігали в
121
собі на кожному кроці кількість описувачів, у яких останньою при інтерпретації була б відповідно ознака вказівника, масиву та функції. При цьому неважко помітити, що ці кількості за 1 крок зростають за правилом:
v1=v+m+f;
m1=m+v;
f1=v;
Оформимо відповідний підрахунок у вигляді функції:
long kilkist(int n)
{
long v=1, m=1, f=1, v1,m1,f1; int i;
for(i=1;i<n;i++)
{
v1=v+m+f;
m1=m+v;
f1=v;
v=v1;
m=m1;
f=f1;
}
return (v+m+f);
}
Тоді main-функція має вигляд:
#include<stdio.h> void main(void)
{
int n;
printf(“введіть довжину процесу інтерпретації описувача n>=1\n”);
scanf(“%d”,&n);
printf(“шукана кількість: %ld\n”,kilkist(n));
}
Нескладний аналіз показує, що код, записаний в тілі циклу for в функції kilkist() може бути спрощений і записаний без використання допоміжних змінних v1, m1 ,f1.
Тоді функція має вигляд: long kilkist(int n)
122
{
long v=1, m=1, f=1, v1; int i; for(i=1;i<n;i++)
{
v+=m+f; f=v-m-f; m+=f;
}
return (v+m+f);
}
Для розв”язку другої частини задачі спочатку отримаємо всі можливі схеми інтерпретації вказаної довжини та проведемо безпосередньо їх інтерпретацію.
Для цього, очевидно нам необхідно мати :
функцію, що генерує схеми
функцію-інтерпретатор, що будує конкретний описувач по заданій схемі
функцію, що формує список.
Враховуючи згадану вище схему, можемо вибрати різні структури даних. Наприклад, добре підходять в даному прикладі тернарні дерева. Але ми зупинимось на однозв”язному списку, одночасно демонструючи технологію роботи з списками.
Виберемо наступну структуру:
struct inter
{
struct inter *next; // вказівник на наступний елемент char *s; // поле, де зберігається інтерпретаційна схема char ost;// останній елемент поля s
};
Якщо поле ost відповідного елемента в списку містить ознаку вказівника, то утворюємо два нових екземпляра структури inter і записуємо їх в список зразу після відповідного елемента, модифікуючи і його поле s. Аналогічно, якщо ознака масиву–один елемент, функ- ції–лише модифікуємо біжучий.
#include<stdio.h>
123
#include<alloc.h>
#include<conio.h>
#include<string.h> struct inter
{
struct inter *next; // вказівник на наступний елемент char *s; // поле, де зберігається інтерпретаційна схема char ost;// останній елемент поля s
};
/*
Параметр функції flag набуває два значення: 0 та 1. Якщо він рівний 0, то функція модифікує саму структуру, що передається через вказівник p, дописуючи символ x як останній символ поля s, не створюючи нової при цьому нової структури в пам”яті. Якщо ж flag рівний 1, то утворюється нова структура, у якої поле s є конкатенацією поля s структури, що передається через вказівник p та символа x.
*/
struct inter* vstavka(char x, struct inter *p,int flag)
{
if(p==NULL)/*Якщо p==NULL проводиться ініціалізація полів структури*/
{
p=(struct inter*)malloc(sizeof(struct inter)); p->s=(char*)malloc(sizeof(char)+1); p->s[0]=x;
p->s[1]='\0'; p->ost=x; p->next=NULL; return p;
}
else/*інакше-проводимо аналіз прапорця*/
{
int size=strlen(p->s); if(flag!=0)
{struct inter* p1=(struct inter*)malloc(sizeof(struct inter));
p1->s=(char*)malloc(size+2); strcpy(p1->s,p->s); p1->s[size]=x; p1->s[size+1]='\0';
124
p1->ost=x;
p1->next=NULL; return p1;
}
else
{
char *srob=(char*)malloc(size+2); strcpy(srob,p->s);
srob[size]=x;
srob[size+1]='\0'; strcpy(p->s,srob); p->ost=x;
return p;
}
}
}
/* Функція-інтерпретатор */
void interpret(char *s, char ident)
{
int i,j,sv=0,sv1=0,slen=strlen(s),identlen=1; for(i=0;i<strlen(s);i++)
{
if (s[i]=='v' && s[i+1]!='v') sv++; if (s[i]=='v') sv1++;
}
char *sinterp= (char*)malloc((slen-sv1)*2 +identlen+sv1+sv*2);
/* sinterp - масив, де зберігається проінтерпретований варіант */
int sost=identlen-1;
/*індекс останнього проініціалізованого елемента масиву*/
sinterp[0]=ident;
sinterp[1]='\0'; for(i=0; i<slen; i++)
{
switch(s[i])
{
case 'v':
/*все беремо в дужки і ставимо зліва зірочку*/ if( s[i+1]!='v'&& i<slen-1)
125
{
for(j=sost; j>=0; j--) sinterp[j+2]=sinterp[j]; sinterp[0]='('; sinterp[1]='*'; sinterp[sost+3]=')'; sost+=3;
}
else
{
for(j=sost; j>=0; j--) sinterp[j+1]=sinterp[j]; sinterp[0]='*'; sost+=1;
}
break; case 'm':
sinterp[sost+1]='[';
sinterp[sost+2]=']';
sost+=2;
break; case 'f':
sinterp[sost+1]='(';
sinterp[sost+2]=')';
sost+=2;
break;
default: printf("вхідний масив невірний");
}
}
for( i=0;i<=sost;i++) printf("%c",sinterp[i]); printf("\n");
//char c=getch(); free(sinterp);
}
При розробці функції інтерпретації врахуємо, що при інтерпретації ознаки вказівника отриману конструкцію необхідно взяти в дужки (інакше порядок інтерпретації буде іншим).
126
/* функція, що формує список. Значення полів s– інтерпретаційні послідовності */
struct inter* form(int n)
{
struct inter* spysok,*current; int i; spysok=vstavka('v',NULL,0); current=spysok; current->next=vstavka('m',NULL,0);
current->next->next=vstavka('f',NULL,0);
for(i=1;i<n;i++)
{
current=spysok;
while(current!=NULL)
{
//printf("%s\n",current->s); //char c=getch();
switch(current->ost)
{
struct inter* currob; case 'v':
currob=vstavka('m',current,1); currob->next=vstavka('f',current,1); current=vstavka('v',current,0); currob->next->next=current->next; current->next=currob; current=currob->next->next;
break; case 'm':
currob=vstavka('m',current,1);
current=vstavka('v',current,0); currob->next=current->next; current->next=currob; current=currob->next;
break; case 'f':
127
current=vstavka('v',current,0); current=current->next;
break;
default:
printf("main список ініціалізований неправильно\n");
}
}
}
return spysok;
}
main()
{
struct inter *spysok,*current; int n;
char c;
printf("введіть n\n"); scanf("%d",&n); spysok=form(n); current=spysok; while(current!=NULL)
{
printf("%s\n",current->s); interpret(current->s,'a'); printf("\n");
c=getch(); current=current->next;
}
free(spysok);
}
128
6 ПРЕДСТАВЛЕННЯ ЧИСЕЛ У КОМП'ЮТЕРІ.
Ми вже говорили, що програми в ході свого виконання здійснюють зміни стану інформаційного середовища чи, що те ж саме, обробляють дані. Тому велике значення має питання про представлення інформації всередині комп'ютера. Грамотно використовувати його можливості, не знаючи способу представлення, складно і це може призвести до появи серйозних помилок.
Як приклад цього, розглянемо наступну програму:
#include <iostream.h> int main(void)
{
float h,x; int i,m;
m =512;
for(i = m - 1; i<= m + 1;i++)
{
h =1./i; x =0.;
for(int j= 1;j<= i;j++) x =x + h;
printf(" x=%10.8f\n",x);
}
return 0;
}
Неважко бачити, що програма для трьох сусідніх значень підсумовує i раз величину 1/i. Здавалося б, кінцевий результат її роботи, рівний і*(1/і) повинен дорівнювати одиниці. Перевіримо. Запустивши програму, побачимо на екрані:
5110.99999666
5121.00000000
5130.99999666
Бачимо, що точне значення відповідає лише значенню i==512. Тому необхідно розібратися з представленням чисел «усереди-
ні» комп'ютера.
6.1 СИСТЕМИ ЧИСЛЕННЯ
Числа, якими ми звикли користуватися, називаються десятковими і тому арифметика з ними також називається десятковою. Кожне
129
число можна скласти з набору цифр, що містить 10 символів - цифр . Якщо припустити, що алфавітом є множина {0,1,2,3,4,5,6,7,8,9}, а число – це слово, то зрозуміло, що назва десяткове походить від кількості букв алфавіту.
Візьмемо, наприклад, число 358. Цей запис означає, що в числі три сотні, п'ять десятків і вісім одиниць. Отже, можна записати на-
ступну рівність:
358 = 300 + 50 + 8 = 3 * 102 + 5 * 101 + 8 * 100
Відзначимо, що цифри, які утворять наше число, множаться на послідовні степені числа 10. Як видно, цифри збільшуються на десять у степені на одиницю меншу порядкового номера цифри при нумерації справаналіво.
Таким чином, усяке написане нами число може бути представлене у вигляді спеціального розкладу по степенях числа 10 і для запису використовується набір з десяти цифр.
Позначимо i-у цифру числа через ai. Тоді число можна записати в наступному вигляді: anan-1…...a2a1. Такий запис числа можна пред-
ставити так:
anan-1…...a2a1 = an *10n-1 + an-1 * 10n-2 + …... + a2 * 101 + a1 * 100
де ai - це символ з алфавіту {0,1,2,3,4,5,6,7,8,9}.
Як бачимо, число десять є основою відповідного представлення чисел. Воно називається основою системи числення, а сама система числення називається десятковою.
Під системою числення розуміється спосіб представлення будьякого числа за допомогою деякого алфавіту символів, що називаються цифрами.
Наочність представлення чисел і порівняльна простота виконання арифметичних операцій характерні для позиційних систем числення.
Система числення називається позиційною, якщо одна і та сама цифра має різне значення в залежності від того, яку позицію вона займає в послідовності цифр, що зображують число. Причому, це значення міняється в залежності від позиції однозначно, за деяким законом. Як приклад непозиційної системи числення - римська система представлення чисел.
Кількість p символів алфавіту, що використовуються у позиційній системі числення, називається її основою.
У загальному випадку в системі з основою p будь-яке число X може бути представлене у вигляді полінома з основою p :
130