Типы данных и спецификаторы ввода-вывода

Типы данных — это «тары» разного размера

В C у каждой переменной есть тип. Тип определяет, сколько памяти занимает значение и как оно хранится. В рамках курса мы работаем в основном со следующими типами:

  • char — самый маленький тип данных (1 байт), обычно хранит один символ;
  • int — целое число (на нашем компиляторе 4 байта);
  • long long — большое целое (8 байт);
  • float / double — дробные числа, double точнее.

Аналогия. Типы — это тары разного размера: рюмка, стакан, ведро. Нельзя налить ведро воды в рюмку — оно перельётся (переполнение). Так и значение не помещается в слишком маленький тип.

ТипТипичный размерДиапазон (примерно)
char1 байт−128 … 127
int4 байта−2.1·10⁹ … 2.1·10⁹
long long8 байт−9.2·10¹⁸ … 9.2·10¹⁸
unsigned int4 байта0 … 4.2·10⁹
float4 байта~7 знаков точности
double8 байт~15 знаков точности

unsigned означает «только неотрицательные»: положительный диапазон примерно вдвое больше.

Размер типа можно узнать точно:

#include <stdio.h>

int main(void)
{
    printf("int:  %zu байт\n", sizeof(int));
    printf("long long: %zu байт\n", sizeof(long long));
    printf("char: %zu байт\n", sizeof(char));
    return 0;
}

Переменные: объявление и присваивание

int count;              /* объявление: выделили «ячейку» */
count = 5;              /* присваивание */
int width = 10;         /* объявление с начальным значением */
double price = 199.90;
char letter = 'A';      /* символ — в ОДИНАРНЫХ кавычках */

Арифметика работает привычно: +, -, *, /, а % берёт остаток от деления целых.

int a = 17, b = 5;
printf("%d %d\n", a / b, a % b);   /* 3 2 — целая часть и остаток */

Важно. Если оба числа целые, то / выполняет целое деление. Чтобы получить дробный результат, нужно, чтобы хотя бы одно значение было дробным: 17 / 5.0.

Спецификаторы: «переводчик» между числом и текстом

Компьютер хранит числа в двоичном виде, а мы читаем и печатаем текст. Спецификатор формата (%…) сообщает функции, как перевести одно в другое.

Аналогия. Спецификатор — этикетка на банке: «кг», «литры», «штуки». Написали на муке «литры» — покупатель запутается. Так и printf с неправильным % напечатает мусор.

Вывод: printf

ТипСпецификаторПример
int%dprintf("%d", 42);
unsigned%uprintf("%u", 42u);
long long%lldprintf("%lld", 42LL);
float / double%fprintf("%f", 3.14);
char%cprintf("%c", 'A');
шестнадцатеричное%xprintf("%x", 255);
знак процента%%printf("100%%");

В printf и float, и double печатаются через %f — float автоматически повышается до double.

Ввод: scanf

ТипСпецификаторПример
int%dscanf("%d", &x);
unsigned%uscanf("%u", &u);
long long%lldscanf("%lld", &ll);
float%fscanf("%f", &f);
double%lfscanf("%lf", &d);
char%cscanf(" %c", &c);

Здесь float и double различаются: для double обязательно %lf (буква l — «long»). Это одна из самых частых ошибок новичков.

Главное отличие ввода: нужен адрес

scanf должна изменить переменную, поэтому ей передают адрес — через &:

int x;
scanf("%d", &x);   /* &x — адрес переменной x */

Аналогия. Значение — жилец, адрес — номер квартиры. Чтобы курьер положил посылку именно к вам, нужен номер квартиры, а не фотография жильца. &x — это «номер квартиры» переменной.

За один вызов можно прочитать несколько значений — они разделяются пробелами или переводами строк:

int a, b;
double c;
scanf("%d %d %lf", &a, &b, &c);   /* ввод: 3 4 2.5 */
printf("%d %d %.2f\n", a, b, c);

Ввод символа и «застрявший» Enter

Спецификатор %c читает любой символ, включая пробел и перевод строки. Если перед этим вводилось число, «застрявший» Enter может попасть в %c. Лечится пробелом перед %c:

int n;
char c;
scanf("%d", &n);
scanf(" %c", &c);   /* пробел перед %c пропускает пробельные символы */
printf("%d %c\n", n, c);

Частые ошибки

ОшибкаЧто не так
scanf("%d", x);забыли & — чтение по мусорному адресу
scanf("%f", &d); для doubleнужен %lf
%d для double в printfполучите «мусор» вместо числа
17 / 5 вместо 17 / 5.0целое деление вместо дробного
'A' и "A" перепутанысимвол — в одинарных кавычках, строка — в двойных
%c после числа без пробелав символ попадёт перевод строки

Мини-шпаргалка

int i;
long long ll;
float f;
double d;
char c;

scanf("%d", &i);
scanf("%lld", &ll);
scanf("%f", &f);
scanf("%lf", &d);      /* для double — только %lf */
scanf(" %c", &c);      /* пробел перед %c пропускает Enter */

printf("i=%d ll=%lld\n", i, ll);
printf("f=%.2f d=%.2f\n", f, d);
printf("c=%c\n", c);

Что запомнить: спецификатор обязан соответствовать типу, а scanf нужен адрес переменной.

Подтверждение

—