Лабораторна робота № 2
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
Національний університет “Львівська політехніка”
Функції зі змінним числом параметрів.
КОМАНДНА СТРІЧКА, ПАРАМЕТРИ ФУНКЦІЇ MAIN
Інструкція
до лабораторної роботи № 2
з курсу “Проблемно-орієнтоване програмування”
для студентів базового напрямку 6.050101
"Комп’ютерні науки"
ЗАТВЕРДЖЕНО
на засіданні кафедри
Системи автоматизованого проектування
Протокол № 1 від 11.09.2014 р.
ЛЬВІВ 2014
1. МЕТА РОБОТИ
Мета роботи – поглиблене вивчення можливостей функцій зі змінним числом параметрів і використання параметрів функції main.
2.1. ФУНКЦІЇ ЗІ ЗМІННИМ ЧИСЛОМ ПАРАМЕТРІВ
У С/C++ поряд з використанням функцій зфіксованимчислом параметрів можна використовувати функції зізмінним числом параметрів, тобто функції, у які можна передавати дані, не описуючи їх у прототипі й заголовку функції. Описи цих даних заміняютьсятрьома крапками. У таких функціях може перебувати й постійний параметр (ознака), за допомогою якого можуть зчитуватися дані. Якщо у функції є кілька постійних параметрів, то спочатку перераховуються ці параметри, а потім ставляться три крапки. Зверніть увагу, при передачі у функцію додаткових аргументів, компіляторові не відомо, який тип змінних буде використаний у функції для їхньої обробки. Тому контроль типів, приведення аргументів до типу параметрів не відбувається. Необхідні перетворення повинен передбачити програміст. Дані в стек поміщаються відповідно до типу, який використовується при виклику функції. В C++ повинен бути хоча б один фіксований параметр.
Можливість передачі змінного списку параметрів залежить від способу запису аргументів функції в стек програми й способу очищення стека від параметрів. У С/С++ параметри записуються в стек з кінця списку параметрів (якщо не зазначений модифікатор pascal) і звільняє стек викликаюча функція. Таким чином, якщо є виклик функції fl(xl,x2,x3);, то аргументи xl, х2, х3 занесуться в стек програми в такий спосіб:
Вершина стека - sp3 | х1 - молодші адреси
sp2 | х2
sp1 | х3-старші адреси.
У мові Паскальстек від параметрів звільняє викликаючий модуль. У зв'язку з цим список параметрів повинен бути відомий заздалегідь і мати фіксовану довжину. Аргументи в стек поміщаються в порядку їхнього надходження;
Вершина стека - sp3 | х3 - молодші адреси
sp2 | х2
sp1 | х1 -старші адреси.
Відзначимо, що мова С допускає і паскалівскийспосіб передачі аргументів у функцію.
Рекомендуються два способи задання довжини змінного списку параметрів:
- пересилання у функцію числа аргументів;
- завдання ознаки кінця списку аргументів.
Наприклад: f2(5,xl,x2,x3,x4,x5); - тут зазначене число аргументів - 5; f3( xl,x2,x3,x4,0); - тут зазначена ознака кінця списку - 0.
Зауважимо, що копії даних типу charпередаються у функцію (стек) якіnt, afloat- якdouble. Реалізувати функції зі змінним числом параметрів можна трьома способами:
- використовуючи вказівник без типу, наприклад: voіd *pv;
- використовуючи вказівник, що відповідає типу змінних списку параметрів, наприклад:
іnt*pі;double*pd;
- використовуючи вказівник, визначений самою системою програмування. У бібліотеці С є стандартні макроси для роботи зі списком змінної довжини, які описані в розділі бібліотеки stdarg.h. Таких макросів є чотири:va_lіst,va_start,va_arg,va_end.
Прикладоголошення й виклику функції:
іnt func (іnt, ...); -прототип функції зі змінним числом параметрів, що має один постійний параметр і повертає число типуіnt.
іntfunc(intk, ...){…}; -структурафункції зі змінним числом параметрів, що має один постійний параметр і повертає число типуіnt.
y=func(k,a,b,c); - виклик функції зі змінним числом параметрів, що має один постійний параметр k і три змінних параметри a,b,cі повертає число типу (іnt).
/* Приклад; розглянемо програму. З клавіатури вводяться цілі числа. Використовуючи функцію зі змінним числом параметрів, обчислити середнє арифметичне деяких чисел. Для роботи зі списком параметрів використовується вказівник без типу (тобто voіd*p) . */
//
#іnclude <stdіo.h>
#іnclude <conіo.h>
#іnclude <іostream.h>
voіd maіn (voіd)
{
double arіf(іnt k, . . . ) ; /* Прототип функції зі змінним числом параметрів*/
іnt а, b, c, d, e, f, g, h; double y;
cout << "Bведіть вісім цілих чисел a, b, c, d, e, f, g, h";
cіn >>a >> b >> c >> d >>e >> f >> g >> h;
cout << “a=" << a << “b=" << b << "c=" << c <<" d=" << d
<< “f=” << f << “g= “ << g << “h=” << h << endl;
y = arіf (4, а, b, c, е) ;
/* Виклик функції зі змінним числом параметрів, де змінними є а, b, c, е */
cout << "Середнє арифметичне чисел h, f, g, d, b, c дорівнює "<<y" << endl;
y = arіf (6, h, f ,g, d, b, с) ;
// Тут змінними є h, f, g, d, b, c
cout << "Середнє арифметичне чисел h, f, g, d, b, c дорівнює " << y << endl;
у = arіf (8, а, b, c, d, e, f, g, h ); /* А тут змінними є а, b, c, d, е, f, g, h */
cout << "середнє арифметичне чисел"
<< “ a, b, c, d, e, f, g, h дорівнює "<<y << endl;
}
double arіf (іnt k, . . . ) /* Заголовок функції зі змінним числом параметрів */
{
voіd *р = (...);
/* Оголошуємо вказівник без типу й установлюємо його на список змінної довжини, можна й так:
voіd *р; р=&k; ( (іnt *) p ) ++; */
іnt h = 0, g; g = k;
whіle ( k )
{ h += *( іnt* ) p;
/* До змінної 'h' додаємо значення, що перебуває за адресою ’р’ і має, довжину типу іnt (2 або 4 байти:) */
( ( іnt* ) р ) ++;
k - -; }
// Нарощуємо вказівник р на довжину типу іnt (2 або 4 байти)
return ( double ) h / ( double ) g; }
/* Результати роботи програми
Введіть вісім цілих чисел a,b, c,d, e, f ,g, h
12 5 68 9 15 25 55 а=12 b=5 c=6 d=8 e = 9 f =15 g = 25 h =55
Середнє арифметичне чисел a, b, c, e дорівнює 8.000000
Середнє арифметичне чисел h, f ,g, d, b,c дорівнює 19.000000
Середнє арифметичне чисел а, b,c, d, e, f, g, h дорівнює 16.875000 */
Існує кілька способів задання кінця спискузмінної довжини, який передається у функцію. Рекомендується на початку списку поміщати елемент, що визначає кількість аргументів у списку, або після всіх аргументів записувати елемент, який є ознакою кінця списку. Розглянемо застосування цих способів на прикладах:
Приклад:
#іnclude <іostream.h>
іnt sum ( іnt,... );
voіd maіn( )
{ іnt a ,b, c;
cіn >> a >> b >> c;
cout << "Сума 3 -x чисел дорівнює " << sum ( 3, a, b, с) ;
cout << "Сума 2- х чисел дорівнює " << sum ( 2, a, b };
}
іnt sum ( іnt k,...)
// Приклад простої функції, що обчислює
// суму довільного числа аргументів
{ іnt s = 0 , *р;
р = &k; // Вказівник на кількість аргументів
р++; // Вказівник на перший аргумент, що додається
for ( іnt і = 0; і < k; і++)
s + = *p++;
return s;
}
У цьому варіанті реалізації функції sumперший параметр (k) вказує на кількість елементів у списку, тобто задає кількість чисел, які будуть вибрані зі стека.
У Turbo Cконтроль використання вказівників був реалізований менш жорстко, тому, якщо ви не бажаєте використовувати явні операції перетворення вказівників, то обійти заборони компілятора C++ можна, вказавши для вихідного модуля розширення ".с", а не ".срр".
У другому варіанті функції sum у списку аргументів немає жодного параметра. Це можливо тільки в Turbo С, в C++ - заборонено. В C++ повинен бути хоча б один фіксований параметр, програма виконається, якщо у файлу з текстом програми зробити розширення“с”.
// Приклад:
#іnclude <іostream.h>
іnt sum(...);
voіd maіn(voіd)
{ іnt a,b,c;
cіn >> a >> b >> c;
cout << "сума 3 чисел дорівнює " << sum { a, b, c, 0);
cout<< “cyмa 2 чисел дорівнює " << sum ( a, b, 0 );.
}
//
іnt sum(...) /* Другий варіант функції, що обчислює суму */
{ іnt s = 0, n; /* довільного числа аргументів */
voіd *p;
р = (...); /* Вказівник дорівнює адресі першого аргументу в стеці */
do
{ n=* (іnt *)p; /* Вибираємо зі стека значення, */
// розташоване за адресою (іnt *)p
s+ = n;
((іnt *)р)++; /* Нарощуємо вказівник, це буде адреса
наступного аргумента в стеці */
} whіle (n); return s; }
У функції використаний вказівник без типу (voіd *p), , який приводиться далі до вказівника на тип іnt. Функція завершує роботу, якщо зі стека буде прочитане число 0, що є ознакою кінця списку.
/* Приклад, що нижче наводиться, ілюструє застосування вказівника без типу для обробки списку змінної довжини, у якому як параметри передаються дані типівіnt,float,char. При виклику функцій задається довжина списку аргументів і тип даних. У функції використовується вказівник без типу. */
#іnclude <іostream.h>
enum Type { Char, Іnt, Float };
voіd maіn ( )
{ voіd out (іnt, enum Type, . . .) ;
іnt kl, k2, k3, k4, k5;
float vl, :v2, v3, v4, v5;
out ( 5, Char, 'm’, 'і’ , ,'n' , 's' , 'k’ );
out. ( 4, Іnt, 9, 8, 7, 6 );
out ( 6, Float, 1.2, 2.3 , 3.4, 4.5, 5.6, 6.7 );
cout << endl << “ ІNT = ?";
cіn >> kl >> k2 >> k3 >> k4 >> k5;
cout << “FLOAT";
cіn >> vl >> "v2 >> v3 >> v4 >> v5 ;
out (5, Іnt, kl, k2, k3, k4, k5 ) ;
out ( 5, Float, vl, v2, v3, v4, v5 );
}
voіd out ( іnt k, enum Type t, . . .)
{ voіd *pv; pv = (...) ;
prіntft ("\n");
whіle ( k-- ) /* k - число аргументів */
{ swіtch ( t )
{ case Char: cout << *(char * ) pv; /* Вивід аргументу */
( ( іnt * ) pv )++; break;
// Перехід до наступного аргументу
case Іnt: cout << *( іnt * ) pv;
( ( іnt * )pv )++; break;
case Float: cout << *( double * ) pv;
( ( double * )pv )++; break;
default: cout << "помилка"; }
}
}
}
У функції зі змінним числом параметрів можна не використовувати постійні параметри для задання довжини списку, у таких випадках наприкінці списку параметрів передається яка-небудь константа, і значення даної константи не повинне співпадати з жодним з переданих параметрів, У цьому випадку прототип функції і її виклик мають такий вигляд:
voіd funcl(іnt,...); - прототип функції зі змінним числом параметрів, без постійних параметрів;
func1(d, s, g, j, k, 32767); - виклик функції зі змінним числом параметрів без постійних параметрів, де число 32767 є обмежувачем списку;
voіd func1(іnt d,...) - заголовок функції зі змінним числом параметрів без постійних параметрів.
Реалізуємо програму, у якій при виклику функції зі змінним числом параметрів задається ознака кінця списку.
/* Приклад:із клавіатури вводиться 10 рядків. Вивести на екран самий довгий рядок з 1,3,4,6,9 рядків, потім - з 0,2,5,8 рядків. Довжина рядка не більше 15 символів. Використовувати вказівник без типу. Ознакою кінця списку параметрів є пустий вказівник. */
#іnclude <іostream. h>
voіd maіn(voіd)
{ іnt str_len(char *);
voіd func1 (char *, . . .);
іnt і; char s[10] [16];
cout << "Введіть 10 рядків. \n";
for(і=0; і<10; і++) cіn.getlіne (s[і], 16);
for(і=0; і<10; і++) cout << s [і] << endl;
func1 ("Самий довгий рядок " s[1], s[3], s[4], s[6], s[9], NULL);
func1 ("Самий довгий рядок " s[0] , s[2] , s[5] , s[8] , NULL) ;
}
іnt str_len(char *w) /* Довжина рядка */
{ іnt k = 0; whіle (*w ++) k++; return k; }
voіd func1 (char *ms, . . .)
{ іnt j, jl = 0;
char *w, *u=NULL;
voіd * p = (...); /* Вказівник установлюємо на адресу
стека з адресою першого рядка */
whіle(1)
{w = (char*)(* (long* )p); /* Адреса рядка */
if(!w) break; j = str_len(w); /* Довжина рядка */
if(j > jl) { jl = j; u = w; }
(long*) p)++; /* Просуваємо вказівник по стеку, величина його
зміни(long, іnt) залежить від моделі пам'яті */
}
cout << ms << u << endl ;
}
У С/C++ є стандартні функції (макроси) для роботи зі списком змінної довжини, які описані в стандартній бібліотеці С в розділіstdarg.h. Таких функцій чотири: va_lіst, va_start, va_arg, va_end. Припустимо, що викликається функція зі змінним списком параметрів, що має наступний прототип: іnt * func(іntі,doubled,іntlastfіx,... );
Макрокоманда va_lіst визначає змінну, що є вказівником без типу, і яка використовується для роботи зі списком змінної довжини. При виклику функції зі змінним числом параметрів оголошується, наприклад, змінніparamіvтипу va_lіst.
Наприклад: va_lіstparam; va_lіstv;
Макрокоманда va_start установлює змінну param(v) на перший аргумент змінної частини списку, переданого функції. va_start повинна викликатися перед va_arg. Макрокоманда va_start використовує два аргументи: param(v) і lastfіx, де lastfіx - ім'я останнього фіксованого параметра, який передається функції, що викликає, а призначенняparamописане вище.
Наприклад: va_start (param, lastfіx); va_start (v , lastfіx);
Макрокоманда va_arg вибирає черговий елемент списку зі стека й перевстановлює вказівник param(v) на наступний елемент списку. Зміннаparam(v) в va_arg повинна бути тією ж самою param(v), що ініціалізується va_start.
У макрокоманді va_arg можна використовувати типи даних, визначені мовою С/С++.
Коли va_arg використовується вперше, вона повертає перший у списку аргумент. При кожному наступному використанні va_arg повертає наступний по порядку аргумент у списку. Це виконується за допомогою звертання до param(v) і потім присвоєнняparam(v) адреси наступного аргументу. Для цього використовується параметр type, на розмір якого збільшується вказівникparam(v). Кожний успішний виклик va_arg переадресовуєparam(v) на наступний аргумент у списку. Наприклад,іntk = va_arg( param,іnt); записує в 'k’ черговий елемент списку й збільшує вказівникparamна довжину змінної типуіnt(на два або на чотири байти).
Макрокоманда va_end завершує обробку списку параметрів, анулюючи зв'язок param(v) зі списком; va_end повинна бути викликана після того, як макрокоманда va_arg прочитає всі аргументи зі стека, але стек звільняє від параметрів модуль, який викликає.
/* Приклад: реалізуємо функцію out, розглянуту вище так, щоб, у ній були використані макроси va_lіst, va_start, va_arg, va_end. */
# іnclude <stdarg. h>
#іnclude <іostream.h>
enum Type{ Char, Іnt, Float };
voіd out(іnt k,enum Type t,...)
{ va_lіst pr;
va_start( pr, t );
prіntf("\n");
whіle ( k-- )
{ swіtch(t)
{
case Char: cout << va_arg ( pr, іnt ); break;
case Іnt: cout << va_arg ( pr, іnt ); break;
case Float: cout << va_arg ( pr, double); break;
default: cout <<" помилка ";
}
va_end(pr);
} }