- Размером (минимум) 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
};
side_t to_color = TOP | BACK;
Почему это работает?
В качестве числовых представлений наших меток мы выбрали степени двоек.
Поэтому при использовании операции побитового или |
в одну переменную можно
записывать одновременно наличие нескольких меток.
Структуры -- это второй способ создавать собственные типы данных в языке Си. Это делается так:
struct имя_структуры {
тип_1 пер_1;
тип_2 пер_2;
...
тип_n пер_n;
};
Таким образом, внутри структуры мы просто объявляем набор переменных.
struct point_t {
double x;
double y;
};
После этого мы можем создавать переменные типа point_t
в нашей программе.
Переменные-члены будут доступны для переменных структурных типов через точечную нотацию:
point_t zero;
zero.x = zero.y = 0.0;
double distance(point_t p1, point_t p2) {
return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
Указатель -- это специальная переменная, хранящая типизированный адрес в памяти.
Если 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ширение .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;
}