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

Зубенко, Омельчук - Програмування. Поглиблений курс

.pdf
Скачиваний:
49
Добавлен:
07.03.2016
Размер:
4.72 Mб
Скачать

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

Приклад 3.35.

extern int a[]; /*a – зовнішній статичний цілий масив*/

іnt f (char y []); /*y – символьний масив-параметр функції*/ extern int (c[0])[2]; /*c – зовнішній двовимірний масив*/ extern int c[][2]; /*c – те саме*/

int z[][3][4];

3.4.2. ДИНАМІЧНІ МАСИВИ

УС99, на відміну від попередніх версій С, є масиви зі змінною довжи- ною динамічні масиви, які вперше з'явилися в мові Алгол-60. Їхній роз- мір з'ясовується не під час компіляції, а в процесі виконання програми. Опис динамічного відрізняється від опису звичайного масиву тим, що константний вираз у специфікаторі масиву замінюється на довільний цілий вираз зі змінними.

Упроцесі виконання оператора опису динамічного масиву спочатку обчислюється його довжина (має бути строго більше 0), потім надається відповідне поле в пам'яті. Звернення до елементів масиву не повинне виходити за його межі оскільки автоматична перевірка порушення цієї вимоги не здійснюється, то таке звернення може мати непередбачувані наслідки. Це стосується й звичайних масивів.

Динамічні масиви можуть бути тільки локальними, визначеними в межах певного блока, і не можуть мати класи пам'яті extern та static.

Діє таке правило:

Якщо розмір динамічного масиву в заголовку функції залежить від змінної, то вона має бути або глобальною для функції, або її опис як параметра повинен передувати опису динамічного масиву в заголов- ку функції.

Після виходу з блока доступ до динамічного масиву припиняється, і при наступному виконанні блока динамічний масив створюється заново.

До гнучких типів належать динамічні масиви та інші типи, що містять такі масиви в описах (напр., тип покажчика на динамічні масиви, тип функцій з параметрами динамічними масивами).

Приклад 3.36. Розглянемо фрагмент програми, в якому два упоряд- ковані динамічні масиви об'єднуються в третій теж упорядкований динамічний масив. Таке об'єднання масивів називається злиттям:

/*merge

здійснює злиття двох упорядкованих динамічних масивів

a[n] та

b[n] у масив с[2*n]*/

331

ПРОГРАМУВАННЯ

double merge (int n, double a[n], double b[n], double c[2*n]) {int i, j, k;

i=j=k=0;

while (i<n && j<n)

if (a[i]<b[j]) c[k++]=a[i++]; else c[k++]=b[j++];

while (i<n) c[k++]=a[i++]; while (j<n) c[k++]=b[j++];

}

іnt maіn(voіd)

{

scanf("%d", &n); double aa[n]; double bb[n]; double cc[2*n];

merge (aa, bb, cc);

}

3.4.3. МАСИВИ ЯК ПАРАМЕТРИ ФУНКЦІЙ

Оскільки перевірка виходу індексів за межі масиву в С не передбаче- на, то як формальні параметри функцій допускаються масиви повного й неповного типів.

У викликах функції параметру-масиву типу Т відповідає фактичний параметр покажчик типу Т, який задає адресу конкретного масиву, що передається як фактичний параметр. Таким покажчиком може бути ім'я певного масиву. Цієї адреси достатньо, щоб за допомогою операції індек- сації отримати доступ до кожного компонента масиву, що передається функції для обробки. Таким чином, при передаванні у функцію фактич- ного параметра масиву передається тільки його адреса, а самі елементи залишаються на місці, не передаються й не копіюються.

Для того, щоб контролювати межі параметра-масиву, до списку пара- метрів часто додають цілий параметр, що задає його довжину.

Якщо параметром функції є динамічний масив, то в його декларації в прототипі функції розмір можна подати символом *'. Будь-який неконс- тантний розмір [e] динамічного масиву в прототипі функції вважають еквівалентним [*].

Приклад 3.37. Масиви як параметри функцій:

332

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

/*f1 знаходить середнє арифметичне компонентів дійсного масиву v довжиною n*/

