Может кто-нибудь показать пример, где этот приоритет имеет значение? - PullRequest
4 голосов
/ 12 июня 2019

Я читаю старшинство и ассоциативность. В таблице я наблюдал эти две вещи -

(i) приоритет операции увеличения (или уменьшения) постфикса больше, чем приоритет операции увеличения (или дек.) Префикса.

(ii) Ассоциативность оператора постфикса inc. (Или dec.) Слева направо, а оператора приращения префикса (или dec.) Справа налево.

Я не уверен, зачем это нужно. Может ли кто-нибудь помочь мне, показывая код (отдельно для каждого случая), который показывает необходимость этих двух фактов? Спасибо.

Я пытался думать о случаях, но не получал таких (поскольку я очень плохо знаком с программированием).

Ответы [ 5 ]

4 голосов
/ 12 июня 2019

Это необходимо для выражения типа

data = *pointer++;

Вы получите значение по указателю в данных, а затем увеличите его до следующего элемента. Если бы приоритет постфикса не был больше, вы бы получили увеличенное значение в данных.

А ассоциативность префиксного оператора справа налево, потому что вам нужно выражение типа

data = **pointer_to_pointer;

будет оцениваться справа налево, как если бы вы написали

data = *(*pointer_to_pointer);
1 голос
/ 12 июня 2019

В C, как и в большинстве языков, постфиксные операторы связываются более тесно, чем префиксные операторы, а префиксные операторы связываются более тесно, чем бинарные операторы.(Есть исключения из второй части, но не на C.) Это обычно соответствует интуиции о значении выражений.

Например, почти все ожидают, что значение

-a[0]

«отрицательный элемент 0 массива a», а не «элемент 0 массива -a», особенно в таких языках, как C, где «массив -a» не имеет смысла.Аналогичным образом,

-a++

имеет ожидаемое значение «отрицательное значение текущего значения a, которое впоследствии увеличивается».Опять же, увеличение -a не имеет смысла, поскольку -a не является переменной.

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

Таким образом, префиксы и постфиксы ++ и -- имеют синтаксис, определенный этим общим правилом.Тем не менее, по крайней мере, в C ошибочно применять оба к одному и тому же операнду (++a--), потому что значение, возвращаемое версиями до и после исправления, не является lvalue, в то время как операнд долженбыть lvalue.В этом смысле конкретный случай сравнения приоритетов префикса и постфикса ++ и -- никогда не обнаруживается в правильной программе.Но другие комбинации префиксных и постфиксных операторов делают, и уровни приоритета должны применяться однородно.

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

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

1 голос
/ 12 июня 2019

Приоритет и ассоциативность операторов выпадают из грамматики языка. Для постфиксных и унарных операторов это выглядит следующим образом:

<em>postfix-expression</em>:
    <em>primary-expression</em>
    <em>postfix-expression</em> [ <em>expression</em> ]
    <em>postfix-expression</em> ( <em>argument-expression-list<sub>opt</sub></em> )
    <em>postfix-expression</em> . <em>identifier</em>
    <em>postfix-expression</em> -> <em>identifier</em>
    <em>postfix-expression</em> ++
    <em>postfix-expression</em> --
    ( <em>type-name</em> ) { <em>initializer-list</em> }
    ( <em>type-name</em> ) { <em>initializer-list</em> , }

<em>unary-expression</em>:
    <em>postfix-expression</em>
    ++ <em>unary-expression</em>
    -- <em>unary-expression</em>
    <em>unary-operator</em> <em>cast-expression</em>
    sizeof <em>unary-expression</em>
    sizeof ( <em>type-name</em> )
    _Alignof ( <em>type-name</em> )

<em>unary-operator</em>: one of
    & * + - ~ !

<em>cast-expression</em>:
    <em>unary-expression</em>
    ( <em>type-name</em> ) <em>cast-expression</em>

C Онлайн-проект 2011 года , Приложение A.2 Грамматика фазовой структуры

