You are on page 1of 359

Програмування, частина 1

(Основи алгоритмізації
і програмування)
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
Лекція №1. Тема №1.
ОСНОВИ АЛГОРИТМІЗАЦІЇ ЗАДАЧ
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
Структура навчальної дисципліни
№ Найменування показників Всього годин
1. Кількість кредитів/год. 7/210
2. Усього годин аудиторної роботи, у тому числі: 105
3.  лекційні заняття, год. 60
4.  практичні заняття, год. 15
5.  лабораторні заняття, год. 30
6. Усього годин самостійної роботи 105
7. Екзамен +

Національний університет «Львівська політехніка»


Мета та завдання дисципліни
Мета вивчення навчальної дисципліни Завдання навчальної дисципліни
Мета вивчення навчальної дисципліни – формування В результаті вивчення навчальної дисципліни здобувач освіти
у здобувачів освіти знань та практичних навичок, повинен бути здатним продемонструвати такі результати
необхідних для усіх етапів розв’язку поставленої навчання:
задачі, таких як розробка алгоритму, написання • знати базові структури алгоритмів і основи алгоритмізації задач;
програми на мові програмування С/С++, • знати прийоми програмування на мові програмування С/С++;
налагодження її і виконання. • вміти розробити алгоритм для конкретної задачі;
• вміти написати на мові програмування С/С++ програму для
вирішення конкретної задачі;
• вміти налагоджувати програму для вирішення конкретної задачі;
• вміти виконувати програму для вирішення конкретної задачі;
• мати навики роботи в операційних системах і інтегрованих
середовищах розробки програмного забезпечення.

Національний університет «Львівська політехніка»


Анотація навчальної дисципліни
Тема №1. ОСНОВИ АЛГОРИТМІЗАЦІЇ ЗАДАЧ
• поняття алгоритму, види алгоритмів – лінійні, з розгалуженням і циклічні, запис алгоритмів за допомогою блок-схем.
Тема №2. ОСНОВИ МОВИ ПРОГРАМУВАННЯ С
• структура програми, типи даних, константи, змінні, консольний ввід-вивід з клавіатури, операції, директиви препроцесора.
Тема №3. ОБЧИСЛЮВАЛЬНІ АЛГОРИТМИ ТА ЇХ РЕАЛІЗАЦІЯ НА МОВІ С
• Оператори - умовний, циклу, поняття функції, локальні і глобальні змінні.
Тема №4. МАСИВИ ТА ОБРОБКА ЧИСЛОВИХ ДАНИХ
• одно і багатовимірні масиви, сортування масивів.
Тема №5. ВКАЗІВНИКИ У МОВІ С
• динамічна пам’ять, вказівники на масиви, динамічні масиви.
Тема №6. РЯДКИ ТА ОБРОБКА ТЕКСТІВ
• масиви типу char і рядки мови С, бібліотечні функції роботи з рядками.
Тема №7. РОБОТА З ДИСКОВИМИ ФАЙЛАМИ
• робота з текстовими і бінарними файлами, структуровані типи даних.

Національний університет «Львівська політехніка»


Оцінювання результатів навчання

30 10 40 50 10 100

Оцінка за дисципліну формується як сума балів за поточний і екзаменаційний контроль.


Поточний контроль - протягом семестру здобувачі освіти виконують лабораторні роботи і проходять тестування до кожної
теми у відповідному курсі у ВНС НУ «ЛП».
Здобувач освіти допускається до здачі екзамену за умови здачі усіх лабораторних робіт!
Екзамен здобувачі освіти складають у письмово-усній формі з фіксацією відповідей на екзаменаційному листі.

Національний університет «Львівська політехніка»


Навчально-методичне
забезпечення
• Електронний навчально-методичний комплекс (ЕНМК) з навчальної дисципліни, розміщений у
Віртуальному навчальному середовищі (ВНС) НУ «ЛП»:
http://vns.lpnu.ua/course/view.php?id=1545.
• Електронний конспект лекцій.
• Електронні вказівки до виконання лабораторних робіт.
• Електронні вказівки до виконання практичних робіт.

Національний університет «Львівська політехніка»


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

Процес підготовки розв’язку задачі і її безпосередня реалізація на комп'ютері відбувається за певну


кількість самостійних етапів:
1. Постановка завдання.
2. Математичне формулювання задачі.
3. Вибір методу розв’язку.
4. Розроблення алгоритму.
5. Складання програми на алгоритмічній мові.
6. Налагодження та розв’язування задачі на комп'ютері.
7. Аналіз отриманих результатів.
Національний університет «Львівська політехніка»
Програмування - це теоретична й практична діяльність з створення програмного забезпечення.

Програмування — ітераційний процес, що складається з постановки завдання, розробки алгоритму,


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

Алгоритмічна мова - формальна мова, призначена для запису і реалізації алгоритмів.

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

Програма — це алгоритм, записаний мовою програмування.

Класифікація мов програмування:


• мови високого і низького рівнів;
• процедурно-орієнтовані і об’єктно-орієнтовані мови програмування.

Національний університет «Львівська політехніка»


1.1. Алгоритми і способи їх запису
Алгоритм – це строга і чітка система правил, яка визначає послідовність дій над деякими об’єктами і яка за
скінчену кількість кроків приводить до досягнення поставленої мети.

Основні властивості (характеристики) алгоритму:


• Детермінованість (однозначна визначеність результату кожної операції, незалежна від виконавця).
• Дискретність (розчленованість алгоритму на окремі операції).
• Масовість (можливість застосування до будь-яких вхідних даних задач виділеного класу).
• Результативність (скінченність процесу перетворення інформації).

Виконавець алгоритму: людина, комп’ютер, комп’ютер, мови програмування. Залежність алгоритму від
виконавця – від множини операцій, які знає (вміє виконувати) виконавець.

Національний університет «Львівська політехніка»


Способи запису алгоритмів. 1) Словесний спосіб

Алгоритм Евкліда (~450 р. – ~380 р. до н.е., грек з Александрії, Основи – основи геометрії та теорії чисел)
знаходження найбільшого спільного дільника (НСД) двох натуральних чисел.
Нехай задані А і В – два натуральних числа. Необхідно знайти НСД цих чисел.

Алгоритм Евкліда визначає такі кроки:


1. Встановити M рівне А і N рівне В. Перейти до п. 2.
2. Перевірити, M рівне N? Якщо так, то перейти до п. 6, інакше перейти до п. 3.
3. Перевірити, M більше за N? Якщо так, то перейти до п. 5, інакше перейти до п. 4.
4. Встановити M рівне N і N рівне M. Перейти до п.5.
5. Визначити різницю значень M і N. Після цього встановити M рівним N і N рівним отриманій різниці.
Перейти до пункту 2.
6. Прийняти M (чи N) за шукане значення НСД і припинити обчислення.

Переваги: зрозуміло для виконавця-людини, відсутність строгої формалізації.


Недоліки: не наочність.
Національний університет «Львівська політехніка»
2) Графічний спосіб

Блок-схема алгоритму - це графічне подання методу


розв'язання задачі, в якому використовуються символи,
що відображають операції (дії) і дані.

У блок-схемі алгоритму кожному типу дій відповідає


геометрична фігура, представлена символом дії.
Символи дії з'єднують лініями переходів, які визначають
черговість виконання дій.

Переваги: строга формалізація, наочність.


Недоліки: не може безпосередньо виконуватися на
комп’ютері.

Рисунок 1.1 – Блок-схема алгоритму Евкліда


Національний університет «Львівська політехніка»
3) За допомогою алгоритмічної мови

Програма на мові програмування Basic:

10 INPUT A, B
20 M=A : N=B
30 IF M=N THEN 80
40 IF M>N THEN 60
50 R=M : M=N : N=R
60 R=M-N : M=N : N=R
70 GOTO 30
80 PRINT “НСД=”; M
90 END

Переваги: строга формалізація, можна безпосередньо виконати на комп’ютері.


Недоліки: менша наочність (у порівнянні з графічним способом запису алгоритму).

Національний університет «Львівська політехніка»


Лекція №2. 1.2. Правила запису
алгоритмів за допомогою блок-схем
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
ДСТУ ISO 5807:2016 Обробляння інформації. Символи та угоди щодо документації стосовно даних, програм та
системних блок-схем, схем мережевих програм та схем системних ресурсів (ISO 5807:1985, IDT).

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

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

Національний університет «Львівська політехніка»


Основні символи для запису
блок-схем алгоритмів
Назва символу Позначення Пояснення
процес Виконання певної операції або групи операцій.
Символ «процес» застосовується для позначення
виконання однієї або групи операцій, що приводять до
зміни значення даних. Подання операцій досить вільне.
Наприклад, для позначення обчислень можна
використовувати математичні вирази або текст.

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


однієї або декількох операцій або кроків програми, які
визначені в іншому місці (в підпрограмі, модулі).

Національний університет «Львівська політехніка»


Назва символу Позначення Пояснення
рішення Символ відображає вибір напрямку виконання алгоритму в
залежності від деяких змінних умов.
Символ Рішення використовується для позначення
переходів управління за умовою. Має один вхід і ряд
виходів, при цьому тільки один з них може бути
активізований після обчислення умов, визначених
всередині цього символу. Результати обчислення можуть
бути записані по сусідству з лініями, які відображають ці
шляхи.

межі циклу Символ, що складається з двох частин, відображає початок


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

Національний університет «Львівська політехніка»


Назва символу Позначення Пояснення
підготовка Символ відображає модифікацію команди або групи
команд з метою впливу на деяку подальшу функцію.
Наприклад, модифікація параметрів циклу.
Символ Підготовка використовується, як правило, для
організації циклу з параметром. Усередині символу
записується параметр циклу, вказується його початкове
значення, гранична умова і правила зміни значення
параметру. Символ розміщується на початку конструкції.
термінатор Символ відображає вихід в зовнішнє середовище і вхід із
зовнішнього середовища (початок або кінець схеми
програми).
Алгоритм починається і закінчується термінаторами
(символами Початок і Кінець).
дані Символ відображає ввід-вивід даних.

Національний університет «Львівська політехніка»


Назва символу Позначення Пояснення
лінія Символ відображає потік даних або управління. При
необхідності або для зручності читання можуть бути додані
стрілки-покажчики.
Лінії переходів використовуються для позначення порядку
виконання дій.

з'єднувач Символ відображає вихід в частину схеми і вхід з іншої


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

Національний університет «Львівська політехніка»


Назва символу Позначення Пояснення
коментар Символ використовують для додавання описових
коментарів або пояснювальних записів з метою пояснення
або приміток. Пунктирні лінії в символі коментар пов'язані з
відповідним символом або можуть обводити групу
символів. Текст коментарів або приміток повинен бути
розміщений поряд з символом коментаря.

Національний університет «Львівська політехніка»


Основні правила застосування символів початок

і виконання блок-схем алгоритмів: ввід


АІВ

1. Символи в блок-схемі повинні бути розташовані рівномірно. Потрібно


М=А
дотримуватися розумної довжини з'єднань і мінімальної кількості довгих ліній. N=B
2. Символи повинні бути по можливості одного розміру і, переважно,
горизонтальної орієнтації.
ні
3. Усередині символу поміщається мінімальна кількість тексту, необхідного для M=N

розуміння функції символу. Для запису використовується природна мова з так


елементами математичної символіки. Якщо обсяг тексту перевищує розмір M>N
символу, то потрібно використовувати символ Коментар.
ні
4. Потоки даних і потоки управління в схемах показані лініями. Напрямок потоку так
зліва направо і зверху вниз вважається стандартним. Якщо потік має напрямок, R=M
вивід М
відмінний від стандартного, вказувати цей напрямок потрібно стрілками. M=N
N=R
5. Лінії в схемах повинні підходити до символу або зліва, або зверху, а виходити
або праворуч, або знизу. Лінії повинні бути направлені до центру символу. R=M-N кінець
M=N
6. Кожен символ має один вхід і один вихід, винятком є символ Рішення, який має N=R
один вхід і кілька виходів. При цьому кожен вихід повинен супроводжуватися
значеннями умов, щоб вказати логічний шлях, який він представляє.
Рисунок 1.1 – Блок-схема алгоритму Евкліда

Національний університет «Львівська політехніка»


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

Національний університет «Львівська політехніка»


Лекція №3.
1.3. Типові структури алгоритмів:
лінійні, з розгалуженням
і циклічні алгоритми
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
Лінійний алгоритм
Обчислювальні процеси описуються наступними типовими структурами
Процес 1
алгоритмів:
• лінійна;
• розгалужена;
• циклічна. Процес 2

Алгоритм лінійної структури - це алгоритм, дії якого виконуються


послідовно, одна за одною. Такий порядок виконання дій називається Процес 3
природним.

Рисунок 1.2 – Лінійний алгоритм

Національний університет «Львівська політехніка»


Початок
Завдання: скласти алгоритм обчислення площі трикутника зі
сторонами A, B, C за формулою Герона:
Ввід
де . А, В, С

Розв’язок: словесний опис алгоритму буде мати вигляд: ABC


p 
1. Ввести A, B, C. 2
2. Обчислити .
3. Обчислити . S p * ( p  A) * ( p  B) * ( p  C )
4. Вивести S.
5. Кінець.
Вивід S

Алгоритм має лінійну структуру при будь-яких вхідних даних. І кожна


наступна дія випливає з попередньої. кінець

Рисунок 1.3 – Блок-схема алгоритму


обчислення площі трикутника за
формулою Герона
Національний університет «Львівська політехніка»
Алгоритм з розгалуженням
Розгалужений (той, що розгалужується) обчислювальний процес - це процес, в якому передбачено
розгалуження виконуваної послідовності дій залежно від результату перевірки будь-якої умови. У даних
алгоритмах природний порядок виконання дій порушується. Словесно розгалуження описується таким
чином:
ЯКЩО умова справедлива (істина), то виконується Процес1, ІНАКШЕ виконується Процес2.

умова
умова умова
так ні так
ні

Процес 1 Процес 2 процес


так процес
ні

Рисунок 1.4 – Алгоритми з розгалуженням

Національний університет «Львівська політехніка»


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

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

Умова - це логічний вираз, яке може приймати два значення - «ТАК» (істина) або «НІ» (неправда). Якщо
умова вірна, дія виконується, у протилежному випадку - дія не виконується.

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

Національний університет «Львівська політехніка»


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

умова тіло циклу


продовження
циклу
так

так ні умова
продовження
циклу
тіло циклу

ні

Рисунок 1.5 – Циклічні алгоритми

Національний університет «Львівська політехніка»


Організація циклу Початок циклу

Для організації будь-якого циклу необхідне виконання наступних умов: Встановлення


початкових значень
• Встановлення початкового значення параметра (змінної) циклу перед параметрів циклу
початком циклу;
• Зміна параметра (змінної) циклу перед кожним новим повторенням Робоча частина
циклу
тіла циклу; (тіло циклу)
так
• Перевірка умови закінчення (виходу з циклу) або повторення циклу;
• Перехід до початку циклу, якщо цикл не закінчений, або вихід з Зміна параметрів
циклу
циклу, якщо умову виходу виконано.

Умова
продовження
циклу

ні

Кінець циклу

Рисунок 1.6 – Блок-схема роботи


циклічного алгоритму
Національний університет «Львівська політехніка»
Класифікація циклів

Розглянемо класифікацію циклів для кращого розуміння їх різновидів та основних відмінностей один від одного.

За місцем розташування умов перевірки повторення або закінчення циклу можна виділити цикли з
передумовою та післяумовою.

• У циклі з передумовою (з попередньою умовою) перевірка виходу стоїть перед тілом циклу. Умова
записується у вигляді логічного виразу. Оператори циклу (тіло циклу) виконуються, поки умова істинна. Якщо
при вході в цикл умова "неправда" (не виконується), то буде вихід з циклу. У цьому випадку цикл не
виконається жодного разу.
• У циклі з післяумовою перевірка виходу стоїть після тіла циклу. Оператори циклу будуть виконуватися до
тих пір, поки не стане можливою умова виходу з циклу. Цикл виконається хоча б один раз.

Національний університет «Львівська політехніка»


Класифікація циклів

За способом контролю закінчення циклу розрізняють такі типи циклів:

• Кількість повторень циклу невідома (цикл з невідомим числом ітерацій). Вихід з циклу виконується при
перевірці додаткової умови.
• Тип арифметичної прогресії (цикл з відомим числом ітерацій), в якому кількість повторення циклу відома
при входженні в цикл – задана явно (як в попередньому прикладі) чи може бути просто вирахувана
(наприклад, в задачі табуляції функції із заданим кроком на заданому проміжку).У цих циклах параметр
(змінна циклу) змінюється від заданого початкового до заданого кінцевого значення, змінюючись при
кожному виконанні циклу на постійну величину, яка називається кроком параметра циклу. Інша назва цього
типу - цикли з параметром.

Національний університет «Львівська політехніка»


Умови правильного використання циклів.

Початкове, кінцеве значення змінної циклу і крок повинні мати один і той же тип.

Якщо початкове і кінцеве значення рівні, то цикл виконується один раз.

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

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

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua
Лекція №4. Тема №2.
ОСНОВИ МОВИ ПРОГРАМУВАННЯ С
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
2.1. Структура С-програма

програми на мові С #директиви


препроцесора

Програма написана мовою С являє собою сукупність однієї чи глобальні змінні;

декількох функцій, команд препроцесора та зовнішніх описів.


Одна з функцій має ім'я main та є головною. прототипи
функцій;
Функція main виконується завжди першою.
Функції описують сукупність дій, які потрібно виконати. main() локальні змінні;
{} оператори
Команди препроцесора вказують на перетворення, яке треба
зробити над програмою.
функція a() локальні змінні;
оператори
Оголошення змінних звичайно розміщується на початку {}

програми.
функція b() локальні змінні;
Всі змінні повинні бути описані до їх використання. {} оператори

Рисунок 2.1 – Структура програми на мові С


Національний університет «Львівська політехніка»
Перед іменем кожної функції потрібно вказувати дані про тип значення, що повертає функція (тип результату).
Якщо функція нічого не повертає, то вказується тип void.
Кожна функція (в тому числі і main) в мові С повинна мати набір параметрів. Цей набір може бути пустим.
Після заголовку функції йде тіло функції.
Тіло функції – це послідовність визначень, описів і операторів, які заключені в фігурні дужки {}.

Визначення описують об’єкти, необхідні для представлення в програмі даних, що обробляються (іменовані
константи, змінні різних типів).

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

Оператори визначають дії програми на кожному кроці її виконання.

Кожне визначення, опис і кожен оператор закінчуються символом ; (крапка з комою).

Національний університет «Львівська політехніка»


Лістинг 2.1: Обчислення середнього арифметичного трьох чисел