double f1 (int n, double v[])

{

double s=0;

for (int i=0;i<n;i++) s+=v[i]; return s/n;

}

extern void f2 (int n, int w[*][5]); /*n – довжина динамічного масивy w*/

іnt maіn(voіd)

{

int m; іnt b[10];

scanf("%d", &m);

int z[m+1][5]; /*z – динамічний масив w*/

printf("\n%f", f1(10, b)); /*виклик функції f1 із фактичним па-

раметром – масивом b*/

f2(m+1, z); /*виклик функції f2 із фактичним параметром – динамічним масивом z*/

}

3.4.4. МАСИВИ Й СИМВОЛЬНІ РЯДКИ

Символьний рядок це послідовність символів, що завершується символом-нулем ‘\0'.

Рядки, довжина яких обмежена певною кількістю n , зручно зо- бражувати символьним масивом довжиною n +1. Наприклад, для

n = 9 опис відповідного масиву може бути таким: char str[10];. Такі рядки можна ініціалізувати за допомогою літералів:

char str[10]="TEST";

Поле рядка str після ініціалізації має вигляд

84

69

83

84

00

 

 

 

 

 

Як бачимо, у кінець рядка компілятор автоматично додає нульовий символ (який не враховується при обчисленні довжини рядка).

Для роботи з рядками у С99 існує бібліотека стандартних функцій. Прототипи їх містяться в бібліотеці <string.h> (деякі описані в під-

розд. 3.5.3).

333

ПРОГРАМУВАННЯ

*Література для CР: Масиви – [55, 102, 132, 133].

Контрольні запитання та вправи

1.Що таке масив?

2.Елементи яких типів можуть утворювати масиви?

3.Який вигляд має оператор опису (декларації) масиву?

4.У чому полягає смисл змінних з індексами й операції індек- сування?

5.Що таке багатовимірний масив?

6.Як розташовані в пам'яті компоненти масивів int(c[2])[3]

та int ((z[2])[3])[4]?

7.Який вигляд має ініціатор масивів int(c[2])[3] та int

((z[2])[3])[4]?

8.Як зображується рядок у мові С?

9.Що таке повний і неповний типи масивів?

10.Що таке гнучкий тип? Навести кілька прикладів гнучких типів.

11.Що таке динамічний масив? Навести приклади декларації динамічного масиву.

12.Які з декларацій масивів int(c[])[3], int(c[2])[2] та int(c[2])[] є коректними й чому?

13.Які особливості передавання одновимірних і багатовимірних масивів у функції?

14.Які з декларацій динамічних масивів:

а) double f (int n, double v[m] [n]), б) double f (int m, double v[*][m]),

в) double f (int n, double v[n]) не є коректними й чому?

15.Навести приклади ініціалізації рядків.

16.Дано послідовність цілих чисел a1,..,an і число с. Знайти пе-

рше й останнє місцезнаходження (перший індекс ліворуч і праворуч) числа с у даній послідовності. Побудувати відпові- дні рекурентні послідовності для розв'язку задачі.

17.Що виведуть такі фрагменти програм: int a[]={0,1,2,3,4};

int b[3][3]={{1,2,3},{4,5,6},{7,8,9}}; int *pb[3]={b[0],b[1],b[2]};

int *p = b[0];

а)

void main() { int i, *p;

for(p=a,i=0;p+i<=a+4;p++,i++) printf (*(p+i));

}

334

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

б)

void main() { int i;

for(i=0; i<3;i++)

printf( "%d\t,%d\t,%d\t",b[i][2- i],*b[i],*(*(b+i)+i));

}?

18.Дано послідовність цілих чисел a1,..,an . Знайти найбільший (найменший) її елемент. Побудувати відповідні рекурентні послідовності для розв'язку задачі.

19.Дано послідовність цілих чисел a1,..,an . Знайти її медіану,

тобто різницю між найбільшим і найменшим елементами. Поміняти місцями в ній найбільші й найменші елементи за

умови: а) усі ai попарно різні; б) у загальному випадку.

