Jak działa operator przecinka


175

Jak działa operator przecinka w C ++?

Na przykład, jeśli to zrobię:

a = b, c;  

Czy koniec równa się b lub c?

(Tak, wiem, że jest to łatwe do przetestowania - wystarczy udokumentować tutaj, aby ktoś mógł szybko znaleźć odpowiedź).

Aktualizacja: to pytanie ujawniło niuans podczas korzystania z operatora przecinka. Żeby to udokumentować:

a = b, c;    // a is set to the value of b!

a = (b, c);  // a is set to the value of c!

To pytanie zostało zainspirowane literówką w kodzie. Co miało być

a = b;
c = d;

Zmienił się w

a = b,    //  <-  Note comma typo!
c = d;

Przeczytaj więcej na ten temat tutaj. stackoverflow.com/questions/12824378/…
Coding Mash

1
Możliwy duplikat Co robi operator przecinka `,` w C? . To pokonało cię o jeden dzień. A odpowiedź Lillqa stanowi odpowiedź na pytanie o a = (b, c);.
jww

5
Ale w tym przypadku a = b, c = d;faktycznie działa tak, jak zamierzono a = b; c = d;?
Bondolin

@NargothBond Niekoniecznie. Jeśli bi dsą wartościami funkcji, które używają (i modyfikują) wspólny stan, kolejność wykonywania nie jest zdefiniowana do C++17.
nyronium

Odpowiedzi:



129

Zwróć uwagę, że operator przecinka może być przeciążony w C ++. Faktyczne zachowanie może zatem bardzo różnić się od oczekiwanego.

Na przykład Boost.Spirit używa operatora przecinka całkiem sprytnie do zaimplementowania inicjatorów list dla tabel symboli. Dzięki temu następująca składnia jest możliwa i znacząca:

keywords = "and", "or", "not", "xor";

Zauważ, że ze względu na pierwszeństwo operatorów kod jest (celowo!) Identyczny z

(((keywords = "and"), "or"), "not"), "xor";

Oznacza to, że pierwszy wywołany operator keywords.operator =("and")zwraca obiekt proxy, na którym operator,wywoływane są pozostałe s:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");

Umm, nie możesz jednak zmienić pierwszeństwa, co oznacza, że ​​prawdopodobnie powinieneś umieścić swoją listę w nawiasach.
Jeff Burdges

18
@Jeff Wręcz przeciwnie. W przypadku nawiasów wokół listy to nie zadziała, ponieważ wtedy kompilator widzi tylko operator przecinka między dwoma char[], którego nie można przeciążać. Kod celowo najpierw wywołuje, operator=a następnie operator,dla każdego pozostałego elementu.
Konrad Rudolph

125

Operator przecinka ma najniższy priorytet ze wszystkich operatorów C / C ++. Dlatego zawsze jest to ostatnie, które wiąże się z wyrażeniem, co oznacza:

a = b, c;

jest równa:

(a = b), c;

Innym interesującym faktem jest to, że operator przecinka wprowadza punkt sekwencji . Oznacza to, że wyrażenie:

a+b, c(), d

gwarantuje ze swoich trzech podwyrażeń ( a + b , c () i d ) oceniano w kolejności. Jest to istotne, jeśli mają skutki uboczne. Zwykle kompilatory mogą oceniać podwyrażenia w dowolnej kolejności; na przykład w wywołaniu funkcji:

someFunc(arg1, arg2, arg3)

argumenty można oceniać w dowolnej kolejności. Zauważ, że przecinki w wywołaniu funkcji nie są operatorami; są separatorami.


15
Warto zaznaczyć, że ,ma tak niski priorytet, że nawet pozostaje w tyle za sobą ;) ... To znaczy: przecinek-jako- operator ma niższy priorytet niż przecinek-jako- separator . Tak więc, jeśli chcesz użyć operatora przecinka jako operatora w pojedynczym argumencie funkcji, przypisaniu zmiennej lub innej liście oddzielonej przecinkami - musisz użyć nawiasów, np .:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
podkreślenie_d

68

Operator przecinka:

  • ma najniższy priorytet
  • jest lewostronny

