Программа, которая читает данные, считает по формулам и выводит результат в нужном виде
<math.h>printf с нужным числом знаков и шириной поляscanf и понимать, что он возвращаетgcc -std=c11 -Wall -Wextra -pedantic файл.c -o файл (в Linux с <math.h> — ещё -lm в конце).Программа раздела — линейная: инструкции выполняются по одной сверху вниз, каждая ровно один раз. Это базовая структура «следование» из раздела 2. Пример — алгоритм «сколько автобусов» из раздела 2:
/* Сколько автобусов заказать: N учеников, c мест в автобусе */
#include <stdio.h> /* 1. подключение библиотек */
int main(void) /* 2. главная функция */
{
int students = 0, seats = 0; /* 3. объявления */
printf("Students and seats: "); /* 4. ввод */
scanf("%d %d", &students, &seats);
int buses = (students + seats - 1) / seats; /* 5. вычисления */
int free_seats = buses * seats - students;
printf("Buses: %d, free seats: %d\n", buses, free_seats); /* 6. вывод */
return 0; /* 7. код завершения */
}
Students and seats: 50 45 Students and seats: 90 45
Buses: 2, free seats: 40 Buses: 2, free seats: 0
Почему (students + seats - 1) / seats округляет вверх. Целое деление отбрасывает остаток. Прибавка seats - 1 — на единицу меньше, чем нужно для «лишнего» автобуса: если students делится на seats нацело, прибавка не дотягивает до следующего целого (90 + 44 = 134, 134 / 45 = 2); если остаток есть хотя бы 1 — дотягивает (91 + 44 = 135, 135 / 45 = 3). Формула верна для целых положительных чисел.
Комментарии пишутся между /* и */ или от // до конца строки (с C99). Компилятор их не читает: комментарий объясняет зачем, а не пересказывает код.
x = выражение — вычислить правую часть и записать в x. В C присваивание — операция, у неё есть значение (присвоенное), поэтому a = b = 0 работает: сначала b = 0, затем a = 0.
| Запись | То же самое |
|---|---|
sum += x; | sum = sum + x; |
n -= 2; | n = n - 2; |
price *= 1.2; | price = price * 1.2; |
rest /= 10; | rest = rest / 10; |
rest %= 10; | rest = rest % 10; |
count++; или ++count; | count = count + 1; |
count--; или --count; | count = count - 1; |
У ++ две формы. Отдельной инструкцией они не отличаются. Внутри выражения префиксная ++i даёт новое значение, постфиксная i++ — старое:
int i = 5;
int a = ++i; /* i = 6, a = 6 */
int b = i++; /* b = 6, i = 7 */
i = i++;, a = i++ + i++;, printf("%d %d", i, i++); — результат не определён стандартом и разный у разных компиляторов. gcc с -Wall предупреждает: operation on 'i' may be undefined [-Wsequence-point]. Правило курса: ++ и -- — только отдельной инструкцией или в заголовке цикла.Операции + - * / работают как в математике, с одним отличием: если оба операнда целые, деление целое — дробная часть отбрасывается (раздел 3, пункт 06). Операция % — остаток от деления, только для целых.
| Выражение | Значение | Пояснение |
|---|---|---|
17 / 5 | 3 | целое деление |
17 % 5 | 2 | 17 = 5 · 3 + 2 |
-17 / 5 | −3 | отбрасывание дробной части — к нулю, не вниз |
-17 % 5 | −2 | знак остатка — как у делимого: −17 = 5 · (−3) + (−2) |
17 / 5.0 | 3.4 | один операнд double — деление вещественное |
1 / 2 * 4.0 | 0.0 | слева направо: 1 / 2 = 0, затем 0 · 4.0 |
4.0 * 1 / 2 | 2.0 | сначала 4.0 · 1 = 4.0, затем 4.0 / 2 |
7 / 0 | — | деление целого на 0 — неопределённое поведение, обычно аварийное завершение |
Пара / и % разбирает число на части. Секунды → часы, минуты, секунды:
/* Секунды от начала суток → часы, минуты, секунды */
#include <stdio.h>
int main(void)
{
int total = 0;
printf("Seconds: ");
scanf("%d", &total);
int hours = total / 3600;
int minutes = total % 3600 / 60;
int seconds = total % 60;
printf("%02d:%02d:%02d\n", hours, minutes, seconds);
return 0;
}
Seconds: 3725 -> 01:02:05
Seconds: 86399 -> 23:59:59
Seconds: 0 -> 00:00:00
total % 3600 / 60: % и / одного приоритета и выполняются слева направо — сначала остаток после целых часов (3725 % 3600 = 125), потом минуты в нём (125 / 60 = 2). Последняя цифра числа — n % 10, число без неё — n / 10; это понадобится для цифр числа в разделе 6.
Операции выполняются по приоритету, при равном — по ассоциативности. Таблица — для операций, которые встретятся до раздела 10; выше в таблице — раньше выполняется.
| Приоритет | Операции | Порядок при равном приоритете |
|---|---|---|
| 1 | () вызов функции, a++ a-- | слева направо |
| 2 | ++a --a -a ! (тип) sizeof | справа налево |
| 3 | * / % | слева направо |
| 4 | + - | слева направо |
| 5 | < <= > >= | слева направо |
| 6 | == != | слева направо |
| 7 | && | слева направо |
| 8 | || | слева направо |
| 9 | ?: | справа налево |
| 10 | = += -= и другие присваивания | справа налево |
Примеры: 2 + 3 * 4 = 14; (2 + 3) * 4 = 20; 10 - 4 - 3 = 3 (слева направо: (10 - 4) - 3); -2 * 3 = −6; (double)7 / 2 = 3.5 — приведение выполняется раньше деления. Математическая формула (a + b) / (2c) в C — (a + b) / (2 * c): без скобок (a + b) / 2 * c — это (a + b) / 2 · c.
&& и ||; присваивание — последним. Во всех остальных случаях ставьте скобки — их читают люди, и лишняя пара скобок дешевле ошибки.Функции из <math.h> принимают и возвращают double:
| Функция | Что считает | Пример |
|---|---|---|
sqrt(x) | квадратный корень, x ≥ 0 | sqrt(2.0) = 1.41421… |
pow(x, y) | xy | pow(1.1, 3) = 1.331 |
fabs(x) | модуль вещественного; для целого — abs из <stdlib.h> | fabs(-2.5) = 2.5 |
floor(x), ceil(x) | округление вниз и вверх | floor(-2.5) = −3, ceil(2.1) = 3 |
round(x) | к ближайшему целому, половина — от нуля | round(2.5) = 3, round(-2.5) = −3 |
sin(x), cos(x) | угол в радианах | sin(PI / 6) ≈ 0.5 |
exp(x), log(x) | ex и натуральный логарифм | log(exp(2.0)) = 2 |
Расстояние между точками (x1; y1) и (x2; y2):
/* Расстояние между двумя точками */
#include <stdio.h>
#include <math.h>
int main(void)
{
double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
printf("x1 y1 x2 y2: ");
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
double dx = x2 - x1;
double dy = y2 - y1;
double distance = sqrt(dx * dx + dy * dy);
printf("Distance: %.3f\n", distance);
return 0;
}
x1 y1 x2 y2: 0 0 3 4 -> Distance: 5.000
x1 y1 x2 y2: 0 0 1 1 -> Distance: 1.414
Квадрат — умножением dx * dx, а не pow(dx, 2): короче и точнее. pow — для дробных и больших степеней.
M_PI не входит в стандарт C и при сборке с -std=c11 не объявлена: error: 'M_PI' undeclared. Объявите свою: const double PI = 3.14159265358979;. И помните про целое деление внутри формул: 9 / 5 * c + 32 — это 1 * c + 32; нужно 9.0 / 5 * c + 32.printfprintf(формат, значения…) выводит строку формата, заменяя каждую спецификацию — от % до буквы — очередным значением:
| Спецификация | Тип значения | Пример → вывод |
|---|---|---|
%d | int, char | printf("%d", 42) → 42 |
%u | unsigned int | |
%lld | long long | |
%f | double (и float) | printf("%f", 2.5) → 2.500000 |
%.2f | double, 2 знака после точки | printf("%.2f", 2.675) → 2.67 (см. ниже) |
%e | double в экспоненциальной записи | printf("%e", 12345.0) → 1.234500e+04 |
%g | double, короткая запись без лишних нулей | printf("%g", 2.5) → 2.5 |
%c | символ | printf("%c", 'A') → A |
%s | строка | printf("%s", "abc") → abc |
%% | знак процента | printf("10%%") → 10% |
Ширина поля — число после %: значение дополняется пробелами слева до этой ширины. Минус — выравнивание влево, ноль — дополнение нулями:
printf("[%5d]\n", 42); /* [ 42] */
printf("[%-5d]\n", 42); /* [42 ] */
printf("[%05d]\n", 42); /* [00042] */
printf("[%8.3f]\n", 3.14159); /* [ 3.142] — 8 символов всего, 3 после точки */
printf("[%-8s]|\n", "abc"); /* [abc ]| */
Так строятся таблицы с ровными столбцами. Специальные символы в строке: \n — перевод строки, \t — табуляция, \" — кавычка, \\ — обратная косая черта.
%.2f округляет хранимое значение, а 2.675 в double хранится как 2.67499999999999982…, поэтому printf("%.2f", 2.675) выводит 2.67, а не 2.68. Для денег, где важно «по правилам», округляют явно: round(x * 100) / 100. А несовпадение спецификации и типа (%d для double, %f для int) — неопределённое поведение; gcc с -Wall предупреждает о нём (-Wformat), и предупреждение нужно исправлять.scanfscanf(формат, адреса…) читает из ввода значения по спецификациям и записывает их в переменные. Ему нужны не значения переменных, а их адреса — знак & перед именем (что такое адрес, подробно — раздел 10):
int n;
double price;
char grade;
scanf("%d", &n); /* целое */
scanf("%lf", &price); /* double — %lf, не %f */
scanf(" %c", &grade); /* пробел перед %c пропускает пробелы и переводы строки */
| Тип | printf | scanf |
|---|---|---|
int | %d | %d |
long long | %lld | %lld |
double | %f | %lf |
char | %c | %c (с пробелом) |
%d и %lf сами пропускают пробелы, табуляции и переводы строки перед числом, поэтому числа можно вводить в одной строке через пробел или каждое на своей. %c читает любой следующий символ, в том числе перевод строки после предыдущего числа, — поэтому перед ним пробел.
scanf возвращает число успешно прочитанных значений. Если вместо числа введено abc, чтение не удастся: scanf("%d", &n) вернёт 0, а n останется без значения. Программа, которая не смотрит на результат scanf, считает дальше по мусору:
/* Что возвращает scanf */
#include <stdio.h>
int main(void)
{
int a = 0, b = 0;
printf("a b: ");
int read = scanf("%d %d", &a, &b);
printf("read %d, a = %d, b = %d\n", read, a, b);
return 0;
}
a b: 12 34 -> read 2, a = 12, b = 34
a b: 12 x -> read 1, a = 12, b = 0
a b: x 34 -> read 0, a = 0, b = 0
Проверка результата — if (scanf(…) != 2) — делается ветвлением, оно в разделе 5. До тех пор программы курса считают, что ввод верный, а начальные значения переменных задаются, чтобы вместо мусора был хотя бы 0.
& — scanf("%d", n): gcc предупреждает format '%d' expects argument of type 'int *', but argument 2 has type 'int', а программа аварийно завершается при вводе; %f для double — предупреждение expects argument of type 'float *', but argument 2 has type 'double *' и неверное значение; текст внутри формата scanf("Enter %d", &n) — scanf требует, чтобы пользователь ввёл и слово Enter. Подсказка для пользователя выводится отдельным printf.Задача: товар стоит P ₽ без НДС, куплено q штук со скидкой d %. Посчитать сумму без скидки, скидку, НДС 20 % на сумму со скидкой и итог. Все суммы — в рублях с копейками.
double, q — int./* Покупка: сумма, скидка, НДС, итог */
#include <stdio.h>
#include <math.h>
const double VAT_RATE = 0.20;
int main(void)
{
double price = 0, discount_pct = 0;
int quantity = 0;
printf("Price, quantity, discount %%: ");
scanf("%lf %d %lf", &price, &quantity, &discount_pct);
double sum = round(price * quantity * 100) / 100;
double discount = round(sum * discount_pct) / 100; /* sum * d / 100, в копейках */
double vat = round((sum - discount) * VAT_RATE * 100) / 100;
double total = sum - discount + vat;
printf("Sum: %10.2f\n", sum);
printf("Discount: %10.2f\n", discount);
printf("VAT: %10.2f\n", vat);
printf("Total: %10.2f\n", total);
return 0;
}
Price, quantity, discount %: 1500 3 10 Price, quantity, discount %: 100 1 0
Sum: 4500.00 Sum: 100.00
Discount: 450.00 Discount: 0.00
VAT: 810.00 VAT: 20.00
Total: 4860.00 Total: 120.00
discount_pct, а не d; VAT_RATE — константа; в формате %%, потому что одиночный % начал бы спецификацию. Ширина %10.2f выравнивает суммы по правому краю — так читают денежные таблицы.
return 0% — остаток со знаком делимого; / и % разбирают число на части++ и -- — отдельной инструкцией; двойное изменение переменной в одном выражении — UB<math.h> работают с double; π объявляется константой; в Linux — -lmprintf: спецификация совпадает с типом; %.2f — знаки после точки, %8.2f — ширина поляscanf принимает адреса (&), для double — %lf, и возвращает число прочитанных значенийscanf; раздела 6 — составные присваивания и ++ в циклах, / и % для цифр числа; раздела 10 — что такое & и почему он нужен scanf.