İ ++ ve ++ i arasındaki fark nedir?


204

Hem C # kodunun sayısız parçalara kullanılıyor onları gördüm ve ben ne zaman kullanılacağını bilmek istiyorum i++veya ++i( igibi bir sayı değişken olmak int, float, double, vs). Bunu bilen var mı?


13
Hiçbir fark yaratmadığı durumlar dışında hiçbirini kullanmamalısınız, çünkü bu sadece insanların şu anda sorduğunuz aynı soruyu sormasına neden olacaktır. "Beni düşündürme" tasarımın yanı sıra kod için de geçerlidir.
Instance Hunter


2
@Daor: Yorumumdaki bağlantıları bile okudun mu? Birincisi C # hakkında, ikincisi de C # üzerine odaklanmış kabul edilen bir cevapla dile agnostik.
gnovice

7
@gnovice, ilk gerçek-bilge farkı hakkında sorduğumda performans farkı soruyor, ikincisi genel olarak farkı sorduğumda bir döngüdeki fark hakkında ve üçüncüsü C ++ hakkında.
Dlaor

1
Bu bir döngüde i ++ ve ++ i arasındaki farkın bir kopyası olmadığına inanıyorum ? - Dloar'ın yukarıdaki yorumunda söylediği gibi , diğer soru özellikle bir döngü içinde kullanım hakkında sorular soruyor.
chue x

Yanıtlar:


201

Garip bir şekilde, diğer iki cevap bunu açıklamıyor gibi görünüyor ve kesinlikle söylemeye değer:


i++'bana değerini söyle isonra artır'

++i'artma i, sonra bana değeri söyle ' anlamına gelir


Bunlar Artış öncesi, artım sonrası operatörleridir. Her iki durumda da değişken artırılır , ancak her iki ifadenin değerini tam olarak aynı durumlarda alacak olsaydınız sonuç farklı olacaktır.



65
Her iki ifade de doğru değildir. İlk ifadenizi düşünün. i ++ aslında "değeri kaydedin, artırın, i'de saklayın, ardından orijinal kaydedilen değeri söyleyin" anlamına gelir. Yani, söylem daha önce belirttiğiniz gibi değil, artışın ardından gerçekleşir . İkinci ifadeyi düşünün. i ++ aslında "değeri kaydedin, artırın, i'de saklayın ve bana artan değeri söyleyin" anlamına gelir. Dediğiniz gibi, değerin i'nin değeri mi, yoksa i'ye atanan değer mi olduğu belirsizdir; farklı olabilirler .
Eric Lippert

8
@Eric, cevabınızla bile bana benziyor, ikinci ifade kategorik olarak doğru. Birkaç adımı hariç tutmasına rağmen.
Evan Carroll

20
Elbette, çoğu zaman operasyonel semantiği tanımlamanın özensiz, yanlış yolları, kesin ve doğru açıklama ile aynı sonuçları verir. Birincisi, yanlış akıl yürütme yoluyla doğru cevapları almakta zorlayıcı bir değer görmüyorum ve ikincisi, evet, bu tür şeyleri yanlış yapan üretim kodunu gördüm. Muhtemelen yılda yarım düzine soru, gerçek programcılardan neden artışlar ve düşüşler ve dizi dereferences ile dolu bazı belirli ifadelerin yaptıkları gibi çalışmadığı hakkında.
Eric Lippert

12
@supercat: Tartışma C değil C # ile ilgili.
Thanatos

428

Maalesef burada zaten gönderilmiş olan bu sorunun tipik cevabı, kalan işlemleri "önce" ve diğerini ise "kalan" işlemleri yapar. Bu, sezgisel bir şekilde fikri karşılasa da, bu ifade tamamen yanlıştır . Zaman içindeki olayların sırası C # 'da son derece iyi tanımlanmıştır ve ++ ön eki (++ var) ve postfix (var ++) sürümlerinin diğer işlemlere göre işleri farklı bir sırayla yapması kesin değildir .

Bu soruya birçok yanlış cevap görmeniz şaşırtıcı değil. Pek çok "kendinize C # öğretin" kitapları da yanlış anlıyor. Ayrıca, C # 'ın yaptığı yol, C' nin yaptığıdan farklıdır. Birçok kişi sanki C # ve C'nin aynı dil olduğunu düşünür; onlar değil. Bence C # 'daki artış ve azalma operatörlerinin tasarımı, bu operatörlerin C'deki tasarım kusurlarını önler.

