C/Operatory: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
m Wycofano edycje użytkownika 212.191.197.47 (dyskusja). Autor przywróconej wersji to Elvi117.
Znacznik: Wycofanie zmian
m Update syntaxhighlight tags - remove use of deprecated <source> tags
Linia 2:
 
Operator przypisania ("="), jak sama nazwa wskazuje, przypisuje wartość prawego argumentu lewemu, np.:
<sourcesyntaxhighlight lang="C">
int a = 5, b;
b = a;
printf("%d\n", b); /* wypisze 5 */
</syntaxhighlight>
</source>
Operator ten ma łączność prawostronną tzn. obliczanie przypisań następuje z prawa na lewo i zwraca on przypisaną wartość, dzięki czemu może być użyty kaskadowo:
<sourcesyntaxhighlight lang="C">
int a, b, c;
a = b = c = 3;
printf("%d %d %d\n", a, b, c); /* wypisze "3 3 3" */
</syntaxhighlight>
</source>
=== Skrócony zapis ===
 
C umożliwia też skrócony zapis postaci <code>a #= b;</code>, gdzie # jest jednym z operatorów: +, -, *, /, %, &, |, ^, << lub >> (opisanych niżej). Ogólnie rzecz ujmując zapis <code>a #= b;</code> jest równoważny zapisowi <code> a = a # (b);</code>, np.:
<sourcesyntaxhighlight lang="C">
int a = 1;
a += 5; /* to samo, co a = a + 5; */
a /= a + 2; /* to samo, co a = a / (a + 2); */
a %= 2; /* to samo, co a = a % 2; */
</syntaxhighlight>
</source>
{{Infobox|Początkowo skrócona notacja miała następującą składnię: <nowiki>a =# b</nowiki>, co często prowadziło do niejasności, np. <nowiki>i =-1</nowiki> (<nowiki>i = -1</nowiki> czy też <nowiki>i = i-1</nowiki>?). Dlatego też zdecydowano się zmienić kolejność operatorów.}}
 
Linia 27:
 
Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu. Konwersja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podana explicite przez programistę). Oto kilka przykładów konwersji niejawnej:
<sourcesyntaxhighlight lang="C">
int i = 42.7; /* konwersja z double do int */
float f = i; /* konwersja z int do float */
Linia 37:
const char *cstr = str; /* konwersja z char* do const char* */
void *ptr = str; /* konwersja z char* do void* */
</syntaxhighlight>
</source>
Podczas konwersji zmiennych zawierających większe ilości danych do typów prostszych (np. double do int) musimy liczyć się z utratą informacji, jak to miało miejsce w pierwszej linijce - zmienna int nie może przechowywać części ułamkowej toteż została ona ''odcięta'' i w rezultacie zmiennej została przypisana wartość 42.
 
Linia 43:
 
Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania, np.:
<sourcesyntaxhighlight lang="C">
double d = 3.14;
int pi = (int)d; /* 1 */
pi = (unsigned)pi >> 4; /* 2 */
</syntaxhighlight>
</source>
W pierwszym przypadku operator został użyty, by zwrócić uwagę na utratę precyzji. W drugim, dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej.
 
Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne konwersje (tj. konwersja z double do int oraz z int do unsigned int), jednak niektóre konwersje są błędne, np.:
<sourcesyntaxhighlight lang="C">
const char *cstr = "foo";
char *str = cstr;
</syntaxhighlight>
</source>
W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję:
<sourcesyntaxhighlight lang="C">
const char *cstr = "foo";
char *str = (char*)cstr;
</syntaxhighlight>
</source>
Należy unikać jednak takich sytuacji i '''nigdy''' nie stosować rzutowania by ''uciszyć kompilator''. Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzie on robił i czy nie ma innego sposobu wykonania danej operacji, który nie wymagałby podejmowania tak drastycznych kroków.
 
Linia 78:
 
{{Infobox|Zobacz również funkcję [[C/div|div]] }}
<sourcesyntaxhighlight lang="C">
int a=7, b=2, c;
c = a % b;
printf ("%d\n",c); /* wypisze "1" */
</syntaxhighlight>
</source>
Należy pamiętać, że (w pewnym uproszczeniu) wynik operacji jest typu
takiego jak ''największy'' z argumentów. Oznacza to, że operacja
Linia 88:
jeżeli wynik przypiszemy do zmiennej rzeczywistej. Dla przykładu,
poniższy kod:
<sourcesyntaxhighlight lang="C">
float a = 7 / 2;
printf("%f\n", a);
</syntaxhighlight>
</source>
wypisze (wbrew oczekiwaniu początkujących programistów) <tt>3.0</tt>, a
nie <tt>3.5</tt>. Odnosi się to nie tylko do dzielenia, ale także
mnożenia, np.:
<sourcesyntaxhighlight lang="C">
float a = 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
printf("%f\n", a);
</syntaxhighlight>
</source>
prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali. Aby
wymusić obliczenia rzeczywiste należy zmienić typ jednego z argumentów
na liczbę rzeczywistą po prostu zmieniając literał lub korzystając z
rzutowania, np.:
<sourcesyntaxhighlight lang="C">
float a = 7.0 / 2; /* wcześniejszy zapis: float a = 7 / 2; */
float b = (float)1000 * 1000 * 1000 * 1000 * 1000 * 1000;
printf("%f\n", a);
printf("%f\n", b);
</syntaxhighlight>
</source>
 
 
Linia 133:
jeden. Ponadto operatory pre- zwracają nową wartość argumentu,
natomiast post- starą wartość argumentu.
<sourcesyntaxhighlight lang="C">
int a, b, c;
a = 3;
b = a--; /* po operacji b=3 a=2 */
c = --b; /* po operacji b=2 c=2 */
</syntaxhighlight>
</source>
Czasami (szczególnie w C++) użycie operatorów stawianych za argumentem
jest nieco mniej efektywne (bo kompilator musi stworzyć nową zmienną
Linia 144:
 
{{uwaga|Bardzo ważne jest, abyśmy poprawnie stosowali operatory dekrementacji i inkrementacji. Chodzi o to, aby w jednej instrukcji nie umieszczać kilku operatorów, które modyfikują ten sam obiekt (zmienną). Jeżeli taka sytuacja zaistnieje, to efekt działania instrukcji jest nieokreślony. Prostym przykładem mogą być następujące instrukcje:
<sourcesyntaxhighlight lang="C">
int a = 1;
a = a++;
Linia 151:
printf("%d %d\n", ++a, ++a);
printf("%d %d\n", a++, a++);
</syntaxhighlight>
</source>
[[C/Używanie_kompilatora#GCC|Kompilator GCC]] potrafi ostrzegać przed takimi błędami - aby to czynił należy podać mu jako argument opcję <tt>-Wsequence-point</tt> lub <tt>-Wall</tt>.}}
 
Linia 213:
 
Przykładowy program działajacy na liczbie 8 bitowej ( unsigned char ):
<sourcesyntaxhighlight lang=c>
// gcc b.c -Wall
// ./a.out
Linia 262:
return 0;
}
</syntaxhighlight>
</source>
 
 
Wynik
 
<sourcesyntaxhighlight lang=bash>
unsigned char has size = 1 byte = 8 bits and range from 0 to 255
 
Linia 282:
decimal number ~a = 252; it's binary expansion = 11111100
 
</syntaxhighlight>
</source>
 
=== Przesunięcie bitowe ===
Linia 305:
Przesunięcie w prawo oznacza przemieszczenie wszystkich bitów argumentu w prawo o określoną liczbę miejsc oraz powielenie najstarszego bitu na skrajnej lewej pozycji.<ref>Język C - Herbert Schildt, Oficyna Wydawnicza LTP, Warszawa 2002</ref>:
 
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
 
Linia 315:
return 0;
}
</syntaxhighlight>
</source>
 
===Zastosowania===
Linia 347:
 
 
<sourcesyntaxhighlight lang=c>
 
// http://blogs.msdn.com/b/nativeconcurrency/archive/2012/10/11/floating-point-arithmetic-intricacies-in-c-amp.aspx
Linia 360:
return 0;
}
</syntaxhighlight>
</source>
 
=== Częste błędy ===
Linia 377:
 
Innym błędem jest użycie zwykłych operatorów porównania do sprawdzania relacji pomiędzy liczbami rzeczywistymi. Ponieważ operacje zmiennoprzecinkowe wykonywane są z pewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie równe. Dla przykładu:
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
 
Linia 390:
printf("%d\n", c == b); /* wypisze 0 */
}
</syntaxhighlight>
</source>
Obejściem jest porównywanie modułu różnicy liczb. Również i takie błędy kompilator GCC potrafi wykrywać - aby to robił należy podać mu argument <tt>-Wfloat-equal</tt>.
 
Linia 434:
 
Żeby w pełni uzmysłowić sobie, co to to oznacza, spójrzmy na wynik wykonania poniższych trzech linijek:
<sourcesyntaxhighlight lang="c">
printf("koniunkcja: %d\n", 18 && 19);
printf("alternatywa: %d\n", 'a' || 'b');
printf("negacja: %d\n", !20);
</syntaxhighlight>
</source>
 
<pre>
Linia 485:
 
Operator sizeof zwraca rozmiar w bajtach (gdzie bajtem jest zmienna typu char) podanego typu lub typu podanego wyrażenia. Ma on dwa rodzaje: <tt>sizeof(typ)</tt> lub <tt>sizeof wyrażenie</tt>. Przykładowo:
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
 
Linia 493:
return 0;
}
</syntaxhighlight>
</source>
Operator ten jest często wykorzystywany przy dynamicznej alokacji pamięci, co zostanie opisane w rozdziale poświęconym [[C/Wskaźniki|wskaźnikom]].
 
Linia 587:
 
W przypadku większości operatorów (wyjątkami są tu &&, || i przecinek) nie da się określić, która wartość argumentu zostanie obliczona najpierw. W większości przypadków nie ma to większego znaczenia, lecz w przypadku wyrażeń, które mają efekty uboczne, wymuszenie konkretnej kolejności może być potrzebne. Weźmy dla przykładu program
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
 
Linia 600:
return foo(1) + foo(2);
}
</syntaxhighlight>
</source>
Otóż nie wiemy czy najpierw zostanie wywołana funkcja <code>foo</code> z parametrem jeden, czy dwa. Jeżeli ma to znaczenie należy użyć zmiennych pomocniczych, zmieniając definicję funkcji <code>main</code> na:
<sourcesyntaxhighlight lang="C">
int main(void)
{
Linia 608:
return tmp + foo(2);
}
</syntaxhighlight>
</source>
Teraz już na pewno najpierw zostanie wypisana jedynka, a potem dopiero dwójka. Sytuacja jeszcze bardziej się komplikuje, gdy używamy wyrażeń z efektami ubocznymi jako argumentów funkcji, np.:
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
 
Linia 628:
return bar(foo(1), foo(2), foo(3), foo(4));
}
</syntaxhighlight>
</source>
Teraz też nie wiemy, która z 24 permutacji liczb 1, 2, 3 i 4 zostanie wypisana i ponownie należy pomóc sobie zmiennymi tymczasowymi, jeżeli zależy nam na konkretnej kolejności:
<sourcesyntaxhighlight lang="C">
int main(void)
{
Linia 638:
return bar(tmp1, tmp2, tmp3, foo(4));
}
</syntaxhighlight>
</source>
===Jak czytać wyrażenia?===
* złożone wyrażenia z wskaźnikami<ref>[http://www.c4learn.com/c-programming/c-reading-complex-pointer-expression/ C reading complex pointer expression]</ref>