Перейти к основному содержимому

5.16. Справочник по Си

Разработчику Архитектору

Справочник по Си

Основы

1. Структура программы на языке Си

Программа на языке Си состоит из одной или нескольких функций. Обязательной является функция main, с которой начинается выполнение программы.

#include <stdio.h>  // подключение заголовочного файла стандартной библиотеки

int main(void) {
printf("Hello, World!\n");
return 0;
}
  • #include — директива препроцессора для включения содержимого заголовочного файла.
  • int main(void) — определение главной функции без параметров, возвращающей целое число.
  • printf — функция вывода текста в стандартный поток вывода.
  • return 0; — завершение функции main со значением 0, что означает успешное завершение программы.

2. Базовые типы данных

Язык Си предоставляет следующие фундаментальные типы данных:

ТипОписаниеРазмер (обычно)Диапазон значений
charСимвол или малое целое1 байт-128 до 127 (signed), 0 до 255 (unsigned)
signed charЦелое со знаком1 байт-128 до 127
unsigned charЦелое без знака1 байт0 до 255
short / short intКороткое целое2 байта-32 768 до 32 767
unsigned shortКороткое целое без знака2 байта0 до 65 535
intЦелое4 байта-2 147 483 648 до 2 147 483 647
unsigned intЦелое без знака4 байта0 до 4 294 967 295
long / long intДлинное целое4 или 8 байтзависит от платформы
unsigned longДлинное целое без знака4 или 8 байтзависит от платформы
long longОчень длинное целое8 байт-9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
unsigned long longОчень длинное целое без знака8 байт0 до 18 446 744 073 709 551 615
floatВещественное одинарной точности4 байта~ ±3.4e±38 (7 цифр точности)
doubleВещественное двойной точности8 байт~ ±1.7e±308 (15–16 цифр точности)
long doubleВещественное расширенной точности10–16 байтзависит от компилятора и архитектуры

Размеры типов зависят от архитектуры процессора и компилятора. Для точного определения размера используется оператор sizeof.

Пример:

printf("Размер int: %zu байт\n", sizeof(int));

3. Модификаторы типов

Модификаторы изменяют свойства базовых типов:

  • signed — явное указание знакового типа (по умолчанию для int, char может быть signed или unsigned в зависимости от реализации).
  • unsigned — указывает, что значение не может быть отрицательным.
  • short — уменьшает размер целого типа.
  • long — увеличивает размер целого или вещественного типа.
  • long long — дополнительно увеличивает размер целого типа.

Комбинации:

  • unsigned long long int
  • signed char
  • long double

4. Константы

Константы — значения, которые не изменяются во время выполнения программы.

Целочисленные константы

  • Десятичные: 42
  • Восьмеричные: 052 (начинаются с 0)
  • Шестнадцатеричные: 0x2A (начинаются с 0x или 0X)
  • Двоичные (в некоторых компиляторах): 0b101010 (не входит в стандарт C89/C90, но поддерживается в GCC и Clang как расширение)

Можно добавлять суффиксы:

  • U или u — беззнаковый (42U)
  • L или l — длинный (42L)
  • LL или ll — очень длинный (42LL)
  • Комбинации: 42ULL, 123456789012345LL

Вещественные константы

  • Десятичные: 3.14, .5, 1.
  • Экспоненциальная форма: 6.02e23, 1.23E-4
  • Суффиксы: f или F — float (3.14f), l или L — long double (3.14L)

Символьные константы

  • 'A' — один символ, хранится как его код ASCII
  • Escape-последовательности: '\n', '\t', '\\', '\'', '\"', '\0' (нулевой символ)

Строковые литералы

  • "Hello" — массив символов, завершённый нулём (\0)
  • Многострочные литералы:
    "Первая часть "
    "вторая часть"
    автоматически объединяются в одну строку.

Константы через #define

#define PI 3.14159
#define MAX_SIZE 100

Константы через const

const int MAX = 100;
const char* GREETING = "Привет";

5. Переменные

Переменная — именованная область памяти, хранящая значение определённого типа.

Объявление:

int age;
float price = 19.99;
char initial = 'T';

