МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”
ЗВІТ ДО ЛАБОРАТОРНОЇ РОБОТИ № 3
РОБОТА З МАСИВАМИ. ВИКОРИСТАННЯ ФУНКЦІЙ
Мета роботи – вивчити синтаксис опису і використання у програмах змінних типу масивів, навчитися використовувати масиви для оброблення матриць, вивчити основні алгоритми сортування масивів; навчитися описувати та застосувати у програмах функції для виконання логічно закінчених алгоритмів.
ОСНОВНІ ТЕОРЕТИЧНІ ВІДОМОСТІ
1.1. Функції.
Оголошення функції (прототип)
Синтаксис:
[<специфікація класу пам’яті>] [<специфікація типу>] <оголошувач>
( [<список аргументів>] );
Оголошення функції специфікує ім’я функції, тип значення що повертається і, можливо, типи її аргументів та їх числові значення. Ці атрибути функції необхідні для перевірки компілятором мови Сі коректності звертання до неї до того, як вона визначена.
Якщо <оголошувач> функції представляє собою ідентифікатор (ім’я функції), то оголошується функція, тип значення якої, що повертається, заданий специфікацією типу. Функція не може повертати масив чи функцію, однак може повертати покажчик на ці об’єкти.
Якщо специфікація типу в оголошенні функції пропущена, то передбачається тип int. На зовнішньому рівні може бути також пропущена специфікація класу пам’яті (див. класи пам’яті), а на внутрішньому рівні хоча б одна із специфікацій – класу пам’яті чи типу – повинна бути присутньою.
Деякі приклади оголошень функцій:
add(int, int); /* приклад 1 */
double calc( ); /* приклад 2*/
void draw(void); /* приклад 3 */
void bul(int a[n][n]); /* приклад 4 */
В першому прикладі оголошується функція з іменем add, яка приймає два аргументи типу int і повертає значення типу int.
В другому прикладі оголошується функція з іменем calc, яка повертає значення типу double. Список типів аргументів пустий.
В третьому прикладі оголошується функція draw з типом що повертає значення void (нічого не повертається). Список типів аргументів також містить ключове слово void, що вказує на відсутність аргументів функції.
В четвертому прикладі оголошується функція bul з типом що повертає значення void. Аргумент функції – двомірний масив (n – розмірність масиву) елементів цілого типу.
Оголошення функції
Оголошення функції визначає її ім’я, тип значення що повертається, клас пам’яті і може також задавати тип деяких чи усіх аргументів функції.
Крім явного оголошення, функція може бути оголошена неявно, по контексту її виклику. Неявне оголошення має місце в тому випадку, коли функція викликається без попереднього оголошення чи визначення. В цьому випадку компілятор мови Сі вважає, що функція, яка викликається, має тип значення що повертається int і клас пам’яті extern. Визначення функції, якщо воно є далі в тому ж самому файлі, може перевизначити тип значення що повертається і клас пам’яті.
Тип значення функції що повертається, вказаний у попередньому оголошенні, повинен відповідати типу значення що повертається у визначенні функції.
Наприклад:
main( )
{
int a = 0, b = 1;
float x = 2.0, y = 3.0;
double realadd(double, double);
a = intadd(a, b);
x = realadd(x, y);
}
intadd(a, b)
int a, b;
{
return (a + b); /* повертає значення типу int */
}
double realadd(x, y)
double x, y;
{
return (x + y); /* повертає значення типу double */
}
В прикладі функція intadd оголошена неявно з типом значення що повертається int, оскільки вона викликана до свого визначення. Компілятор не перевірятиме типи аргументів при виклику даної функції, тому що список типів аргументів для неї не заданий.
Функція realadd повертає значення типу double. В функції main є попереднє оголошення функції realadd. Тип значення що повертається (double), заданий у визначенні, відповідає типу значенню що повертається, заданому в попередньому оголошені. В попередньому оголошені також визначені типи двох параметрів функції realadd. Типи фактичних аргументів відповідають типам, що задані в попередньому оголошені, і також відповідають типам формальних параметрів у визначенні функції realadd.
1.1.3. Виклик функції.
Виклик функції передає управління і фактичні аргументи (якщо вони є) заданій функції.
Синтаксично виклик функції має вигляд:
<вираз> ([<список виразів>]);
<вираз> обчислюється, і його результат інтерпретується як адрес функції. Вираз повинен мати тип функція.
<список виразів>, в якому вирази ідуть через кому, представляє собою перелік фактичних аргументів, що передаються функції. Список виразів може бути пустим.
При виконанні виклику функції відбувається присвоєння значень фактичних аргументів формальним параметрам. Перед тим кожен фактичний аргумент обчислюється, над ним виконуються необхідні перетворення, і він копіюється в стек. Перший фактичний аргумент відповідає першому формальному параметру, другий – другому і т.д. Всі аргументи передаються по значенню, тільки масиви – по посиланню (див. масиви).
Приклад:
main( )
{
intadd(2, 3); /*Виклик функції intadd з передачею фактичних
аргументів 2, 3 заданій функції */
}
intadd(a, b) /*Визначення функції intadd з формальними аргументами
а, b */
int a, b;
{
. . . .
}
В даному прикладі показано присвоєння фактичних аргументів 2, 3 формальним аргументам a, b функції intadd. Виклик функції в такому випадку робить наступні дії:
a = 2;
b = 3;
Таким чином формальний аргумент – змінна у програмі що викликається, а фактичний аргумент – конкретне значення, присвоєне цій змінній програмою що здійснює виклик. Фактичний аргумент може бути константою, змінною чи більш складним виразом.
Викликана функція працює з копією фактичних аргументів, тому ніяка зміна значень формальних параметрів не відіб’ється на значеннях аргументів, з яких була зроблена копія.
Визначення функції.
Функція – самостійна одиниця програми, спроектована для реалізації конкретної задачі. Виконання програми завжди починається з команд, що містяться в функції main( ), яка в свою чергу може викликати інші функції (getchar( ), printf( ), scanf( ) та ін.).
Визначення функції специфікує ім’я функції, атрибути її формальних параметрів, і тіло функції, що містить оголошення і оператори. У визначені функції також може задаватися клас пам’яті функції і тип значення що повертається.
Синтаксис визначення функції:
[<специфікація класу пам’яті>] [<специфікація типу>] <ім’я функції>
( [<список аргументів>] )
[<оголошення аргументів>]
<тіло функції>
У визначенні функції допускається вказівка специфікації класу пам’яті static чи extern (див. клас пам’яті).
Наявність списку аргументів і оголошень не є обов’язковою. Змінні, відмінні від аргументів, оголошуються в середині тіла функції.
Наприклад:
diff(x, y) /* функція diff з аргументами x, y */
int x, y; /* оголошення аргументів x, y */
{
int a, b; /* оголошення змінних а, b */
. . . .
}
Функції повинні мати той же тип, що і значення, які вони повертають в якості результатів. По замовчуванню передбачається що функції мають тип int. Якщо функція має інший тип, він повинен бути вказаний і в програмі що викликається, і в самому визначенні функції.
Наприклад:
main( )
{
float q, x, duff( ); /* оголошення в програмі що викликається */
int n;
. . . .
q = duff(x, n);
. . . .
}
float duff(u, k) /* оголошення у визначенні функції */
float u;
int k;
{
float tor;
. . . .
return(tor); /* повертає значення типу float */
}
1.1.5. Передача значень функції
Аргументи використовуються для передачі значень із програми що викликається в функцію. Якщо значення змінних a і b будуть 5 і 2, то при виклику
с = diff(a, b);
здійснюється передача цих значень змінним x і y. Значення 5 і 2 називаються фактичними аргументами, а змінні x і y, вказані в оголошені функції diff( ), – формальними аргументами.
Наприклад
main( )
{
int c;
c = diff(5, 2);
}
diff(x, y)
int x, y;
{
int z;
z = x – y;
return(z);
}
Використання ключового слова return дозволяє передавати у викликаючу програму одне значення із функції що викликається. В нашому прикладі змінній с присвоюється значення змінної z що рівне 3.
1.1.6. Оголошення покажчика.
Покажчик – це змінна, котра призначена для збереження адресу об’єкта деякого типу.
Синтаксис:
[<специфікація типу>] * <описувач>;
Специфікація типу задає тип змінної, на яку посилається покажчик, а символ (*) визначає саму змінну як покажчик.
Оголошення покажчика специфікує ім’я змінної-покажчика і тип об’єкта, на який може вказувати ця змінна. Специфікація типу може задавати базовий, пустий, структурний тип чи тип об’єднання. Якщо специфікація типу пропущена, передбачається тип int.
В мові Сі використовуються змінні типу покажчик. Значенням такої змінної слугує адреса деякої величини. Якщо ми дамо покажчику ім’я ptr, то зможемо написати, наприклад, такий оператор:
ptr = &pooh; /* присвоєння адресу pooh змінній ptr */
де &pooh – константа типу покажчик ( адрес деякої змінної pooh);
ptr – змінна типу покажчик.
В такому випадку ptr “вказує на” pooh. Різниця між двома формами запису: ptr і &pooh, полягає в тому, що ptr – змінна, в той час як &pooh – константа.
Таким чином, якщо, в змінній ptr міститься посилання на змінну pooh, тоді для доступу до значення цієї змінної можна скористатися операцією “непрямої адресації” :
val = *ptr; /* визначення значення, на яке вказує ptr */
Останні два оператори, взяті разом, еквівалентні наступному:
val = pooh;
Приклад:
pooh = 22;
ptr = &pooh; /* покажчик на pooh */
val = *ptr;
Результат виконання даного фрагмента – присвоєння значення 22 змінній val.
Переважно виконання функції не робить ніякого впливу на значення змінних програми яка здійснює виклик. Щоб мати можливість безпосередньо змінювати значення змінних програми яка здійснює виклик, необхідно використовувати покажчики в якості аргументів. Це може виявитися необхідним у випадку, якщо у програму що здійснює виклик необхідно передати більше ніж одне значення.
Приклад:
/* Програма яка здійснює обмін значеннями між двома змінними x i y*/
main( )
{
int x = 5, y = 10;
printf(“Спочатку x = %d і y = %d. \n”, x, y);
interchange(&x,&y); /* передача адрес функції */
printf(“Тепер x = %d і y = %d. \n”, x, y);
}
interchange(u, v)
int *u, *v; /* u i v є покажчиками */
{
int temp;
temp = *u; /* temp присвоюється значення, на яке вказує u */
*u = *v;
*v = temp;
}
Результат виконання програми:
Спочатку x = 5 і y = 10.
Тепер x = 10 і y = 5.
Таким чином шляхом передачі функції адрес змінних x і y ми представили їй можливість доступу до них. Використовуючи покажчики і операцію *, функція отримує доступ до величин, що поміщені у відповідні комірки пам’яті і міняє їх місцями.
1.2. Класи пам’яті
Класи пам’яті мови Сі дають можливість визначити, з якими функціями пов’язані які змінні і як довго змінна зберігається у програмі. Клас пам’яті дозволяє встановити два факти. По-перше, визначити, які функції мають доступ до змінної. (Межі, до яких змінна доступна, характеризують її “область дії”). По-друге, визначити, як довго змінна знаходиться в пам’яті.
Існує чотири ключових слова що використовуються для опису класів пам’яті: еxstern (для зовнішнього), auto (для автоматичного), static і register (для статичного і регістрового).
Змінні, що визначені поза функцією, є зовнішніми і мають глобальну область дії.
Наприклад:
/* глобальна змінна units */
int units; /* зовнішня змінна */
main( )
{
extern int units;
funct( );
}
funct( )
{
extern int units;
. . . .
}
Ключове слово extern пропонує компілятору шукати визначення цієї змінної поза функцією. Зовнішні змінні, які визначенні раніше функції, доступні їй навіть якщо не оголошені усередині неї. В даному випадку змінна units буде доступна як функції main( ), так і функції funct( ).
По замовчуванню змінні, що оголошені усередині функції, є автоматичними і локальними (область дії такої змінної обмежена блоком {} , в якому ця змінна оголошена.) Можна, однак це підкреслити явно з допомогою необов’язкового ключового слова auto.
Приклад:
main( )
{
auto int units;
. . . .
}
Автоматична змінна починає існувати при виклику функції, що містить її. Коли функція завершує свою роботу і повертає управління туди, звідки її викликали, автоматична змінна зникає.
Статичні змінні бувають як зовнішніми так і внутрішніми.
Внутрішні статичні змінні мають таку ж область дії, як і автоматичні змінні, однак вони не зникають, коли функція що їх містить, завершує свою роботу. Компілятор зберігає їх значення від одного виклику функції до іншого.
Різниця між зовнішньою змінною і зовнішньою статичною змінною полягає в області їх дії. Звичайна зовнішня змінна може використовуватися функціями в будь-якому файлі, в той час як зовнішня статична змінна може використовуватись тільки функціями того ж самого файлу, причому після визначення змінної.
Регістрові змінні, зберігаються в регістрах центрального процесора, де доступ до них і робота з ними виконується набагато швидше, ніж над звичайними змінними що зберігаються у пам’яті. Однак, якщо кількість доступних регістрів “зайнята”, то змінна стає простою автоматичною змінною.
КЛАС ПАМ’ЯТІ
КЛЮЧОВЕ СЛОВО
ТРИВАЛІСТЬ ІСНУВАННЯ
ОБЛАСТЬ ДІЇ
Автоматичний
Auto
Тимчасово
Локальна
Регістровий
Register
Тимчасово
Локальна
Статичний
Static
Постійно
Локальна
Зовнішній
Extern
Постійно
Глобальна
(всі файли)
Зовнішній статичний
Static
Постійно
Глобальна
(один файл)
Класи пам’яті, які перелічені вище пунктирної лінії, оголошуються на внутрішньому рівні.
Класи пам’яті, які перелічені нижче пунктирної лінії, оголошуються на зовнішньому рівні.
1.3. Оголошення масивів
Масив – це набір елементів одного типу, які мають одне і тeж базове ім’я і відрізняються один від одного числовою ознакою.
Синтаксис:
[<специфікація типу>] <оголошувач> [<константний вираз>];
[<специфікація типу>] <оголошувач> [ ];
Масив дозволяє зберігати як єдине ціле послідовність змінних однакового типу. Оголошення масиву визначає тип елементів масиву і його ім’я, а також може визначати число елементів в масиві. Змінна типу масив бере участь у виразах як константа-покажчик на значення що задане специфікацією типу. Якщо специфікація типу опущена, то передбачається тип int.
Оголошення масиву може мати одну з двох синтаксичних форм, вказаних вище. Квадратні дужки, які знаходяться за <оголошувачем>, є ознакою типу масив.
Константний вираз, що знаходиться в квадратних дужках, визначає число елементів в масиві. Індексація елементів масиву починається з нуля. Таким чином, останній елемент масиву має індекс на одиницю менший, ніж число елементів в масиві.
В другій синтаксичній формі константний вираз в квадратних дужках пропущений. Ця форма може бути використана, якщо в оголошені масиву є присутнім ініціалізатор, або масив оголошується як формальний параметр функції, або дане оголошення є посиланням на оголошення масиву десь в іншому місці програми. Однак для багатомірного масиву може бути пропущена тільки перша розмірність.
Для створення масиву компілятору необхідно знати тип даних і належний клас пам’яті, а також повинна бути відома кількість елементів масиву. Масиви можуть мати ті ж типи даних і класи пам’яті що і прості змінні.
Зовнішні і статичні масиви можна ініціалізувати. Автоматичні і регістрові масиви ініціалізувати не можна.
Приклади декількох оголошень масивів:
int temp[365]; /* зовнішній масив з 365 цілих чисел */
int days[ ] = {30, 31}; /* ініціалізація зовнішнього масиву */
main( )
{
float rain[365]; /* автоматичний масив з 365 чисел типу float */
static char code[12]; /* статичний масив з 12 символів */
extern temp[ ]; /* зовнішній масив; розмір вказаний вище */
}
Багатомірний масив, чи масив масивів, оголошується шляхом задання послідовності константних виразів у квадратних дужках, що знаходяться за оголошувачем:
[<специфікація типу>] <оголошувач> [<константний вираз>] [<константний вираз>]…;
Кожен константний вираз у квадратних дужках визначає число елементів в даному вимірі масиву, тому оголошення двомірного масиву містить два константних вирази, тримірного – три і т.д.
Масиву виділяється пам’ять, яка є необхідною для розміщення всіх його елементів. Елементи масиву з першого по останній розташовуються у послідовних комірках пам’яті, по зростанню адрес. Елементи багатомірного масиву запам’ятовуються по рядках. Наприклад, масив, який представляє собою матрицю розміром два рядки на три стовпці char[2][3] буде зберігатися наступним чином: спочатку в пам’яті запам’ятовуються три елементи першого рядка, потім три елементи другого рядка.
1.4. Покажчики масивів
Позначення масиву представляє собою неявну форму використання покажчиків. Наприклад, ім’я масиву визначає також його перший елемент, тобто якщо dates[ ] – масив, тоді
dates = = & dates[0]
і обидві частини рівності визначають адресу першого елемента масиву. Обидва позначення є константами типу покажчик, оскільки вони не змінюються на протязі всієї програми. Однак їх можна присвоювати (як значення) змінній типу покажчик і змінювати значення цієї змінної.
Приклад:
#include<stdio.h>
void main(void)
{
int dates[4];
int i, *pt;
for(i = 0; i < 4; i++)
scanf(" %d ", &dates[i]);
pt = dates; /* присвоєння адресу покажчику масиву */
for(i = 0; i < 4; i++)
printf(" %d %р\n", pt[i], &pt[i]);
}
Якщо ми присвоюємо 4-м елементам масиву наступні значення: 23, 65, 89, 7, то результат виконання програми буде наступним:
23 325D:OF84
65 325D:OF86
89 325D:OF88
7 325D:OF8A
У другому стовпці виведені адреси комірок в яких знаходяться значення елементів масиву.
Таким чином, якщо dates визначає адресу першого елемента масиву то *(dates) – його значення.
Наприклад:
dates + 2 = = & dates[2] /* адрес 3-го елемента масиву */
*(dates + 2) = = dates[2] /* значення 3-го елемента масиву */
Дані співвідношення показують можливість використання покажчика для визначення окремого елемента масиву, а також знаходження його значення.
Масиви можна використовувати в програмі в якості аргументів функції.
Коли ім’я масиву використовується в якості аргументу, функції передається покажчик. Далі функція використовує цей покажчик для виконання змін у вихідному масиві.
Скелет даної програми продемонстрований на прикладі.
Приклад:
main( )
{
int ages[50]; /* масив з 50 елементів */
convert(ages);
. . . .
}
convert(years)
int years[ ]; /* покажчик на масив*/
{
. . . .
}
ages – аргумент функції convert, що є покажчиком на перший елемент масиву. Таким чином, оператор виклику функції передає їй покажчик, тобто адресу функції convert( ). Це означає що аргумент функції є покажчиком, тому функцію convert( ) можна записати наступним чином:
convert( int years[ ] )
{
. . . .
}
Оператор int years[ ]; оголошує змінну years покажчиком масиву цілих чисел. Оператори, що використовують покажчик years у функції convert( ), фактично працюють з масивом ages, що знаходиться в тілі функції main( )
У випадку використання двомірного масиву порядок його елементів визначається тим, що найправіший індекс масиву змінюється першим. Тому, наприклад, якщо ми маємо оголошення:
int dear[3][2]; /* масив типу int із 3-ох стрічок і 2-ох стовпців */
int *pr; /* покажчик на цілий тип */
тоді pr = dear вказує адрес елемента першого стовпця першого рядка:
dear = = &dear[0][0]
Тоді:
pr + 1= = &dear[0][1] /* 1-й рядок, 2-й стовпець */
pr + 5 = = &dear[2][1] /* 3-й рядок, 2-й стовпець */
Визначення і опис функції що керує двомірним масивом записується наступним чином:
main( )
{
static int dear[3][4];
convert(dear);
. . . .
}
convert(dear)
int dear[ ][4];
{
. . . .
}
Оператор int dear[ ][4]; повідомляє компілятор про необхідність розбиття масиву на рядки по чотири стовпці.
Приклад програми яка здійснює сортування елементів масиву методом обміну:
#include<stdio.h>
#include<stdlib.h>
#define n 5
void bul(int a[n][n]);
void main(void)
{
int i,j,k;
int a[n][n];
system("cls");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
printf("a[%d][%d] =", i+1, j+1);
scanf("%d", &a[i][j]);
}
}
printf("old array\n");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf ("%5d", a[i][j]);
printf("\n");
}
bul(a);
printf("\nnew array\n");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
printf ("%5d", a[i][j]);
}
printf("\n");
}
}
void bul(int a[ ][n])
{
int i, k, j, c;
for(i = 0; i < n; i++)
{
for(k = n-1; k>=0; k--)
{
for(j = 0; j < k; j++)
{
if(a[i][j] < a[i][j+1])
{
c = a[i][j];
a[i][j] = a[i][j+1];
a[i][j+1] =c;
}
}
}
}
}
Приклад фрагменту програми що здійснює сортування елементів одновимірного масиву методом вибору:
for(i = 0; i < n-1; i++)
{
k=i;
x=b[i];
for(j=i+1; j < n; j++)
{
if(b[j] > x)
{
k=j;
x=b[j];
b[k]=b[i];
b[i]=x;
}
}
}
Приклад програми що здійснює сортування елементів одновимірного масиву методом вставки:
for(i = 1; i < n; i++)
{
x=b[i];
j=i-1;
while(b[j] < x && j>=0)
{
b[j+1] = b[j])
j--;
}
b[j+1] = x;
}
ЗАВДАННЯ
18
Впорядкувати елементи рядків матриці за зростанням їх значень методом вибору
fi(aij)-середнє арифметичне значення елементів у кожному стовпці над допоміжною діагоналлю; F(fi(aij))- добуток fi(aij)
Текст программи:
#include<stdio.h>
#include<conio.h>
int a[100][100];
int n,m;
FILE*fin;
int sort(int *a, int n)
{ char i,j;
char buf;
for (i=0;i<n;i++)
for (j=i+1;j<n;j++)
{if (a[i]>a[j])
{buf=a[i];
a[i]=a[j];
a[j]=buf;
}
}
return 0;
}
int Function()
{char i,j;
float sum;
char num;
/* for (i=0;i<n;i++)
{
for (j=0;j<m-i-1;j++)
printf("%d ",a[i][j]);
printf("\n");
}
*/
for (i=0;i<n;i++)
{sum=0;
for (j=0;j<m-i-1;j++)
{printf("%d ",a[i][j]);
sum+=a[i][j];
}
while (j<m)
{printf(" ");
printf(" ");
j++;
}
num=m-i-1;
printf("%f",sum/num);
printf("\n");
}
return 0;
}
int printA()
{
char i,j;
for (i=0;i<n;i++)
{
for (j=0;j<m;j++)
printf("%d ",a[i][j]);
printf("\n");
}
return 0;
}
int main ()
{
char i,j;
fin=fopen("TEST.txt","r");
fscanf(fin,"%d",&n);
fscanf(fin,"%d",&m);
for (i=0;i<n;i++)
for (j=0;j<m;j++)
fscanf(fin,"%d",&a[i][j]);
/* clrscr();
*/
printf("A[%d][%d] before sort:\n",n,m);
printA();
printf("\n");
for (i=0;i<n;i++)
sort(a[i],m);
printf("A[%d][%d] after sort:\n",n,m);
printA();
printf("\n");
printf("A[%d][%d] with awage number:\n",n,m);
Function();
getchar();
return 0;
}
Результати виконання
Висновок:
Я вивчив синтаксис опису і використання у програмах змінних типу масивів, навчився використовувати масиви для оброблення матриць, вивчив основні алгоритми сортування масивів; навчився описувати та застосувати у програмах функції для виконання логічно заікінчених алгоритмів.