МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
Національний університет “Львівська політехніка”
РЕКУРСИВНІ ФУНКЦІЇ, ВКАЗІВНИКИ НА ФУНКЦІЇ В АЛГОРИТМІЧНІЙ МОВІ С
Методичні вказівки
до лабораторної роботи № 8 з курсу
“Проблемно-орієнтовані мови програмування”
для базового напрямку “Комп’ютерні науки”
Затверджено
на засіданні кафедри
систем автоматизованого проектування
Протокол № _1_ від 22.08.2011 р.
ЛЬВІВ – 2011
1. МЕТА РОБОТИ
Мета роботи – поглиблене вивчення можливостей функцій з використанням рекурсії та вказівників.
2. ТЕОРЕТИЧНІ ВІДОМОСТІ
2.1. Рекурсивні функції
Рекурсивним називається такий спосіб реалізації функції., коли функція може звертатися сама до себе. У рекурсивній функції повинні виконуватися наступні правила:
- при кожному виклику такої функції в неї повинні передаватися модифіковані дані;
- на якомусь етапі повинен бути припинений подальший виклик даної функції. Рекурсивний процес повинен крок за кроком так спрощувати завдання, щоб зрештою для нього з'явилося не рекурсивне рішення. Тут легко припуститися помилки, що полягає в тім, що функція буде послідовно викликати саму себе нескінченно довго;
після завершення кожного виклику рекурсивної функції в точку повернення повинен передаватися деякий результат для подальшого використання.
/* Приклад: використання рекурсивної функції. Припустимо, що задано два числа a і b. Необхідно більше із цих чисел розділити на менше. */
#include <iostream.h>
void main (void)
{
float a, b;
float recurs (float, float) ;
cout <<”Введіть значення a і b " << endl;
cin >> a >> b // Ввід значень
cout << " a= " << a << " b = " << b;
cout << " Результат ділення: " << recurs (a, b);
// Прототип функції
float recurs (float a, float b) / * Рекурсивна функція */
if(a<b) return recurs(b,a); /* Якщо а < b, те параметри міняються
/*місцями (рекурсивний виклик) */
else return a/b; /* Вихід з рекурсії */
}
У функцію recurs передаються значення двох дійсних чисел. Припустимо спочатку, що умова а < b не виконується. У цьому випадку при першому ж звертанні до recurs має місце нерекурсивний вихід. Нехай тепер умова а < b виконується. Тоді функція recurs звертається до самої себе, однак параметри а і b у виклику міняються місцями, тобто параметру а у функції передається більше значення (b), а параметру b - менше значення (a). Після чого відбувається вихід з рекурсій і в main буде переданий результат.
Рекурсія нерідко входить у визначення математичних алгоритмів.
// Приклад: обчислення факторіала за допомогою рекурсії:
//
#jinclude <iostream.h>
void main (void)
long fact (int.); /* Прототип функції */
int n;
cout << "Введіть n" << endl;
cin >> n; /* Ввід значення */
cout << " Факторіал " << n << “ = “ << fact (n);
}
long fact (int n) /* Рекурсивна функція */
{
return (n) ? (long) n * fact(n-1):1;
}
Розглянемо процес виконання цієї програми. Функція main викликає функцію fact і передає параметр n Якщо n == 0, то повернеться значення 1. Інакше функція fact(n) повинна викликати fact( n-l). Так буде тривати до виклику fact(0), що поверне значення 1 у точку виклику fact(l).
Покажемо порядок виконання кожного виклику функції fact і кожного оператора return.
Порядок викликів:
Порядок повернень:
fact (n)
return{1)
fact ( n-l)
return(1*0!)
. . .
fact (l)
return (( n-l)*( n-2) !)
fact (0)
return(n*( n-l)!)
Для розуміння механізму виконання рекурсивної функції запишемо її в такий спосіб:
long fact ( int n )
{
if (n) return (long) n * fact ( n-1); // IP*
else return 1;
}
Слово else можна й опустити.
Розглянемо стан стека у випадку обчислення 3!
Головний модуль (main) запише в стек число 3 і адресу повернення (позначимо його IP main) для продовження виконання програми, тобто повернення в точку виклику - у функцію cout << "Факторіал" << n << "=" << fact(n); - для виводу результату.
При кожному наступному виклику функції (long) n * fact ( n-1) обчислення даного виразу залишається незавершеним, у стек послідовно записуються число n (2, 1, 0) і адреса повернення на завершення обчислення зазначеного вираpe. Це буде та ж сама адреса, позначимо її умовно IP* і далі (дивися зображення стану стека) зазначено, яке n було на вході у функцію при збереженні даного IP*.
Нижче зображений стан стека при обчисленні 3! В таблиці 1: bp (base pointer) – позначення одного з регістрів процесора; IP – регістра лічильника команд (instruction pointer).
Ліворуч від стека зазначена група операторів, що генерує черговий виклик функції. При виклику в стек містяться дані, що приводить до його нарощуванню (від старших адрес до молодших, тобто зменшується вміст регістра sp). Праворуч від зображення стека показана послідовність формування результату (виконання оператора return (long) n * fact ( n-1);). При цьому виконуються команди вилучення інформації зі стека, тобто дані в цьому випадку вибираються і стек буде зменшуватися. Вказівник вершини стека (вмістиме регістра sp) тепер буде збільшуватися, число 3 зі стека видалить функція main (наростить sp на довжину типу int).
Не рекурсивна вітка return 1; видобуде зі стека верхню адреса повернення (IP* ----0), і далі буде реалізовуватися процес згортання рекурсії, формування кінцевого результату.
Таблиця 1
Послідовні
cть виклику
Молодші
(вершина
адреси області стека
стека)
Послідовність
повернення
bp
sp- вказівник
вершини стека
IP* 0
1*fact(0)
0
0!=1=fact(0)
bp
2*fact(1
IP* 1
1*0!=1=fact(1)
1
bp
3*fact(2)
IP* 2
2*1!=2=fact(2)
2
bp
IP-main 3
printf(" ", n, fact(3));
3
старші адреси
області стека
3*2!=6=fact(3), повернення у функцію main (),
вивід результату
/* Приклад: рекурсивна процедура обрахування чисел, що належать послідовності Фібоначчі. */
#include <iostream.h>
void main (void)
{
long n; int i;
// Прототип функції
long fib(long);
do{
do{
cout << " n-? “ << endl;
} while (cin >> n, n<0);
cout << " Fib(" << n << " ) = " << fib(n);
cout << " Вихід- 0 ";
} while (cin >> i, i);
}
long fib (long n) // Реалізація функції
{
. if ( n==0 | | n== 1) return 1;
return fib( n-l)+fib( n-2);
}
Функція main також може бути рекурсивною в Turbo C, тобто розширення файлу з вихідною програмою повинне бути '.с', а не '.срр'. Наприклад: L10_041.С.
/* Приклад: не оголошуючи масиву, ввести групу даних, вивести їхню загальну кількість, порядкові номери i значення чисел у зворотному порядку. Ознака кінця вводу чисел - 0. */
//
# include <iostream.h>
void main (void)
{
static int n; int m, l=0; // Можна static n=0;
static int k;
/* Дані із класом зберігання static зберігаються в пам'яті на весь час виконання програми */
cout << "Число- ? " << endl;
cin >> m;
if ( m ) { n++; l++;
cout << " Виклик : n=” << n << " l= " << l << endl;
main ( );
/* Запис main без дужок не забезпечує рекурсивного виклику, але й не дає помилки компіляції*/
k++;
cout<<"Вихід: номер=" << k << " число=" << m << " 1= " << l << " n= " << n;
}
Результат роботи програми:
число-? 4 виклик: n=0,l=1
число-? 3 виклик: n=1, l=1
число-? 2 виклик: n=2,1=1
число-? 1 виклик: n=3,1=1
число-? 0 виклик: n=4,1=1
вихід: номер=1 число=0 l=0 n=4
вихід: номер=2 число=1 l=1 n=4
вихід: номер=3 число=2 1=1 n=4
вихід: номер=4 число=3 1=1 n=4
вихід: номер=5 число=4 1=1 n=4.
Наступний характерний приклад рекурсивної функції - це швидке сортування (сортування Хоара). Для заданого масиву вибирається один елемент, що розбиває інші елементи на дві підмножини - те, що менше, і те, що не менше його. Та ж процедура рекурсивно застосовується й до двох отриманих підмножин. Якщо в підмножині залишається менш двох елементів, то рекурсія завершується.
/*qsort сортує v[left] . , ,v[right] по зростанню*/
void qsort(int v[], int left, int right)
{
int i, last;
void swap (int v[],int i, int j);
if (left>=right) /* Нічого не робиться, якщо */
return; /*в масиві менше двох елементів */
swap (v, left, (left+right) /2) ; /* елемент, що ділить, */
last=left; /* Переноситься в v[0] */
for (i = left+1; i <= right; i++) /* Ділення на частини */
if (v[i] < v[left]>
swap(v, ++last, i) ;
swap (v, left, last) ; /* Заміняємо елемент, що ділить, */
qsort(v,left, last-l) ;
qsort (v, last+1, right) ;
}
void swap (int v[] , int i, int j )
// swap міняє місцями v[i] і v[j]
{
int temp;
temp=v [ i ] ;
v[j]=temp;
}
Рекурсивна програма не забезпечує ні ощадливої витрати пам'яті, ні швидкодії, але в порівнянні з деякими нерекурсивними часто коротше, а також легше для розуміння. Такі програми зручні для обробки структур даних типу дерев.
2.2. Приклади програм з використанням рекурсії
/* Приклад: не оголошуючи масиву, ввести групу даних і вивести їх у зворотному порядку. */
#include <iostream.h>
//Рекурсивна функція
void rec()
{ int i;
cin >> i;
if(i!=0) rec ();
else cout << "Вивід чисел: " << endl;
cout << i;
} •
void main(}
{
cout << "Введіть числа. Ознака закінчення вводу - 0 : " << endl;
rec ();
}
/* Результат виконання програми:
Введіть числа . Ознака закінчення вводу -0 : 3 2 4 1 5 6 7 9 8 0
Вивід чисел: 0 8 9 7 6 5 1 4 2 3 */
/* Приклад: не оголошуючи масиву довгих цілих чисел, ввести групу даних, вивести їх у зворотному порядку разом з їхніми порядковими номерами. */
#include <iostream.h>
void rec (int num) /* Рекурсивна функція */
{ int i;
cin >> i;
if (i!=0) rec(num + l) ;
else cout << "Вивід чисел:" << endl;
cout << ",Число, "<< i << ", порядковий .номер " << num << "\n";
}
void main ()
{cout << “Введіть числа. Ознака закінчення вводу - 0: " << endl;
гес (0) ;
}
/* Результат виконання програми:
Введіть числа. Ознака закінчення вводу - 0: 1 2 3 4 5 6 7 8 9 0
Вивід чисел:
Число 0, порядковий номер 10
Число 9, порядковий номер 9
Число 8, порядковий номер 8
Число 7, порядковий номер 7
Число 6, порядковий номер 6
Число 5, порядковий номер 5
Число 4, порядковий номер 4
Число 3, порядковий номер 3
Число 2, порядковий номер 2
Число 1, порядковий номер 1 */
/* Приклад: використовуючи рекурсивний виклик функції main(), обчислити суму елементів масиву цілого типу. Масив не оголошувати. Останній -елемент масиву - 0. У файлу з вихідним модулем повинне бути розширення .с */
#include<stdio.h>
#include <iostream.h>
int main()
{ int i;
cin >> i ;
if(i) { i+=main (); cout << " s= " << i; }
return i; ' -
}
/* Другий варіант: сума обчислюється у функції, результат виводиться в main () */
int fun()
{
int i;
scanf (“%d”,&i);
if (i) i+ = fun ();
return i;
}
void main ()
{ printf (“%d”, fun());
}
2.3. Вказівники на функції. Масиви вказівників на функції
У мові С сама функція не може бути значенням змінної, але можна визначити вказівник на функцію. З ним уже можна оперувати, як зі змінною: передавати його іншим функціям, поміщати в масиви й т.д. Оголошення виду:
int ( *fl ) ( );
говорить про те, що fl - вказівник на функцію, що повертає ціле значення. Перша пара дужок необхідна, без них
int *f 1 ( ); /* це не вказівник на функцію */
означало б, що fl -функція, що повертає вказівник на ціле значення. Після оголошення вказівник а на функцію в програмі можна використовувати оператори виду:
y = ( *fl ) ( . . .); або y = f1(...);
Вказівник на функцію - такий тип змінної, котрій можна присвоювати адреси точки входу у функцію, тобто адресу першої виконуваної команди. Ця змінна надалі може використовуватися для виклику функції замість її імені. Визначення вказівника на функцію має наступний загальний вид:
тип_результату (* ім'я вказівника на функцію) (список типів параметрів) ;
Наприклад:
double (*fd) ( int, int );
/* fd - покажчик на функцію, що повертає результат типу double і приймає два параметри типу int. */
int ( *find ) ( int, float * ) ;
/ find - вказівник на функцію. Функція повертає результат типу int і приймає два параметри: число типу int і вказівник на число типу float.*/
char* ( * comp ) ( const char *sl, const char *s2 ) ;
/* comp - вказівник на функцію, що повертає результат вказівник на char і приймаючу як параметри два вказівники на char.*/
При визначенні вказівника на функцію дозволяється одночасно із вказівкою типу параметрів задавати і їхні імена.
Нехай є прототипи функцій піднесення числа типу int у третю, п'яту і сьому степінь і які повертають результат типу int (звичайно, все це легко реалізувати й в одній функції):
int f3 ( int );
int f5 ( int ) ;
int f7 ( int );
і є вказівник на функцію наведеного вище виду:
int ( *fst ) ( int );
Тоді оператор fst = f3; присвоїть вказівнику fst адресу входу у функцію f3, fst = f5; - адресу входу у функцію f5 і, відповідно, fst = f7; - адресу входу у функцію f7. Після цього викликати кожну з функцій (f3, f5, f7) можна будь-яким оператором, записаним нижче (наприклад, f3):
f3 ( a ); // звертання до функції, використовуючи її ім'я.
(*fst ) ( а ); // виклик функції через вказівник.
fst( a ); // виклик функції також через вказівник.
Останній варіант також правильний, тому що f3, fst - це адреси входу у функцію. Однак виклик ( *fst ) ( а ) явно показує, що використовується вказівник на функцію, а не викликається функція з ім'ям fst, якщо бачити тільки один оператор fst ( a );.
У ряді задач, що використовують математичні методи, вказівник на функцію необхідно передавати як параметр у функцію, що реалізує відповідний метод. Наприклад, для обчислення значення інтеграла треба знати ім'я функції, у якій обчислюється значення підінтегрального виразу; для пошуку екстремума деякої цільової функції треба знати ім'я цієї функції і т.п.
Для ілюстрації розглянемо простий приклад. Нехай потрібно обчислити наступні вирази:
y = 4 * 4 * 4 + 7 * 7 * 7 і . y = 3*3*3*3*3 + 9*9*9*9*9.
За умовою задачі, сума елементів обчислюється в окремій функції, з якої повинна викликатися і функція піднесення числа до степеня.
#include <stdio.h>
#include <conio.h>
int f 3 ( int x )
// Піднесення числа в третю степінь
{ return x * x * x;
}
int f5( int x )
// Піднесення числа в п'яту степінь
int х2 = х * х;
return х2 * х2 * х;
}•
void main( )
{
int ( *fst ) ( int );
// Вказівник на функцію
int sum (int , int, int ( *fst ) ( int ) );
// Прототип функції sum
fst = f3;
// fst адреса входу в f3
printf(" 4 ^ 3 + 7 ^ 3 = %d \n ", sum( 4, 7, fst ) );
fst = f5;
// fst = адреса входу у f5
printf(" 3 ^ 5 + 9 ^ 5 = %d\n ,", sum( 3, 9, fst ) ) ;
}
int sum( int m, int n, int ( *fp ) ( int ) )
// Реалізація функції sum
{ int yl, y2;
yl = ( *fp ) ( m );
// Виклик або f3, або f5
y2 = ( *fp ) ( n );
// Виклик або f3, або f5
return y+y2;
}
/*Є й інші варіанти реалізації функції sum. */
int sum( int m, int n, int ( *fst ) ( int ) )
{ return fst( m ) + fst( n ) ;
}
Вказівники на функцію широко застосовуються в програмуванні:
- багато бібліотечних функцій як аргумент одержують вказівник на функцію;
- використання вказівників на функцію як аргументів дозволяє розробляти універсальні функції, наприклад функції чисельного рішення рівнянь, чисельного інтегрування й диференціювання;
- масиви вказівників на функції використовуються для організації меню.
Вказівники на функції можуть бути компонентами структур.
Як і звичайні змінні, вказівники на функції можуть об’єднуватися в масиви. Наприклад, визначити й проініціалізувати масив вказівників на функції можна в такий спосіб:
/* Опис прототипів функцій, точки входів яких будуть елементами масиву вказівників на функції. */
float funcl( float );
float func2 ( float ) ;
float func3( float );
float func4( float ) ;
float func5( float ) ;
// Масив вказівників на функції
float ( *fparray [5] ) (float ) = { funcl, func2, func3, func4, func5 } ;
Доступ до елементів масиву fparray виконується, як до звичайних елементів масиву.
Наприклад:
float х = 1;
cout << fparray[0] ( х) ;
// або cout << (*fparray[0] ) ( х) ;
При організації меню можна використовувати вказівники на масиви функцій. Кожному пункту меню ставиться у відповідність його реалізуюча функція, вказівник на яку поміщається в масив. Після того як користувач зробив вибір, за відповідним індексом вибирається функція, що реалізує цей пункт меню.
Одним з прикладів, де можуть використовуватись вказівники на функції, є обчислення коренів трансцендентних рівнянь. Розглянемо цей випадок на прикладі найпростішого методу половинного ділення.
Нехай відомо, що на відрізку [a,b] рівняння має один корінь, тобто в точках a і b значення функції мають різні знаки. Якщо | а - b |<e (де е - точність обчислень), то [а, b] можна вважати коренем, наприклад х = а (необхідна точність досягнута). Інакше за наближене значення кореня візьмемо середину відрізка . Якщо , то корінь знайдений. Інакше вибираємо той з відрізків ([a , ], [, b]), на кінцях якого значення функції мають різні знаки, і застосовуємо до нього ті ж міркування.
// Функція вимагає під’єднання бібліотечного модуля <math.h>
int hdiv(double ( *f ) ( double),double a ,double b, double *x, double e)
{
double y; int n=0; /* n - кількість ітерацій */
while ( fabs ( a-b ) > e )
{
*x - ( a + b ) / 2; n++;
y = f ( *x );
if( y == 0 ) break;
if( f(a) *y < 0) b = *x;
else a = *x;
}
*x = ( a + b ) / 2; return n;
}
Розглянемо наступний приклад з використанням вказівників на функції.
/* Приклад. Визначити масив вказівників на функції. Вводити цифру, яка визначатиме, яку функцію необхідно виконати: 0 – знайти мінімальне число, 1 – знайти максимальне число, 2- обрахувати суму, 3- обрахувати різницю, 4 – добуток, 5 – частку, 9 – завершити роботу. Виконати відповідну функцію, використовуючи вказівник на неї */
#include <stdio.h>
#include <conio.h>
void main (void)
{
int min (int, int); /* - minimum is found */
int max (int, int); /* - maximum is found */
int plus(int, int); /* - sum is found */
int minus (int, int); /* difference is found */
int mul (int, int); /* - product is found */
int div (int, int); /* - quotient is found */
// array of pointers
int (*y[10]) (int, int) = { min, max, plus, minus, mul, div};
// names of operations
char *str[ ] = {“min”, “max”, “plus”, “minus”, “mul”, “div”};
int m, n; char i;
while (l)
{
puts (“Input the operation: \n”);
puts (“0 – to find minimum \n”);
puts (“1 – to find maximum \n”);
puts (“2 – to find sum \n”);
puts (“3 – to find difference \n”);
puts (“4 – to find product \n”);
puts (“5 – to find quotient \n”);
puts (“9 – exit \n”);
scanf(“%d”, &i);
if ( i == 9 ) return;
if ( i < 0 || i > 5 )
{
puts (“Incorrect operation code \n”); continue;
}
puts (“Input the operands (m, n): \n”);
scanf(“%d%d”, &m, &n);
// Call the function by the pointer – element of array
printf (“%s= %d \n”, str[i],(*y[i]) (m, n) );
// or in the following way
printf (“%s= %d \n”, str[i], y[i], (m, n) );
}
}
// description of functions
int min (int m, int n) {return ( m < n ) ? m: n; }
int max (int m, int n) {return ( m > n ) ? m: n; }
int plus (int m, int n) {return m + n ; }
int minus (int m, int n) {return m - n; }
int mul (int m, int n) {return m * n; }
int div (int m, int n)
{
if ( n) return m / n;
return 32767; /* n value is equal to 0 */
}
2.4. Вказівники типу near і far
Як і звичайні вказівники на дані, вказівники на функцію можуть мати тип near, far або huge. Вказівник типу near займає в пам’яті 2 байти, вказівники far і huge – 4 байти. Тип вказівника на функцію, який встановлюється по замовчуванню, залежить від моделі пам’яті. В моделях пам’яті COMPACT, TINY i SMALL по замовчуванню вказівник на функцію має тип near і задає тільки зміщення до точки входу в функцію відносно значення в сегментному регістрі CS. При прямому чи побічному виклику функції через near-вказівник використовується машинна команда “близького” прямого чи побічного виклику процедури, яка пов’язана з установкою нового значення тільки у регістрі IP.
Для моделей пам’яті MEDIUM, LARGE і HUGE по замовчуванню вказівник на функцію займає 4 байти і включає як зміщення, так і адресу сегмента точки входу у функцію. При прямому чи побічному виклику функції через far-вказівник на функцію використовується машинна команда далекого прямого чи побічного виклику процедури, яка пов’язана як з установкою нового значення в регістрі IP, так і зі зміною значення сегментного регістра CS.
В моделі пам’яті HUGE при вході у функцію додатково встановлюється значення регістра DS, яке відповідає сегменту даних функції. Пряма адресація при виклику процедури відповідає виклику функції через вказівник-константу. Побічний виклик використовується при виклику функції по вказівнику-змінній.
Прийнятий по замовчування формат вказівника на функцію може бути відмінений явним заданням типу функції з використанням ключових слів near, far або huge. Наприклад:
int far function (int, int); / * Прототип функції */
. . .
int far function (int a, int b) /* Визначення функції */
{ Тіло функції}
Синтаксис мови С вимагає співпадіння модифікаторів типу функції як в прототипі, так і у визначенні функції. Компілятор завжди трактує першу частину визначення як тип значення, яке повертає функція, а наступне слово, як модифікатор. Тому, наприклад,
char far *far str_func (void);
є прототипом far-функції, яка повертає значення вказівника типу char far *. Сама функція є far-функцією. Опис, наведений нижче, визначає вказівник func_ptr на far-функцію (можна сказати, і far-вказівник на функцію), яка приймає два аргументи типу int і повертає значення типу char far *:
char far *far (*func_ptr) (int, int);
При порівнянні far-вказівників операціями <, >, <=, >= використовуються тільки зміщення (вказівники порівнюються як числа типу int), а операціями != і == far-вказівники порівнюються як числа типу long. Нехай, наприклад, оголошені наступні вказівники:
void far* vp1=0xb8000000;
void far* vp2=0xb4004000;
void far* vp3=0xb0008000;
Дані вказівника вказують на одну і ту ж фізичну адресу, але операція порівняння == дає результат FALSE, != дає значення TRUE.
При нарощенні вказівника типу far використовується тільки його зміщення. Якщо при цьому результат не вміщається у зміщення, то відбувається відрізання старших розрядів результату. Наприклад, після додавання до vp3 числа 8000h зміщення вказівника стане рівним b000:0000h. Таким чином, за межі сегмента вийти неможливо.
3. КОНТРОЛЬНІ ЗАПИТАННЯ
У яких випадках використовуються рекурсивні функції?
Опишіть дію рекурсивної функції на прикладі обчислення Як змінюється вміст стеку при цьому?
Чи може бути рекурсивною функція main ?
На що вказує вказівник функції?
Яким чином відбувається об’єднання вказівників у масиви?
Наведіть приклад програми обчислення кореня функції з використанням вказівника.
Які особливості використання вказівників типу near і far ?
Якого типу є far-вказівники при виконанні операцій <, >, <=, >= ?
Якого типу є far-вказівники при виконанні операцій != і == ?
4. ЛАБОРАТОРНЕ ЗАВДАННЯ
Вивчити елементи алгоритмічної мови С, пов’язані з рекурсивними функціями і вказівниками на функції.
Одержати індивідуальне завдання.
Скласти програму на алгоритмічній мові С.
Відлагодити програму, виконати обчислення.
5. ЗМІСТ ЗВІТУ
Мета роботи.
Короткий опис конструкцій алгоритмічної мови С, що вивчаються.
Індивідуальне завдання.
Текст програми у відповідності з індивідуальним завданням.
5. Результати обчислень.
6. Аналіз результатів, висновки.
6. СПИСОК ЛІТЕРАТУРИ
Керниган Б., Ритчи Д. Язык программирования С. - М. - Финансы и статистика. - 1992. – 272 с.
Уэйт М., Прата С., Мартин Д. Язык С. Руководство для начинающих. - М. - Мир. - 1988. –512 с.
Глинський Я. М., Анохін В. Є., Ряжська В. А. C++ i C++ Builder. – Львів: Деол. – 2003. – 192 с.
Герберт Шилдт. Полный справочник по C++. М. – С.-П.-К., Вильямс. – 2003. – 800 с.
Демидович Е. М. Основы алгоритмизации и программирования. Язык Си. (Учебное пособие). – Санкт-Петербург: “БХВ Петербург”. – 2006. – 439 с.
ЗАВДАННЯ ДО ЛАБОРАТОРНОЇ РОБОТИ
1. Не оголошуючи масиву, ввести групу даних і вивести їх у зворотному порядку.
2. Не оголошуючи масиву довгих цілих чисел, ввести групу даних. Вивести їх у зворотному порядку разом з їх порядковими номерами.
3. Визначити масив вказівників на функцію. Ввести цифру,