Правила именования:

  • Имя начинается с буквы или подчёркивания _
  • Может содержать буквы, цифры, подчёркивания
  • Регистрозависимо: count и Count — разные переменные
  • Не может совпадать с ключевыми словами (int, return, if и т.д.)

Область видимости:

  • Локальные переменные — внутри функции или блока { }
  • Глобальные переменные — вне всех функций, доступны во всей программе

Хранение:

  • auto — автоматическая (по умолчанию для локальных)
  • static — сохраняет значение между вызовами функции
  • extern — ссылка на глобальную переменную, определённую в другом файле
  • register — подсказка компилятору разместить переменную в регистре (редко используется)

6. Операторы

Арифметические операторы

  • + — сложение
  • - — вычитание
  • * — умножение
  • / — деление (целочисленное, если оба операнда целые)
  • % — остаток от деления (только для целых)

Операторы присваивания

  • = — простое присваивание
  • +=, -=, *=, /=, %= — составные присваивания

Операторы инкремента и декремента

  • ++ — увеличение на 1
  • -- — уменьшение на 1
  • Префиксная форма (++x) — сначала изменение, потом использование
  • Постфиксная форма (x++) — сначала использование, потом изменение

Операторы сравнения

  • == — равно
  • != — не равно
  • < — меньше
  • > — больше
  • <= — меньше или равно
  • >= — больше или равно

Логические операторы

  • && — логическое И
  • || — логическое ИЛИ
  • ! — логическое НЕ

Побитовые операторы

  • & — побитовое И
  • | — побитовое ИЛИ
  • ^ — побитовое исключающее ИЛИ (XOR)
  • ~ — побитовое НЕ (инверсия)
  • << — сдвиг влево
  • >> — сдвиг вправо

Условный оператор (тернарный)

result = (a > b) ? a : b;

Оператор sizeof

Возвращает размер типа или переменной в байтах:

size_t s = sizeof(int);

Оператор взятия адреса & и разыменования *

  • &x — адрес переменной x
  • *p — значение по адресу, на который указывает указатель p

Указатели, массивы, строки и управление памятью

1. Указатели

Указатель — переменная, хранящая адрес другой переменной в памяти.

Объявление указателя

int *p;          // указатель на int
char *str; // указатель на char
void *generic; // универсальный указатель (без типа)

Инициализация

int x = 42;
int *p = &x; // p содержит адрес переменной x

Разыменование

int value = *p;  // значение по адресу, на который указывает p (равно 42)
*p = 100; // изменение значения переменной x через указатель

Арифметика указателей

  • Прибавление целого числа к указателю смещает его на n * sizeof(тип) байт.
  • Разность двух указателей одного типа даёт количество элементов между ними.

Пример:

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // эквивалентно &arr[0]
printf("%d\n", *(p + 2)); // выводит 30

Указатели и функции

Указатели позволяют передавать аргументы по ссылке:

void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}

Вызов:

int x = 5, y = 10;
swap(&x, &y);

Указатель на указатель

int x = 42;
int *p = &x;
int **pp = &p;
printf("%d\n", **pp); // 42

Константные указатели

  • const int *p — указатель на константное значение (нельзя изменить значение через p)
  • int *const p = &x — константный указатель (нельзя изменить адрес, на который он указывает)
  • const int *const p = &x — константный указатель на константное значение

2. Массивы

Массив — упорядоченный набор элементов одного типа, размещённых в непрерывной области памяти.

Объявление и инициализация

int arr[5];                      // массив из 5 неинициализированных int
int nums[] = {1, 2, 3, 4, 5}; // размер определяется автоматически
int matrix[3][3]; // двумерный массив

Доступ к элементам

arr[0] = 100;        // первый элемент
printf("%d", arr[2]); // третий элемент

Индексация начинается с 0. Максимальный допустимый индекс — размер - 1.

Массивы и указатели

Имя массива без индекса преобразуется в указатель на первый элемент:

int arr[5];
int *p = arr; // то же, что и &arr[0]

Размер массива можно получить только в той области видимости, где он объявлен как массив (не как параметр функции):

