Типизация переменных — это набор правил, по которым каждой переменной, значению и выражению сопоставляется тип данных, определяющий, какие значения там допустимы и какие операции над ними разрешены. И C, и C# статически типизированы: тип переменной известен на этапе компиляции. Но системы типов у них разные, поэтому ниже я развожу их отдельными таблицами и примерами. Разберу, что такое статическая и сильная типизация, встроенные типы обоих языков, их размеры и диапазоны, деление на value и reference в C# и приведение типов.
Содержание
Статическая типизация — это не то же, что сильная
Два свойства часто путают, разведу их сразу.
- Статическая типизация — тип переменной фиксируется при компиляции, а не в момент выполнения. Компилятор знает тип каждой переменной заранее и ловит несоответствия до запуска. Это про C и C#.
- Сильная (строгая) типизация — язык почти не выполняет неявных преобразований «за спиной» и запрещает бессмысленные операции над несовместимыми типами.
C статически типизирован, но по строгости слабее C#: он охотно делает неявные числовые преобразования и допускает приведение через указатели. C# строже: многие сужающие преобразования требуют явного приведения, а логический и целый типы не смешиваются. Так что «строгость» — это спектр, а не переключатель.
Встроенные типы в C: размеры платформозависимы
Стандарт C задает не точные размеры, а минимальные гарантии: char — не меньше 8 бит, short и int — не меньше 16 бит, long — не меньше 32, long long — не меньше 64. Конкретный размер зависит от платформы и модели данных компилятора (LP64 на Linux/macOS, LLP64 на Windows). Поэтому таблицу ниже читайте как «типичные значения», а точные размеры на своей системе проверяйте через sizeof.
| Тип C | Типичный размер | Диапазон (знаковый вариант) | printf |
|---|---|---|---|
char |
1 байт | зависит от знаковости реализации | %c |
signed char |
1 байт | [-128, 127] (гарантия не уже [-127, 127]) | %d |
unsigned char |
1 байт | [0, 255] | %hhu |
short |
2 байта | [-32768, 32767] | %hd |
unsigned short |
2 байта | [0, 65535] | %hu |
int |
2 или 4 байта | [-2147483648, 2147483647] при 4 байтах | %d |
unsigned int |
2 или 4 байта | [0, 4294967295] при 4 байтах | %u |
long |
4 или 8 байт | зависит от модели данных | %ld |
long long |
8 байт | [-9223372036854775808, 9223372036854775807] | %lld |
float |
4 байта | одинарная точность IEEE 754 | %f |
double |
8 байт | двойная точность IEEE 754 | %f, %g |
long double |
8-16 байт | расширенная точность (зависит от платформы) | %Lf |
Минимальный полный пример, который печатает реальные размеры и диапазоны, — его можно скопировать и запустить.
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
int main(void) {
printf("CHAR_BIT = %d\n", CHAR_BIT);
printf("sizeof: char=%zu short=%zu int=%zu long=%zu long long=%zu\n",
sizeof(char), sizeof(short), sizeof(int),
sizeof(long), sizeof(long long));
printf("sizeof: float=%zu double=%zu long double=%zu\n",
sizeof(float), sizeof(double), sizeof(long double));
printf("INT_MAX=%d LONG_MAX=%ld\n", INT_MAX, LONG_MAX);
bool flag = 42; /* любое ненулевое значение станет 1 */
printf("bool from 42 = %d\n", flag);
return 0;
}
Вывод на macOS (Apple Silicon, clang, модель LP64):
CHAR_BIT = 8
sizeof: char=1 short=2 int=4 long=8 long long=8
sizeof: float=4 double=8 long double=8
INT_MAX=2147483647 LONG_MAX=9223372036854775807
bool from 42 = 1
Именно поэтому размеры нельзя запоминать как абсолют. На Linux x86-64 с gcc та же строка про long double покажет 16 (80-битный расширенный формат хранится в 16 байтах), а на Windows с MSVC long займет 4 байта, а не 8, потому что там модель LLP64.
Логический тип в C появился в C99 как _Bool. Заголовок <stdbool.h> вводит привычные bool, true, false. Особенность видна в примере выше: присваивание bool flag = 42 сохраняет не 42, а 1 — любое ненулевое значение нормализуется к единице.
Встроенные типы в C#: размеры фиксированы спецификацией
В C# ключевые слова вроде int или long — это псевдонимы типов из пространства System (например, int = System.Int32). Их размеры одинаковы на всех платформах, в отличие от C. Ниже целочисленные и вещественные типы.
| Тип C# | Псевдоним System | Размер | Диапазон / точность |
|---|---|---|---|
sbyte |
SByte | 8 бит | [-128, 127] |
byte |
Byte | 8 бит | [0, 255] |
short |
Int16 | 16 бит | [-32768, 32767] |
ushort |
UInt16 | 16 бит | [0, 65535] |
int |
Int32 | 32 бита | [-2147483648, 2147483647] |
uint |
UInt32 | 32 бита | [0, 4294967295] |
long |
Int64 | 64 бита | [-9.2e18, 9.2e18] |
ulong |
UInt64 | 64 бита | [0, 1.8e19] |
float |
Single | 32 бита | ~7 значащих цифр, до ~3.4e38 |
double |
Double | 64 бита | ~15-17 значащих цифр, до ~1.7e308 |
decimal |
Decimal | 128 бит | 28-29 значащих цифр, до ~7.9e28 |
char |
Char | 16 бит | одна кодовая единица UTF-16, [0, 65535] |
bool |
Boolean | реализационно | true / false |
Несколько практических уточнений к таблице.
decimal хранит десятичную мантиссу и масштаб, поэтому не накапливает двоичную ошибку округления в пределах своей точности (28-29 значащих цифр) — его берут для денег и финансовых расчетов. «Точный» здесь значит «без двоичного дрейфа в пределах масштаба», а не «с бесконечной точностью»: результат все равно округляется до доступных разрядов.
char в C# — это одна 16-битная кодовая единица UTF-16, а не полный символ Unicode. Символы вне базовой плоскости (например, многие эмодзи) кодируются суррогатной парой из двух char, так что один видимый символ может занимать два элемента. ASCII (0-127) — подмножество Unicode, поэтому латиница и цифры укладываются в один char.
bool в C# не смешивается с целыми: 1 не превращается в true, а 0 — в false, в отличие от C. Это как раз проявление большей строгости.
Актуально на 2026: в современных .NET есть и дополнительные числовые типы — Half (16-битное вещественное, с .NET 5), Int128/UInt128 (с .NET 7) и платформозависимые nint/nuint. В таблицу выше я их не включил как неклассические для базового курса.
Value и reference: где хранится значение
В C# все типы делятся на две категории (это ключевое отличие от C, где такого деления в системе типов нет).
- Тип значения (value type) хранит данные непосредственно. К ним относятся все числовые типы из таблицы,
bool,char, а такжеstructиenum. - Ссылочный тип (reference type) хранит ссылку на объект. Это
class,string, массивы, делегаты.
Упрощенная модель «типы значений в стеке, ссылочные в куче» удобна для старта, но это именно упрощение. Точнее так: сам объект ссылочного типа всегда живет в управляемой куче, а переменная-ссылка хранит адрес. Значение value-типа лежит там, где объявлено: локальная переменная — обычно в стеке, но поле value-типа внутри объекта класса лежит в куче вместе с этим объектом. Так что «стек против кучи» зависит от того, где объявлена переменная, а не только от категории типа.
Приведение типов
В C преобразования во многом неявные: int спокойно повышается до double в выражении. Опасны сужающие преобразования — вот тройка «неверно — результат — как правильно».
int over = 300;
char c = over; /* сужение int -> char: данные не влезают */
printf("%d\n", c);
Результат на платформе, где char знаковый (macOS/clang):
44
Число 300 не помещается в диапазон char, значение усекается по модулю — получается не 300. Если значение действительно должно быть символом-байтом, используйте подходящий тип и контролируйте диапазон явно:
if (over >= 0 && over <= 255) {
unsigned char c = (unsigned char) over; /* явно и осознанно */
}
В C# сужающие преобразования, где возможна потеря данных, требуют явного приведения — компилятор не даст присвоить double в int молча. Расширяющие (например, int в long) выполняются неявно. Переполнение при явном приведении по умолчанию не проверяется; чтобы получить исключение вместо тихого усечения, оборачивают код в блок checked.
int i = 1_000_000;
long l = i; // неявно, безопасно
double d = 3.99;
int back = (int) d; // явное приведение, дробная часть отбрасывается: 3
int a = int.MaxValue;
checked {
// int sum = a + 1; // здесь бросит OverflowException, а не даст мусор
}
Литералы задают тип значения: суффикс L — для long, U — для беззнаковых (UL для ulong), f/F — для float, d/D — для double, m/M — для decimal. Без суффикса дробный литерал считается double, поэтому float x = 3.14; не скомпилируется — нужно 3.14f.
Выводы
- И C, и C# статически типизированы, но C# строже: меньше неявных преобразований,
boolи целые не смешиваются. - В C размеры типов платформозависимы (стандарт задает лишь минимумы) — проверяйте через
sizeof; в C# размеры фиксированы спецификацией. - В C# типы делятся на value и reference; правило «стек/куча» — упрощение, важнее, где объявлена переменная.
charв C# — это кодовая единица UTF-16, а не всегда целый символ;decimalточен в пределах своих 28-29 разрядов.- Сужающие преобразования опасны: в C они часто неявные и молча усекают, в C# требуют явного приведения, а
checkedловит переполнение.
Где применяется / связь с практикой
Понимание системы типов — фундамент любого кода на C и C#: от выбора int или long под счетчик до decimal под деньги вместо double. Ошибка на этом уровне тихо портит данные (переполнение, потеря точности, неверное сужение) и находится уже в проде. Поэтому типы разбирают в самом начале обучения.
Освойте тему на практике
Если хотите пройти это на практике — с задачами и ревью, — посмотрите курс C# Basic. Перед стартом полезно сходить на открытые уроки Otus: там разбирают темы вживую и можно оценить формат.
FAQ
Почему float x = 3.14; в C# не компилируется?
Дробный литерал без суффикса считается double, а его нельзя неявно сузить до float. Нужно написать 3.14f.
Чем decimal лучше double для денег?
decimal хранит десятичную мантиссу и не накапливает двоичную ошибку округления в пределах 28-29 значащих цифр, тогда как double не может точно представить даже 0.1 в двоичном виде.
Почему один эмодзи в C# может занимать два char?
char — это 16-битная кодовая единица UTF-16. Символы вне базовой плоскости кодируются суррогатной парой из двух кодовых единиц, поэтому в строке они занимают два char.