20.Дано послідовність цілих чисел a1,..,an . Перебудувати її та- ким чином, щоб: а) спочатку йшли поспіль (у тому ж самому порядку) від'ємні її члени, потім нульові, а вже потім до- датні; б) те саме, що в а), але нульові члени мають залиша- тися на своїх місцях.

21.Дано багаточлен P(x ) степеня n , заданий масивом a(n )

його цілих коефіцієнтів. Знайти коефіцієнти багаточлена:

а) (x 4)P(x ) ; б) P 2(x) .

22. Дано багаточлен P(x) степеня n із цілими коефіцієнтами.

Побудувати рекурентні послідовності й написати функцію double polinom(int а[], int n, double x) для обчис-

лення за схемою Горнера значення P(x ). Знайти: а) P(1) ; б)

1

P(x )dx ; г) усі цілі корені P(x ). Функцію

P(1)/P (1); в)

 

0

polinom зберігати в окремому файлі "p.c". Для включення її до основної програми застосувати директиву #include

"p.c".

23.Написати функцію для відшукання перших n простих чи- сел. Надрукувати перші 100 простих чисел.

24.Знайти всі прості числа на інтервалі від 2 до n . Застосувати ґратку Ератосфена: спочатку із сукупності [2,n] відкидають- ся парні числа, потім з того, що залишилося кратні 3 тощо.

25.Обчислити надвеликі числа: а) 2100 ; б) 100!.

26 * . Обчислити 100 перших десяткових цифр основи натура- льного логарифма e за допомогою ряду із вправи 25, під-

розд. 3.3 [33].

335

ПРОГРАМУВАННЯ

27.Дано числовий масив довжиною n . Надрукувати всі 2n йо- го підмасивів: а) з використанням рекурсії; б) без неї.

28.Скорочення перебору. Дано числовий масив довжиною n

невід'ємних елементів і число s . Знайти всі його підмасиви із сумою елементів s . У процесі перебирання ігнорувати всі підмасиви із сумою елементів більше s .

29.Надрукувати гістограму частоти символів тексту, що вво- диться з клавіатури.

30.Знайти індекси двох однакових елементів матриці.

31. Дано матрицю розміром n ×m . Розташувати її рядки: а) так, щоб перший стовпчик став упорядкованим за зрос- танням; б) за зростанням сум елементів рядків; в) за зрос- танням найменших елементів рядків.

32.Характеристикою вектора називають середньоарифметичне його компонентів. Написати функцію для перестановки в ма- триці двох рядків за їхніми номерами. Переставити рядки в матриці відповідно до зростання їхніх характеристик.

33.Визначити, чи впорядкований даний вектор (за зростанням чи спаданням). Знайти номер максимального за характери- стикою (див. вправу 32) упорядкованого рядка матриці.

34.Перевірити, чи складається вектор з попарно різних компо- нентів. Підрахувати кількість рядків матриці, що склада- ються з попарно різних елементів.

35.Перевірити, чи утворюють компоненти вектора довжиною m перестановку чисел від 1 до m . Підрахувати, скільки рядків у даній матриці розміром n ×m є перестановками

чисел від 1 до m

36.Перевірити, чи містить вектор лише непарні компоненти. Знайти серед рядків матриці, що містять лише непарні елемен- ти, рядок з найбільшою характеристикою (див. вправу 32).

37.Серія це послідовність, що складається з однакових еле- ментів. Знайти: а) довжину найбільшої серії серед рядків матриці; б) рядки з найдовшою серією.

38.Два вектори схожі, якщо збігаються множини їхніх компо- нент. Написати функцію для перевірки, чи схожі два век- тори. Визначити, які рядки матриці: а) найбільш схожі; б) найменш схожі.

39.Написати функцію для обчислення скалярного добутку двох векторів. Визначити рядок матриці, скалярний добуток якого з першим рядком матриці максимальний.

40.За даною числовою квадратною матрицею A порядку m і ве- ктором b довжиною m отримати вектор: а) Ab ; б) (A3 E )b .

336

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

41 * . За даною булевою квадратною матрицею A порядку m

обчислити матрицю An . Урахувати, що квадратні матриці є півгрупою відносно множення, тому можна скористатися алгоритмом швидкого піднесення до степеня.

