Каковы исторические причины Языки C имеют предварительные приращения и пост-приращения?

(Примечание: я не спрашиваю об определениях pre-increment vs. post-increment или о том, как они используются в C/С++. Поэтому я не думаю, что это дублированный вопрос.)

Разработчики C (Dennis Ritchie и др.) создали приращения и сокращения операторов по очень веским причинам. Я не понимаю, почему они решили создать различие пре-инкрементов/декрементов?

Я считаю, что эти операторы были гораздо полезнее, когда C разрабатывался, чем сегодня. Большинство программистов на C/С++ используют одно или другое, а программисты из других языков находят различие сегодня странным и запутанным (NB: это основано исключительно на анекдотических доказательствах).

Почему они решили это сделать, и что изменилось при вычислении, что это различие сегодня не так полезно?

Для записи разница между двумя может быть видна в коде С++:

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" и "уменьшение на 1" были очень распространенной операцией в коде (истинно по сей день).

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

i++;

понятие "до/после" не используется (и поэтому оно делает то же самое, что и ++i;), но в

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

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

От Разработка языка C

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

Благодаря @dyp для упоминания этого документа.

Ответ 2

При обратном отсчете от 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.

Ответ 3

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

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

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

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

  • Эстетика

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

EDIT: добавлено сохранение нескольких байтов во входном файле в разделе спекуляции, предоставляя также довольно приятную "историческую" причину.

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

Конечно, я не уверен, но я думаю, что просить "историческую" причину, отличную от личного вкуса, исходит из предположения, которое не обязательно верно.

Ответ 4

Для C

Посмотрите на оригинальное обоснование Кернигана и Ричи (оригинальные K & R стр. 42 и 43):

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

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

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

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

Для С++

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

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

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

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

Кстати, без этих операторов С++ не будет С++, но C_plus_1;-)

Ответ 5

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

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

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

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

Ответ 6

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