int main(void)
{
double res, xl, x2, x3;
xl = 4.5;
x2 = 5.6;
x3 = 7.8;
res = (xl + x2 + x3) / 3;
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 2.2: Обчислення середнього арифметичного трьох чисел
та вивід результату

#include <stdio.h>
int main(void)
{
double res, xl, x2, x3;
xl = 4.5;
x2 = 5.6;
x3 = 7.8;
res = (xl + x2 + x3) / 3;
printf("Result is %f\n", res);
return 0;
}

Результати роботи програми:


Result is 5.966667

Національний університет «Львівська політехніка»


Лістинг 2.3: Обчислення кореня квадратного суми квадратів двох
чисел

#include <stdio.h>
#include <math.h>
int main(void)
{
double res, xl, x2;
xl = 4.5;
x2 = 5.6;
res = sqrt(xl * xl + x2 * x2);
printf("Square root is %f\n", res);
return 0;
}

Результати роботи програми:


Square root is 7.184010

Національний університет «Львівська політехніка»


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

Синтаксичні помилки бувають у тому випадку, коли порушені правила мови С. Ці синтаксичні помилки будуть
знайдені компілятором.

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

Семантичні помилки виникають тоді, коли неправильно використана семантика тої чи іншої конструкції,
наприклад, передача параметрів функції, які не відповідають її аргументам.

Логічні помилки призводять до невірного результату. Компілятор не знаходить таких помилок, оскільки вони не
суперечать правилам мови С. Такі помилки можна знайти виконуючи програму крок за кроком, переглядаючи
значення кожної змінної.

Національний університет «Львівська політехніка»


2.2. Етапи створення
виконавчого коду програми
Для того, щоб забезпечити перетворення програмного коду на мові С у виконавчий файл, потрібно
виконати наступні дії:
1. Скориставшись текстовим редактором написати програму і зберегти її у файлі на диску. Цей файл
містить код програми.
2. Скомпілювати код програми.
3. Зв’язати об’єктний код основної програми (отриманий в процесі компіляції) з додатковим об’єктним
кодом бібліотечних та/або користувацьких функцій і, таким чином, скомпонувати єдину програму.

Національний університет «Львівська політехніка»


Компілятор

– це програма, яка перетворює програму написану мовою високого рівня в набір команд машинної мови, який
використовується комп’ютером.
Цей процес є двоетапним. Зокрема, при компіляції програми на мові С спочатку виконується препроцесінг, а
після цього — компіляція в об’єктний код.
Перед основною компіляцією виконується попередня обробка (препроцесінг) програмного коду. При цьому всі
директиви препроцесора, що починаються з символу #, обробляються однаково, а саме замість самої
директиви в програмний код вставляється контент відповідного заголовного файлу. Заголовні файли зазвичай
містять необхідні описи бібліотечних функцій, змінних та структур даних. Код, отриманий після виконання
препроцесінгу, називається одиницею трансляції.
Компілятор створює файли форматy COFF (об'єктні файли .obj).

Національний університет «Львівська політехніка»


Компоновщик

(лінкер) – програма, яка генерує виконувчий файл шляхом зв'язування об'єктних файлів проекту.
Програми С зазвичай використовують бібліотечні функції, для яких вже існує об’єктний код, наприклад, sin(),
cos(), sqrt(), тощо. Під час компоновки об’єктний код програми об’єднується з об’єктним кодом бібліотечних
функцій, які використовуються в програмі, та стандартним кодом початкового завантаження. (Код початкового
завантаження забезпечує можливість виклику функції main() із середовища операційної системи) В результаті
створюється виконавчий файл.
Компоновщик створює виконавчі файли (.exe) або бібліотеки динамічного компонування (DLL).

Національний університет «Львівська політехніка»


Програма на С
Повний
Препроцесор текст Компілятор
програми
Файли, що
підключаються

Об єктний код
програми
Виконавчий
Компоновщик
файл

Стандартні
бібліотеки

Рисунок 2.2 – Створення виконавчого коду програми

Національний університет «Львівська політехніка»


IDE
(Integrated Development Environment – інтегроване середовище розробки) - поєднання текстового редактора і
компілятора. Розробка, компіляція і запуск своїх програми здійснюється безпосередньо в IDE. Інтегровані
середовища розробки спрощують процес складання програм, так як написання коду, компіляція і запуск
програм виконуються в одній програмі - IDE. Ще однією важливою особливістю IDE є те, що IDE допомагає
швидко знайти і виправити помилки компіляції.

Visual Studio
– це широко відоме повнофункціональне середовище розробки від компанії Microsoft, яка дозволяє працювати
з такими платформами, як Windows, Інтернет, хмарні сервіси і Android. Можливості IDE Visual Studio
дозволяють правильно і ефективно писати код, реорганізовувати, аналізувати і виправляти проблеми з кодом.
Офіційний сайт - https://visualstudio.microsoft.com.

Національний університет «Львівська політехніка»


Особливості Visual Studio:
• Свій компілятор - MSVC.
• Існують версії, що працюють на macOS і Windows.
• Підтримує такі мови: ASP.NET, Ajax, DHTML, Visual C ++, JavaScript, JScript, Visual Basic, Visual C #, Visual F
#, XAML і інші.
Плюси IDE Visual Studio:
• Безкоштовно розповсюджується Visual Studio Community, з достатнім набором можливостей.
• Платні версії можуть надаватися навчальним закладам і студентам безкоштовно.
• Зручна система розумного автодоповнення.
• Велика кількість налаштувань середовища розробки під «себе», завдяки вбудованим механізмам і доступним
доповненням.
Мінуси IDE Visual Studio:
• Версії Visual Studio Professional і Visual Studio Enterprise є платними.
• Досить вимоглива до заліза і ресурсів комп’ютера.
• Ряд розробників вважають її надлишкової для створення дрібних проектів.
• Досить маленька швидкість запуску проектів і програм.
• Не працює на Linux.

Національний університет «Львівська політехніка»


Лекція №5. 2.3. Алфавіт мови С.
2.4. Типи даних, змінні і константи.
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
Алфавіт мови С
– набір символів, які можна використовувати для запису програмного коду:
• великі та малі літери латинської абетки;
• арабські цифри;
• пробільні символи : пробіл, символи табуляції, символ переходу на наступний рядок тощо;
• спеціальні символи , . ; : ? ‘ ” ! | / \ ~ ( ) [ ] { } < > # % ^ & - + * =

Змінна
– іменована ділянка оперативної пам’яті, яка застосовується для зберігання даних під час
роботи програми. При оголошенні змінної для неї резервується певна ділянка пам’яті, розмір
якої залежить від конкретного типу змінної. Значення змінної – фактичне значення, яке
міститься у цій ділянці пам’яті.

Національний університет «Львівська політехніка»


Ідентифікатор
– це назва (ім’я), яку користувач надає об’єктам, наприклад змінним, константам, функціям.

Ідентифікатори записуються латинськими буквами, цифрами, знаком підкреслення.


Розпочинаються ідентифікатори з латинських літер або знаку підкреслення.

A, a, max, Max, MAX, _max, max1, max_znach – правильно записані ідентифікатори.

1max, max-znach, max znach, a..b – неправильно записані ідентифікатори

При написанні імені ідентифікатора враховується регістр.(MAX, Max, max- три різні ідентифікатори).

Національний університет «Львівська політехніка»


Ключові слова
це зарезервовані ідентифікатори, які використовуються для написання команд.

auto continue float interrupt short unsigned asm default for


long signed void break do far near sizeof volatile case double
goto pascal static while cdecl else huge switch struct char
enum if register typedef const extern int return union

Національний університет «Львівська політехніка»


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

/* Коментар до програми
може займати кілька рядків */

Окрім символів /* та */ для невеликих коментарів в один рядок в С використовують знак //.

// Текст після цього символу і до кінця рядка є коментарем.

Також, окрім пояснень, доволі часто коментарі використовують при налагодженні для тимчасового
“вимкнення” певного фрагмента програми.

Національний університет «Львівська політехніка»


2.4. Типи даних, змінні і константи.
Тип змінної
вид і розмір даних, які змінна може зберігати. Кожен тип даних зберігається й опрацьовується за певними
правилами. Слід зауважити, що розмір одного й того ж типу даних може відрізнятися на комп’ютерах різних
платформ, а також залежить від налагоджень компілятора.

Усі типи змінних мови С розподіляють на дві групи: основні типи та структуровані.

До основних (базових) типів можна віднести char, int, float та double, а також їхні варіанти з
специфікаторами short (короткий), long (довгий), signed (зі знаком) та unsigned (без
знаку).

Структуровані (похідні) типи базуються на основних, до них належать масиви будь-яких типів, вказівники,
структури, об’єднання, тощо.

Національний університет «Львівська політехніка»


Основні типи даних в С
Тип Назва Розмір, Діапазон Приклади Тип
байт можливих значень чисел
char знаковий символьний 1 від –128 до 127 ‘a’, ’9’, ‘\n’
unsigned char символьний беззнаковий 1 від 0 до 255
short короткий цілий 2 від –32 768 до 32 767 -5, 23, 0
unsigned short беззнаковий короткий цілий 2 від 0 до 65 535 0, 23
int цілий 4 від –2 147 483 648 -500, -2, 1150, 1000 ЦІЛІ
до 2 147 483 647
unsigned int беззнаковий цілий 4 від 0 до 4 294 967 295 1150, 1000
long довгий цілий 4 від –2 147 483 648 -500, -2, 1150, 1000
до 2 147 483 647
unsigned long беззнаковий довгий цілий 4 від 0 до 4 294 967 295 1150, 1000

Національний університет «Львівська політехніка»


Основні типи даних в С
Тип Назва Розмір, Діапазон Приклади Тип
байт можливих значень чисел
float дійсний 4 3,4E +/- 38 (7 знаків) -0.2,
10,
double дійсний подвійної точності 8 1,7E +/- 308 (15 знаків) ДІЙСНІ
2E7 (2*107),
long double довгий дійсний 8 (10) як і double -2.5E-2 (-2.5*10-2)

В С для відокремлення цілої частини числа від дійсної застосовується десяткова крапка.
Окрім звичної форми, дійсні константи можна записувати у експоненціальній формі.
Наприклад: 2.38е3 (яке дорівнює 2.38 * 103 = 2380), 3.61Е–4 (яке дорівнює 3.61 * 10–4 = 0.000361).
Число перед символом “е” називається мантисою, а після символу “е” – порядком, тобто замість основи 10
використовується літера “e”, після якої ставиться показник степеня, наприклад: 1е3 (1 * 103 = 1000), –2.7е–
4 (–2.7 * 10–4 = 0.00027).

Національний університет «Львівська політехніка»


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

Наприклад:

char ch;
unsigned int n;
int c; // Інтерпретується як signed int c
short а; // Інтерпретується як signed short int а
unsigned d; // Інтерпретується як unsigned int d
signed f; // Інтерпретується як signed int f
float u, v;
double z;

Національний університет «Львівська політехніка»


Ініціалізація змінних
це присвоєння їм початкового значення, які надалі може бути змінено. Це можна зробити, зокрема, при
оголошенні змінної.
int i, j = 1, q = 0xFFF;
ціла змінна i не є ініціалізована, j – ініціалізована значенням 1, q – ініціалізована шістнацятковим значенням
0xFFF (десяткове 4095).
При ініціалізації змінних їм можна присвоювати арифметичні вирази:
long megabute=1024*1024;
Ініціалізація дійсних і символьних змінних відбувається аналогічно:
char tol='а';
float x = 6.5E–3;

Для визначення розміру пам’яті, займаної змінною, існує операція sizeof(), яка повертає значення довжини
зазначеної змінної чи типу, наприклад:
a = sizeof(int); // a = 4
b = sizeof(long double); // b = 10

Національний університет «Львівська політехніка»


Константа
величина, яка не змінюється упродовж виконування програми. Для оголошення константи у програмі
використовується специфікатор const. При цьому зазначається тип константи, наприклад:

const float Pi = 3.14159;

У якості значення константи можна подавати константний вираз, який містить раніше оголошені константи та
змінні. Наприклад:

const float Pi2 = 2 * Pi, k = Pi / 180;

Константам при створенні слід неодмінно надавати значення, які в подальшому не можна буде змінювати.
У мові С розрізнюють чотири типи констант: цілі, дійсні, символьні константи та рядкові константи.

Національний університет «Львівська політехніка»


Для цілих констант слід зазначати один з цілих типів, наприклад:

const int B = 286;

Окрім десяткових цілих констант, в С можна використовувати вісімкові (перед ними обов’язково ставиться 0) і
шістнадцяткові (перед ними слід ставити 0х) цілі константи, наприклад:

const int С=01, Q=0555, W=0777; // вісімкові константи


const int k=0xFF, K=0x1A, v=0x23; // шістнадцяткові константи

Приклади значень цілих констант:


Десяткові Вісімкові Шістнадцяткові
Константи константи константи
16 020 0x10
127 0117 0x2B
240 0360 0xF0

Національний університет «Львівська політехніка»


Для дійсних констант слід зазначити тип (float, double чи long double) і записати їхні значення з десятковою
крапкою. Окрім звичної форми, дійсні константи можна записувати і в експоненціальній формі з рухомою
крапкою. Приклади оголошення дійсних констант:
const float fi = –1.84;
const double еps = 1е–4; // еквівалентно до еps =0.0001

Символьна константа – один символ в одинарних лапках , наприклад:


const char d='+', а='ц', с='9';

Рядкова константа (літерал) – послідовність символів, включаючи великі та малі літери кирилиці й латиниці, а
також цифри і пробіли, розташовані поміж подвійних лапок “ ”, наприклад:
const char S[10]="Львів";
const char *SS="НУ Львівська політехніка";

Національний університет «Львівська політехніка»


Лекція №6.
2.5. Консольний ввід-вивід з клавіатури.
2.6. Математичні функції.
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
При запуску програми на С автоматично відкриваються п'ять потоків, основними з яких є наступні:
• Стандартний потік введення stdin (клавіатура);
• Стандартний потік виводу stdout (екран монітора);
• Стандартний потік виводу повідомлень про помилки stderr (екран монітора).

Для управління введенням-виведенням за допомогою цих потоків використовуються ряд функцій:


• getchar (): введення з клавіатури одного символу;
• putchar (): вивід на консоль одного символу;
• gets (): введення одного рядка;
• puts (): вивід одного рядка на консоль;
• scanf (): введення з форматуванням даних;
• printf (): вивід з форматуванням даних.

Для того, щоб програма могла використовувати згадані функції, на початку програми слід вказати директиву
#include <stdio.h>.

Національний університет «Львівська політехніка»


Для форматованого виводу інформації в консольному режимі є функція printf().
Загальний вигляд функції є такий:

printf( формат, список_змінних );

Тут формат задає пояснювальний текст та вигляд значень змінних, імена яких задає параметр список_змінних.
Параметр список_змінних не є обов’язковим і являє собою послідовність розділених комами змінних, значення
яких виводяться.
Специфікатор формату задає вигляд виведеного результату.
Специфікатор формату для printf():
%[прапорці] [ширина] [.точність] [{ h | l | L }]тип

Національний університет «Львівська політехніка»


Специфікатори формату функції printf()
Модифікатор Значення
прапорці Прапорці :
– Здійснює вирівнювання по лівому краю.
+ Дані зі знаком друкуються зі знаком плюс, якщо вони додатні, і зі знаком мінус, якщо вони
від’ємні.
Пробіл (Space) Дані зі знаком друкуються з пробілом (без знаку), якщо вони додатні, і зі знаком
мінус, якщо вони від’ємні.
# Виводить спочатку 0 для форми %o, та 0х для форми %х. Для форм з плаваючою крапкою
гарантує вивід десяткової крапки.
0 Для чисельних форм заповняє поле нулями замість пробілів.
ширина Мінімальна ширина поля.
.точність Точність.
Для %e, %Е, %f, %F задається кількість цифр, які будуть виведені справа від десяткового числа.
Для %g, %G задається максимальне число значущих цифр.
Для %s задається максимальне число символів, які будуть надруковані.
Для цілих чисел задається мінімальне число цифр.

Національний університет «Львівська політехніка»


Специфікатори формату функції printf()
Модифікатор Значення
h Використовується для кодування значень short int та unsigned short int.
l Використовується для кодування значень long int та unsigned long int..
L Використовується для кодування значень long double.
тип Символи перетворення :
d – десяткове ціле число;
і – десяткове ціле число зі знаком;
u – десяткове ціле число без знаку;
o – беззнакова вісімкова форма (без лідируючого нуля);
x – беззнакова шістнадцяткова форма (без лідируючого 0х);
c – окремий символ;
s – рядок символів;
e – число з рухомою крапкою, експоненціальне представлення;
f – число з рухомою крапкою, представлення з поставленою крапкою;
g – використовується формат %e або %f, %e використовується, якщо показник експоненти менший ніж – 4
чи більше рівний заданої точності.
p – вказівник.

Національний університет «Львівська політехніка»


Керуючі та спеціальні символи
Символ Дія
\n Переводить курсор на початок наступного рядка.
\t Переводить курсор в наступну позицію табуляції.
\\ Зворотня дробова.
\’ Апостроф.

Національний університет «Львівська політехніка»


Лістинг 2.4: Вивід з клавіатури за допомогою функції printf()
#include <stdio.h>
int main(void)
{
int a = 50;
float b = -0.5;
double d = -5.5;
char c = 'A';
printf("%d\n", a); //50
printf("%+d\n", a); //+50
printf("%5d\n", a); // 50
printf("%o\n", a); //62
printf("%x\n", a); //32
printf("%#o\n", a); //062
printf("%#x\n", a); //0x32
printf("%#8x\n", a); // 0x32
printf("%f\n", b); //-0.500000
printf("%e\n", b); //-5.000000e-01
printf("%5.1f\n", b); //-0.5
printf("%05.1f\n", b); //-00.5
printf("%5.1e\n", b); //-5.0e-01
printf("%f\n", d); //-5.500000
printf("%5.1e\n", d); //-5.5e+00
printf("%c\n", c); //A
printf("%5c\n", c); // A
}
Національний університет «Львівська політехніка»
У загальному вигляді функція scanf() для введення значення однієї змінної виглядає як

scanf( формат, &змінна );

де: формат − рядок специфікаторів формату у подвійних лапках, змінна − це ім’я змінної, значення якої
вводиться. Знак & означає операцію отримання адреси змінної у пам’яті.
При виконанні функції scanf() відбувається таке: програма призупиняє роботу і очікує, доки користувач
набере на клавіатурі рядок символів та натисне клавішу <Enter>. Після натиснення клавіші <Enter> функція
scanf() перетворює введений рядок відповідно до специфікаторів формату на дані й записує їх до змінних,
адреси яких зазначено.
Функція scanf() корпорацією Microsoft визнана небезпечною, а тому, щоб використовувати її в нових версіях
Visual Studio необхідно на початку програми помістити таке оголошення:
#define _CRT_SECURE_NO_WARNINGS
Або користуватись безпечною версією цієї функції scanf_s().
Специфікатор формату для scanf:
%[*] [ширина] [{h | l | L}]тип

Національний університет «Львівська політехніка»


Специфікатор формату функції scanf()
Модифікатор Значення
* Ігнорує наступний ввід.
ширина Максимальна ширина поля.
h Для %hd, %hi значення будуть збережені за допомогою типу short int.
Для %ho, %hx, % hu значення будуть збережені за допомогою типу unsigned short int.
l Для %ld, %li значення будуть збережені за допомогою типу long.
Для %lo, %lx, %lu значення будуть збережені за допомогою типу unsigned long.
Для %le, %lf, %lg значення будуть збережені за допомогою типу double.
L Значення будуть збережені за допомогою типу long double.
тип Символи перетворення :
d – інтерпретує ввід як десяткове ціле число зі знаком;
і – інтерпретує ввід як десяткове ціле число зі знаком;
u – інтерпретує ввід як десяткове ціле число без знаку;
o – інтерпретує ввід як вісімкове ціле число зі знаком;
x – інтерпретує ввід як шістнадцяткове ціле число зі знаком;
c – інтерпретує ввід як окремий символ;
s – інтерпретує ввід як рядок символів;
e, f, g – інтерпретує ввід як число з рухомою крапкою (float);
p – інтерпретує ввід як вказівник (адресу).
Національний університет «Львівська політехніка»
Лістинг 2.5: Ввід з клавіатури за допомогою функції scanf()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
int a1, a2;
float b1, b2;
printf("input a:"); //input a:-5
scanf("%d", &a1);
printf("a=%d\n", a1); //a=-5
printf("input a and b:"); //input a and b:45 -45
scanf("%d%d", &a1, &a2);
printf("a=%d b=%d\n", a1, a2); //a=45 b=-45
printf("input a:"); //input a:34.6
scanf("%f", &b1);
printf("a=%f\n", b1); //a=34.599998
printf("input a and b:");
scanf("%f%f", &b1, &b2); //input a and b:-2.4 9
printf("a=%f b=%f\n", b1, b2); //a=-2.400000 b=9.000000
char c;
printf("input c:"); //input c:f
while (getchar() != '\n');
c = getchar();
printf("c=%c\n", c); //c=f
}

Національний університет «Львівська політехніка»


2.6. Математичні функції.
Стандартна бібліотека мови програмування С містить заголовний файл math.h, в якому розміщені функції
для виконання простих математичних операцій.
Більшість функцій в якості параметра використовують числа з рухомою комою.
Усі функції, які приймають або повертають значення кута, працюють з радіанами.
Більшість функцій мають декілька варіантів, в залежності від типів даних:
float cosf(float x);
double cos(double x);
long double cosl(long double x);
Всі ці функції обчислюють косинус, але з різними типами чисел з рухомою комою.
Функції, що закінчуються на f, працюють з float, на l - з long double.

Національний університет «Львівська політехніка»


Ось деякі математичні функції:
double cos(double x); – косинус числа x.
double sin(double x); – синус числа x.
double tan(double x); – тангенс числа x.
double pow(double x, double y); – піднесення числа x до степеня y.
double sqrt(double x); – корінь квадратний з числа x.

double exp(double x); – 𝑒 𝑥 .


double log(double x); - натуральний логарифм числа x.
double log10(double x); - десятковий логарифм числа x.
double fabs(double x); - абсолютне значення числа x.
У файлі stdlib.h є функція abs, яка дозволяє отримати абсолютне значення цілого числа x:
int abs(int x);
double fmod(double x, double y); - залишок від ділення x на y.
double round(double x); - заокруглює до найближчого цілого, але тип залишається double.
long int lround(double x); - заокруглює до найближчого цілого, повертає число типу long int.

Національний університет «Львівська політехніка»


Лекція №7.
2.7. Операнди, вирази і операції.
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
Вираз
Комбінація знаків операцій і операндів, результатом якої є певне значення, називається виразом.
Знаки операцій визначають дії, які повинні бути виконані над операндами.
Кожен операнд у виразі може бути виразом.
Значення виразу залежить від розташування знаків операцій і круглих дужок у виразі, а також від пріоритету
виконання операцій.
При обчисленні виразів тип кожного операнда може бути перетворений до іншого типу.

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

Національний університет «Львівська політехніка»


Унарні операції в мові С
Знак операції Операція
- арифметичне заперечення (заперечення і доповнення)
~ порозрядне (побітове) логічне заперечення (доповнення)
! логічне заперечення
* розадресація (непряма адресація)
& обчислення адреси
+ унарний плюс
++ збільшення (інкремент)
-- зменшення (декремент)
sizeof розмір змінної чи типу в байтах

Національний університет «Львівська політехніка»


Унарні операції виконуються справа наліво:

Операція операнд;

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

Постфіксна форма:
int a=1, b=2, c;
c=a*b++; // c=2

Префіксна форма:
int a=1, b=2, c;
c=a*++b; // c=3

Національний університет «Львівська політехніка»


Бінарні операції в мові С
Знак операції Операція Група операцій
* Множення
/ Ділення Мультиплікативні
% Залишок від ділення
+ Додавання
Адитивні
- Віднімання
<< Зсув вліво
Операції зсуву
>> Зсув вправо
< Менше
<= Менше або рівно
> Більше Операції
>= Більше або рівно відношення

== Рівно
!= Не рівно
Національний університет «Львівська політехніка»
Бінарні операції в мові С
Знак операції Операція Група операцій
& Порозрядне І
| Порозрядне АБО Порозрядні (побітові) операції
^ Порозрядне виключне АБО
&& Логічне І
Логічні операції
|| Логічне АБО
, Послідовне обчислення Послідовного обчислення
= Присвоєння
*= Множення з присвоєнням
/= Ділення з присвоєнням
%= Залишок від ділення з присвоєнням
-= Віднімання з присвоєнням
+= Додавання з присвоєнням Операції присвоєння
<<= Зсув вліво з присвоєнням
>>= Зсув вправо з присвоєнням
&= Порозрядне І з присвоєнням
|= Порозрядне АБО з присвоєнням
^= Порозрядне виключне АБО з присвоєнням
Національний університет «Львівська політехніка»
Порозрядні операції
(виконуються над окремими розрядами)

a b a&b a|b ~a a^b


0 0 0 0 1 0
0 1 0 1 1 1
1 0 0 1 0 1
1 1 1 1 0 0

Бінарні операції виконуються зліва направо:

Операнд1 операція операнд2;

При записі виразів слід пам'ятати, що символи (*), (&), (!), (+) можуть позначати унарні або бінарні операції.

Національний університет «Львівська політехніка»


Умовна (тернарна) операція

операнд1? операнд2: операнд3;

Якщо операнд1 має ненульове значення (правдиве, true), то обчислюється операнд2 і його значення є
результатом операції. Якщо операнд1 дорівнює 0, то обчислюється операнд3 і його значення є результатом
операції.

А = (D <= B)? B: D;
Змінній А присвоюється максимальне значення змінних D і B.

Національний університет «Львівська політехніка»


Пріоритети операцій
Пріоритет Знак операції Типи операцій Порядок виконання
2 () [] . -> Вираз Зліва направо
1 - ~ ! * & ++ -- sizeof приведення типів Унарні Справа наліво
3 */% Мультиплікативні
4 +- Адитивні
5 << >> Зсуву
6 < > <= >= Відношення
7 == != Відношення (рівність)
8 & Порозрядне І Зліва направо
9 ^ Порозрядне виключне АБО
10 | Порозрядне АБО
11 && Логічне І
12 || Логічне АБО
13 ?: Умовна операція

Національний університет «Львівська політехніка»


Пріоритети операцій
Пріоритет Знак операції Типи операцій Порядок виконання
=
*=
/=
%=
+=
14 -= Присвоєння Справа наліво
&=
|=
>>=
<<=
^=
15 , Послідовне обчислення Зліва направо

Національний університет «Львівська політехніка»


Лекція №8. 2.8. Зведення типів.
2.9. Оголошення власних типів.
2.10. Директиви препроцесора.
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
Перетворення (зведення) типів
виконуються, якщо операнди, які входять до виразу, мають різні типи. Зведення типів здійснюється автоматично
за правилом: менш точний тип зводиться до більш точного. Наприклад, якщо в арифметичному виразі беруть
участь коротке ціле (short) і ціле (int), результат зводиться до int, якщо ціле і дійсне – до дійсного.

Типи даних від менш точного до більш точного (беззнакові вважаються точнішими за знакові):

char
short
int
long
float
double
long double

Національний університет «Львівська політехніка»


Приклад:

unsigned char ch;


double ft, sd;
unsigned long n;
int i;
sd = ft * (i + ch / n);

При виконанні цього присвоювання правила зведення використовуватимуться у такий спосіб. Операнд ch
зводиться до unsigned int. Після чого він зводиться до типу unsigned long. За цим самим правилом i зводиться до
unsigned long, і результат операцій у круглих дужках матиме тип unsigned long. Тоді він зводиться до типу double, і
результат всього виразу матиме тип double.

Національний університет «Львівська політехніка»


Правила зведення типів.
1) Результат операції ділення буде цілим числом, якщо ділене і дільник є цілими, і дійсним числом, якщо один з
операндів є дійсного типу. Наприклад, результатом 2/3 буде 0, а результатом 2.0/3 чи 2./3 – 0.666(6):
int m = 2, n = 3;
float a = m / n; // в результаті a = 0
2) При присвоюванні остаточний результат зводиться до типу змінної, яка стоїть ліворуч “=”; при цьому тип може як
підвищуватися, так і знижуватись. Якщо тип лівого операнда є менш точним, аніж тип результату виразу, розміщеного
праворуч оператора присвоювання, то можлива втрата точності чи то взагалі неправильний результат, наприклад:
float a = 2.8, b = 1.7;
int c = a * b;
Внаслідок виконання цих команд змінна с набуде значення 4, а не 4.76, тобто дійсну частину числа буде втрачено.
3) Ще один приклад дає неправильний результат:
double a = 300, b = 200;
shоrt c = a * b;
Після виконання цих команд змінна с набуде значення –5536 замість очікуваного 60 000. Це пов’язано з тим, що змінна
типу shоrt може зберігати значення не більше за 32767.

Національний університет «Львівська політехніка»


Явне зведення типів .
Операцію явного зведення типів слід застосовувати, щоб уникнути помилок, подібних до вищенаведених прикладів. Ця
операція має вигляд:

(тип) арифметичний_вираз;
тобто перед ім’ям змінної у дужках зазначається тип, до якого її слід перетворити.
Наприклад, вищерозглянутий приклад буде обчислювати правильний результат, якщо записати його у такий спосіб:
int m = 2, n = 3;
double a = (double) m/n; // У результаті a=0.66(6)
Явне зведення типу є джерелом можливих помилок, оскільки вся відповідальність за його результат покладається на
програміста. Операцію явного зведення типів слід використовувати обережно, лише якщо іншого способу здобуття
коректного результату немає. Наприклад, у виразі z/(x+25), де z, x – цілі змінні, дробова частина втрачається.
Запобігти цьому можна, якщо перетворити чисельник чи знаменник до дійсного типу. Для цього слід використати один
з трьох способів:
1) приписати десяткову крапку до числа 25. Вираз набуде вигляду z/(x+25.);
2) домножити чисельник чи знаменник ліворуч на 1.0. Вираз набуде вигляду 1.0*z/(x+25);
3) явно зазначити тип результату (double)z/(x+25).

Національний університет «Львівська політехніка»


2.9. Оголошення власних типів.
Окрім оголошень змінних різних типів, в С є можливість оголошувати власні типи. Це можна здійснювати,
використовуючи для оголошення типу ключове слово typedef. При оголошенні ключовим словом typedef
створюється новий тип даних, який надалі може бути використаний у межах видимості цього типу для
оголошення змінних, наприклад:
typedef int vector[40];
vector x; // Змінна x – масив 40-ка цілих чисел
Що є еквівалентним до оголошення:
int x [40];
Використовуючи ключове слово typedef, можна оголошувати які завгодно типи, включаючи вказівники, функції
чи масиви.
Оголошення типу у загальному вигляді має синтаксис

typedef тип нове_ім’я [розмірність];


Тут квадратні дужки є елементом синтаксису. Розмірність може бути відсутньою.

Національний університет «Львівська політехніка»


Приклади оголошення власних типів:
typedef unsigned char byte; /* Перейменування типу “беззнаковий char” на byte */
typedef char MyS[50]; /* Перейменування типу “рядок з 50-ти символів” на MyS */

Розглянемо ще один приклад оголошення без перейменовування:


char s[20]; // Слово s – рядок з 20-ти символів,
char mas[10][20]; // mas – масив з 10 слів.
Перейменування типу “рядок з 20-ти символів” на тип “слово”:
typedef char slovo [20];
slovo s; // Оголошення змінної s типу slovo
slovo mas[10]; // і масиву з 10-ти слів

Взагалі typedef є просто засобом спрощення запису операторів оголошення змінних.


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

Національний університет «Львівська політехніка»


2.10. Директиви препроцесора.
Препроцесор – це комп’ютерна програма, яка приймає дані на вході і видає дані, призначені для входження
іншої програми, приміром, такої як компілятор.
У мови програмування С та C++ вбудовано підтримку препроцесора.
Директиви препроцесора є інструкціями, записаними в тексті С-програми, котрі виконуються до трансляції
програми. Директиви препроцесора дозволяють змінювати текст програми, наприклад, замінювати певні
лексеми в тексті, вставляти текст з іншого файлу, забороняти трансляцію частини тексту тощо. Всі директиви
препроцесора розпочинаються зі знаку #. Після директив препроцесора крапка з комою не ставиться.
У програмуванні термін “директива” (вказівка) за використанням є схожий до терміну “команда”, оскільки так
само використовується для опису певних конструкцій мови програмування (тобто вказівок компіляторові чи
асемблерові стосовно особливостей опрацювання при компіляції).

Національний університет «Львівська політехніка»


Формат директиви препроцесора:
#ім’я_директиви лексеми_препроцесора
Тут до лексем препроцесора належать символьні константи, імена файлів, які долучаються, ідентифікатори,
знаки операцій, розділові знаки, рядкові константи (рядки) й будь-які символи, відмінні від пробілу.
У препроцесорах мови С визначені такі директиви:
#define – визначення макросу чи препроцесорного ідентифікатора;
#include – долучення тексту з файла;
#undef – скасування визначення макросу чи препроцесорного ідентифікатора;
#if – перевірка умови-виразу;
#ifdef, #ifndef – перевірка визначеності (невизначеності) ідентифікатора;
#else – початок альтернативної гілки для #if;
#endif – завершення умовної директиви #if;
#elif – складена директива #else/#if;
#line – змінення номера наступного нижче рядка;
#error – формування тексту повідомлення про помилку при трансляції;
#pragma – дії, передбачені реалізацією; # – порожня директива.

Національний університет «Львівська політехніка»


Директива #define
має кілька модифікацій. Вони передбачають визначення макросів та препроцесорних ідентифікаторів, кожному
з яких ставиться у відповідність певна послідовність символів. У подальшому тексті програми препроцесорні
ідентифікатори замінюються на раніш визначені послідовності символів. Ідентифікатори, які замінюють текстові
чи то числові константи, називають іменованими константами. Ідентифікатори, які замінюють фрагменти
програм, називають макровизначеннями, причому макровизначення можуть мати аргументи.
Директива #define має дві синтаксичні форми:

#define ідентифікатор текст


#define ідентифікатор (список параметрів) текст
Ця директива замінює всі подальші входження ідентифікатора на текст. Такий процес називається
макропідстановкою. Текст може бути яким завгодно фрагментом програми на С, а також може бути відсутнім. В
останньому разі всі екземпляри ідентифікатора вилучаються з програми.

Національний університет «Львівська політехніка»


Щоб задати константу n зі значенням 10 слід написати директиву
#define n 10
Ця директива є аналогом такої інструкції
const int n = 10;
Відмінності полягають у тому, що:
1) при використанні константи пам’ять під константну змінну виділяється, а при використанні директиви – ні;
2) при використанні директиви не виконується перевірка типу значення, що може спричинитися до помилок у
програмі;
3) тип значення n при використанні директиви визначається автоматично, тобто, якщо у програмі є суттєво, щоб
n мала тип unsigned short, зробити це за допомогою директиви неможливо.
Отже, використання директиви #define у С без особливої на те потреби є небажаним.

Директива #undef
використовується для відміни чинності поточного визначення директиви #define для зазначеного
ідентифікатора. Не є помилкою використання директиви #undef для ідентифікатора, який не було визначено
директивою #define. Синтаксис цієї директиви є таким:

#undef ідентифікатор
Національний університет «Львівська політехніка»
Директива #include
долучає до тексту програми вміст зазначеного файла. Ця директива широко використовується для долучення
до програм так званих заголовних файлів. Існують три форми цієї директиви:

#include <ім’я_заголовного_файла>
#include "ім’я_заголовного_файла"
#include ідентифікатор_макроса

Відмінність між першими двома формами директиви полягає у методі пошуку препроцесором файла, що
долучається. Якщо ім’я файлу записано у кутових дужках, то послідовність пошуку препроцесором заданого
файлу у каталогах визначається заданими каталогами включення (include directories). Якщо ім’я файлу
зазначено зі шляхом, то препроцесор ніде більше цей файл не буде шукати.
Третя форма директиви #include припускає наявність макросу, який визначає файл, що долучається.
Ідентифікатор макроса не повинен починатися з символів “<” та “"”.

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua
Лекції №9,10. Тема №3.
ОБЧИСЛЮВАЛЬНІ АЛГОРИТМИ
ТА ЇХ РЕАЛІЗАЦІЯ НА МОВІ С
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
3.1. Оператори в мові С
Оператори визначають дії, що мають здійснюватися комп'ютером під час виконання програми.

Всі оператори мови С можуть бути умовно розділені на наступні категорії:


• Умовні оператори, до яких відносяться оператор умови if і оператор вибору switch;
• Оператори циклу (for, while, do while);
• Оператори керування програмним потоком (break, continue, return, goto);
• Інші оператори (оператор "вираз", порожній оператор).

Оператори в програмі можуть об'єднуватися в складені оператори за допомогою фігурних дужок.


Будь-який оператор у програмі може бути позначений міткою, що складається з імені і наступної за ним двокрапки.
Всі оператори мови С, крім складених операторів, закінчуються крапкою з комою ";".

Національний університет «Львівська політехніка»


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

Оператор-вираз – будь-який вираз, що завершується крапкою з комою. Дія такого оператора полягає в
обчисленні виразу.
x++;
a*=b;
a=cos(b * 5);

Складений оператор – група операторів, обмежених фігурними дужками. Примітка – кожен оператор
завершується символом ; після закриваючої фігурної дужки крапку з комою не ставлять.
{ [оголошення]
:
оператор; [оператор];
:
}
Національний університет «Львівська політехніка»
3.2. Алгоритми з розгалуженням
і умовний оператор if
Умовний оператор мови С if має дві форми: скорочену та повну.
Скорочена форма має вигляд: умова
так
if (умова) оператор;

оператор;
Якщо умова є правдива (істинна, ненульове значення, значення true),
то виконується оператор чи група операторів в операторних дужках {}, ні
інакше відбудеться перехід на наступний оператор.

Рисунок 3.1 – Блок-схема скороченої форми


умовного оператора if
Національний університет «Львівська політехніка»
Повна форма оператора if:

if (умова) оператор1;
else оператор2; умова
так ні

Якщо умова є правдива (істинна, ненульове значення, значення


оператор1; оператор2;
true), то виконується оператор1, інакше (неправда, нульове
значення, значення false) – виконується оператор2, після чого
відбудеться перехід на наступний оператор. Зауважимо, що
виконується лише один з операторів, а не обидва.
В обох формах після завершення виконання оператора if
управління передається наступному оператору програми. Рисунок 3.2 – Блок-схема повної форми
умовного оператора if

Національний університет «Львівська політехніка»


Завдання: обчислити квадратний корінь заданого дійсного числа.
початок
Квадратний корінь існує тільки для додатніх чисел, тому варто робити перевірку, чи
число більше або рівне нулю, щоб отримати коректний результат.
ввід х
Лістинг 2.1: Обчислення середнього арифметичного трьох чисел
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> x >= 0
так
#include <math.h>
int main(void)
{
res x
double res, x;
printf("Enter x:");
ні
scanf("%lf", &x);
if (x >= 0) вивід
res
{
res = sqrt(x);
printf("Square root is %f\n", res);
кінець
}
return 0;
} Рисунок 3.3 – Блок-схема обчислення
квадратного кореня заданого числа
Національний університет «Львівська політехніка»
Завдання: обчислити квадратний корінь заданого дійсного числа.

початок

Щоб отримати
результат і для
ввід х
від’ємних
значень, будемо
обчислювати
корінь так x >= 0 ні
квадратний, для
чисел менших
за нуль, з числа res x res   x
з протилежним
знаком (щоб
отримати
додатнє вивід
значення). res

кінець

Національний університет «Львівська політехніка» Рисунок 3.4 – Блок-схема обчислення квадратного кореня заданого числа
Лістинг 3.2: Оператор розгалуження if-else. Обчислення квадратного кореня
заданого числа
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
int main(void)
{
double res, x;
printf("Enter x:");
scanf("%lf", &x);
if (x >= 0)
{
res = sqrt(x);
}
else
{
res = sqrt(-x);
}
printf("Square root is %f\n", res);
return 0;
}
Національний університет «Львівська політехніка»
Завдання: дано два числа А і В. Знайти більше з них.
початок
Позначимо найбільше як max, отже,
потрібно скласти схему алгоритму
обчислення X = max (A, B). Ввід
У даному прикладі можливі два АіВ
варіанти відповіді: A або B, тобто
реалізується повна альтернатива.
Вхідні дані можуть мати будь-які ні А >= В так
значення.
Вибір варіанту буде проведено за
Х=В Х=А
результатами перевірки умови: A>B?
Для однозначності рішення
вважаємо, що при A = B, X = A.
У загальному випадку розташування Вивід Х
знаку рівності визначається
постановкою самої задачі.

кінець

Рисунок 3.5 – Блок-схема алгоритму для знаходження більшого з двох чисел


Національний університет «Львівська політехніка»
Лістинг 3.3. : Алгоритми з розгалуженням. Алгоритм пошуку більшого
з двох чисел

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int A, B, X;
printf("Enter A and B:");
scanf("%d%d", &A, &B);
if (A >= B) X = A;
else X = B;
printf("X=%d", X);
}

Національний університет «Львівська політехніка»


початок
Завдання: дано три числа А, В і С. початок

Знайти найбільше з них.


Ввід Ввід
А, В, С А, В, С

Позначимо найбільше як
max, отже, потрібно ні А >= В так
ні А >= В так
скласти схему алгоритму
обчислення X = max (A,
Y=В Y=А
B, С). Спочатку можна С >= В ні ні А >= С
так так
знайти максимальне з
двох значень А і В
Х=В Х=С Х=А
(Y = max (A, B)), а потім ні Y >= C так
знайти більше з Y і С:
X = max (A, B). Отже, X=C X=Y
Вивід Х
X = max (max (A, B), С).
У даному прикладі
можливі три варіанти Вивід Х
кінець
відповіді: A або B, або С.

кінець

Рисунок 3.6 – Блок-схеми алгоритму для знаходження більшого з трьох чисел


Національний університет «Львівська політехніка»
Лістинг 3.4. : Алгоритми з розгалуженням. Алгоритм пошуку більшого з
трьох чисел

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int A, B, C, Y, X;
printf("Enter A, B, C:");
scanf("%d%d%d", &A, &B, &C);
if (A >= B) Y = A;
else Y = B;
if (Y >= C) X = Y;
else X = C;
printf("X=%d", X);
}

Національний університет «Львівська політехніка»


Замість двох операторів if можна використати один, але складніший:

if (A >= B)
{
if (A >= C) X = A;
else X = C;
}
else {
if (C >= B) X = C;
else X = B;
}

У випадку вкладеного оператора if доцільно використовувати фігурні дужки, щоб згрупувати оператори у
складений оператор і, таким чином, явно вказати ваші наміри. Якщо дужки відсутні, компілятор співставляє
найближчий if, що відповідає else.

Національний університет «Львівська політехніка»


Нам необхідно
Завдання: дано три числа А, В і С. Перевірити чи усі вони додатні.
перевірити таку умову:
(A > 0) і (В > 0) і (С > 0).
початок початок
Це можна зробити,
використавши три
Ввід Ввід
символи рішення, а А, В, С А, В, С
можна використати
один символ рішення,
А>0 (A > 0) і (В > 0) і (С > 0)
записавши всередині так ні так

складену умову.
ні B>0
ні так Вивід Вивід
До цього часу усі «не усі додатні» «усі додатні»
умови, які ми ні C>0
так
розглядали були кінець
простими. Вивід Вивід
«не усі додатні» «усі додатні»
Складена умова – дві
або більше простих
кінець
умови, з’єднаних
знаком логічної
операції (АБО, І, НЕ). Рисунок 3.7 – Блок-схеми алгоритму перевірки, чи додатні три числа

Національний університет «Львівська політехніка»


Лістинг 3.5. : Алгоритми з розгалуженням. Алгоритм перевірки, чи
додатні три числа

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int A, B, C;
printf("Enter A, B, C:");
scanf("%d%d%d", &A, &B, &C);
if (A >= 0)
if (B >= 0)
if (C >= 0) printf("all positive\n");
else printf("not all positive\n");
else printf("not all positive\n");
else printf("not all positive\n");
}

Національний університет «Львівська політехніка»


Замість складного вкладеного if можна використати простіший варіант з складеною умовою:

if (A >= 0 && B >= 0 && C >= 0)


printf("all positive\n");
else printf("not all positive\n");

Національний університет «Львівська політехніка»


Завдання: дано числа A і B. Менше з них початок

замінити сумою, а більше добутком.


Ввід
А, В

У даному прикладі необхідно зберегти


значення А і В перед обчисленнями у X=A
проміжних змінних X і Y. Y=B

A >=B
ні так

A = X+Y A = X*Y
B = X*Y B = X+Y

Вивід
А, В

кінець

Рисунок 3.8 – Блок-схеми алгоритму заміни меншого з чисел А і В


Національний університет «Львівська політехніка»
сумою і більшого добутком
Лістинг 3.6. : Алгоритми з розгалуженням. Алгоритм заміни меншого з двох
чисел сумою і більшого добутком
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int A, B, X, Y;
printf("Enter A, B:");
scanf("%d%d", &A, &B);
X = A;
Y = B;
if (A >= B)
{
A = X * Y;
B = X + Y;
}
else
{
A = X + Y;
B = X * Y;
}
printf("A=%d, B=%d", A, B);
}
Національний університет «Львівська політехніка»
Завдання: задано квадратне рівняння з початок

коефіцієнтами A, B, C. Знайти корені рівняння.


ввід
А, В, С

Квадратним називають рівняння виду:


D = B*B – 4*A*C

Для обчислення коренів квадратного


так D >= 0 і A != 0
рівняння спочатку обчислюють
дискримінант:
D  B  4* A*C
2  B D
x1 
2* A ні
тоді корені квадратного рівняння рівні:

