Перейти к содержимому

Как выглядит знак присваивания в программировании

  • автор:

3. Команда присваивания

Знак «\(:=\)» читается как присвоить и заменяет предыдущее значение переменной, стоящей в левой части на новое, которое находится в правой части.

Знаки «\(=\)» и «\(:=\)» — это разные знаки. Знак равно обозначает равенство двух величин. А знак «\(:=\)» присваивает новое значение переменной.

Если справа от знака присваивания стоит не конкретное значение, а целое выражение, то сначала вычисляется его значение, а потом оно присваивается переменной, находящейся слева от знака.
\(A := 10\);
\(B := (A + 4)/2\).
Ответ: \(B := 7\).

Свойства присваивания:
1. если переменной не присвоено значение, она является неопределённой;
2. присвоенное значение переменной сохраняется до тех пор, пока не будет выполнятся новая операция присваивания с этой же переменной;
3. при присваивании переменной очередного значения, предыдущее значение из памяти стирается.

Даны две коробки с бусинками. В одной коробке только красные бусинки, а во второй только синие, при этом перепутаны цвета коробочек. Красные бусины лежат в синей коробке, а синие в красной.

Как можно поменять местами бусинки? Напишите алгоритм.

Очевидно, для того чтобы пересыпать бусинки из одной коробки в другую, понадобится какая-то третья коробочка.
Введем обозначения.
\(A := krasnye\)
\(B := sinie\)
\(C := net\)

алг коробочки
лит\(A\), \(B\), \(C\)
нач
\(C := krasnye\)
\(A := sinie\)
\(B := krasnye\)
кон

Если бы мы не использовали третью переменную, а решили сразу поменять местами значение переменных, получили бы следующее:
\(B := krasnye\)
А значение переменное \(B\) теперь \(krasnye\), значение \(sinie\) из памяти компьютера уже стёрлось.

5. Оператор присваивания

Screenshot_18.png

Выполняя первую команду переменной \(a\) присваивается значение \(2\) и это значение записывается в ячейку оперативной памяти.

Далее переменной \(b\) присваивается значение \(4\), оно также записывается в оперативную память.
В третье строке переменной \(а\) присваивается новое значение, вычислим:

Теперь в оперативной памяти хранится значение переменной \(a=14\). Предыдущее значение \(2\) из памяти стёрлось.

Выполним последнюю строку данного нам алгоритма. (Помним о том, что на данный момент в оперативной памяти хранятся данные \(a=14\), \(b=4\).)

Оператор присваивания

Казалось бы, что может быть проще оператора присваивания? Есть некоторая переменная, ей нужно присвоить некоторое значение, равное константе, другой переменной, или оно является результатом некоторого вычисления.

Оператор присваивания обозначается одиночным знаком равенства (=).
В C# оператор присваивания действует таким же образом, как и в других языках программирования. Ниже приведена его общая форма:
имя_переменной = выражение;
Здесь имя_переменной должно быть совместимо с типом выражения.

Однако вам полезно знать еще кое-что про этот оператор, чтобы понимать код, написанный другими программистами.

У простого оператора присваивания имеется одна интересная особенность, о которой вам будет полезно знать: он позволяет создавать цепочку операций присваивания.
Рассмотрим следующий фрагмент кода:
int x, у, z;
x = у = z = 10; // присвоить значение 10 переменным x, у и z
В приведенном выше фрагменте кода одно и то же значение 10 задается для переменных х, у и z с помощью единственного оператора присваивания. Это значение присваивается сначала переменной z, затем переменной у и, наконец, переменной х. Такой способ присваивания «по цепочке» удобен для задания общего значения целой группе переменных.

Составные операторы присваивания

В C# предусмотрены специальные составные операторы присваивания, упрощающие программирование некоторых операций присваивания. Обратимся сначала к простому примеру:
x = x + 1;

Его можно переписать следующим образом:
x += 1;
Пара операторов += указывает компилятору на то, что переменной х должно быть присвоено ее первоначальное значение, увеличенное на единицу.

