Основы языка Си: структура Си-программы, базовые типы и конструирование новых типов, операции и выражения
Выражения
Выражения в Си составляются из переменных или констант, к которым применяются различные операции. Для указания порядка операций можно использовать круглые скобки.
Отметим, что, помимо обычных операций, таких, как сложение или умножение, в Си существует ряд операций, несколько непривычных для начинающих. Например, запятая и знак равенства (оператор присваивания) являются операциями в Си; помимо операции сложения +, есть еще операция увеличить на += и операция увеличения на единицу ++. Зачастую они позволяют писать эстетически красивые, но не очень понятные для начинающих программы.
Впрочем, эти непривычные операции можно не использовать, заменяя их традиционными.
Оператор присваивания
Оператор присваивания является основой любого алгоритмического языка (см. лекцию 3). В Си он записывается с помощью символа равенства, например, строка
x = 100;
означает присвоение переменной x значения 100. Для сравнения двух значений используется двойное равенство ==, например, строка
bool f = (2 + 2 == 5);
присваивает логической переменной f значение false (поскольку 2+2 не равно пяти, логическое выражение в скобках ложно).
Непривычным для начинающих может быть то, что оператор присваивания " = " в Си - бинарная операция, такая же, как, например, сложение или умножение. Значением операции присваивания = является значение, которое присваивается переменной, стоящей в левой части. Это позволяет использовать знак присваивания внутри выражения, например,
x = (y = sin(z)) + 1.0;
Здесь в скобках стоит выражение y = sin(z), в результате вычисления которого переменной y присваивается значение sin z. Значением этого выражения является значение, присвоенное переменной y, т.е. sin z. К этому значению затем прибавляется единица, т.е. в результате переменной x присваивается значение sin (z)+1.
Выражения, подобные приведенному в этом примере, иногда используются, когда необходимо запомнить значение подвыражения (в данном случае sin (z) ) в некоторой переменной (в данном случае y ), чтобы затем не вычислять его повторно. Еще один пример:
n = (k = 3) + 2;
В результате переменной k присваивается значение 3, а переменной n - значение 5. Конечно, в нормальных программах такие выражения не встречаются.
Арифметические операции
К четырем обычным арифметическим операциям сложения +, вычитания -, умножения * и деления / в Си добавлена операция нахождения остатка от деления первого целого числа на второе, которая обозначается символом процента %. Приоритет у операции вычисления остатка % такой же, как и у деления или умножения. Отметим, что операция % перестановочна с операцией изменения знака (унарным минусом), например, в результате выполнения двух строк
x = -(5 % 3); y = (-5) % 3;
обеим переменным x и y присваивается отрицательное значение -2.
Операции увеличения и уменьшения
В Си добавлены операции увеличения и уменьшения на единицу, которые, к примеру, очень удобно применять к счетчикам. Операция увеличения записывается с помощью двух знаков сложения ++, операция уменьшения - с помощью двух минусов --. Например, операция ++, примененная к целочисленной переменной i, увеличивает ее значение на единицу:
++i; эквивалентно i = i+1
Операции увеличения и уменьшения на единицу можно применять только к дискретным типам - целочисленным переменным различного вида и указателям. Операцию нельзя применять к вещественным переменным! Например, следующий фрагмент программы является ошибочным:
double x; . . . ++x; // Ошибка! Операция ++ неприменима // к вещ. переменной
Операция ++ увеличивает значение переменной на "минимальный атом". Так как для вещественных переменных такого "атомарного" значения нет, операции увеличения и уменьшения для них запрещены.
Для указателей операция ++ увеличивает значение переменной на размер одного элемента того типа, на который ссылается указатель. Для указателя "атомом" является один элемент заданного типа, поэтому размер одного элемента и является шагом изменения значения указателя. Это очень естественно, т.к. после увеличения указатель будет содержать адрес следующего элемента данного типа, а после уменьшения - адрес предыдущего элемента. Пример:
double a[100]; double *p = &(a[15]); // в p записывается адрес // элемента массива a[15] ++p; // в p будет адрес элемента a[16] // (адрес увеличивается на sizeof(double) == 8)
Описаны массив a вещественных чисел типа double и указатель p на элементы типа double. При описании указателя p в него заносится начальное значение, равное адресу элемента a[15] массива a. После выполнения операции увеличения ++ в переменной p будет содержаться адрес следующего элемента a[16]. Физически содержимое переменной p увеличивается на размер одного элемента типа double, т.е. на 8.
Операции увеличения ++ и уменьшения -- на единицу имеют префиксную и суффиксную формы. В префиксной форме операция записывается перед переменной, как в приведенных выше примерах. В суффиксной форме операция записывается после переменной:
++x; // Префиксная форма x--; // Суффиксная форма
Разница между префиксной и суффиксной формами проявляется только при вычислении сложных выражений. Если используется префиксная форма операции ++, то сначала переменная увеличивается, и только после этого ее новое значение используется в выражении. При использовании суффиксной формы значение переменной сначала используется в выражении и только затем увеличивается. Примеры:
int x = 5, y = 5, a, b; a = (++x) + 2; // переменной a присваивается значение 8 b = (y++) + 2; // переменной b присваивается значение 7
С логической точки зрения, префиксная операция более естественна (при использовании суффиксной формы надо сперва вычислить сложное выражение и только затем вернуться к увеличению переменной, т.е. операция ++ выполняется не в момент ее использования, а как бы откладывается на потом). Забегая вперед, отметим, что это различие весьма существенно при программировании на C++ в случае переопределения операторов увеличения для классов. Тем не менее, в большинстве книг по Си суффиксная форма используется чаще (скорее всего, эта традиция, связаная с эстетикой текста).
Дадим два совета (возможно, не бесспорные) по использованию операций ++ и --:
- никогда не применяйте эти операции в сложных выражениях! Ничего, кроме путаницы, это не дает. Например, вместо фрагменталучше использовать фрагмент
double *p, x, y; . . . y = *p++ + x;
С точки зрения компилятора, они абсолютно эквивалентны, но второй фрагмент проще и понятнее (и, значит, вероятность ошибки программирования меньше);double *p, x, y; . . . y = *p + x; ++p;
- всегда отдавайте предпочтение префиксной форме операций ++ и --. Например, вместо фрагменталучше использовать фрагмент
int x, y; . . . x++; y--; // Используется суффиксная форма
int x, y; . . . ++x; --y; // Лучше применять префиксную форму
Операции "увеличить на", "домножить на" и т.п.
В большинстве алгоритмов при выполнении операции сложения чаще всего переменная-результат операции совпадает с первым аргументом:
x = x + y;
Здесь складываются значения двух переменных x и y, результат помещается в первую переменную x. Таким образом, значение переменной x увеличивается на значение y. Подобные фрагменты встречаются в программах гораздо чаще, чем фрагменты вида
x = y + z;
где аргументы и результат различны. Рассмотрим, например, фрагмент программы, вычисляющий сумму элементов массива вещественных чисел (забегая вперед, мы используем в нем конструкцию цикла "пока"):
double a[100]; double s; int i; . . . s = 0.0; i = 0; while (i < 100) { s = s + a[i]; ++i; }
Здесь сумма элементов массива накапливается в переменной s. В строке
s = s + a[i];
к сумме s прибавляется очередной элемент массива a[i], т.е. значение s увеличивается на a[i]. В Си существует сокращенная запись операции увеличения:
s += a[i];
Оператор += читается как "увеличить на". Строка
x += y; // Увеличить значение x на y
эквивалентна в Си строке
x = x + y; // x присвоить значение x + y,
но короче и нагляднее.
Оператор вида ?= существует для любой операции ?, допустимой в Си. Например, для арифметических операций +, -, *, /, % можно использовать операции
+= увеличить на -= уменьшить на *= домножить на /= поделить на %= поделить с остатком на
к примеру, строка
x *= 2.0;
удваивает значение вещественной переменной x.
Операторы вида ?= можно использовать даже для операций ?, которые записываются двумя символами. Например, операции логического умножения и сложения (см. раздел 1.4.4) записываются в Си как && (двойной амперсенд) и || (двойная вертикальная черта). Соответственно, логические операторы "домножить на" и "увеличить на" записываются в виде &&= и ||=, например,
bool x, y; x &&= y; // эквивалентно x = x && y; x ||= y; // эквивалентно x = x || y;