B D  B D
x1,2  x2 
2* A 2* A
Якщо дискримінант від’ємний, або А=0, вивід
вивід
«коренів
то говорять, що рівняння коренів не має. x 1 і x2 немає»

кінець

Рисунок 3.9 – Блок-схема алгоритму обчислення коренів


Національний університет «Львівська політехніка» квадратного рівняння
Лістинг 3.7. : Умовний оператор if. Алгоритм розв’язку квадратного рівняння
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
int main(void)
{
double A, B, C, D, xl, x2;
printf("Enter A, B and C: ");
scanf("%lf%lf%lf", &A, &B, &C);
D = B * B - 4 * A * C;
if (D >= 0 && A != 0)
{
xl = (-B + sqrt(D)) / (2 * A);
x2 = (-B - sqrt(D)) / (2 * A);
printf("\nxl = %f x2 = %f", xl, x2);
}
else
printf("\nCan't solve equation");
return 0;
}
Національний університет «Львівська політехніка»
3.3. Оператор switch
Оператор switch призначений для організації вибору з безлічі різних варіантів.
Формат оператора вибору варіантів switch:

switch (вираз)
{ [оголошення]
case значення_мітка_1 : {послідовність_операторів_1;}
............
case значення_мітка_n : {послідовність_операторів_n}
[ default: послідовність операторів; ]
}

Національний університет «Львівська політехніка»


Виконання оператора switch:
Спочатку обчислюється вираз у дужках.
Далі значення виразу порівнюється зі значеннями міток після ключових слів case.
Якщо значення виразу збіглося зі значенням якоїсь мітки, то виконується відповідна послідовність операторів,
позначена цією міткою і записана після двокрапки, допоки не зустрінеться оператор break.
Якщо значення виразу не збіглося з жодною міткою, то виконуються оператори, які слідують за ключовим
словом default. Мітка default є необов'язковою конструкцією оператора switch, на що вказують квадратні дужки
[].
Щоб здійснити вихід з оператора switch необхідно після послідовності операторів поставити оператор break.
Якщо оператор break є відсутній наприкінці операторів відповідного case, то буде почергово виконано всі
оператори до наступного break чи то до кінця switch для всіх гілок case незалежно від значення їхніх міток.

Національний університет «Львівська політехніка»


default вираз мітка_1
default вираз мітка_1

мітка_n мітка_2
послідовність мітка_2 послідовність
операторів операторів 1 послідовність послідовність послідовність
послідовність
операторів n; операторів 2; операторів 1;
операторів;
break; break; break;
мітка_n
послідовність
операторів 2

...

послідовність
операторів n

Рисунок 3.11 – Блок-схема оператора switch з


Рисунок 3.10 – Блок-схема оператора switch використанням оператора break.

Національний університет «Львівська політехніка»


Лістинг 3.8. : Приклад використання оператора switch
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int A, B, X, i;
printf("Enter A, B:");
scanf("%d%d", &A, &B);
printf("1 +\n2 -\n3 *\n4 /");
printf("\nEnter i:");
scanf("%d", &i);
switch (i)
{
case 1: X = A + B; break;
case 2: X = A - B; break;
case 3: X = A * B; break;
case 4: X = A / B; break;
default: X = 0; break;
}
printf("X=%d", X);
}
Національний університет «Львівська політехніка»
Для завчасного переривання повторювань операторів циклу будь-якого типу в тілі циклу можна застосовувати

оператор break, який перериває виконання оператора, в якому розміщено цей break, і передає керування на

наступний оператор. Зауважимо, що всередині вкладених операторів do-while, for, while чи switch оператор
break завершує лише самий внутрішній з цих операторів, тому break неможна використовувати для виходу з
декількох вкладених циклів.

Формат оператора наступний:


break;

Для переходу до наступної ітерації циклу можна застосовувати оператор continue. Цей оператор, подібно до

оператора break, використовується лише всередині операторів циклу for, while, do-while, але, на відміну від
останнього, оператор continue не припиняє подальше виконання циклу, а переходить до чергової ітерації того
циклу, у тілі якого він виявився. Оператор continue, так само як і оператор break, перериває найглибший з циклів.

Формат оператора наступний:


continue;

Національний університет «Львівська політехніка»


Оператор goto (перейти до) дозволяє передавати керування у будь-яку точку коду (програми), котру позначено

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

Формат цього оператора наступний:


goto мітка;
...
мітка: оператор;

Використання оператора безумовного переходу goto в практиці програмування на мові С наполегливо не


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

Оператор return повертає результат роботи функції:

Формат оператора наступний:


return;
return значення;
Національний університет «Львівська політехніка»
Лекції №11,12.
3.4. Циклічні алгоритми і оператори циклу
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
початок
Завдання: порахувати суму чисел від 1 до 100.
Встановлення
i=1
Тілом циклу у нас буде операція s=0
початкових значень
i - параметр циклу
накопичення суми,
параметр циклу буде мати
Робоча частина
початкове значення рівне s = s+i циклу
одиниці, (тіло циклу)
так
і при кожного повторенні циклу
він буде збільшуватись ще на Зміна параметра
i = i+1
циклу
одиницю.
Умовою продовження циклу
Умова
буде перевірка чи параметр не i <= 100 продовження
більший за число сто. циклу
ні

вивід s

кінець

Рисунок 3.12- Блок-схема обчислення суми


Національний університет «Львівська політехніка» чисел від 1 до 100
Обчислювальний процес називається циклічним, якщо він неодноразово повторюється, поки
виконується певна задана умова. Блок повторюваних операторів називають тілом циклу.
Існують три різновиди операторів циклу:
• оператор циклу for;
• оператор циклу з передумовою while;
• оператор циклу з післяумовою do-while.

Всі оператори циклу неодмінно містять такі складові частини:


• присвоювання початкових значень (ініціалізація);
• умова продовження циклу;
• тіло циклу;
• зміна параметра циклу.

Національний університет «Львівська політехніка»


Синтаксис циклу з передумовою while:

while (умова)
{ тіло циклу }; умова

так ні
Послідовність операторів (тіло циклу) виконується, поки
умова є правдива (істинна, true, має ненульове значення),
тіло циклу
а вихід з циклу здійснюється, коли умова стане хибною
(false, матиме нульове значення).
Якщо умова є хибною при входженні до циклу, то
послідовність операторів не виконуватиметься жодного
разу, а керування передаватиметься до наступного Рисунок 3.13 – Блок-схема циклу while
оператора програми.
Лістинг 3.9. : Обчислення суми чисел від 1 до 100
за допомогою оператора while

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i, s;
i = 1;
s = 0;
while (i <= 100)
{
s = s + i;
i = i + 1;
}
printf("s=%d", s);
}

Національний університет «Львівська політехніка»


Синтаксис циклу з післяумовою do-while:

do
{ тіло циклу
тіло циклу;
так
}
while (умова); умова

Послідовність операторів (тіло циклу) виконується один чи кілька разів, ні


поки умова не стане хибною (false чи дорівнюватиме нулю).
Якщо умова є правдива, то оператори тіла циклу виконуються
повторно. Рисунок 3.14 – Блок-схема циклу do-while

Оператор циклу do-while використовується в тих випадках, коли є


потреба виконати тіло циклу хоча б одноразово, оскільки перевірка
умови здійснюється після виконання операторів.
Якщо тіло циклу складається з одного оператора, то операторні дужки {}
не є обов’язкові.
Лістинг 3.10. : Обчислення суми чисел від 1 до 100 за допомогою
оператора do-while

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i, s;
i = 1;
s = 0;
do
{
s = s + i;
i = i + 1;
} while (i <= 100);
printf("s=%d", s);
}

Національний університет «Львівська політехніка»


Оператор циклу з параметром for зазвичай
використовується, коли є заздалегідь відома кількість ініціалізація;
ініціалізація умова;
повторювань, або коли умова продовження виконання
модифікації
циклу записується коротким виразом.
так
Синтаксис оператора:
тіло циклу
for (ініціалізація; умова; модифікації) умова

тіло циклу; ні
так

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

Рисунок 3.15 – Блок-схема циклу for


Виконання оператора for:
На початку виконання одноразово у блоці ініціалізації задаються початкові значення змінних (параметрів), які
керують циклом.
Потім перевіряється умова і, якщо вона правдива (true чи має ненульове значення), то виконується тіло циклу
(чи група операторів в операторних дужках {}).
Модифікації змінюють параметри циклу і, в разі правдивості умови, виконання циклу триває.
Якщо умова хибна (false чи дорівнює нулю), відбувається вихід із циклу і керування передається на оператор,
який слідує за оператором for.
Суттєвим є те, що перевірка умови виконується на початку циклу.
Кожне повторення (крок) циклу називається ітерацією.
В операторі можливі конструкції, коли є відсутній той чи інший блок: ініціалізація може бути відсутня, якщо
початкове значення задати попередньо; умова – якщо припускається, що умова є завжди правдива, тобто слід
неодмінно виконувати тіло циклу, поки не зустрінеться оператор break; а модифікації – якщо зміна параметра
циклу здійснюється в тілі циклу чи взагалі це є непотрібним. Тоді сам вираз блоку пропускається, але крапка з
комою (;) неодмінно має залишитись. Можливою є наявність порожнього оператора (оператор є відсутній) у тілі
циклу.

Національний університет «Львівська політехніка»


Лістинг 3.11. : Обчислення суми чисел від 1 до 100 за допомогою оператора for

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i, s;
for (s = 0, i = 1; i <= 100; i++) s += i;
printf("s=%d", s);
}
Цей оператор можна прочитати так: “виконати команду s += i 100 разів (для значень і від 1 до 100 включно, де і на
кожній ітерації збільшується на 1)”.
Порядок виконання цього циклу є таким:
1) присвоюються початкові значення (s=0, i=1);
2) перевіряється умова (i<=100);
3) якщо умова є правдива (true), виконується тіло циклу: до суми, обчисленої на попередній ітерації, додається нове
число;
4) параметр циклу збільшується на 1. Далі повертаємось до пункту 2. Якщо умова у пункті 2 стане хибною (false), то
відбудеться вихід із циклу.
Національний університет «Львівська політехніка»
Оператори while, do-while і for можуть завчасно завершитись при виконуванні операторів break,
goto, return (вихід з поточної функції) усередині тіла циклу.
З допомогою оператора continue можна достроково завершувати чергову ітерацію, оминаючи решта
операторів циклу.

Якщо в тілі циклу for присутній оператор continue, то його виконання приводить до повторного обчислення
модифікацій і нової ітерації циклу.
2 3 4 початок
x x x
Завдання: необхідно обчислити: y  x      , 0  x  1, x  
2 3 4
ввід x
i
x
Обчислення припинити при r   , де r  ,а  рівне 0,0001.
i=2
i q=x
y=x
Дана задача це приклад циклу з невідомим числом ітерацій,
адже вихід з циклу виконується за додатковою умовою, q = q*x
r = q/i
яка залежить від значення вхідної змінної x. y = y+r
так

i = i+1

r>=0.0001

ні

вивід y

кінець

Національний університет «Львівська політехніка» Рисунок 3.16 – Блок-схема алгоритму обчислення у


Лістинг 3.12. : Обчислення y
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i;
double x, y, r, q;
printf("Enter x:");
scanf("%lf", &x);
i = 2;
q = x;
y = x;
do
{
q = q * x;
r = q / i;
y = y + r;
i++;
} while (r >= 0.0001);
printf("y=%f", y);
}

Національний університет «Львівська політехніка»


3 5 7 початок
x x x
Завдання: необхідно обчислити: y  x      , 0  x  1, x  
3! 5! 7! ввід x
i
x
Обчислення припинити при r   , де r  , а  рівне 0,0001. i=2
i! r=x
y=x
Як і попередня задача, це приклад циклу з невідомим числом
ітерацій.
x*x
r (r)*
i*(i1)
так y = y+r

i = i+2

|r|>=0.0001

ні

вивід y

кінець

Національний університет «Львівська політехніка» Рисунок 3.17 – Блок-схема алгоритму обчислення у


Лістинг 3.13. : Обчислення y
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
int main()
{
int i;
double x, y, r, q;
printf("Enter x:");
scanf("%lf", &x);
i = 2;
r = x;
y = x;
do
{
r = -r * (x * x / (i * (i + 1)));
y = y + r;
i = i + 2;
} while (fabs(r) >= 0.0001);
printf("y=%f", y);
}

Національний університет «Львівська політехніка»


початок початок початок
Завдання: необхідно обчислити:
50
1 1 1 1
y   2  1 2  2  2 i=1 y=0 y=0
i 1 i 2 3 50 y=0

Дана задача цикл за і від i від 1 до 50


1 до 50 з кроком 1
це вже y = y+1/(i*i)
з кроком 1
приклад так так

циклу з
y = y+1/(i*i)
параметром, i = i+1 y = y+1/(i*i)
тут значення ні
параметра
i <= 50
(змінна і) кінець
змінюється циклу за і
ні
від одиниці вивід y
вивід y
до
п’ятдесяти. вивід y
кінець
кінець

кінець

Національний університет «Львівська політехніка»


Рисунок 3.18 – Блок-схеми алгоритму обчислення у
Лістинг 3.14. : Обчислення y
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
int main()
{
int i;
double y;
i = 1;
y = 0;
do
{
y = y + 1 / ((double)i * i);
i++;
} while (i <= 50);
printf("y=%f", y);
}

При використанні циклу for отримаємо наступне:


for (i = 1, y = 0; i <= 50; i++)
y = y + 1 / ((double)i * i);

Національний університет «Львівська політехніка»


Цикли можуть бути вкладеними один в одного.
При використанні вкладених циклів треба складати програму в такий спосіб, щоб внутрішній цикл повністю
вкладався в тіло зовнішнього циклу, тобто цикли не повинні перетинатися.
Своєю чергою, внутрішній цикл може містити власні вкладені цикли.
Імена параметрів зовнішнього та внутрішнього циклів мають бути різними.
Припускаються такі конструкції:
for(k=1; k<=10; k++)
{...
for(i=1; i<=10; i++)
{...
for(m=1; m<=10; m++)
{ ...
}
}
}

Національний університет «Львівська політехніка»


Завдання: необхідно обчислити інтеграл f(x) на відрізку від a до b. f(x) = sin(x).
y y
B B

y = f(x) y = f(x)

A A

a b x a=x0 x1 x2 b=x3 x

Рисунок 3.19 – Алгоритм обчислення інтеграла f(x)


Для знаходження інтегралу f(x) на відрізку від a до b нам необхідно знайти площу фігури, як показано на рисунку
3.19. Спочатку розіб’ємо відрізок від a до b на певну кількість однакових частин, і будемо шукати площу кожної
частини окремо. Сума усіх площ і буде прощею фігури. Площу кожної частинки обчислюємо як площу трапеції за
формулою: S = h *(A+B)/2, де h – ширина відрізка, А і B сторони трапеції.
S = h*(f(x0)+f(x1))/2+h*(f(x1)+f(x2))/2+h*(f(x2)+f(x3))/2= h*((f(a)+f(b))/2+f(x1)+f(x2));
Де h = (b-a)/N, N – кількість відрізків, чим їх буде більше, тим точніший отримаємо результат.
Національний університет «Львівська політехніка»
Лістинг 3.15.: Алгоритм обчислення інтеграла sin(x) початок

#define _CRT_SECURE_NO_WARNINGS ввід


#include <stdio.h> a, b, N
#include <math.h>
int main(void) h = (b – a)/2
{ S1 = (sin(a)+sin(b))/2
S2 = 0
double a, b, x, h, S1, S2, S;
int N;
x від (a + h)
printf("Enter a, b and N: ");
поки x < b
scanf("%lf%lf%d", &a, &b, &N); з кроком x = x + h
/*-- Trapetzium algorithm: --*/ так
h = (b - a) / N;
S1 = (sin(a) + sin(b)) / 2; S2 = S2 + sin(x)
S2 = 0;
for (x = a + h; x < b; x = x + h) ні
S2 = S2 + sin(x);
S = h * (S1 + S2); S = h*(S1 + S2)
/*---------------------------*/
printf("\nFor a = %f, b = %f, N = %d, S = %f", a, b, N, S);
return 0; вивід
} S

Рисунок 3.20 – Блок-схема алгоритму обчислення інтеграла sin(x) кінець


Національний університет «Львівська політехніка»
початок
Завдання: знайти величину найбільшого числа в
послідовності з n чисел. (Додаткова вимога:
ввід n
масивів не використовувати).
ввід а
На початку роботи
вважаємо найбільшим
(максимальним) перший max = a
i=2
елемент послідовності, а
далі порівнюємо i від 2 до n
максимальне з іншими з кроком 1
елементами почергово. ні
так
Якщо знаходимо
елемент більший за ввід а

найбільший, то з цього
моменту найбільшим
a > max
вважаємо вже його. вивід
так max

max = a ні
кінець

Національний університет «Львівська політехніка» Рисунок 3.21 – Блок-схема алгоритму знаходження найбільшого числа послідовності
Лістинг 3.16.: Пошук найбільшого числа в послідовності
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
int n, a, i, max;
printf("Enter n:");
scanf("%d", &n);
printf("\nEnter a:");
scanf("%d", &a);
max = a;
for (i = 2; i <= n; i++)
{
printf("\nEnter a:");
scanf("%d", &a);
if (a > max)
max = a;
}
printf("\nmax=%d", max);
return 0;
}

Національний університет «Львівська політехніка»


Лекції №13,14.
3.5. Функції в мові програмування С.
3.6. Передача параметрів функції main.
3.7. Класи пам'яті. Локальні і глобальні змінні.
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
3.5. Функції в мові програмування С
Функція – це незалежна іменована частина програми, яка може багаторазово викликатися з інших частин
програми, маніпулювати даними та повертати результати.
Кожна функція має власне ім’я, за яким здійснюють її виклик.
З використанням функцій в мові С пов'язані три поняття:
• визначення функції (опис дій, які виконує функція),
• оголошення функції (задання форми звернення до функції)
• і виклик функції.
Визначення функції задає тип значення, що повертається, ім'я функції, типи і кількість формальних
параметрів, а також оголошення змінних і оператори, звані тілом функції, що визначають дію функції.
У мові С немає вимоги, щоб визначення функції обов'язково передувало її виклику. Визначення функцій, які
використовуються, можуть слідувати за визначенням функції main, перед нею, або перебувати в іншому файлі.
Однак для того, щоб компілятор міг здійснити перевірку відповідності типів переданих фактичних параметрів
типам формальних параметрів до виклику функції потрібно помістити оголошення (прототип) функції.
Оголошення функції має такий же вигляд, що і визначення функції, з тією лише різницею, що тіло функції
відсутнє, і імена формальних параметрів теж можуть бути опущені.

Національний університет «Львівська політехніка»


Оголошення функції складається з прототипу (чи заголовка) і має форму

тип_результату ім’я_функції( перелік формальних параметрів з вказанням типу );

Наприклад оголошення функції обчислення обчислення інтеграла sin(x) може мати вигляд:

double MyTrapezSquare(double a, double b, int N);

де MyTrapezSquare – ім’я функції;


a, b і N – формальні вхідні параметри, які при виклику функції набувають фактичних значень;
double – тип результату, який має повернути функція. Результат повертається оператором return.
Функція не може повертати, як результат, функцію чи масив, проте вона може повертати вказівник на функцію чи
масив.

Національний університет «Львівська політехніка»


Визначення функції, окрім заголовку, містить тіло функції (дії, які виконує функція):
тип_результату ім’я_функції( перелік параметрів )
{ тіло функції }

Наприклад, визначення функції MyTrapezSquare може бути таким:

double MyTrapezSquare(double a, double b, int N)


{
double x, h, S1, S2;
h = (b - a) / N;
S1 = (sin(a) + sin(b)) / 2;
S2 = 0;
for (x = a + h; x < b; x = x + h)
S2 = S2 + sin(x);
return h * (S1 + S2);
}
початок
початок
підпрограми
обчислення інтегралу
Якщо функції
sin(x)
ввід
виконують певні
a, b, N
обчислення й дії, h = (b – a)/2
які не потребують S1 = (sin(a)+sin(b))/2
S2 = 0 підпрограма обчислення
повернення інтегралу sin(x)
конкретних
x від (a + h) S = MyTrapezSquare(a, b, N);
результатів, то їх поки x < b
тип вказують як з кроком x = x + h
вивід
тип void (тобто так S
порожній, без
S2 = S2 + sin(x)
типу). У таких кінець
функціях оператор ні
return може бути
відсутнім чи
S = h*(S1 + S2)
записуватись без
значення, яке
повертається. кінець
підпрограми
обчислення інтегралу
sin(x)
Рисунок 3.22 – Блок-схема алгоритму обчислення інтеграла sin(x) з використанням
підпрограми для основних обчислень
Лістинг 3.16.: Визначення (опис) та виклик функції
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
double MyTrapezSquare(double a, double b, int N)
{
double x, h, S1, S2;
h = (b - a) / N;
S1 = (sin(a) + sin(b)) / 2;
S2 = 0;
for (x = a + h; x < b; x = x + h)
S2 = S2 + sin(x);
return h * (S1 + S2);
}
int main(void)
{
double a, b, S; int N;
printf("Enter a, b and N: ");
scanf("%lf%lf%d", &a, &b, &N);
S = MyTrapezSquare(a, b, N);
printf("\nFor a = %f, b = %f, N = %d, S = %f", a, b, N, S);
return 0;
}
Національний університет «Львівська політехніка»
Інструкція для виклику функції складається з імені функції і ( ). Тут круглі дужки означають операцію
виклику функції. Виклик функції MyTrapezSquare() можна здійснити таким чином

MyTrapezSquare(a, b, N);
чи
MyTrapezSquare(1.1, 4.2, 10);

Програма спочатку виділяє місце для розміщення параметрів, а після цього копіює фактичні значення параметрів у
відповідні виділені комірки пам'яті.
Тоді програма починає виконувати оператори функції, починаючи з першого оператора.
Коли програма дійде до виконання оператора return чи досягне закриваючої дужки } блоку функції, виконання
функції завершується і управління повертається у точку виклику функції.
Коли виконання функції завершується не через оператор return, а через досягнення закриваючої дужки } блоку
функції, то у цьому випадку значення функції для викликаючого оператора є невизначеним.
Тому функція, яке не є типу void, мусить мати оператор return.
#include <stdio.h>
int MySum(int M, int N) Лістинг 3.17.: Опис функції, звертання
{ до функції, фактичні параметри
int i, Sum;
i = M; Sum = 0;
while (i <= N)
{
Sum = Sum + i;
i++;
}
return Sum;
}
int main(void)
{
int i, N, Sum;
i = 137; N = 895;
Sum = MySum(i, N);
Sum = MySum(5, 555);
Sum = MySum(Sum, Sum + 1);
Sum = MySum(N, N + 3) + 4;
MySum(11, 123);
printf("Sum = %d", Sum);
return 0;
}
Національний університет «Львівська політехніка»
Лістинг 3.18.: Функція типу void (без return)

#include <stdio.h> Результати роботи програми:


void MyPrint(int M)
{
printf("\n\n------------"); ------------
------------
printf("\n------------");
Parameter is
printf("\nParameter is\n equal to %d", M);
equal to 7
printf("\n------------");
------------
printf("\n------------"); ------------
}
int main(void) ------------
{ ------------
MyPrint(7); Parameter is
MyPrint(5); equal to 5
return 0; ------------
} ------------

Національний університет «Львівська політехніка»


#define _CRT_SECURE_NO_WARNINGS Лістинг 3.19.: Прототип функції
#include <stdio.h>
#include <math.h>
double MyTrapezSquare(double a, double b, int N);
int main(void)
{
double a, b, S; int N;
printf("Enter a, b and N: ");
scanf("%lf%lf%d", &a, &b, &N);
S = MyTrapezSquare(a, b, N);
printf("\nFor a = %f, b = %f, N = %d, S = %f", a, b, N, S);
return 0;
}
double MyTrapezSquare(double a, double b, int N)
{
double x, h, S1, S2;
h = (b - a) / N;
S1 = (sin(a) + sin(b)) / 2;
S2 = 0;
for (x = a + h; x < b; x = x + h)
S2 = S2 + sin(x);
return h * (S1 + S2);
}

Національний університет «Львівська політехніка»


/* ----- File main.c ----- */
#define _CRT_SECURE_NO_WARNINGS
Лістинг 3.20.: #include "MyTrapezSquare.h".
#include <stdio.h>
#include "MyTrapezSquare.h"
int main(void)
{
double a, b, S; int N;
printf("Enter a, b and N: ");
scanf("%lf%lf%d", &a, &b, &N);
S = MyTrapezSquare(a, b, N);
printf("\nFor a = %f, b = %f, N = %d, S = %f", a, b, N, S);
return 0;
}
/* File MyTrapezSquare.h */
double MyTrapezSquare(double a, double b, int N);
/* File MyTrapezSquare.cpp */
#include <math.h>
double MyTrapezSquare(double a, double b, int N)
{
double x, h, S1, S2;
h = (b - a) / N;
S1 = (sin(a) + sin(b)) / 2;
S2 = 0;
for (x = a + h; x < b; x = x + h)
S2 = S2 + sin(x);
return h * (S1 + S2);
}
Національний університет «Львівська політехніка»
Лістинг 3.21.: Функція MyTrapezSquare, обчислення із заданою точністю
#define _CRT_SECURE_NO_WARNINGS double TrapezSquare(double a, double b, double eps)
#include <stdio.h> {
#include <math.h> double res1, res2;
double MyTrapezSquare(double a, double b, int N) int N;
{ N = 2;
double x, h, S1, S2; res1 = MyTrapezSquare(a, b, N);
N = N * 2;
h = (b - a) / N; res2 = MyTrapezSquare(a, b, N);
S1 = (sin(a) + sin(b)) / 2; while (fabs(res2 - res1) >= eps)
S2 = 0; {
for (x = a + h; x < b; x = x + h) res1 = res2;
S2 = S2 + sin(x); N = N * 2;
return h * (S1 + S2); res2 = MyTrapezSquare(a, b, N);
} }
return res2;
}
int main(void)
{
double a, b, eps, S;
printf("Enter a, b and eps: ");
scanf("%lf%lf%lf", &a, &b, &eps);
S = TrapezSquare(a, b, eps);
printf("\nFor a = %f, b = %f,
eps = %f, S = %f", a, b, eps, S);
return 0;
Національний університет «Львівська політехніка» }
Лістинг 3.22.: Використання прототипів функцій дозволяє уникнути строго
порядку опису функцій
#define _CRT_SECURE_NO_WARNINGS double TrapezSquare(double a, double b, double eps)
#include <stdio.h> {
#include <math.h> double res1, res2;
// Function prototypes: int N;
double MyTrapezSquare(double a, double b, int N); N = 2;
double TrapezSquare(double a, double b, double eps); res1 = MyTrapezSquare(a, b, N);
int main(void) N = N * 2;
{ res2 = MyTrapezSquare(a, b, N);
double a, b, eps, S; while (fabs(res2 - res1) >= eps) {
printf("Enter a, b and eps: "); res1 = res2;
scanf("%lf%lf%lf", &a, &b, &eps); N = N * 2;
/*-- Testing a, b, eps: --*/ res2 = MyTrapezSquare(a, b, N);
if ((b - a) <= 0 || eps <= 0) { }
printf("Bad input values"); return res2;
return -1; }
} double MyTrapezSquare(double a, double b, int N)
S = TrapezSquare(a, b, eps); {
printf("\nFor a = %f, b = %f, double x, h, S1, S2;
eps = %f, S = %f", a, b, eps, S); h = (b - a) / N;
return 0; S1 = (sin(a) + sin(b)) / 2;
} S2 = 0;
for (x = a + h; x < b; x = x + h)
S2 = S2 + sin(x);
return h * (S1 + S2);
Національний університет «Львівська політехніка» }
Лістинг 3.23.: Окрема функція для обчислення підінтегральної функції
#define _CRT_SECURE_NO_WARNINGS double TrapezSquare(double a, double b, double eps)
#include <stdio.h> {
#include <math.h> double res1, res2; int N; N = 2;
// Function prototypes: res1 = MyTrapezSquare(a, b, N);
double MyTrapezSquare(double a, double b, int N); N = N * 2;
double TrapezSquare(double a, double b, double eps); res2 = MyTrapezSquare(a, b, N);
double MyFun(double x); while (fabs(res2 - res1) >= eps) {
int main(void) res1 = res2;
{ N = N * 2;
double a, b, eps, S; res2 = MyTrapezSquare(a, b, N);
printf("Enter a, b and eps: "); }
scanf("%lf%lf%lf", &a, &b, &eps); return res2;
/*-- Testing a, b, eps: --*/ }
if ((b - a) <= 0 || eps < 0) double MyTrapezSquare(double a, double b, int N)
{ {
printf("Bad input values"); double x, h, S1, S2;
return -1; h = (b - a) / N;
} S1 = (MyFun(a) + MyFun(b)) / 2; S2 = 0;
S = TrapezSquare(a, b, eps); for (x = a + h; x < b; x = x + h)
printf("\nFor a = %f, b = %f, S2 = S2 + MyFun(x);
eps = %f, S = %f", a, b, eps, S); return h * (S1 + S2);
return 0; }
} double MyFun(double x)
{
return sin(x);
Національний університет «Львівська політехніка» }
Будь-яка функція в програмі на мові С може бути викликана рекурсивно, тобто вона може викликати саму
себе.
Компілятор допускає будь-яке число рекурсивних викликів, але це число обмежується ресурсом пам'яті
комп'ютера і при дуже великому числі рекурсивних викликів може статися переповнення стеку.
Класичним прикладом рекурсивної функції є обчислення факторіала. Для того щоб визначити факторіал числа
n, слід помножити n на факторіал числа (n-1):
n! = n*(n-1)! // Це так зване рекурсивне співвідношення.
Відомо також, що 0!=1 й 1!=1, тобто умовою зупинки рекурсії буде: якщо n=0 чи n=1, то факторіал дорівнює 1.
Отже, рекурсивна функція обчислення факторіала числа n виглядатиме так:
long fact (long n)
{ if (n= =0 || n= =1) return 1;
return (n*fact(n-1)); }
Якщо у функції main() зустрінеться виклик функції fact(3), при виконуванні цієї функції буде викликано функцію
fact(2), яка своєю чергою викличе fact(1). Для останньої спрацює умова зупинки рекурсії (n= =1) і у функцію
fact(2) буде повернуто значення 1 та обчислено значення 2 (2! = 2), яке своєю чергою буде повернуто до
функції fact(3) і буде використано для обчислення 3!. Отже, програма має 3 рекурсивні виклики.

Національний університет «Львівська політехніка»


3.6. Передача параметрів функції main
Функція main, з якої починається виконання С-програми, може бути визначена з параметрами, які передаються
з зовнішнього оточення, наприклад, з командного рядка.
У зовнішньому оточенні всі дані представляються у вигляді рядків символів.
Для передачі цих рядків у функцію main використовуються два параметри, перший параметр служить для
передачі числа переданих рядків, другий для передачі самих рядків.
Загальноприйняті імена цих параметрів argc і argv.
Параметр argc має тип int, його значення формується з аналізу командного рядка і дорівнює кількості слів у
командному рядку, включаючи і ім'я програми (під словом розуміється будь-який текст, що не містить символу
пробіл).
Параметр argv це масив вказівників на рядки, кожен з яких містить одне слово з командного рядка.
Функція main може мати і третій параметр, який прийнято називати argp, і який служить для передачі у функцію
main параметрів операційної системи (середовища) в якій виконується С-програма.
Заголовок функції main має вигляд:
int main (int argc, char *argv[], char *argp[])

Національний університет «Львівська політехніка»


Якщо, наприклад, командний рядок С-програми має вигляд:
A:\>cprog working 'C program' 1
то аргументи argc, argv, argp представляються в пам'яті як показано в схемі
argc [ 4 ]
argv [ ]--> [ ]--> [A:\cprog.exe\0]
[ ]--> [working\0]
[ ]--> [C program\0]
[ ]--> [1\0]
[NULL]
argp [ ]--> [ ]--> [path=A:\;C:\\0]
[ ]--> [lib=D:\LIB\0]
[ ]--> [include=D:\INCLUDE\0]
[ ]--> [conspec=C:\COMMAND.COM\]
[NULL]
Операційна система підтримує передачу значень для параметрів argc, argv, argp, а на користувачу лежить
відповідальність за передачу і використання фактичних аргументів функції main.

