c.md

Личный сайт Go-разработчика из Казани

Что ж, Си всё ещё является лидером среди современных высокопроизводительных языков.

Для большинства программистов, Си – это самый низкоуровневый язык на котором они когда-либо писали, но этот язык даёт больше, чем просто повышение производительности. Держите это руководство в памяти и вы сможете использовать Си максимально эффективно.

1// Однострочный комментарий начинается с // - доступен только после С99. 2 3/* 4Многострочный комментарий выглядит так. Работает начиная с С89. 5*/ 6 7// Импорт файлов происходит с помощью **#include** 8#include <stdlib.h> 9#include <stdio.h> 10#include <string.h> 11 12// Файлы <в угловых скобочках> будут подключаться из стандартной библиотеки. 13// Свои файлы необходимо подключать с помощью "двойных кавычек". 14#include "my_header.h" 15 16// Объявление функций должно происходить в .h файлах или вверху .c файла. 17void function_1(); 18void function_2(); 19 20// Точка входа в программу – это функция main. 21int main() { 22 // для форматированного вывода в консоль используется printf 23 // %d – означает, что будем выводить целое число, \n переводит указатель вывода 24 // на новую строчку 25 printf("%d\n", 0); // => напечатает "0" 26 // Каждый оператор заканчивается точкой с запятой. 27 28 /////////////////////////////////////// 29 // Типы 30 /////////////////////////////////////// 31 32 // int обычно имеет длину 4 байта 33 int x_int = 0; 34 35 // short обычно имеет длину 2 байта 36 short x_short = 0; 37 38 // char гарантированно имеет длину 1 байта 39 char x_char = 0; 40 char y_char = 'y'; // Символьные литералы заключаются в кавычки '' 41 42 // long как правило занимает от 4 до 8 байт 43 // long long занимает как минимум 64 бита 44 long x_long = 0; 45 long long x_long_long = 0; 46 47 // float это 32-битное число с плавающей точкой 48 float x_float = 0.0; 49 50 // double это 64-битное число с плавающей точкой 51 double x_double = 0.0; 52 53 // Целые типы могут быть беззнаковыми. 54 unsigned short ux_short; 55 unsigned int ux_int; 56 unsigned long long ux_long_long; 57 58 // sizeof(T) возвращает размер переменной типа Т в байтах. 59 // sizeof(obj) возвращает размер объекта obj в байтах. 60 printf("%zu\n", sizeof(int)); // => 4 (на большинстве машин int занимает 4 байта) 61 62 // Если аргументом sizeof будет выражение, то этот аргумент вычисляется 63 // ещё во время компиляции кода (кроме динамических массивов). 64 int a = 1; 65 // size_t это беззнаковый целый тип который использует как минимум 2 байта 66 // для записи размера объекта 67 size_t size = sizeof(a++); // a++ не выполнится 68 printf("sizeof(a++) = %zu, где a = %d\n", size, a); 69 // выведет строку "sizeof(a++) = 4, где a = 1" (на 32-битной архитектуре) 70 71 // Можно задать размер массива при объявлении. 72 char my_char_array[20]; // Этот массив занимает 1 * 20 = 20 байт 73 int my_int_array[20]; // Этот массив занимает 4 * 20 = 80 байт (сумма 4-битных слов) 74 75 // Можно обнулить массив при объявлении. 76 char my_array[20] = {0}; 77 78 // Индексация массива происходит также как и в других Си-подобных языках. 79 my_array[0]; // => 0 80 81 // Массивы изменяемы. Это просто память как и другие переменные. 82 my_array[1] = 2; 83 printf("%d\n", my_array[1]); // => 2 84 85 // В C99 (а также опционально в C11), массив может быть объявлен динамически. 86 // Размер массива не обязательно должен быть рассчитан на этапе компиляции. 87 printf("Enter the array size: "); // спрашиваем юзера размер массива 88 char buf[0x100]; 89 fgets(buf, sizeof buf, stdin); 90 size_t size = strtoul(buf, NULL, 10); // strtoul парсит строку в беззнаковое целое 91 int var_length_array[size]; // объявление динамического массива 92 printf("sizeof array = %zu\n", sizeof var_length_array); 93 // Вывод программы (в зависимости от архитектуры) будет таким: 94 // > Enter the array size: 10 95 // > sizeof array = 40 96 97 // Строка – это просто массив символов, оканчивающийся нулевым (NUL (0x00)) байтом 98 // представляемым в строке специальным символом '\0'. 99 // Нам не нужно вставлять нулевой байт в строковой литерал, 100 // компилятор всё сделает за нас. 101 char a_string[20] = "This is a string"; 102 printf("%s\n", a_string); // %s обозначает вывод строки 103 104 printf("%d\n", a_string[16]); // => 0 105 // байт #17 тоже равен 0 (а также 18, 19, и 20) 106 107 // Если между одинарными кавычками есть символ – это символьный литерал, 108 // но это тип int, а не char (по историческим причинам). 109 110 int cha = 'a'; // хорошо 111 char chb = 'a'; // тоже хорошо (подразумевается преобразование int в char) 112 113 /////////////////////////////////////// 114 // Операторы 115 /////////////////////////////////////// 116 117 // Можно использовать множественное объявление. 118 int i1 = 1, i2 = 2; 119 float f1 = 1.0, f2 = 2.0; 120 121 // Арифметика обычная 122 i1 + i2; // => 3 123 i2 - i1; // => 1 124 i2 * i1; // => 2 125 i1 / i2; // => 0 (0.5, но обрезается до 0) 126 127 f1 / f2; // => 0.5, плюс-минус погрешность потому что, 128 // цифры с плавающей точкой вычисляются неточно! 129 130 // Остаток от деления 131 11 % 3; // => 2 132 133 // Операции сравнения вам уже знакомы, но в Си нет булевого типа. 134 // Вместо него используется int. 0 это false, всё остальное это true. 135 // Операции сравнения всегда возвращают 1 или 0. 136 3 == 2; // => 0 (false) 137 3 != 2; // => 1 (true) 138 3 > 2; // => 1 139 3 < 2; // => 0 140 2 <= 2; // => 1 141 2 >= 2; // => 1 142 143 // Си это не Питон – операции сравнения могут быть только парными. 144 int a = 1; 145 // ОШИБКА: 146 int between_0_and_2 = 0 < a < 2; 147 // Правильно: 148 int between_0_and_2 = 0 < a && a < 2; 149 150 // Логика 151 !3; // => 0 (логическое НЕ) 152 !0; // => 1 153 1 && 1; // => 1 (логическое И) 154 0 && 1; // => 0 155 0 || 1; // => 1 (логическое ИЛИ) 156 0 || 0; // => 0 157 158 // Битовые операторы 159 ~0x0F; // => 0xF0 (побитовое отрицание) 160 0x0F & 0xF0; // => 0x00 (побитовое И) 161 0x0F | 0xF0; // => 0xFF (побитовое ИЛИ) 162 0x04 ^ 0x0F; // => 0x0B (исключающее ИЛИ (XOR)) 163 0x01 << 1; // => 0x02 (побитовый сдвиг влево (на 1)) 164 0x02 >> 1; // => 0x01 (побитовый сдвиг вправо (на 1)) 165 166 // Будьте осторожны при сдвиге беззнакового int, эти операции не определены: 167 // - сдвиг в знаковый бит у целого числа (int a = 1 << 32) 168 // - сдвиг влево отрицательных чисел (int a = -1 << 2) 169 170 /////////////////////////////////////// 171 // Структуры ветвления 172 /////////////////////////////////////// 173 174 // Условный оператор 175 if (0) { 176 printf("I am never run\n"); 177 } else if (0) { 178 printf("I am also never run\n"); 179 } else { 180 printf("I print\n"); 181 } 182 183 // Цикл с предусловием 184 int ii = 0; 185 while (ii < 10) { 186 printf("%d, ", ii++); // инкрементация происходит после того как 187 // значение ii передано ("postincrement") 188 } // => prints "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " 189 190 printf("\n"); 191 192 // Цикл с постусловием 193 int kk = 0; 194 do { 195 printf("%d, ", kk); 196 } while (++kk < 10); // инкрементация происходит перед тем как 197 // передаётся значение kk ("preincrement") 198 // => prints "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " 199 200 printf("\n"); 201 202 // Цикл со счётчиком 203 int jj; 204 for (jj=0; jj < 10; jj++) { 205 printf("%d, ", jj); 206 } // => prints "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " 207 208 printf("\n"); 209 210 // Ветвление с множественным выбором 211 switch (some_integral_expression) { 212 case 0: // значения должны быть целыми константами (и могут быть выражениями) 213 do_stuff(); 214 break; // если не написать break; то управление будет передано следующему блоку 215 case 1: 216 do_something_else(); 217 break; 218 default: 219 // если не было совпадения, то выполняется блок default: 220 fputs("ошибка!\n", stderr); 221 exit(-1); 222 break; 223 } 224 225 /////////////////////////////////////// 226 // Форматирование вывода 227 /////////////////////////////////////// 228 229 // Каждое выражение в Си имеет тип, но вы можете привести один тип к другому, 230 // если хотите (с некоторыми искажениями). 231 232 int x_hex = 0x01; // Вы можете назначать переменные с помощью шестнадцатеричного кода. 233 234 // Приведение типов будет пытаться сохранять цифровые значения. 235 printf("%d\n", x_hex); // => Prints 1 236 printf("%d\n", (short) x_hex); // => Prints 1 237 printf("%d\n", (char) x_hex); // => Prints 1 238 239 // Типы могут переполняться без вызова предупреждения. 240 printf("%d\n", (unsigned char) 257); // => 1 (Max char = 255 if char is 8 bits long) 241 242 // Для определения максимального значения типов `char`, `signed char` и `unisigned char`, 243 // соответственно используйте CHAR_MAX, SCHAR_MAX и UCHAR_MAX макросы из <limits.h> 244 245 // Целые типы могут быть приведены к вещественным и наоборот. 246 printf("%f\n", (float)100); // %f formats a float 247 printf("%lf\n", (double)100); // %lf formats a double 248 printf("%d\n", (char)100.0); 249 250 /////////////////////////////////////// 251 // Указатели 252 /////////////////////////////////////// 253 254 // Указатель – это переменная которая хранит адрес в памяти. 255 // При объявлении указателя указывается тип данных переменной на которую он будет ссылаться. 256 // Вы можете получить адрес любой переменной, а потом работать с ним. 257 258 // Используйте & для получения адреса переменной. 259 int x = 0; 260 printf("%p\n", (void *)&x); // => Напечатает адрес в памяти, где лежит переменная x 261 // (%p выводит указатель на void *) 262 263 // Для объявления указателя нужно поставить * перед именем. 264 int *px, not_a_pointer; // px это указатель на int 265 px = &x; // сохранит адрес x в px 266 printf("%p\n", (void *)px); // => Напечатает адрес в памяти, где лежит переменная px 267 printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer)); 268 // => Напечатает "8, 4" в 64 битной системе 269 270 // Для того, чтобы получить значение по адресу, напечатайте * перед именем. 271 // Да, * используется при объявлении указателя и для получении значения по адресу 272 // немного запутано, но вы привыкнете. 273 printf("%d\n", *px); // => Напечатает 0, значение перемененной x 274 275 // Вы также можете изменять значение, на которое указывает указатель. 276 (*px)++; // Инкрементирует значение на которое указывает px на единицу 277 printf("%d\n", *px); // => Напечатает 1 278 printf("%d\n", x); // => Напечатает 1 279 280 // Массивы удобно использовать для большого количества однотипных данных. 281 int x_array[20]; 282 int xx; 283 for (xx = 0; xx < 20; xx++) { 284 x_array[xx] = 20 - xx; 285 } // Объявление x_array с значениями 20, 19, 18,... 2, 1 286 287 // Объявление указателя на int с адресом массива. 288 int* x_ptr = x_array; 289 // x_ptr сейчас указывает на первый элемент массива (со значением 20). 290 // Это работает, потому что при обращении к имени массива возвращается 291 // указатель на первый элемент. 292 // Например, когда массив передаётся в функцию или присваивается указателю, он 293 // неявно преобразуется в указатель. 294 // Исключения: когда массив является аргументом для оператор '&': 295 int arr[10]; 296 int (*ptr_to_arr)[10] = &arr; // &arr не является 'int *'! 297 // он является "указателем на массив" (из десяти 'int'ов). 298 // или когда массив это строчный литерал, используемый при объявлении массива символов: 299 char arr[] = "foobarbazquirk"; 300 // или когда массив является аргументом `sizeof` или `alignof` операторов: 301 int arr[10]; 302 int *ptr = arr; // то же самое что и "int *ptr = &arr[0];" 303 printf("%zu %zu\n", sizeof arr, sizeof ptr); // напечатает "40, 4" или "40, 8" 304 305 // Декрементация и инкрементация указателей зависит от их типа 306 // (это называется арифметика указателей) 307 printf("%d\n", *(x_ptr + 1)); // => Напечатает 19 308 printf("%d\n", x_array[1]); // => Напечатает 19 309 310 // Вы также можете динамически выделять несколько блоков памяти с помощью 311 // функции malloc из стандартной библиотеки, которая принимает один 312 // аргумент типа size_t – количество байт необходимых для выделения. 313 int *my_ptr = malloc(sizeof(*my_ptr) * 20); 314 for (xx = 0; xx < 20; xx++) { 315 *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx 316 } // Выделяет память для 20, 19, 18, 17... 2, 1 (как int'ы) 317 318 // Работа с памятью с помощью указателей может давать неожиданные и 319 // непредсказуемые результаты. 320 printf("%d\n", *(my_ptr + 21)); // => Напечатает кто-нибудь знает, что? 321 // Скорей всего программа вылетит. 322 323 // Когда вы закончили работать с памятью, которую ранее выделили, вам необходимо 324 // освободить её, иначе это может вызвать утечку памяти или ошибки. 325 free(my_ptr); 326 327 // Строки это массивы символов, но обычно они представляются как 328 // указатели на символ (как указатели на первый элемент массива). 329 // Хорошей практикой считается использование `const char *' при объявлении 330 // строчного литерала. При таком подходе литерал не может быть изменён. 331 // (например "foo"[0] = 'a' вызовет ошибку!) 332 333 const char *my_str = "This is my very own string literal"; 334 printf("%c\n", *my_str); // => 'T' 335 336 // Это не работает, если строка является массивом 337 // (потенциально задаваемой с помощью строкового литерала) 338 // который находится в перезаписываемой части памяти: 339 340 char foo[] = "foo"; 341 foo[0] = 'a'; // это выполнится и строка теперь "aoo" 342 343 void function_1() 344} // конец функции main() 345 346/////////////////////////////////////// 347// Функции 348/////////////////////////////////////// 349 350// Синтаксис объявления функции: 351// <возвращаемый тип> <имя функции>(аргументы) 352 353int add_two_ints(int x1, int x2) { 354 return x1 + x2; // Используйте return для возврата значения 355} 356 357/* 358Данные в функцию передаются "по значению", но никто не мешает 359вам передавать в функцию указатели и менять данные по указателям. 360 361Например: инвертировать строку прямо в функции 362*/ 363 364// void означает, что функция ничего не возвращает 365void str_reverse(char *str_in) { 366 char tmp; 367 int ii = 0; 368 size_t len = strlen(str_in); // `strlen()` является частью стандартной библиотеки 369 for (ii = 0; ii < len / 2; ii++) { 370 tmp = str_in[ii]; 371 str_in[ii] = str_in[len - ii - 1]; // ii-тый символ с конца 372 str_in[len - ii - 1] = tmp; 373 } 374} 375 376char c[] = "This is a test."; 377str_reverse(c); 378printf("%s\n", c); // => Выведет ".tset a si sihT" 379 380/////////////////////////////////////// 381// Типы и структуры определяемые пользователем 382/////////////////////////////////////// 383 384// typedef используется для задания стандартным типам своих названий 385typedef int my_type; 386my_type my_type_var = 0; 387 388// Структуры это просто коллекция данных, память выделяется последовательно, 389// в том порядке в котором записаны данные. 390struct rectangle { 391 int width; 392 int height; 393}; 394 395// sizeof(struct rectangle) == sizeof(int) + sizeof(int) – не всегда верно 396// из-за особенностей компиляции (необычное поведение при отступах)[1]. 397 398void function_1() { 399 struct rectangle my_rec; 400 401 // Доступ к структурам через точку 402 my_rec.width = 10; 403 my_rec.height = 20; 404 405 // Вы можете объявить указатель на структуру 406 struct rectangle *my_rec_ptr = &my_rec; 407 408 // Можно получить доступ к структуре и через указатель 409 (*my_rec_ptr).width = 30; 410 411 // ... или ещё лучше: используйте оператор -> для лучшей читабельности 412 my_rec_ptr->height = 10; // то же что и "(*my_rec_ptr).height = 10;" 413} 414 415// Вы можете применить typedef к структуре, для удобства. 416typedef struct rectangle rect; 417 418int area(rect r) { 419 return r.width * r.height; 420} 421 422// Если вы имеете большую структуру, можно получить доступ к ней "по указателю", 423// чтобы избежать копирования всей структуры. 424int area(const rect *r) { 425 return r->width * r->height; 426} 427 428/////////////////////////////////////// 429// Указатели на функции 430/////////////////////////////////////// 431 432/* 433Во время исполнения функции находятся по известным адресам в памяти. 434Указатель на функцию может быть использован для непосредственного вызова функции. 435Однако синтаксис может сбивать с толку. 436 437Пример: использование str_reverse по указателю 438*/ 439 440void str_reverse_through_pointer(char *str_in) { 441 // Определение функции через указатель. 442 void (*f)(char *); // Сигнатура должна полностью совпадать с целевой функцией. 443 f = &str_reverse; // Присвоить фактический адрес (во время исполнения) 444 // "f = str_reverse;" тоже будет работать. 445 // Имя функции (как и массива) возвращает указатель на начало. 446 (*f)(str_in); // Просто вызываем функцию через указатель. 447 // "f(str_in);" или вот так 448}

На почитать

Лучше всего найдите копию K&R, она же «The C Programming Language», это книга написанная создателями Си. Но будьте осторожны, она содержит идеи которые больше не считаются хорошими.

Если у вас появился вопрос, почитайте compl.lang.c Frequently Asked Questions.

Очень важно использовать правильные отступы и ставить пробелы в нужных местах. Читаемый код лучше, чем красивый или быстрый код. Чтобы научиться писать хороший код, почитайте Linux kernel coding style.

[1] http://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member