Virgül operatörü ne yapıyor?


167

,Operatör C'de ne yapar ?



1
Cevabımda belirttiğim gibi, sol operandın değerlendirilmesinden sonra bir sıralama noktası var. Bu, sadece gramer olan bir işlev çağrısındaki virgülden farklıdır.
Shafik Yaghmour

2
@SergeyK. - Bunun birbirinden yıllar önce sorulduğu ve cevaplandığı göz önüne alındığında, diğerinin bu sorunun bir kopyası olması daha olasıdır. Bununla birlikte, diğeri de c ve c ++ ile çift etiketlidir , bu da bir sıkıntıdır. Bu, iyi cevapları olan sadece C için bir Soru-Cevap.
Jonathan Leffler

Yanıtlar:


129

İfade:

(expression1,  expression2)

İlk ifade1 değerlendirilir, daha sonra ifade2 değerlendirilir ve tüm ifade için ifade2 değeri döndürülür.


3
i = (5,4,3,2,1,0) yazarsam ideal olarak 0 döndürmelidir, doğru mu? ama ben 5 değeri atanıyor? Nerede yanlış yaptığımı anlamama yardım eder misiniz?
Jayesh

19
@James: Virgül işleminin değeri her zaman son ifadenin değeri olur. Hiçbir noktada i5, 4, 3, 2 veya 1 değerlerine sahip olmayacaktır. Basitçe 0'dır. İfadelerin yan etkileri olmadığı sürece pratik olarak işe yaramaz.
Jeff Mercado

6
Not virgül ifade LHS'nin değerlendirilmesi ve RHS değerlendirilmesi arasında bir tam dizi noktası vardır (bakınız Shafik Yaghmour sitesindeki yanıt C99 standart bir alıntı için). Bu, virgül operatörünün önemli bir özelliğidir.
Jonathan Leffler

5
i = b, c;eşdeğerdir (i = b), catama nedeniyle nedeniyle =virgül operatörü daha yüksek önceliğe sahiptir ,. Virgül operatörü en düşük önceliğe sahiptir.
siklaminist

1
Parantezlerin iki açıdan yanıltıcı olduğundan endişe ediyorum: (1) gerekli değildir - virgül operatörünün parantez içine alınması gerekmez; ve (2) bir işlev çağrısının argüman listesindeki parantezlerle karıştırılabilirler - ancak argüman listesindeki virgül virgül operatörü değildir. Ancak, bunu düzeltmek tamamen önemsiz değildir. Belki: ifadede: expression1, expression2;ilk expression1olarak, muhtemelen yan etkileri (bir işlevi çağırmak gibi) expression2için değerlendirilir , daha sonra bir dizi noktası vardır, daha sonra değerlendirilir ve değer döndürülür ...
Jonathan Leffler

119

En çok whiledöngülerde kullanıldığını gördüm :

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

İşlemi yapacak, sonra yan etkiye dayalı bir test yapacaktır. Diğer yol bunu böyle yapmak olurdu:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}

21
Hey, bu çok şık! Sık sık bu sorunu çözmek için alışılmadık şeyler yapmak zorunda kaldım.
staticsan

6
Eğer böyle bir şey yaparsanız muhtemelen daha az belirsiz ve daha okunabilir olurdu while (read_string(s) && s.len() > 5). Açıkçası read_stringbir dönüş değeri yoksa (veya anlamlı bir değeri yoksa) bu işe yaramaz . (Düzenle: Üzgünüz, bu
yazının

11
@staticsan Vücuttaki while (1)bir break;ifadeyle kullanmaktan korkmayın . Kodun kırılma kısmını while testine veya down-while testine zorlamaya çalışmak genellikle enerji israfıdır ve kodun anlaşılmasını zorlaştırır.
potrzebie

8
@jamesdlin ... ve insanlar hala okuyor. Söyleyecek faydalı bir şeyin varsa söyle. Konular genellikle son gönderinin tarihine göre sıralandığından, forumlarda yeniden yayınlanan konularla ilgili sorunlar var. StackOverflow'un böyle sorunları yok.
Dimitar Slavchev

3
@potrzebie Virgül yaklaşımını çok daha iyi seviyorum while(1)ve break;
Michael

39

Virgül operatörü sol işlenen değerlendirecek, sonuç atmak ve sonra sağ işlenen değerlendirir ve o sonuç olacaktır. Bağlantıda belirtildiği gibi deyimsel kullanım, bir fordöngüde kullanılan değişkenlerin başlatılmasıdır ve aşağıdaki örneği verir:

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

Aksi takdirde değil çok var harika kullanımları virgül operatörü okumak ve korumak zor kodu oluşturmak üzere kötüye kolay olmasına rağmen,.

Gönderen taslak C99 standardında aşağıdaki gibi dilbilgisi geçerli:

expression:
  assignment-expression
  expression , assignment-expression

ve paragraf 2 diyor ki:

Virgülle operatör sol işlenen bir boşluk ifadesi olarak değerlendirilir; değerlendirilmesinden sonra bir sıralama noktası vardır. Sonra doğru işlenen değerlendirilir; sonucun türü ve değeri vardır. 97) Bir virgül operatörünün sonucunu değiştirme veya bir sonraki sıra noktasından sonra erişme girişiminde bulunulursa, davranış tanımsızdır.

Dipnot 97 diyor ki:

Bir virgül operatörü etmez bir lvalue vermeyecektir .