Національний університет «Львівська політехніка»


3.7. Класи пам'яті.
Локальні і глобальні змінні.
С-програма являє собою визначення функції main, яка для виконання необхідних дій викликає інші функції. Компілятор
мови С дозволяє також розбити програму на декілька окремих частин (вихідних файлів), відтранслювати кожну частину
окремо, і потім об'єднати всі частини в один виконавчий файл.
У мові С всі змінні повинні бути оголошені до їх використання.
Опис змінної може виконуватися у формі оголошення або визначення.
Оголошення інформує компілятор про тип змінної і класи пам’яті, а визначення містить, крім цього, вказівку
компілятору про виділення пам’яті відповідно до типу змінної. Змінна може бути оголошена багаторазово, а визначена
тУ C більшість оголошень є одночасно і визначеннями., оскільки оголошення тільки описує властивості змінної, а
визначення зв’язує її з конкретною областю пам’яті. ільки в одному місці програми.
Клас пам’яті, яка виділяється, визначається специфікатором класу пам'яті, і визначає час життя і область видимості
змінної, пов'язані з поняттям блоку програми.
У мові С блоком вважається послідовність оголошень, визначень і операторів, взята в фігурні дужки. Існують два види
блоків - складений оператор і визначення функції (тіло функції). Блоки можуть включати в себе складені оператори,
але не визначення функцій.

Національний університет «Львівська політехніка»


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

Область видимості – це частина тексту програми, в якій може бути використаний даний об'єкт.
Об'єкт вважається видимим в блоці або в вихідному файлі, якщо в цьому блоці або файлі відомі ім'я і тип об'єкту.
Об'єкт може бути видимим в межах блоку, вихідного файлу або у всіх вихідних файлах, що утворюють програму.
Це залежить від того, на якому рівні оголошений об'єкт: на локальному, тобто всередині деякого блоку, або на
глобальному, тобто поза усіма блоками.
Якщо змінна оголошена всередині блоку (локальна змінна), то вона видима в цьому блоці, і у всіх внутрішніх
блоках. Якщо змінна оголошена поза усіма блоками (глобальна змінна), то вона видима від точки оголошення до
кінця даного вихідного файлу.

Національний університет «Львівська політехніка»


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

auto,
register,
static
або extern,
а якщо він не вказаний, то мається на увазі клас пам'яті auto.

Об'єкти класів auto і register мають тимчасовий час життя.


Специфікатори static і extern визначають об'єкти з постійним часом життя.

Клас пам’яті вказується першим при оголошенні змінної:

[клас пам’яті] тип ім’я_змінної [ініціалізація];

Національний університет «Львівська політехніка»


Змінна з класом пам'яті auto має тимчасовий час життя і видима тільки в блоці, в якому оголошена.
Пам'ять для такої змінної виділяється при вході в блок і звільняється при виході з блоку.
При повторному вході в блок цій змінній може бути виділена інша ділянка пам'яті.
Змінна з класом пам'яті auto автоматично не ініціалізується. Вона повинна бути проініціалізована явно при
оголошенні шляхом присвоєння їй початкового значення.
Значення неініціалізованої змінної з класом пам'яті auto вважається невизначеним.

Специфікатор класу пам'яті register вказує компілятору виділити пам'ять для змінної в регістрі процесора,
якщо це можливо.
Використання регістрової пам'яті зазвичай призводить до скорочення часу доступу до змінної.
Змінна, оголошена з класом пам'яті register, має ту ж область видимості, що і змінна auto.
Число регістрів, які можна використовувати для значень змінних, обмежена можливостями комп'ютера, і в тому
випадку, якщо компілятор не має в розпорядженні вільних регістрів, то змінній виділяється пам'ять як для класу
auto.
Клас пам'яті register може бути зазначений тільки для змінних з типом int або вказівників з розміром, рівним
розміру int.

Національний університет «Львівська політехніка»


Локальні змінні (оголошені всередині блоку) з специфікатором класу пам'яті static, забезпечують можливість
зберегти значення змінної при виході з блоку і використовувати його при повторному вході в блок.
Така змінна має постійний час життя і область видимості всередині блоку, в якому вона оголошена. На відміну
від змінних з класом auto, пам'ять для яких виділяється у стеку, для змінних з класом static пам'ять виділяється
в сегменті даних, і тому їх значення зберігається при виході з блоку.
Змінні класу пам'яті static можуть бути проініціалізовані константним виразом. Якщо явної ініціалізації немає, то
такий змінної присвоюється нульове значення.

Локальна змінна (оголошена всередині блоку) з класом пам'яті extern, є посиланням на глобальну змінну з
тим же самим ім'ям, визначену в одному з вихідних файлів програми. Мета такого оголошення полягає в тому,
щоб зробити визначення глобальної змінної видимим всередині блоку.
В оголошеннях з класом пам'яті extern не допускається ініціалізація, так як ці оголошення посилаються на вже
існуючі та визначені раніше змінні. Змінна, на яку робиться посилання за допомогою специфікатора extern,
може бути визначена тільки один раз в одному з вихідних файлів програми.
Імена формальних параметрів, оголошені в списку параметрів функції, видимі тільки від точки оголошення
параметра до кінця тіла функції.
В мові С допускається (але не рекомендується) оголошення локальної змінної з тим же ім’ям, що і глобальна
змінна. В такому випадку в блоці, де оголошена така змінна, використовується локальна змінна, а не
глобальна.
Національний університет «Львівська політехніка»
При оголошенні глобальних змінних (поза усіма блоками) може бути використаний специфікатор
класу пам'яті static чи extern, а так само можна оголошувати змінні без вказівки класу пам'яті.
Класи пам'яті auto і register для глобальних змінних неприпустимі.
Оголошення глобальних змінних - це або визначення змінних, або посилання на визначення, зроблені в іншому
місці програми. Якщо при оголошенні глобальної змінної клас пам’яті не вказаний, і явної ініціалізації немає, то
змінна набуде нульового значення.
Глобальна змінна видима в межах залишку вихідного файлу, в якому вона визначена. Вище свого опису і в
інших вихідних файлах ця змінна невидима (якщо тільки вона не оголошена з класом extern).
Глобальна змінна може бути визначена тільки один раз у межах своєї області видимості. В іншому вихідному
файлі може бути оголошена інша глобальна змінна з таким же ім'ям, конфлікту при цьому не виникає, оскільки
кожна з цих змінних буде видимою тільки у своєму вихідному файлі.
Специфікатор класу пам'яті extern для глобальних змінних використовується, як і для локального оголошення,
в якості посилання на змінну, оголошену в іншому місці програми, тобто для розширення області видимості
змінної. При такому оголошенні область видимості змінної розширюється до кінця вихідного файлу, в якому
зроблено оголошення.
Специфікатор класу пам'яті static для глобальних змінних вказує, що з інших файлів програми не можна
отримати доступ до цієї змінної, навіть з використанням специфікатора extern. Область видимості такої змінної
завжди обмежена файлом.

Національний університет «Львівська політехніка»


Усі функції є глобальними. Вони можуть бути оголошені з класом пам'яті static або extern.
Правила визначення області видимості для функцій відрізняються від правил видимості для змінних і
полягають у наступному:

1. Функція, оголошена як static, видима в межах того файлу, в якому вона визначена.
Кожна функція може викликати іншу функцію з класом пам'яті static з свого вихідного файлу, але не може
викликати функцію визначену з класом static в іншому вихідному файлі.
Різні функції з класом пам'яті static можуть мати однакові імена , якщо вони визначені в різних вихідних файлах,
і це не веде до конфлікту.

2. Функція, оголошена з класом пам'яті extern, видима в межах всіх вихідних файлів програми. Будь-яка
функція може викликати функції з класом пам'яті extern.

3. Якщо в оголошенні функції відсутній специфікатор класу пам'яті, то за замовчуванням приймається клас
extern.

Національний університет «Львівська політехніка»


Лістинг 3.14.: Локальні змінні
#include <stdio.h>
void MySwap(int* M, int* N)
{
int temp;
temp = *M;
*M = *N;
*N = temp;
}
int main(void)
{
int x, y;
x = 1; y = 5;
MySwap(&x, &y);
printf("x = %d y = %d", x, y);
return 0;
}

Результати роботи програми:

x=5y=1

Національний університет «Львівська політехніка»


Лістинг 3.15.: Глобальні змінні
#include <stdio.h> Результати роботи програми:
/* Global variables */
int M, N; M=5N=1
void MySwap(void)
{
int temp;
temp = M;
M = N;
N = temp;
}
int main(void)
{
M = 1; N = 5;

MySwap();

printf("M = %d N = %d", M, N);


return 0;
}

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua
Лекції №15,16. Тема №4.
МАСИВИ ТА ОБРОБКА ЧИСЛОВИХ ДАНИХ
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
4.1. Одновимірні масиви
та доступ до їх елементів
Масив у програмуванні – це впорядкована сукупність однотипних елементів.
Масиви широко застосовуються для зберігання і опрацювання однорідної інформації, приміром таблиць, векторів,
матриць, коефіцієнтів рівнянь тощо.
Кожен елемент масиву однозначно можна визначити за ім’ям масиву та індексами.
Ім'я масиву (ідентифікатор) вибирають за тими самими правилами, що й для змінних.
Індекси визначають місцезнаходження елемента в масиві.
Кількість індексів визначає вимірність масиву.
Індексами можуть бути лише змінні, константи чи вирази цілого типу.
Значення індексів записують після імені масиву в квадратних дужках.
При оголошенні масивів у квадратних дужках зазначається кількість елементів, а нумерація елементів завжди
розпочинається з нуля.

Національний університет «Львівська політехніка»


Відмінності масиву від звичайних змінних:
• спільне ім’я для всіх значень;
• доступ до конкретного значення за його номером (індексом);
• можливість опрацювання у циклі.

Одновимірний масив (вектор) оголошується у програмі в такий спосіб:

тип_даних ім’я_масиву [розмір_масиву];


Тип_даних задає тип елементів масиву.
Елементами масиву не можуть бути функції й елементи типу void. Розмір_масиву у квадратних дужках задає
кількість елементів масиву.
На відміну від інших мов, у С не перевіряється вихід за межі масиву, тому, щоб уникнути помилок у програмі, слід
стежити за розмірністю оголошених масивів.

Значення розмір_масиву при оголошенні масиву може бути не вказано в таких випадках:
• при оголошенні масив ініціалізується;
• масив оголошено як формальний параметр функції (докладніше див. далі);
• масив оголошено як посилання на масив, явно визначений в іншому модулі.
Національний університет «Львівська політехніка»
Використовуючи ім’я масиву та індекс, можна звертатися до елементів масиву:

ім’я_масиву [значення_індексу]
Значення індексів перебувають в діапазоні від нуля до величини, на одиницю меншу за розмір масиву, визначений
при його оголошенні, оскільки в С нумерація індексів розпочинається з нуля.
Наприклад,

int A[10];
оголошує масив з ім’ям A, який містить 10 цілих чисел; при цьому виділяє і закріплює за цим масивом пам’ять для
усіх 10-ти елементів відповідного типу (int – 4 байти), тобто 40 байтів.
Отже, при оголошенні масиву виділяється пам’ять, потрібна для розташування усіх його елементів.
Елементи масиву з першого до останнього запам’ятовуються у послідовно зростаючих адресах пам’яті.
Поміж елементами масиву в пам’яті проміжків немає.
Елементи масиву запам’ятовуються один за одним поелементно.
Зауважте на звертання у програмі до елементів масиву: А[0] – перший елемент, А[1] – другий, А[9] – останній.
Так, для розміщення елементів одновимірного масиву int В[5] виділяється по 4 байти під кожен з 5-ти елементів
масиву – тобто усього 20 байт:

B[0] B[1] B[2] B[3] B[4]


Національний університет «Львівська політехніка»
Для отримання доступу до i-го елемента масиву В, можна написати В[i], де і – змінна циклу, яка може набувати
значення від 0 до 4. При цьому величина i перемножується на розмір типу int і являє собою адресу i-го елемента
масиву В від його початку, після чого здійснюється вибір елемента масиву В за сформованою адресою.

Ось кілька прикладів оголошення масивів:


char N[20];
int grades[125];
float mass[30];
double v[1500];
Перший з масивів N містить 20 символів. Звертання до елементів масиву може бути таким: N[0], N[1], ..., N[19].
Другий масив grades містить 125 цілих чисел. Звертання до елементів та-кого масиву може бути таким: grades[0],
grades[1], ..., grades[124].
Третій масив mass містить 30 дійсних чисел. Звертання до елементів масиву може бути таким: mass[0], mass[1], ...,
mass[29].
Четвертий масив v містить 1500 дійсних чисел з подвійною точністю. Звертання до елементів такого масиву може
бути таким: v[0], v[1], ..., v[1499].

Національний університет «Львівська політехніка»


Лістинг 4.1: Опис масиву, найпростіші операції з елементами масиву
int main(void)
{
int ar[4], iSum;
float set[3], dSum;
// Array ar contains 4 elements:
ar[0] = 7;
ar[1] = 4;
ar[2] = 2;
ar[3] = 1;
// Array set contins 3 elemenrs:
set[0] = 2.3;
set[1] = 1;
set[2] = 4.5;
// Calculation with array elements:
iSum = ar[0] + ar[1] + ar[2] + ar[3];
dSum = set[0] + set[1] + set[2];
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 4.2.: Ввід елементів масиву з клавіатури
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
int ar[4], iSum;
float set[3], dSum;
printf("Enter 4 int-numbers for ar array: ");
scanf("%d%d%d%d", &(ar[0]), &(ar[1]), &(ar[2]), &(ar[3]));
printf("Enter 3 float-numbers for set array: ");
scanf("%f%f%f", &(set[0]), &(set[1]), &(set[2]));
// Calculation with array elements:
iSum = ar[0] + ar[1] + ar[2] + ar[3];
dSum = set[0] + set[1] + set[2];
return 0;
}

Вводити чи виводити значення одновимірних масивів можна лише поелементно, для чого слід організовувати цикли
зі зміненням значень індексу зазначеного масиву.

Національний університет «Львівська політехніка»


#define _CRT_SECURE_NO_WARNINGS Лістинг 4.3.: Глобальні масиви
#include <stdio.h>
/*--- Function prototype ---*/
int iArrSum(void);
float dArrSum(void);
/*--- Global arrays ---*/
int ar[4];
float set[3];
int main(void)
{
int iSum; float dSum;
printf("Enter 4 int-numbers for ar array: ");
scanf("%d%d%d%d", &(ar[0]), &(ar[1]), &(ar[2]), &(ar[3]));
printf("Enter 3 double-numbers for set array: ");
scanf("%f%f%f", &(set[0]), &(set[1]), &(set[2]));
// Calculation with array elements:
iSum = iArrSum();
dSum = dArrSum();
return 0;
}
int iArrSum(void)
{ return (ar[0] + ar[1] + ar[2] + ar[3]); }
float dArrSum(void)
{ return (set[0] + set[1] + set[2]); }
Національний університет «Львівська політехніка»
Лістинг 4.4. Звертання до неіснуючого елемента масиву
#include <stdio.h>
int main(void)
{
int i, ar[4];
// Array ar contains 4 elements:
ar[0] = 7;
ar[1] = 4;
ar[2] = 2;
ar[3] = 1;
for (i = 0; i <= 4; i++)
printf("ar[%d] = %d\n", i, ar[i]);
return 0;
}
Результати роботи програми:
ar[0] = 7
ar[1] = 4
ar[2] = 2
ar[3] = 1
ar[4] = 4
ar[5] = 1245120

Національний університет «Львівська політехніка»


Лістинг 4.5.: Робота з індексами, «перевертання» масиву

#include <stdio.h> Результати роботи програми:


int main(void) ar2[0] = 1
{ ar2[1] = 2
int i, ar1[4], ar2[4]; ar2[2] = 4
ar2[3] = 7
// Array ar contains 4 elements:
ar1[0] = 7;
ar1[1] = 4;
ar1[2] = 2;
ar1[3] = 1;
/*--- Array reversing ---*/
for (i = 0; i < 4; i++)
ar2[3 - i] = ar1[i];
/*--- Control printing ---*/
for (i = 0; i < 4; i++)
printf("ar2[%d] = %d\n", i, ar2[i]);
return 0;
}

Національний університет «Львівська політехніка»


4.2. Ініціалізація одновимірних масивів
При оголошенні масивів елементам масиву можна (необов’язково всім) присвоювати початкові значення, які у
подальшому в програмі може бути змінено.
Якщо реальна кількість ініціалізованих значень є меншою за розмірність масиву, то решта елементів масиву набуває
значення 0:

тип_даних ім’я_масиву [розмір_масиву] = {значення_елементів_масиву};


Наприклад,
int a[5]={9,33,–23,8,1};
//а[0]=9, а[1]=33, а[2]=–23, а[3]=8, а[4]=1
float b[10] = {1.5, -3.8, 10};
// b[0]=1.5, b[1]=-3.8, b[2]=10, b[3]=b[4]=…=b[9]=0

Національний університет «Львівська політехніка»


Масиви в програмах можна оголошувати також зі створенням типу користувача:
typedef тип_даних ім’я_типу [розмір_масиву];
ім’я_типу ім’я_масиву;
Наприклад, створимо тип з ім’ям mass як масив з 10-ти додатних цілих чисел і оголосимо два масиви – a та b –
створеного типу:
typedef unsigned short mass[10];
mass a, b;

Для оголошування масивів можна використовувати типізовані констант-масиви, які дозволяють водночас і оголосити
масив, і задати його значення як константи:
сonst тип_даних ім’я_масиву [розмір_масиву] = {значення_елементів_масиву};
Слід пам’ятати, що змінювати значення елементів констант-масивів не припустимо.
Приклади створення констант-масивів:
const int arr[5]={9,3,–7,0,123}; //масив з 5-ти цілих чисел
const float f[5]={1.5,6,8,7.4,–1.125}; //масив з 5-ти дійсних чисел
const С[5]={15,–6,546}; //масив з 5-ти цілих чисел типу int, // де С[0]=15, С[1]= –6, С[2]=546, С[3]=0 і С[4]=0

Національний університет «Львівська політехніка»


Лістинг 4.6.: Ініціалізація масиву

int main(void)
{
int iSum = 0, ar[4] = { 7, 4, 2, 1 };
float dSum = 0, set[3] = { 2.3, 1, 4.5 };
// Calculation with array elements:
iSum = ar[0] + ar[1] + ar[2] + ar[3];
dSum = set[0] + set[1] + set[2];
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 4.7.: Операція sizeof()

#include <stdio.h>
int main(void)
{
int size, val, ar[3] = { 1, 2 };
size = sizeof(ar) / sizeof(int);
val = ar[2];
printf("size = %d val = %d\n", size, val);
return 0;
}

Результати роботи програми:


size = 3 val = 0

Національний університет «Львівська політехніка»


4.3. Прості задачі опрацювання масивів
Опрацювання масиву полягає у виконанні операцій над його елементами. Окрім вводу-виводу масивів існує
перелік найпоширеніших базових алгоритмів опрацювання масивів:
• обчислення узагальнюючих характеристик (сум, добутків і кількості елементів);
• пошук максимального чи мінімального елемента;
• пошук заданих елементів;
• переставляння елементів;
• упорядкування масивів.

Національний університет «Львівська політехніка»


Лістинг 4.8.: Cереднє арифметичне елементів масиву
#include <stdio.h>
int main(void)
{
int size, i; float Sum = 0;
int Arr[] = { 1, 6, 3, 2, 5, 1, 77, -18, 99, 16, 45, 12,
-45, 54, 567, -123, 5, 9, -4, 67, -17, 44, 2, 3, 9, 5,
34, 11, -11, 234, 67, 82, 91, 3, 7, 5, -32, -56, -77,
678, -987, 456, -123, 555, 3, 78, 1, 93, -4, 37,
2, 33, 6, 543, -88, 37, 66, -66, 777, 12, -45, 567,
999, 888, 3, -765, 34, 89, -37, 41, 333, 56, -987
};
float Aver;
size = sizeof(Arr) / sizeof(int);
for (i = 0; i < size; i++)
Sum = Sum + Arr[i];
Aver = Sum / size;
printf("Average is %f\n", Aver);
return 0;
}
Результати роботи програми:
Average is 61.205479
Національний університет «Львівська політехніка»
Лістинг 4.9.: Середнє арифметичне і дисперсія
#include <stdio.h>
#include <math.h>
int main(void)
{
int size, i; float Sum = 0;
int Arr[] = { 1, 6, 3, 2, 5, 1, 77, -18, 99, 16, 45, 12,
-45, 54, 567, -123, 5, 9, -4, 67, -17, 44, 2, 3, 9, 5,
34, 11, -11, 234, 67, 82, 91, 3, 7, 5, -32, -56, -77,
678, -987, 456, -123, 555, 3, 78, 1, 93, -4, 37,
2, 33, 6, 543, -88, 37, 66, -66, 777, 12, -45, 567,
999, 888, 3, -765, 34, 89, -37, 41, 333, 56, -987
};
double Aver, dSum, Disp;
size = sizeof(Arr) / sizeof(int);
for (i = 0; i < size; i++)
Sum = Sum + Arr[i];
Aver = Sum / size; dSum = 0;
for (i = 0; i < size; i++)
dSum = dSum + (Arr[i] - Aver) * (Arr[i] - Aver);
Disp = sqrt(dSum / size);
printf("Average is %f Dis is %f\n", Aver, Disp);
return 0;
}
Національний університет «Львівська політехніка»
Завдання: знайти найбільше число у масиві. початок

ввід n

На блок-схемі маємо два цикли – i від 0 до n-1


ні
з кроком 1
перший запам’товує введені значення, а
другий шукає найбільше значення. так
max = a0

ввід ai

i від 1 до n-1
ні
з кроком 1

так

ai > max
вивід
max
так

max = ai ні
кінець

Рисунок 4.1 – Блок-схема алгоритму знаходження найбільшого числа


у масиві
Національний університет «Львівська політехніка»
Лістинг 4.10.: Знаходження максимального елемента масиву

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
const int n = 10;
int a[n], i, max;
for (i = 0; i < n; i++)
{
printf("Enter a[%d]:", i);
scanf("%d", &a[i]);
}
max = a[0];
for (i = 1; i < n; i++)
{
if (a[i] > max)
max = a[i];
}
printf("\nmax=%d", max);
return 0;
}

Національний університет «Львівська політехніка»


Аналогічним чином можна знайти і найменше значення у послідовності.
Деколи є необхідність знайти не найбільше чи найменше значення, а його номер в послідовності (індекс в
масиві).
Існують задачі пошуку заданого значення в послідовності. Результатом може бути номер цього числа, або
відповідь: що число знаходиться у даній послідовності, або такого числа в послідовності немає.
Найпростіший алгоритм пошуку – простий перебір (лінійний пошук).
Є й інші методи пошуку, один з них – бінарний (двійковий) пошук, який шукає елемент у відсортованій
послідовності. Відсортована послідовність за зростанням ділиться навпіл і задане значення порівнюється з
центральним елементом послідовності. Якщо значення, яке ми шукаємо менше за центральний елемент, то
далі шукаємо в першій половині послідовності, інакше в другій аналогічним способом.

Національний університет «Львівська політехніка»


4.4. Масиви як параметри функцій
Базові алгоритми опрацювання масивів можуть бути складовими блоками при розв’язку більш складних задач. А тому
організовувати їх доцільно у вигляді окремих функцій.
Якщо результатом виконання функції є одне значення, яке повертається оператором return, то воно підставляється у
точку виклику функції. Тому тип функції і є типом результату. Коли функція має повертати масив, типом функції слід
оголосити вказівник на тип елемента масиву. Наприклад, оголошена в такий спосіб функція може повертати масив
цілих чисел:

int * function ();

Національний університет «Львівська політехніка»


Якщо масив використовують у якості параметра функції, то треба зазначити адресу початку масиву. Зробити це можна
одним з трьох способів:
float fun (int a[10]);
float fun (int a[]);
float fun (int *a);
За першим способом явно зазначено кількість елементів масиву. У другій синтаксичній формі константний вираз у
квадратних дужках є відсутній. Така форма є припустима, коли кількість елементів масиву є глобальною змінною чи
константою. За третього способу передається вказівник як посилання на масив, явно визначений в основній програмі.
Більш докладно роботу з вказівниками буде розглянуто пізніше.
Хоча наведені записи мають різний синтаксис, насправді вони є рівнозначними, оскільки авторами стандарту C для
більшої ясності було вирішено, що масив оголошений як параметр функції є вказівником. При цьому число у квад-ратних
дужках ігнорується. А тому доцільно передавати розмірність масиву окремим параметром:
float fun (int a[], int n);
float fun (int *a, int n);
Якщо функцію організовано для опрацювання елементів масиву, наприклад для сортування елементів, у такому разі
можна оголосити функцію з типом результату void (нема величини, що повертається). Оскільки сам масив передається у
функцію за адресою, то будь-які зміни значень елементів масиву у функції буде видно і в основній програмі, яка викликає
цю функцію.

Національний університет «Львівська політехніка»


Лістинг 4.11.: Функції для вводу і виводу початок початок
підпрограми підпрограми
елементів масиву вводу виводу

void InputArray(int x[], int size)


{ i=0 i=0
int i;
for (i = 0; i < size; i++)
{
printf("Enter x[%d]:", i); i < size i < size
scanf("%d", &x[i]);
} так так
}
void PrintArray(int x[], int size) ввід xi вивід xi
{
int i; ні ні
for (i = 0; i < size; i++) i = i +1 i = i +1
{
printf("x[%d]=%d\n", i, x[i]);
}
кінець кінець
} підпрограми підпрограми
вводу виводу
Національний університет «Львівська політехніка» Рисунок 4.2 – Блок-схеми алгоритмів вводу/виводу масиву чисел
Лістинг 4.12.: Окрема функція для знаходження початок
підпрограми
максимального елемента масиву пошуку найбільшого

max = x0
int SearchMaxArray(int x[], int size)
{
int i, max; i від
1 до size-1
max = x[0]; з кроком 1
for (i = 1; i < size; i++)
так
{
if (x[i] > max) xi > max
max = x[i];
} так
return max;
} max = xi ні ні

кінець
підпрограми
пошуку найбільшого

Національний університет «Львівська політехніка» Рисунок 4.3 – Блок-схема алгоритму пошуку найбільшого числа в масиві
початок

Тепер, маючи
ввід n
окремі
підпрограми
для вводу і
пошуку підпрограма вводу
масиву а
найбільшого InputArray(a, n);
елемента
масиву, можна
перерисувати
підпрограма пошуку
блок-схему найбільшого
алгоритму max = SearchMaxArray(a, n)
знаходження
найбільшого.
вивід
max

кінець

Рисунок 4.4 – Блок-схема алгоритму знаходження найбільшого числа з


Національний університет «Львівська політехніка» використанням функцій
Лістинг 4.13.: Знаходження максимального елемента масиву з
#define _CRT_SECURE_NO_WARNINGS використанням функцій
#include <stdio.h>
#include <math.h>
void InputArray(int x[], int size) int main(void)
{ {
int i; const int n = 10;
for (i = 0; i < size; i++) int a[n], max;
{ InputArray(a, n);
printf("Enter x[%d]:", i); max = SearchMaxArray(a, n);
scanf("%d", &x[i]); printf("max=%d", max);
} return 0;
} }
int SearchMaxArray(int x[], int size)
{
int i, max;
max = x[0];
for (i = 1; i < size; i++)
{
if (x[i] > max)
max = x[i];
}
return max;
}
Національний університет «Львівська політехніка»
Лістинг 4.14.: Підпрограма знаходження позиції в масиві максимального
елемента

int SearchMaxPosArray(int x[], int size)


{
int i, max;
max = 0;
for (i = 1; i < size; i++)
{
if (x[i] > x[max])
max = i;
}
return max;
}

Національний університет «Львівська політехніка»