Итак, как это определяет приоритет и ассоциативность, и почему это имеет значение? Давайте начнем с выражения типа *p++ - мы разыменовываем p++ или увеличиваем *p? Это две совершенно разные операции, поэтому важно, как устроена грамматика. Давайте проследим через это:

        *            p            ++
        |            |            |
        |         primary         |
        |        expression       |
        |            |            |
        |         postfix         |
        |        expression       |
        |            |            |
        |            +------+-----+
        |                   |
        |                postfix
        |               expression
        |                   |
        |                 unary
        |               expression
        |                   |
      unary                cast
     operator           expression
        |                   |
        +---------+---------+
                  |
                unary
              expression

На английском:

  1. <em>unary-expression</em> производит <em>unary-operator cast-expression</em>
  2. <em>unary-operator</em> производит *
  3. <em>cast-expression</em> производит <em>unary-expression</em>
  4. <em>unary-expression</em> производит <em>postfix-expression</em>
  5. <em>postfix-expression</em> производит <em>postfix-expression</em> ++
  6. <em>postfix-expression</em> производит <em>primary-expression</em>
  7. <em>primary-expression</em> производит p

Это означает, что выражение *p++ анализируется как *(p++) - оператор * будет применен к результату из p++.

То же самое для *p[i] - мы будем разыменовывать указатель на p[i] вместо подписки *p.

Для немного более сложного примера, который входит в ассоциативность, давайте посмотрим на оператор выбора члена ->, как в выражении foo->bar->bletch->blurga. Грамматика для оператора выбора элемента ->:

<em>postfix-expression</em> -> <em>identifier</em>

Это говорит нам о том, что foo->bar->bletch уменьшается до postfix-expression, а blurga уменьшается до identifier. Следовательно, ассоциативность оператора справа налево, и выражение анализируется как ((foo->bar)->blurga)->bletch, а не foo->(bar->(blurga->bletch)).

Те таблицы, которые вы просматриваете, представляют собой сводку настроек грамматики. Грамматика настроена так, что группировки операторов и операндов несколько интуитивны. Вы ожидаете, что выражение типа ++foo.bar[i] будет увеличивать foo.bar[i], вы ожидаете, что *f() разыменует значение указателя, возвращенное функцией, и т. Д.

1 голос
/ 12 июня 2019

С исторической точки зрения на приоритет операторов влиял B and BCPL programming languages. В статье The Development of the C Language Деннис Ричи объясняет, как он выбрал приоритет.

Случайность синтаксиса способствовала воспринимаемой сложности языка. Оператор косвенного обращения, записанный * в C, является синтаксически унарным префиксным оператором , как в BCPL и B . Это хорошо работает в простых выражениях, но в более сложных случаях для управления синтаксическим анализом требуются скобки. Например, чтобы отличить косвенность по значению, возвращаемому функцией от вызова функции, обозначенной указателем, пишется * fp () и (* pf) () соответственно

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

1 голос
/ 12 июня 2019

(i) приоритет постфиксного оператора больше, чем приоритет префиксного оператора.

Рассмотрим код:

int x[] = {1,2,3};
int *p = x;
int y;
y = ++p[0]; 

Это увеличит первый элементx и присвойте его y.

по сравнению с этим, где мы явно изменим приоритет, чтобы префикс ++ получил более высокий:

y = (++p)[0];

Это будет не увеличивает x элементов, но переместит p ко второму элементу x и присвоит ему y.

(ii) левая ассоциативность оператора постфиксасправа, но префиксный оператор справа налево.

Это означает, что: p->x->y следует читать как (p->x)->y, а не p->(x->y), что даже не делаетсмысл.

То же самое для других операторов группы postfix - ассоциативность RTL для них просто не имеет смысла: p[x][y][z] - это то же самое, что ((p[x])[y])[z], но p[x]([y]([z])) бессмысленно (и незаконно).

...