int size = sizeof(arr) / sizeof(arr[0]); // работает только внутри той же функции

Передача массива в функцию

Массив всегда передаётся как указатель:

void print_array(int arr[], int n);      // эквивалентно int *arr
void print_array(int *arr, int n);

Функция не знает реальный размер массива — его нужно передавать отдельно.

3. Строки

В языке Си строка — это массив символов, завершённый нулевым символом \0.

Объявление строк

char str1[] = "Hello";                 // автоматический размер (6 байт: 5 букв + \0)
char str2[10] = "Hi"; // остаток заполняется нулями
char *str3 = "World"; // указатель на строковый литерал (константный!)

Важно: строковые литералы ("...") размещаются в защищённой памяти. Попытка изменить str3[0] = 'w' вызывает неопределённое поведение.

Функции работы со строками (из <string.h>)

ФункцияОписание
strlen(s)Возвращает длину строки (без учёта \0)
strcpy(dest, src)Копирует src в dest (включая \0)
strncpy(dest, src, n)Копирует не более n символов; не гарантирует завершение \0
strcat(dest, src)Добавляет src к концу dest
strncat(dest, src, n)Добавляет не более n символов из src
strcmp(s1, s2)Сравнивает строки: 0 — равны, <0 — s1 < s2, >0 — s1 > s2
strncmp(s1, s2, n)Сравнивает первые n символов
strchr(s, c)Ищет первое вхождение символа c в строке s
strstr(s1, s2)Ищет подстроку s2 в строке s1
strtok(s, delim)Разбивает строку на токены по разделителям

Пример:

#include <string.h>
char greeting[20] = "Hello";
strcat(greeting, ", World!");
printf("%s\n", greeting); // Hello, World!

4. Управление памятью

Язык Си предоставляет ручное управление динамической памятью через функции из <stdlib.h>.

Выделение памяти

  • malloc(size) — выделяет size байт, возвращает void*, содержимое не инициализировано
  • calloc(n, size) — выделяет память для n элементов по size байт, заполняет нулями
  • realloc(ptr, new_size) — изменяет размер ранее выделенного блока

Пример:

int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
// обработка ошибки
}
// использование arr...

Освобождение памяти

  • free(ptr) — освобождает память, выделенную через malloc, calloc или realloc
  • После free указатель становится недействительным («висячим»). Рекомендуется присвоить NULL.

Правила работы с динамической памятью

  • Всегда проверяйте результат malloc/calloc на NULL
  • Не вызывайте free для одного и того же указателя дважды
  • Не вызывайте free для указателя, не полученного через malloc/calloc/realloc
  • Освобождайте всю выделенную память до завершения программы (во избежание утечек)

Пример безопасного использования

int *create_array(size_t n) {
int *p = calloc(n, sizeof(int));
if (p == NULL) {
fprintf(stderr, "Ошибка выделения памяти\n");
exit(EXIT_FAILURE);
}
return p;
}

void destroy_array(int *p) {
free(p);
}

5. Многомерные массивы

Статические многомерные массивы

int matrix[3][4]; // 3 строки, 4 столбца
matrix[0][0] = 1;

Хранятся в памяти последовательно по строкам.

Динамические двумерные массивы (вариант 1: массив указателей)

int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = malloc(cols * sizeof(int));
}
// использование...
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);

Динамические двумерные массивы (вариант 2: единый блок памяти)

int *data = malloc(rows * cols * sizeof(int));
int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = data + i * cols;
}
// освобождение:
free(matrix);
free(data);

Второй вариант эффективнее по памяти и кэш-локальности.


Функции, рекурсия, область видимости, классы памяти и компиляция

1. Функции

Функция — именованный блок кода, выполняющий определённую задачу. Каждая программа на Си содержит хотя бы одну функцию — main.

Структура определения функции

возвращаемый_тип имя_функции(список_параметров) {
// тело функции
return значение; // если возвращаемый тип не void
}

Пример:

int max(int a, int b) {
return (a > b) ? a : b;
}

Объявление (прототип) функции

Перед использованием функции её можно объявить:

int max(int a, int b); // прототип