Лістинг 4.15.: Масиви як параметри функції.
float ArrAver(int ar[], int size);
int main(void)
{
int size; float Aver;
int Arr[] = { 1, 6, 3, 2, 5, 1, 77, -18, 99, 16, 45, 12,
-45, 54, 567, -123, 5, 9, -4, 67, -17, 44, 2, 3, 9, 5,
34, 11, -11, 234, 67, 82, 91, 3, 7, 5, -32, -56, -77,
678, -987, 456, -123, 555, 3, 78, 1, 93, -4, 37,
2, 33, 6, 543, -88, 37, 66, -66, 777, 12, -45, 567,
999, 888, 3, -765, 34, 89, -37, 41, 333, 56, -987
};
size = sizeof(Arr) / sizeof(int);
Aver = ArrAver(Arr, size);
return 0;
}
float ArrAver(int ar[], int size)
{
int i; float Sum = 0;
for (i = 0; i < size; i++)
Sum = Sum + ar[i];
return Sum / size;
}
Національний університет «Львівська політехніка»
Лістинг 4.16.: Покращання модульності попередньої програми
#include <stdio.h>
#include <math.h>
float ArrAver(int ar[], int size);
float ArrDisp(int ar[], int size);
int main(void)
{
int size; float Disp;
int Arr[] = { 1, 6, 3, 2, 5, 1, 77, -18, 99, 16, 45, 12,
-45, 54, 567, -123, 5, 9, -4, 67, -17, 44, 2, 3, 9, 5,
34, 11, -11, 234, 67, 82, 91, 3, 7, 5, -32, -56, -77,
678, -987, 456, -123, 555, 3, 78, 1, 93, -4, 37,
2, 33, 6, 543, -88, 37, 66, -66, 777, 12, -45, 567,
999, 888, 3, -765, 34, 89, -37, 41, 333, 56, -987
};
size = sizeof(Arr) / sizeof(int);
Disp = ArrDisp(Arr, size);
printf("Disp is %f\n", Disp);
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 4.16.: Покращання модульності попередньої програми
float ArrAver(int ar[], int size)
{
int i; float Sum = 0;
for (i = 0; i < size; i++)
Sum = Sum + ar[i];
return Sum / size;
}
float ArrDisp(int ar[], int size)
{
float Aver, dSum = 0; int i;
Aver = ArrAver(ar, size);
for (i = 0; i < size; i++)
dSum = dSum + (ar[i] - Aver) * (ar[i] - Aver);
return sqrt(dSum / size);
}

Національний університет «Львівська політехніка»


Лекції №17,18,19.
4.5. Алгоритми сортування масивів.
4.6. Багатовимірні масиви.
4.7. Алгоритми опрацювання матриць.
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
4.5. Алгоритми сортування масивів
Ще один клас задач – це сортування елементів послідовності за зростанням (кожен наступний елемент
послідовності більший за попередній або рівний йому) або за спаданням (кожен наступний елемент послідовності
менший за попередній або рівний йому).

Є декілька методів сортування:


• сортування «бульбашкою»;
• методом вставки;
• методом вибору;
• швидке сортування;
• Інші методи сортування.

Національний університет «Львівська політехніка»


Початок
сортування

Метод сортування «бульбашкою»: i=1

порівнюються почергово два сусідні елементи і, якщо один з


елементів не відповідає критерію сортування, то ці елементи i < size

міняються місцями. так

Після порівняння усіх елементів послідовності найбільший j=0

елемент стане на останнє місце у послідовності, тобто він буде


j < size - i
уже на «своєму» місці. ні
так
Надалі знову повторюємо такі ж дії, але вже з послідовністю,
xj > xj+1
на одиницю меншою за попередню.
так
Дана задача містить у собі нову структуру – вкладений
temp = xi
циклічний алгоритм (цикл у циклі). xi = xj+1 ні
ні
xj+1 = temp
Зовнішній цикл виконується n-1 раз, а внутрішній n-i раз, де i
параметр циклу зовнішнього циклу. j=j+1

Кінець
i = i +1
сортування

Національний університет «Львівська політехніка» Рисунок 4.5 – Блок-схема алгоритму сортування методом «бульбашки»
Лістинг 4.17.: Підпрограма сортування за зростанням методом “бульбашки”
void BubbleSort(int x[], int size)
{
int i, j, temp;
for (i = 1; i < size; i++)
for (j = 0; j < (size - i); j++)
if (x[j] > x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}

Національний університет «Львівська політехніка»


Початок
сортування

Сортування методом вставки: i=0

порівнюємо перший елемент послідовності з усіма іншими по


черзі, і коли один з елементів не відповідає критерію i < size - 1

сортування, то цей елемент міняємо місцями з першим так

(вставляємо елемент на своє місце). j=i

Далі робимо аналогічну дію для послідовності, починаючи з


j < size
наступного елементу.
ні
так

xi > xj

так

temp = xi
xi = xj ні
ні
xj = temp

j=j+1

Кінець
i = i +1
сортування

Національний університет «Львівська політехніка» Рисунок 4.6 – Блок-схема алгоритму сортування методом вставки
Лістинг 4.18.: Підпрограма сортування за зростанням методом вставки
void InsertSort(int x[], int size)
{
int i, j, temp;
for (i = 0; i < size - 1; i++)
for (j = i; j < size; j++)
if (x[i] > x[j])
{
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}

Національний університет «Львівська політехніка»


Сортування методом вибору: початок Початок
пошуку сортування
знаходимо найбільший елемент і
i=0
міняємо його місцями з останнім. max = 0
i=1
Далі робимо аналогічну дію для
i < size - 1
послідовності, не розглядаючи вже
так i < size так
останній елемент (він уже на своєму ні
підпрограма
місці).
пошуку мax
Раніше ми розглядали алгоритм xi > xmax ні

temp = xmax
пошуку найбільшого значення у так xmax = xn-i
послідовності, трішки max = i ні
xn-i = temp

модифікувавши його, отримаємо i = i +1


Кінець
сортування
кінець
алгоритм пошуку номеру i = i +1
пошуку
найбільшого.
Маючи номер найбільшого, міняємо
місцями найбільший елемент з
Рисунок 4.7 – Блок-схеми алгоритму сортування методом вибору
останнім.

Національний університет «Львівська політехніка»


Лістинг 4.19.: Підпрограма сортування за зростанням методом вибору.
Алгоритм сортування методом вибору використовує фунцію пошуку позиції максимального числа в масиві
SearchMaxPosArray, яку ми розглядали раніше.

void СhoiceSort(int x[], int size)


{
int i, max, temp;
for (i = 0; i < size; i++)
{
max = SearchMaxPosArray(x, size - i);
temp = x[max];
x[max] = x[size - 1 - i];
x[size - 1 - i] = temp;
}
}

Національний університет «Львівська політехніка»


Лістинг 4.20.: Програма заповнення масиву псевдовипадковими числами
Для великих масивів буває незручно вводити усі значення з клавітури, а тому, для генерування елементів
масиву, можна скористатись функцією випадкового генерування чисел. Функція
int rand( void );
генерує випадкові числа, повертає псевдовипадкове ціле число. Щоб скористатись цією функцією необхідно
підключити файл
#include <stdlib.h>

void FillArray(int x[], int size)


{
int i;
for (i = 0; i < size; i++)
x[i] = rand() % 100;
}

Національний університет «Львівська політехніка»


Лістинг 4.21.: Програма сортування масиву за зростанням
#define _CRT_SECURE_NO_WARNINGS void СhoiceSort(int x[], int size)
#include <stdio.h> {
#include <stdlib.h> int i, max, temp;
#include <time.h> for (i = 0; i < size; i++)
void FillArray(int x[], int size) {
{ max = SearchMaxPosArray(x, size - i);
int i; temp = x[max];
for (i = 0; i < size; i++) x[max] = x[size - 1 - i];
x[i] = rand() % 100 - 50; x[size - 1 - i] = temp;
} }
}
void PrintArray(int x[], int size) int main(void)
{ {
int i; const int n = 10; int a[n];
for (i = 0; i < size; i++) srand(time(NULL));
{ printf("x[%d]=%d\n", i, x[i]); } printf("Before sorting:\n");
} FillArray(a, n);
int SearchMaxPosArray(int x[], int size) PrintArray(a, n);
{ СhoiceSort(a, n);
int i, max; printf("After sorting:\n");
max = 0; PrintArray(a, n);
for (i = 1; i < size; i++) return 0;
{ }
if (x[i] > x[max]) max = i;
}
return max;
} Національний університет «Львівська політехніка»
Результати роботи програми:
Before sorting:
x[0]=-13
x[1]=14
x[2]=11
x[3]=9
x[4]=-39
x[5]=5
x[6]=20
x[7]=30
x[8]=28
x[9]=33
After sorting:
x[0]=-39
x[1]=-13
x[2]=5
x[3]=9
x[4]=11
x[5]=14
x[6]=20
x[7]=28
x[8]=30
x[9]=33

Національний університет «Львівська політехніка»


Лістинг 4.22.: Сортування масиву за спаданням
#include <stdio.h> int dArrMinPos(float ar[], int size)
int dArrMinPos(float ar[], int size); {
void dMySortRev(float ar[], int size); float Min; int i, pos;
int main(void) Min = ar[0]; pos = 0;
{ for (i = 1; i < size; i++)
int i, size; if (ar[i] < Min) { Min = ar[i]; pos = i; }
float Arr[] = { 1.3, 6.7, 3.4, 2.0, 5.1, 1.56, 77, return pos;
-18, 99.1, 56.789, -12.5, 5.5, 9.7, -4.67, }
0.67, -17.44, 11.11, -11.22, 234.6, 6.7, 8.2,
9.1, 3.77, 7.1234, 9.99, 8.88, -7.65, 3.4, void dMySortRev(float ar[], int size)
8.9, -3.7, 4.1, 3.33, 5.6 }; {
size = sizeof(Arr) / sizeof(float); float temp; int i, pos;
dMySortRev(Arr, size); for (i = 0; i < size; i++) {
for (i = 0; i < size; i++) pos = dArrMinPos(ar, size - i);
printf("Ar[%d] = %f\n", i, Arr[i]); temp = ar[size - 1 - i];
return 0; ar[size - 1 - i] = ar[pos];
} ar[pos] = temp;
}
}

Національний університет «Львівська політехніка»


4.6. Багатовимірні масиви
Як вже зазначалось, вимірність масиву визначається кількістю індексів.
Елементи одновимірного масиву (вектора) мають один індекс, двовимірного масиву (матриці, таблиці) – два індекси:
перший з них – номер рядка, другий – номер стовпчика.
Кількість індексів у масивах є необмежена.
При розміщуванні елементів масиву в пам’яті комп’ютера першою чергою змінюється крайній правий індекс, потім
решта – справа наліво.
Багатовимірний масив оголошується у програмі в такий спосіб:
тип ім’я [ розмір1 ] [ розмір2 ] … [ розмірN ];
Кількість елементів масиву дорівнює добуткові кількості елементів за кожним індексом. У прикладі
int a[3][4];
оголошено двовимірний масив з 3-х рядків та 4-х стовпчиків (12-ти елементів) цілого типу:
a[0][0], a[0][1], a[0][2], a[0][3],
a[1][0], a[1][1], a[1][2], a[1][3],
a[2][0], a[2][1], a[2][2], a[2][3];

Національний університет «Львівська політехніка»


Під масив надається пам’ять, потрібна для розташування усіх його елементів.
Елементи масиву один за одним, з першого до останнього, запам’ятовуються у послідовно зростаючих адресах пам’яті
так само, як і елементи одновимірного масиву.
При оголошенні масиву можна ініціалізовувати початкові значення його елементів, причому необов’язково усіх,
наприклад:
1) int w[3][3]={{2, 3, 4},{3, 4, 8},{1, 0, 9}};
2) float C[4][3]={1.1, 2, 3, 3.4, 0.5, 6.8, 9.7, 0.9};
3) float C[4][3]={{1.1, 2, 3},{3.4, 0.5, 6.8},{9.7, 0.9}};
4) float C[4][3]={{1.1, 2},{3, 3.4, 0.5},{6.8},{9.7, 0.9}};
У першому прикладі оголошено й ініціалізовано масив цілих чисел w[3][3].
Елементам масиву присвоєно значення зі списку: w[0][0]=2, w[0][1]=3, w[0][2]=4, w[1][0]=3 тощо.
Списки, виокремлені у фігурні дужки, відповідають рядкам масиву.
Записи другого й третього прикладів є еквівалентні, і в них елементи останнього рядка не є ініціалізовані, тобто не є
визначені.
У таких випадках у числових масивах неініціалізовані елементи набувають значення 0.

Національний університет «Львівська політехніка»


Для оголошення масивів можна також використовувати типізовані констант-масиви, які дозволяють водночас оголосити
масив і задати його значення в розділі оголошень констант, наприклад:
const int arr[2][5] = {{9, 3, 0, 12, –5},{ –7, 23, 2, 4, 0}};
але змінювати значення елементів констант-масивів у програмі є неприпустимо.
В С можна використовувати перетин масиву (section).
Це поняття подібно до поняття мінору матриці в математиці.
Перетин формується внаслідок вилучення однієї чи кількох пар квадратних дужок.
Пари квадратних дужок можна відкидати лише справа наліво й лише послідовно.
Перетини масивів використовуються при організації обчислювального процесу в функціях мовою С, розроблюваних
користувачем.
Приклади:
1) int х[5][3];
Якщо при звертанні до певної функції написати х[0], то передаватиметься нульовий рядок масиву х.
2) int y[2][3][4];
При звертанні до масиву y можна записати, наприклад, y[1][2] – і буде передаватися вектор з чотирьох елементів, а
звертання y[1] надасть двовимірний масив розміром 3х4. Не можна писати y[2][4], вважаючи на увазі, що
передаватиметься вектор, тому що це не відповідає обмеженню, накладеному на використання перетинів масиву.
Національний університет «Львівська політехніка»
Лістинг 4.23.: Програма знаходження суми елементів двовимірного
масиву
#include <stdio.h>
int main(void) {
float A[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
float Sum;
int i, j;
Sum = 0;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++)
Sum = Sum + A[i][j];
printf("Sum = %f\n", Sum);
}
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 4.24.: Програма знаходження суми елементів двовимірного
масиву
#include <stdio.h>
float ArrSum(float X[], int n)
{
int i;
float result; result = 0;
for (i = 0; i < n; i++)
result = result + X[i];
return result;
}
int main(void) {
float A[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
float Sum;
int i;
Sum = 0;
for (i = 0; i < 3; i++) {
Sum = Sum + ArrSum(A[i], 3);
printf("Sum = %f\n", Sum);
}
return 0;
}
Національний університет «Львівська політехніка»
4.7. Алгоритми опрацювання матриць.
Матриця – це математичний об’єкт, записаний у вигляді прямокутної таблиці чисел, над яким можна виконувати
операції додавання, віднімання, множення і множення на скаляр.
Матрицею A розмірності m x n називається таблиця чисел, яка складається з m рядків та n стовпців.

𝑎11 𝑎12 ⋯ 𝑎1𝑛


𝑎21 𝑎22 ⋯ 𝑎2𝑛
𝐴 = 𝐴𝑚×𝑛 = ⋯ ⋯ ⋯ ⋯
𝑎𝑚1 𝑎𝑚2 ⋯ 𝑎𝑚𝑛
Числа, що складають матрицю, називаються її елементами і нумеруються двома індексами, які вказують на номер
рядка та стовпця на перетині яких розташований даний елемент. Тобто, елемент aij міститься в i-му рядку та j-му
стовпці матриці A.
Матриця яка складається з одного стовпця чи одного рядка, називається відповідно матриця-стовпець чи матриця-
рядок. Матрицю-стовпець і матрицю-рядок називають також векторами. Матриця, у якої кількість рядків і стовпців
рівна, називається квадратною матрицею.
Алгоритм введення/виведення елементів матриці буде містити у собі вкладений цикл: зовнішній цикл виконується
стільки раз, скільки рядків у матриці, а внутрішній цикл – це введення елементів рядка матриці, який виконується
стільки раз, скільки стовпців у матриці.

Національний університет «Львівська політехніка»


початок початок
підпрограми підпрограми
вводу матриці виводу матриці
початок початок
i=0 i=0 підпрограми підпрограми
вводу матриці виводу матриці

i<m i<m
i = від 0 до m -1 i = від 0 до m -1
з кроком 1 з кроком 1
так так
так так
j=0 j=0
j = від 1 до n - 1 j = від 1 до n - 1
ні ні з кроком 1 з кроком 1
j<n j<n
так так
так так
ввід вивід
ввід вивід xij ні xij ні
xij xij

ні ні
j = j +1 j = j +1 ні ні

кінець кінець кінець кінець


i=i+1 i=i+1 підпрограми
підпрограми підпрограми підпрограми
вводу матриці виводу матриці вводу матриці виводу матриці

Рисунок 4.8 – Блок-схеми алгоритмів вводу і виводу елементів Рисунок 4.9 – Блок-схеми алгоритмів вводу і виводу
матриці елементів матриці з використанням символу підготовка

Національний університет «Львівська політехніка»


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

Національний університет «Львівська політехніка»


Лістинг 4.25.: Функції для вводу і виводу елементів двовимірного масиву
const int m = 3, n = 4; (матриці)
void InputMatrix(int x[m][n])
{
int i, j;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
{
printf("Enter [%d][%d]:", i, j);
scanf("%d", &x[i][j]);
}
}
void PrintMatrix(int x[m][n])
{
int i, j;
printf("\n");
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++)
printf("%d\t", x[i][j]);
printf("\n");
}
}
Національний університет «Львівська політехніка»
Лістинг 4.26.: Функція генерування елементів двовимірного масиву (матриці)

Для генерування елементів масиву можна скористатись функцією випадкового генерування чисел.

void FillMatrix(int x[m][n])


{
int i, j;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
{
x[i][j] = rand() % 100 - 50;
}
}

Національний університет «Львівська політехніка»


Сумою матриць А і В є матриця С, елементи якої рівні сумі відповідних елементів матриць А і В.
Додавати між собою можна тільки матриці однакового розміру.

𝑎11 𝑎12 ⋯ 𝑎1𝑛 𝑏11 𝑏12 ⋯ 𝑏1𝑛 𝑐11 𝑐12 ⋯ 𝑐1𝑛


𝑎21 𝑎22 ⋯ 𝑎2𝑛 𝑏 𝑏22 ⋯ 𝑏2𝑛 𝑐21 𝑐22 ⋯ 𝑐2𝑛
⋯ ⋯ ⋯ ⋯ + ⋯
21
⋯ ⋯ ⋯ = ⋯ ⋯ ⋯ ⋯
𝑎𝑚1 𝑎𝑚2 ⋯ 𝑎𝑚𝑛 𝑏𝑚1 𝑏𝑚2 ⋯ 𝑏𝑚𝑛 𝑐𝑚1 𝑐𝑚2 ⋯ 𝑐𝑚𝑛

𝑐𝑖𝑗 = 𝑎𝑖𝑗 + 𝑏𝑖𝑗

Національний університет «Львівська політехніка»


початок підпрограми
Лістинг 4.27.: Функція сумування двох матриць сумування матриць

void SumMatrix(int a[m][n], int b[m][n], int c[m][n]) i=0


{
int i, j;
for (i = 0; i < m; i++) i<m
for (j = 0; j < n; j++)
так
{
c[i][j] = a[i][j] + b[i][j]; j=0
} ні
}
j<n

так

cij = aij + bij

ні
j = j +1

кінець підпрограми
i=i+1
сумування матриць

Рисунок 4.10 – Блок-схема алгоритму сумування матриць А і В


Національний університет «Львівська політехніка»
Добутком матриці A на число k називається матриця B = k * A того ж розміру, отримана з початкової
множенням на задане число всіх її елементів:

𝑎11 𝑎12 ⋯ 𝑎1𝑛 𝑘 ∗ 𝑎11 𝑘 ∗ 𝑎12 ⋯ 𝑘 ∗ 𝑎1𝑛


𝑎21 𝑎22 ⋯ 𝑎2𝑛 𝑘 ∗ 𝑎21 𝑘 ∗ 𝑎22 ⋯ 𝑘 ∗ 𝑎2𝑛
𝑘∗ ⋯ ⋯ ⋯ ⋯ = ⋯ ⋯ ⋯ ⋯
𝑎𝑚1 𝑎𝑚2 ⋯ 𝑎𝑚𝑛 𝑘∗𝑎 𝑘 ∗ 𝑎𝑚2 ⋯ 𝑘 ∗ 𝑎𝑚𝑛
𝑚1

𝑏𝑖𝑗 = 𝑘 ∗ 𝑎𝑖𝑗

Національний університет «Львівська політехніка»


початок підпрограми
Лістинг 4.28.: Функція множення матриці на множення матриці
на число
число
i=0
void MultMatrixNum(int a[m][n], int b[m][n], int k)
{
int i, j; i<m
for (i = 0; i < m; i++)
так
for (j = 0; j < n; j++)
b[i][j] = a[i][j] * k; j=0
} ні

j<n

так

bij = k*aij

ні
j = j +1

кінець підпрограми
i=i+1 множення матриці
на число

Рисунок 4.11 – Блок-схема алгоритму множення матриці на число


Національний університет «Львівська політехніка»
Результатом множення матриць Am×n та Bn×k буде матриця Cm×k така, що елемент матриці C, що знаходяться
в i-тому рядку та j-тому стовпчику (cij), дорівнює сумі добутків елементів i-того рядку матриці A на відповідні
елементи j-того стовпця матриці B:

𝑎11 𝑎12 ⋯ 𝑎1𝑛 𝑏11 𝑏12 ⋯ 𝑏1𝑘 𝑐11 𝑐12 ⋯ 𝑐1𝑘


𝑎21 𝑎22 ⋯ 𝑎2𝑛 𝑏 𝑏22 ⋯ 𝑏2𝑘 𝑐21 𝑐22 ⋯ 𝑐2𝑘
⋯ ⋯ ⋯ ⋯ ∗ 21
⋯ ⋯ ⋯ ⋯ = ⋯ ⋯ ⋯ ⋯
𝑎𝑚1 𝑎𝑚2 ⋯ 𝑎𝑚𝑛 𝑏𝑛1 𝑏𝑛2 ⋯ 𝑏𝑛𝑘 𝑐𝑚1 𝑐𝑚2 ⋯ 𝑐𝑚𝑘
𝑐𝑖𝑗 = 𝑎𝑖1 ∗ 𝑏1𝑗 + 𝑎𝑖2 ∗ 𝑏2𝑗 + ⋯ + 𝑎𝑖𝑛 ∗ 𝑏𝑛𝑗

Дві матриці можна перемножити між собою тоді і тільки тоді, коли кількість стовпців першої матриці дорівнює
кількості рядків другої матриці.

Національний університет «Львівська політехніка»


Часткові випадки – множення матриці на матрицю стовпець,

𝑎11 𝑎12 ⋯ 𝑎1𝑛 𝑏1 𝑐1


𝑎21 𝑎22 ⋯ 𝑎2𝑛 𝑏2 𝑐2
⋯ ⋯ ⋯ ⋯ ∗ ⋯ = ⋯
𝑎𝑚1 𝑎𝑚2 ⋯ 𝑎𝑚𝑛 𝑏𝑛 𝑐𝑚
𝑐𝑖 = 𝑎𝑖1 ∗ 𝑏1 + 𝑎𝑖2 ∗ 𝑏2 + ⋯ + 𝑎𝑖𝑛 ∗ 𝑏𝑛
множення матриці рядка на матрицю

𝑏11 𝑏12 ⋯ 𝑏1𝑛


𝑎1 𝑎2 … 𝑎𝑚 𝑏
∗ ⋯21 𝑏22 ⋯ 𝑏2𝑛 = 𝑐1 𝑐2 … 𝑐𝑛
⋯ ⋯ ⋯
𝑏𝑚1 𝑏𝑚2 ⋯ 𝑏𝑚𝑛
𝑐𝑖 = 𝑎1 ∗ 𝑏1𝑖 + 𝑎2 ∗ 𝑏2𝑖 + ⋯ + 𝑎𝑚 ∗ 𝑏𝑚𝑖
і множення рядка на стовпець.

𝑏1
𝑎1 𝑎2 … 𝑎𝑛 ∗ 𝑏⋯2 = 𝑐
𝑏𝑛
𝑐 = 𝑎𝑖 ∗ 𝑏𝑖 + 𝑎𝑖 ∗ 𝑏𝑖 + ⋯ + 𝑎𝑛 ∗ 𝑏𝑛
Національний університет «Львівська політехніка»
початок підпрограми початок підпрограми
множення матриць
множення матриць

i=0

i = від 0 до m-1
з кроком 1
i<m
так
так

j=0 j = від 0 до k-1


Рисунок 4.12 – Рисунок 4.13 – з кроком 1

Блок-схема Блок-схема так


j<k
алгоритму алгоритму
так
множення множення cij = 0
cij = 0 ні
матриць матриць з
використанням
r=0

символу r = від 0 до n-1


з кроком 1
r<n підготовка ні
так
так

cij = cij + air * brj ні cij = cij + air * brj


ні
r = r +1
ні
ні

j = j +1

кінець підпрограми
множення матриць
i=i+1 кінець підпрограми
множення матриць
Національний університет «Львівська політехніка»
Лістинг 4.29.: Функція множення матриці на матрицю

void MultMatrix(int a[m][n], int b[n][k], int c[m][k])


{
int i, j, l;
for (i = 0; i < m; i++)
for (j = 0; j < k; j++)
{
c[i][j] = 0;
for (l = 0; l < n; l++)
c[i][j] = c[i][j] + a[i][l] * b[l][j];
}
}

Національний університет «Львівська політехніка»


Лістинг 4.30.: Програма множення матриці на матрицю
#define _CRT_SECURE_NO_WARNINGS void PrintMatrixA(int x[][n])
#include <stdio.h> {
#include <stdlib.h> int i, j;
#include <time.h> printf("\n");
const int m = 2, n = 3, k = 2; for (i = 0; i < m; i++)
void FillMatrixA(int x[][n]) {
{ for (j = 0; j < n; j++)
int i, j; printf("%8d\t", x[i][j]);
for (i = 0; i < m; i++) printf("\n");
for (j = 0; j < n; j++) }
{ }
x[i][j] = rand() % 20; void PrintMatrixB(int x[][k])
} {
} int i, j;
void FillMatrixB(int x[][k]) printf("\n");
{ for (i = 0; i < n; i++)
int i, j; {
for (i = 0; i < n; i++) for (j = 0; j < k; j++)
for (j = 0; j < k; j++) printf("%8d\t", x[i][j]);
{ printf("\n");
x[i][j] = rand() % 20; }
} }
}

Національний університет «Львівська політехніка»


Лістинг 4.30.: Програма множення матриці на матрицю
void PrintMatrixC(int x[][k]) int main(void)
{ {
int i, j; int A[m][n], B[n][k], C[m][k];
printf("\n"); srand(time(NULL));
for (i = 0; i < m; i++) FillMatrixA(A);
{ FillMatrixB(B);
for (j = 0; j < k; j++) MultMatrix(A, B, C);
printf("%8d\t", x[i][j]); printf("Matrix A");
printf("\n"); PrintMatrixA(A);
} printf("Matrix B");
} PrintMatrixB(B);
void MultMatrix(int a[m][n], int b[n][k], int c[m][k]) printf("Matrix C");
{ PrintMatrixC(C);
int i, j, l; return 0;
for (i = 0; i < m; i++) }
for (j = 0; j < k; j++)
{
c[i][j] = 0;
for (l = 0; l < n; l++)
c[i][j] = c[i][j] + a[i][l] * b[l][j];
}
}

Національний університет «Львівська політехніка»


Результати роботи програми:
Matrix A
11 2 7
12 10 6
Matrix B
4 8
7 15
13 4
Matrix C
149 146
196 270

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua
Лекції №20, 21, 22, 23.
Тема №5. ВКАЗІВНИКИ У МОВІ С
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
5.1. Поняття вказівника
Пам’ять комп’ютера являє собою сукупність комірок для зберігання
інформації, кожна з яких має власний номер.
пам ть
Ці номери називаються адресами.
адреси
Розмір однієї комірки пам’яті становить 1 байт.
10000
При оголошенні змінної в пам’яті виділяється ділянка обсягом,
Комірки з адресами
відповідним до типу змінної, наприклад 4 байти для типу int. 10001
значення 10000-10003
10006 містять значення 10006,
Ця ділянка пам’яті пов’язується з іменем змінної. 10002 яке є адресою іншої
Можна сказати, що адреса змінної – це адреса першої комірки цієї 10003
комірки

ділянки.
10004
Існують спеціальні змінні – вказівники, в яких можна зберігати
адреси комірок пам’яті, тобто адреси змінних. 10005

Слід звернути увагу на те, що адреса і значення змінної – це 10006


зовсім різні поняття.
Зазвичай адреси змінних не є важливими, найголовніше –
значення цих змінних.
Рисунок 5.1 – Організація пам’яті комп’ютера
Вказівники використовуються для прямого доступу до даних у
пам’яті.
Національний університет «Львівська політехніка»
Вказівник оголошується за допомогою *:

тип *ім’я;
Тут тип – це базовий тип вказівника, котрий може бути яким завгодно типом. Ім’я є ідентифікатором змінної-вказівника.
Слід звернути увагу на те, що тип – це не тип вказівника, а тип даних, адресу яких буде записано у вказівнику.
Наприклад, вказівник на пам’ять, в якій зберігатиметься ціле число: int* А;
Вказівник на дійсне число: float* В;
При цьому змінна А – це адреса ділянки пам’яті, в якій розташоване ціле число, В – адреса ділянки пам’яті, в якій
розташоване дійсне число. Обидві змінні – А та В – є вказівниками, тобто їхніми значеннями є адреси. Окрім того,
адреси змінних типу int та float займають у пам’яті однакову кількість байтів. Але насправді змінні А та В мають
різний тип: А – вказівник на ціле, В – вказівник на дійсне.
Вказівникові можна присвоїти лише адресу змінної відповідного типу, оскільки мова С не виконує автоматичного
перетворення типів вказівників.
Тому такі команди є помилковими:
A = B; // Не можна переприсвоювати один одному вказівники на різні типи;
А = 10; // не можна присвоювати числа вказівникам.

Національний університет «Львівська політехніка»


У мові С визначено дві операції для роботи з вказівниками:
*– розадресація, чи розіменування вказівника;
& – адресація, тобто отримання адреси змінної.
Для прикладу у програмі оголосимо цілу змінну Х зі значенням 7:
int Х = 7;
тоді, щоб її адресу записати до вказівника А, слід записати команду:
А = &Х; // Значенням змінної А є адреса змінної Х.
Якщо пам’ять, у якій зберігається змінна Х, починається з комірки під номером 2000, тоді змінній А після цієї
команди буде присвоєно значення 2000.
Тепер до значення 7, яке зберігається у змінній Х, можна звернутися не лише за ім’ям Х, але й через вказівник.
При цьому слід застосувати операцію розадресації вказівника (вона позначається символом “*”), наприклад:

Цей запис означає: “значення, яке зберігається за адресою, записаною у змінній А”.

Національний університет «Львівська політехніка»


Щоб збільшити на 2 оголошену раніше змінну X, можна записати так:
X = X + 2; /* За ім’ям X комп’ютер визначить адресу ділянки пам’яті, в якій вона розташована,
візьме значення, що зберігається у пам’яті за цією адресою, збільшить його на 2 і запише назад у
ту ж саму ділянку пам’яті */
чи так:
*A = (*A) + 2; /* Комп’ютер з ділянки пам’яті за адресою, записаною у змінній A, візьме
значення, яке зберігається в ній, збільшить це значення на 2 і запише в ту ж саму ділянку */
У першому разі відбувається звертання до змінної з ім’ям X, у другому – до змінної через її адресу, яку записано у
змінній-вказівнику A.
Звернімо увагу на те, що “зірочки” при оголошенні вказівника та операції розадресації – це зовсім різні речі, які лише
позначаються однаковим символом.
Вказівник, який не вказує на жодне значення, називається порожнім, чи нульовим. Такий вказівник має значення 0 чи
NULL.
Вказівники використовуються при роботі з динамічними змінними і при передаванні параметрів до функцій.

Національний університет «Львівська політехніка»


Лістинг 5.1.: Передача вказівників у функцію

#include <stdio.h>
void MySwap(int* pM, int* pN);
int main(void)
{
int M = 1, N = 5;
printf("Initial values:\n M = %d N = %d\n\n", M, N);
MySwap(&M, &N);
printf("After MySwap() call:\n M = %d N = %d\n\n", M, N);
return 0;
}
void MySwap(int* pM, int* pN)
{
int temp;
temp = *pM;
*pM = *pN;
*pN = temp;
}

Національний університет «Львівська політехніка»


5.2. Вказівники на одновимірні масиви
Масиви і вказівники у мові С тісно пов’язані і можуть використовуватись майже еквівалентно.
Ім’я масиву можна сприймати як константний вказівник на перший елемент масиву.
Його відмінність від звичайного вказівника полягає у тому, що його неможна модифікувати.
Здійснимо оголошення масиву mass з п’яти цілих чисел з ініціалізацією значень елементів і вказівника на ціле ptr:
int mass[5] = { 10,-2,0,40,3 }, * ptr;
При такому оголошенні масиву пам’ять виділяється не лише для п’яти елементів масиву, а й для вказівника з ім’ям
mass, значення якого дорівнює адресі першого елемента масиву mass[0], тобто сам масив залишається безіменним,
а доступ до елементів масиву здійснюється через вказівник з ім’ям mass.

10 -2 0 40 3

*mass *(mass+1) *(mass+2) *(mass+3) *(mass+4)

Національний університет «Львівська політехніка»


Для того, щоби звернутися до 3-го елементу цього масиву, можна записати чи то mass[2] чи *(mass + 2).
При реалізації на комп’ютері перший з цих способів зводиться до другого, тобто індексний вираз перетвориться до
адресного. Оскільки операції над вказівниками виконуються швидше, тому, якщо елементи масиву обробляються
почергово, то доцільніше використовувати другий спосіб. Якщо ж вибір елементів є випадковим, то, щоб уникнути
помилок, прийнятнішим є перший спосіб. Крім того, перший спосіб є більш наочним і звичним для сприйняття, що
сприяє кращій читабельності програм.
Оскільки ім’я масиву є вказівником на перший елемент масиву, можна надати вказівнику адресу першого елементу
масиву за допомогою оператора:
ptr = mass;
Цей запис є еквівалентним присвоюванню адреси першого елемента масиву (тобто до елемента з нульовим
індексом) у вигляді оператора
ptr = &mass[0]; *ptr *(ptr+1) *(ptr+2) *(ptr+3) *(ptr+4)

10 -2 0 40 3

*mass *(mass+1) *(mass+2) *(mass+3) *(mass+4)

Національний університет «Львівська політехніка»


Після цього звернутися до першого елемента масиву і надати йому значення 2 можна будь-яким з шести операторів:
*mass = 2;
mass[0] = 2;
*(mass + 0) = 2;
*ptr = 2;
ptr[0] = 2;
*(ptr + 0) = 2;
Усі ці оператори за дією є тотожними, але швидше за всі виконуватимуться присвоювання *mass = 2; та *ptr = 2;
оскільки в них не треба виконувати операції додавання.
Вказівники можна індексувати так само, як і масиви. Наприклад, вираз ptr[3] посилається до четвертого елемента
масиву mass.
Зауважимо, що не слід плутати такі оголошення:
int* p1[10]; // приклад 1
int (*p2)[10]; // приклад 2
У першому прикладі оголошено масив вказівників з ім’ям p1. Масив складається з 10 елементів, кожний з яких є
вказівником на змінну типу int.
У другому прикладі оголошено змінну-вказівник з ім’ям p2, яка вказує на масив з 10 цілих чисел типу int.

Національний університет «Львівська політехніка»


Лістинг 5.2.: Індекси, вказівники

#include <stdio.h>
int main(void)
{
int ar[5] = { 1, 2, 3, 4, 5 };
int Sum, i;
/*---- Indexing ----*/
Sum = 0;
for (i = 0; i < 5; i++)
Sum = Sum + ar[i];
printf("Indexing: Sum = %d\n", Sum);
/*-- Pointer syntax --*/
Sum = 0;
for (i = 0; i < 5; i++)
Sum = Sum + *(ar + i);
printf("Pointer syntax: Sum = %d\n", Sum);
return 0;
}

Національний університет «Львівська політехніка»


