Типы данных, переменные, printf и scanf
Переменные
Переменная — именованная область памяти определённого типа. Тип определяет размер области и то, как интерпретировать её биты.
int counter = 0; /* объявление с инициализацией */
double sum; /* без инициализации: внутри функции значение — мусор! */
char letter = 'A'; /* символ — это маленькое целое число (код 65) */
Неинициализированные переменные
Локальная переменная без начального значения содержит то, что лежало в памяти до неё. Глобальные и static переменные, наоборот, всегда обнуляются.
Целые типы
| Тип | Типичный размер (x86-64 Linux) | Диапазон |
|---|---|---|
char |
1 байт | −128…127 (или 0…255) |
short |
2 байта | −32 768…32 767 |
int |
4 байта | ≈ −2,1·10⁹…2,1·10⁹ |
long |
8 байт (на 32-битных — 4) | ≈ ±9,2·10¹⁸ |
long long |
8 байт | ≈ ±9,2·10¹⁸ |
Модификатор unsigned делает тип беззнаковым: unsigned int хранит 0…4 294 967 295.
Стандарт C гарантирует только минимальные размеры, поэтому точный размер узнают оператором sizeof:
printf("%zu %zu %zu\n", sizeof(int), sizeof(long), sizeof(double)); /* 4 8 8 */
Если нужен точный размер — типы из <stdint.h>: int32_t, uint64_t, uint8_t.
Вещественные типы
| Тип | Размер | Точность |
|---|---|---|
float |
4 байта | ~7 значащих цифр |
double |
8 байт | ~15–16 цифр |
double x = 0.1 + 0.2;
printf("%.17f\n", x); /* 0.30000000000000004 — двоичная дробь неточна */
Переполнение и деление
unsigned int u = 0;
u = u - 1; /* 4294967295: беззнаковые «заворачиваются» по модулю 2^32 */
int a = 7 / 2; /* 3: целочисленное деление */
int r = 7 % 2; /* 1: остаток */
double d = 7.0 / 2; /* 3.5 */
double e = (double)7 / 2; /* 3.5: явное приведение типа */
Знаковое переполнение
INT_MAX + 1 — неопределённое поведение (undefined behavior): компилятор вправе считать, что этого не бывает, и оптимизировать код неожиданным образом.
Системные типы
В системном программировании вы постоянно будете встречать типы-«псевдонимы»:
| Тип | Где используется | На Linux обычно |
|---|---|---|
size_t |
размеры (sizeof, malloc, strlen) |
unsigned long |
ssize_t |
результат read/write (может быть −1) |
long |
pid_t |
идентификатор процесса (fork, getpid) |
int |
off_t |
смещение в файле | long |
uid_t |
идентификатор пользователя | unsigned int |
Их фактический размер зависит от системы, поэтому при печати безопаснее явно привести: printf("%d", (int)pid) или printf("%ld", (long)pid).
printf: форматированный вывод
printf("%d: %d\n", ppid, pid); /* строка из экзаменационной задачи */
printf("%5d|%-5d|\n", 42, 42); /* " 42|42 |" — ширина и выравнивание */
printf("%.2f\n", 3.14159); /* "3.14" */
printf("%x %o\n", 255, 8); /* "ff 10" — шестнадцатеричный и восьмеричный */
printf("%s %c\n", "текст", 'A');
printf("%p\n", (void *)&counter); /* адрес в памяти */
printf("100%%\n"); /* знак процента */
| Спецификатор | Тип |
|---|---|
%d, %i |
int |
%u |
unsigned int |
%ld, %lu |
long, unsigned long |
%zu |
size_t |
%f, %e, %g |
double (и float) |
%c |
символ |
%s |
строка (char *) |
%p |
указатель |
%x |
целое в hex |
scanf: форматированный ввод
int n;
double x;
if (scanf("%d %lf", &n, &x) != 2) { /* вернёт число успешно прочитанных значений */
fprintf(stderr, "Ошибка ввода\n");
return 1;
}
Обратите внимание на &: scanf должна записать значение в переменную, поэтому ей передаётся адрес переменной. Об адресах — в уроке про указатели. И ещё отличие: для double в scanf нужен %lf, а в printf хватает %f.
Стандартные потоки
У каждого процесса при запуске открыты три потока:
| Поток | Дескриптор | Назначение |
|---|---|---|
stdin |
0 | ввод |
stdout |
1 | вывод (буферизуется) |
stderr |
2 | ошибки (не буферизуется) |
printf(...) — то же, что fprintf(stdout, ...).
Итог
- Тип задаёт размер и смысл битов; размер узнаём через
sizeof;double— 8 байт. - Беззнаковые типы заворачиваются по модулю, знаковое переполнение — неопределённое поведение.
- Деление целых — целочисленное.
- Системные типы
size_t,pid_t,ssize_tпечатаем с правильным форматом или приводим явно.
Практика решено 0 из 6
Чему равно sizeof(double) на x86 и x86-64?
Сколько байт занимает массив double a[70]? Введите число.
Что напечатает фрагмент?
unsigned char c = 255;
c = c + 1;
printf("%d\n", c);
Что напечатает printf("%d\n", 7 / 2);?
Напечатайте идентификатор процесса и его родителя одной строкой в формате pid=123 ppid=45.
Значения уже лежат в переменных pid_t pid и pid_t ppid. Приведите их к int и используйте формат %d.
pid_t pid = getpid();
pid_t ppid = getppid();
printf("pid=%d ppid=%d\n", (int)pid, (int)ppid);
Сопоставьте спецификаторы printf и типы: отметьте «+», если спецификатор подходит для типа.
%d | %u | %ld | %f | %zu | %p | |
|---|---|---|---|---|---|---|
| int | ||||||
| unsigned int | ||||||
| long | ||||||
| double | ||||||
| size_t | ||||||
| void * |
Войдите, чтобы отмечать пройденные уроки.