Прототип указывает компилятору сигнатуру функции: имя, типы параметров и возвращаемый тип. Это позволяет вызывать функцию до её определения.

Параметры функции

  • Параметры передаются по значению: внутри функции создаются копии аргументов.
  • Для изменения исходных переменных используются указатели.
  • Список параметров может быть пустым: void func(void) — явное указание отсутствия параметров.

Возвращаемое значение

  • Тип void означает, что функция ничего не возвращает.
  • Оператор return завершает выполнение функции и возвращает значение.
  • В функции main возврат 0 означает успешное завершение; ненулевые значения — ошибки.

Перегрузка функций

Язык Си не поддерживает перегрузку функций. Каждая функция должна иметь уникальное имя.

2. Рекурсия

Рекурсия — вызов функцией самой себя.

Условия корректной рекурсии

  • Должен существовать базовый случай — условие, при котором рекурсия прекращается.
  • Каждый рекурсивный вызов должен приближать вычисления к базовому случаю.

Пример: вычисление факториала

unsigned long long factorial(int n) {
if (n <= 1) return 1; // базовый случай
return n * factorial(n - 1); // рекурсивный вызов
}

Ограничения рекурсии

  • Глубина рекурсии ограничена размером стека вызовов.
  • Чрезмерная глубина приводит к переполнению стека (stack overflow).
  • Итеративные решения часто эффективнее по памяти.

3. Область видимости (scope)

Область видимости определяет, где в программе доступно имя переменной или функции.

Локальная область видимости

  • Переменные, объявленные внутри блока { }, видны только внутри этого блока и вложенных блоков.
  • Параметры функции также имеют локальную область видимости.

Глобальная область видимости

  • Переменные и функции, объявленные вне всех функций, видны во всём файле от места объявления до конца.
  • Если глобальный идентификатор объявлен до всех функций, он виден во всём файле.

Область видимости файла

  • Идентификаторы с модификатором static на уровне файла видны только в этом файле.
  • Это механизм инкапсуляции на уровне компиляции.

Пример:

static int counter = 0; // видна только в этом .c файле

void increment(void) {
counter++;
}

4. Классы памяти (storage classes)

Класс памяти определяет время жизни, область видимости и способ хранения переменной.

auto

  • По умолчанию для всех локальных переменных.
  • Существует только во время выполнения блока.
  • Хранится в стеке.

register

  • Подсказка компилятору разместить переменную в регистре процессора.
  • Применимо только к целочисленным и указательным типам.
  • Нельзя взять адрес (&) такой переменной.
  • Современные компиляторы игнорируют эту подсказку, так как сами эффективно распределяют регистры.

static

Применяется в двух контекстах:

  1. Для локальных переменных:

    • Время жизни — вся программа.
    • Инициализируется один раз.
    • Сохраняет значение между вызовами функции.
    void counter(void) {
    static int count = 0;
    count++;
    printf("%d\n", count);
    }
  2. Для глобальных переменных и функций:

    • Ограничивает область видимости текущим файлом.
    • Предотвращает конфликты имён при линковке нескольких файлов.

extern

  • Указывает, что переменная или функция определена в другом файле.
  • Используется для совместного доступа к глобальным данным между единицами компиляции.

Файл globals.c:

int global_var = 42;

Файл main.c:

extern int global_var; // ссылка на переменную из другого файла

5. Компиляция и сборка программы

Процесс преобразования исходного кода в исполняемый файл включает несколько этапов.

Этапы компиляции

  1. Препроцессирование
    Выполняется препроцессором (cpp). Обрабатывает директивы:

    • #include — вставка содержимого файла
    • #define — макроподстановка
    • #if, #ifdef, #ifndef, #else, #elif, #endif — условная компиляция
    • #pragma — специфичные для компилятора инструкции
  2. Компиляция
    Преобразует предварительно обработанный .c файл в ассемблерный код или объектный файл (.o или .obj).

  3. Ассемблирование (если генерируется ассемблер)
    Преобразует ассемблерный код в машинный код в объектном файле.

  4. Линковка
    Объединяет объектные файлы и библиотеки в единый исполняемый файл.

    • Разрешает внешние ссылки (extern)
    • Включает код из стандартной библиотеки (libc)