5.3. Арифметика вказівників
Вказівники можуть застосовуватися як операнди в арифметичних виразах, виразах присвоювання і виразах
порівняння. Проте, не усі операції, зазвичай використовувані у таких виразах, дозволені для вказівників.
З вказівниками може виконуватися обмежена кількість арифметичних операцій.
Вказівник можна збільшувати (++), зменшувати (––), додавати до вказівника цілі числа (+ чи +=), віднімати від нього
цілі числа (– чи –=) або віднімати один вказівник від іншого.
Здебільшого арифметичні операції застосовуються до вказівників на масиви.
Більше того, арифметика вказівників втрачає всякий сенс, якщо вона виконується не над вказівниками на масив.
Змінення (збільшення/зменшення) значення вказівника на масив по суті є зсувом адреси у межах цього масиву.
Перетворення цілої величини до адресного зсуву припускає, що у межах зсуву щільно розташовані елементи
однакового розміру. Це припущення справедливо для елементів масиву, оскільки масив визначається як набір
величин однаково типу, а його елементи розташовані у суміжних комірках пам’яті.
Додавання і віднімання адрес, які посилаються на будь-які величини, крім елементів масиву, дає непередбачуваний
результат, оскільки в такому разі не гарантується щільне заповнення пам’яті.

Національний університет «Львівська політехніка»


Розглянемо на прикладах дію арифметичних операцій на вказівники:
Додавання й віднімання цілого числа до вказівника відрізняється від звичайної арифметики. При їх виконанні
адреса, яка зберігається у вказівникові, змінюється кратно до розміру даних, на які вказує вказівник. Для оголошеного
вище масиву mass і вказівника ptr, який вказує на початок масиву, команда
ptr += 3;
збільшить адресу, яка міститься у ptr, на 4*3=12 байти, і ptr вказуватиме на четвертий елемент масиву зі значенням 40.
Загальна формула, яка обчислює значення вказівника при додаванні (відніманні) цілого числа N:
Нова адреса = стара адреса +/- розмір базового типу * N;
Операції інкремента (++) і декремента (--), тобто збільшення чи зменшення вказівника на 1. При цьому адреса, яка
зберігається у цьому вказівнику, збільшується на розмір базового типу в байтах. Наприклад, команда
ptr++;
збільшить його значення на 4, оскільки розмір змінної типа int зазвичай дорівнює 4, а сам вказівник вказуватиме на
наступний елемент масиву.
Зменшення вказівника на 1 означає віднімання розміру в байтах базового типу від адреси, яка зберігається у вказівнику.

Національний університет «Львівська політехніка»


Різниця однотипних вказівників. Вказівники можна віднімати один від одного. Наприклад, якщо ptr1 вказує на
перший елемент масиву mass, а вказівник ptr2 – на четвертий, то результат виразу ptr2 – ptr1 має тип int і
дорівнює 3 – різниці індексів елементів, на які вказують ці вказівники. І так буде, не дивлячись на те, що адреси, які
містяться у цих вказівниках, розрізняються на 12 байтів (якщо елемент масиву займає 4 байти).
Тобто, результат такої операції дорівнює кількості елементів вихідного типу між зменшуваним і від’ємником,
причому якщо перша адреса молодша, то результат має від’ємне значення.
Загальна формула при відніманні однотипних вказівників:
Кількість елементів = (перший вказівник – другий вказівник) / розмір типу
Наприклад:
int *ptr1, *ptr2, mass[5] = { 10,-2,0,40,3 }, i;
ptr1 = mass + 1;
ptr2 = mass + 4;
i = ptr1 - ptr2; /* дорівнює –3 */
i = ptr2 - ptr1; /* дорівнює 3 */

Національний університет «Львівська політехніка»


При порівнянні вказівників порівнюються адреси, які містяться у вка-зівниках, а результат порівняння дорівнює
0(false) або 1(true).
Порівняння вказівників операціями “>” , “<” , “>=” , “<=” мають сенс лише для вказівників на один той самий
масив.
Проте, операції відношення “==” та “!=” мають сенс для будь-яких вказівників.
При цьому вказівники є рівнозначними, якщо вони вказують на одну і ту саму адресу в пам’яті.
Наприклад, для розглянутих вище вказівників ptr1 та ptr2 у команді
if (ptr1 > ptr2) mass[3] = 4;
значення ptr1 є менше за значення ptr2 і тому оператор mass[3] = 4; не буде виконаний.
Приклад порівняння вказівника ptr з ненульовим значенням:
if (ptr != NULL) printf("\nnot null pointer\n");

Національний університет «Львівська політехніка»


Лістинг 5.3.: Вказівники
#include <stdio.h>
int main(void)
{
int Arr[3] = { 5, 6, 7 };
int* pi = &(Arr[0]);
/*-- Pointers arithmetic --*/
*pi = 1; pi++;
*pi = 1; pi++;
*pi = 1; pi++;
printf("%d %d %d\n", Arr[0], Arr[1], Arr[2]);
return 0;
}

Результати роботи програми:


1 1 1

Національний університет «Львівська політехніка»


5.4. Вказівники на багатовимірні масиви
Багатовимірні масиви у мові С – це масиви масивів, тобто такі масиви, елементами яких є масиви.
При оголошені таких масивів у пам’яті комп’ютера створюється декілька різних об’єктів.
Приміром, при виконанні оголошення двовимірного масиву int arr[4][3]; у пам’яті виділяється ділянка для
зберігання значення змінної arr, яка є вказівником на масив з чотирьох вказівників.
Для цього масиву з чотирьох вказівників теж виділяється пам’ять.
Кожний з цих чотирьох вказівників містить адресу масиву з трьох елементів типу int, і, отже, у пам’яті комп’ютера
виділяється чотири ділянки для зберігання чотирьох масивів чисел типу int, кожна з яких складається з трьох
елементів. Такий розподіл пам’яті показано на схемі:

arr

arr[0] arr[0][0] arr[0][1] arr[0][2]

arr[1] arr[1][0] arr[1][1] arr[1][2]

arr[2] arr[2][0] arr[2]1] arr[2][2]

arr[3] arr[3][0] arr[3][1] arr[3][2]

Національний університет «Львівська політехніка»


Отже, оголошення int arr[4][3]; породжує в програмі три різних об’єкти:
• вказівник з ідентифікатором arr,
• безіменний масив з чотирьох вказівників,
• безіменний масив з дванадцяти чисел типу int.
Для доступу до безіменних масивів використовується адресні вирази з вказівником arr. Доступ до елементів масиву
вказівників здійснюється із зазначенням одного індексного виразу у формі arr[2] чи *(arr + 2). Для доступу до
елементів двовимірного масиву чисел типу int має бути використано два індексні вирази у формі arr[1][2] чи
еквівалентних їй *(*(arr + 1) + 2) та (*(arr + 1))[2].
Нагадаємо, що елементи двовимірних масивів розміщуються у пам’яті підряд і між його рядками немає ніяких проміжків.
Такий порядок дає можливість звертатися до будь-якого елементу багатовимірного масиву, використовуючи адресу його
початкового елемента та лише один індексний вираз. Так для матриці arr звертання *(*(arr)) є посиланням на
елемент arr[0][0], звертання *((*arr) + 2)– на елемент arr[0][2], а звертання *(*(arr + 3))– на елемент
arr[3][0] і т. д.
Для звертання до елемента arr[3][2] також можна використовувати вказівник, оголошений як
int* p = &(arr[0][0]);
з одним індексним виразом у формі p[3 * 3 + 2] чи p[11].

Національний університет «Львівська політехніка»


5.5. Вказівники на функції
Хоча функція - це не змінна, але вона має своє місце в пам'яті (адресу), яке може бути присвоєно вказівникові.
Адреса, присвоєна вказівникові, є вхідною точкою в функцію. Вказівник може використовуватися замість імені функції.
Він також дозволяє передавати функцію як звичайний аргумент в інші функції.
Якщо ми маємо описано таку функцію:
int sum(int a, int b)
{
return a + b;
}}

То можемо описати вказівник на цю функцію:


int (*fptr)(int, int);
Тепер можемо використати вказівник на функцію у програмі:
fptr = sum;
int result = fptr(10, 40);

Національний університет «Львівська політехніка»


Лістинг 5.4.: Сортування масиву цілих чисел.
Бібліотечна функція qsort:
#include <stdlib.h>
void qsort(void* array, size_t n, size_t size, int (*compare)(const void*, const void*));

#include <stdio.h>
#include <stdlib.h>
int data_cmp(const void* p1, const void* p2);
int main(int argc, char* argv[])
{
int data[] = { 12, 4, 1961, 17, 7, 1969, 27, 8, 1856, 1, 9, 1939, 22, 6, 1942 };
int i; int datacount = sizeof(data) / sizeof(int);
printf("Before sorting:\n");
for (i = 0; i < datacount; i++)
printf("%6d\n", data[i]);
qsort(data, datacount, sizeof(int), data_cmp);
printf("\nAfter sorting:\n");
for (i = 0; i < datacount; i++)
printf("%6d\n", data[i]);
return 0;
}
int data_cmp(const void* p1, const void* p2)
{
int x = *(int*)p1; int y = *(int*)p2;
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
Національний університет «Львівська політехніка»
5.6. Динамічна пам’ять
Окрім звичайної пам’яті (стека), в якій автоматично розміщуються змінні за їхнього оголошення, існує ще й динамічна
пам’ять (heap − купа), в якій змінні можуть розміщуватися динамічно.
Це означає, що пам’ять виділяється під час виконання програми, й лише тоді, коли у програмі зустрінеться спеціальна
інструкція.
Основна потреба у динамічному виділенні пам’яті виникає, коли розмір або кількість даних заздалегідь є невідомі, а
визначаються в процесі виконання програми.
Існують функції, які дозволяють керувати динамічним розподілом пам’яті. Щоб їх використовувати треба підключити
бібліотеку:
#include <malloc.h>

Національний університет «Львівська політехніка»


Функція
void* malloc(size_t size);
(від англ. “memory allocation” – виділення пам’яті) робить запит до ядра операційної системи щодо виділення ділянки
пам’яті заданої кількості байтів.
Єдиний аргумент цієї функції size − кількість байтів, яку треба виділити.
Функція повертає вказівник на початок виділеної пам’яті.
Якщо для розміщення заданої кількості байтів є недостатньо пам’яті функція malloc() повертає NULL.
Вміст ділянки лишається незмінним, тобто там може залишитися “бруд”.
Якщо аргумент size дорівнює 0, функція повертає NULL.
Наприклад,
int* a = (int*)malloc(sizeof(int));
виділяє пам’ять під ціле число й адресу початку цієї ділянки пам’яті записує у вказівник a.
Виділення пам’яті під 10 дійсних чисел за допомогою цієї функції:
float* a = (float*)malloc(sizeof(float) * 10);

Національний університет «Львівська політехніка»


Функція
void* calloc(size_t num, size_t size);
виділяє блок пам’яті розміром num*size (під num елементів по size байтів кожен) і повертає вказівник на виділений
блок.
Кожен елемент виділеного блока ініціалізується нульовим значенням (на відміну від функції malloc()).
Функція calloc() повертає NULL, якщо не вистачає пам’яті для виділення нового блоку, або якщо значення num чи
size дорівнюють 0.
Виділення пам’яті під 10 дійсних чисел за допомогою функції calloc():
float* a = (float*)calloc(10, sizeof(float));
Функція
void* realloc( void* memblock, size_t size);
змінює розмір виділеного раніш блока пам’яті з адресою memblock на новий обсяг, який становитиме size байтів.
Якщо змінення відбулося успішно, функція realloc() повертає вказівник на виділену ділянку пам’яті, а інакше
повертається NULL.
Якщо memblock є NULL, функція realloc() виконує такі самі дії, що й malloc().
Якщо size є 0, виділений за адресою memblock блок пам’яті звільнюється – і функція повертає NULL.

Національний університет «Львівська політехніка»


Пам’ять, виділену за допомогою функцій malloc() і сalloc(), звільнюють за допомогою функції free():
void free(void* memblock);
де memblock – адреса початку виділеної раніше пам’яті, наприклад:
free(а);
Використання згаданих функцій разом з вказівниками надає можливість керувати розподілом динамічної пам’яті.
Якщо не звільняти динамічно виділену пам’ять, коли вона стає більш не потрібною, у системі може виникнути
нестача вільної пам’яті. Іноді це називають «витоком пам’яті».

Національний університет «Львівська політехніка»


Лістинг 5.5.: Динамічне виділення пам’яті
#include <stdio.h>
#include <malloc.h>
int main(void)
{
void* pv; int* pi; double* pd;
/*---- get dynamic memory ----*/
pv = malloc(12);
/*-- work with dynamic memory --*/
if (pv != 0) {
/*---- int-value ----*/
pi = (int*)pv;
*pi = 987;
/*-- 4-bytes shift --*/
pi++;
/*-- double-value --*/
pd = (double*)pi;
*pd = 3.14;
/*-- printing --*/
pi = (int*)pv;
printf("int-value=%d; double-value=%f", *pi, *pd);
free(pv);
}
return 0;
} Національний університет «Львівська політехніка»
У мові С++ є оператори для роботи з динамічною пам’яттю. Найчастіше використовується оператор new, який у
загальному вигляді записується як:
тип* ім’я_вказівника = new тип;
Приклади:
int* p = new int;
Тут виділяється місце в пам’яті під ціле число й адреса цієї ділянки пам’яті записується у змінну-вказівник p. Звернутися
до цього числа можна буде через вказівник на нього: *p = 2;
int* p = new int(5);
Ця команда не лише виділить місце у пам’яті під ціле число, а й запише в цю ділянку пам’яті значення 5. Адреса першої
комірки виділеної ділянки пам’яті присвоюється змінній-вказівнику p. Звернутися до числа, на яке вказує p, можна
аналогічно до попереднього прикладу.
Приміром, щоб збільшити таке число на 2, слід написати: (*p) += 2;
int* p = new int[5];
У цьому разі виділяється пам’ять під 5 цілих чисел, тобто фактично створюється так званий динамічний масив з 5-ти
елементів. Звернутися до кожного з цих чисел можна за його номером: p[0], p[1] і т. д. або через вказівник: *p − те ж
саме, що p[0], *(p + 1) − те ж саме, що p[1] і т. д.
int* p = new int[5]{ 1, 2, 3, 4, 5 };
У цьому разі виділяється пам’ять під 5 цілих чисел і вони ініціалізуються значеннями від 1 до 5.

Національний університет «Львівська політехніка»


Пам’ять, яку було виділено динамічно, автоматично не звільнюється, тому програміст повинен обов’язково звільнити
її самостійно за допомогою спеціальної команди.
При виділенні пам’яті за допомогою оператора new, для звільнення пам’яті використовується delete :
delete вказівник;
Наприклад:
delete a;
Якщо оператором new було виділено пам’ять під кілька значень водночас (як у розглянутому вище прикладі 3),
використовується наступна форма команди delete
delete []вказівник;
Квадратні дужки повинні бути порожніми, операційна система контролює кількість виділеної пам’яті і при звільненні їй
відома потрібна кількість байтів.
Наприклад:
delete []p;

Національний університет «Львівська політехніка»


5.7. Динамічні одновимірні масиви
Відмінності динамічного масиву від звичайного полягають у тому, що:
• пам’ять під динамічний масив виділяється динамічно за допомогою вищерозглянутих функцій;
• кількість елементів динамічного масиву може бути задано змінною (але у програмі її неодмінно має бути визначено
до виділення пам’яті під масив).
Синтаксис оголошення динамічного одновимірного масиву за допомогою оператора new є такий:
базовий тип* ім’я = new базовий тип[кількість елементів];
Приклад оголошення дійсного динамічного масиву зі змінною кількістю елементів:
int N = 10;
float* a = new float[N];
Тут кількість елементів масиву є змінною і зберігається в N.
Звільнення пам’яті від цього масиву a матиме вигляд:
delete []a;

Національний університет «Львівська політехніка»


Синтаксис оголошення динамічного одновимірного масиву за допомогою функції malloc() є такий:
тип* ім’я = (тип*)malloc(sizeof(тип) * кількість_ елементів);
Приклад оголошення дійсного динамічного масиву зі змінною кількістю елементів за допомогою функції malloc() :
int N = 10;
float* a = (float*)malloc(sizeof(float) * N);
Приклад оголошення дійсного динамічного масиву зі змінною кількістю елементів за допомогою функції calloc():
int N = 10;
float* a = (float*)calloc(N, sizeof(float));
Звільнення пам’яті з-під цього масиву a:
free(a);
Існує можливість звертатися до елементів масиву без індексації за допомогою вказівників.
У циклі за допомогою індексу поточний елемент масиву записується як a[i].
Згадаймо, що ім’я масиву а можна використовувати як вказівник на початок (нульовий елемент) масиву.
Тоді, згідно з арифметикою вказівників, a + i – це вказівник на елемент, який міститься на i комірок далі від початку
масиву, тобто вказівник на a[i].
Значення елемента a[i] можна записати за допомогою операції розадресації: *(a + i).

Національний університет «Львівська політехніка»


Лістинг 5.6.: Динамічні масиви
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
int main(void)
{
int* pi; int N, i;
printf("Enter the number of int value: ");
scanf("%d", &N);
/*---- get dynamic memory ----*/
pi = (int*)malloc(N * sizeof(int));
/*-- work with dynamic memory --*/
if (pi != 0) {
for (i = 0; i < N; i++)
pi[i] = i + 5;
for (i = 0; i < N; i++)
printf("int-value for %d index is %d;\n",
i, pi[i]);
free(pi);
}
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 5.7.: Програма для сортування динамічного масиву методом «бульбашки»
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int* CreateArray(int n)
{
int* a = (int*)malloc(n * sizeof(int));
return a;
}
void DeleteArray(int* a)
{
free(a);
}
void BubbleSort(int x[], int size)
{
int i, j, temp;
for (i = 1; i < size; i++)
for (j = 0; j < (size - i); j++)
if (x[j] > x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}

Національний університет «Львівська політехніка»


Лістинг 5.7.: Програма для сортування динамічного масиву методом «бульбашки»
void PrintArray(int x[], int size)
{
for (int i = 0; i < size; i++)
printf("%6d\t", x[i]);
printf("\n");
}
void FillArray(int x[], int size)
{
for (int i = 0; i < size; i++)
x[i] = rand() % 100;
}
int main(void)
{
int N, *A;
printf("Enter the size of the array:");
scanf("%d", &N);
A = CreateArray(N);
FillArray(A, N);
printf("Array before sorting:\n");
PrintArray(A, N);
BubbleSort(A, N);
printf("Array after sorting:\n");
PrintArray(A, N);
DeleteArray(A);
return 0;
}
Національний університет «Львівська політехніка»
5.8. Динамічні двовимірні масиви
Двовимірний динамічний масив з m рядків і n стовпчиків займає в пам’яті сусідні m*n комірок, тобто зберігається так
само, як і одновимірний масив з m*n елементів.
При розміщенні елементи двовимірних масивів розташовуються в пам’яті підряд один за одним з першого до
останнього без проміжків у послідовно зростаючих адресах пам’яті.
Наприклад, масив 3×5 зберігається у пам’яті в такий спосіб:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0-й рядок 1-й рядок 2-й рядок

У такому масиві перші п’ять елементів належать до першого рядка, наступні п’ять – до другого і останні п’ять – до
третього.

Національний університет «Львівська політехніка»


Нагадаємо, що a – вказівник на початок масиву, тобто на елемент a[0][0].
Щоб звернутися, наприклад, до елемента a[1][3], слід “перестрибнути” від початку масиву через 5 елементів
нульового рядка й 3 елементи першого рядка, тобто написати: *(a + 1 * 5 + 3).
У загальному випадку до елемента a[i][j] можна звернутися в такий спосіб: *(a + i * 5 + j).
Але цей спосіб роботи з двовимірним масивом є не надто зручний, тому що в програмі при звертанні до елемента
масиву доводиться розадресовувати вказівник і обчислювати індекс елемента.
Оголосити дійсний динамічний масив 3×5 можна як одновимірний з 15-ти елементів:
float* a = (float*)malloc(3 * 5 * sizeof(float));
або
float* a = (float*)сalloc(3 * 5, sizeof(float));
або
float* a = new float[3 * 5];
Пам’ять від створеного в такий спосіб масиву очищується за допомогою операцій відповідно delete і free:
free(a);
або
delete []a;

Національний університет «Львівська політехніка»


Розглянемо інший спосіб роботи з динамічним двовимірним масивом. Для цього розмістимо в пам’яті матрицю 3х5:

0 0 1 2 3 4 0-й рядок

1 0 1 2 3 4 1-й рядок

2 0 1 2 3 4 2-й рядок

При цьому буде виділено пам’ять під кожний рядок матриці окремо, тобто буде утворено три різні одновимірні
масиви.
Адреси нульових елементів цих масивів зберігатимуться в допоміжному масиві a (пам’ять під нього слід виділити
заздалегідь).
Елементами цього масиву будуть адреси дійсних чисел, тому вони матимуть тип “вказівник на дійсне число”, тобто
float*.
Нагадаємо, що загальний вигляд оголошення вказівника на динамічний масив є такий:
тип елемента* ім’я масиву;

Національний університет «Львівська політехніка»


Тоді при оголошенні масиву а слід записати дві зірочки:
float** a = (float**)malloc(3 * sizeof(float*));
// Оголошення й розміщення в пам’яті допоміжного масиву з 3-х елементів типу float*
for (int i = 0; i < 3; i++)
a[i] = (float*)malloc(5 * sizeof(float));
// У циклі виділяється пам’ять під 3 масиви по 5 елементів (рядки матриці) й адреси
// Значення адрес нульових елементів цих масивів записуються у відповідні елементи масиву a

Після цього можна працювати з матрицею як зі звичайним двовимірним масивом, звертаючись до кожного елемента
за його індексом, наведеним у квадратних дужках: a[i][j], – що є більш природним і зручним, аніж попередній
спосіб.
Звільнення пам’яті необхідно виконувати в зворотному порядку – спочатку звільнити пам’ять під рядки в циклі, а потім
звільнити пам’ять, що виділялась під масив вказівників.
for (int i = 0; i < 3; i++)
free(a[i]);
free(a);

Національний університет «Львівська політехніка»


Аналогічним є оголошення за допомогою сalloc():
float** a = (float**)calloc(3, sizeof(float*));
for (int i = 0; i < 3; i++)
a[i] = (float*)calloc(5, sizeof(float));
Звільнення пам’яті аналогічне як при роботі з функцією malloc().
І за допомогою оператора new: виділення пам’яті:
float** a = new float* [3];
for (int i = 0; i < 3; i++)
a[i] = new float[5];
звільнення пам’яті:
for (int i = 0; i < 3; i++)
delete []a[i];
delete []a;
Для роботи з динамічними двовимірними масивами напишемо функції створення масиву, заповнення його
випадковими значеннями, друку елементів масиву на екран, звільнення виділеної пам’яті під масив, функцію
множення матриці на матрицю для тестування розроблених функцій.
Усі функції працюють з двовимірними масивами розміром m рядків на n стовпців.

Національний університет «Львівська політехніка»


Лістинг 5.9.: Програма для множення двох матриць з допомогою динамічних двовимірних
#define _CRT_SECURE_NO_WARNINGS масивів
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int** CreateMatrix(int m, int n)
{
int** a = (int**)malloc(m * sizeof(int*));
for (int i = 0; i < m; i++)
a[i] = (int*)malloc(n * sizeof(int));
return a;
}
void DeleteMatrix(int** a, int m, int n)
{
for (int i = 0; i < m; i++)
free(a[i]);
free(a);
}
void FillMatrix(int** a, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
a[i][j] = rand() % 10;
} Національний університет «Львівська політехніка»
Лістинг 5.9.: Програма для множення двох матриць з допомогою динамічних двовимірних
масивів
void PrintMatrix(int** a, int m, int n)
{
int i, j;
printf("\n");
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++)
printf("%d\t", a[i][j]);
printf("\n");
}
}
void MultMatrix(int** a, int** b, int** c, int m, int n, int k)
{
int i, j, l;
for (i = 0; i < m; i++)
for (j = 0; j < k; j++)
{
c[i][j] = 0;
for (l = 0; l < n; l++)
c[i][j] = c[i][j] + a[i][l] *b[l][j];
}
}
Національний університет «Львівська політехніка»
Лістинг 5.9.: Програма для множення двох матриць з допомогою динамічних двовимірних
масивів
int main(void)
{
int M, N, K, **A, **B, **C;
printf("Еnter the matrix sizes (m, n, k):");
scanf("%d%d%d", &M, &N, &K);
A = CreateMatrix(M, N);
FillMatrix(A, M, N);
printf("Matrix A:\n");
PrintMatrix(A, M, N);
B = CreateMatrix(N, K);
FillMatrix(B, N, K);
printf("Matrix B:\n");
PrintMatrix(B, N, K);
C = CreateMatrix(M, K);
MultMatrix(A, B, C, M, N, K);
printf("Matrix C:\n");
PrintMatrix(C, M, K);
DeleteMatrix(A, M, N);
DeleteMatrix(B, N, K);
DeleteMatrix(C, M, K);
return 0;
}
Національний університет «Львівська політехніка»
Лістинг 5.9.: Програма для множення двох матриць з допомогою динамічних двовимірних
масивів
Результати роботи програми:

Enter the size of the matrices (m, n, k):2 3 4


Matrix A:

1 7 4
0 9 4
Matrix B:

8 8 2 4
5 5 1 7
1 1 5 2
Matrix C:

47 47 29 61
49 49 29 71

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua
Лекції №24, 25, 26.
Тема №6. РЯДКИ ТА ОБРОБКА ТЕКСТІВ
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
6.1. Дані типу char і символьні константи
в ASCII коді
Символами вважаються: великі й малі літери, цифри, знаки арифметичних дій ('+‘, '–', '*', '/', '='), пробіл,
розділові знаки ('.' , ',' , ';' , ':' , '!' , '?' , '–'), службові символи, що відповідають клавішам <Enter>,
<Esc>, <Tab> тощо.
В С значення символьних констант записуються у одинарних лапках: '3' , 'f' , '+' , '%'.
Як було зазначено раніше для кодування усіх символів використовується восьмирозрядна послідовність 0 і 1, тобто
один байт. Наприклад: символ цифри '9' кодується послідовністю бітів 00111001, символ літери латиниці 'W' –
01010111. За допомогою одного байта можна закодувати 256 різних комбінацій бітів, а отже, 256 різних символів.
Щоб не було розходжень у кодуванні символів, існує єдиний міжнародний стандарт – так звана таблиця ASCII-кодів
(American Standard Code for Information Interchange – американський стандартний код для обміну інформацією, див.
додаток A).
Символи ASCII мають коди від 0 до 127, тобто значення першої половини можливих значень байта, хоча часто кодами
ASCII називають всю таблицю з 256 символів. Перші 128 ASCII-кодів є єдині для всіх країн, а коди від 128 до 255
називають розширеною частиною таблиці ASCII, де залежно від країни розташовується національний алфавіт і
символи псевдографіки.

Національний університет «Львівська політехніка»


У таблиці ASCII всі символи пронумеровано, тобто вони мають власний унікальний код. Так само як у кожній мові
людського спілкування існує алфавіт (перелік усіх літер у чітко визначеному порядку), усі комп’ютерні символи теж є
суворо упорядкованими. Символ ' ' (пробіл) має код 32, цифри мають коди від 48 для '0' до 57 для '9', великі
латинські літери – від 65 для 'A' до 90 для 'Z', малі літери латиниці – від 97 для 'a' до 122 для 'z'.
Кодування другої половини таблиці ASCII має різні варіанти.
Найпоширенішими є DOS-кодування (866 кодова сторінка) і кодування 1251, яке є основним для Windows.
Звернімо увагу на різницю поміж цифрами і їхнім символьним зображенням. Наприклад, символ цифри ‘4' має ASCII-код
52 і не має безпосереднього відношення до числа 4.
Керувальні символи таблиці ASCII не мають символьного подання, тобто не мають візуального зображення, тому їх інколи
називають недрукованими (non-printed), наприклад: <Esc>, <Enter>, <Tab> тощо. Ці символи розташовано в перших 32-х
кодах таблиці ASCII-кодів. Звертатися до таких символів можна через їхній код чи за допомогою так званої ескейп-
послідовності (escape).
Ескейп-послідовність – це спеціальна комбінація символів, яка розпочинається зі зворотної косої риси і записується в
одинарних лапках, наприклад: '\0' , '\n'.
Кожна з наведених у таблиці комбінацій символів вважається за один символ.

Національний університет «Львівська політехніка»


Таблиця 6.1 – Деякі поширені у застосуванні ескейп-послідовності

Символьне подання Опис


\n символ переведення курсора на початок наступного рядка
\r переведення каретки
\t символ переведення курсора на наступну позицію табуляції (відповідає клавіші <Tab>)
\b символ вилучення попереднього символу перед курсором (відповідає клавіші <BackSpace>)
\a символ звукового сигналу системного динаміка
\\ символ \ (зворотна скісна риса)
\? символ ? (знак запитання)
\' символ ' (одинарні лапки)
\" символ " (подвійні лапки)
\0 символ з кодом 0 є завершальним символом рядка символів
\Десяткове число символ, код якого зазначено у десятковій системі числення
\0Вісімкове число символ, код якого зазначено у вісімковій системі числення
\0хШістнадцяткове число символ, код якого зазначено у шістнадцятковій системі числення

Національний університет «Львівська політехніка»


Тип символьних змінних у С називається char. Так, при оголошенні
char c, s, g;
в оперативній пам’яті для кожної з цих трьох змінних буде відведено по одному байту. Коли символьні змінні
набувають певних значень, то комп’ютер зберігатиме в пам’яті не власне символи, а їхні коди. Наприклад, замість
літери 'A' зберігатиметься її код 65. Тому, якщо присвоїти символьній змінній певне число, то С сприйме його як
код символу з таблиці ASCII-кодів. Зважаючи на різні кодування розширеної частини ASCII-таблиці для уникнення
помилок вважається за оптимальне при роботі з символами використовувати їхнє символьне подання замість
кодів. Наприклад, при оголошенні
char c = 115;
змінна c набуде значення символу 's', який має код 115.
Зауважимо, що у С, на відміну від більшості мов програмування, дані типу char змінюються у діапазоні –128 ... 127,
причому додатні числа 0 ... 127 зайняті символами спільної частини ASCII-таблиці, а символи розширеної частини
ASCII-таблиці у С відповідають від’ємним числам. Наприклад, літера кирилиці 'ч' – має код –9, а кодом літери
'я' є –1 (для таблиці 1251).

Національний університет «Львівська політехніка»


Окрім типу char, існує його беззнакова модифікація unsigned char. Дані типу unsigned char мають значення у
діапазоні 0 ... 255. В ASCII-таблиці значення кодів літер кирилиці є більшими за 127, тому, якщо треба мати справу
зі змінними, значеннями яких є літери кирилиці, їх бажано оголошувати типом unsigned char :
unsigned char c;
Символи можна порівнювати. Більшим вважається той символ, у якого код є більший, тобто символ,
розташований у таблиці ASCII-кодів пізніше. Наприклад: 'a' < 'h', 'A' < 'a'.
Оскільки символьний тип char вважається у С за цілий тип, змінні цього типу можна додавати й віднімати.
Результатом додавання буде символ, код якого дорівнює сумі кодів символів-доданків, наприклад:
char c = 'A';
char c1 = c + 5; // c1 = 'F'
char c2 = c + 32; // c2 = 'a'
char c3 = c - 10; // c2 = '7

Національний університет «Львівська політехніка»


Лістинг 6.1.: ASCII-код –> символ
#include <stdio.h>
int main(void)
{
char x = 65, y = 92;
printf("ASCII-code %d is %c symbol\n", x, x);
printf("ASCII-code %d is %c symbol\n", y, y);
return 0;
}

Лістинг 6.2.: ASCII-код –> символ (2)


#include <stdio.h>
int main(void)
{
char x = 'A', y = '\\';
printf("ASCII-code %d is %c symbol\n", x, x);
printf("ASCII-code %d is %c symbol\n", y, y);
return 0;
}

Результати роботи програми:


ASCII-code 65 is A symbol
ASCII-code 92 is \ symbol
Національний університет «Львівська політехніка»
Лістинг 6.3.: ASCII-код –> символ (3)
#include <stdio.h> Результати роботи програми:
int main(void) ASCII code ASCII-symbol
{ --------------------------
char i; 33 !
34 "
printf("ASCII code ASCII-symbol\n"); 35 #
printf("------------------------\n"); 36 $
for (i = 33; i < 50; i++) 37 %
printf("%6d %10c\n", i, i); 38 &
39 '
return 0;
40 (
} 41 )
42 *
43 +
44 ,
45 -
46 .
47 /
48 0
49 1

Національний університет «Львівська політехніка»


Лістинг 6.4.: ASCII-коди керуючих символів
#include <stdio.h>
int main(void)
{
char x = '\n', y = '\t';
printf("Symbol \\n has code %d\n", x);
printf("Symbol \\t has code %d\n", y);
return 0;
}

Результати роботи програми:


Symbol \n has code 10
Symbol \t has code 9

Національний університет «Львівська політехніка»


6.2. Масиви типу char та рядки мови С
Для опрацювання символьної інормації можна скористатися масивом символів.

Лістинг 6.5.: Масиви типу char


#include <stdio.h>
int main(void)
{
int i, size;
char myT[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd' };
printf("Words in char-array\n");
printf("--------------------\n");
size = sizeof(myT) / sizeof(char);
for (i = 0; i < size; i++)
printf("%c", myT[i]);
return 0;
}

Результати роботи програми:


Words in char-array
--------------------
Hello, world
Національний університет «Львівська політехніка»
Лістинг 6.6.: Операції із масивом символів
#include <stdio.h>
/*--- Function prototype ---*/
void ReversePhrase(char ar[], int size);
int main(void)
{
int i, size;
char myT[]={'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd'};
size = sizeof(myT) / sizeof(char);
ReversePhrase(myT, size);
printf("Words in reverse symbol order\n");
printf("-----------------------------\n");
for (i = 0; i < size; i++)
printf("%c", myT[i]);
return 0;
}
void ReversePhrase(char ar[], int size)
{
int i; char temp;
for (i = 0; i < size / 2; i++)
{
temp = ar[size - 1 - i];
ar[size - 1 - i] = ar[i];
ar[i] = temp;
}
}

Національний університет «Львівська політехніка»


Символьні рядки можуть зберігати яку завгодно символьну інформацію, приміром: імена файлів, назви книг, імена
працівників та інші символьні сполучення.
Рядки у С являють собою послідовність (масив) символів із завершальним нуль-символом.
Нуль-символ (нуль-термінатор) – це символ з кодом 0, який записується у вигляді керувальної послідовності '\0'.
За розташуванням нуль-символу визначається фактична довжина рядка.
Відмінною рисою рядка є те, що в ньому насправді може бути менше символів, аніж зазначено при оголошенні.
Окрім того, з рядками можна виконувати певні специфічні дії, які не можна здійснювати з числовими масивами
(наприклад перевіряти наявність у масиві літери чи послідовності літер, копіювати масив як одне ціле, порівнювати
масиви за алфавітом, дописувати один масив наприкінці іншого тощо).
Пам’ять під розміщення рядків, як і для будь-яких масивів, може виділятися як компілятором, так і динамічно – при
виконуванні програми.
Довжина динамічного рядка може задаватися змінною з визначеним заздалегідь значенням, а довжина статичного
рядка має задаватися лише константою.

Національний університет «Львівська політехніка»


Рядок може бути оголошеним в один з нижче наведених способів:
1.
char* s; // Оголошення вказівника на перший символ рядка;
// пам’ять під сам рядок не виділяється
2.
char ss[32]; // Оголошення рядка ss з 31-го символа;
// пам’ять виділяється компілятором
3.
int n;
scanf("%d", &n);
char* str = new char[n];
// Оголошення рядка str з n-1 символів, пам’ять виділяється динамічно
При зазначенні довжини рядка слід враховувати завершальний нуль-символ.
Зауважимо, що при оголошенні рядка першим способом пам’ять під рядок не виділяється і це може бути дуже
небезпечним, оскільки до тієї самої ділянки пам’яті може бути розміщено інші змінні й рядок буде втрачено.

Національний університет «Львівська політехніка»


При оголошенні рядок можна ініціалізувати рядковою константою, при цьому нуль-символ формується автоматично
після останнього символу:
char str[10] = "world";

str w o r l d \0 \0 \0 \0 \0

При цьому виділиться пам’ять під масив з 9-ти елементів та 10-й – нуль-символ (всього 10 байт) і перші 5 символів
рядка записуються в перші 5 байт цієї пам’яті (str[0] = 'w', str[1] = 'o', str[2] = 'r', str[3] = 'l',
str[4] = 'd'), а в шостий елемент str[5] записується нуль-символ. Якщо рядок при оголошенні ініціалізується,
його розмірність можна опускати (компілятор сам виділить потрібну кількість байтів):
char str[] = "world"; // Виділено й заповнено 6 байтів

str w o r l d \0

Національний університет «Львівська політехніка»


Рядки у лапках завжди неявно містять нуль-символ, тому при ініціалізації прописувати його немає потреби.
Окрім того, різні наведені способи введення символьних масивів автоматично долучають нуль-символ у кінець масиву.
При оголошенні й ініціалізації рядка слід бути впевненим, що розмір масиву є достатній, щоб умістити всі символи
рядка з нуль-символом. Річ у тім, що функції, які опрацьовують рядки, керуються позицією нуль-символу, а не
розміром рядка. С не накладає жодних обмежень на довжину рядка.
Звернімо увагу на те, що рядкова константа (у подвійних лапках) і символьна константа (в одинарних лапках) не є
взаємозамінними. Це константи різних типів.
Символ у одинарних лапках, наприклад, 's' є символьною константою. Для зберігання такої константи компілятор C
виділяє лише один байт пам’яті.
Символ у подвійних лапках, наприклад, "s" є рядковою константою, що окрім символу 's' містить символ '\0', який
долучається компілятором. Більш того, "s" фактично являє собою адресу пам’яті, в якій зберігається рядок.
Як і числові масиви, рядки опрацьовуються поелементно у циклі. Операція присвоювання одного рядка іншому є
невизначена (оскільки рядок є масивом) і може виконуватися за допомогою циклу чи за допомогою функцій
стандартної бібліотеки.

Національний університет «Львівська політехніка»


Лістинг 6.7.: Рядки мови С
#include <stdio.h>
/*--- Function prototype ---*/
void ReversePhrase(char ar[]);
int main(void)
{
char myT[] = "Hello, world";
ReversePhrase(myT);
printf("Words in reverse symbol order\n");
printf("-----------------------------\n");
printf("%s", myT);
return 0;
}
void ReversePhrase(char ar[])
{
int i = 0, size = 0;
char temp;
while (ar[i] != 0) i++;
size = i;
for (i = 0; i < size / 2; i++)
{
temp = ar[size - 1 - i];
ar[size - 1 - i] = ar[i];
ar[i] = temp;
}
}

Національний університет «Львівська політехніка»


6.3. Ввід-вивід рядків.
Функція gets():
char* gets(char* buffer);
зчитує символи з клавіатури до появи символу переведення рядка <Enter> і записує їх у рядок *buffer (власне
символ <Enter> до рядка не долучається, а замість нього записується нуль-символ). Ця функція небезпечна і
починаючи з Visual Studio 2015 вона недоступна. Існує безпечна версія цієї функції:
char* gets_s(char* buffer, size_t sizeInCharacters);
де параметр sizeInCharacters вказує максимальний розмір рядка.
Функція puts() :
int puts(const char* str);
виводить рядок str на екран, замінюючи нуль-символ на <Enter>.
Ввід-вивід рядка можна здійснювати за допомогою функцій scanf() i printf(), де в якості специфікатора треба
вказати "%s". При використанні функції scanf() варто вказувати максимальний розмір рядка ("%10s"), щоб не
виникло переповнення буфера. Функція scanf() вважає рядком усе до появи розділювача (пробіл, табуляція, тощо),
тому варто користуватись функцією gets_s() .

Національний університет «Львівська політехніка»


Лістинг 6.8.: Вивід рядків
#include <stdio.h>
int main(void)
{
char str1[] = "Hello, ";
char str2[] = "world";
printf("%s%s", str1, str2);
return 0;
}

Результати роботи програми:


Hello, world

Національний університет «Львівська політехніка»


Лістинг 6.9.: Вивід рядків (2)
#include <stdio.h>
int main(void)
{
char str1[] = "Hello, \n";
char str2[] = "world";
printf("%s%s", str1, str2);
return 0;
}

Результати роботи програми:


Hello,
world

Національний університет «Львівська політехніка»


Лістинг 6.10.: Вивід рядків (3)
#include <stdio.h>
int main(void)
{
char str1[] = "Hello, \n";
char str2[] = { 'h', 'j' };
printf("%s%s", str1, str2);
return 0;
}

Результати роботи програми:


Hello,
hjММHello,

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

str1 H e l l o , \32 \n \0
str2 h j

Національний університет «Львівська політехніка»


Лістинг 6.11.: Ввід рядків, scanf()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
char str1[128], str2[128];
printf("Enter two strings, please: ");
scanf("%s%s", str1, str2);
printf("\n%s %s", str1, str2);
return 0;
}

Результати роботи програми:


Enter two strings, please: Kharkiv Zhovti Vody
Kharkiv Zhovti

Національний університет «Львівська політехніка»


Лістинг 6.12.: Ввід рядків, gets()
#include <stdio.h>
int main(void)
{
char str1[128], str2[128];
printf("Enter two strings, please: ");
gets_s(str1, sizeof(str1)); //gets(str1);
gets_s(str2, sizeof(str2)); //gets(str2);
printf("\n%s %s", str1, str2);
return 0;
}

Результати роботи програми:


Enter two strings, please:
Kharkiv
Zhovti Vody

Kharkiv Zhovti Vody

Національний університет «Львівська політехніка»


6.4. Стандартні бібліотечні функції
для роботи з рядками
С має багату колекцію функцій опрацювання рядків із завершальним нулем. Якщо в рядку відсутній нуль-термінатор,
опрацювання рядка може тривати скільки завгодно, допоки в пам’яті не зустрінеться '\0'. У якості аргументів до
функцій переважно передаються вказівники на рядки. Якщо при виконуванні функції здійснюється перенесення
символів рядка з місця-джерела до місця-призначення, для рядка-призначення слід завчасно зарезервувати місце в
пам’яті. Копіювання рядків з використанням просто вказівника, а не адреси початку завчасно оголошеного масиву –
одна з найпоширеніших помилок програмування, навіть у досвідчених програмістів. При виділенні місця для рядка-
призначення слід виділяти місце і для нуль-термінатора.
У наступній таблиці наведено функції стандартної бібліотеки для роботи з С-рядками. Для використання цих функцій
слід залучити до програми бібліотеку <string.h>.

Національний університет «Львівська політехніка»


Таблиця 6.2 – Функції стандартної бібліотеки <string.h>
Призначення Функція
повертає довжину рядка str (без урахування символу завершення size_t strlen(const char* str);
рядка)
Дописує до рядка strDestination рядок strSource і, як результат, char* strcat(
повертає strDestination. Не перевіряє чи достатньо місця в рядку char* strDestination,
strDestination. const char* strSource);
Безпечна версія функції strcat(). Параметр numberOfElements – це errno_t strcat_s(
максимальний розмір рядка strDestination. Повертає 0, якщо char* strDestination,
дописування відбулось успішно, або код помилки в іншому випадку. size_t numberOfElements,
const char* strSource);
Дописує до рядка strDestination count символів рядка strSource char* strncat(
і, як результат, повертає strDestination. Не перевіряє чи достатньо char* strDestination,
місця в рядку strDestination. const char* strSource,
size_t count);
Безпечна версія функції strncat(). Параметр numberOfElements – errno_t strncat_s(
це максимальний розмір рядка strDestination. Повертає 0, якщо char* strDestination,
дописування відбулось успішно, або код помилки в іншому випадку. size_t numberOfElements,
const char* strSource,
size_t count);

Національний університет «Львівська політехніка»


Таблиця 6.2 – Функції стандартної бібліотеки <string.h>
Призначення Функція
перетворює всі латинські літери рядка str до нижнього регістру (малі char* _strlwr(char* str);
букви).
перетворює всі латинські літери рядка str до верхнього регістру char* _strupr(char* str);
(великі букви).
порівнює рядки string1 і string2 і повертає нульове значення, якщо int strcmp(
рядки є однакові (string1), від’ємне (string1 < string2) чи додатне const char* string1,
(string1 > string2). const char* string2);
на відміну від попередньої функції, порівнює лише перші count int strncmp(
символів рядків string1 і string2. const char* string1,
const char* string2,
size_t count);
порівнює два рядки, не розрізнюючи прописні й малі літери латиниці. int _stricmp(
const char* string1,
const char* string2);
порівнює лише перші count символів двох рядків, не розрізняючи int _strnicmp(
великі й малі літери латиниці const char* string1,
const char* string2,
size_t count);

Національний університет «Львівська політехніка»


Таблиця 6.2 – Функції стандартної бібліотеки <string.h>
Призначення Функція
копіює рядок strSource до рядка strDestination, при цьому char* strcpy(
попереднє значення strDestination втрачається. char* strDestination,
const char* strSource);
Безпечна версія функції strcpy(). Параметр dest_size – це errno_t strcpy_s(
максимальний розмір рядка dest. Повертає 0, якщо дія успішна, або char* dest,
код помилки в іншому випадку. rsize_t dest_size,
const char* src);
копіює перші count символів рядка strSource в перші count символів char* strncpy(
рядка strDest. Якщо count менше за фактичний розмір рядка char* strDest,
strSource, то нуль-символ не дописується. const char* strSource,
size_t count);
Безпечна версія функції strncpy(). Параметр numberOfElements – errno_t strncpy_s(
це максимальний розмір рядка strDest. Повертає 0, якщо дія char* strDest,
відбулась успішно, або код помилки в іншому випадку. size_t numberOfElements,
const char* strSource,
size_t count);
відшукує перше входження символу c в рядку str і повертає char* strchr(
вказівник на цей символ, тобто частину рядка str, розпочинаючи з const char* str,
символу c і до кінця рядка. Якщо символу c в рядку str немає, int c);
результат – NULL.
Національний університет «Львівська політехніка»
Таблиця 6.2 – Функції стандартної бібліотеки <string.h>
Призначення Функція
відшукує останнє входження символу c в рядку str і повертає char* strrchr(
частину рядка str, розпочинаючи з останнього входження символу c const char* str,
і до кінця рядка. Якщо символу c в рядку s немає, результат – NULL. int c);
відшукує підрядок strSearch в рядку str, повертає частину рядка char* strstr(
str, розпочинаючи з першого спільного символу для обох рядків і до const char* str,
кінця str. const char* strSearch);
замінює усі символи рядка str на символ c. char* _strset(
char* str,
int c);
Безпечна версія функції _strset(). Параметр numberOfElements – errno_t _strset_s(
це максимальний розмір рядка str. Повертає 0, якщо дія відбулась char* str,
успішно, або код помилки в іншому випадку. size_t numberOfElements,
int c);
замінює лише перші count символів рядка str на символ c. char* _strnset(
char* str,
int c,
size_t count);

Національний університет «Львівська політехніка»


Таблиця 6.2 – Функції стандартної бібліотеки <string.h>
Призначення Функція
Безпечна версія функції _strnset(). Параметр numberOfElements – errno_t _strnset_s(
це максимальний розмір рядка str. Повертає 0, якщо дія відбулась char* str,
успішно, або код помилки в іншому випадку. size_t numberOfElements,
int c,
size_t count);
відшукує місце першого входження у рядок str будь-якого символу char* strpbrk(
рядка strCharSet і повертає частину рядка str, розпочинаючи з const char* str,
цього символу і до кінця рядка. const char* strCharSet);
реверс рядка str. char* _strrev(char* str);
повертає частину (лексему) рядка strToken від поточної позиції char* strtok(
вказівника до розділового символу, зазначеного у рядку strDelimit; char* strToken,
зазвичай використовується для перетворювання рядка на const char* strDelimit);
послідовність лексем.

Національний університет «Львівська політехніка»


Лістинг 6.13.: Бібліотечні функції для роботи з рядками
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[128] = "Zhovti Vody";
char str2[] = "L'viv";
printf("str1 --> %s\n", str1);
strcpy(str1, str2);
printf("str1 --> %s\n", str1);
return 0;
}
При роботі даної програми працюємо з двома змінними – рядком str1 і str2 :

str1 Z h o v t i \32 V o d y \0 ... \0


str2 L v i v \0
Після виконання функції strcpy() в рядку str1 буде наступне:

str1 L v i v \0 \32 V o d y \0 ... \0


Результати роботи програми:
str1 --> Zhovti Vody
str1 --> L'viv
Національний університет «Львівська політехніка»
Лістинг 6.14.: Порожні рядки
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "L'viv";
/*---- 1st method ----*/
str[0] = 0;
printf("1. str --> %s\n", str);
str[0] = 'L';
printf("2. str --> %s\n", str);
/*---- 2nd method ----*/
strcpy(str, "");
printf("3. str --> %s\n", str);
str[0] = 'L';
printf("4. str --> %s\n", str);
return 0;
}
Результати роботи програми:
1. str -->
2. str --> L'viv
3. str -->
4. str --> L'viv
Національний університет «Львівська політехніка»
Лістинг 6.15.: Конкатенація рядків
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[128] = "Zhovti Vody";
char str2[] = "L'viv";
int len;
len = strlen(str1);
printf("Before concatenation:\n str1 --> %s len = %d\n", str1, len);

