osn_progr_final
.pdfvoid swap(int a, int b)
{
int temp=a; a=b; b=temp;
}
main()
{
int x=3,y=6; swap(x,y);
printf(“x=%d, y=%d”,x,y);
}
Результат роботи: x=3, y=6.
Тобто ніякого обміну не відбулося. І дійсно, в функції swap утворилися локальні копії фактичних параметрів-змінних x та y, вони помінялись місцями в тілі функції. А в функції main все залишилось без змін. Для досягнення необхідного нам результату необхідно передати в функцію swap адреси відповідних змінних і здійснити обмін з їх використанням:
void swap(int a, int * b)
{
int temp=*a; *a=*b; *b=temp;
}
main()
{
int x=3,y=6; swap(&x,&y); printf(“x=%d, y=%d”,x,y);
}
Масиви передаються в функції через адресу першого елемента, тому запис формального параметра у вигляді char*s, char s[10] та char s[ ] еквівалентні.
Компілятор виконує неявні перетворення типів формальних параметрів. Не може бути формальний параметр типу, меншого ніж int серед цілих типів і меншого ніж double серед плаваючих.
С має специфічний механізм порядку передачі параметрів в функцію . Як вже згадувалось, вона відбувається, починаючи з останнього параметра (з кінця), що відрізняється від інших мов програмування.
161
7.3 ФУНКЦІЇ ІЗ ЗМІННОЮ КІЛЬКІСТЮ ПАРАМЕТРІВ
В мові С допускається сигнатура функції виду: (<тип 1> ident 1,<тип 2> ident 2,...)
Остання конструкція визначає функцію із змінною кількістю параметрів. При виклиці такої функції повинна бути вказана кількість параметрів, не менша від кількості явно заданих у визначенні:
float (int a, int b,...)
{ …
}
...
a=f(1,2,4,4); /*вірно*/ a=f(1,2); /*вірно*/
a=f(2) /*невірно*/
Допускається також сигнатура виду:
<СПКП> <тип> <опусивач> (...). Тоді функція може мати довільну кількість параметрів, в тому числі і їх відсутність.
Ідеологія роботи з функціями, що мають змінну кількість параметрів, наступна. Для організації доступу до першого невизначеного параметра можна описати в тілі функції вказівник на тип параметра і ініціалізувати його конструкцією ... :
float (int a, int b,...)
{ …
int *p=…;
}
Тоді *p буде значенням відповідного параметра. Далі доступ здійснюється шляхом збільшення вказівника на 1: p++ та використання операцій явного приведення типу.
Аналогічно можна було б в прикладі ініціалізувати вказівник p адресою останнього визначеного елемента : p=&b і, збільшуючи його значення, отримати доступ до інших параметрів:
for ( i=0;i<5;i++) printf(“%d\n”,*p++);
Для роботи з функціями, що мають змінну кількість параметрів, використовуються макроси va_start , va_arg та va_ end, визначені в файлі <stdarg.h>. Макрос va_start використовується для ініціалізації відповідного вказівника адресою першого невизначеного параметра, va_arg-для взяття відповідного значення та переходу на наступний параметр, va_ end-для коректного завершення роботи з відповідним вказівником (обнулення).
Визначаються ці макроси, наприклад так (у різних версіях С порізному. Нижче наведений варіант продуктів фірми Microsoft):
162
typedef char * va_list; #define va_start (ap,v) ap=(va_list)&v+sizeof(v) #define va_arg (ap,t) ((t*)(ap+=sizeof(t)))[-1] #define va_end (ap) ap=NULL
Приклад:
#include <stdio.h> #include <stdarg.h>
/* обраховує суму аргументів до 0 */ void sum(char *msg, ...)
{
int total = 0; va_list ap; int arg;
va_start(ap, msg);
while ((arg = va_arg(ap,int)) != 0) { total += arg;
}
printf(msg, total); va_end(ap);
}
int main(void) {
sum("Сума 1+2+3+4 є %d\n", 1,2,3,4,0); return 0;
}
7.4 РЕКУРСІЯ
Функції С можуть використовуватись рекурсивно. Це означає, що функція може звертатись сама до себе. Як приклад, розглянемо класичну програму, що друкує число у вигляді рядка символів:
printd(n) |
/* print n in decimal |
|
* (recursive) */ |
int n;
{
int i;
if (n < 0) { putchar('-'); n = -n;
}
if ((i = n / 10) != 0) printd(i);
putchar(n % 10 + '0');
163
}
Коли функція викликає себе рекурсивно, то при кожному звертанні утворюється новий набір всіх автоматичних змінних. Таким чином, в printd(123) перша функція printd має n = 123. Вона передає 12 другій printd, а коли та повертає управління їй, друкує 3. Точно так само друга printd передає 1 третій (котра ту одиницю друкує), а потім друкує 2.
7.5 ПАРАМЕТРИ ФУНКЦІЇ MAIN.
Функція main може мати наступні параметри: main(int arg c,char* argv[], char * envp[])
argc-кількість параметрів, що передаються в функцію main, має тип int ;
arg v-масив вказівників на тип char: char* argv [ ].
Кожен вказівник містить адресу наступного аргументу. Значення arg c завжди >1, оскільки обов’язковим параметром є ім’я файлу вихідної С-програми
main (int arg c,char* arg v)
{ printf (“ім’я файлу-%s”,argv[0]); return 0;
}
envp-масив вказівників на тип char: char* envp [ ]
Він містить змінні оточення операційної системи. Для роботи із змінними оточення використовуються функції:
char* getenv(char* varname) та void putenv (char* var).
Функція getenv дає можливість отримати значення в списку змінних оточення , putenv добавляє в список змінних оточення рядок.
Приклад:
char* s=getenv (PATH)
Рядок var повинен бути конструкцією виду: varname=string.
Приклад:
char* s = ”PATH=e:\bc\my\”; putenv(s);
Параметри функції main задаються в командному рядку операційної системи при запуску програми.
Як приклад розглянемо функцію, яка друкує всі свої аргументи командного рядка :
164
main(int argc,char* argv, char * envp) { for (int i=0;i<argc;i++);
printf (“%s\n”,argv[i]); return 0;
}
7.6 ЛIТЕРНI ВКАЗIВНИКИ ТА ФУНКЦIЇ.
Вказівник на тип char можна ініціалізувати символьними рядка-
ми:
char *pc=”abc”;
Аналогічно, масив елементів типу char можемо ініціалізувати: char c[ ]={‘a’,’b’,’c’};
Тоді pc[0]==c[0]==’a’, pc[1]==c[1]==’b’, pc[2]==c[2]==’c’.
Однак, перше та друге визначення має відмінність. Вона полягає в тому, що рядкова константа обов’язково містить 0-символ. Тому pc[3]==’\0’. Це потрібно враховувати при роботі з функціями, які працюють з рядками. Такі функції можуть містити таку конструкцію:
while(*s!=’\0’) s++;
Тоді при передачі імені масиву на вхід такої функції може бути не знайдений 0-символ і функція спрацює некоректно.
Приклад:
void strcpy(char *s, char *t) {int i=0
while (s[i]=t[i]!='\0') i++;
}
Відмітимо, що цю функцію можна написати і так: void strcpy(char *s, char *t)
{while (*s++=*i++);}
Сама функція у мові С не є змінною, хоча ім’я функції містить адресу точки входу в функцію. Цю ситуацію можна обійти, якщо використовується вказiвник на функцію:
[<спец. типу>](*iм'я)(<список типiв параметрiв>) Приклад 1
double (*f)(int);
extern double f1(int),f2(int); main()
{
int n;double s; scanf ("%d",&n);
if (n>0) f=f1; else f=f2; for (i=0;i<100;i++)
s+=(*f)(i);
165
}
Приклад 2.
extern int f1(int),f2(int);
extern void work(int, int(*)(int)); {main()
work(1,f1);
work(2,f2);
}
7.7 ЧАС ЖИТТЯ ТА ОБЛАСТЬ ДІЇ. ОГОЛОШЕННЯ ТА ВИЗНАЧЕННЯ
Час життя - це проміжок часу, під час якого об’єкт існує в процесі виконання програми. Час життя буває глобальним і локальним. Глобальний - коли об’єкт існує на протязі всього часу виконання програми. Локальний - коли об’єкт існує на деякому проміжку, що включається в час виконання програми.
Під областю дії об’єкту будемо розуміти частину програми (коду програми), де можливим є використання імені об’єкта. Може бути об’єкт, що має глобальний час життя, але локальну область дії.
Під оголошенням функції будемо розуміти конструкцію, в якій задається лише прототип функції та (необов’язково) специфікація класу пам’яті.
Приклад 1. float s=5; float f(float); s=s+f(1);
У другому рядочку лише оголошується функція f, тіло якої визначається в іншому місці програми (наприклад, нижче по тексту).
Під визначенням змінної будемо розуміти конструкцію, обробка якої викликає виділення пам’яті.
Приклад 2. int i=0;
Під оголошенням змінної будемо розуміти конструкцію яка є лише посиланням на визначення, зроблені в іншому місці програми( як правило, в іншому файлі).
Приклад 3. int i; i=4; //...
{
static int i=5;
}
Розглянемо наступний рядок програми:
166
int i;
Як його інтерпретувати з точки зору оголошення чи визначення |
|
змінної i? Це може бути як оголошення так і визначення. Якщо в про- |
|
грамі більше не зустрічається оголошення чи визначення змінної i, |
|
то це буде визначення. Воно |
супроводжується виділенням пам’яті , |
та ініціалізацію нулем, якщо |
цей рядок коду знаходиться на зовніш- |
ньому |
рівні. Якщо в іншому місці програми знаходиться визна- |
чення змінноі i, то вказана конструкція є оголошенням |
|
файл1 |
файл2 |
int i ; |
int i=5; |
…
Змінна вважається оголошеною на зовнішньому рівні, якщо вона знаходиться в тексті програми за межами функції. Якщо змінна описана всередині функції, то вважається, що вона визначена на внутрішньому рівні. Функція може бути визначена лише на зовнішньому рівні, але оголошена і на внутрішньому:
main( )
{ …
{
float f( int,int ); i++;
…};
};
Змінна, визначена на зовнішньому рівні, має глобальний час життя і область дії від точки визначення до кінця вихідного файлу. Вище від точки визначення в файлі змінна недоступна:
…..
void f(void)
{
i++;
}
int i=5; main( )
{
i++;
}
В прикладі в функції f змінна і недоступна. Щоб вона була доступною, необхідно розширити область дії змінної шляхом її оголошення перед функцією f: int i;
Змінна, оголошена на внутрішньому рівні, має локальний час життя і область діі від точки визначення до кінця блоку. За межами блоку де вона не визначена - змінна не доступна.
167
Оголошення функції можливе і на внутрішньому рівні. Тоді область дії функції буде від точки визначення до кінця файлу.
7.8 КЛАСИ ПАМ’ЯТІ.
Існує чотири специфікації класу пам’яті: register, auto, static, extern.
Специфікатори класу пам’яті можуть впливати на час життя та область дії програмних об’єктів.
Специфікатор класу пам’яті register дозволяє розміщувати відповідну змінну (типу int) в регістрах мікропроцесора. Якщо місця там немає, то відповідна специфікація ігнорується і змінна вважається автоматичною, тобто має клас пам’яті auto.
Клас пам’яті auto може бути опущеним. Будь-яка змінна вважається автоматичною по-замовчуванню, локальна змінна записується в стек.
Специфікатор класу пам’яті static, що застосовується до зовнішнього об’єкта - змінної чи функції, обмежує область дії лише вихідним файлом. Відповідний специфікатор може застосовуватись і до внутрішніх об’єктів. Це дає можливість вплинути на час життя відповідної змінної - він стає глобальним.
Приклад 1.
{
static int i;
…
}
Статистичні змінні записуються в сегмент даних.
Специфікатор класу пам’яті extern вказує компілятору на те, що даний об’єкт є лише посиланням на визначення в іншому місці про-
грами. |
|
|
файл1 |
файл2 |
|
extern int i; |
int i=5; |
|
Зауважимо, що при визначенні змінної допускається її ініціалізація: |
||
TYPE x = вираз; |
|
|
є (майже) еквівалентом для |
|
|
TYPE x; |
/* опис */ |
|
x = вираз; /* обрахування початкового значення */ |
||
Розглянемо приклад: |
|
|
#include <stdio.h> |
|
|
extern double sqrt(); |
/* квадратний корінь */ |
|
double x |
= 1.17; |
|
double s12 = sqrt(12.0); |
/* #1 */ |
168
double y |
= x * 2.0; |
/* #2 */ |
FILE *fp |
= fopen("out.out", "w"); /* #3 */ |
|
main(){ |
|
|
double ss = sqrt(25.0) + x; |
/* #4 */ |
|
} |
|
|
Рядки з мітками #1, #2 и #3 помилкові. Адже при ініціалізації статичних даних (а s12, y і fp такими і є, оскільки описані поза тілом деякої функції) вираз повинен складатися тільки з констант, оскільки він обраховується компілятором. Тому ні використання значень змінних, ні виклики функцій в даному випадку не є допустимими (але можна брати адресу змінних).
В рядку #4 ми ініціалізуєм автоматичну змінну ss. Тому вираз для ініціалізації обраховується вже не компілятором, а під час виконання програми, що дає нам право використовувати змінні, виклики функцій і.т.п., тобто вирази мови С без обмежень.
Інформація про залежність часу життя та області дії від класу пам’яті та рівня оголошення чи визначення надається в наступній табличці:
Рівень |
Об’єкт |
СПКП |
Час життя |
Область дії |
|
|
|
|
|
|
Визначення |
Static |
Глобальний |
Від точки визна- |
|
змінної |
|
|
чення до кінця |
|
|
|
|
файла |
|
Оголошення |
Extern |
Глобальний |
Від точки визна- |
Зовнішній |
змінної |
|
|
чення до кінця |
|
|
|
|
файла |
|
|
|
|
|
|
Оголошення |
Extern чи |
Глобальний |
Від точки визна- |
|
чи визначення |
Static |
|
чення до кінця |
|
функції |
|
|
файла |
|
Оголошення |
Extern |
Глобальний |
Блок |
|
змінної |
|
|
|
|
|
|
|
|
|
Визначення |
Static |
Глобальний |
Блок |
Внутрішній |
змінної |
|
|
|
|
|
|
|
|
|
Визначення |
Auto чи |
Локальний |
Блок |
|
змінної |
Register |
|
|
|
|
|
|
|
|
Оголошення |
Extern чи |
Глобальний |
Залишок ви- |
|
функції |
Static |
|
хідного файла |
7.9 ПРИКЛАДИ ПРОГРАМ
Приклад 1 Функція піднесення до степеня.
Розглянемо функцію power і основну програма, що її використовує.
169
main() |
|
{ |
/* test power function */ |
int |
i; |
for (i = 0; i < 10; ++i) printf("%d %d %d\n",
i, power(2, i), power(-3, i));
}
power(x, n)/* raise x n-th power; n * > 0 */
int x, n;
{
int i, p; p = 1;
for (i = 1; i <= n; ++i) p = p * x;
return (p);
}
Аргументи функції описані в рядку int x,n;
Описи аргументів містяться між списком аргументів і лівою фігурною дужкою, що відкривається; кожен опис закінчується крапкою з комою. Імена, використані для аргументів функції power, є локальними і недоступні ніяким іншим функціям: інші процедури можуть використовувати ті ж самі імена без виникнення конфлікту. Це вірно для змінних “і” та “p”; “і” у функції power ніяк не зв'язане з “і” в фун-
кції main.
Значення, обчислене функцією power, передаються в main за допомогою оператора return. Всередині круглих дужок можна написати будьякий вираз.
Приклад 2
Написати і протестувати функцію I_TO_B(n,s,b), що переводить ціле число n у рядок s, що представляє число в системі числення з основою b.
#include<dos.h>
#include<stdio.h>
#include<string.h>
#include<conio.h>
void I_TO_B(long n,char *str)
{
170