Команды компиляции (на примере GCC)

gcc -E program.c -o program.i      # только препроцессирование
gcc -S program.c -o program.s # до ассемблера
gcc -c program.c -o program.o # компиляция без линковки
gcc program.c -o program # полная компиляция и линковка

Многофайловые проекты

Структура:

main.c
utils.c
utils.h

utils.h:

#ifndef UTILS_H
#define UTILS_H
int add(int a, int b);
#endif

utils.c:

#include "utils.h"
int add(int a, int b) {
return a + b;
}

main.c:

#include <stdio.h>
#include "utils.h"

int main(void) {
printf("%d\n", add(2, 3));
return 0;
}

Сборка:

gcc main.c utils.c -o app

Или поэтапно:

gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
gcc main.o utils.o -o app

Заголовочные файлы (.h)

  • Содержат объявления: прототипы функций, определения типов, макросы.
  • Не содержат определений переменных (кроме static inline или const в некоторых случаях).
  • Защищаются от повторного включения через include guards (#ifndef ... #define ... #endif) или #pragma once.

6. Макросы и условная компиляция

Макросы с параметрами

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

Важно заключать параметры и всё выражение в скобки, чтобы избежать побочных эффектов при подстановке.

Специальные макросы

  • __FILE__ — имя текущего файла
  • __LINE__ — номер текущей строки
  • __func__ — имя текущей функции (C99)
  • __DATE__, __TIME__ — дата и время компиляции

Условная компиляция

#ifdef DEBUG
printf("Отладочная информация\n");
#endif

#if defined(_WIN32)
// код для Windows
#elif defined(__linux__)
// код для Linux
#endif

Часто используется для кроссплатформенности, отладки, включения/выключения функций.


Структуры, объединения, перечисления, typedef, битовые поля и продвинутый препроцессор

1. Структуры (struct)

Структура — составной тип данных, объединяющий несколько переменных (полей) разных типов под одним именем.

Объявление структуры

struct Point {
int x;
int y;
};

Определение переменных структурного типа

struct Point p1;                     // без инициализации
struct Point p2 = {10, 20}; // инициализация в порядке полей
struct Point p3 = {.y = 5, .x = 3}; // инициализация по именам (C99)

Доступ к полям

  • Через оператор . для переменных:
    p1.x = 100;
    printf("%d\n", p1.y);
  • Через оператор -> для указателей:
    struct Point *pp = &p1;
    pp->x = 200;
    printf("%d\n", pp->y);

Вложенные структуры

struct Address {
char street[50];
int zip;
};

struct Person {
char name[30];
struct Address addr;
};

// доступ:
struct Person p;
p.addr.zip = 12345;

Массивы структур

struct Student students[100];
students[0].grade = 5;

Передача структур в функции

  • По значению (копируется вся структура):
    void print_point(struct Point p) { ... }
  • По указателю (эффективнее для больших структур):
    void move_point(struct Point *p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
    }

Размер структуры

Размер структуры определяется с учётом выравнивания (padding). Поля выравниваются по границам, кратным их размеру, для повышения производительности.

Пример:

struct Example {
char a; // 1 байт
int b; // 4 байта → между a и b добавляется 3 байта padding
};
// sizeof(struct Example) == 8 на большинстве систем

Для управления выравниванием используются компилятор-специфичные директивы, например:

#pragma pack(1)  // отключает выравнивание
struct Packed {
char a;
int b;
};
#pragma pack() // восстанавливает настройки

2. Объединения (union)

Объединение — тип данных, все поля которого разделяют одну и ту же область памяти. Размер объединения равен размеру его самого большого поля.

Объявление и использование

union Data {
int i;
float f;
char str[20];
};

union Data d;
d.i = 10; // запись в поле i
d.f = 220.5; // перезаписывает содержимое i

Важно: в каждый момент времени в объединении корректно только одно поле — то, в которое последним выполнялась запись.

Применение

  • Интерпретация одного и того же участка памяти как разных типов.
  • Экономия памяти, когда объект может быть одного из нескольких типов, но не всех одновременно.

3. Перечисления (enum)

Перечисление — тип данных, определяющий набор именованных целочисленных констант.

Объявление

enum Color {
RED,
GREEN,
BLUE
};

По умолчанию:

  • RED == 0
  • GREEN == 1
  • BLUE == 2

Можно задавать явные значения:

enum Status {
OK = 0,
ERROR = -1,
TIMEOUT = -2
};

Использование

enum Color background = GREEN;
if (background == RED) { ... }

Перечисления повышают читаемость кода и помогают избежать «магических чисел».

4. Псевдонимы типов (typedef)

Ключевое слово typedef создаёт новое имя для существующего типа.

Примеры

typedef unsigned long ulong;
typedef struct Point Point; // теперь можно писать Point p; вместо struct Point p;
typedef enum Color Color;

Полное объявление с typedef:

typedef struct {
int x, y;
} Point;

Теперь Point — полноценный тип, и struct больше не требуется.

Преимущества

  • Упрощает синтаксис.
  • Скрывает детали реализации (например, что тип — это структура).
  • Улучшает переносимость (например, size_t, time_t в стандартной библиотеке).

5. Битовые поля

Битовые поля позволяют упаковывать данные на уровне отдельных битов внутри структуры.

Объявление

struct Flags {
unsigned int is_ready : 1; // 1 бит
unsigned int is_valid : 1; // 1 бит
unsigned int error_code : 4; // 4 бита
unsigned int reserved : 26; // остальные биты в 32-битном слове
};

Использование

struct Flags f = {0};
f.is_ready = 1;
f.error_code = 5;

Особенности

  • Тип поля должен быть целочисленным (int, unsigned int, _Bool и т.д.).
  • Порядок битов зависит от архитектуры (little-endian / big-endian).
  • Нельзя взять адрес битового поля (&f.is_ready — ошибка).
  • Полезны для работы с аппаратными регистрами, сетевыми протоколами, компактным хранением флагов.

6. Продвинутые возможности препроцессора

Макросы с переменным числом аргументов (variadic macros)

Поддерживаются начиная с C99:

#define LOG(fmt, ...) printf("[LOG] " fmt "\n", __VA_ARGS__)

Использование:

LOG("Значение: %d", 42); // → printf("[LOG] Значение: %d\n", 42);

Если макрос может вызываться без дополнительных аргументов, используется GCC-совместимый синтаксис:

#define DEBUG_PRINT(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)

Строкификация (#)

Преобразует макропараметр в строковый литерал:

#define STR(x) #x
char *s = STR(hello); // → "hello"

Конкатенация (##)

Объединяет два токена:

#define VAR(name) var_##name
int VAR(count) = 10; // → int var_count = 10;

Предопределённые макросы

МакросОписание
__STDC__Определяется как 1, если компилятор соответствует ANSI C
__STDC_VERSION__Версия стандарта (например, 201710L для C17)
__LINE__Номер текущей строки
__FILE__Имя текущего файла
__func__Имя текущей функции (C99)
__DATE__Дата компиляции ("Mmm dd yyyy")
__TIME__Время компиляции ("hh:mm:ss")

Условная компиляция: расширенные формы

#if defined(MAX_SIZE) && MAX_SIZE > 100
// код для больших размеров
#elif defined(DEBUG)
// отладочный код
#else
// общий случай
#endif

Защита заголовочных файлов

Стандартный шаблон:

#ifndef MY_HEADER_H
#define MY_HEADER_H

// содержимое заголовка

#endif // MY_HEADER_H

Альтернатива (не во всех компиляторах):

#pragma once

Стандартная библиотека — ввод-вывод, память, строки, математика, время, символы

Стандартная библиотека языка Си предоставляет переносимые функции для работы с файлами, памятью, строками, математическими вычислениями, временем и другими базовыми задачами. Все функции объявлены в заголовочных файлах, поставляемых вместе с компилятором.


1. <stdio.h> — Ввод и вывод данных

Этот заголовок содержит функции для работы с потоками (FILE*) — абстракцией над файлами, консолью, сетевыми соединениями и другими источниками/приёмниками данных.

Основные потоки

  • stdin — стандартный ввод (обычно клавиатура)
  • stdout — стандартный вывод (обычно терминал)
  • stderr — стандартный поток ошибок (не буферизуется)

Форматированный вывод

int printf(const char *format, ...);          // вывод в stdout
int fprintf(FILE *stream, const char *format, ...); // вывод в указанный поток
int sprintf(char *str, const char *format, ...); // запись в строку
int snprintf(char *str, size_t n, const char *format, ...); // безопасная версия с ограничением длины

Спецификаторы формата:

  • %d, %i — целое со знаком
  • %u — целое без знака
  • %x, %X — шестнадцатеричное (нижний/верхний регистр)
  • %o — восьмеричное
  • %c — символ
  • %s — строка (char*)
  • %f, %F — вещественное (десятичное)
  • %e, %E — экспоненциальная форма
  • %g, %G — автоматический выбор между %f и %e
  • %p — адрес (указатель)
  • %% — вывод символа %

Модификаторы ширины и точности:

  • %5d — минимум 5 символов, выравнивание по правому краю
  • %-10s — минимум 10 символов, выравнивание по левому краю
  • %.2f — 2 знака после запятой
  • %.*s — точность задаётся аргументом

Форматированный ввод

int scanf(const char *format, ...);           // чтение из stdin
int fscanf(FILE *stream, const char *format, ...); // чтение из потока
int sscanf(const char *str, const char *format, ...); // чтение из строки

Особенности scanf:

  • Пропускает начальные пробельные символы.
  • Останавливается при несоответствии формата.
  • Для чтения строки: char s[100]; scanf("%99s", s); — защита от переполнения.
  • %[...] — чтение набора символов: scanf("%[a-zA-Z]", s);
  • %n — записывает количество прочитанных символов (не ввод!).

Небуферизованный ввод-вывод

int getchar(void);    // читает один символ из stdin
int putchar(int c); // выводит один символ в stdout
int fgetc(FILE *f); // читает символ из потока
int fputc(int c, FILE *f); // записывает символ в поток

Работа с файлами

FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);

Режимы открытия:

  • "r" — чтение (файл должен существовать)
  • "w" — запись (создаёт или обнуляет файл)
  • "a" — добавление (запись в конец)
  • "r+" — чтение и запись (файл существует)
  • "w+" — чтение и запись (создаёт или обнуляет)
  • "a+" — чтение и добавление

Двоичные режимы: добавляют b (например, "rb", "wb"), особенно важно на Windows.

Буферизация

  • setbuf(stream, buf) — установка буфера
  • setvbuf(stream, buf, mode, size) — гибкая настройка
  • fflush(stream) — принудительная запись буфера на диск

Позиционирование в файле

long ftell(FILE *stream);        // текущая позиция
int fseek(FILE *stream, long offset, int origin); // перемещение
void rewind(FILE *stream); // переход в начало

Значения origin:

  • SEEK_SET — от начала файла
  • SEEK_CUR — от текущей позиции
  • SEEK_END — от конца файла

2. <stdlib.h> — Общие утилиты

Управление памятью

  • void *malloc(size_t size);
  • void *calloc(size_t nmemb, size_t size);
  • void *realloc(void *ptr, size_t new_size);
  • void free(void *ptr);

Преобразование строк в числа

  • int atoi(const char *nptr); — строка → int (без обработки ошибок)
  • long atol(const char *nptr); — → long
  • long long atoll(const char *nptr); — → long long

Более надёжные функции:

  • long strtol(const char *nptr, char **endptr, int base);
  • double strtod(const char *nptr, char **endptr);
  • float strtof(...), long double strtold(...)

Параметр endptr указывает на первый недопустимый символ — позволяет проверить корректность преобразования.

Генерация случайных чисел

int rand(void);          // возвращает число от 0 до RAND_MAX
void srand(unsigned int seed); // инициализация генератора

Пример:

srand(time(NULL));
int dice = rand() % 6 + 1; // число от 1 до 6

Завершение программы

  • void exit(int status); — завершает программу, вызывает обработчики atexit
  • void _Exit(int status); — немедленное завершение (C99)
  • Коды: EXIT_SUCCESS, EXIT_FAILURE

Выполнение команд оболочки

  • int system(const char *command); — запускает команду в системной оболочке

3. <string.h> — Операции со строками и памятью

Уже частично рассмотрено в части 2. Здесь — полный охват.

Строковые функции (работают с \0-завершёнными строками)

  • size_t strlen(const char *s);
  • char *strcpy(char *dest, const char *src);
  • char *strncpy(char *dest, const char *src, size_t n);
  • char *strcat(char *dest, const char *src);
  • char *strncat(char *dest, const char *src, size_t n);
  • int strcmp(const char *s1, const char *s2);
  • int strncmp(const char *s1, const char *s2, size_t n);
  • char *strchr(const char *s, int c);
  • char *strrchr(const char *s, int c); — последнее вхождение
  • char *strstr(const char *haystack, const char *needle);
  • size_t strspn(const char *s, const char *accept); — длина начального сегмента, содержащего только символы из accept
  • size_t strcspn(const char *s, const char *reject); — длина сегмента до первого символа из reject
  • char *strtok(char *str, const char *delim); — разбиение на токены (изменяет исходную строку!)

Функции работы с памятью (байтовые операции)

  • void *memcpy(void *dest, const void *src, size_t n); — копирование без пересечения
  • void *memmove(void *dest, const void *src, size_t n); — копирование с возможным пересечением
  • void *memset(void *s, int c, size_t n); — заполнение байтами
  • int memcmp(const void *s1, const void *s2, size_t n); — побайтовое сравнение

4. <math.h> — Математические функции

Требует линковки с математической библиотекой: gcc program.c -lm

Основные функции

  • double sin(double x);, cos, tan
  • double asin(double x);, acos, atan, atan2
  • double exp(double x); — e^x
  • double log(double x); — натуральный логарифм
  • double log10(double x); — десятичный логарифм
  • double pow(double base, double exponent);
  • double sqrt(double x);
  • double fabs(double x); — модуль
  • double ceil(double x); — округление вверх
  • double floor(double x); — округление вниз
  • double round(double x); — математическое округление (C99)
  • double fmod(double x, double y); — остаток от деления вещественных чисел

Константы (если определено _USE_MATH_DEFINES или включены расширения)

  • M_PI — π
  • M_E — e
  • M_SQRT2 — √2

5. <time.h> — Работа со временем

Типы

  • time_t — целочисленный тип для хранения времени (обычно секунды с 1 января 1970)
  • struct tm — структура с полями года, месяца, дня и т.д.
  • clock_t — для измерения процессорного времени

Функции

  • time_t time(time_t *tloc); — текущее календарное время
  • struct tm *localtime(const time_t *timer); — преобразование в местное время
  • struct tm *gmtime(const time_t *timer); — в UTC
  • time_t mktime(struct tm *tm); — обратное преобразование
  • char *asctime(const struct tm *tm); — строковое представление (формат фиксирован)
  • size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); — гибкое форматирование
  • clock_t clock(void); — процессорное время с начала выполнения

Пример форматирования:

time_t now = time(NULL);
struct tm *t = localtime(&now);
char buffer[100];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", t);
printf("Текущее время: %s\n", buffer);

6. <ctype.h> — Классификация и преобразование символов

Все функции принимают int (для совместимости с EOF), но работают с unsigned char.

Проверки

  • int isalpha(int c); — буква
  • int isdigit(int c); — цифра
  • int isalnum(int c); — буква или цифра
  • int isspace(int c); — пробельный символ (' ', \t, \n и др.)
  • int isupper(int c);, islower(int c);
  • int isxdigit(int c); — шестнадцатеричная цифра
  • int ispunct(int c); — знак препинания
  • int isprint(int c); — печатаемый символ

Преобразования

  • int toupper(int c);
  • int tolower(int c);

Пример:

char c = 'a';
if (islower(c)) c = toupper(c); // → 'A'