Каковы исторические причины, по которым языки C имеют преинкременты и постинкременты?

(Примечание: я не спрашиваю об определениях перед инкрементом или после инкремента или о том, как они используются в 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

10

Решение

Увеличение и уменьшение на 1 в то время широко поддерживалось в аппаратном обеспечении: один код операции и быстро. Это потому, что «увеличение на 1» и «уменьшение на 1» были очень распространенной операцией в коде (верно по сей день).

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

i++;

концепция «до / после» не используется (и поэтому ++i;), но в

printf ("%d", ++i);

это. Это различие столь же важно в наше время, как и когда разрабатывался язык C (эта идиома была скопирована с его предшественника, названного «B»).

От Развитие языка Си

Эта особенность [ячейки памяти «автоинкремента» PDP-7], вероятно, предлагала такие операторы Томпсону [Кен Томпсон, который разработал «B», предшественник C]; обобщение, чтобы сделать их префиксом и постфиксом, было его собственным. Действительно, ячейки с автоинкрементом не использовались непосредственно при реализации операторов, и более сильной мотивацией для нововведения было, вероятно, его наблюдение, что перевод ++ x был меньше, чем перевод x = x + 1.

Спасибо @dyp за упоминание этого документа.

10

Другие решения

Когда вы отсчитываете от 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.

5

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

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

  • лень / сохранение пространства:

    Вы могли бы сохранить несколько нажатий клавиш / байтов во входном файле, используя соответствующую версию в таких конструкциях, как while(--i) против while(i--), (посмотрите на ответ pmg, чтобы понять, почему оба имеют значение, если вы не видели его в первом запуске)

  • эстетика

    По причинам симметрии, имеющим только одну версию, либо пре-, либо постинкремент / декремент, может быть что-то упущенное.

РЕДАКТИРОВАТЬ: добавлен щадящий несколько байтов во входном файле в разделе спекуляции, теперь также предоставляет довольно хорошую «историческую» причину.

В любом случае, основным моментом при составлении списка были примеры возможных объяснений, которые не слишком историчны, но все еще актуальны.

Конечно, я не уверен, но я думаю, что просьба о «исторической» причине, отличной от личного вкуса, начинается с предположения, что это не всегда так.

2

Для C

Давайте посмотрим на Керниган & Ричи оригинальное обоснование (оригинал К&Р стр. 42 и 43):

Необычный аспект заключается в том, что ++ и — могут использоваться как префикс или
как постфикс (…) В контексте, где значение не требуется (..) выберите
префикс или постфикс по вкусу. Но есть ситуации, когда
один или другой специально призван.

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

Три приведенных примера (squeeze(), getline() а также strcat() ) использовать только постфикс в выражениях с использованием индексации. Авторы сравнивают код с более длинной версией, которая не использует встроенные приращения. Это подтверждает, что основное внимание уделяется компактности.

К&R выделите на стр. 102 использование этих операторов в сочетании с разыменованием указателя (например, *--p а также *p--). Дальнейшего примера не приводится, но опять же, они ясно дают понять, что выгода заключается в компактности.

Для C ++

Бьярне Страуструпу хотелось совместимости с C, поэтому C ++ унаследовал префикс и постфиксный инкремент и декремент.

Но это еще не все: в его книгеДизайн и эволюция C ++«, Страуструп объясняет, что изначально он планировал иметь только одну перегрузку для обоих, postfix и prefix, в пользовательских классах:

Несколько человек, особенно Брайан Керниган, указали, что это
ограничение было неестественным с точки зрения C и мешало пользователям
от определения класса, который может быть использован в качестве замены для
обычный указатель

Что заставило его найти текущую разницу в сигнатурах, чтобы различать префикс и постфикс.

Кстати, без этих операторов C ++ был бы не C ++, а C_plus_1 😉

2

Рассмотрим следующий цикл:

for(uint i=5; i-- > 0;)
{
//do something with i,
// e.g. call a function that _requires_ an unsigned parameter.
}

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

Гораздо большая проблема заключается в следующем: можно перегрузить операторы приращения (все 4) для класса. Но тогда операторы критически отличаются: операторы post обычно приводят к временной копии создаваемого экземпляра класса, в отличие от предварительных операторов. Это огромная разница в семантике.

1

У PDP-11 была одна инструкция, которая соответствовала *p++и еще один для *--p (или, возможно, наоборот).

0
По вопросам рекламы [email protected]