42 * . Написати функцію для розв'язання методом Гаусса систе- ми лінійних рівнянь. Застосувати її для розв'язку систем:

10x1 + x2 + x3 =12; 4x1 + 0.24x2 0.08x3 = 8; 2x1 +10x2 + x3 =13; 0.09x1 + 3x2 0.15x3 = 9;

2x1 + 2x2 +10x3 =14 ; 0.04x1 0.082x2 + 4x3 = 20 [19, 33]. 43 * . За даною квадратною матрицею A порядку m знайти

обернену матрицю A1.

3.5. Тип покажчиків

¾Специфікатор покажчиків

¾Операції з покажчиками

¾Індексація покажчиків і масиви

¾Масиви покажчиків

¾Функції виділення пам'яті

¾Моделювання динамічних масивів

Ключові слова: тип покажчиків, покажчик, покажчики на об'єкти, функції загального призначення типу void*, операція взяття адреси &, розіменування покажчика *, константа NULL, зведення покажчиків, операції індексації, збіль- шення та зменшення покажчиків, адресна арифметика, масив покажчиків, ди- намічний масив, функції динамічного виділення пам'яті: malloc, calloc та free, моделювання динамічних масивів, надвеликі масиви.

Тип покажчиків складають адреси полів об'єктів у ОП певного фік- сованого типу з адресою порожнього поля NULL, що є препроцесорною константою з нульовим значенням і використовується для подання ситуації, коли покажчик не адресує об'єкт даного типу. Константи та змінні типу покажчиків називаються покажчиками.

Кажуть, що покажчик посилається на об'єкт, який він адресує. Якщо об'єктом, що адресує покажчик p, є інший покажчик, який теж

337

ПРОГРАМУВАННЯ

адресує певний об'єкт, то кажуть, що p опосередковано посилається

на об'єкт (непряма адресація).

Отримати доступ до об'єкта, що адресується покажчиком p, можна за допомогою імені *p (див. операцію розіменування в підрозд. 3.5.2).

Зв'язки між покажчиками й об'єктами, на які вони посилаються, зручно подавати графічно. Наприклад: а) покажчик p посилається на цілу змінну n=150, розміщену за адресою 0x00FF; б) покажчик p поси- лається на цілий покажчик q, розміщений за адресою 0x0064, який, у свою чергу, посилається на ту саму змінну n=150. Тоді в першому ви- падку імена *p та n є синонімами. У другому випадку покажчик p непрямо посилається на n, імена n, *q і **p синоніми. Ці факти графічно подаються так:

p: *p,n:

 

0x00FF

 

 

150

 

 

 

 

 

 

 

 

 

 

p:

*p,q:

 

**p,*q, n:

0x0064

 

 

0x00FF

 

150

 

 

 

 

 

 

 

 

 

Кожному типу T відповідає тип покажчиків на нього T*. Наприклад, тип покажчиків char* складають адреси байтів із кодами символів, тип int* адреси двобайтних слів із двійковими цілими числами тощо.

Для подання покажчиків використовують шістнадцяткові беззна-

кові константи: 0X00FF ( =255), 0x00FF ( =255), 0x7FFF ( =32767).

Структура покажчиків залежить від архітектури процесора. Зазвичай ОП має сторінкову організацію, тобто розбита на сторінки (сегменти)

фіксованого розміру, наприклад 216 байти, кожна з яких має поряд- ковий номер (базу). Адреса поля тоді має структуру пари (база, зсув). У межах фіксованого сегмента вказують тільки зсув поля відносно його початку: від 0х0000 до 0x7FFF.

Розрізняють покажчики на об'єкти, функції та загального призна-

чення типу void*. Останні ще називають безтиповими. Вони сумісні з будь-якими покажчиками на об'єкт і використовуються в основному в прототипах і описах функцій. Покажчики на функції розглядаються також у підрозд. 3.6.

Покажчики застосовуються у двох різних напрямах:

а) по-перше, вони забезпечують прямий доступ до об'єктів програ- мних даних (як статичних, так і динамічних) і тим самим можли- вість "ручного" керування пам'яттю;

