- Размером (минимум) 1 байт:
char, unsigned char, signed char - Размером 2 байта:
short, short int, unsigned short, unsigned short int - Размером 4 байта:
int, unsigned, signed, unsigned int, signed int, long, long int, unsigned long - Размером 8 байт:
long long, unsigned long long
float, double, long double
Для каждой переменной типа T существует тип T[] -- "массив элементов типа T", а так же тип T[N] --
"массив элементов типа T длины N".
Тип void не является типом в общем виде. Невозможно описать переменную типа void (хотя можно описать переменную типа void*).
В основном void используется для определения функций без возвращаемого значения.
Перечисления -- первый способ добавить свой собственный тип в язык Си. Это делается так:
enum имя_перечисления { ИМЕНА_МЕТОК, ЧЕРЕЗ_ЗАПЯТУЮ }; // точка с запятой в концеПример такого перечисления:
enum color_t { RED, BLUE, WHITE, BLACK }; После этого в нашем языке появляется новый тип color_t, и мы можем использовать этот тип для переменных.
Константы внутри перечислений можно задавать с указанным числовым обозначением
(в памяти переменные перечислений хранятся так же, как переменные типа int).
Это позволяет нам делать, к примеру, следующее:
enum side_t {
TOP = 1,
BOTTOM = 2,
LEFT = 4,
RIGHT = 8,
FRONT = 16,
BACK = 32
};
enum side_t to_side = TOP | BACK;Почему это работает?
В качестве числовых представлений наших меток мы выбрали степени двоек.
Поэтому при использовании операции побитового или | в одну переменную можно
записывать одновременно наличие нескольких меток.
Структуры -- это второй способ создавать собственные типы данных в языке Си. Это делается так:
struct имя_структуры {
тип_1 пер_1;
тип_2 пер_2;
...
тип_n пер_n;
};Таким образом, внутри структуры мы просто объявляем набор переменных.
struct point_t {
double x;
double y;
};После этого мы можем создавать переменные типа struct point_t в нашей программе.
Переменные-члены будут доступны для переменных структурных типов через точечную нотацию:
struct point_t zero;
zero.x = zero.y = 0.0;
double distance(struct point_t p1, struct point_t p2) {
return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}Структуры можно инициализировать несколькими способами:
struct point_t p1 = {0}; // инициализация нулями всех полей
struct point_t p2 = {1.0, 2.0}; // инициализация по порядку
struct point_t p3 = {.x = 3.0, .y = 4.0}; // именованная инициализация (C99)
struct point_t p4 = {.y = 5.0}; // частичная инициализация, x будет 0Именованная инициализация (designated initializers) позволяет указывать значения полей в любом порядке и инициализировать только нужные поля, остальные будут заполнены нулями.
Чтобы избежать необходимости писать struct каждый раз при использовании структурного типа,
можно использовать typedef:
typedef struct point_t {
double x;
double y;
} point_t;
// Теперь можно использовать просто point_t вместо struct point_t
point_t zero = {0};
point_t p = {.x = 1.0, .y = 2.0};Или можно объявить typedef отдельно:
struct point_t {
double x;
double y;
};
typedef struct point_t point_t;
// Теперь оба варианта работают
struct point_t p1 = {1.0, 2.0};
point_t p2 = {3.0, 4.0};Указатель -- это специальная переменная, хранящая типизированный адрес в памяти.
Если T -- это тип, то T* описывает тип "указатель на область памяти типа T".
C указателями можно произвести два действия:
Разыменование указателя -- это получение значения по адресу, записанному в указатель.
Например, если x -- это переменная типа int*, то *x будет являться значением типа int.
Это значение является "левым": его можно использовать как в выражениях, так и как переменную слева от знака присваивания:
*p = 42;К каждому указателю можно прибавить или вычесть произвольное (целое) число.
Эта операция масштабируется относительно типа указателя. Например, если p --
это указатель типа T*, то запись p+x (где x -- число) будет
обозначать адрес в памяти, равный p + sizeof(T) * x.
Так, к примеру, если p -- это указатель типа int*, то разница между
ячейками p и p+1 будет составлять на большинстве машин 4.
Адрес произвольной переменной типа T можно получить с помощью оператора &. Его можно записать в переменную типа T*:
int x = 42;
int* p2x = &x;Программа на языке си записывается в файлы. В большинстве случаев эти файлы имеют расширение .c.
На верхнем уровне программа состоит из директив препроцессора, объявлений новых типов и глобальных переменных, а так же деклараций функций.
В программировании выражение -- это комбинация слов языка, которая может быть интерпретирована согласно правилам языка Си как значение.
Примером выражения может являться:
- Константа
- Переменная
- Вызов функций
- Присваивание
- Арифметическое или логическое выражение (операндами могут являться другие выражения).
Утверждение -- это минимальная конструкция языка, которую можно воспринимать как действие.
Утверждение-выражение (expression statement) -- это выражение, за которым идёт точка с запятой. Несмотря на то, что каждое выражение можно превратить в утверждение, обычно это делают с утверждениями, обладающими побочными эффектами -- такими, как присваивания и вызовы функций.
7; // вполне корректное, хотя и абсолютно бесполезное УВ
puts("Hello, world!"); // вызов функции
foo = getchar(); // присваиваниеВ языке Си переменные можно объявлять в любом месте программы. Синтаксис объявления переменных таков:
тип имя [= начальное_значение][, имя [= начальное значение]]*(здесь выражения внутри квадратных скобках являются необязательными, а * обозначает, что этот кусок можно повторить много раз
int x; // просто объявление переменной
int y = 42, z; // переменная y объявлена с начальным значением, а z -- без
char z = getchar(),
w = getchar(); // z и w могут иметь разное значениеБлочное утверждение -- это набор утверждений, взятых в фигурные скобки.
{
foo = bar();
int x = 42;
}Условие -- одна из самых основных конструкций, позволяющая выбрать путь выполнения программы. Её синтаксис такой:
if (cond_expr) true_stmt [else false_stmt]При выполнении кода условного утверждения сначала вычисляется cond_expr.
Если его значение не является нулевым, то выполняется true_stmt.
Иначе, если есть, выполняется false_stmt.
if (x > 5) // простое условное утверждение
puts("x is greater than 5");
else
puts("x is equal or less than 5");
if (z < 0) z = -z; // условие без блока else
if (x + y < z) { // условие, в результате которого делается много действий
int temp = x;
x = z - y;
y = z - temp;
}
if (x > 0) // условное выражение может быть частью false_stmt
puts("x is positive");
else if (x < 0)
puts("x is negative");
else
puts("x is null");Циклы while и do while повторяют действие, пока условие верно. Его синтаксис такой:
while (cond_expr) true_stmt
do true_stmt while (cond_expr);При выполнении цикла while вычисляется cond_expr. Если это 0, то выполнение цикла завершается.
В противном случае выполняется true_stmt, и выполнение переходит в начало.
При выполнении цикла do while сначала выполняется true_stmt, а потом вычисляется cond_expr.
Если это 0, то выполнение цикла завершается. Иначе выполнение переходит в начало.
Циклы while и do while так же называют "цикл с предусловием" и "цикл с постусловием".
int x = 42;
while (x > 0) // цикл с пред-условием
printf("%d", x--);
do { // цикл с пост-условием
printf("%d", x);
} while (x > 0);Цикл for -- это цикл с пред-условием с дополнительными действиями.
Его синтаксис такой:
for (init_expr; cond_expr; iter_expr) true_stmtПри выполнении цикла for сначала выполняется утверждение-выражение init_expr;.
Далее, пока cond_expr не 0, выполняется сначала true_stmt, а потом iter_expr;.
for (int i = 0; i < 42; i++) sum += i;В цикле for все три выражения опциональны. Поэтому все следующие примеры корректны.
for (;;) ; // бесконечный цикл, который ничего не делает.
// то же, что и while (1);
for (int x = 0;x < 5; x++) ; // тело цикла не указано. Выполняется только iter_expr
for (; x > 5 ;) x--; // в чистом виде whileИногда возникает необходимость перестать выполнять тело цикла, и либо снова перейти к условию, либо вообще покинуть цикл.
Утверждения continue; и break; соответственно делают именно это.
Следует помнить, что при использовании continue; iter_expr; выполняется.
Switch позволяет выбрать действие в зависимости от значения выражения. Его синтаксис такой:
switch (expr) {
case v1:
[v1_stmt]*
case v2:
[v2_stmt]*
/// ...
default:
[default_stmt]*
}При выполнении утверждения switch сначала вычисляется значение expr.
Далее выбирается метка case, соответствующая значению выражения.
(Если такой нету, выбирается default. Если и метки default нету, то
происходит переход к следующему за switch утверждению.)
После того, как метка выбрана, начинается последовательное выполнение всех утверждений, идущих за этой меткой
(т.е. новая метка не прерывает выполнение switch).
Если такое поведение нежелательно, то последним утверждением перед следующей меткой нужно поставить break;.
switch (x + y % 2) {
case 0:
printf("Число %d чётное.\n", x + y);
break;
default:
printf("Число %d нечётное.\n", x + y);
}
switch (x % 10) { // одно и то же действие по нескольким меткам
case 2:
case 3:
case 5:
case 7:
printf("Остаток от деления %d на 10 простой.\n", x);
}Функция в языках программирования -- это набор действий, параметризованный набором переменных-"аргументов".
Определить функцию очень просто. Синтаксис определения такой:
тип_возвращаемого_значения имя_функции(аргументы) {
[stmt]*
}Синтаксис аргументов:
[тип_аргумента аргумент[,тип_аргумента аргумент]*]После определения функции её можно вызывать по имени, указав значения аргументов в скобках через запятую.
void foo() { // функция без возвращаемого значения и аргументов, выводящая FOOOOOO на экран
printf("FOOOOOOOOOO\n");
}
void bar(int count) { // функция bar(count) выводит строчку BAAAA....AAR (количество А определяется аргументом count)
putchar('B');
for (int i = 0; i < count; i++) putchar('A');
puts("R");
}
void baz(int x, int y) { // функция, выводящая результаты различных арифметических операций
printf("%d + %d == %d\n", x, y, x + y);
printf("%d - %d == %d\n", x, y, x - y);
printf("%d * %d == %d\n", x, y, x * y);
printf("%d / %d == %d\n", x, y, x / y);
printf("%d %% %d == %d\n", x, y, x % y);
}В математике функция -- это закон, который набору аргументов сопоставляет результат.
Так как программирование изначально часто использовалось для различных математических подсчётов,
то неудивительно, что эту часть в программировании тоже сделали возможной.
Для возвращения значения используется утверждение return (return statement):
int sum(int x, int y) { // функция sum(x, y) возвращает, как ни странно, x + y
return x + y;
}