Ön ek ve postfix ++ işlemlerinin tam olarak C # 'da ne olduğunu belirlemek için yanıtlanması gereken iki soru vardır. İlk soru sonuç nedir? ikinci soru, artışın yan etkisi ne zaman gerçekleşiyor?

Her iki sorunun cevabının ne olduğu belli değil, ama bir kez gördüğünüzde oldukça basit. X değişkeninin x ++ ve ++ x tam olarak ne yaptığını sizin için açıklayayım.

Önek formu (++ x) için:

  1. x değişkeni üretmek için değerlendirilir
  2. Değişkenin değeri geçici bir konuma kopyalanır
  3. Geçici değer, yeni bir değer üretmek üzere artırılır (geçici değerin üzerine yazılmaz!)
  4. Yeni değer değişkente saklanır
  5. İşlemin sonucu yeni değerdir (yani geçici değerin artan değeri)

Postfix formu (x ++) için:

  1. x değişkeni üretmek için değerlendirilir
  2. Değişkenin değeri geçici bir konuma kopyalanır
  3. Geçici değer, yeni bir değer üretmek üzere artırılır (geçici değerin üzerine yazılmaz!)
  4. Yeni değer değişkente saklanır
  5. Operasyonun sonucu geçici

Dikkat edilmesi gereken bazı noktalar:

İlk olarak, olayların zaman içindeki sırası her iki durumda da tamamen aynıdır . Yine, kesinlikle olduğunu değil bu durumda zaman içinde olayların sırası öneki ve postfix arasında değişir. Değerlendirmenin diğer değerlendirmelerden önce veya diğer değerlendirmelerden sonra gerçekleştiğini söylemek tamamen yanlıştır. Değerlendirmeler , her iki durumda da aynı sırayla gerçekleşir; 1'den 4'e kadar olan adımların aynı olduğunu görebilirsiniz. Sadece fark son adım - Sonuç geçici veya yeni, artırılır değerin değeri olup olmadığını.

Bunu basit bir C # konsol uygulamasıyla kolayca gösterebilirsiniz:

public class Application
{
    public static int currentValue = 0;

    public static void Main()
    {
        Console.WriteLine("Test 1: ++x");
        (++currentValue).TestMethod();

        Console.WriteLine("\nTest 2: x++");
        (currentValue++).TestMethod();

        Console.WriteLine("\nTest 3: ++x");
        (++currentValue).TestMethod();

        Console.ReadKey();
    }
}

public static class ExtensionMethods 
{
    public static void TestMethod(this int passedInValue) 
    {
        Console.WriteLine("Current:{0} Passed-in:{1}",
            Application.currentValue,
            passedInValue);
    }
}

Sonuçlar burada...

Test 1: ++x
Current:1 Passed-in:1

Test 2: x++
Current:2 Passed-in:1

Test 3: ++x
Current:3 Passed-in:3

İlk testte, hem uzantıların hem currentValuede TestMethod()uzantıya iletilenlerin beklendiği gibi aynı değeri gösterdiğini görebilirsiniz.

Bununla birlikte, ikinci durumda, insanlar size artışın çağrıdan sonracurrentValue gerçekleştiğini söylemeye çalışacaklar , ancak sonuçlardan da görebileceğiniz gibi, 'Güncel: 2' sonucuyla belirtildiği gibi, çağrıdan önce gerçekleşiyor .TestMethod()

Bu durumda, önce değeri currentValuegeçici olarak saklanır. Daha sonra, bu değerin artırılmış bir versiyonu currentValue, orijinal değeri hala saklayan geçici değere dokunmadan geri kaydedilir . Sonunda geçici geçilir TestMethod(). Artış çağrısından sonra artış TestMethod()olsaydı, aynı, artmamış değeri iki kez yazacaktır, ancak yazmaz.

Bu dikkat etmek önemlidir her iki dönen değer currentValue++ve ++currentValueişlemleri geçici ve dayanmaktadır zaman değil, ya operasyon çıkışlarında değişkeninde saklanan gerçek değer.

Yukarıdaki işlemler sırasında hatırlayın, ilk iki adım değişkenin o anki değerini geçici olarak kopyalar. Dönüş değerini hesaplamak için kullanılan budur; önek sürümü söz konusu olduğunda, bu geçici değer arttıkça, sonek sürümü söz konusu olduğunda bu değer doğrudan / artırılmazdır. Değişkenin kendisi, geçici olarak ilk depolamadan sonra tekrar okunmaz.