Для многих двоичных операций, то есть операций, требующих наличия двух операндов, существуют отдельные составные операторы присваивания.

Общая форма всех этих операторов имеет следующий вид:
имя переменной op = выражение;
где op — арифметический или логический оператор, применяемый вместе с оператором присваивания.

Ниже перечислены составные операторы присваивания для арифметических и логических операций:

+= : присваивание после сложения. Присваивает левому операнду сумму левого и правого операндов, выражение A += B равнозначно выражению A = A + B ;

-= : присваивание после вычитания. Присваивает левому операнду разность левого и правого операндов, A -= B эквивалентно A = A — B ;

*= : присваивание после умножения. Присваивает левому операнду произведение левого и правого операндов, A*=B эквивалентно A=A*B ;

/= : присваивание после деления. Присваивает левому операнду частное левого и правого операндов, A /= B эквивалентно A = A / B ;

%= : присваивание после деления по модулю. Присваивает левому операнду остаток от целочисленного деления левого операнда на правый, A %= B эквивалентно A = A % B ;

|= : присваивание после поразрядной дизъюнкции. Присваивает левому операнду результат поразрядной дизъюнкции его битового представления с битовым представлением правого операнда, A |= B эквивалентно A = A | B ;

^= : присваивание после операции исключающего ИЛИ. Присваивает левому операнду результат операции исключающего ИЛИ его битового представления с битовым представлением правого операнда, A=A^B ;

>>= : присваивание после сдвига разрядов вправо. Присваивает левому операнду результат сдвига его битового представления вправо на определенное количество разрядов, равное значению правого операнда, A >>= B эквивалентно A = A >> B .

Составные операторы присваивания записываются более кратко, чем их не составные эквиваленты. Поэтому их иногда еще называют укороченными операторами присваивания.
У составных операторов присваивания имеются два главных преимущества. Во-первых, они более компактны, чем их «не сокращенные» эквиваленты. И во-вторых, они дают более эффективный исполняемый код, поскольку левый операнд этих операторов вычисляется только один раз.

Именно по этим причинам составные операторы присваивания чаще всего применяются в программах, профессионально написанных на C#.
Далее познакомимся с понятием «Поразрядные операторы

NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.

Почему «=» означает присваивание?

В среде ФП часто критикуют данный момент императивного программирования: «Как так может быть, что a = a + 1? Это всё равно что сказать „1 = 2“. В мутабельном присваивании нет смысла».

Здесь мы наблюдаем несовпадение обозначения: «равно» должно обозначать «равенство», когда на практике оно обозначает «присвоить». Я согласен с этой критикой и считаю, что это неудачная нотация. Но также мне известно, что в некоторых языках вместо a = a + 1 пишут выражение a := a + 1 . Почему же эта запись не является нормой?

На этот вопрос обычно отвечают «потому что так сделано в C». Но это похоже на перекладывание ответственности на кого-то другого: кто из нас знает, почему так сделано в C? Давайте разбираться вместе!

Большая четвёрка

В начале 1960-ых существовало четыре доминирующих высокоуровневых языка: COBOL, FORTRAN II, ALGOL-60, и LISP. В то время, программисты разбивали присваивание на два класса: инициализацию (initialization) — когда вы впервые определяете переменную, и переприсвоение (reassignment) — когда вы вы изменяется значение существующей переменной.

Итак, давайте добавим комментарии к нашему примеру на Python и получим следующий код:

a = 1 # Инициализация a = a + 1 # Переприсвоение print(a)

В то время люди не пользовались конкретно этими терминами для обозначения операций, но по сути это было как раз то, что делал каждый программист. В таблице ниже вы можете увидеть, какие из операторов использовались для каждого языка, и как выполнялась проверка на равенство.

Язык Инициализация Присваивание Равенство
FORTRAN = = .EQ.
COBOL INITIALIZE MOVE [1] EQUAL
ALGOL N/A := =
LISP let set equal

