Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

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);

}

Результат роботи: 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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]