Daha basit bir ifadeyle, postfix sürümü değişkenden okunan değeri (yani geçici değerin değerini) döndürürken, önek sürümü değişkene geri yazılan değeri (yani geçici değerin artan değerini) döndürür. İkisi de değişkenin değerini döndürmez.

Bunu anlamak önemlidir, çünkü değişkenin kendisi değişken olabilir ve başka bir iş parçacığında değişebilir, bu da bu işlemlerin dönüş değerinin değişkente depolanan geçerli değerden farklı olabileceği anlamına gelir.

İnsanların öncelik, ilişkilendirilebilirlik ve yan etkilerin uygulanma sırası hakkında çok kafa karıştırması şaşırtıcıdır, çoğunlukla C'de çok kafa karıştırıcı olduğu için şüpheleniyorum. C #, tüm bu konularda daha az kafa karıştırıcı olacak şekilde dikkatle tasarlanmıştır. Bu sorunların bazı ek analizleri için, ön ek ve son düzeltme işlemlerinin "zaman içinde işleri hareket ettirme" fikrinin sahtelik olduğunu daha fazla göstermem de dahil olmak üzere:

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

Bu SO sorusuna yol açtı:

int [] arr = {0}; int değeri = arr [arr [0] ++]; Değer = 1?

Ayrıca konuyla ilgili önceki makalelerimle de ilgilenebilirsiniz:

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

ve

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

ve C'nin doğruluk hakkında akıl yürütmeyi zorlaştırdığı ilginç bir durum:

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

Ayrıca, zincirleme basit görevler gibi yan etkileri olan diğer işlemleri düşünürken benzer ince sorunlarla karşılaşıyoruz:

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

Ve işte artım işleçlerinin neden değişkenlerden ziyade C # değerlerine neden olduğuna dair ilginç bir yazı :

Neden C benzeri dillerde ++ i ++ yapamıyorum?


4
+1: Gerçekten meraklı olmak için, "C'deki bu işlemlerin tasarım kusurları" ile ne demek istediğinize bir referans verebilir misiniz?
Justin Ardini

21
@ Justin: Bazı bağlantılar ekledim. Ama temel olarak: C'de, şeylerin zamanında ne olacağı konusunda hiçbir garantiniz yok. Uyumlu bir derleyici, aynı sıra noktasında iki mutasyon olduğunda hoşuna giden her şeyi yapabilir ve asla uygulama tanımlı davranış olan bir şey yaptığınızı söylemenize gerek yoktur. Bu, insanları bazı derleyiciler üzerinde çalışan ve diğerlerinde tamamen farklı bir şey yapan tehlikeli olarak taşınamayan kod yazmaya yönlendirir.
Eric Lippert

14
Gerçekten meraklı için, bu iyi bir bilgi olduğunu söylemeliyim, ancak ortalama C # uygulaması için, diğer cevaplardaki ifadeler ile devam eden gerçek şeyler arasındaki fark, gerçekten yaptığı dilin soyutlama seviyesinin çok altında fark yok. C # birleştirici değildir ve zamanların% 99.9'undan i++veya ++ikodda kullanılır, arka planda olan şeyler sadece; arka planda . Bu seviyede olup bitenlerin üzerindeki soyutlama seviyelerine tırmanmak için C # yazıyorum, bu yüzden bu C # kodunuz için gerçekten önemliyse, zaten yanlış dilde olabilirsiniz.
Tomas Aschan

24
@Tomas: Öncelikle, sadece ortalama uygulamaların kütlesi değil, tüm C # uygulamaları hakkında endişeliyim . Ortalama olmayan uygulama sayısı fazladır. İkincisi, fark yarattığı durumlar dışında hiçbir fark yaratmaz . Muhtemelen yılda yarım düzine kez gerçek kodda gerçek hatalara dayanan bu şeyler hakkında sorular alıyorum. Üçüncü olarak, daha büyük noktanıza katılıyorum; ++ fikrinin tamamı, modern kodda çok tuhaf görünen çok düşük seviyeli bir fikirdir. Gerçekten sadece orada çünkü C benzeri dillerin böyle bir operatöre sahip olması deyimsel.
Eric Lippert