/*--- Concatenation ---*/


strcat(str1, " ");
strcat(str1, str2);
len = strlen(str1);
printf("After concatenation:\n str1 --> %s len = %d\n", str1, len);
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 6.15.: Конкатенація рядків

При роботі даної програми працюємо з двома змінними – рядком str1 і str2 :

str1 Z h o v t i \32 V o d y \0 ... \0


str2 L v i v \0

Після виконання функції strcat() в рядку str1 буде наступне:

str1
Z h o v t i \32 V o d y \32 L v i v \0 ... \0

Результати роботи програми:

Before concatenation:
str1 --> Zhovti Vody len = 11
After concatenation:
str1 --> Zhovti Vody L'viv len = 17

Національний університет «Львівська політехніка»


Лістинг 6.16.: Вказівники, масив рядків
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void) {
char StrArr[3][128] = { "Hello", "world", "I am C" };
char Buf[128];
int i;
for (i = 0; i < 3; i++)
printf("%s\n", StrArr[i]);
strcpy(Buf, StrArr[0]);
strcat(Buf, ", ");
strcat(Buf, StrArr[1]);
strcat(Buf, " - ");
strcat(Buf, StrArr[2]);
printf("%s\n", Buf);
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 6.16.: Вказівники, масив рядків

При роботі даної програми працюємо з двома змінними – масивом рядків StrArr і рядком Buf:

strArr H e l l o \0 \0 \0 ... \0
w o r l d \0 \0 \0 ... \0
I \32 a m \32 C \0 \0 ... \0

Значення рядка Buf є неініціалізованим:

buf ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ... ?

Після виконання програми у рядку Buf буде наступне:

buf H e l l 0 , \32 w o r l d \32 - \32 I \32 a m \32 C \0 ? ... ?

Результати роботи програми:

Hello
world
I am C
Hello, world - I am C

Національний університет «Львівська політехніка»


Лістинг 6.17.: Обробка рядків
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[128] = "";
char str2[] = "L'viv";
strncpy(str1, str2, 3);
printf("str1 --> %s\n", str1);
return 0;
}
При роботі даної програми працюємо з двома змінними – рядком str1 і str2 :

str1 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 ... \0
str2 L v i v \0

Після виконання функції strncpy() в рядку str1 буде наступне:

str1 L v \0 \0 \0 \0 \0 \0 \0 \0 \0 ... \0

Результати роботи програми:


str1 --> L'v
Національний університет «Львівська політехніка»
6.5. Вказівники на рядки
Поширеним засобом доступу до символів рядка є вказівники типу char*.
У прикладі
char* st = (char*)"Computer program";
компілятор записує всі символи рядка до масиву і присвоює змінній st адресу першого елемента масиву.
Рядок може вважатися за порожній у двох випадках: якщо вказівник на рядок має нульове значення NULL
(немає взагалі жодного рядка) чи коли вказівник вказує на масив, який складається з одного нульового
символу (не містить жодного значущого символу).
char* pc1 = 0; // pc1 не адресує жодного масиву символів
const char* pc2 = ""; // pc2 адресує нульовий символ

Національний університет «Львівська політехніка»


#define _CRT_SECURE_NO_WARNINGS
Лістинг 6.18.: Рядки + вказівники
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[128] = "";
char str2[] = "L'viv";
char* pAux;
pAux = str2;
pAux++;
strncpy(str1, pAux, 3);
printf("str1 --> %s\n", str1);
return 0;
}
При роботі даної програми працюємо з двома змінними – рядком str1 і str2 :

str1 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 ... \0
str2 L v i v \0

pAux pAux++
Після виконання функції strncpy() в рядку str1 буде наступне:

str1 v i \0 \0 \0 \0 \0 \0 \0 \0 \0 ... \0

Результати роботи програми:


str1 --> 'vi
Національний університет «Львівська політехніка»
#define _CRT_SECURE_NO_WARNINGS Лістинг 6.19.: Рядки + вказівники (2)
#include <stdio.h>
#include <string.h>
int main(void)
{
char text[64] = "When you say \"yes\", I say \"yes\" too";
char aux[64] = ""; char frag[] = "yes";
char* pPos; char* pAux = NULL; int N;
pPos = strstr(text, frag);
if (pPos != 0) {
N = pPos - text;
strncpy(aux, text, N);
strcat(aux, "no");
pAux = pPos + strlen(frag);
}
while (pPos != 0) {
pPos = strstr(pAux, frag);
if (pPos != 0) {
N = pPos - pAux;
strncat(aux, pAux, N);
strcat(aux, "no");
pAux = pAux + N + strlen(frag);
}else strcat(aux, pAux);
}
printf("Original text: %s\n", text);
printf("Processed text: %s\n", aux);
return 0;
} Національний університет «Львівська політехніка»
Лістинг 6.20.: Сортування масиву рядкових даних
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int word_cmp(const void* s1, const void* s2);
int main(int argc, char* argv[])
{
char* wordsptr[] = { (char *)"one", (char*)"two", (char*)"three", (char*)"four",
(char*)"five", (char*)"six", (char*)"seven", (char*)"eight", (char*)"nine", (char*)"ten" };
int i;
int wordcount = sizeof(wordsptr) / sizeof(char*);
printf("Before sorting:\n");
for (i = 0; i < wordcount; i++)
printf("%s\n", wordsptr[i]);
qsort(wordsptr, wordcount, sizeof(char*), word_cmp);
printf("\nAfter sorting:\n");
for (i = 0; i < wordcount; i++)
printf("%s\n", wordsptr[i]);
return 0;
}
int word_cmp(const void* p1, const void* p2)
{
return strcmp(*(char**)p1, *(char**)p2);
} Національний університет «Львівська політехніка»
6.6. Перетворення чисел в рядки та навпаки
Виконати перетворювання рядка на число можна багатьма способами. Перший – використання бібліотечних функцій
atoi() , atof() , atol(). Ці функції входять до стандартної бібліотеки мови С і мають такий формат:
int atoi(const char* str);
long atol(const char* str);
double atof(const char* str);
Результатами перетворювання цих функцій є числа відповідного типу. Функції цілочисельного перетворювання atoi() і
atol()сприймають такий формат перетворюваного рядка:
[пробіли][знак]цифри
а функція перетворювання рядка в дійсне число atof(), відповідно:
[пробіли][знак][цифри][.цифри][{ e | E }[знак]цифри]
Тут пробіли – будь-який зі знаків пробілу, табуляції ('\t'), вертикальної табуляції ('\v') – при перетворюванні
ігноруються. Знак – символ '+' чи '-' (якщо його не зазначено, то вважається, що число є додатне). Цифри – символи
від '0' до '9'. Для числа з рухомою крапкою, якщо немає цифр до символу '.', має бути хоча б одна цифра після
нього. Дійсне число може бути подано в експоненціальній формі з одним із символів-префіксів експоненти.
Основний недолік цих функцій полягає в тому, що вони ніяк не сигналізують про помилку, якщо така станеться в
перебігу перетворювання рядка на число, наприклад через невідповідність його формату чи то з інших причин.
Національний університет «Львівська політехніка»
Лістинг 6.21.: Перетворення рядка в число, atof()
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
double d1, d2, sum;
char str1[128]; char str2[128];
printf("Enter first number\n");
gets_s(str1, sizeof(str1)); //gets(str1);
printf("Enter second number\n");
gets_s(str2, sizeof(str2)); //gets(str2);
d1 = atof(str1); d2 = atof(str2);
sum = d1 + d2;
printf("sum = %f\n", sum);
return 0;
}

Результати роботи програми:


Enter first number
3.5
Enter second number
2.1
sum = 5.600000

Національний університет «Львівська політехніка»


Для перетворення цілого числа на рядок можна скористатися такими функціями:
char* _itoa(int value, char* buffer, int radix);
char* _ltoa(long value, char* buffer, int radix);
char* _ultoa(unsigned long value, char* buffer, int radix);
Безпечні версії цих функцій:
errno_t _itoa_s(int value, char* buffer, size_t size, int radix);
errno_t _ltoa_s(long value, char* buffer, size_t size, int radix);
errno_t _ultoa_s(unsigned long value, char* buffer, size_t size, int radix);
Для цих функцій перший вхідний параметр value – ціле число, яке треба перетворити на рядок; другий параметр
buffer є буфером, до якого буде записано результат перетворювання, а третій параметр radix – це основа системи
числення, в якій буде подано число. size – максимальний розмір буфера. Повертають нуль, якщо дія успішна, або
код помилки в іншому випадку.
int sprintf(char* buffer, const char* format[, argument] ...);
Безпечна версія:
int sprintf_s(char* buffer, size_t sizeOfBuffer, const char* format, ...);

Національний університет «Львівська політехніка»


Лістинг 6.22.: Перетворення числа в рядок, sprintf()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
double d = 3.456;
int m = 33;
char Buf[128];
printf("num1 = %d\nnum2 = %f\n\n", m, d);
sprintf(Buf, "num1 = %d\nnum2 = %f", m, d);
printf("%s\n", Buf);
return 0;
}

Результати роботи програми:


num1 = 33
num2 = 3.456000
num1 = 33
num2 = 3.456000

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua
Лекції №27, 28, 29, 30.
Тема №7.
РОБОТА З ДИСКОВИМИ ФАЙЛАМИ
Автор курсу: старший викладач кафедри ЕОМ НУ «ЛП» Ногаль Марія Василівна
7.1. Загальні відомості про файли
Файлами є іменовані області пам’яті на зовнішньому носії, призначені для довготривалого зберігання інформації.
Файли мають імена й є організовані в ієрархічну деревовидну структуру з каталогів (тек) і простих файлів.
Для доступу до даних файла з програми в ній слід прописати функцію відкриття цього файла, тим самим встановити
зв’язок поміж ім’ям файла і певною файловою змінною у програмі.
Файли відрізняються від звичайних масивів тим, що
• вони можуть змінювати свій розмір;
• звертання до елементів файлів здійснюється не за допомогою операції індексації [], а за допомогою спеціальних
системних викликів та функцій;
• доступ до елементів файла відбувається з так званої позиції зчитування-записування, яка автоматично
просувається при операціях зчитування-записування, тобто файл є видимим послідовно. Існують, щоправда,
функції для довільного змінювання цієї позиції.
Часто використовується така метафора: якщо уявляти собі файли як книжки (лише зчитування) і блокноти (зчитування
й записування), які стоять на полиці, то відкриття файла – це вибір книжки чи блокнота за заголовком на його
обкладинці й відкриття обкладинки (на першій сторінці). Після відкриття можна читати, дописувати, викреслювати і
правити записи, перегортати книжку. Сторінки можна зіставити з блоками файла, а полицю з книжками – з каталогом.
С надає засоби опрацювання двох типів файлів: текстових та бінарних.

Національний університет «Львівська політехніка»


Текстові файли призначено для зберігання текстів, тобто сукупності символьних рядків змінної довжини.
Кожен рядок завершується керувальною послідовністю '\n', а розділювачами слів та чисел у рядку є пробіли й
символи табуляції.
Оскільки вся інформація текстового файла є символьною, програмне опрацювання такого файла полягає в читанні
рядків, виокремлюванні з рядка слів і, за потреби, перетворюванні цифрових символьних послідовностей на числа
відповідними функціями перетворювання.
Створювати, редагувати текстові файли можна не лише в програмі, а й у якому завгодно текстовому редакторі,
наприклад Блокноті чи Word.
Бінарні файли зберігають дані в тому самому форматі, в якому вони були оголошені, і їхній вигляд є такий самий, як
і в пам’яті комп’ютера. І тому відпадає потреба у використанні розділювачів: пробілів, керувальних послідовностей, а
отже, обсяг використовуваної пам’яті порівняно з текстовими файлами з аналогічною інформацією є значно меншим.
Окрім того, немає потреби у застосуванні функцій перетворення числових даних. Але кожне опрацювання даних
бінарних файлів можливе лише за наявності програми, якій має бути відомо, що саме і в якій послідовності
зберігається у цьому файлі.

Національний університет «Львівська політехніка»


7.2. Робота з текстовими файлами
У мові С файл розглядається як потік послідовності байтів. Інформація про файл заноситься до змінної типу FILE*. Цей
тип оголошує вказівник потоку, який використовується надалі у всіх операціях з цим файлом. Тип FILE визначено у
бібліотеці <stdio.h>. Тому, якщо в програмі передбачається робота з файлами, цей заголовний файл слід долучити:
#include <stdio.h>
Тепер можна оголосити файлову змінну – вказівник потоку, який в подальшому буде передаватися у функції введення-
виведення у якості параметра:
FILE* f;
Функція fopen() для відкривання файла має такий синтаксис:
FILE* fopen(const char* filename, const char* mode);
Перший параметр filename визначає ім’я файла, який відкривається. Другий параметр mode задає режим відкривання
файла.

Національний університет «Львівська політехніка»


Таблиця 7.1 – Специфікатори режиму відкриття файлів
Параметр Опис
r Відкрити файл для читання даних
r+ Відкрити файл для читання і запису даних
a Відкрити чи створити файл для запису даних в кінець файлу
a+ Відкрити чи створити файл для читання і запису даних в кінець файлу
w Створити файл для запису даних
w+ Створити файл для читання і запису даних

До зазначених специфікаторів наприкінці чи перед знаком "+" може дописуватись символ чи то "t" – для текстових
файлів, чи "b" – для бінарних (двійкових) файлів.

Національний університет «Львівська політехніка»


Функція fopen() повертає вказівник на об’єкт, який керує потоком. Наприклад, створити файл з ім’ям test.txt можна
так:
FILE* f;
f = fopen("test.txt", "wt");
Якщо файл відкрити не вдалося, fopen() повертає нульовий вказівник NULL. Для уникання помилок після відкриття
файла слід перевірити, чи насправді файл відкрився:
if(f == NULL)
{
printf("Error!\n");
return;
}
Безпечна версія цієї функції
errno_t fopen_s(FILE * *pFile, const char* filename, const char* mode);
повертає нуль у разі успіху або код помилки в разі невдачі.
Наприклад, створити файл з ім’ям test.txt можна так:
int err = fopen_s(&f, "test.txt", "wt");
if (err != 0) { printf("Error!\n"); return; }
Національний університет «Львівська політехніка»
Припинити роботу з файлом можна за допомогою функції
int fclose(FILE * stream);
int _fcloseall(void);
Функція fclose() закриває файл, на який посилається параметр функції.
Функція _fcloseall() закриває усі відкриті файли.
Перевірка кінця файла здійснюється функцією feof():
int feof(FILE * stream);
Наведемо приклад відкривання текстового файла для зчитування.
FILE* f;
// Перевіряємо, чи повертає ця функція нульовий вказівник
if ((f = fopen("test.txt", "rt")) == NULL)
{
printf("Error");
return;
}
// Читання даних з файла.
fclose(f); // Закриття файла.

Національний університет «Львівська політехніка»


Лістинг 7.1.: Файли
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* pf;
pf = fopen("MyFileName", "wb");
fclose(pf);
return 0;
}