В ALGOL не было отдельного оператора для инициализации — вместо этого вы создавали переменную определенного типа и затем использовали оператор для присвоения ей чего-либо. Вы могли написать integer x; x := 5; , но не x := 5; . Единственный язык из списка, который использовал = для присваивания, это FORTRAN — и он выглядит подходящим кандидатом для ответа на наш вопрос.

Но мы-то с вами знаем, что C происходит от ALGOL; что, в свою очередь, означает, что по какой-то причине было решено отказаться от оператора присваивания := и изменить значение оператора = с проверки на равенство…

ALGOL порождает CPL

ALGOL-60, скорее всего, является одним из самых влиятельных языков программирования в истории computer science. Вероятно, что при всём этом он также является одним из самых бесполезных языков. В основной спецификации языка намеренно не было предусмотрено никакой функциональности для ввода/вывода. Вы могли «захардкодить» вводы и измерять выводы, но если вам нужно было сделать с ними что-либо полезное, вам требовалось найти компилятор, который расширял бы базовый язык. ALGOL был спроектирован с целью исследования алгоритмов и поэтому он «ломался», когда вы пытались сделать на нём что-либо ещё.

Однако, он оказался настолько «крепким» языком, что другие захотели обобщить его для использования в бизнесе и в промышленности. Первую подобную попытку предприняли Кристофер Страчи и Кембриджский университет. Получившийся в итоге язык CPL добавил к функциональности ALGOL достаточное количество инновационных возможностей, о большей части которых мы в дальнейшем глубоко пожалели. Одной из них было определение с инициализацией, в котором переменная могла быть инициализирована и присвоена в одном выражении. Теперь вместо того, чтобы писать x; x := 5; вы могли просто написать integer x = 5 . Просто супер!

Но здесь мы переключились с := на = . Это происходит потому, что в CPL было три типа инициализации переменной:

  • = означало инициализацию по значению.
  • ≃ означала инициализацию по ссылке, поэтому если x ≃ y, то переприсваивание x также изменяет y. Но если вы написали x ≃ y + 1 и попробовали переприсвоить x, то программа бы «упала».
  • ≡ означает инициализацию через подстановку, т.е. превращение x в функцию, не принимающую аргументов (niladic function), которая вычисляет правостороннее значение каждый раз, когда её используют. При этом нигде не объясняется, что должно случиться, если вы попробуете переприсвоить x — и я, поверьте, тоже не слишком хочу знать это.

Всего год спустя Кен Айверсон создаст APL, который станет использовать символ ← для всех видов присваиваний. Поскольку на большинстве клавиатур такой клавиши нет и никогда не было, от него быстро откажется и сам автор — его следующий язык, J, тоже будет использовать для присваиваний символ =: [2]. Однако, APL глубоко повлиял на S, который в свою очередь глубоко повлиял на R — вот почему

CPL порождает BCPL

CPL был замечательным языком, обладавшим всего одним небольшим недостатком: ни у кого не получалось написать его реализацию. Несколько человек смогли частично реализовать различные подмножества из его «фич», но этот язык оказался слишком большим и сложным для компиляторов той эпохи. Поэтому неудивительно, что Мартин Ричардс решил избавиться от ненужной сложности ящика и создал BCPL. Первый компилятор BCPL появился в 1967 году… а первый компилятор CPL — лишь в 1970-м.

Среди многих других упрощений оказались и правила «трёх типов инициализации», которые приказали долго жить. Ричардс считал, что выражения-подстановки были вещью узкоспециальной, и их можно было заменить функциями (то же самое, по его мнению, касалось и присваиваний). Поэтому он совместил их всех в простое = , за исключением наименований адресов глобальной памяти, которые использовали : . Как и в случае CPL, = представляло собой проверку на равенство. Для присвоения (reassignment), он использовал := — аналогично тому, как это сделали CPL и ALGOL. Многие из последовавших после языков также следовали этому соглашению: = для инициализации, := для присваивания, = для равенства. Но в широкие массы это пошло тогда, когда Никлаус Вирт создал Pascal — вот почему сегодня мы называем подобные обозначения «в стиле Pascal».

