MyTetra Share
Делитесь знаниями!
О, смотри-ка какое хорошее место. Дайте два!
Как научиться понимать синтаксис языка C++
14.09.2018
15:32
Раздел: Компьютер - Программирование - Язык C++

Я не понимаю язык Си++. Да, я два десятилетия программирую на этом языке, но для меня он и по сей день остается загадкой. Даже если отбросить в сторону все сложности поддерживаемых языком парадигм и структур, то окажется, что язык этот не становится более понятным. Это происходит из-за того, что сложен не только весь язык как таковой, сложна такая простая и детерминированная вещь как его синтаксис.


Я часто смотрю на код в чужих проектах и говорю себе: "Твою ж мать, что тут написано?". Причем, проблемы не только в понимании структуры чужой программы, проблемы возникают при понимании даже одной строчки кода.


Говорят, что писать программы надо с использованием языка, а не на языке. Многие Си++ программисты этого либо не знают, либо не могут себе такой роскоши позволить. Они вязнут в низкоуровневых подробностях, и за собой способны утянуть и других разработчиков и всю структуру проекта. Вместо борьбы со сложностью, чем по своей сути и является программирование, получается игра в "догадайся, что это". Поначалу игра может быть и увлекательной, но быстро начинает утомлять.


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


Все что здесь написано - сугубо личное видение синтаксиса языка Си++. Здесь могут быть существенные фактологические и логические ошибки, происходящие по одной банальной причине - автор сего текста не знает и не понимает язык Си++, и с трудом продирается сквозь его синтаксис.



Контекстная зависимость


Почему синтаксис языка Си++ такой сложный? Потому что в нем во главу угла поставлена великая и ужасная Контекстная Зависимость. Что это такое? Говоря простыми словами, это когда один и тот же символ или оператор означает совершенно разные вещи в зависимости от того, в каком месте команды он находится.


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


Хорошая новость заключается в том, что контекстов на самом деле немного. Плохая новость заключается в том, что выражения в языке Си++ можно построить так заковыристо, что понять контекст будет сложно. И, конечно, обычное дело, когда в одной команде языка Си++ для одного и того же оператора в разных частях команды действуют разные контексты.


Далее перечислены контексты, которые наиболее часто встречаются в коде Си++:


  • Контекст в зависимости от типа переменной
  • Контекст при определении переменной
  • Контекст в выражении
  • Контекст при определении функции (метода)
  • Контекст при манипуляциях с областью видимости


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



Выражение, инструкция, присваивание


Начать понимать синтаксис языка Си++ лучше всего с того, чтобы уложить в голове две базовые вещи, используемые в языке:


  • Выражение
  • Инструкция


Чтобы их различать, можно дать такие определения:


Выражение - это то, что выдает (в результате своего вычисления) некое значение.


Инструкция - это выражение (зачастую, составное), завершенное точкой с запятой.


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


Например, есть у нас две переменных: x и y. Уже одно только написание имени переменной x будет выдавать значение переменной x. Аналогично и для переменной y. А написание имен переменных, объединенных оператором суммы будет выдавать значение суммы: x + y. В этом и заключается работа с выражениями.


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


Другими словами, когда написано a = b + c , то происходит две вещи:


  1. В переменную a кладется значение суммы b + c;
  2. Результатом выражения присваивания становится значение присваиваемого выражения.


Сложно? На самом деле все просто. На конкретных цифрах это будет выглядеть так: имеем b = 7 и c = 3. Тогда значение переменной a станет 10. Но и результатом вычисления всего выражения a = b + c тоже будет число 10. Для лучшего понимания можно все заключить в круглые скобочки и сказать, что результатом выражения (a = b + c) является число 10.


И именно поэтому в языке СИ++ появляются такие странные инструкции присваивания:


a = b = x + y;


Для понимания того, что эта инструкция делает, надо в первую очередь понять используемый в ней контекст. Ясно, что в этой в этой инструкции написано выражение, в котором происходит присваивание, а значит нам надо рассматривать выражение с контекстом присваивания. Особенностью контекста присваивания в языке Си++ является то, что присваивание выполняется справа-налево. То есть, сначала вычисляется то, что находится справа, и присваивается тому, что находится слева. Естественно, чтобы задать более левые значения, надо вначале вычислить правые. Поэтому последовательность действий с точки зрения языка можно написать так:


  • Вычисляется выражение x + y. Потому что оно самое правое.
  • Вычисляется выражение b = x + y. При вычислении этого выражения переменной b будет присвоена сумма x + y, а значение всего выражения b = x + y будет эта же сумма.
  • Переменной a присваивается значение выражения b = x + y.


Здесь есть один тонкий момент. Я думаю, что переменной a присваивается значение выражения b = x + y. Возможно, правильно было бы сказать, что переменной a присваивается значение выражения b. Согласитесь, выражения b и b = x + y - это все-таки разные выражения, хотя их значения в данном случае одинаковые. Какое же из этих утверждений правильное? Здесь можно рассуждать следующим образом:


Мы разбираем данное выражение итеративно, рассматривая блоки операторов присваивания справа-налево. При этом мы должны все время оперировать термином "выражение", и не перепрыгивать на термин "значение (результат) выражения". То, что в момент разбора выражения происходит присвоение значений переменным, роли не играет. Таким образом, правильнее будет утверждать, что переменной a присваивается значение выражения b = x + y.


Кстати, в более простых языках, например в таком как Pascal, присваивание является инструкцией, а не выражением. То есть, при выполнении присваивания в Pascal, происходит вычисление только выражения в правой части. А разультата всей конструкции присваивания просто нет как понятия. Поэтому в Паскале нет такой возможности, как множественное присваивание. А если в каких-то диалектах оно и появится, то только в виде синтаксического сахара.


Завершая тему присваивания, можно показать еще один "странный" пример. Поскольку присваивание - это выражение, то его можно использовать в качестве аргумента функции:


n = function( a = b );


Значение выражения a = b равно тому, что присваивалось, то есть значению b. Поэтому в функцию передастся значение b. Здесь возникает вопрос: а равно ли значение переменной a значению b в момент выполнения функции? Правильный ответ: да, равно. Нетрудно запомнить, что при вычислении выражения с оператором присвоения =, присвоение происходит сразу при вычислении выражения. Перед тем, как передаться в функцию, выражение должно быть вычислено. Именно при этом вычислении переменной a присвоится значение b. И только потом будет вызвана функция.


Но тут нужно помнить, что если использовать в выражении операторы постинкремента, например a = b++, то значение такого выражения будет неизменнённое значение b. Функция выполнится, и только после этого значение b увеличится на 1. А значение a все время останется прежним, неувеличенным значением b. Подробнее можно прочитать здесь: Понимание преинкремента и постинкремента в языке C++.


Надо ли в своем коде использовать конструкции, подобные n = function( a = b ) ? Мое мнение: нет, не надо. Да, с такой конструкцией код становится несколько короче, но эта краткость идет в ущерб пониманию и читабельности. Гораздо правильнее с точки зрения дальнейшей поддержки исходного кода написать инструкции так, как они будут выполняться во времени при выполнении вышеозначенного кода. То есть, вот так:


a = b;

n = function( a );


Выглядит такая запись, по сравнению с предыдущим вариантом, не круто, а прямолинейно и наивно. Но понять ее не составляет никакого труда. Кроме того, современные оптимизирующие компиляторы для обоих вариантов генерируют одинаковый код.


Дописать...




Так же в этом разделе:
 
MyTetra Share v.0.52
Яндекс индекс цитирования