Національний університет «Львівська політехніка»


З текстового файла можна читати цілі рядки й окремі символи.
Зчитування рядка з файла здійснюється функцією fgets():
char* fgets(char* str, int numChars, FILE * stream);
де str – перший параметр – рядок типу char*;
numChars – максимальна кількість читаних символів (байтів);
stream – вказівник на потік даних файла.
Записування даних до текстового файла можна здійснювати за допомогою функції fputs() :
int fputs(const char* str, FILE * stream);
де str – рядок типу char,
stream – файловий потік.
Для записування даних до файла слід відкрити файл у форматі записування даних у кінець файла, за потреби
перетворити рядок на тип char* і записати дані до файла.

Національний університет «Львівська політехніка»


Лістинг 7.2.: Файли, fputc()

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* pf;
pf = fopen("MyFileName", "wb");
if (pf != 0)
{
fputc('A', pf);
fputc('B', pf);
fclose(pf);
}
return 0;
}

Національний університет «Львівська політехніка»


Лістинг 7.3.: Файли, fgetc()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* pf;
char a, b;
pf = fopen("MyFileName", "rb");
if (pf != 0)
{
a = fgetc(pf);
b = fgetc(pf);
fclose(pf);
}
printf("First is %c; Second is %c", a, b);
return 0;
}

Вмістиме файла MyFileName


AB
Результати роботи програми:
First is A; Second is B
Національний університет «Львівська політехніка»
Зчитування форматованих даних можна також здійснювати за допомогою функції fscanf():
int fscanf(FILE * stream, const char* format[, argument]);
Записування до текстового файла можна здійснити також за допомогою функції fprintf():
int fprintf(FILE * stream, const char* format[, argument]);
Текстові файли дозволяють переміщувати поточну позицію зчитування-запису. Для визначення поточної позиції
файла, яка автоматично зміщується на кількість опрацьованих байт, використовується функція ftell():
long int ftell(FILE * stream);
A змінити поточну позицію файла можна за допомогою функції fseek():
int fseek(FILE * stream, long offset, int whence);
Ця функція задає зсув на offset байтів щодо точки відліку, яка задається параметром whence. Параметр whence
може набувати таких значень: 0 – початок файлу, 1 - поточна позиція, 2 – кінець файлу.
Наприклад, для переміщення поточної позиції на початок файла можна скористатися функцією
fseek(f, 0, 0);
За допомогою функцій ftell() та fseek() можна визначити сумарний обсяг пам’яті у байтах, який займає файл. Для
цього достатньо переміститися на кінець файла:
fseek(f, 0, 2);
int d = ftell(f);

Національний університет «Львівська політехніка»


Отже, послідовність роботи з файлом при записуванні даних є така:
1) Відкрити чи то створити файл fname.txt для записування (або для зчитування й записування) у кінець файла:

FILE* f;
f = fopen("fname.txt", "at+");

2) Записати дані до файла f однією з команд:

// Записування С-рядка s
fputs(s, f);
//Форматоване записування С-рядка nazva, дійсного числа price і цілого числа kol
fprintf(f, "%s %6.2f %i\n", nazva, price, kol);

3) Закрити файл:

fclose(f);

Національний університет «Львівська політехніка»


Для зчитування даних з файла треба виконати таку послідовність дій:
1) Відкрити файл для зчитування

FILE* f;
f = fopen("fname.txt", "rt+");

2) Здійснити зчитування даних з файла однією з команд:

// Зчитування С-рядка s довжиною у 80 символів


fgets(s, 80, f);
// Форматоване зчитування за адресами відповідних змінних
fscanf(f, "%s%f%i\n", &nazva, &price, &kol);

Для зчитування послідовно всіх даних з файла треба організувати цикл з умовою, яка перевіряє досягання кінця файла,
використовуючи чи то функцію feof(f), яка є ідентична до true при досяганні кінця файла, чи функцію зчитування
даних, яка дає нульовий результат за досягання кінця файла. Можна використовувати інформацію про сумарний розмір
файла і контролювати досягання кінця файла функцією ftell(f).

3) Закрити файл:
fclose(f); Національний університет «Львівська політехніка»
Лістинг 7.4.: Файли, fputs(), fgets()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* pf;
char str1[] = "Hello,\n"; char str2[] = " world!\n";
char Str1[128] = ""; char Str2[128] = "";
/*------- file writing -------*/
pf = fopen("MyFileName", "wt");
if (pf != 0)
{
fputs(str1, pf);
fputs(str2, pf);
fclose(pf);
}
/*------- file reading -------*/
pf = fopen("MyFileName", "rt");
if (pf != 0)
{
fgets(Str1, sizeof(Str1), pf);
fgets(Str2, sizeof(Str2), pf);
fclose(pf);
}
printf("Str1 = %sStr2 = %s\n", Str1, Str2);
return 0;
}
Національний університет «Львівська політехніка»
Лістинг 7.5.: Файли, fgets() (2)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* pf;
char Buf[128] = "";
char* pch;
/*------- file reading -------*/
pf = fopen("MyFileName", "rt");
if (pf != 0)
{
pch = fgets(Buf, sizeof(Buf), pf);
while (pch != 0)
{
printf("Str1 = %s", Buf);
pch = fgets(Buf, sizeof(Buf), pf);
}
fclose(pf);
}
return 0;
}

Національний університет «Львівська політехніка»


7.3. Робота з бінарними файлами
У бінарному (двійковому) файлі число, на відміну від текстового, зберігається у внутрішньому його поданні.
У двійковому форматі можна зберігати не лише числа, а й рядки та цілі інформаційні структури. Причому останні
зберігати зручніше, завдяки тому що відсутня потреба явно зазначати кожен елемент структури, а зберігається вся
структура як цілковита одиниця даних. Хоча цю інформацію не можна прочитати як текст, вона зберігається більш
компактно і точно.
Тому, що саме і в якій послідовності розміщено в бінарному файлі, має бути відомо програмі, яка цей файл
опрацьовує.
Функція fread() читає інформацію у вигляді потоку байтів і в незмінному вигляді розміщує її в пам’яті.
Слід розрізнювати текстове подавання чисел і їхнє бінарне подавання.
size_t fread(
char* buffer, // Масив для зчитування даних,
size_t elemSize, // розмір одного елемента,
size_t numElems, // кількість елементів для зчитування
FILE* f); // і вказівник потоку
Тут size_t означений як беззнаковий цілий тип у системних заголовних файлах. Функція намагається прочитати
elemSize елементів з файла, який задається вказівником f, розмір кожного елементу становить elemSize. Функція
повертає реальну кількість прочитаних елементів, яка може бути менше за numElems, у разі завершення файла чи
помилки зчитування.

Національний університет «Львівська політехніка»


Функція бінарного записування до файла fwrite() є аналогічна до функції зчитування fread(). Вона має такий
прототип:
size_t fwrite(
char* buffer, // Масив записуваних даних,
size_t elemSize, // розмір одного елемента,
size_t numElems, // кількість записуваних елементів
FILE* f); // і вказівник потоку

Функція повертає кількість реально записаних елементів, яка може бути менше за numElems, якщо при записуванні
виникла помилка: приміром, не вистачило вільного простору на диску.
Так само, як і в текстових, у бінарних файлах можна визначати позицію зчитування-записування і переміщувати її у
довільне місце файла командами відповідно ftell() та fseek(). Можливість переміщувати позицію є корисна для
файлів, які складаються з однорідних записів однакового розміру. Приміром, якщо у файлі записано лише дійсні числа
типу double, то для того, щоб прочитати i-тe число, слід виконати оператори:
fseek(F, sizeof(double)* (i - l), 0);
fread(&а, sizeof(double), 1, F);

Національний університет «Львівська політехніка»


Лістинг 7.6.: Файли, fwrite(), fread() (1)
#define _CRT_SECURE_NO_WARNINGS Результати роботи програми:
#include <stdio.h> iRes is 5; dRes is 8.880000
int main(void)
{
FILE* pf;
int m = 5; double d = 8.88;
int iRes; double dRes;
/*------- file writing -------*/
pf = fopen("MyFileName", "wb");
if (pf != 0)
{
fwrite(&m, sizeof(int), 1, pf);
fwrite(&d, sizeof(double), 1, pf);
fclose(pf);
}
/*------- file reading -------*/
pf = fopen("MyFileName", "rb");
if (pf != 0)
{
fread(&iRes, sizeof(int), 1, pf);
fread(&dRes, sizeof(double), 1, pf);
fclose(pf);
}
printf("iRes is %d; dRes is %f", iRes, dRes);
return 0;
}
Національний університет «Львівська політехніка»
Лістинг 7.7.: Файли, fwrite(), fread() (2)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> Результати роботи програми:
int main(void) iArrRes[0] = 5; dArrRes[0] = 8.880000
{ iArrRes[1] = 6; dArrRes[1] = 9.900000
FILE* pf; int i; iArrRes[2] = 7; dArrRes[2] = 1.000000
int iArr[3] = { 5, 6, 7 };
double dArr[3] = { 8.88, 9.9, 1 };
int iArrRes[3]; double dArrRes[3];
/*------- file writing -------*/
pf = fopen("MyFileName", "wb");
if (pf != 0){
fwrite(&(iArr[0]), sizeof(int), 3, pf);
fwrite(&(dArr[0]), sizeof(double), 3, pf);
fclose(pf);
}
/*------- file reading -------*/
pf = fopen("MyFileName", "rb");
if (pf != 0){
fread(&(iArrRes[0]), sizeof(int), 3, pf);
fread(&(dArrRes[0]), sizeof(double), 3, pf);
fclose(pf);
}
for (i = 0; i < 3; i++)
printf("iArrRes[%d] = %d; dArrRes[%d] = %f\n", i, iArrRes[i], i, dArrRes[i]);
return 0;
}
Національний університет «Львівська політехніка»
Лістинг 7.8.: Файли, fwrite(), fread() (3)
#define _CRT_SECURE_NO_WARNINGS Результати роботи програми:
#include <stdio.h> iArrRes[0] = 5
int main(void)
iArrRes[1] = 6
{
FILE* pf; int i = 0, len; iArrRes[2] = 7
int iArr[3] = { 5, 6, 7 }; iArrRes[3] = 0
int iArrRes[8] = { 0 }; iArrRes[4] = 0
/*------- file writing -------*/ iArrRes[5] = 0
pf = fopen("MyFileName", "wb");
iArrRes[6] = 0
if (pf != 0) fwrite(&(iArr[0]), sizeof(int), 3, pf);
fclose(pf); iArrRes[7] = 0
/*------- file reading -------*/
pf = fopen("MyFileName", "rb");
if (pf != 0)
{
len = fread(&(iArrRes[i]), sizeof(int), 1, pf);
while (len != 0 && i < 7)
{
i++;
len = fread(&(iArrRes[i]), sizeof(int), 1, pf);
}
fclose(pf);
}
for (i = 0; i < 8; i++)
printf("iArrRes[%d] = %d\n", i, iArrRes[i]);
return 0;
} Національний університет «Львівська політехніка»
Лістинг 7.9.: Файли, найпростіша довідкова система
#define _CRT_SECURE_NO_WARNINGS Файл Telephone.txt
#include <stdio.h> Kovalenko 1234567
#include <string.h> Honchar 1235467
int main(void) Kovalchuk 1236543
{ Tkachenko 1237322
FILE* pf; char* pch; Kovalskyj 2231234
Melnyk 2232341
char NameBuf[128] = ""; char ResultBuf[128] = "";
Honcharenko 2234301
printf("Enter a name, please: ");
Koval 4532345
gets_s(NameBuf, sizeof(NameBuf));
Kovaljuk 4533267
/* file reading and telephone number searching */ Tkach 4533385
pf = fopen("Telephone.txt", "rt"); Melnychenko 4534358
if (pf != 0){
pch = fgets(ResultBuf, sizeof(ResultBuf), pf); Результати роботи програми:
while (pch != 0){ Enter a name, please: Tkach
pch = strstr(ResultBuf, NameBuf); -------------------------
if (pch != 0){ Tkachenko 1237322
printf("-------------------------\n"); -------------------------
printf("%s", ResultBuf); Tkach 4533385
} ==========================
pch = fgets(ResultBuf, sizeof(ResultBuf), pf);
The end.
}
fclose(pf);
printf("==========================\n");
printf("\nThe end.");
}
return 0;
} Національний університет «Львівська політехніка»
7.4. Структуровані типи даних
У попередніх розділах для зберігання даних розглянуто прості типи даних: числа, символи, рядки тощо. Але часто на
практиці перед програмістом постає завдання запрограмувати той чи інший об’єкт, який характеризується водночас
кількома параметрами, приміром, точка на площині задається парою дійсних чисел (x, y), а дані про людину можна
задавати кількома параметрами: прізвище, ім’я, по-батькові – рядки, рік народження – число тощо. Для поєднання
кількох параметрів в одному об’єктові в С існує спеціальний тип даних, який має назву структура. На відміну від
масивів, які також є структурованим типом даних і містять елементи одного типу, структури дозволяють поєднувати
дані різних типів.
Структура – це складений тип даних, змінні (поля) якого можуть містити різноманітну й різнотипну інформацію.
Опис структури має наступний синтаксис:
struct ім’я_типу_структури
{
тип1 поле1;
тип2 поле2;
. . .
типN полеN;
} список_змінних;

Ім’я типу структури задається програмістом виходячи зі змісту даних, які зберігатимуться у структурі. Список змінних
наприкінці оголошення структури може бути відсутнім, у цьому разі після фігурної дужки має бути крапка з комою.
Національний університет «Львівська політехніка»
Наведемо приклад оголошення структури з ім’ям sportsman, яка поєднає в собі інформацію про змагання
спортсменів: прізвище і вік спортсмена, країна, кількість його очок, утворюючи відповідні поля:
struct sportsman
{
char name[32];
char country[32]; // Прізвище та країна,
int age;
float rating; // вік та кількість очок.
};
Як наслідок цього оголошення створено новий тип sportsman. Тепер у програмі цей тип може використовуватись
нарівні зі стандартними типами для оголошення змінних. Оголошені змінні типу sportsman матимуть чотири поля: два
рядки (name і country), ціле (age) і дійсне (rating) числа.
sportsman Sp; /* Змінна Sp типу sportsman може зберігати інформацію про одного спортсмена. */
sportsman Mas[15]; /* Масив Mas типу sportsman може містити інформацію про 15 спортсменів. *
При оголошуванні змінної типу структури пам’ять під усі поля структури виділяється послідовно для кожного поля. У
наведеному прикладі структури sportsman під змінну Sp послідовно буде виділено 32, 32, 4, 4 байти – усього 72
байти. Поля змінної Sp у пам’яті буде розміщено у такому порядку:
0 1 2 3 4 5 6 … 31 0 1 2 3 4 5 6 … 31 0 1 2 3 0 1 2 3
char name[32] char country[32] int age float rating

Слід зазначити, що сумарну пам’ять, яка виділяється під структуру, може бути збільшено компілятором на вимогу
процесора для так званого вирівнювання адрес. Реальний обсяг пам’яті, яку займає структура, можна визначити за
допомогою sizeof().
Національний університет «Львівська політехніка»
При оголошуванні структур їхні поля можна ініціалізовувати початковими значеннями.
Наприклад, оголошення змінної Sp типу sportsman й ініціалізація її даними про 20-річного спортсмена з України за
прізвищем Бойко, який набрав 75.3 очок, матиме вигляд:
sportsman Sp = { "Бойко", "Україна", 20, 75.3 };
Оголошення типу “точка на площині” POINT з полями-координатами – x та у і змінної spot цього типу – точки з
координатами (20, 40) може бути таким:
struct POINT
{
int x, у;
}
spot = { 20, 40 }; // Точка має координати x = 20, у = 40
При ініціалізації масивів структури кожен елемент масиву слід записувати у фігурних дужках, наприклад при
створенні двовимірного масиву x розміром 2х3 типу структури complex з полями real та im ініціалізація початкових
значень елементів може мати вигляд:
struct complex
{
float real, im;
} x[2][3] = { {{1.4, 7}, {5, 3}, {-3.1, -1.1}},{{2, -6.7}, {-5.2}, {0, 6}} };

Національний університет «Львівська політехніка»


Доступ до полів структури здійснюється за допомогою операції “крапка”:
cтруктурна_змінна.ім’я_поля
Наприклад, для розглянутої змінної Sp типу sporsman надання значень її полям може мати вигляд:

strcpy(Sp.name, "Бойко");
strcpy(Sp.country, "Україна");
Sp.age = 20;
Sp.rating = 75.3;
Можна створювати тип структури за допомогою typedef, наприклад:

typedef struct
{
char name[32], group[6];
int Bal_math, Bal_inform;
} student; // Оголошення типу “студент”
student W; // і змінної W цього типу.
Тут означено тип структури за ім’ям student для зберігання даних про успішність студентів з полями: прізвище, група,
екзаменаційні оцінки з математики та інформатики й оголошено змінну W цього типу.

Національний університет «Львівська політехніка»


Структури можуть розміщуватись у динамічній пам’яті за допомогою оператора new і оголошуватися як вказівники.
У такому разі доступ до полів структури здійснюється за допомогою операції вибору – “стрілка” (->, на клавіатурі
послідовно натискаються клавіші “мінус” і “більше”):
sportsman* p = new sportsman; // Виділення пам’яті,
p->rating = 95.3; // присвоєння очок полю rating
(*p).age = 23; // і віку полю age.
Зверніть увагу на те, що якщо p – вказівник на структуру, то (*p) – сама структура і доступ до її полів здійснюється за
допомогою крапки. Якщо змінні типу “структура” оголошуються у програмі лише один раз, то, зазвичай, їх записують
наприкінці оголошення структури і при цьому ім’я структури можна явно не зазначати, наприклад:
struct
{
char name[32];
char country[10];
int age;
float rating;
}
Sp;
Тут оголошено змінну Sp як структуру з чотирма полями без створення типу “спортсмен”.

Національний університет «Львівська політехніка»


Поля структури можуть бути якого завгодно типу, у тому числі й масивом, покажчиком чи структурою, окрім типу тієї ж
самої структури (але можуть бути вказівником на неї).
За приклад створимо дві структури для опрацювання інформації про співробітників у відділі кадрів: прізвище, ім’я, по-
батькові співробітника, домашня адреса, домашній телефон, дата народження, дата вступу на роботу, зарплатня.
struct date
{
int day, month, year; // День, місяць та рік
};
struct person
{
char name[32], address[150]; // Прізвище, адреса,
char phone[10];
date birth_date;
// телефон, дата народження,
date work_date; // дата вступу на роботу
float salary; // та зарплатня.
};
У цьому прикладі оголошується новий тип – структура date для зберігання дати (день, місяць, рік). Окрім того,
оголошується тип – структура person, яка використовує тип date для полів birth_date і work_date. Посилання на
поля вкладеної структури формується з імені структурної змінної, імені структурного поля й імені поля вкладеної
структури. Те, що поля структур самі можуть бути структурами, надає можливість цілісно описувати доволі складні
об’єкти реального світу. Оскільки структури, на відміну від масивів, зберігають дані різних типів, їх відносять до
неоднорідних типів даних.
Національний університет «Львівська політехніка»
Оголосимо змінну створеного типу person :
person P;
Присвоювання прізвища у змінну P :
strcpy(P.name, "Шкуропат");
Присвоювання дати народження 5 березня 1987 року у змінну P:
P.birth_date.day = 5;
P.birth_date.month = 3;
P.birth_date.year = 1987;
Розмістимо змінну man типу person у динамічній пам’яті:
person* man = new person;
І присвоїмо ті ж самі значення:
strcpy(man->name, "Шкуропат");
(man->birth_date).day = 5;
(man->birth_date).month = 3;
(man->birth_date).year = 1987;
Оголосимо масив з даними про 100 осіб:
person P[100];
Попередні присвоювання для четвертої людини:
strcpy(P[3].name, "Шкуропат");
P[3].birth_date.day = 5;
P[3].birth_date.month = 3;
P[3].birth_date.year = 1987; Національний університет «Львівська політехніка»
Лістинг 7.10 : Сортування масиву календарних дат. Календарні дати представлені з
#include <stdio.h> допомогою структури date
#include <stdlib.h>
typedef
struct {
int d; //day
int m; //month
int y; //year
} date;
int date_cmp(const void* s1, const void* s2)
int date_cmp(const void* s1, const void* s2);
/* Compares dates. */
int main(int argc, char* argv[])
{
{
date* p1 = (date*)s1;
date dates[] = { { 12, 4, 1961 }, { 17, 7, 1969 },
date* p2 = (date*)s2;
{ 27, 8, 1856 }, { 1, 9, 1939 }, { 22, 6, 1942 },
int r;
{ 1, 12, 1991 }, { 1, 8, 1914 }, { 9, 3, 1814 } };
r = p1->y - p2->y;
int i;
if (!r)
int datescount = sizeof(dates) / sizeof(date);
{
printf("Before sorting:\n");
r = p1->m - p2->m;
for (i = 0; i < datescount; i++)
if (!r)
printf("%2d.%02d.%4d\n",
r = p1->d - p2->d;
dates[i].d, dates[i].m, dates[i].y);
}
qsort(dates, datescount, sizeof(date), date_cmp);
return r;
printf("\nAfter sorting:\n");
}
for (i = 0; i < datescount; i++)
printf("%2d.%02d.%4d\n",
dates[i].d, dates[i].m, dates[i].y);
return 0;
} Національний університет «Львівська політехніка»
#define _CRT_SECURE_NO_WARNINGS Лістинг 7.11.: Динамічні масиви структур
#include <stdio.h>
#include <malloc.h> Результати роботи програми:
struct TelPersonInfo Enter the number of TelPersonInfo elements: 2
{ Enter 1 name: Koval
char Name[128]; Enter 1 TelNumber: 12345
int TelNum;
Enter 2 name: Melnyk
};
Enter 2 TelNumber: 54321
int main(void) {
struct TelPersonInfo* pTPI; int N, i;
printf("Enter the number of TelPersonInfo elements: ");
Koval 12345
scanf("%d", &N); Melnyk 54321
/*---- get dynamic memory ----*/
pTPI = (TelPersonInfo*)malloc(N * sizeof(struct TelPersonInfo));
/*-- work with dynamic memory --*/
if (pTPI != 0){
for (i = 0; i < N; i++) {
printf("Enter %d name: ", i + 1);
scanf("%s", (pTPI + i)->Name);
printf("Enter %d TelNumber: ", i + 1);
scanf("%d", &(pTPI[i].TelNum));
} printf("\n\n");
for (i = 0; i < N; i++) {
printf("%s", (pTPI + i)->Name);
printf(" %d\n", (pTPI + i)->TelNum);
} free(pTPI);
}
return 0;
} Національний університет «Львівська політехніка»
В мові С є можливість працювати з окремими бітами певної змінної. Бітові поля – це особливий тип структури, який
визначає довжину кожного поля в бітах.
struct ім’я_структури
{
тип поле1 : довжина;
тип поле2 : довжина;
...
тип полеN : довжина;
}
Тип поля має бути цілого типу, unsigned або signed. При використанні бітових полів слід бути впевненим, що пам’яті
буде достатньо для усіх бітів.
struct date
{
unsigned int day : 5;
unsigned int month : 4;
unsigned int year : 23;
};
Така структура в пам’яті буде відображатись наступним чином:

22 21 20 … 1 0 3 2 1 0 4 3 2 1 0
year month day

Національний університет «Львівська політехніка»


Об’єднання – формат даних, який може містити різні типи даних, але лише один тип водночас.
Можна сказати, що об’єднання є окремим випадком структури, всі поля якої розташовуються за однією й тією самою
адресою.
Формат опису об’єднання є такий самий як і у структури, лише замість ключового слова struct використовується слово
union. Але, тоді як структура може містити, скажімо, елементи типу і int, і short, і double, об’єднання може містити чи
то int, чи short, чи double :
union prim
{
int x;
short y;
double z;
};
Обсяг пам’яті, яку займає об’єднання дорівнює найбільшому з розмірів його полів.
Об’єднання застосовують для економії пам’яті у тих випадках, коли відомо, що понад одного поля водночас не потрібно.
Зручно використовувати об’єднання, коли необхідно дійсне число типу float представити у вигляді масиву байт:
union types
{
float f;
unsigned char b[4];
};

Національний університет «Львівська політехніка»


При написанні програм часто виникає потреба у визначенні наперед відомої кількості іменованих констант, які мають
мати різні значення (при цьому конкретні значення можуть бути неважливими).
Для цього зручно користуватися типом перерахування enum, всі можливі значення якого задаються списком
цілочисельних констант:
enum ім’я_типу { список_констант };
Ім’я типу задається в тому разі, якщо у програмі потрібно визначати змінні цього типу.
Змінним перераховного типу можна присвоювати кожне значення зі списку констант, зазначених при оголошуванні типу.
Імена перераховних констант мають бути унікальними.
Перераховні константи подаються й опрацьовуються як цілі числа і можуть ініціалізуватися у звичайний спосіб.
Окрім того вони можуть мати однакові значення.
За відсутності ініціалізації перша константа обнулюється, а кожній наступній присвоюється значення на одиницю більше,
аніж попередній.
enum week { sat = 0, sun = 0, mon, tue, wed, thu, fri } rоb_den;
Тут описано перерахування week з відповідною множиною значень і оголошено змінну rоb_den типу week. Перераховні
константи sat та sun мають значення 0, mon – значення 1, tue – 2, wed – 3, thu – 4, fri – 5.
enum { first, second = 100, third };
У цьому разі first за замовчуванням дорівнює 0, а наступні неініціалізовані константи перерахування перевищують
своїх попередників на 1. Приміром, third матиме значення 101.
Національний університет «Львівська політехніка»
До перераховних змінних можна застосовувати арифметичні операції й операції відношення, наприклад:
enum days { sun, mon, tue, wed, thu, fri, sat };
days day1 = mon, day2 = thu;
int diff = day2 - day1;
Арифметичні операції над змінними перераховних типів зводяться до операцій над цілими числами.
Проте, не зважаючи на те, що компіляторові відомо про цілочисельну форму подання перераховних значень, варто
використовувати цей факт надто обережно.
Рекомендовано без нагальної потреби не використовувати цілочисельну інтерпретацію перераховних значень.
Істотним недоліком типів перерахування є те, що вони не розпізнаються засобами введення-виведення С.
При виведенні такої змінної виводиться не її формальне значення, а її внутрішнє подання, тобто ціле число.

Національний університет «Львівська політехніка»


7.5. Внутрішнє представлення даних
Цілі типи даних.
У пам'яті ціле число представлено у вигляді послідовності бітів фіксованого (кратного 8) розміру. Ця послідовність нулів
і одиниць - не що інше, як двійковий запис числа, оскільки зазвичай для подання використовується позиційний
двійковий код. Діапазон цілих чисел, як правило, визначається кількістю байтів в пам'яті комп'ютера, що відводяться під
одну змінну.
Беззнакові цілі представляють тільки невід'ємні числа, при цьому всі розряди коду використовуються для подання
значення числа і максимальне число відповідає коду у якого у всіх розрядах одиниці: 111 … 111. Змінна цілого типу без
знаку приймає значення від 0 до +2n−1, де n – кількість розрядів числа.
У C і C++ для позначення беззнакових типів використовується модифікатор unsigned.
Числа з знаком представляються у так званому доповняльному коді.
Старший розряд числа позначає знак числа – якщо він рівний 0 – то число додатне, якщо 1 – то число від’ємне.
Змінна цілого типу зі знаком приймає значення від −2n-1 до +2n-1−1, де n –кількість розрядів числа.
Для додатного числа доповняльний код рівний прямому, а для від’ємного оберненому плюс одиниця. Математично
доповняльний код Xдоп = 2n+1 — X, де X— число, яке треба представити у доповняльному коді, n — к-сть розрядів
числа.

Національний університет «Львівська політехніка»


Дійсні типи даних.
Для кодування дійсних даних (типи float і double) використовують стандарт IEEE 754, який визначає формати і
методи для арифметики з плаваючою комою в комп'ютерних системах – стандартні та розширені функції для чисел
одинарної, подвійної, розширеної і розширюваної точності - і рекомендує формати для обміну даними.
Дані з плаваючою комою представляються за допомогою трьох полів:
• 1-бітовий знак S;
• w-бітовий зміщений порядок E = e + bias;
• t-бітовий хвіст мантиси T.

молодший розряд старший розряд

знак S зміщений порядок E xвіст мантиси T

1 біт w бітів t бітів

Національний університет «Львівська політехніка»


Для представлення у цьому форматі двійкове число спочатку нормалізується, тобто приводиться до такого вигляду,
коли мантиса потрапляє в діапазон 1 ≤ m < 2. Таким чином ціла частина нормалізованої мантиси завжди дорівнює 1.
Знак додатних чисел кодується нулем, від’ємних одиницею.
В поле порядку пишеться зміщений порядок – до порядку нормалізованого числа додається константа, таким чином всі
порядки представляються додатними числами.
В поле мантиси записується, т. зв. "хвіст" мантиси – всі дробові цифри мантиси (крім першої одиниці, яка є цілою
частиною).
Ціла частина мантиси в нормалізованому вигляді дорівнює 1 і не входить до коду числа. Це так звана прихована
одиниця.
Приклад. 0,312510 = 0,01012 = 1,01 × 2-2.
Для 32-х розрядного формату порядок зміщується на 127. E = –2 + 127 = 125 = 11111012.
Мантиса записується без першої одиниці, тобто T = 01 (1,01 –1 = 0,01).
Компоненти коду: знак – 0 (1 розряд), зміщений порядок – 01111101 (8 розрядів), хвіст мантиси
01000000000000000000000 (23 розряди).
Повний 32-бітний код 00111110101000000000000000000000.

Національний університет «Львівська політехніка»


Лістинг 7.12.: Внутрішнє представлення даних
#define _CRT_SECURE_NO_WARNINGS double_type d1, d2;
#include <stdio.h> d1.f = 275.5555555; d2.f = -275.5555;
printf("\ndouble:");
int main(void) printf("\n%26g\t %016llx ", d1.f, d1.i);
{ printf("\n%26g\t %016llx ", d2.f, d2.i);
union float_type printf("\n%26lld\t %016llx ", d1.i, d1.i);
{ printf("\n%26lld\t %016llx ", d2.i, d2.i);
float f; d1.i = 2321777620394769639; d2.i = -2289908398520801427;
int i; printf("\n%26lld\t %016llx ", d1.i, d1.i);
}; printf("\n%26lld\t %016llx ", d2.i, d2.i);
float_type f1, f2; printf("\n%26g\t %016llx ", d1.f, d1.i);
f1.f = 275.5; f2.f = -275.5555; printf("\n%26g\t %016llx ", d2.f, d2.i);
printf("float:"); return 0;
printf("\n%13g\t %08x ", f1.f, f1.i); }
printf("\n%13g\t %08x ", f2.f, f2.i);
printf("\n%13d\t %08x ", f1.i, f1.i);
printf("\n%13d\t %08x ", f2.i, f2.i);
f1.i = 566551438; f2.i = -507190386;
printf("\n%13d\t %08x ", f1.i, f1.i);
printf("\n%13d\t %08x ", f2.i, f2.i);
printf("\n%13g\t %08x ", f1.f, f1.i);
printf("\n%13g\t %08x ", f2.f, f2.i);
union double_type
{
double f;
long long i;
}; Національний університет «Львівська політехніка»
Лістинг 7.12.: Внутрішнє представлення даних
Результати роботи програми:
float:
275.5 4389c000
-275.556 c389c71b
1133101056 4389c000
-1014380773 c389c71b
566551438 21c4e38e
-507190386 e1c4e38e
1.33417e-18 21c4e38e
-4.53995e+20 e1c4e38e
double:
275.556 407138e38e29f9cf
-275.555 c07138e353f7ced9
4643555240789539279 407138e38e29f9cf
-4579816797041602855 c07138e353f7ced9
2321777620394769639 20389c71c714fce7
-2289908398520801427 e0389c71a9fbe76d
1.83558e-153 20389c71c714fce7
-3.29981e+155 e0389c71a9fbe76d

Національний університет «Львівська політехніка»


Контактна інформація:

79013, м. Львів, вул. С. Бандери, 12,


(032) 258-26-80, coffice@lpnu.ua
http://lpnu.ua

You might also like