summaryrefslogtreecommitdiffhomepage
path: root/uk-ua/c-ua.html.markdown
blob: c4dac75f45630ed76752a5e28a3826df10291ad7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
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)