338

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

б) по-друге, можливості адресної арифметики й непрямої адресації використовують для оптимізації коду програми.

Нагадаємо, що динамічними називаються дані, зв'язування яких з об'єктами в пам'яті відбувається не до, а в процесі виконання програми.

Увага! При роботі з покажчиками слід уникати невизначених, тоб- то ненульових, покажчиків, що не посилаються на реальний об'єкт даного типу ►

Приклад 3.38. Невизначений покажчик:

void main (void)

{double *cptr; /*cptr – покажчик типу double*/

*cptr=1.0;

}

Оператор присвоювання *cptr=1.0; записує число 1.0 у пам'ять за адресою, на яку вказує покажчик cptr. Це може мати непередбачу- вані наслідки, оскільки значення покажчика cptr у програмі ще не

ініціалізувалося, отже, запис потрапить у випадкову ділянку пам'яті Вважається коректною обов'язкова ініціалізація покажчика під час його опису. Як мінімум це має бути його обнулення. Наприклад,

int *p=NULL;.

3.5.1. СПЕЦИФІКАТОР ПОКАЖЧИКІВ

Синтаксис специфікатора покажчика:

<cпецифікатор-покажчика>::=<покажчик> <прямий-специфікатор> <покажчик>::=*[<кваліфікатор-типу>] [<покажчик>]

Прямий специфікатор (див. підрозд. 3.13) може бути ідентифікатором, спеціфікатором масиву, функції й покажчика, узятим у круглі дужки.

Приклад 3.39. Специфікатори покажчика:

Опис

Тип

int * p;

Покажчик на цілі

int *p[];

Масив покажчиків на цілі

int *p();

Функція зі значенням типу, який є покажчиком на цілі

int (*p)();

Покажчик на функцію із цілими значеннями

const int *p;

Покажчик на цілі константи

int * const p;

Константний покажчик на цілі

сhar **p;

Покажчик на символьні покажчики

339

ПРОГРАМУВАННЯ

Список кваліфікаторів типу містить їхній перелік (див. підрозд. 3.13), де кваліфікатор restrict використовується тільки з покажчиками й по- відомляє компілятору, що в певний період часу покажчик є єдиним за- собом доступу до об'єкта, на який він посилається. Жодний інший по- кажчик чи змінна не мають звертатись до об'єкта, якщо він адресується покажчиком із кваліфікатором restrict. Наприклад, якщо адресу ди- намічного масиву подати restrict-покажчиком:

int * restrict dm; dm=(int*)calloc(100000,sizeof(int));

то до його елементів можна звертатись лише через покажчик dm. Розглянемо інший приклад, а саме прототип стандартної функції

strcpy (див. табл. 3.12):

char *strcpy(char *s1,const char *s2);

яка копіює рядок s2 за адресою s1. Кваліфікатор const указує, що адреса рядка s2 у процесі виклику функції не змінюється. Однак сам рядок може змінитися при копіюванні, якщо поля рядків s1 та s2 перетинаються. Параметри s1 та s2 із кваліфікатором restrict:

char *strcpy(char* restrict s1,const char* restrict s2); будуть забороняти таку ситуацію.

Компілятор оптимізує вирази з покажчиками, які не змінюються (з метою пришвидшення виконання програми). Без кваліфікатора restrict оптимізація могла б мати непередбачувані наслідки.

3.5.2. ОПЕРАЦІЇ З ПОКАЖЧИКАМИ

Операція взяття адреси змінної & повертає адресу свого операнда. Наприклад, у результаті присвоювання p=&n; покажчик p отримає зна- чення, що є адресою цілої змінної n і дорівнює адресі першого з двох байтів поля, в якому зберігається значення змінної. Нехай n зберігається в полі з адресою 0x00FF, а її значення дорівнює 150. Тоді після присво- ювання змінна p отримає значення 255 і буде посилатися на змінну n:

p: n:

150

Об'єкти змінних до присв.

p: n:

255 150

Об'єкти змінних після присв.

Друга операція для роботи з покажчиками позначається симво- лом * і називається розіменуванням. Її ми вже розглядали на початку

340