diff options
author | Anastasiia Bondarenko <nastiasunrise@gmail.com> | 2021-08-22 22:30:29 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-22 21:30:29 +0200 |
commit | ed5f7694fdfd114cbdf9f299ee54ffaf77059cd9 (patch) | |
tree | a504f5e35275620f16f624d682b13e9ecf9b6853 /uk-ua | |
parent | 757ed9b49b8de4a3147fe0a44f91960095fdbc55 (diff) |
[c/uk-ua] Add ukrainian translation for Clang (#3762)
* [c/uk-ua] Add ukrainian translation for Clang
* Apply suggestions from code review
Co-authored-by: Andre Polykanine <ap@oire.me>
Diffstat (limited to 'uk-ua')
-rw-r--r-- | uk-ua/c-ua.html.markdown | 860 |
1 files changed, 860 insertions, 0 deletions
diff --git a/uk-ua/c-ua.html.markdown b/uk-ua/c-ua.html.markdown new file mode 100644 index 00000000..c4dac75f --- /dev/null +++ b/uk-ua/c-ua.html.markdown @@ -0,0 +1,860 @@ +--- +language: c +filename: learnc-ua.c +contributors: + - ["Adam Bard", "http://adambard.com/"] + - ["Árpád Goretity", "http://twitter.com/H2CO3_iOS"] + - ["Jakub Trzebiatowski", "http://cbs.stgn.pl"] + - ["Marco Scannadinari", "https://marcoms.github.io"] + - ["Zachary Ferguson", "https://github.io/zfergus2"] + - ["himanshu", "https://github.com/himanshu81494"] + - ["Joshua Li", "https://github.com/JoshuaRLi"] + - ["Dragos B. Chirila", "https://github.com/dchirila"] +translators: + - ["AstiaSun", "https://github.com/AstiaSun"] +lang: uk-ua +--- + +О, C! Досі мова для сучасних обчислень у високопродуктивних продуктах. + +C це імовірно найбільш низькорівнева мова, яку будуть використовувати більшість програмістів. Проте, вона компенсує це не тільки швидкістю виконання. Як тільки ви оціните її можливість ручного управління пам'яттю, С зможе відвести саме в ті місця, в які вам потрібно було потрапити. + +> **Дещо про прапори компілятора** +> +> За замовчуванням, gcc та clang досить тихо інформують про попередження та помилки +> при компіляції, хоч це і може бути дуже корисною інформацією. Тому рекомендується +> використовувати більш вимогливий компілятор. Ось кілька рекомендацій: +> +> `-Wall -Wextra -Werror -O2 -std=c99 -pedantic` +> +> За інформацією про ці та інші прапори зверніться до головної сторінки man вашого +> компілятора C (наприклад, `man 1 gcc`) або ж просто заґуґліть. + + +```c +// Однорядкові коментарі починаються з // +// Проте вони з'явились тільки після С99. + +/* +Багаторядкові коментарі мають такий вигляд. І працюють в C89. +*/ + +/* +Багаторядкові коментарі не можуть вкладатись один в одний. +/* Будьте обережними */ // коментар закінчується на цьому рядку... +*/ // ...а не на цьому! + +// Константа: #define <keyword> +// Назви констант, як правило, пишуться великими літерами, проте це не вимога +#define DAYS_IN_YEAR 365 + +// Ще одним способом оголосити константи є перелічення констант. +// До речі, всі вирази мають закінчуватись крапкою з комою. +enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT}; +// MON отримає значення 2 автоматично, TUE дорівнюватиме 3 і т.д. + +// Імпортувати заголовки можна за допомогою #include +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +// (Заголовки із стандартної бібліотеки С вказуються між <кутовими дужками>.) +// Щоб додати власні заголовки, потрібно використовувати "подвійні лапки" +// замість кутових: +//#include "my_header.h" + +// Сигнатури функцій попередньо оголошуються в .h файлах або на початку .с файлів. +void function_1(); +int function_2(void); + +// Потрібно оголосити 'прототип функції' перед main(), реалізація функцій +// відбувається після функції main(). +int add_two_ints(int x1, int x2); // прототип функції +// Варіант `int add_two_ints(int, int);` теж правильний (не потрібно називати +// аргументи). Рекомендується також називати аргументи в прототипі для +// кращого розуміння. + +// Вхідною точкою програми є функція під назвою main. Вона повертає чисельний тип. +int main(void) { + // реалізація програми +} + +// Аргументи командного рядка, вказані при запуску програми, також передаються +// у функцію main. +// argc - це кількість переданих аргументів +// argv — це масив масивів символів, що містить самі аргументи +// argv[0] - назва програми, argv[1] - перший аргумент, і т.д. +int main (int argc, char** argv) +{ + // printf дозволяє вивести на екран значення, вивід - це форматований рядок, + // в даному випадку %d позначає чисельне значення, \n — це новий рядок + printf("%d\n", 0); // => Виводить 0 + + /////////////////////////////////////// + // Типи + /////////////////////////////////////// + + // Всі змінні повинні бути оголошені на початку поточного блоку області видимості. + // В цьому коді вони оголошуються динамічно. С99-сумісні компілятори + // дозволяють оголошення близько до місця, де значення використовується. + + // int (цілочисельний знаковий тип) зазвичай займає 4 байти + int x_int = 0; + + // short (цілочисельний знаковий тип) зазвичай займає 2 байти + // + short x_short = 0; + + // Символьний тип char гарантовано займає 1 байт + char x_char = 0; + char y_char = 'y'; // Символьні літерали позначаються '' + + // long (цілочисельний знаковий тип) має розмір від 4 до 8 байтів; великі значення + // типу long гарантовано займають 8 байтів + long x_long = 0; + long long x_long_long = 0; + + // Тип float - це зазвичай 32-бітове число з плаваючою крапкою + float x_float = 0.0f; // Суфікс 'f' позначає літерал з плаваючою крапкою + + // Тип double - це зазвийчай 64-бітове число з плаваючою крапкою + double x_double = 0.0; // дійсне число без суфіксів має тип double + + // Цілочисельні типи можуть не мати знаку (бути більше, або ж рівними нулю) + unsigned short ux_short; + unsigned int ux_int; + unsigned long long ux_long_long; + + // Char всередині одинарних лапок інтерпретуються як числа в наборі + // символів комп'ютера. + '0'; // => 48 в таблиці ASCII. + 'A'; // => 65 в таблиці ASCII. + + // sizeof(T) повертає розмір змінної типу Т в байтах + // sizeof(obj) віддає розмір виразу (змінна, літерал, і т.п.) + printf("%zu\n", sizeof(int)); // => 4 (на більшості пристроїв з 4-байтним словом) + + // Якщо аргумент оператора `sizeof` — це вираз, тоді його аргументи не оцінюються + // (крім масивів, розмір яких залежить від змінної). + // Значення, що повертається в цьому випадку, - це константа часу компіляції. + int a = 1; + // size_t - беззнаковий чисельний тип розміром щонайменше 2 байти, який + // використовується для відображення розміру об'єкта. + size_t size = sizeof(a++); // a++ не оцінюється + printf("sizeof(a++) = %zu where a = %d\n", size, a); + // Виводить "sizeof(a++) = 4 where a = 1" (на 32-бітній архітектурі) + + // Масиви повинні бути проініціалізовані з конкретним розміром. + char my_char_array[20]; // Цей масив займає 1 * 20 = 20 байтів + int my_int_array[20]; // Цей масив займає 4 * 20 = 80 байтів + // (припускаючи 4-байтні числа) + + // Таким чином можна проініціалізувати масив нулем: + char my_array[20] = {0}; + // де "{0}" називається "ініціалізатором масиву". + + // Зазначте, можна явно не оголошувати розмір масиву, ЯКЩО ви проініціалізуєте + // масив у тому ж рядку. Тому, наступне оголошення еквівалентне: + char my_array[] = {0}; + // АЛЕ, потрібно визначити розмір масиву під час виконання, як тут: + size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]); + + // ПОПЕРЕДЖЕННЯ якщо ви вирішили використовувати даний підхід, потрібно + // визначити розмір **перед тим**, як ви почнете передавати масив у функцію + // (побачите дискусію пізніше). Масиви перетворюються на вказівники при + // передачі як аргументи у функцію, тому попереднє твердження буде видавати + // хибний результат всередині функції. + + // Індексація по масиву така ж сама, як і в інших мовах програмування або, + // скоріше, як у інших с-подібних мовах. + my_array[0]; // => 0 + + // Масиви незмінні, це просто частина пам'яті! + my_array[1] = 2; + printf("%d\n", my_array[1]); // => 2 + + // Масиви, розмір яких залежить від змінної, в С99 (та в С11 як вибірковий + // функціонал) можуть бути оголошені також. Розмір такого масиву не має бути + // константою під час компіляції: + printf("Enter the array size: "); // спитати користувача розмір масиву + int array_size; + fscanf(stdin, "%d", &array_size); + int var_length_array[array_size]; // оголосити масив + printf("sizeof array = %zu\n", sizeof var_length_array); + + // Приклад: + // > Enter the array size: 10 + // > sizeof array = 40 + + // Рядки - це просто масиви символьних літералів (char), що закінчуються NULL + // (0x00) байтом, представленим у рядках як спеціальний символ '\0'. + // (Не потрібно включати байт NULL в рядкові літерали; компілятор сам вставляє + // його наприкінці масиву.) + char a_string[20] = "This is a string"; + printf("%s\n", a_string); // %s форматує рядок + + printf("%d\n", a_string[16]); // => 0 + // тобто, байт #17 - це 0 (так само, як і 18-ий, 19-ий, та 20-ий) + + // Якщо між одинарними лапками є букви, тоді це символьний літерал. + // Він має тип `int`, а не `char` (так історично склалось). + int cha = 'a'; // добре + char chb = 'a'; // також добре (неявне перетворення з int на char) + + // Багатовимірні масиви: + int multi_array[2][5] = { + {1, 2, 3, 4, 5}, + {6, 7, 8, 9, 0} + }; + // Доступ до елементів: + int array_int = multi_array[0][2]; // => 3 + + /////////////////////////////////////// + // Оператори + /////////////////////////////////////// + + // Скорочення для багатьох оголошень: + int i1 = 1, i2 = 2; + float f1 = 1.0, f2 = 2.0; + + int b, c; + b = c = 0; + + // Арифметичні операції + i1 + i2; // => 3 + i2 - i1; // => 1 + i2 * i1; // => 2 + i1 / i2; // => 0 (0.5 округлено до 0) + + // Потрібно перетворити хоча б одну з цілочисельних змінних на float, щоб + // отримати результат з плаваючою крапкою + (float)i1 / i2; // => 0.5f + i1 / (double)i2; // => 0.5 // Так само і для типу double + f1 / f2; // => 0.5, з певною точністю + // Такі обчислення не є точними + + // Ділення за модулем також є + 11 % 3; // => 2, остача від ділення + + // Оператори порівняння ймовірно схожі, проте в С немає логічного типу. + // Натомість використовується int. + // (Або _Bool або bool в C99.) + // 0 - хибно (false), всі інші значення - правда (true). Оператори + // порівняння завжди повертають 0 або 1. + 3 == 2; // => 0 (false) + 3 != 2; // => 1 (true) + 3 > 2; // => 1 + 3 < 2; // => 0 + 2 <= 2; // => 1 + 2 >= 2; // => 1 + + // C - це не Python, порівняння не утворюють ланцюги. + // Попередження: Рядок нижче скомпілюється, але він означає `(0 < a) < 2`. + // В даному випадку, це 1, тому що (0 < 1). + int between_0_and_2 = 0 < a < 2; + // Натомість потрібно використати: + int between_0_and_2 = 0 < a && a < 2; + + // Логічні оператори з числами + !3; // => 0 (Логічне НЕ) + !0; // => 1 + 1 && 1; // => 1 (Логічне І) + 0 && 1; // => 0 + 0 || 1; // => 1 (Логічне АБО) + 0 || 0; // => 0 + + // Тернарний вираз з умовою ( ? : ) + int e = 5; + int f = 10; + int z; + z = (e > f) ? e : f; // => 10 "if e > f return e, else return f." + + // Оператори збільшення та зменшення на 1: + int j = 0; + int s = j++; // Повернути j ПОТІМ збільшити j. (s = 0, j = 1) + s = ++j; // Збільшити j ПОТІМ повернути j. (s = 2, j = 2) + // так само і для j-- та --j + + // Побітові операції! + ~0x0F; // => 0xFFFFFFF0 (побітове заперечення, "перше доповнення", результат + // для 32-бітного int) + 0x0F & 0xF0; // => 0x00 (побітове І) + 0x0F | 0xF0; // => 0xFF (побітове АБО) + 0x04 ^ 0x0F; // => 0x0B (побітове XOR) + 0x01 << 1; // => 0x02 (побітовий зсув вліво (на 1)) + 0x02 >> 1; // => 0x01 (побітовий зсув вправо (на 1)) + + // Будьте обережними при зсуві цілочисельних значень зі знаком. + // Наступні дії дають невизначений результат: + // - зсув на біт, що зберігає знак числа (int a = 1 << 31) + // - зсув вліво на від'ємне число (int a = -1 << 2) + // - зсув на число, що більше за ширину типу + // TODO: LHS + // - зсув на зміщення, що >= ширині типу в лівій частині виразу: + // int a = 1 << 32; // Невизначена поведінка, якщо ширина int 32 біти. + + /////////////////////////////////////// + // Структури розгалуження + /////////////////////////////////////// + + // Оператор умови + if (0) { + printf("I am never run\n"); // ніколи не буде виконано + } else if (0) { + printf("I am also never run\n"); // теж ніколи не буде виконано + } else { + printf("I print\n"); // це буде надруковано + } + + // Цикл з передумовою + int ii = 0; + while (ii < 10) { // БУДЬ-ЯКЕ значення, що менше 10 - правда. + printf("%d, ", ii++); // ii++ збільшує ii на 1 ПІСЛЯ передачі поточного значення. + } // => надрукує "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " + + printf("\n"); + + // Цикл з післяумовою + int kk = 0; + do { + printf("%d, ", kk); + } while (++kk < 10); // ++kk збільшує kk на 1 ПЕРЕД передачою поточного значення. + // => надрукує "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " + + printf("\n"); + + // Цикл з лічильником + int jj; + for (jj=0; jj < 10; jj++) { + printf("%d, ", jj); + } // => виводить "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " + + printf("\n"); + + // *****Додатково*****: + // Цикли та функції обов'язково повинні мати тіло. Якщо тіло не потрібно: + int i; + for (i = 0; i <= 5; i++) { + ; // використовуйте крапку з комою, щоб симулювати тіло (пусте твердження) + } + // Або + for (i = 0; i <= 5; i++); + + // Розгалуження з множинним вибором: switch() + switch (a) { + case 0: // значення повинні бути *константними* виразами і мати вбудований тип + //(наприклад, перелічення) + printf("Hey, 'a' equals 0!\n"); + break; // якщо не використати break, то управління буде передано наступному блоку + case 1: + printf("Huh, 'a' equals 1!\n"); + break; + // Будьте обережними, виконання продовжиться до тих пір, поки + // не зустрінеться наступний "break". + case 3: + case 4: + printf("Look at that.. 'a' is either 3, or 4\n"); + break; + default: + // якщо вираз a не співпадає з описаними значеннями, то виконується + // блок default + fputs("Error!\n", stderr); + exit(-1); + break; + } + /* + Використання "goto" в С + */ + typedef enum { false, true } bool; + // вводимо таке перелічення, оскільки С не має логічного типу до С99 + bool disaster = false; + int i, j; + for(i=0;i<100;++i) + for(j=0;j<100;++j) + { + if((i + j) >= 150) + disaster = true; + if(disaster) + goto error; + } + error : + printf("Error occurred at i = %d & j = %d.\n", i, j); + /* + https://ideone.com/GuPhd6 + Даний приклад виведе "Error occurred at i = 51 & j = 99." + */ + + /////////////////////////////////////// + // Приведення до типів + /////////////////////////////////////// + + // Кожне значенння в С має тип, але можна перевести значення з одного типу в + // інший, якщо потрібно (із деякими обмеженнями). + + int x_hex = 0x01; // Змінним можна присвоювати літерали в шістнадцятковій + // системі числення + + // Приведення до типу призведе до спроби зберегти чисельне значення + printf("%d\n", x_hex); // => Виводить 1 + printf("%d\n", (short) x_hex); // => Виводить 1 + printf("%d\n", (char) x_hex); // => Виводить 1 + + // В данному випадку попередження не виникатиме, якщо значення виходить за межі + // значення типу + printf("%d\n", (unsigned char) 257); // => 1 (максимальне значення char = 255, + // якщо char має довжину 8 біт) + + // Для того, щоб дізнатись максимальний розмір `char`, `signed char` або ж + // `unsigned char`, потрібно використати макроси CHAR_MAX, SCHAR_MAX та UCHAR_MAX + // відповідно з <limits.h>. + + // Вбудовані типи можуть бути приведені до типу із плаваючою крапкою і навпаки. + printf("%f\n", (double) 100); // %f завжди перетворює число на double... + printf("%f\n", (float) 100); // ...навіть, якщо це float. + printf("%d\n", (char)100.0); + + /////////////////////////////////////// + // Вказівники + /////////////////////////////////////// + + // Вказівник - це змінна, що зберігає адресу у пам'яті. Оголошення вказівника + // також потребує інформації про тип об'єкта, на який він вказує. Можна + // отримати адресу пам'яті будь-якої змінної, а потім працювати з нею. + + int x = 0; + printf("%p\n", (void *)&x); // Оператор & повертає адресу змінної у пам'яті + // (%p форматує об'єкт вказівника типу void *) + // => Виводить деяку адресу в пам'яті + + // Для оголошення вказівника потрібно поставити * перед його назвою. + int *px, not_a_pointer; // px - це вказівник на цілочисельне значення (int) + px = &x; // Зберігає адресу змінної x в px + printf("%p\n", (void *)px); // => Виводить адресу в пам'яті + printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer)); + // => Виводить "8, 4" на звичайній 64-бітній системі + + // Щоб прочитати значення, яке зберігається за адресою, на яку вказує вказівник, + // потрібно поставити знак * перед назвою змінної. + // Так, * використовується одночасно і для оголошення вказівника, і для отримання + // значення за адресою. Звучить заплутано, проте тільки спочатку. + printf("%d\n", *px); // => Виводить 0, значення x + + // Можна також змінити значення, на яке посилається вказівник. + // Тут звернення до адреси обернене у круглі дужки, тому що + // ++ має вищий пріоритет виконання, ніж *. + (*px)++; // Збільшити значення, на яке вказує px, на 1 + printf("%d\n", *px); // => Виводить 1 + printf("%d\n", x); // => Виводить 1 + + // Масиви зручно використовувати для виділення неперервного блоку пам'яті. + int x_array[20]; // оголошує масив з 20 елементів (розмір можна задати лише один раз) + int xx; + for (xx = 0; xx < 20; xx++) { + x_array[xx] = 20 - xx; + } // Ініціалізує x_array значеннями 20, 19, 18,... 2, 1 + + // Оголосити вказівник типу int, який посилається на масив x_array + int* x_ptr = x_array; + // x_ptr тепер вказує на перший елемент масиву (число 20). + // + // Це працює, тому що при зверненні до імені масиву повертається вказівник + // на перший елемент. Наприклад, коли масив передається у функцію або присвоюється + // вказівнику, він неявно приводиться до вказівника. + // Виключення: + // - коли вказівник передається як аргумент із оператором `&`: + int arr[10]; + int (*ptr_to_arr)[10] = &arr; // &arr НЕ має тип `int *`! + // Він має тип "вказівник на масив" (з 10 чисел). + // - коли масив - це рядковий літерал, що використовується для ініціалізації + // масив символів: + char otherarr[] = "foobarbazquirk"; + // - коли масив - це аргумент операторів `sizeof` або `alignof`: + int arraythethird[10]; + int *ptr = arraythethird; // те ж саме, що з int *ptr = &arr[0]; + printf("%zu, %zu\n", sizeof(arraythethird), sizeof(ptr)); + // Ймовірно, виводить "40, 4" або "40, 8" + + // Інкрементація та декрементація вказівника залежить від його типу. + // (так звана арифметика вказівників) + printf("%d\n", *(x_ptr + 1)); // => Виводить 19 + printf("%d\n", x_array[1]); // => Виводить 19 + + // Можна також динамічно виділити послідовні блоки в пам'яті за допомогою + // функції malloc зі стандартної бібліотеки. malloc приймає один аргумент типу + // size_t, що описує кількість байтів для виділення (зазвичай із купи, проте це + // може бути неправдою на вбудованих системах - стандарт С нічого про це не повідомляє). + int *my_ptr = malloc(sizeof(*my_ptr) * 20); + for (xx = 0; xx < 20; xx++) { + *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx + } // Проініціалізувати пам'ять значеннями 20, 19, 18, 17... 2, 1 (як int) + + // Будьте обережними із передачею значень, що надаються користувачем, в malloc! + // Про всяк випадок, використовуйте calloc в таких ситуаціях (який, на відміну від + // malloc, також заповнює пам'ять нулями). + int* my_other_ptr = calloc(20, sizeof(int)); + + // Немає стандартного способу визначити розмір динамічно виділеного масиву в С. + // Через це, якщо масиви будуть часто передаватись в програмі, потрібна інша змінна, + // яка буде відслідковувати кількість елементів в масиві. Детальніше в розділі + // про функції. + size_t size = 10; + int *my_arr = calloc(size, sizeof(int)); + // Додати елемент до масиву. + size++; + my_arr = realloc(my_arr, sizeof(int) * size); + if (my_arr == NULL) { + // Не забувайте перевіряти результат виконання realloc на помилки! + return + } + my_arr[10] = 5; + + // Робота з вказівниками може призводити до неочікуваних і непрогнозованих + // результатів, якщо звернутись до пам'яті, що не була виділена вами. + printf("%d\n", *(my_ptr + 21)); // => Хто зна, що буде виведено. + // Може навіть вилетіти з помилкою. + + // Після закінчення роботи із виділеною за допомогою malloc пам'яттю, її обов'язково + // потрібно звільнити. Інакше ніхто не зможе нею скористатися, аж поки програма не + // завершить свою роботу (така ситуація називається "витоком пам'яті"). + free(my_ptr); + + // Рядки - це масиви символів, проте вони найчастіше представлені як + // вказівник на символ (тобто, вказівник на перший елемент масиву). Вважається + // хорошим підходом використовувати `const char *', посилаючись на об'єкт + // рядка, оскільки його не можна змінити ("foo"[0] = 'a' ЗАБОРОНЕНО). + const char *my_str = "This is my very own string literal"; + printf("%c\n", *my_str); // => 'T' + + // Це не працюватиме, якщо рядок - це масив (потенційно створений за допомогою + // рядкового літерала), що зберігається у частині пам'яті, яку можна перезаписувати: + char foo[] = "foo"; + foo[0] = 'a'; // Дозволяється, foo тепер містить "aoo" + + function_1(); +} // Кінець функції main + +/////////////////////////////////////// +// Функції +/////////////////////////////////////// + +// Синтаксис оголошення функції: +// <тип повернення> <назва функції>(<аргументи>) + +int add_two_ints(int x1, int x2) +{ + return x1 + x2; // Використовуйте return, щоб повернути значення +} + +/* +Дані у функцію передають за значенням. Коли функція викликається, аргументи, що +передаються у функцію, копіюються з оригіналів (окрім масивів). Всі зміни над +значенням аргументів всередині функції не впливають на значення оригіналів. + +Використовуйте вказівники, якщо потрібно редагувати безпосередньо оригінальні +значення аргументів. + +Приклад: замінити рядок на обернений. +*/ + +// void означає, що функція нічого не повертає +void str_reverse(char *str_in) +{ + char tmp; + size_t ii = 0; + size_t len = strlen(str_in); // `strlen()` це частина стандартної бібліотеки С + // Зауважте: довжина, яку повертає `strlen`, не включає + // термінальний NULL байт ('\0') + for (ii = 0; ii < len / 2; ii++) { // в C99 можна напряму оголошувати тип `ii` в циклі + tmp = str_in[ii]; + str_in[ii] = str_in[len - ii - 1]; // ii-й символ з кінця + str_in[len - ii - 1] = tmp; + } +} +// Зауважте: для використання strlen() потрібно завантажити файл заголовку string.h + +/* +char c[] = "This is a test."; +str_reverse(c); +printf("%s\n", c); // => ".tset a si sihT" +*/ +/* +Оскільки можна повертати тільки одну змінну, для зміни значення більшої +кількості змінних можна використовувати виклик за посиланням +*/ +void swapTwoNumbers(int *a, int *b) +{ + int temp = *a; + *a = *b; + *b = temp; +} +/* +int first = 10; +int second = 20; +printf("first: %d\nsecond: %d\n", first, second); +swapTwoNumbers(&first, &second); +printf("first: %d\nsecond: %d\n", first, second); +// змінні обмінюються значеннями +*/ + +/* +Масиви завжди передаються у функції як вказівники, не зважаючи на тип масиву +(статичний чи динамічний). Тому всередині функція не знає про розмір масиву. +*/ +// Розмір масиву завжди має передаватись разом із масивом! +void printIntArray(int *arr, size_t size) { + int i; + for (i = 0; i < size; i++) { + printf("arr[%d] is: %d\n", i, arr[i]); + } +} +/* +int my_arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +int size = 10; +printIntArray(my_arr, size); +// виведе "arr[0] is: 1" і т.д. +*/ + +// Ключове слово extern використовується, якщо всередині функції потрібно звернутись +// до змінної, що була оголошена поза функцією. +int i = 0; +void testFunc() { + extern int i; // використовуємо зовнішню змінну i +} + +// Зробити зовнішню змінну приватною у вихідному файлі за допомогою static: +static int j = 0; // інші файли, що використовують testFunc2(), + // не матимуть доступу до змінної j +void testFunc2() { + extern int j; +} +// Ключове слово static робить змінну недоступною для коду поза даною одиницею +// компіляції. (На більшості систем, одиниця компіляції - це файл). +// static можна використовувати до глобальних змінних, функцій, локальних +// змінних у функціях. Локальні змінні, проініціалізовані static, поводять +// себе як глобальні змінні, проте тільки в межах даного файлу. Статичні +// змінні ініціалізуються 0, якщо інше значення не було вказане. +// **Як варіант, функції можна зробити приватними оголосивши їх як static** + +/////////////////////////////////////// +// Користувацькі типи та структури +/////////////////////////////////////// + +// Ключове слово typedef використовується, щоб створити псевдонім типу +typedef int my_type; +my_type my_type_var = 0; + +// Структури - це такі собі колекції з даними. Пам'ять для полів виділяється +// послідовно, в порядку їх написання: +struct rectangle { + int width; + int height; +}; + +// Проте це не означає, що +// sizeof(struct rectangle) == sizeof(int) + sizeof(int) +// в зв'язку з вирівнюванням пам'яті [1] + +void function_1() +{ + struct rectangle my_rec; + + // Доступ до полів структури відбувається через . + my_rec.width = 10; + my_rec.height = 20; + + // Можна створити вказівники на структуру + struct rectangle *my_rec_ptr = &my_rec; + + // Звернення до структури через вказівник та зміна значень поля: + (*my_rec_ptr).width = 30; + + // Але є й альтернативний спосіб звернутись до поля через вказівник, використовуючи + // оператор -> (краще читається) + my_rec_ptr->height = 10; // Те ж саме, що (*my_rec_ptr).height = 10; +} + +// Можна використати typedef перед struct +typedef struct rectangle rect; + +int area(rect r) +{ + return r.width * r.height; +} + +// Якщо ваша структура доволі громіздка, можна звертатись до неї через вказівник, +// щоб уникнути копіювання всієї структури: +int areaptr(const rect *r) +{ + return r->width * r->height; +} + +/////////////////////////////////////// +// Вказівники на функції +/////////////////////////////////////// +/* +Під час виконання функції знаходяться за відомими адресами в пам'яті. Вказівники +на функції - це ті ж самі вказівники, що зберігають адресу у пам'яті, проте можуть +використовуватись, щоб викликати функції напряму і передавати обробники (або функції зі +зворотнім зв'язком). Хоча, синтаксис спочатку може бути доволі незрозумілим. + +Приклад: use str_reverse from a pointer +*/ +void str_reverse_through_pointer(char *str_in) { + // Оголосити вказівник на функцію під назвою f. + void (*f)(char *); // Сигнатура повинна точно співпадати із цільовою функцією. + f = &str_reverse; // Присвойте адресу певної функції (визначається під час виконання) + // f = str_reverse; повинно працювати також + (*f)(str_in); // Виклик функції через вказівник + // f(str_in); // Це альтернативний, але теж вірний синтаксис виклику функції. +} + +/* +Якщо сигнатури функцій співпадають, можна присвоїти будь-яку функцію тому ж +самому вказівнику. Вказівники на функції зазвичай використовуються як псевдоніми +для спрощення та покращення читабельності коду. Приклад: +*/ + +typedef void (*my_fnp_type)(char *); + +// Використання при оголошенні змінної вказівника: +// ... +// my_fnp_type f; + + +// Спеціальні символи: +/* +'\a'; // символ попередження (дзвінок) +'\n'; // символ нового рядка +'\t'; // символ табуляції (вирівнювання по лівому краю) +'\v'; // вертикальна табуляція +'\f'; // нова сторінка +'\r'; // повернення каретки +'\b'; // стирання останнього символу +'\0'; // нульовий символ. Зазвичай розташовується в кінці рядка. +// hello\n\0. \0 використовується для позначення кінця рядка. +'\\'; // зворотній слеш +'\?'; // знак питання +'\''; // одинарні лапки +'\"'; // подвійні лапки +'\xhh'; // шістнадцяткове число. Наприклад: '\xb' = символ вертикальної табуляції +'\0oo'; // вісімкове число. Наприклад: '\013' = символ вертикальної табуляції + +// форматування виводу: +"%d"; // ціле число (int) +"%3d"; // ціле число, щонайменше 3 символи (вирівнювання по правому краю) +"%s"; // рядок +"%f"; // число з плаваючою крапкою (float) +"%ld"; // велике ціле число (long) +"%3.2f"; // число з плаваючою крапкою, щонайменше 3 цифри зліва і 2 цифри справа +"%7.4s"; // (аналогічно для рядків) +"%c"; // символ +"%p"; // вказівник. Зазначте: потребує перетворення типу на (void *) перед + // використанням у `printf`. +"%x"; // шістнадцяткове число +"%o"; // вісімкове число +"%%"; // друкує % +*/ + +/////////////////////////////////////// +// Порядок виконання +/////////////////////////////////////// + +//---------------------------------------------------// +// Оператори | Асоціативність// +//---------------------------------------------------// +// () [] -> . | зліва направо // +// ! ~ ++ -- + = *(type)sizeof | справа наліво // +// * / % | зліва направо // +// + - | зліва направо // +// << >> | зліва направо // +// < <= > >= | зліва направо // +// == != | зліва направо // +// & | зліва направо // +// ^ | зліва направо // +// | | зліва направо // +// && | зліва направо // +// || | зліва направо // +// ?: | справа наліво // +// = += -= *= /= %= &= ^= |= <<= >>= | справа наліво // +// , | зліва направо // +//---------------------------------------------------// + +/****************************** Файли заголовків ********************************* + +Файли заголовків важливі в С. Вони розділяють вихідний код та визначення на різні +файли, що робить їх кращими для розуміння. + +Файли заголовків синтаксично подібні до вихідних файлів С, проте описуються у".h" +файлах. Їх можна додати в код за допомогою директиви #include "example.h", якщо +example.h існує в тому ж каталозі, що і файл С. +*/ + +/* +Так можна запобігти тому, що заголовок буде оголошений кілька разів. Така ситуація +виникає у випадку циклічної залежності, тобто коли вміст заголовку вже було +оголошено. +*/ +#ifndef EXAMPLE_H /* якщо EXAMPLE_H ще не оголошено. */ +#define EXAMPLE_H /* Визначити макрос EXAMPLE_H. */ + +/* +Заголовки можна додавати в інші заголовки, таким чином вони разом додаються +у подальшому. +*/ +#include <string.h> + +/* +Макроси можуть бути визначені також у заголовку та використовуватись у файлах, +що містять цей заголовок. +*/ +#define EXAMPLE_NAME "Dennis Ritchie" + +/* Макроси функції також можна визначити. */ +#define ADD(a, b) ((a) + (b)) +/* +Зверніть увагу на круглі дужки навколо аргументів! Важливо переконатись, що +a та b не можна проінтерпретувати інакше. Наприклад: +MUL(x, y) (x * y); +MUL(1 + 2, 3) -> (1 + 2 * 3), що є помилкою +*/ + +/* Struct та typedef можуть використовуватись для узгодженості між файлами. */ +typedef struct Node +{ + int val; + struct Node *next; +} Node; + +/* Так само і перелічення. */ +enum traffic_light_state {GREEN, YELLOW, RED}; + +/* +Прототипи функцій також можна оголосити так, щоб використовувати у кількох +файлах. Але так робити не варто. Краще оголосити їх у С файлі. +*/ +Node createLinkedList(int *vals, int len); + +/* +Окрім вище згаданих випадків, всі інші визначення мають описуватись у С файлах. +*/ + +#endif /* Кінець директиви передкомпіляції if. */ + +``` +## Додаткові матеріали + +Кращим посібником для вивчення С буде книга авторства Деніса Рітчі (творець С) та Браяна Кернігана, +[K&R, aka "The C Programming Language"](https://en.wikipedia.org/wiki/The_C_Programming_Language). +Але обережно з нею, книга старезна і містить неточності (ідеї, що вже вважаються не надто прийнятними). + +Ще одним хорошим ресурсом є книга "Learn C The Hard Way" (наявна тільки англійською). + +На деякі часті запитання дасть відповідь англомовний ресурс [compl.lang.c Frequently Asked Questions](http://c-faq.com). + +Нагадаю, що важливо використовувати правильні інтервали, відступи та загалом мати узгоджений стиль коду. +Зручний для читання код краще, ніж складний код або зроблений нашвидкоруч. За прикладом можна звернутись до +[Linux kernel coding style](https://www.kernel.org/doc/Documentation/process/coding-style.rst). + +Щодо всього іншого, Ґуґл на допомогу! + +[1] [Чому розмір структури не дорівнює сумі розмірів її полів? (англ.)](http://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member) |