Domyślna wersja operatora przecinka jest zdefiniowana dla wszystkich typów (wbudowanych i niestandardowych) i działa w następujący sposób - biorąc pod uwagę exprA , exprB:

  • exprA jest oceniany
  • wynik exprAjest ignorowany
  • exprB jest oceniany
  • wynik funkcji exprBjest zwracany jako wynik całego wyrażenia

W przypadku większości operatorów kompilator może wybrać kolejność wykonywania, a nawet wymagane jest, aby pominąć wykonanie, jeśli nie wpływa to na końcowy wynik (np. false && foo()Pominie wywołanie foo). Nie dotyczy to jednak operatora przecinka i powyższe kroki zawsze będą miały miejsce * .

W praktyce domyślny operator przecinka działa prawie tak samo jak średnik. Różnica polega na tym, że dwa wyrażenia oddzielone średnikiem tworzą dwie oddzielne instrukcje, podczas gdy separacja przecinkami zachowuje wszystko jako jedno wyrażenie. Z tego powodu operator przecinka jest czasami używany w następujących scenariuszach:

  • Składnia C wymaga pojedynczego wyrażenia , a nie instrukcji. np. wif( HERE )
  • Składnia C wymaga jednej instrukcji, a nie więcej, np. Przy inicjalizacji forpętlifor ( HERE ; ; )
  • Jeśli chcesz pominąć nawiasy klamrowe i zachować jedno stwierdzenie: if (foo) HERE ;(nie rób tego, to naprawdę brzydkie!)

Jeśli instrukcja nie jest wyrażeniem, średnika nie można zastąpić przecinkiem. Na przykład te są niedozwolone:

  • (foo, if (foo) bar)( ifnie jest wyrażeniem)
  • int x, int y (deklaracja zmiennej nie jest wyrażeniem)

W Twoim przypadku mamy:

  • a=b, c;, odpowiednik a=b; c;, zakładając, że ajest to typ, który nie przeciąża operatora przecinka.
  • a = b, c = d;odpowiednik a=b; c=d;, zakładając, że ajest to typ, który nie przeciąża operatora przecinka.

Zwróć uwagę, że nie każdy przecinek jest w rzeczywistości operatorem przecinka. Kilka przecinków, które mają zupełnie inne znaczenie:

  • int a, b; --- lista deklaracji zmiennych jest oddzielona przecinkami, ale nie są to operatory przecinkowe
  • int a=5, b=3; --- jest to również lista deklaracji zmiennych oddzielonych przecinkami
  • foo(x,y)--- lista argumentów oddzielonych przecinkami. W rzeczywistości xi ymożna je oceniać w dowolnej kolejności!
  • FOO(x,y) --- rozdzielona przecinkami lista argumentów makr
  • foo<a,b> --- oddzielona przecinkami lista argumentów szablonów
  • int foo(int a, int b) --- lista parametrów oddzielonych przecinkami
  • Foo::Foo() : a(5), b(3) {} --- oddzielona przecinkami lista inicjalizacyjna w konstruktorze klasy

* Nie jest to do końca prawdą, jeśli zastosujesz optymalizacje. Jeśli kompilator rozpozna, że ​​określony fragment kodu nie ma absolutnie żadnego wpływu na resztę, usunie niepotrzebne instrukcje.

Więcej informacji: http://en.wikipedia.org/wiki/Comma_operator


Czy warto zauważyć, że jeśli operator ,jest przeciążony, tracisz jakiekolwiek gwarancje asocjatywności (tak jak tracisz właściwości zwarciowe operator&&i operator||jeśli są przeciążone)?
YoungJohn,

Operator przecinka jest lewostronny, niezależnie od tego, czy jest przeciążony, czy nie. Wyrażenie a, b, czawsze znaczy (a, b), ci nigdy a, (b, c). Ta ostatnia interpretacja może nawet prowadzić do błędu kompilacji, jeśli elementy są różnych typów. To, czego możesz chcieć, to kolejność oceny argumentów? Nie jestem tego pewien, ale być może masz rację: może się zdarzyć, że czostanie to ocenione wcześniej, (a, b) nawet jeśli przecinek jest lewostronny.
CygnusX1