11
+1 ama gerçeği söylemek zorundayım ... Yaptığım postfix operatörlerinin tek kullanımının satırda yalnız olmadığı (bu yüzden değil i++;) olduğu yıllar ve yıllar for (int i = 0; i < x; i++)... Ve ben çok çok Bundan mutlu! (ve hiçbir zaman önek operatörünü kullanmam). Ben deşifre etmek için 2 dakika üst düzey bir programcı gerektirecek bir şey yazmak zorunda ... Eh ... Bir kod satırı daha yazmak veya geçici bir değişken tanıtmak daha iyidir :-) Bence "makale" (ben kazandım "cevap"
deme

22

Eğer varsa:

int i = 10;
int x = ++i;

sonra xolacak 11.

Ama eğer varsa:

int i = 10;
int x = i++;

sonra xolacak 10.

Eric'in belirttiği gibi, artış her iki durumda da aynı anda gerçekleşir, ancak sonuç olarak farklı olan değer verilir (teşekkürler Eric!).

Genellikle, ++iiyi bir neden olmadığı sürece kullanmayı seviyorum . Örneğin, bir döngü yazarken şunu kullanmayı seviyorum:

for (int i = 0; i < 10; ++i) {
}

Veya, sadece bir değişkeni artırmak gerekirse, ben kullanmak istiyorum:

++x;

Normalde, şu ya da bu şekilde çok fazla önemi yoktur ve kodlama stiline iner, ancak operatörleri diğer ödevlerde (orijinal örneklerimdeki gibi) kullanıyorsanız, potansiyel yan etkilerin farkında olmak önemlidir.


2
Muhtemelen kod satırlarını sıraya koyarak örneklerinizi açıklığa kavuşturabilirsiniz - yani "var x = 10; var y = ++ x;" ve "var x = 10; var y = x ++;" yerine.
Tomas Aschan

21
Bu cevap tehlikeli bir şekilde yanlış. Artış gerçekleştiğinde, kalan operasyonlara göre değişmez, böylece bir durumda kalan operasyonlardan önce yapılırken , diğer durumda kalan operasyonlardan sonra yanıltıcıdır. Artış her iki durumda da aynı anda yapılır . Farklı olan şey , artış yapıldığında değil , sonuç olarak hangi değerin verildiğidir .
Eric Lippert

3
Bu bir C # anahtar kelime iolduğu vargibi değişken adı kullanmak için düzenledim .
Jeff Yates

2
Yani değişken atamadan ve sadece "i ++;" veya "++ i;" aynı sonuçları verecek mi yoksa yanlış mı anlıyorum?
Dlaor

1
@Eric: Orijinal ifadenin neden "tehlikeli" olduğunu açıklayabilir misiniz? Ayrıntılar yanlış olsa bile doğru kullanıma yol açar.
Justin Ardini

7
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.

Bu sorunuza cevap veriyor mu?


4
Postfix artışının ve önek düşüşünün değişken üzerinde herhangi bir etkisi olmadığını düşünebiliriz: P
Andreas

5

İşlecin çalışma şekli, aynı anda artmasıdır, ancak bir değişkenten önce ise, ifade artan / azalan değişkenle değerlendirilir:

int x = 0;   //x is 0
int y = ++x; //x is 1 and y is 1

Değişkenin peşindeyse, geçerli ifade, henüz artırılmamış / azaltılmamış gibi orijinal değişkenle yürütülür:

int x = 0;   //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0

Gerekmedikçe, ön artış / azalış (++ x) kullanımında dcp ile hemfikirim. Gerçekten arttırma / azaltmayı kullandığım tek zaman, bu tür döngüler veya döngüler halindeyken. Bu döngüler aynıdır:

while (x < 5)  //evaluates conditional statement
{
    //some code
    ++x;       //increments x
}

veya

while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
    //some code
}

Dizileri dizine eklerken bunu da yapabilirsiniz:

int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678;   //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);

Vs vs...


4

Sadece kayıt için, C ++ 'da, eğer herhangi birini kullanabiliyorsanız (yani) işlemlerin sırasını umursamıyorsanız (sadece artırmak veya azaltmak ve daha sonra kullanmak istiyorsanız) ön ek operatörü daha verimlidir nesnenin geçici bir kopyasını oluşturmak zorunda. Ne yazık ki, çoğu kişi başlangıçta öğrendiğimiz şey olduğu için önek (++ var) yerine posfix (var ++) kullanır. (Bu konuda bir röportajda bana soruldu). Bu C # doğru olup olmadığından emin değilim, ama olacağını düşünüyorum.


2
Bu yüzden tek başına kullanıldığında (yani daha büyük bir ifadede değil) ++ i kullanıyorum. Ne zaman i ++ ile ac # döngü görüyorum, biraz ağlıyorum, ama şimdi c # bu aslında daha iyi hissediyorum aynı kod olduğunu biliyorum. Şahsen ben hala ++ i kullanacağım çünkü alışkanlıklar zor ölüyor.
Mikle
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.