Bu, virgül operatörünün sonucuna atayamayacağınız anlamına gelir .

Virgül operatörünün en düşük önceliğe sahip olduğunu ve bu nedenle kullanımın ()büyük bir fark yaratabileceği durumlar olduğunu belirtmek önemlidir , örneğin:

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

aşağıdaki çıktıya sahip olacaktır:

1 4

28

Virgül operatörü, iki ifadeyi her iki tarafı da bir araya getirir ve her ikisini de soldan sağa doğru değerlendirir. Sağ tarafın değeri, tüm ifadenin değeri olarak döndürülür. (expr1, expr2)gibidir { expr1; expr2; }ancak sonucunu expr2bir işlev çağrısında veya atamada kullanabilirsiniz.

Genellikle fordöngülerde, bunun gibi birden çok değişkeni başlatmak veya korumak için görülür :

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

Bunun dışında, her zaman bir makroda bir araya gelmesi gereken iki işlemi tamamlarken, sadece başka bir durumda "öfkeyle" kullandım. Bir ağda göndermek için çeşitli ikili değerleri bir bayt arabelleğine kopyalayan kodumuz vardı ve bir işaretçi, ulaştığımız yerde korunuyordu:

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

Değerler shorts veya ints olduğunda bunu yaptık:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

Daha sonra bunun gerçekten geçerli bir C olmadığını okuduk, çünkü (short *)ptro zamanlar derleyici umursamamasına rağmen artık bir l değeri değil ve artırılamıyor. Bunu düzeltmek için ifadeyi ikiye ayırdık:

*(short *)ptr = short_value;
ptr += sizeof(short);

Bununla birlikte, bu yaklaşım her iki ifadeyi de her zaman koymayı hatırlayan tüm geliştiricilere dayanıyordu. Çıktı işaretçisini, değeri ve değerin türünü iletebileceğiniz bir işlev istedik. Bu şablonlarla C ++ değil, keyfi bir tür almak için bir fonksiyonumuz olamazdı, bu yüzden bir makroya yerleştik:

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

Virgül operatörünü kullanarak bunu ifadelerde veya istediğimiz gibi ifadelerde kullanabildik:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

Bu örneklerden herhangi birinin iyi bir stil olduğunu önermiyorum! Nitekim, Steve McConnell hatırlamak görünüyor Kod tamamlayın bile virgülle operatörleri kullanarak karşı danışmanlık fordöngü: okunabilmesi ve bakımı için, döngü yalnızca bir değişken tarafından kontrol edilmelidir ve ifadeler forkendisi sadece döngü kontrol kodu içermelidir hattı, diğer başlatma veya döngü bakım bitleri değil.


Teşekkürler! StackOverflow'daki ilk cevabımdı: o zamandan beri belki de özlüğe değer verileceğini öğrendim :-).
Paul Stephenson

Bazen burada bir çözümün evrimini (oraya nasıl ulaştığınızı) açıkladığınız gibi biraz ayrıntıya değer veriyorum.
yeşil diod

8

Birden çok ifadenin değerlendirilmesine neden olur, ancak sonuç olarak sadece sonuncuyu kullanır (değer, bence).

Yani...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

x değeri 8 olarak ayarlanmalıdır.


3

Daha önceki yanıtların belirttiği gibi, tüm ifadeleri değerlendirir, ancak son ifadeyi ifadenin değeri olarak kullanır. Şahsen ben sadece döngü ifadelerinde yararlı buldum:

for (tmp=0, i = MAX; i > 0; i--)

2

Yararlı olduğunu gördüğüm tek yer, ifadelerden birinde (muhtemelen init ifadesi veya döngü ifadesi) birden çok şey yapmak istediğiniz bir funky döngü yazmanızdır.

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

Herhangi bir sözdizimi hatası varsa veya katı C olmayan bir şeyle karışırsam affedersiniz, operatörün iyi bir form olduğunu iddia etmiyorum, ama bunun için kullanabilirsiniz. Yukarıdaki durumda, whilebunun yerine muhtemelen bir döngü kullanacağım, böylece init ve loop üzerindeki çoklu ifadeler daha açık olacaktır. (Ve ben i1 ve i2 satır içi bildirmek ve sonra başlatmak yerine .... başlatmak filan filan filan.)


Demek istediğim i1 = 0, i2 = boyut -1
frankster

-2

@Rajesh ve @JeffMercado'dan gelen soruları ele almak için bunu canlandırıyorum, çünkü bu en iyi arama motoru hitlerinden biri.

Örneğin aşağıdaki kod snippet'ini ele alalım

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

Yazdırılacak

1 5

iEn cevapları tarafından açıklandığı gibi durum ele alınır. Tüm ifadeler soldan sağa sırada değerlendirilir, ancak yalnızca sonuncusu atanır i. ( İfadenin sonucu ) is1`.

jÇünkü durum farklı öncelik kuralları aşağıdaki ,en düşük operatör önceliği vardır. Bu kurallar nedeniyle derleyici atama-ifade, sabit, sabit ... görür . İfadeleri tekrar düzene-sağ-sol ve bunların yan etkileri görülebilir kalmak nedenle değerlendirilir jolduğu 5bir sonucu olarak j = 5.

Interstingly, int j = 5,4,3,2,1;dil spec tarafından izin verilmiyor. Bir başlatıcı atama ifadesini bekler, böylece doğrudan bir ,operatöre izin verilmez.

Bu yardımcı olur umarım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.