mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2025-04-26 15:13:56 +00:00
[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>
This commit is contained in:
parent
757ed9b49b
commit
ed5f7694fd
860
uk-ua/c-ua.html.markdown
Normal file
860
uk-ua/c-ua.html.markdown
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user