Насколько мне известно, BCPL был также первым «слабо типизированным» языком, поскольку единственным типом данных было машинное слово (data word)[3]. Это позволило сделать компилятор куда более портабельным за счет потенциального увеличения количества логических ошибок, но Ричардс надеялся на то, что улучшения в процессе и наименования с описанием позволят противостоять этому. Помимо все этого, именно в BCPL впервые появились фигурные скобки с целью определения блоков.

BCPL порождает B

Кен Томпсон хотел, чтобы BCPL мог выполняться на PDP-7. Несмотря на то, что у BCPL был «компактный компилятор», он всё ещё был в четыре раза больше, чем минимальный объем рабочей памяти на PDP-7 (16 кБ вместо 4 кБ). Поэтому Томпсону требовалось создать новый, более минималистичный язык. Также по личным эстетическим причинам он хотел минимизировать количество символов в исходном коде. Это и повлияло на дизайн языка B сильнее всего; вот почему в нём появились такие операторы, как ++ и —.

Если вы оставите в стороне использование поименованных адресов глобальной памяти, в BCPL всегда использовались следующие обозначения: = для инициализации и := для переприсваивания (reassignment). Томпсон решил, что эти вещи можно совместить в единый токен, который можно использовать для всех видов присваивания, и выбрал =, поскольку оно было короче. Однако, это привнесло некоторую неоднозначность: если x уже был объявлен, то чем было x = y — присваиванием или проверкой на равенство? И это ещё не всё — в некоторых случаях предполагалось, что это обе операции сразу! Поэтому он был вынужден добавить новый токен == как единую форму выражения смысла «равняется этому». Как выражался сам Томпсон:

Поскольку присваивание в типовой программе встречается примерно в два раза чаще, чем сравнение на равенство, уместно было сделать оператор присваивания вполовину короче.

За время, прошедшее между появлением BCPL и B, была создана Simula 67, первый объектно-ориентированный язык. Simula последовала соглашениям ALGOL о строгом разделении шагов инициализации и переприсвоения. Алан Кей примерно в это же время начал работу над Smalltalk, который добавил блоки, но последовал такому же синтаксису.

Томпсон (к которому присоединился Денис Ритчи) выпустил первую версию B примерно в 1969 году. Так что вплоть до 1971 года (примерно) большинство новых языков использовали для присваивания обозначение := .

B порождает C

… остальное – уже история.

Хорошо, есть ещё кое-что, о чём стоит рассказать. ML вышел год спустя, и, насколько мне известно, был первым языком, который привлек серьезное внимание к чистым функциям и отсутствию мутаций. Но в нем по-прежнему был спасательный круг в виде ссылочных ячеек (reference cells), которые можно было переприсваивать новым значениям при помощи оператора := .

Начиная с 1980, мы наблюдаем рост популярности новых императивных языков, ориентированных на корректность — в частности, Eiffel и Ada, оба из которых используют для операции присваивания символ := .

Если посмотреть на всю картину в целом, = никогда не был «естественным выбором» для оператора присваивания. Почти все языки в семейном дереве ALGOL использовали вместо этого для присваивания := , возможно в силу того, что = было столь тесно ассоциировано с равенством. В наши дни большинство языков использует = поскольку его использует C, и мы можем проследить эту историю до CPL, который представлял собой тот ещё бардак.

Примечания

1. В этом месте COBOL становится очень странным. У них есть несколько операторов, которые могут неявно мутировать, вроде ADD TO и COMPUTE. COBOL — плохой язык.
2. Мне нравится думать, что это было своеобразным приколом над := , хотя на самом деле этот оператор согласован с остальными частями языка, который использует . и : как суффиксы глаголов.
3. Позже в BCPL добавят ключевое слово для типа с плавающей запятой. И когда я говорю «позже», я имею в виду 2018 год.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *