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

Dodane 432 bajty ,  2 lata temu
m
Update syntaxhighlight tags - remove use of deprecated <source> tags
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)
 
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.}}
 
 
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 */
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.
 
 
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.
 
 
{{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
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>
 
 
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ą
 
{{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++;
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>.}}
 
 
Przykładowy program działajacy na liczbie 8 bitowej ( unsigned char ):
<sourcesyntaxhighlight lang=c>
// gcc b.c -Wall
// ./a.out
return 0;
}
</syntaxhighlight>
</source>
 
 
Wynik
 
<sourcesyntaxhighlight lang=bash>
unsigned char has size = 1 byte = 8 bits and range from 0 to 255
 
decimal number ~a = 252; it's binary expansion = 11111100
 
</syntaxhighlight>
</source>
 
=== Przesunięcie bitowe ===
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>
 
return 0;
}
</syntaxhighlight>
</source>
 
===Zastosowania===
 
 
<sourcesyntaxhighlight lang=c>
 
// http://blogs.msdn.com/b/nativeconcurrency/archive/2012/10/11/floating-point-arithmetic-intricacies-in-c-amp.aspx
return 0;
}
</syntaxhighlight>
</source>
 
=== Częste błędy ===
 
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>
 
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>.
 
 
Ż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>
 
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>
 
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]].
 
 
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>
 
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)
{
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>
 
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)
{
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>
90

edycji