Widziałem je zarówno wykorzystywane w wielu kawałków kodu C # i chciałbym wiedzieć kiedy użyć i++lub ++i( ibędąc liczba zmienna jak int, float, doubleitp). Czy ktoś to wie?
Widziałem je zarówno wykorzystywane w wielu kawałków kodu C # i chciałbym wiedzieć kiedy użyć i++lub ++i( ibędąc liczba zmienna jak int, float, doubleitp). Czy ktoś to wie?
Odpowiedzi:
Co dziwne, wygląda na to, że dwie pozostałe odpowiedzi tego nie przeliterowały i zdecydowanie warto powiedzieć:
i++oznacza „powiedz mi wartość i, a następnie przyrost”
++ioznacza „przyrost i, a następnie powiedz mi wartość”
Są to operatorzy działający przed inkrementacją, po inkrementacji. W obu przypadkach zmienna jest zwiększana , ale jeśli weźmiesz wartość obu wyrażeń w dokładnie tych samych przypadkach, wynik będzie inny.
Typowa odpowiedź na to pytanie, niestety zamieszczone już tutaj, jest taka, że jeden wykonuje przyrost „przed” pozostałymi operacjami, a drugi wykonuje przyrost „po” pozostałych operacjach. Choć intuicyjnie prowadzi to do pomysłu, to stwierdzenie jest całkowicie błędne . Sekwencja wydarzeń w czasie jest bardzo dobrze zdefiniowane w C #, a to zdecydowanie nie przypadek, że przedrostek (++ var) i postfix (var ++) wersje ++ robić rzeczy w innej kolejności w stosunku do innych operacji.
Nic dziwnego, że zobaczysz wiele błędnych odpowiedzi na to pytanie. Wiele książek „nauczysz się języka C #” również się myli. Ponadto sposób, w jaki C # robi, jest inny niż w C. Wiele osób uważa, że C # i C są tym samym językiem; oni nie są. Projekt operatorów inkrementacji i dekrementacji w C # moim zdaniem pozwala uniknąć wad projektowych tych operatorów w C.
Istnieją dwa pytania, na które należy odpowiedzieć, aby ustalić, jakie dokładnie działanie przedrostka i Postfiksa ++ znajdują się w języku C #. Pierwsze pytanie brzmi: jaki jest wynik? a drugie pytanie dotyczy tego, kiedy ma miejsce efekt uboczny przyrostu?
Nie jest oczywiste, jaka jest odpowiedź na którekolwiek z tych pytań, ale w rzeczywistości jest to dość proste, gdy je zobaczysz. Pozwól, że wyjaśnię ci dokładnie, co x ++ i ++ x robią dla zmiennej x.
W przypadku formularza prefiksu (++ x):
W przypadku formularza Postfiks (x ++):
Niektóre rzeczy do zauważenia:
Po pierwsze, kolejność zdarzeń w czasie jest dokładnie taka sama w obu przypadkach . Ponownie absolutnie nie jest tak, że kolejność zdarzeń w czasie zmienia się między prefiksem i postfiksem. Całkowicie fałszywe jest twierdzenie, że ocena odbywa się przed innymi ocenami lub po innych ocenach. Oceny odbywają się w dokładnie takiej samej kolejności w obu przypadkach, co widać w krokach od 1 do 4 jako identyczne. Tylko różnicą jest to ostatni krok - czy wynik jest wartość tymczasowa lub nowym, zwiększonym wartości.
Możesz to łatwo zademonstrować za pomocą prostej aplikacji na konsolę C #:
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);
}
}
Oto wyniki...
Test 1: ++x
Current:1 Passed-in:1
Test 2: x++
Current:2 Passed-in:1
Test 3: ++x
Current:3 Passed-in:3
W pierwszym teście możesz zobaczyć, że zarówno to, jak currentValuei to, co zostało przekazane do TestMethod()rozszerzenia, pokazuje tę samą wartość, zgodnie z oczekiwaniami.
Jednak w drugim przypadku ludzie będą próbować powiedzieć, że wzrost currentValuenastępuje po połączeniu z TestMethod(), ale jak widać z wyników, dzieje się to przed połączeniem, jak wskazuje wynik „Bieżący: 2”.
W takim przypadku najpierw wartość currentValuejest przechowywana jako tymczasowa. Następnie przyrostowa wersja tej wartości jest przechowywana z powrotem, currentValueale bez dotykania wartości tymczasowej, która nadal przechowuje oryginalną wartość. W końcu ten tymczasowy jest przekazywany do TestMethod(). Jeśli przyrost nastąpił po wywołaniu do TestMethod(), zapisałby dwukrotnie tę samą niekrementowaną wartość, ale tak nie jest.
Ważne jest, aby pamiętać, że wartość zwracana zarówno z operacji, jak
currentValue++i++currentValueopiera się na wartości tymczasowej, a nie rzeczywistej przechowywanej w zmiennej w momencie zakończenia którejkolwiek operacji.Przywołaj w powyższej kolejności operacji, dwa pierwsze kroki kopiują bieżącą wartość zmiennej do wartości tymczasowej. Właśnie tego używa się do obliczenia wartości zwracanej; w przypadku wersji przedrostkowej jest to wartość tymczasowa zwiększana, podczas gdy w przypadku wersji przedrostkowej jest to wartość bezpośrednio / nie zwiększana. Sama zmienna nie jest odczytywana ponownie po początkowym zapisaniu w pamięci tymczasowej.
Mówiąc prościej, wersja postfiksowa zwraca wartość odczytaną ze zmiennej (tj. Wartość tymczasową), podczas gdy wersja prefiksowa zwraca wartość zapisaną z powrotem do zmiennej (tj. Wartość przyrostową wartości tymczasowej). Ani nie zwracaj wartości zmiennej.
Jest to ważne, aby zrozumieć, ponieważ sama zmienna może być niestabilna i uległa zmianie w innym wątku, co oznacza, że wartość zwrotna tych operacji może różnić się od bieżącej wartości przechowywanej w zmiennej.
Zaskakujące jest, że ludzie często są zdezorientowani co do pierwszeństwa, asocjatywności i kolejności wykonywania działań niepożądanych, podejrzewam, że głównie dlatego, że jest tak mylący w C. C # został starannie zaprojektowany, aby był mniej mylący pod każdym względem. Aby uzyskać dodatkową analizę tych problemów, w tym moją dalszą demonstrację fałszywości idei, że operacje na prefiksach i postfiksach „przenoszą rzeczy w czasie”, patrz:
https://ericlippert.com/2009/08/10/precedence-vs-order-redux/
, które doprowadziły do tego pytania SO:
int [] arr = {0}; int wartość = arr [arr [0] ++]; Wartość = 1?
Być może zainteresują Cię moje poprzednie artykuły na ten temat:
https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/
i
https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/
oraz ciekawy przypadek, w którym C utrudnia uzasadnienie poprawności:
https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited
Ponadto napotykamy podobne subtelne problemy, gdy rozważamy inne operacje, które mają skutki uboczne, takie jak powiązane proste zadania:
https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple
A oto ciekawy post na temat tego, dlaczego operatory przyrostowe dają wartości w C # zamiast w zmiennych :
i++lub ++isą one używane w kodzie, rzeczy działające w tle są właśnie takie; w tle . Piszę C #, aby wspiąć się na poziomy abstrakcji powyżej tego, co dzieje się na tym poziomie, więc jeśli to naprawdę ma znaczenie dla twojego kodu C #, możesz już być w złym języku.
i++;jest) w for (int i = 0; i < x; i++)... I jestem bardzo, bardzo szczęśliwy z tego! (i nigdy nie używam operatora prefiksu). Jeśli będę musiał napisać coś, co będzie wymagało starszego programisty 2 minuty na rozszyfrowanie ... Cóż ... Lepiej napisać jeszcze jedną linię kodu lub wprowadzić tymczasową zmienną :-) Myślę, że twój „artykuł” (wygrałem nazywam to „odpowiedzią”) potwierdza mój wybór :-)
Jeśli masz:
int i = 10;
int x = ++i;
wtedy xbędzie 11.
Ale jeśli masz:
int i = 10;
int x = i++;
wtedy xbędzie 10.
Uwaga: jak wskazuje Eric, przyrost występuje w obu przypadkach jednocześnie, ale to, jaka wartość jest podana jako wynik, różni się (dzięki Eric!).
Ogólnie lubię używać, ++ichyba że istnieje dobry powód, aby tego nie robić. Na przykład, pisząc pętlę, lubię używać:
for (int i = 0; i < 10; ++i) {
}
Lub, jeśli muszę tylko zwiększyć zmienną, lubię używać:
++x;
Zwykle jeden lub drugi sposób nie ma większego znaczenia i sprowadza się do stylu kodowania, ale jeśli używasz operatorów wewnątrz innych zadań (jak w moich oryginalnych przykładach), ważne jest, aby zdawać sobie sprawę z potencjalnych skutków ubocznych.
inazwy zmiennej, a nie varjako słowa kluczowego C #.
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.
Czy to odpowiada na twoje pytanie?
Sposób działania operatora polega na tym, że jest on zwiększany w tym samym czasie, ale jeśli znajduje się przed zmienną, wyrażenie będzie oceniać za pomocą zmiennej zwiększającej / zmniejszającej:
int x = 0; //x is 0
int y = ++x; //x is 1 and y is 1
Jeśli jest za zmienną, bieżąca instrukcja zostanie wykonana z oryginalną zmienną, tak jakby nie była jeszcze zwiększana / zmniejszana:
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
Zgadzam się z dcp w używaniu wstępnego zwiększania / zmniejszania (++ x), chyba że jest to konieczne. Naprawdę jedyny raz, kiedy używam przyrostowego / malejącego czasu, to pętle while lub pętle tego rodzaju. Te pętle są takie same:
while (x < 5) //evaluates conditional statement
{
//some code
++x; //increments x
}
lub
while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
//some code
}
Możesz to również zrobić podczas indeksowania tablic i takie:
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);
Itd itd...
Dla przypomnienia, w C ++, jeśli możesz użyć albo (tj.), Nie przejmujesz się kolejnością operacji (chcesz tylko zwiększyć lub zmniejszyć i użyć go później), operator prefiksu jest bardziej wydajny, ponieważ nie trzeba utworzyć tymczasową kopię obiektu. Niestety większość ludzi używa posfiksu (var ++) zamiast przedrostka (++ var), tylko dlatego, że nauczyliśmy się tego na początku. (Zapytano mnie o to w wywiadzie). Nie jestem pewien, czy jest to prawda w języku C #, ale zakładam, że tak będzie.