(Примечание: я не спрашиваю об определениях перед инкрементом или после инкремента или о том, как они используются в C / C ++. Поэтому я не думаю, что это дублирующий вопрос.)
Разработчики C (Деннис Ритчи и др.) Создали операторы увеличения и уменьшения по очень веским причинам. Что я не понимаю, так это почему они решили провести различие между предыдущими пост-приращениями / убываниями?
Я чувствую, что эти операторы были гораздо полезнее, когда C разрабатывался, чем сегодня. Большинство программистов на C / C ++ используют один или другой, а программисты из других языков считают это различие странным и запутанным (примечание: это основано исключительно на неподтвержденных данных).
Почему они решили сделать это, и что изменилось в вычислениях, что это различие не столь полезно сегодня?
Напомним, что разницу между ними можно увидеть в коде C ++:
int x = 3;
cout << "x = 3; x++ == " << x++ << endl;
cout << "++x == " << ++x << endl;
cout << "x-- == " << x-- << endl;
cout << "--x == " << --x << endl;
даст в качестве выхода
x++ == 3
++x == 5
x-- == 5
--x == 3
Увеличение и уменьшение на 1 в то время широко поддерживалось в аппаратном обеспечении: один код операции и быстро. Это потому, что «увеличение на 1» и «уменьшение на 1» были очень распространенной операцией в коде (верно по сей день).
Формы после и до предварительной обработки влияли только на то место, где этот код операции был вставлен в сгенерированный машинный код. Концептуально, это имитирует «увеличение / уменьшение» до или же после используя результат «. В одном утверждении
i++;
концепция «до / после» не используется (и поэтому ++i;
), но в
printf ("%d", ++i);
это. Это различие столь же важно в наше время, как и когда разрабатывался язык C (эта идиома была скопирована с его предшественника, названного «B»).
Эта особенность [ячейки памяти «автоинкремента» PDP-7], вероятно, предлагала такие операторы Томпсону [Кен Томпсон, который разработал «B», предшественник C]; обобщение, чтобы сделать их префиксом и постфиксом, было его собственным. Действительно, ячейки с автоинкрементом не использовались непосредственно при реализации операторов, и более сильной мотивацией для нововведения было, вероятно, его наблюдение, что перевод ++ x был меньше, чем перевод x = x + 1.
Спасибо @dyp за упоминание этого документа.
Когда вы отсчитываете от n
это очень важно, будь то перед декрементом или после декремента
#include <stdio.h>
void foopre(int n) {
printf("pre");
while (--n) printf(" %d", n);
puts("");
}
void foopost(int n) {
printf("post");
while (n--) printf(" %d", n);
puts("");
}
int main(void) {
foopre(5);
foopost(5);
return 0;
}
Увидеть код работает на Ideone.
Чтобы получить ответ, выходящий за рамки спекуляций, скорее всего, вам нужно лично спросить Денниса Ритчи и его коллег.
В дополнение к уже приведенному ответу я хотел бы добавить две возможные причины, по которым я пришел:
лень / сохранение пространства:
Вы могли бы сохранить несколько нажатий клавиш / байтов во входном файле, используя соответствующую версию в таких конструкциях, как while(--i)
против while(i--)
, (посмотрите на ответ pmg, чтобы понять, почему оба имеют значение, если вы не видели его в первом запуске)
эстетика
По причинам симметрии, имеющим только одну версию, либо пре-, либо постинкремент / декремент, может быть что-то упущенное.
РЕДАКТИРОВАТЬ: добавлен щадящий несколько байтов во входном файле в разделе спекуляции, теперь также предоставляет довольно хорошую «историческую» причину.
В любом случае, основным моментом при составлении списка были примеры возможных объяснений, которые не слишком историчны, но все еще актуальны.
Конечно, я не уверен, но я думаю, что просьба о «исторической» причине, отличной от личного вкуса, начинается с предположения, что это не всегда так.
Для C
Давайте посмотрим на Керниган & Ричи оригинальное обоснование (оригинал К&Р стр. 42 и 43):
Необычный аспект заключается в том, что ++ и — могут использоваться как префикс или
как постфикс (…) В контексте, где значение не требуется (..) выберите
префикс или постфикс по вкусу. Но есть ситуации, когда
один или другой специально призван.
Текст продолжается некоторыми примерами, которые используют приращения в индексе, с явной целью написания «более компактный«код. Таким образом, причина этих операторов заключается в удобстве более компактного кода.
Три приведенных примера (squeeze()
, getline()
а также strcat()
) использовать только постфикс в выражениях с использованием индексации. Авторы сравнивают код с более длинной версией, которая не использует встроенные приращения. Это подтверждает, что основное внимание уделяется компактности.
К&R выделите на стр. 102 использование этих операторов в сочетании с разыменованием указателя (например, *--p
а также *p--
). Дальнейшего примера не приводится, но опять же, они ясно дают понять, что выгода заключается в компактности.
Для C ++
Бьярне Страуструпу хотелось совместимости с C, поэтому C ++ унаследовал префикс и постфиксный инкремент и декремент.
Но это еще не все: в его книгеДизайн и эволюция C ++«, Страуструп объясняет, что изначально он планировал иметь только одну перегрузку для обоих, postfix и prefix, в пользовательских классах:
Несколько человек, особенно Брайан Керниган, указали, что это
ограничение было неестественным с точки зрения C и мешало пользователям
от определения класса, который может быть использован в качестве замены для
обычный указатель
Что заставило его найти текущую разницу в сигнатурах, чтобы различать префикс и постфикс.
Кстати, без этих операторов C ++ был бы не C ++, а C_plus_1 😉
Рассмотрим следующий цикл:
for(uint i=5; i-- > 0;)
{
//do something with i,
// e.g. call a function that _requires_ an unsigned parameter.
}
Вы не можете повторить этот цикл с операцией предварительного декремента, не перемещая операцию декремента за пределы конструкции for (…), и просто лучше иметь инициализацию, целочисленные значения и проверить все в одном месте.
Гораздо большая проблема заключается в следующем: можно перегрузить операторы приращения (все 4) для класса. Но тогда операторы критически отличаются: операторы post обычно приводят к временной копии создаваемого экземпляра класса, в отличие от предварительных операторов. Это огромная разница в семантике.
У PDP-11 была одна инструкция, которая соответствовала *p++
и еще один для *--p
(или, возможно, наоборот).