Операции присваивания
Инициализация задает начальное значение переменной. Например:
int ival = 1024; |
int *pi = 0;
В результате операции присваивания объект получает новое значение, при этом старое пропадает:
ival = 2048; |
|
pi = &iva1;
Иногда путают инициализацию и присваивание, так как они обозначаются одним и тем же знаком =. Объект инициализируется только один раз– при его определении. В то же время операция может быть применена к нему многократно.
Что происходит, если тип объекта не совпадает с типом значения, которое ему хотят присвоить? Допустим,
ival = 3.14159; // правильно?
В таком случае компилятор пытается трансформировать тип объекта, стоящего справа, в тип объекта, стоящего слева. Если такое преобразование возможно, компилятор неявно изменяет тип, причем при потере точности обычно выдается предупреждение. В нашем случае вещественное значение 3.14159 преобразуется в целое значение 3, и это значение присваивается переменной ival.
Если неявное приведение типов невозможно, компилятор сигнализирует об ошибке:
pi = ival; // ошибка
Неявная трансформация типа int в тип указатель на int
невозможна. (Набор допустимых неявных преобразований типов мы обсудим в разделе 4.14.)
Левый операнд операции присваивания должен быть l-значением. Очевидный пример неправильного присваивания:
1024 = ival; // ошибка
Возможно, имелось в виду следующее:
int value = 1024; |
value = ival; // правильно
Однако недостаточно потребовать, чтобы операнд слева от знака присваивания был l-значением.
Так, после определений
const int array_size = 8; int ia[ array_size ] = { 0, 1, 2, 2, 3, 5, 8, 13 }; |
int *pia = ia;
выражение
array_size = 512; // ошибка
ошибочно, хотя array_size и является l-значением: объявление array_size константой не дает возможности изменить его значение. Аналогично
ia = pia; // ошибка
ia – тоже l-значение, но оно не может быть значением массива.
Неверна и инструкция
pia + 2=1; // ошибка
Хотя pia+2
дает адрес ia[2], присвоить ему значение нельзя. Если мы хотим изменить элемент ia[2], то нужно воспользоваться операцией разыменования. Корректной будет следующая запись:
*(pia + 2) = 1; // правильно
Операция присваивания имеет результат – значение, которое было присвоено самому левому операнду. Например, результатом такой операции
ival = 0;
является 0, а результат
ival = 3.14159;
равен 3. Тип результата – int в обоих случаях. Это свойство операции присваивания можно использовать в подвыражениях. Например, следующий цикл
extern char next_char(); int main() { char ch = next_char(); while ( ch != '\n' ) { // сделать что-то ... ch = next_char(); } // ... |
может быть переписан так:
extern char next_char(); int main() { char ch; while (( ch = next_char() ) != '\n' ) { // сделать что-то ... } // ... |
Заметим, что вокруг выражения присваивания необходимы скобки, поскольку приоритет этой операции ниже, чем операции сравнения. Без скобок первым выполняется сравнение:
next_char() != '\n'
и его результат, true или false, присваивается переменной ch. (Приоритеты операций будут рассмотрены в разделе 4.13.)
Аналогично несколько операций присваивания могут быть объединены, если это позволяют типы операндов. Например:
int main () { int ival, jval; ival = jval = 0; // правильно: присваивание 0 обеим переменным |
}
Обеим переменным ival и jval
присваивается значение 0. Следующий пример неправилен, потому что типы pval и ival
различны, и неявное преобразование типов невозможно. Отметим, что 0
является допустимым значением для обеих переменных:
int main () { int ival; int *pval; ival = pval = 0; // ошибка: разные типы |
}
Верен или нет приведенный ниже пример, мы сказать не можем, , поскольку определение jval в нем отсутствует:
int main() { // ... int ival = jval = 0; // верно или нет? // ... |
Это правильно только в том случае, если переменная jval определена в программе ранее и имеет тип, приводимый к int. Обратите внимание: в этом случае мы присваиваем 0
значение jval и инициализируем ival. Для того чтобы инициализировать нулем обе переменные, мы должны написать:
int main() { // правильно: определение и инициализация int ival = 0, jval = 0; // ... |
В практике программирования часты случаи, когда к объекту применяется некоторая операция, а результат этой операции присваивается тому же объекту. Например:
int arraySum( int ia[], int sz ) { int sum = 0; for ( int i = 0; i < sz; ++i ) sum = sum + ia[ i ]; return sum; |
Для более компактной записи С и С++ предоставляют составные операции присваивания. С использованием такого оператора данный пример можно переписать следующим образом:
int arraySum( int ia[], int sz ) { int sum = 0; for ( int i =0; i < sz; ++i ) // эквивалентно: sum = sum + ia[ i ]; sum += ia[ i ]; return sum; |
Общий синтаксис составного оператора присваивания таков:
a op= b;
где op=
является одним из десяти операторов:
+= -= *= /= %=
<<= >>= &= ^= |=
Запись a op= b в точности эквивалентна записи a = a op b.
Упражнение 4.6
Найдите ошибку в данном примере. Исправьте запись.
int main() { float fval; int ival; int *pi; fval = ival = pi = 0; |
Упражнение 4.7
Следующие выражения синтаксически правильны, однако скорее всего работают не так, как предполагал программист. Почему? Как их изменить?
(a) if ( ptr = retrieve_pointer() != 0 ) (b) if ( ival = 1024 ) |