ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 21.12.2021
Просмотров: 979
Скачиваний: 3
Прототипи функцій використовуються для перевірки коректності виклику функцій. Виникає помилка на етапі компіляції у випадку не узгодження прототипу функції та її визначення.
Синтаксис оголошення прототипу функції наступний:
<тип> <ім’я функції> (параметр1, параметр2,…);
Цей синтаксис ідентичний визначенню функції, за винятком того що він не включає тіло самої функції, а в кінці ставиться крапка з комою (;).
Компілятор ігнорує імена змінних вказаних в прототипі функції, тобто при оголошенні прототипу достатньо вказати лише типи параметрів.
Приклад 7. Використання прототипу функції без вказання імен змінних у прототипі.
#include <stdio.h>
int function(int, int, int); /*оголошення прототипу функції*/
int main(void) /*головна програма*/
{
printf ("suma = %d \n", function (4, 5, 6));
return 0;
}
int function(int a, int b, int c) /*визначення функції*/
{
return a+b+c;
}
Приклад 7. Використання прототипів функцій.
#include <stdio.h>
#include <stdlib.h>
void nap(int a); /*оголошення прототипу функції nap*/
void Henap(int a); /*оголошення прототипу функції Henap*/
int main ()
{
int i;
do {
printf( " BBegiTb 4iCJlO(Buxig - 0): ");
scanf ("%i",& i);
nap(i);
} while (i!=0);
system("PAUSE");
return 0;
}
void nap (int a) /*визначення функції nap*/
{
if ((a%2)!=0)
printf( "napHe.\n ");
else Henap(a);
}
void Henap (int a) /*визначення функції Henap*/
{
if ((a%2)==0)
printf( "HenapHe.\n ");
else nap(a);
}
Результат роботи програми:
У прикладі 7.1 показано випадок у якому необхідно використовувати прототипи функцій.
На початку програми оголошуються прототипи функцій nap і Henap:
void nap (int a);
void Henap (int a);
Прототипи дозволяють викликати функції до моменту їх визначення. На рисунку 6.5 зображено структури програм з використанням прототипу функції та без нього.
Рисунок 6.5 – Cтруктура програми з прототипом функції та без нього
У програмі з прикладу 7.1 неможливо обійтись без прототипів, так як існує потреба взаємного виклику двох функцій (функція nap викликає функцію Henap, яка в свою чергу викливає функцію nap). Якщо не використовувати прототипи, то компілятор видаватиме помилку, так як функція описана в програмі першою, викликатиме функцію, яка не описувалась в програмі раніше.
8. РЯДКИ В С
Рядки є одним з найбільш корисних та важливих типів даних мови С. Символьний рядок - це масив символів, замкнений у лапки. Він має тип char. Нульовий символ ‘\0’ автоматично додається останнім байтом символьного рядка та виконує роль ознаки його кінця.
Приклад 1. Символьний рядок.
#include <string>
int main (){
char myString[] = "string!";
printf("%s\n", myString);
system("pause");
return 0;
}
Результат роботи програми:
Кількість елементів у масиві дорівнює кількості символів у рядку плюс один, оскільки нульовий символ також є елементом масива. Кожна рядокова константа, навіть у випадку, коли вона ідентична іншій рядоковій константі, зберігається у окремому місці пам'яті. Якщо необхідно ввести у рядок символ лапок, то перед ним треба поставити символ зворотного слешу "\". У рядок можуть бути введені будь-які спеціальні символьні константи, перед якими стоїть символ "\"..
4.1 Прототипи
Прототипи всіх функцій, що працюють з рядками символів, містяться у файлі string.h. Всі функції працюють з рядками, що закінчуються нульовим символом. Деякі з них дивіться у таблиці 4.1.
Таблиця 4.1 – Прототипи функцій
Ім’я функції |
Виконання |
int strcmp(const char *, const char *); |
Лексикографічне порівняння рядків. Повертає < 0 якщо перша стрічка менша за другу; > 0 якщо друга стрічка більша за першу і 0, кщо вони рівні. |
int strcat( char *, char *) |
З'єднати два рядки(об’єднаний рядок записується у першу змінну, а друга змінна залишається без змін); |
int strcpy(char *, char *) |
Копіювати рядок s2 у рядок s1(перша змінна дорівнює другій, а друга залишається незмінною); |
int strlen(char *) |
Визначити довжину рядку (кількість символів без нульового символа). |
Приклад 2. Використання прототипів функцій.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
char s1[]="Hello, ";
char s2[]={'w','o','r','l','d','!','\0'};
strcat(s1, s2);
printf("s1:%s s2:%s\n", s1, s2);/*на екран буде виведено:
s1:Hello, world! s2: world!*/
printf("%c\n", s1[4]);//на екран буде виведено літеру o
/*зверніть увагу, що в даному випадку використано %с
замість %s так як виводиться лише 1 символ*/
int len=strlen(s1);/*довжина першого рядка тепер дорівнює
13*/
printf("%i\n", strcmp(s1, s2));/*програма виведе від’ємне
число, так як s1<s2 за
лексикографічним порівнянням.*/
strcpy(s1, s2);
printf("%s %s %i\n", s1, s2, len); /*на екран буде виведено:
world! world! 13*/
system("pause");
}
Результат роботи програми:
4.2 Функції перетворення буферів
Для роботи з масивом символів, що не має у кінці нульового байта, можна користуватися функціями перетворення буферів. Протипи цих функцій знаходяться у файлі mem.h. Ці функції дозволяють присвоювати кожному байту в межах вказаного буфера задане значення, а також використовуються для порівняння вмісту двох буферів.
memcpy() - копіювання символів з одного буфера у другий, поки не буде скопійований заданий символ або не буде скопійовано визначену кількість символів
memcmp() - порівнює вказану кількість символів з двох буферів
4.3 Функції перевірки літер
У файлі ctype.h описано прототипи функцій, що призначені для перевірки літер. Ці функції повертають ненульове значення (істина), коли її аргумент задовольняє заданій умові або належить вказаному класу літер, та нуль в іншому випадку(табл 4.2).
Таблиця 4.2 – Функції перевірки літер
int islower(int с) |
символ с є малою літерою; |
int isupper(int c) |
символ с є великою літерою; |
int isalnum(int c) |
символ с є буквою або цифрою; |
int isalpha(int c) |
символ с є буквою; |
int tolower(int c) |
перетворення літери у нижній регістр; |
int strtol(int c) |
перетворення рядка у довге ціле число; |
4.4 Рядок символів
У мові С немає окремого типу даних “рядок символів”, подібно до типу string у алгоритмічній мові PASCAL. Тому робота з рядками реалізована шляхом використання одновимірних масивів типу char. Рядок символів – це одновимірний масив типу char, останнім елементом якого є нульовий байт. Нульовий байт – це байт, кожен біт якого рівний нулю, при цьому для нульового байта визначена символьная константа \0 (ознака закінчення рядка або нуль-термінатор). Тому, якщо рядок містить k символів, в описі масиву потрібно вказати розмірність k+1.
Так, для збереження у масиві рядкової константи “Лабораторна робота з рядками”, необхідно описати масив char s[29]. В кінці рядкової константи символ ´ \0 вказувати не потрібно, оскільки це зробить компілятор мови С(рис. 4.1).
Приклад 3. Рядок символів.
S=”ПИРІЖОК=)”;
Рисунок 4.1 – Сприйняття комп’ютером символьного рядка.
Так сприймає рядок комп’ютер. Тому, S[0]==’П’, S[8]==’)’.
4.5 Операції з рядками:
а) Рядки можна ініціалізувати при декларуванні.
Приклад 4. Ініціалізація даних.
char S1[10] = ”123456789”, S2[] = ”Srting”, S3[] = {‘1’,‘2’,‘3’,‘\0’};
В двох останніх випадках розмір рядків буде встановлений за кількістю символів;
б) Рядки можна вводити з клавіатури, не використовуючи при цьому оператора циклу, подібно звичайним масивам.
Приклад 5. Введення масиву.
char TeMa [20];
printf(“Введіть тему лабораторної роботи:\n”);
scanf(“%s”, TeMa);
4.6 Символьні константи
Для символьних даних в C введено тип char. Для представлення символьної інформації використовуються символи, символьні змінні і текстові константи.
Приклад 6. Змінні типу char.
const char c=’c’; /*символ-константа – займає один байт*/
char a,b; /*символьні змінні, займають по одному байту*/
const char *s=“Приклад рядка\n” ; /*рядкова константа */
Рядок в С - це масив символів, що закінчується нуль-символом ‘\0’. За місцезнаходженням цього символу визначається фактична довжина рядка. Кількість елементів у такому масиві на 1 більше за зображення рядка(рис. 4.2).
Рисунок 4.2 – Символ і рядок
9. ПОКАЖЧИКИ
5.1 Визначення та ініціалізація покажчиків
Пам'ять комп'ютера можна представити у вигляді послідовності комірок, кожна з мінімальним розміром один байт. Комірки нумеруються послідовно (рис. 5.1).
Рисунок 5.1 - Послідовна нумерація комірок пам’яті
Покажчик – це зміна, що зберігає адресу іншої змінної.
Синтаксис визначення покажчика наступний:
<тип> *<ім’я покажчика>
де тип – це тип даних на який вказує покажчик;
ім’я покажчика – ідентифікатор.
Синтаксис виклику функції наступний:
Приклад 1. Присвоєння адреси змінній(рис. 5.2).
pointer = &variable; /*якщо «&» перед ініціалізатором, то це операція взяття адреси присвоєння адреси variable змінній pointer */
тобто pointer «вказує» на variable:
Рисунок 5.2 – Присвоєння адреси змінній
Розглянемо ще один фрагмент:
Приклад 2. Змінна та її адреса(рис. 5.3).
variable = 13;
varsecond = variable;
pointer = &variable;
Рисунок 5.3 – Змінна та її адреса.
Покажчик визначається наступним чином:
<тип> *< ідентифікатор> = <ініціалізатор>;
Під ініціалі затором розуміється якась адреса зміної(тоді використовуємо взяття адреси змінної &) або вже існуючий покажчик, але не вираз типу &41.
5.2 Визначення покажчиків:
Приклад 3. Синтаксис покажчика.
int* p=&a; /* покажчик р містить значення адреси змінної а */
float* ptr (NULL); /* Нульовий покажчик на об’єкт типу float */
char* p; /* Неініціалізований покажчик на об’єкт типу char */
Значення адреси змінної одержується за допомогою унарної операції ”&”.Розглянемо код з виводом в консольне вікно:
Приклад 4. Використання операції ”&”.
#include <iostream>
#include <stdlib.h>
int main(){
int a = 8;
int *p = &a;
printf(" p=%i, &p=%i, a=%i, &a=%i \n", p, &p, a, &a);
system("PAUSE");
return EXIT_SUCCESS;
}
Результат роботі:
Рисунок 5.4 - Схема даних в памяті:
Як бачимо в зміній а зберігається значення 8, в зміній р зберігається значення адреси а, тобто р вказує на комірку зі зміною а.
Якщо ж операція «*» використовується не при ініціалізації покажчика то ця операція має назву розіменовування:
Приклад 5. Розіменовування покажчика(рис. 5.5).
int x=2; /*змінна типу int */
int *y =&x; /* покажчик на елемент даних типу int */
int z =*y; /* через покажчик до поля x вноситься значення 1,тобто z=1 */
Рисунок 5.5 - Розіменовування покажчика
Z звертається до y, y це покажчик на x, що містить адресу. Присвоїти z значення y ми не можемо, тому що це різні типи даних: адреса і значення. Шляхом розіменовування ми отримуємо значення на яке вказує y і присвоюємо його змінній z.
Власне унарна операція «*» застосована до покажчика, забезпечує доступ до вмісту комірки пам'яті, на яку посилається покажчик. Наприклад, * y можна описати словами як "те, що міститься за адресою, на який вказує y".
Покажчики можуть використовуватися в виразах. Якщо. наприклад, зміна uk вказує на ціле x, то * y може у всіх випадках використовуватися замість x; так, * y +1 збільшує x на одиницю, а * y = 0 рівносильне x = 0. Два оператори присвоювання y = &x; z =*y; виконують те ж саме, що і один оператор z = x. Користь від застосування покажчиків в таких ситуаціях, м'яко висловлюючись, невелика.
Найбільш повні їх переваги при обробці масивів і, зокрема, символьних рядків. Покажчики та масиви тісно пов'язані один з одним. Перш ніж розглянути цей зв'язок детально, зазначимо, що якщо uk - деякий покажчик, то uk ++ збільшує його значення і він тепер вказує на наступний, сусідню адресу об'єкта. Значення uk використовується у виразі, а потім збільшується. Аналогічно визначаються операції uk--, + + uk,--uk. У загальному випадку покажчик uk можна складати з цілим числом i. Оператор uk + = i пересуває посилання на i елементів щодо поточного значення. Ці конструкції підпорядковуються правилам адресної арифметики.
Рисунок 5.6 - Адресна арифметика
Як бачимо є шість елементів – шість комірок, розміром по 4 байта(рис. 5.6). Для першого елемента масиву (що за одне й є покажчиком на масив) наприклад взято зміщення в памяті 1000 байт, там де нам було виділено її ОС в ОЗП. До речі , покажчик вказує й на тип, таким чином зміщення буде на відповідний розмір типу(в даному випадку для float – 4байта) і вказувати на дані що містяться в наступних 4-х байтах.
Таким чином, нехай покажчик (p) на перший елемент масиву (що за одне й є покажчиком на масив) має адресу (зміщення) 1000 і вказує на записані там дані , тобто 0,67. Використавши інкрементування р++ ми переходимо до наступного елемента в масиві (і зміщуємось в памяті на розмір типу – 4байта). Покажчик уже вказує на 2,56 з адресою 1004.
Обмежень по переміщенню в масиві немає, тому потрібно бути обережним.
Наприклад: у нас є 6 елементів в масиві, але це не означає, що покажчик не може виходити за його межі, він може вказувати на комірки з адресою 1024, 1028 і т.д. Отже, якщо ми перемістимо покажчик за межі масиву(операціями р++ чи р--), то він вказуватиме на значення типу float і що там буде за значення не відомо, так як там знаходитиметься «сміття», якийсь випадковий набір нулів та одиниць, що використовуватимуть правила переведення з двійкової системи в десяткову з плаваючою точкою, щоб відобразити зміст.
Для інших типів все ж так саме: int(зміщення 4 байти), double(зміщення 8 байти), long(зміщення 4 байти), unsigned int(зміщення 4 байти), byte(зміщення 1 байт) і т.д.. (рис. 5.7)
Рисунок 5.7 – Зміщення покажчика для різних типів даних.
5.3 Масиви
А тепер повернемося до масивів. Нехай є опис int a [5]; Він визначає масив розміром 5 елементів, тобто п'ять послідовних розташованих осередків пам'яті a [0], a [1], a [2], a [3], a [4]. Адреса i-го елемента масиву дорівнює сумі адреси початкового елемента масиву і зміщення цього елемента на i одиниць від початку масиву. Це досягається індексуванням: a [i] - i-й елемент масиву. Але доступ до будь-якого елементу масиву може бути виконаний і за допомогою покажчиків, причому, більш ефективно. Якщо uk-покажчик на ціле, описаний як int * uk, то uk після виконання операції uk = &a[0] -містить адресу a [0], а uk + i вказує на i-й елемент масиву. Таким чином, uk + i є адреса a [i]. Оскільки ім'я масиву в програмі ототожнюється з адресою його першого елемента, то вираз uk = & a [0] еквівалентно такому: uk = a. Тому значення a [i] можна записати як *(a+i). Застосувавши до цих двох елементів операцію взяття адреси, отримаємо, що &a[i] та a+i ідентичні.