1
Tylko niewielki komentarz do rozdzielanej przecinkami listy inicjalizacyjnej w konstruktorze klas, kolejność nie jest określana przez pozycję na liście. O kolejności decyduje pozycja deklaracji klasy. Np. struct Foo { Foo() : a(5), b(3) {} int b; int a; }Ewaluuje b(3)wcześniej a(5). Jest to ważne, jeśli lista jest tak: Foo() : a(5), b(a) {}. b nie będzie ustawione na 5, ale raczej niezainicjowaną wartość a, o której kompilator może ostrzegać lub nie.
Jonathan Gawrych

Niedawno natknąłem się na operator przecinka z dwoma liczbami zmiennoprzecinkowymi, jaki jest sens obliczania i odrzucania liczby?
Aaron Franke,

Myślę, że nikt nie może na to odpowiedzieć. Musiałbyś to pokazać w kontekście. Prawdopodobnie osobne pytanie?
CygnusX1,

38

Wartość abędzie b, ale wartość wyrażenia będzie c. To jest w

d = (a = b, c);

a byłby równy bi dbyłby równy c.


19
Prawie poprawne. Instrukcje nie mają wartości, a wyrażenia mają. Wartość tego wyrażenia to c.
Leon Timmermans,

Dlaczego jest używany zamiast a = b; d = c;?
Aaron Franke,

To pozwoliło mi zrozumieć, o jakich skutkach ubocznych mówią ludzie.
sznurowadło


2

Wartość a będzie równa b, ponieważ operator przecinka ma niższy priorytet niż operator przypisania.


2

Tak Operator przecinka ma niski priorytet niż operator przypisania

#include<stdio.h>
int main()
{
          int i;
          i = (1,2,3);
          printf("i:%d\n",i);
          return 0;
}

Wynik: i = 3
Ponieważ operator przecinka zawsze zwraca wartość najbardziej po prawej stronie.
W przypadku operatora przecinka z operatorem przypisania:

 int main()
{
      int i;
      i = 1,2,3;
      printf("i:%d\n",i);
      return 0;
}

Ouput: i = 1
Jak wiemy, operator przecinka ma niższy priorytet niż przypisanie .....


Więc czym różni się drugi przykład od tego, który znajduje się i = 1;w tym wierszu?
Aaron Franke,

-3

Po pierwsze: przecinek w rzeczywistości nie jest operatorem, dla kompilatora jest po prostu tokenem, który ma znaczenie w kontekście innych tokenów.

Co to oznacza i po co się przejmować?

Przykład 1:

Aby zrozumieć różnicę między znaczeniem tego samego tokena w innym kontekście, spójrzmy na ten przykład:

class Example {
   Foo<int, char*> ContentA;
}

Zwykle C ++ Początkujący pomyśli, że to wyrażenie nie mogli / byłoby porównać rzeczy, ale to jest absolutnie źle, rozumieniu <, >a ,żetony depent od kontekstu użycia.

Prawidłowa interpretacja powyższego przykładu jest oczywiście taka, że ​​jest to wstawienie szablonu.

Przykład 2:

Kiedy piszemy typową pętlę for z więcej niż jedną zmienną inicjalizacyjną i / lub więcej niż jednym wyrażeniem, które należy wykonać po każdej iteracji pętli, również używamy przecinka:

for(a=5,b=0;a<42;a++,b--)
   ...

Znaczenie przecinka zależy od kontekstu użycia, tutaj jest to kontekst forkonstrukcji.

Co właściwie oznacza przecinek w kontekście?

Aby to jeszcze bardziej skomplikować (jak zawsze w C ++), sam operator przecinka może zostać przeciążony (dzięki Konradowi Rudolphowi za wskazanie tego).

Wracając do pytania, Kodeks

a = b, c;

oznacza coś w rodzaju kompilatora

(a = b), c;

ponieważ pierwszeństwo z =tokena / podmiotu jest wyższy niż priorytet z ,tokena.

i jest to interpretowane w kontekście

a = b;
c;

(zwróć uwagę, że interpretacja zależy od kontekstu, tutaj nie jest to ani wywołanie funkcji / metody, ani instancja szablonu).


1
jasne

2
Ponieważ pracuje się z operatorem, (sic), przecinek jest rzeczywiście operatorem.
DragonLord,

2
O ile rozpoznanie, czy dany token przecinka jest rozpoznawany jako operator przecinka (w przeciwieństwie np. Do separatora argumentów) może samo w sobie stanowić wyzwanie, to pytanie to dotyczy konkretnie operatora przecinka .
CygnusX1
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.