- Lernen
- Informatik & Informationswissenschaften
- Programmierung
- C-Programmierung – Zeiger, Speicherverwaltung und sicheres C
- Datentypen und Operatoren in C
C-Grundlagen: Datentypen, Operatoren und Programmablauf
printf aus und lies sie mit scanf ein. Bilde Ausdrücke mit den arithmetischen, Vergleichs-, logischen und bitweisen Operatoren von C und sage Typumwandlungen und Operatorpriorität voraus. Steuere dann ein Programm mit if, switch, while, do-while und for. Lerne abschließend, warum Ganzzahlen überlaufen, was dabei geschieht und wie du Code schreibst und testest, der Überläufe verhindert.AnfängerEinheit 1 von 8 in Kurs: C-Programmierung – Zeiger, Speicherverwaltung und sicheres C
Was du lernst
- C-Quellcode in ein lauffähiges Programm umwandeln, die Aufgaben des Compilers in jeder Phase verstehen und seine Meldungen nutzen, um Fehler zu finden und zu beheben.
- Ein erstes C-Programm schreiben, kompilieren und ausführenDie Teile eines minimalen C-Programms lesen, es mit gcc oder clang kompilieren und die ausführbare Datei von einem Terminal aus starten.
- Compilermeldungen lesen und Fehler behebenÜbersetzungsfehler, Warnungen, Linkerfehler, Laufzeitfehler und logische Fehler unterscheiden und sie mithilfe von Compilermeldungen und Testläufen beheben.
- Ein erstes C-Programm schreiben, kompilieren und ausführen
- Variablen deklarieren und initialisieren, zwischen den Ganzzahl- und Gleitkommatypen von C wählen und den Wertebereich und die Genauigkeit jedes Typs vorhersagen.
- Variablen deklarieren, initialisieren und benennenVariablen mit einem Typ und einem aussagekräftigen Namen anlegen, ihnen Anfangswerte geben und Konstanten für feste Werte verwenden.
- Einen Ganzzahltyp auswählen und seinen Wertebereich kennenUnter den vorzeichenbehafteten und vorzeichenlosen Ganzzahltypen, Zeichen- und booleschen Werten von C anhand ihrer Größen, Wertebereiche und Literalformen auswählen.
- Mit Gleitkommazahlen arbeiten und ihre Grenzen kennenReelle Zahlen in float und double speichern, ihre Genauigkeit und Rundungsfehler vorhersagen und sicher zwischen Gleitkommawerten und Ganzzahlen umwandeln.
- Variablen deklarieren, initialisieren und benennen
- Zahlen und Text mit printf präzise formatieren und von Benutzern eingegebene Werte mit scanf lesen und prüfen.
- Ausgaben mit printf formatierenKonvertierungsspezifikationen, Feldbreiten, Genauigkeit, Flags und Escape-Sequenzen verwenden, um genau die beabsichtigte Ausgabe zu erzeugen.
- Eingaben mit scanf lesen und prüfenVon Benutzern eingegebene Zahlen und Zeichen lesen, den Leseerfolg prüfen und ungültige Eingaben behandeln.
- Ausgaben mit printf formatieren
- Die arithmetischen, Zuweisungs-, Vergleichs-, logischen und bitweisen Operatoren von C verwenden und Wert und Typ eines beliebigen Ausdrucks anhand von Typumwandlungen, Operatorpriorität und Assoziativität vorhersagen.
- Mit arithmetischen Operatoren und Zuweisungsoperatoren rechnen+, -, *, / und %, Ganzzahldivision, zusammengesetzte Zuweisung sowie Inkrement und Dekrement anwenden und ihre typischen Fallstricke vermeiden.
- Typumwandlungen in Ausdrücken mit unterschiedlichen Datentypen vorhersagenErklären, wie C Operanden unterschiedlicher Typen umwandelt, explizite Typumwandlungen gezielt einsetzen und die Falle bei vorzeichenbehafteten und vorzeichenlosen Werten vermeiden.
- Bedingungen mit Vergleichsoperatoren, logischen und bitweisen Operatoren verknüpfenKorrekte Vergleiche und zusammengesetzte Bedingungen schreiben, Kurzschlussauswertung nutzen und einzelne Bits bearbeiten.
- Operatorpriorität, Assoziativität und Auswertungsreihenfolge berücksichtigenVorhersagen, wie C die Operatoren in einem Ausdruck gruppiert, Ausdrücke schrittweise auswerten und bei unklarer Gruppierung Klammern verwenden.
- Mit arithmetischen Operatoren und Zuweisungsoperatoren rechnen
- Mit if, else und switch zwischen Alternativen wählen und Aufgaben mit while-, do-while- und for-Schleifen wiederholen, die genau wie beabsichtigt starten, laufen und enden.
- Entscheidungen mit if und else treffenif-, else if- und else-Anweisungen mit korrekten Bedingungen und geschweiften Klammern schreiben und häufige Fallstricke der C-Syntax vermeiden.
- Mit switch zwischen Fällen auswählenMit switch, case-Marken, break und default zwischen festen ganzzahligen Werten wählen und Fall-through nur gezielt einsetzen.
- Mit while und do-while wiederholen, solange eine Bedingung erfüllt istBedingungsgesteuerte Schleifen schreiben, die korrekt starten, Fortschritt machen und enden, einschließlich Eingabeschleifen und Schleifen, die mindestens einmal ausgeführt werden müssen.
- Mit for-Schleifen zählen, Schleifen verschachteln und vorzeitig abbrechenfor-Schleifen schreiben, die genau so oft wie beabsichtigt ausgeführt werden, Schleifen ineinander verschachteln und break und continue gezielt einsetzen.
- Entscheidungen mit if und else treffen
- Erklären, was geschieht, wenn ein Ergebnis im Ganzzahltyp nicht darstellbar ist, und Code schreiben und testen, der Überläufe vermeidet oder erkennt.
- Erklären, was geschieht, wenn ein Wert nicht im Datentyp darstellbar istWraparound bei vorzeichenlosen Ganzzahlen, undefinierten Überlauf bei vorzeichenbehafteten Ganzzahlen und Konvertierungen außerhalb des Wertebereichs beschreiben und mit realen Fehlfunktionen verbinden.
- Code schreiben, der Überläufe vermeidet oder erkenntTypen mit ausreichendem Wertebereich wählen, Operationen vor einem möglichen Überlauf prüfen und Compilerwerkzeuge sowie Grenzwerttests einsetzen, um Überläufe früh zu erkennen.
- Erklären, was geschieht, wenn ein Wert nicht im Datentyp darstellbar ist
Datentypen und Operatoren in C einfach erklärt
1C-Programme erstellen und ausführen
Durch die Programmerstellung wird C-Code zu einem ausführbaren Programm. Lerne die Teile eines vollständigen ersten Programms kennen, kompiliere es mit einem ausgewählten Sprachstandard und führe die entstandene ausführbare Datei aus. Wenn eine Erstellung oder ein Test fehlschlägt, nutze die Phase des Fehlers und die erste Compilermeldung, um die Ursache zu finden. Wenn du den Zyklus aus Bearbeiten, Erstellen und Testen sorgfältig durchläufst, kann eine alte ausführbare Datei eine fehlgeschlagene Änderung nicht verbergen, und jede Korrektur lässt sich leichter prüfen.
1.1 Vom Quellcode zum laufenden Programm
Ein C-Programm beginnt als reiner Text und läuft als Maschinencode. Der Compiler verbindet diese beiden Formen. Wenn du eine Änderung speicherst, änderst du den Quellcode; die zuvor erstellte ausführbare Datei ändert sich dadurch nicht.
Ein vollständiges Programm lesen
Speichere dies als hello.c:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
#include <stdio.h> stellt Deklarationen für die Standardein- und -ausgabe bereit, darunter printf. Die Ausführung beginnt in main. In int main(void) bezeichnet int den Rückgabetyp und void bedeutet, dass es keine Parameter gibt. Geschweifte Klammern umschließen den Rumpf. Der Aufruf von printf gibt den Text in Anführungszeichen aus; \n wechselt in eine neue Zeile. return 0; beendet das Programm und meldet dem Betriebssystem Erfolg. Ein Status ungleich null kann einen Fehler melden. Seit C99 wird auch beim Erreichen des Endes von main null zurückgegeben.
Einfache Anweisungen enden mit einem Semikolon. Die include-Zeile nicht. C unterscheidet Groß- und Kleinschreibung: Printf und printf sind verschiedene Namen. Leerzeichen zwischen Tokens beeinflussen das Programm gewöhnlich nicht, aber einheitliche Einrückung macht die Blockstruktur sichtbar.
Kompilieren und ausführen
Öffne im Terminal den Ordner mit dem Quellcode und gib ein:
gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello
Der erste Befehl erstellt eine ausführbare Datei namens hello; der zweite führt sie aus dem aktuellen Ordner aus. Ihre Ausgabe ist Hello, world! gefolgt von einem Zeilenumbruch. Du kannst clang anstelle von gcc verwenden. Ohne -o hello lautet der übliche Ausgabename unter Linux oder macOS a.out. Ausführbare Dateien unter Windows verwenden .exe. make hello von CS50 ist eine Abkürzung, die das Erstellungswerkzeug auffordert, den Compiler aufzurufen.
Speichere nach einer Änderung der Begrüßung, kompiliere erneut und führe das Programm wieder aus. Schlägt die Kompilierung fehl, kann noch eine ältere ausführbare Datei vorhanden sein. Deren Ausführung zeigt das alte Verhalten. Prüfe daher, ob die Erstellung erfolgreich war, bevor du deine Änderung beurteilst.
Was bei der Erstellung geschieht
Der Präprozessor verarbeitet Direktiven wie #include, indem er Headertext einfügt. Der eigentliche Compiler übersetzt C in Assemblersprache. Der Assembler erzeugt Objektcode, der noch nicht aufgelöste Namen enthalten kann. Der Linker verbindet Objektcode und löst Verweise wie printf auf. Bibliothekscode kann durch statisches Linken in eine ausführbare Datei kopiert oder über eine gemeinsam genutzte Bibliothek bereitgestellt werden, die bei der Programmausführung geladen wird.
Du kannst die Erstellung bei Zwischenstufen anhalten:
| Option | Ergebnis |
|---|---|
-E | Vorverarbeiteter Text |
-S | Assemblersprache |
-c | Eine Objektdatei, etwa hello.o |
Eine Objektdatei ist noch nicht die fertige ausführbare Datei. Eine ausführbare Datei kann ohne Compiler laufen, wird aber für ein bestimmtes Betriebssystem und einen bestimmten Prozessor erstellt. Um das Programm auf eine andere Plattform zu übertragen, kompiliere dort seinen Quellcode.
Kommentare und Sprachversionen
// beginnt einen Kommentar, der am Zeilenumbruch endet. /* ... */ kann mehrere Zeilen umfassen, aber solche Kommentare können nicht verschachtelt werden: Das erste */ beendet den Kommentar. Nutze Kommentare, um eine Entscheidung zu erklären, die der Code allein offenlässt.
Wähle die Sprachversion ausdrücklich aus. C17 unterstützt die obigen Beispiele, einschließlich //-Kommentaren, long long und Deklarationen in for-Köpfen. C23 ergänzt Funktionen wie bool als Schlüsselwort; es wurde als ISO/IEC 9899:2024 veröffentlicht. -Wall -Wextra aktiviert nützliche Warnungen. Die Voreinstellungen der Compiler unterscheiden sich, sodass ein ausdrückliches -std=c17 die beabsichtigten Sprachregeln klarstellt.
Wichtige Erkenntnisse
- C-Quellcode muss nach jeder Änderung erneut kompiliert werden.
- Die Ausführung beginnt in
main; die Rückgabe von null meldet Erfolg. - Die Phasen der Programmerstellung sind Vorverarbeitung, Kompilierung, Assemblierung und Linken.
- Verwende
-std=c17 -Wall -Wextra, um den Standard zu wählen und Warnungen zu aktivieren.
1.2 Die erste Ursache eines Fehlers finden
Der Zeitpunkt, zu dem ein Problem auftritt, hilft dir, seine Ursache zu finden. Ein Programm kann sich nicht erstellen lassen, während der Ausführung stoppen oder mit einem falschen Ergebnis enden. Diese Situationen verlangen unterschiedliche Prüfungen.
Problemarten unterscheiden
| Art | Was du siehst | Wo du suchen solltest |
|---|---|---|
| Übersetzungsfehler | Keine neue ausführbare Datei | Syntax, Deklarationen und Typen |
| Warnung | Ein wahrscheinliches Problem; die Erstellung läuft gewöhnlich weiter | Der in der Meldung genannte Ausdruck |
| Linkerfehler | Objektcode ist vorhanden, aber die ausführbare Datei kann nicht fertiggestellt werden | Nicht aufgelöste Funktionsnamen oder benötigte Bibliotheken |
| Laufzeitfehler | Ein Problem tritt während der Ausführung auf | Operationen und Werte, die bei dieser Eingabe erreicht werden |
| Logischer Fehler | Das Programm läuft, liefert aber ein unbeabsichtigtes Ergebnis | Bedingungen, Formeln und Aktualisierungen |
Eine Warnung kann neben einem gewöhnlichen Fehler auch unzulässigen Code oder undefiniertes Verhalten anzeigen. Eine erfolgreiche Kompilierung garantiert kein korrektes Programm.
Eine Compilermeldung lesen
Eine Compilermeldung nennt normalerweise eine Stelle im Quellcode, einen Schweregrad und eine Beschreibung. Eine Meldung wie hello.c:5:16: error: expected ';' after expression weist auf ein fehlendes Semikolon nahe Zeile 5 hin. Manchmal bemerkt der Compiler das Problem erst in der nächsten Zeile. Untersuche die gemeldete Zeile und die unmittelbar davor.
Behebe zuerst den ersten Fehler und kompiliere dann erneut. Ein nicht geschlossenes Anführungszeichen kann spätere Zeilen als Teil einer Zeichenkette erscheinen lassen; eine nicht geschlossene geschweifte Klammer kann spätere Deklarationen im falschen Block erscheinen lassen. Mehrere Meldungen können nach einer einzigen Korrektur verschwinden. Beginne nicht damit, jede in der Ausgabe genannte Zeile zu ändern.
Für diesen Ausschnitt:
int total = 6
printf("%d\n", total);
Die Deklaration benötigt ein Semikolon nach 6. In der printf-Zeile kann der Compiler erstmals feststellen, dass die vorangehende Deklaration nicht beendet wurde. Prüfe auch die genaue Schreibweise, zusammengehörige runde und geschweifte Klammern sowie die Headerdatei, die eine Bibliotheksfunktion deklariert. Ein Aufruf von printf benötigt <stdio.h>; Aufrufe nicht deklarierter Funktionen sind ab C99 nicht erlaubt.
Warnungen ernst nehmen
Erstelle das Programm mit -Wall -Wextra. Diese Optionen helfen, eine Zuweisung in einer Bedingung, ungenutzte Variablen, Vergleiche zwischen vorzeichenbehafteten und vorzeichenlosen Werten und unpassende Ausgabeformate zu erkennen. Zum Beispiel verlangt printf("%d\n", 3.5); eine Ganzzahl, übergibt aber einen double; das Format wandelt das Argument nicht für dich um. Korrigiere das Format zu %f oder wandle den Wert bewusst um. -Werror sorgt dafür, dass Warnungen die Erstellung stoppen.
Undefiniertes Verhalten verstehen
Undefiniertes Verhalten bedeutet, dass C keine Anforderungen an das Verhalten des Programms stellt. Ein Überlauf bei vorzeichenbehafteten Ganzzahlen, Ganzzahldivision durch null und manche Lesezugriffe auf nicht initialisierte Variablen können dazu führen. Ein Programm kann stoppen, einen unerwarteten Wert ausgeben oder scheinbar funktionieren. Compilereinstellungen können beeinflussen, was geschieht.
Eine Meldung wie „Floating point exception“ kann auf manchen Systemen sogar durch Ganzzahldivision durch null entstehen. Untersuche die Operation und die Eingabe, nicht nur den Wortlaut einer Betriebssystemmeldung. Prüfe einen Divisor, bevor du die Division ausführst.
Ein falsches Ergebnis nachvollziehen
Sage bei einem logischen Fehler einen kleinen Fall von Hand voraus und vergleiche ihn mit der tatsächlichen Ausgabe. Füge vorübergehend printf-Aufrufe hinzu, die eine laufende Summe oder den von einer Bedingung geprüften Wert anzeigen. Ändere jeweils nur eine Ursache und erstelle das Programm nach jeder Änderung erneut. Teste gewöhnliche Werte und Grenzfälle wie null, eins, eine negative Eingabe und die größte erwartete Eingabe. Ein Ablaufprotokoll zeigt, wo das Programm erstmals von deiner Vorhersage abweicht.
Wichtige Erkenntnisse
- Behebe die erste Meldung und erstelle das Programm erneut, bevor du späteren Meldungen nachgehst.
- Eine Warnung kann auf undefiniertes Verhalten hinweisen; die Kompilierung allein belegt keine Korrektheit.
- Stimme Formatkonvertierungen auf die Argumenttypen ab.
- Verfolge bei einem falschen Ergebnis eine kleine Eingabe und vergleiche die Zwischenwerte.
- Teste neben gewöhnlichen Eingaben auch Grenzfälle.
2Daten in Variablen der grundlegenden Datentypen speichern
Ein Typ bestimmt, wie ein gespeicherter Wert interpretiert wird und welche Werte er aufnehmen kann. Beginne mit Deklarationen, Anfangswerten und den Namen, die innerhalb eines Blocks verfügbar sind. Wähle dann Ganzzahltypen anhand ihres Wertebereichs und Gleitkommatypen anhand ihrer Genauigkeit und ihres Wertebereichs. Zeichencodes und boolesche Werte gehören ebenfalls dazu. Wenn du Literale lesen und Grenzen prüfen kannst, hilft dir das vorherzusagen, was sich bei einer Berechnung darstellen lässt, bevor du sie schreibst.
2.1 Variablen, Namen und feste Werte
Eine Variable gibt einem gespeicherten Wert einen Namen. Ihr Typ legt fest, wie C diesen Speicher interpretiert. Du deklarierst den Typ, bevor du die Variable verwendest; während ihrer gesamten Lebensdauer behält sie diesen Typ.
Gib jeder Variablen einen Anfangswert
int count = 0;
double length_m = 1.8;
count = count + 1;
Die ersten beiden Zeilen deklarieren und initialisieren Variablen. Die dritte weist einer vorhandenen Variablen einen neuen Wert zu: Sie liest den aktuellen Wert von count, addiert eins und speichert das Ergebnis zurück. Eine Zuweisung ist eine Aktion, daher ist count = count + 1 sinnvoll, auch wenn es keine mathematische Gleichung wäre.
Die Initialisierung erfolgt in einer Deklaration; eine Zuweisung kann später erfolgen. In int a, b = 5; wird nur b initialisiert. Ein lokales a hat einen unbestimmten Wert, bis du einen Wert darin speicherst. Das Lesen kann unvorhersehbares Verhalten einschließlich undefinierten Verhaltens hervorrufen. Initialisiere Zähler und Summen bei ihrer Deklaration, etwa mit int total = 0;.
Eine Zuweisung kopiert den Wert zum jeweiligen Zeitpunkt:
int width = 4;
int area = width * 3;
width = 7;
printf("%d %d\n", width, area);
Das gibt 7 12 aus. Die Zuweisung an area hat einmalig 12 berechnet. Sie hat keine Formel erzeugt, die bei jeder Änderung von width neu rechnet. Um area zu aktualisieren, weise der Variablen eine neue Berechnung zu.
Wähle lesbare Namen
Ein Bezeichner kann Buchstaben, Ziffern und Unterstriche enthalten, darf aber nicht mit einer Ziffer beginnen oder ein Schlüsselwort sein. height_cm und second_place sind zulässig; 2nd_place nicht, und my-score wird als Subtraktion gelesen. Bei Namen wird Groß- und Kleinschreibung unterschieden, sodass total und Total verschieden sind. Namen, die eine Bedeutung oder Einheit enthalten, erleichtern die Prüfung von Formeln.
Beachte den Blockgültigkeitsbereich
Ein lokaler Name ist von seiner Deklaration bis zum Ende seines Blocks verfügbar. Seit C99 können Deklarationen zwischen anderen Anweisungen stehen.
int count = 2;
{
int count = 9;
printf("%d\n", count);
}
printf("%d\n", count);
Die innere Deklaration verdeckt den äußeren Namen, während dieser Block ausgeführt wird. Die Ausgabe ist 9 und danach 2, jeweils in einer eigenen Zeile. Es handelt sich um zwei Variablen, nicht um zwei Zuweisungen an eine Variable. Denselben Namen zweimal in einem Block zu deklarieren, ist ein Fehler. Namen in verschachtelten Blöcken erneut zu verwenden, ist erlaubt, macht den Code aber schwerer nachvollziehbar. Wähle daher Namen, die jede Rolle deutlich machen.
Benenne feste Werte
const int SECONDS_PER_MINUTE = 60;
#define MAX_HEIGHT 8
const erzeugt eine typisierte Variable, der über diesen Namen kein neuer Wert zugewiesen werden kann. Eine spätere Anweisung SECONDS_PER_MINUTE = 90; muss eine Compilermeldung auslösen. Der Initialisierer gibt bei der Deklaration den festen Wert an.
#define erzeugt eine Ersetzung durch den Präprozessor: Spätere Vorkommen von MAX_HEIGHT werden vor der Kompilierung durch das Token 8 ersetzt. In dieser Direktive stehen weder = noch ein Semikolon. Ein Semikolon würde Teil der Ersetzung und könnte einen Ausdruck wie height > MAX_HEIGHT fehlerhaft machen.
Ein Name wie SECONDS_PER_MINUTE erklärt Lesenden, warum 60 in einer Berechnung erscheint, und gibt dir eine zentrale Stelle für Änderungen. Großbuchstaben sind eine Namenskonvention, keine Regel, die einen Wert konstant macht. Dieses Verhalten wird durch die Deklaration oder Direktive festgelegt.
Wichtige Erkenntnisse
- Initialisiere lokale Variablen, bevor du sie liest.
- Eine Zuweisung kopiert einen Wert einmalig; abhängige Werte aktualisieren sich nicht selbst.
- Ein Block begrenzt, wo ein lokaler Name verwendet werden kann.
- Bei Namen wird Groß- und Kleinschreibung unterschieden; sie dürfen nicht mit einer Ziffer beginnen.
conststellt eine typisierte schreibgeschützte Variable bereit;#definestellt eine Token-Ersetzung bereit.
2.2 Ganzzahl-, Zeichen- und boolesche Typen auswählen
Ganzzahltypen speichern ganze Zahlen innerhalb eines festen Wertebereichs exakt. Wähle einen Typ anhand der Werte, die das Programm verarbeiten muss, einschließlich der Zwischenergebnisse, und danach, ob negative Werte sinnvoll sind.
Größen und Wertebereiche
Der Standard garantiert Mindestbreiten: mindestens 8 Bit für char, 16 für short und int, 32 für long und 64 für long long. Jeder Typ in dieser Reihenfolge ist mindestens so breit wie der vorherige. Die tatsächlichen Größen hängen von der Plattform ab.
| Typ | Typische Bytes unter 64-Bit-Linux/macOS | Unterschied unter 64-Bit-Windows |
|---|---|---|
char | 1 | Keiner |
short | 2 | Keiner |
int | 4 | Keiner |
long | 8 | 4 Bytes |
long long | 8 | Keiner |
sizeof(int) liefert die Größe in Bytes als size_t; gib sie mit %zu aus. Die Headerdatei <limits.h> stellt Namen wie INT_MIN, INT_MAX, UINT_MAX, LLONG_MAX und CHAR_BIT, die Anzahl der Bits pro Byte, bereit.
Mit N Wertbits nimmt eine vorzeichenlose Ganzzahl Werte von 0 bis 2ᴺ − 1 an. Eine vorzeichenbehaftete Ganzzahl mit N Bits im Zweierkomplement reicht von −2ᴺ⁻¹ bis 2ᴺ⁻¹ − 1. Ein 32-Bit-int reicht somit von −2.147.483.648 bis 2.147.483.647, während ein 32-Bit-unsigned int bis 4.294.967.295 reicht. C23 schreibt das Zweierkomplement vor; C17 erlaubte auch andere Darstellungen.
In einem 8-Bit-Muster im Zweierkomplement hat das höchstwertige Bit den Stellenwert −128. 11111111 stellt −1 dar und 10000000 stellt −128 dar. Um −5 zu bilden, beginne mit 00000101, invertiere die Bits zu 11111010 und addiere eins, um 11111011 zu erhalten.
Auch Zeichen sind Ganzzahlen
Ein char speichert einen Zeichencode in einem Byte. Verwende einfache Anführungszeichen für eine Zeichenkonstante, wie in 'A'. In ASCII hat 'A' den Code 65 und '0' den Code 48. printf("%c %d\n", 'A', 'A'); gibt in ASCII A 65 aus. Eine Zeichenkonstante hat in C den Typ int, sodass sizeof('A') gleich sizeof(int) ist.
Ein einfaches char kann je nach Implementierung vorzeichenbehaftet oder vorzeichenlos sein. Verwende signed char oder unsigned char, wenn der numerische Wertebereich wichtig ist. <stdint.h> bietet Ganzzahltypen mit exakter Bitbreite wie int32_t und uint8_t auf Plattformen an, die diese exakten Breiten unterstützen; gängige Plattformen tun das.
Ganzzahlliterale lesen
Eine Dezimalkonstante wie 42 hat den Typ int, wenn sie darin darstellbar ist. Suffixe fordern andere Typen an: 42u ist unsigned, 42L ist long, 42LL ist long long und 42ULL ist unsigned long long. Die hexadezimale Schreibweise verwendet 0x, wie in 0x2A. Eine führende null bedeutet oktale Schreibweise: 052 bedeutet ebenfalls 42 und 010 bedeutet 8. 09 ist keine gültige Oktalzahl. C23 ergänzt die binäre Schreibweise wie 0b101010.
Wahrheitswerte und Typwahl
In C17 bindest du für bool, true und false die Headerdatei <stdbool.h> ein; C23 macht sie zu Schlüsselwörtern. Eine Umwandlung in bool ergibt für null falsch und für jeden Wert ungleich null wahr, einschließlich 0.5. Gib einen booleschen Wert mit %d aus, um 0 oder 1 zu sehen.
Verwende int für gewöhnliche Anzahlen und Schleifenzähler. Verwende long long oder einen geeigneten Typ mit exakter Bitbreite, wenn der Wertebereich etwa 2,1 Milliarden überschreiten kann. Vorzeichenlose Typen eignen sich für Bitmuster, aber eine Subtraktion unter null erzeugt einen Wraparound zu einem großen Wert, sodass weiterhin Wertebereichsprüfungen nötig sind.
Wichtige Erkenntnisse
- Nutze die Typgrenzen, statt die Größe von
longzu schätzen. - Ein vorzeichenbehafteter 32-Bit-
intreicht bis 2.147.483.647. - Vorzeichenlose Werte haben keinen negativen Wertebereich und erzeugen bei Unterschreitung von null einen Wraparound.
- Eine führende null kennzeichnet ein oktales Ganzzahlliteral.
- Ob ein einfaches
charvorzeichenbehaftet ist, hängt von der Implementierung ab. - In C17 stammen die booleschen Namen aus
<stdbool.h>.
2.3 Dezimalwerte und begrenzte Genauigkeit
Ein Gleitkommatyp speichert eine Näherung einer reellen Zahl. Er hat einen großen Wertebereich, aber nur eine feste Anzahl signifikanter Stellen. Wertebereich und Genauigkeit beantworten unterschiedliche Fragen: wie groß ein Wert sein kann und wie fein sich benachbarte Werte unterscheiden lassen.
Wähle einen Gleitkommatyp
Auf gängigen IEEE-754-Systemen belegt float 4 Bytes und bietet etwa 6–7 signifikante Dezimalstellen. double belegt 8 Bytes und bietet etwa 15–16. Ihre größten endlichen Werte betragen etwa 3,4 × 10³⁸ und 1,8 × 10³⁰⁸. long double ist mindestens so genau wie double und hat ein plattformabhängiges Format. double ist eine nützliche Standardwahl für Messgrößen.
Konstanten wie 2.0, 3.14 und 1e-3 sind double; 1e-3 bedeutet 0,001. Ergänze f für float, wie in 2.5f, oder L für long double. Die Konstante 2 ist eine Ganzzahl und kann daher zu einer Ganzzahldivision führen, wo 2.0 eine Gleitkommadivision ergeben würde.
Die gespeicherte Näherung ansehen
Viele Dezimalbrüche haben keine exakte endliche Binärdarstellung. Auf einem gängigen System mit doppelter Genauigkeit gilt:
printf("%.20f\n", 0.1);
printf("%.17f\n", 0.1 + 0.2);
Die Ausgaben sind 0.10000000000000000555 und 0.30000000000000004. Die zusätzlichen Stellen zeigen die gespeicherte Näherung. Sie erklären, warum 0.1 + 0.2 == 0.3 falsch sein kann.
Verwende beim Vergleich berechneter Messwerte eine Toleranz:
#include <math.h>
/* inside main */
if (fabs(a - b) < 1e-9) {
printf("Close enough\n");
}
fabs liefert eine absolute Differenz. Wähle die Toleranz passend zur Größenordnung und Genauigkeit der Werte; 1e-9 ist ein Beispiel, keine allgemeingültige Einstellung. Manche Linux-Erstellungen benötigen -lm am Ende des Compilerbefehls, um mathematische Funktionen zu linken.
Die Genauigkeit wirkt sich auf große ganze Zahlen aus
Ein binärer double stellt jede ganze Zahl bis 2⁵³ exakt dar. Ein binärer float stellt oberhalb von 2²⁴ = 16.777.216 nicht mehr jede ganze Zahl dar: Wird einem solchen float der Wert 16.777.217 zugewiesen, ergibt sich 16.777.216. Ein größerer Wertebereich bedeutet nicht, dass jeder ganzzahlige Schritt verfügbar bleibt.
Die Ausgabe rundet den gespeicherten Binärwert. printf("%.2f\n", 2.675); gibt häufig 2.67 aus, weil der gespeicherte Wert etwas kleiner als 2,675 ist. Verwende für exakte Beträge in ganzen Cent eine Ganzzahl wie long long cents = 1999; und formatiere die Anzeige erst am Ende.
Bewusst umwandeln und runden
Die Umwandlung in eine Ganzzahl verwirft den Nachkommateil in Richtung null: (int)3.99 ist 3 und (int)-3.99 ist −3. round aus <math.h> gibt einen auf die nächste ganze Zahl gerundeten Gleitkommawert zurück; halbe Werte werden von null weg gerundet: round(-2.5) ergibt −3,0. Wandle das Ergebnis nur dann in eine Ganzzahl um, wenn es darstellbar ist. Eine Umwandlung von Gleitkommawerten in Ganzzahlen, deren Ganzzahlanteil außerhalb des Wertebereichs liegt, führt zu undefiniertem Verhalten.
In der IEEE-Arithmetik können Ergebnisse außerhalb des endlichen Wertebereichs unendlich werden. Die Gleitkommadivision eines Werts ungleich null durch null kann Unendlich ergeben; null durch null ergibt NaN, was „keine Zahl“ bedeutet. Ein NaN ist selbst bei einem Vergleich mit sich selbst ungleich. Dies ist das Verhalten von IEEE-Gleitkommawerten; die Ganzzahldivision durch null führt zu undefiniertem Verhalten.
Wichtige Erkenntnisse
- Gleitkommatypen haben sowohl eine begrenzte Genauigkeit als auch einen begrenzten Wertebereich.
- Vergleiche berechnete Messwerte mit einer geeigneten Toleranz.
- Die Ausgabe rundet die gespeicherte Näherung.
- Die Umwandlung von Gleitkommawerten in Ganzzahlen schneidet in Richtung null ab und setzt ein darstellbares Ergebnis voraus.
3Mit printf Ausgaben anzeigen und mit scanf Eingaben lesen
Ein Programm braucht einen klaren Austausch von Text und Werten. Formatiere die Ausgabe so, dass Lesende Beschriftungen, Felder und gerundete Messwerte unterscheiden können, und stimme dann jede Eingabekonvertierung auf die Variable ab, die den Wert aufnimmt. Beim Lesen von Text sind mehrere Ergebnisse möglich: alle angeforderten Werte, nur einige Werte oder gar keine Konvertierung. Wenn du diese Ergebnisse und den ungelesenen Text prüfst, beruhen spätere Berechnungen auf Werten, die das Programm tatsächlich erhalten hat.
3.1 Werte im gewünschten Format ausgeben
printf wandelt Werte mithilfe eines Formatstrings in Text um. Gewöhnliche Zeichen werden wie geschrieben ausgegeben. Eine mit % beginnende Konvertierung gibt an, wie das nächste Argument angezeigt werden soll.
Stimme jede Konvertierung auf ihr Argument ab
printf("%d samples, %.2f kg\n", 4, 1.5);
Das gibt 4 samples, 1.50 kg und einen Zeilenumbruch aus. Die Konvertierungen entsprechen der Reihenfolge nach den Argumenten. Ein Format wandelt den Typ des übergebenen Arguments nicht in den von dir beabsichtigten Typ um.
| Werttyp oder Anzeige | Konvertierung |
|---|---|
int | %d oder %i |
unsigned int | %u |
long, long long | %ld, %lld |
size_t | %zu |
double oder ein zu double erweiterter float | %f, %e, %g |
long double | %Lf |
| Zeichen, Zeichenkettenliteral | %c, %s |
| Vorzeichenlos hexadezimal, oktal | %x oder %X, %o |
Ein float-Argument wird in printf zu double erweitert, sodass beide %f verwenden; %lf ist hier gleichbedeutend. Die Übergabe von 3 an %f ist eine Typabweichung und keine Aufforderung, 3.0 auszugeben. Verwende 3.0 oder (double)3. Ein unpassender Typ oder ein fehlendes Argument kann undefiniertes Verhalten verursachen. Erstelle das Programm mit aktivierten Warnungen, damit Formatstrings als Literale geprüft werden.
Breite und Genauigkeit festlegen
Eine Breite ist eine Mindestanzahl von Zeichen einschließlich Vorzeichen und Dezimalpunkt. Eine Gleitkomma-Genauigkeit nach dem Punkt steuert die Anzahl der Nachkommastellen für %f. In %8.2f ist acht die Mindestbreite und zwei die Anzahl der Stellen nach dem Dezimalpunkt. Eine breitere Zahl wird vollständig ausgegeben; die Breite schneidet sie nicht ab.
Für die Ganzzahl 42 ergibt %5d den Wert 42, %-5d ergibt 42 , %05d ergibt 00042 und %+d ergibt +42. Ohne Genauigkeitsangabe gibt %f sechs Nachkommastellen aus.
printf("%-6s%8s\n", "Item", "Price");
printf("%-6s%8.2f\n", "Tea", 2.5);
printf("%-6s%8.2f\n", "Cake", 12.75);
Jede Beschriftung belegt ein linksbündiges Feld der Breite sechs, gefolgt von einem rechtsbündigen Preisfeld der Breite acht. Beide Preise haben zwei Nachkommastellen, sodass ihre Dezimalpunkte untereinander stehen. Die Leerzeichen entstehen durch die Feldbreiten, nicht durch eine automatische Trennung zwischen Argumenten.
Wissenschaftliche und allgemeine Darstellung
%e verwendet wissenschaftliche Schreibweise: 12.345,678 wird bei der Standardgenauigkeit als 1.234568e+04 ausgegeben. Für %g zählt die Genauigkeit signifikante Stellen, standardmäßig sechs. Es verwendet wissenschaftliche Schreibweise, wenn der Dezimalexponent kleiner als −4 oder mindestens so groß wie die Genauigkeit ist, und entfernt nachgestellte Nullen. So gibt %g 0,0001 als 0.0001, 0,00001 als 1e-05 und 1.000.000,0 als 1e+06 aus. Es wählt nicht einfach die kürzestmögliche Schreibweise.
Sonderzeichen ausgeben
In einer C-Zeichenkette bedeutet \n einen Zeilenumbruch, \t einen Tabulator, \\ einen umgekehrten Schrägstrich und \" ein Anführungszeichen. Ein Prozentzeichen in einem Format lautet %%: printf("100%%\n"); gibt 100% und einen Zeilenumbruch aus.
printf fügt selbst keine Leerzeichen oder Zeilenumbrüche hinzu. printf("Count:"); printf("%d", 4); gibt Count:4 aus. Ein Terminal kann Text ohne Zeilenumbruch verzögert anzeigen; fflush(stdout); sendet eine noch ausstehende Eingabeaufforderung, bevor die Eingabe angefordert wird.
Wichtige Erkenntnisse
- Stimme Konvertierungen auf die tatsächlichen Argumenttypen ab.
- Die Feldbreite ist ein Minimum; die Genauigkeit von
%fzählt Nachkommastellen. - Die Genauigkeit von
%gzählt signifikante Stellen. - Schreibe
%%für ein wörtliches Prozentzeichen in einem Format. printffügt keine Leerzeichen oder Zeilenumbrüche hinzu, sofern das Format sie nicht vorgibt.
3.2 Eingaben lesen und das Gelesene prüfen
scanf liest Text, konvertiert ihn und speichert Werte in Variablen. Prüfe das Ergebnis, bevor du diese Variablen verwendest. Text, der mit Ziffern beginnt, kann nur teilweise akzeptiert werden. Eine erfolgreiche Konvertierung und eine vollständig gültige Eingabezeile sind daher verschiedene Dinge.
Gib ein Ziel des richtigen Typs an
int count;
double mass_kg;
if (scanf("%d %lf", &count, &mass_kg) != 2) {
return 1;
}
Das & übergibt die Adresse jeder Variablen, damit scanf darin speichern kann. %d benötigt einen int, %u eine vorzeichenlose Ganzzahl, %ld einen long und %lld einen long long. Für Gleitkommaeingaben benötigt %f einen float und %lf einen double. Anders als bei printf sind diese beiden Konvertierungen nicht austauschbar. Ein fehlendes & oder ein falscher Zieltyp kann undefiniertes Verhalten verursachen.
%d liest dezimal. %i erkennt auch hexadezimale und oktale Eingaben mit führender null; bei %i bedeutet der Text „010“ den Wert 8. Verwende %d für gewöhnliche dezimale Anzahlen.
Prüfe den Rückgabewert
scanf gibt die Anzahl der abgeschlossenen Zuweisungen zurück. Bei zwei angeforderten Werten bedeutet 2 Erfolg. Eine fehlgeschlagene Konvertierung lässt ihr Ziel unverändert. Es kann noch nicht initialisiert sein; verwende es daher nur, wenn die zurückgegebene Anzahl den Erfolg bestätigt. EOF bedeutet, dass die Eingabe endete oder ein Eingabefehler auftrat, bevor die erste Konvertierung abgeschlossen wurde.
Bei scanf("%d:%d", &h, &m) muss der Doppelpunkt wörtlich erscheinen. Die Eingabe „14:30“ füllt beide Variablen. Die Eingabe „14 30“ füllt h, findet dann keinen passenden Doppelpunkt und lässt m unverändert. Der Rückgabewert ist 1.
Leerraumzeichen und teilweise Eingaben verstehen
Numerische Konvertierungen überspringen führende Leerzeichen, Tabulatoren und Zeilenumbrüche. %c liest das unmittelbar nächste Zeichen, einschließlich eines verbliebenen Zeilenumbruchs. Verwende scanf(" %c", &choice), um vor dem Lesen eines sichtbaren Zeichens Leerraumzeichen zu überspringen. Vermeide einen abschließenden Zeilenumbruch im Format: Er kann dazu führen, dass der Aufruf auf ein späteres Zeichen wartet, das kein Leerraumzeichen ist.
Mit %d speichert „12abc“ den Wert 12 und lässt „abc“ ungelesen. „3.7“ speichert 3 und lässt „.7“ zurück. „abc“ speichert nichts und gibt 0 zurück. Wird dieselbe Konvertierung erneut aufgerufen, ohne die fehlerhafte Eingabe zu verbrauchen, kann sie endlos am selben Zeichen scheitern.
Eine fehlgeschlagene Konvertierung behandeln
int n, result;
while ((result = scanf("%d", &n)) != 1) {
if (result == EOF) {
return 1;
}
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) {
}
printf("Try again: ");
fflush(stdout);
}
getchar gibt einen int zurück und kann so sowohl ein Zeichen als auch EOF darstellen. Die innere Schleife verwirft die fehlerhafte Zeile. Die äußere Schleife versucht es erneut, akzeptiert aber weiterhin einen numerischen Anfang wie 12 in „12abc“. Wenn du verlangst, dass das nächste Zeichen '\n' ist, prüfst du die strengere Regel, dass auf eine Ganzzahl unmittelbar ein Zeilenumbruch folgt; diese Regel lehnt auch nachfolgende Leerzeichen ab. Eine Regelung, die Leerzeichen oder eine letzte Zeile mit EOF als Ende zulässt, muss diese Fälle ausdrücklich behandeln.
Prüfe nach einer erfolgreichen Konvertierung den zulässigen Bereich, etwa 1 bis 8 für eine Höheneinstellung. scanf weist einen numerischen Wert außerhalb des Wertebereichs nicht sicher zurück: Ein für seinen Zieltyp zu großer Wert führt zu undefiniertem Verhalten. Verwende Testeingaben, die im gewählten Typ darstellbar sind. Um ein Ergebnis wie Masse dividiert durch Größe zum Quadrat zu berechnen, prüfe, ob die Messwerte positiv sind, und halte das Quadrat als height_m * height_m zusammen.
Wichtige Erkenntnisse
- Übergib Variablenadressen und verwende
%lf, um einendoublezu lesen. - Prüfe, ob die zurückgegebene Anzahl der benötigten Anzahl von Werten entspricht.
- Eine erfolgreiche Konvertierung kann zusätzliche Eingaben ungelesen lassen.
- Verbrauche fehlerhafte Eingaben, bevor du eine fehlgeschlagene numerische Konvertierung erneut versuchst.
- Prüfe nach dem Lesen die zulässigen Werte; die Eingabekonvertierung hat eigene Wertebereichsgrenzen des Typs.
- Verwende
" %c", wenn beim Lesen eines Zeichens Leerraumzeichen übersprungen werden sollen.
4Mit Operatoren und Ausdrücken rechnen
Ein Ausdruck verbindet Werte, Typen und Operatoren. Verfolge zunächst Ganzzahldivisionen und Aktualisierungen und danach die Typumwandlungen, die bei jeder Operation erfolgen. Vergleiche und logische Schutzbedingungen steuern, ob eine Berechnung erreicht wird, während bitweise Operationen auf einzelne Bits wirken. Unterscheide schließlich die durch die Operatorpriorität festgelegte Gruppierung von der nötigen Sequenzierung, wenn ein Ausdruck eine Variable ändert. Sowohl der Wert als auch der Typ eines Zwischenergebnisses sind wichtig.
4.1 Arithmetik, Zuweisung und Aktualisierung um eins
Jeder C-Ausdruck hat einen Wert und einen Typ. Der Typ bestimmt, wie eine Operation funktioniert. Eine Ganzzahldivision kann zum Beispiel einen gebrochenen Teil des Ergebnisses nicht erhalten, selbst wenn du das Ergebnis später in einer Gleitkommavariablen speicherst.
Quotienten und Divisionsreste
+, - und * addieren, subtrahieren und multiplizieren. / dividiert und % liefert einen ganzzahligen Rest. Multiplikation, Division und Restbildung binden stärker als Addition und Subtraktion.
Sind beide Divisionsoperanden Ganzzahlen, schneidet C den Quotienten in Richtung null ab. 17 / 5 ist 3, -7 / 2 ist −3 und 7 / -3 ist −2. Der Rest erfüllt die Divisionsgleichung: Multiplizierst du den Quotienten mit dem Divisor und addierst den Rest, erhältst du den Dividenden. Daher ist -7 % 2 gleich −1 und 7 % -3 gleich 1. Ein Rest ungleich null hat das Vorzeichen des Dividenden.
Um auf eine gerade Zahl zu prüfen, verwende n % 2 == 0. Um bei positiven oder negativen Ganzzahlen auf eine ungerade Zahl zu prüfen, verwende n % 2 != 0; eine Prüfung auf genau 1 erfasst negative ungerade Werte nicht. % benötigt ganzzahlige Operanden; <math.h> stellt fmod für Gleitkommareste bereit. Ein ganzzahliger Divisor von null macht sowohl / als auch % undefiniert. Prüfe ihn daher vor der Operation.
Für 135 Minuten ergibt 135 / 60 zwei volle Stunden und 135 % 60 ergibt 15 verbleibende Minuten. Bei einer positiven dezimalen Ganzzahl ermittelt % 10 ihre letzte Ziffer und / 10 entfernt diese Ziffer: 4096 ergibt 6 und 409.
Erhalte Nachkommateile, wenn du sie brauchst
int correct = 45, total = 60;
double percent = 100.0 * correct / total;
Die Gleitkommakonstante macht die Multiplikation zu einer Gleitkommaoperation, und die Division ergibt 75,0. In correct / total * 100 ist die erste Operation eine Ganzzahldivision und ergibt null. Durch Multiplikation dieser null lässt sich der verlorene Nachkommateil nicht zurückgewinnen. Die vorherige ganzzahlige Multiplikation correct * 100 / total ergibt hier 75, sofern das Produkt darstellbar ist.
Einen neuen Wert speichern
Eine Zuweisung wertet ihre rechte Seite aus und speichert den umgewandelten Wert links. Der Zuweisungsausdruck hat diesen gespeicherten Wert, sodass a = b = 0; beide Variablen auf null setzt, wobei von rechts gruppiert wird.
Zusammengesetzte Zuweisungen verbinden eine Berechnung und das Speichern: x += 5 addiert fünf zu x, und x /= 2 dividiert x unter Verwendung seines aktuellen Typs durch zwei. Der gesamte rechte Operand gehört zur Berechnung. Ist x gleich 4, macht x *= 2 + 1 daraus 12, weil mit drei multipliziert wird.
Präfix- und Postfixaktualisierungen
++ addiert eins und -- subtrahiert eins. Als eigenständige Anweisungen haben i++; und ++i; dieselbe abschließende Wirkung. Innerhalb eines größeren Ausdrucks liefert die Postfixform den alten und die Präfixform den neuen Wert.
| Anfang | Anweisung | Endwerte |
|---|---|---|
a = 5 | y = a++; | y = 5, a = 6 |
a = 5 | y = ++a; | y = 6, a = 6 |
Halte Änderungen so, dass ihre Reihenfolge leicht erkennbar ist. i = i++ + 1; und printf("%d %d\n", n, n++); enthalten nicht sequenzierte, widersprüchliche Verwendungen derselben Variablen und führen zu undefiniertem Verhalten. Trenne Lesen, Aktualisieren und Ausgeben in einzelne Anweisungen, wenn ihre Reihenfolge wichtig ist.
Wichtige Erkenntnisse
- Die Ganzzahldivision schneidet in Richtung null ab.
- Ein ganzzahliger Divisionsrest ungleich null hat das Vorzeichen des Dividenden.
- Verwende vor der Division einen Gleitkommaoperanden, wenn du einen Nachkommateil benötigst.
- Die Postfixform liefert den alten Wert; die Präfixform liefert den aktualisierten Wert.
- Setze widersprüchliche Lesezugriffe und Aktualisierungen in getrennte Anweisungen.
4.2 Typen werden vor dem Speichern eines Ergebnisses bestimmt
C wandelt Werte bei der Anwendung jedes Operators um. Die Zielvariable erhält das fertige Ergebnis; sie greift nicht nachträglich in den Ausdruck ein und ändert frühere Operationen.
Vor der Division umwandeln
int sum = 7, count = 2;
double a = sum / count;
double b = (double)sum / count;
double c = (double)(sum / count);
a wird zu 3,0: Die beiden ganzzahligen Operanden ergeben die Ganzzahl 3, die anschließend umgewandelt wird. b wird zu 3,5: Die explizite Typumwandlung wandelt sum vor der Division um, sodass auch count in double umgewandelt wird. c wird zu 3,0: Seine explizite Typumwandlung steht außerhalb der Division und erfolgt, nachdem der Nachkommateil verworfen wurde.
Dieselbe Regel gilt innerhalb eines längeren Ausdrucks. 7 / 2 * 2.0 ergibt 6,0, weil das erste Ergebnis die Ganzzahl 3 ist. 7 / 2.0 * 2 ergibt 7,0. Gehe die Operatoren in ihrer Gruppierungsreihenfolge durch und notiere den Typ jedes Zwischenwerts.
Einen gemeinsamen arithmetischen Typ finden
Ist einer der Operanden ein Gleitkommawert, wandelt die Arithmetik im Allgemeinen in den breitesten beteiligten Gleitkommatyp um: long double, dann double, dann float. Bei Ganzzahlen durchlaufen kleine Typen zuerst die Ganzzahlerweiterung. Auf Plattformen mit 8-Bit-char, 16-Bit-short und 32-Bit-int sind alle Werte der kleineren Typen in int darstellbar, sodass diese Typen zu int erweitert werden.
Bei unsigned char a = 200, b = 100; ist a + b auf einem System mit 8-Bit-Bytes und 32-Bit-int die Ganzzahl 300. Während der Addition erfolgt bei 255 kein Wraparound. Das Speichern von 300 in einem vorzeichenlosen 8-Bit-Zeichentyp reduziert den Wert auf 44. Die Erweiterung kann auch einen Überlauf bei vorzeichenbehafteten Ganzzahlen erzeugen: Zwei unsigned-short-Werte von 50.000 werden als int multipliziert, aber 2.500.000.000 überschreitet einen 32-Bit-INT_MAX.
Bei vorzeichenbehafteten und vorzeichenlosen Typen bestimmen Rang und darstellbarer Wertebereich den gemeinsamen Typ. int mit unsigned int wird in einen vorzeichenlosen Typ umgewandelt. Ein vorzeichenbehafteter Typ höheren Rangs kann dafür sorgen, dass die Operation vorzeichenbehaftet bleibt, wenn er jeden Wert des vorzeichenlosen Typs darstellen kann; andernfalls kann dennoch vorzeichenlose Arithmetik entstehen.
Die Falle bei vorzeichenbehafteten und vorzeichenlosen Werten
Bei einer vorzeichenlosen 32-Bit-Ganzzahl ist -1 < 0u falsch. Die vorzeichenbehaftete −1 wird vor dem Vergleich in den vorzeichenlosen Wert 4.294.967.295 umgewandelt. Ebenso ergibt 2u - 10 den Wert 4.294.967.288. Auch ein sizeof-Ergebnis ist vorzeichenlos, was beim Vergleich mit einer möglicherweise negativen Anzahl wichtig ist. Aktiviere Warnungen und verwende für zusammengehörige Größen kompatible Typen.
Umwandlungen beim Speichern können Informationen verlieren
Eine Umwandlung von Gleitkommawerten in Ganzzahlen schneidet in Richtung null ab. Der Ganzzahlanteil muss darstellbar sein, sonst ist das Verhalten undefiniert. Eine in einen vorzeichenlosen N-Bit-Typ umgewandelte Ganzzahl wird modulo 2ᴺ reduziert; −1 wird zum größten vorzeichenlosen Wert. Unter C17 liefert eine Ganzzahlkonvertierung außerhalb des Wertebereichs in einen vorzeichenbehafteten Typ ein implementierungsdefiniertes Ergebnis oder löst ein implementierungsdefiniertes Signal aus. Diese Umwandlung unterscheidet sich vom undefinierten arithmetischen Überlauf bei vorzeichenbehafteten Ganzzahlen.
Auch Zeichenkonstanten nehmen an arithmetischen Operationen teil. '7' - '0' ergibt 7, weil C aufeinanderfolgende Codes für Dezimalziffern garantiert. In ASCII folgen auch die Buchstaben aufeinander, aber gehe nicht für jeden Zeichensatz davon aus. Bei Ganzzahlformeln kann eine Multiplikation vor der Division nur dann nützliche Informationen erhalten, wenn das Produkt in seinem Berechnungstyp darstellbar ist.
Wichtige Erkenntnisse
- Der Zieltyp ändert frühere arithmetische Operationen nicht.
- Wandle einen Operanden vor der Operation explizit um, die einen breiteren Typ oder einen Gleitkommatyp benötigt.
- Kleine Ganzzahltypen werden vor arithmetischen Operationen erweitert.
- Gemischte vorzeichenbehaftete und vorzeichenlose Operanden können Vergleiche und Subtraktionen verändern.
- Für Konvertierungen außerhalb des Wertebereichs und arithmetische Überläufe bei vorzeichenbehafteten Ganzzahlen gelten unterschiedliche Regeln.
- Achte auf Zwischentypen ebenso wie auf Endwerte.
4.3 Bedingungen und Bitmuster
Eine Bedingung prüft, ob etwas wahr ist. Eine bitweise Operation arbeitet auf den einzelnen Bits einer Ganzzahl. C stellt für diese Aufgaben unterschiedliche Operatoren bereit, auch wenn ihre Zeichen ähnlich aussehen.
Werte vergleichen und Prüfungen verknüpfen
Die Vergleiche <, >, <=, >=, == und != liefern einen int: 1 für wahr und 0 für falsch. In einer Bedingung ist null falsch und jeder Wert ungleich null wahr. Somit prüft if (count), ob count ungleich null ist. == vergleicht zwei Werte; = weist einen neuen zu.
&& bedeutet, dass beide Bedingungen wahr sein müssen, || bedeutet, dass mindestens eine wahr sein muss, und ! kehrt den Wahrheitswert um. Sie geben 0 oder 1 zurück. Ein Bereich von 1 bis einschließlich 8 benötigt zwei Vergleiche:
height >= 1 && height <= 8
1 <= height <= 8 drückt diesen Bereich in C nicht aus. Der Ausdruck wird als (1 <= height) <= 8 gruppiert, sodass der zweite Vergleich 0 oder 1 erhält und immer wahr ist. Um auf Werte außerhalb des Bereichs zu prüfen, schreibe height < 1 || height > 8.
Die Negation einer zusammengesetzten Bedingung verändert beide Teile: !(a && b) entspricht !a || !b und !(a || b) entspricht !a && !b. Das sind die de Morganschen Regeln. Sie erklären, warum für Werte außerhalb eines Intervalls ein „oder“ nötig ist, während für Werte innerhalb ein „und“ nötig ist.
Eine Schutzbedingung vor die riskante Operation setzen
if (d != 0 && n / d > 2) {
printf("Above two\n");
}
&& wertet zuerst seine linke Seite aus. Ist sie falsch, wird die rechte Seite übersprungen. || überspringt seine rechte Seite, wenn die linke Seite wahr ist. Diese Kurzschlussauswertung ermöglicht es einer Schutzbedingung, eine spätere Operation abzusichern. Würdest du die beiden obigen Teile umkehren, erfolgte die Division vor der Prüfung des Divisors. Bei vorzeichenbehafteten Ganzzahlen gibt es ein separates Problem mit dem Wertebereich, wenn INT_MIN auf einem System mit Zweierkomplement durch −1 dividiert wird; prüfe auch dieses Wertepaar, wenn beliebige Werte möglich sind.
Ein Schaltjahrtest verbindet Teilbarkeit und Ausnahmen: (y % 4 == 0 && y % 100 != 0) || y % 400 == 0. Das Jahr 2000 besteht den Test auf 400; 1900 besteht ihn nicht, weil die Ausnahme für Jahrhundertjahre gilt.
Bit für Bit arbeiten
Das bitweise & behält Bits, die in beiden Operanden gesetzt sind; | behält Bits, die in mindestens einem gesetzt sind; ^ behält Bits, die sich unterscheiden; ~ invertiert Bits. Mit 12 (1100) und 10 (1010) ergeben sich für &, | und ^ die Werte 8, 14 und 6.
Bitverschiebungen verschieben Bits. Für nicht negative Werte, bei denen das Ergebnis darstellbar ist, multipliziert x << k mit 2ᵏ, und x >> k verwirft k niederwertige Bits, dividiert also durch 2ᵏ und verwirft den Rest. Verwende für Bitoperationen vorzeichenlose Typen. Unter C17 ist eine Verschiebung um eine negative Anzahl oder um mindestens die Breite des erweiterten Typs undefiniert; die Linksverschiebung eines negativen vorzeichenbehafteten Werts ist undefiniert, und seine Rechtsverschiebung ist implementierungsdefiniert.
Bitpositionen beginnen bei null. flags | (1u << 3) setzt Bit 3; flags & ~(1u << 3) löscht es; (flags >> 3) & 1u liest es aus. 1 & 2 ist 0, weil kein Bit in beiden Werten gesetzt ist, während 1 && 2 gleich 1 ist, weil beide Werte ungleich null sind. Bitweise Operatoren werten beide Operanden aus und können logische Schutzbedingungen nicht ersetzen.
Wichtige Erkenntnisse
- Vergleiche und logische Operatoren liefern 0 oder 1.
- Eine Bereichsprüfung verbindet zwei Vergleiche mit
&&. - Schutzbedingungen mit Kurzschlussauswertung müssen vor der geschützten Operation stehen.
- Verwende logische Operatoren für Wahrheitswerte und bitweise Operatoren mit vorzeichenlosen Werten für Masken.
- Bitpositionen beginnen bei null; die Anzahl der Verschiebungen muss innerhalb der Breite des erweiterten Typs liegen.
4.4 Ausdrücke gruppieren und Seiteneffekte ordnen
Zwei Fragen helfen dir beim Lesen eines Ausdrucks: Welche Operanden gehören zu welchem Operator, und wann erfolgen Operationen mit Seiteneffekten? Operatorpriorität und Assoziativität beantworten die Frage nach der Gruppierung. Sie legen im Allgemeinen nicht die Reihenfolge der Operandenauswertung fest.
Beachte die Operatorpriorität
Operatoren, die in dieser Tabelle höher stehen, binden stärker. Jede Zeile steht unter der vorherigen; die durch „dann“ getrennten Operatoren haben eigene Stufen.
| Von hoch nach niedrig | Operatoren |
|---|---|
| Postfix | x++, x--, Aufrufe |
| Unär | ++x, --x, +, -, !, ~, explizite Typumwandlungen, sizeof, Adressoperator & |
| Multiplikativ | *, /, % |
| Additiv | +, - |
| Bitverschiebung | <<, >> |
| Vergleich | <, <=, >, >= |
| Gleichheit | ==, != |
| Bitweise | &, dann ^, dann ` |
| Logisch | &&, dann ` |
| Bedingt | ?: |
| Zuweisung | =, += und andere zusammengesetzte Zuweisungen |
| Komma | , |
Klammern setzen die Gruppierung außer Kraft. 2 + 3 * 4 ist 14; (2 + 3) * 4 ist 20.
Assoziativität bei gleichen Stufen nutzen
Die meisten binären arithmetischen Operatoren gruppieren von links nach rechts. 10 - 4 - 3 bedeutet (10 - 4) - 3, also 3. 100 / 10 / 2 ergibt 5. Zuweisungen gruppieren von rechts nach links, sodass a = b = 0 in beiden Variablen null speichert. Die Gruppierung beschreibt die Struktur des Ausdrucks; sie legt keine allgemeine Ausführungsregel von links nach rechts fest.
Verfolge für int a = 3, b = 4, c = 5; den Ausdruck a + b * c > 20 && c % 2 == 1. Die Multiplikation ergibt 20, die Addition 23 und 23 > 20 ergibt 1. Die Restbildung ergibt 1 und ihre Gleichheitsprüfung ergibt 1. Das abschließende 1 && 1 ist 1.
Bitprüfungen und Zuweisungen mit Klammern absichern
x & 1 == 0 bedeutet x & (1 == 0) und ergibt daher immer null. Schreibe (x & 1) == 0, um das niederwertigste Bit zu prüfen. Auch die Addition bindet stärker als die Bitverschiebung: 1 << 2 + 1 ist 1 << 3, also 8.
Die Zuweisung bindet schwächer als der Vergleich. a = b == c speichert das Vergleichsergebnis 0 oder 1 in a. In while ((ch = getchar()) != '\n') bewirken die inneren Klammern, dass die Zuweisung zuerst das Zeichen erhält. Ohne sie würde ch ein Vergleichsergebnis erhalten.
Einen Wert auswählen oder zwei Aktionen sequenzieren
condition ? first : second wertet die Bedingung und dann nur den ausgewählten Wertausdruck aus. max = (a > b) ? a : b; wählt einen Wert; ein if kann einen Block von Aktionen auswählen. Der Kommaoperator wertet zuerst seinen linken Ausdruck aus und liefert den Wert seines rechten Ausdrucks. Er kann zwei Aktualisierungen in einem for-Kopf koordinieren. Kommas zwischen Funktionsargumenten sind Trennzeichen und legen diese Reihenfolge nicht fest.
Seiteneffekte trennen
C lässt viele Reihenfolgen von Operanden und Argumenten offen. n++ * n++ enthält nicht sequenzierte Änderungen an derselben Variablen und führt daher zu undefiniertem Verhalten. Zusätzliche Klammern beheben die fehlende Sequenzierung nicht. Verwende getrennte Anweisungen, wenn eine Aktion vor einer anderen erfolgen muss. &&, ||, ?: und der Kommaoperator haben bestimmte Sequenzierungsregeln; die gewöhnliche arithmetische Operatorpriorität bietet keine solche Garantie.
Wichtige Erkenntnisse
- Die Operatorpriorität bestimmt die Gruppierung; sie bestimmt im Allgemeinen nicht die Auswertungsreihenfolge.
- Auf einer arithmetischen Stufe wird gewöhnlich von links nach rechts gruppiert.
- Setze ein bitweises Ergebnis in Klammern, bevor du es vergleichst.
- Der bedingte Operator wertet nur den ausgewählten Wertausdruck aus.
- Der Kommaoperator sequenziert Operanden; Kommas zwischen Argumenten tun das nicht.
- Trenne widersprüchliche Aktualisierungen, um undefiniertes Verhalten zu vermeiden.
5Den Programmablauf mit Bedingungen und Schleifen steuern
Der Programmablauf bestimmt, welche Anweisungen wie oft ausgeführt werden. Nutze Bedingungen, um einen Zweig auszuwählen, exakte ganzzahlige Fälle für den Einstieg in ein switch und Schleifen, um eine Aufgabe zu wiederholen, bis ihre Abbruchregel erfüllt ist. Ablaufprotokolle machen die Reihenfolge sichtbar: Anfangswerte, Prüfungen, Anweisungen im Rumpf und Aktualisierungen. Achte besonders auf Eingaben an den Grenzen und darauf, wohin break oder continue innerhalb verschachtelter Konstrukte führen, wo eine kleine Syntaxänderung den gesamten Ablauf verändern kann.
5.1 Den richtigen Zweig auswählen
Eine if-Anweisung entscheidet, ob eine Aktion ausgeführt wird. Ihre Bedingung muss in runden Klammern stehen. Null bedeutet falsch; jeder Wert ungleich null bedeutet wahr.
Zeige den Zweig mit geschweiften Klammern
if (temperature < 0) {
printf("Freezing\n");
} else {
printf("At or above freezing\n");
}
Der erste Block wird bei einer negativen Temperatur ausgeführt. Bei genau null ist der Vergleich falsch und der else-Block wird ausgeführt. Lies den Grenzwert direkt aus <, <=, > oder >= ab; die Meldung sollte zu dieser Grenze passen.
Ohne geschweifte Klammern wird nur die nächste Anweisung gesteuert:
if (temperature > 30)
printf("Hot\n");
printf("Drink water\n");
Die zweite Meldung wird bei jeder Temperatur ausgegeben. Einrückung hilft Menschen, ändert aber nicht die Grammatik. Mit geschweiften Klammern kannst du einem Zweig eine zweite Anweisung hinzufügen, ohne sie versehentlich außerhalb zu lassen.
Eine else-if-Kette ordnen
Eine if–else if–else-Kette führt den ersten Zweig aus, dessen Bedingung wahr ist, und überspringt dann den Rest. Beginne bei Punktebereichen mit der höchsten unteren Schranke:
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'F';
}
Eine Punktzahl von 85 besteht die erste Prüfung nicht und besteht die zweite. Eine Punktzahl von 90 besteht die erste. Würdest du score >= 70 zuerst prüfen, gelangte 85 in diesen Zweig und erreichte nie den vorgesehenen B-Zweig. Getrennte if-Anweisungen verhalten sich anders: Jede Bedingung wird unabhängig geprüft, sodass mehrere Blöcke ausgeführt werden können.
Achte auf zwei kleine Syntaxfehler
if (x = 5) weist 5 zu und behandelt diesen Wert ungleich null dann als wahr. Verwende if (x == 5) zum Vergleichen. Bei if (x = 0) erfolgt ebenfalls eine Zuweisung, aber die Bedingung ist falsch. Warnungen helfen, versehentliche Zuweisungen in Bedingungen zu erkennen.
In if (x > 0); ist das Semikolon eine leere gesteuerte Anweisung. Ein danach gesetzter Block wird unabhängig ausgeführt, selbst wenn x negativ ist. Setze kein Semikolon zwischen eine if-Bedingung und den dazugehörigen Block.
Ordne jedes else ausdrücklich zu
Ein else gehört zum nächstgelegenen früheren if, das noch kein else hat. Verwende bei verschachtelten Entscheidungen geschweifte Klammern, um die beabsichtigte Zuordnung klarzustellen. Eine Verschachtelung ist nützlich, wenn fehlgeschlagene Prüfungen unterschiedliche Meldungen benötigen: Prüfe zuerst, ob ein Messwert gültig ist, und dann, ob sein Wert im gewünschten Bereich liegt. Die Verknüpfung beider Prüfungen mit && funktioniert, wenn nur eine gemeinsame Aktion nötig ist.
Ausnahmen vor allgemeinen Regeln prüfen
Schaltjahre bieten ein nützliches Beispiel für eine geordnete Entscheidung. Prüfe zuerst die Teilbarkeit durch 400, die ein Schaltjahr bedeutet. Prüfe als Nächstes die Teilbarkeit durch 100, die bedeutet, dass es kein Schaltjahr ist. Prüfe dann die Teilbarkeit durch 4; andernfalls ist das Jahr kein Schaltjahr. Für 2000 gilt der erste Zweig. Für 1900 gilt der zweite. Eine Prüfung nur auf Teilbarkeit durch vier würde die Ausnahme für Jahrhundertjahre übersehen. Die Reihenfolge ist wichtig, weil allgemeine Regeln Fälle mit einer spezielleren Ausnahme einschließen können.
Wichtige Erkenntnisse
- Eine else-if-Kette führt nur ihren ersten wahren Zweig aus.
- Geschweifte Klammern steuern, welche Anweisungen zu einem Zweig gehören.
==vergleicht;=weist zu.- Ein Semikolon nach einer
if-Bedingung erzeugt einen leeren Zweig.
5.2 Feste Fälle mit switch auswählen
Ein switch wählt anhand eines ganzzahligen Werts einen Einstiegspunkt. Das ist nützlich für ein Befehlszeichen oder eine Menünummer mit mehreren festen Alternativen. Die Ausführung kann anschließend in spätere Fälle übergehen, wenn du sie nicht stoppst.
Einen Fall auswählen und verlassen
switch (choice) {
case 1:
printf("Start\n");
break;
case 2:
printf("Pause\n");
break;
default:
printf("Unknown choice\n");
break;
}
Ist choice gleich 2, beginnt das Programm bei case 2, gibt Pause aus und verlässt das switch bei break. Passt kein Fall, beginnt die Ausführung bei default. Ohne default überspringt ein nicht passender Wert das switch.
Der steuernde Ausdruck muss einen Ganzzahltyp haben, dazu gehören auch char und Aufzählungstypen. Jeder Fall muss ein eindeutiger ganzzahliger konstanter Ausdruck sein, etwa 3, 'q' oder ein geeigneter #define-Name. In C17 ist eine const int-Variable kein ganzzahliger konstanter Ausdruck für eine case-Marke. Gleitkommawerte und Zeichenketten können keine steuernden Werte eines switch sein.
Fall-through verstehen
case-Marken kennzeichnen Einstiegspunkte, keine getrennten Blöcke, die automatisch enden. Entfernst du oben das erste break, gibt die Auswahl 1 sowohl Start als auch Pause aus. Das Programm prüft nicht, ob choice gleich 2 ist, bevor es diesen Fall weiter ausführt. Die Ausführung stoppt erst bei einer Anweisung, die den Programmablauf an eine andere Stelle überträgt, oder am Ende des switch.
Mehrere Marken können eine Aktion gemeinsam nutzen:
case 'y':
case 'Y':
printf("Yes\n");
break;
Beide Zeichen führen zu derselben Aktion. Wenn ein Fall absichtlich eigene Anweisungen ausführt und in einen anderen übergeht, erkläre die Absicht mit einem Kommentar. C23 bietet außerdem [[fallthrough]];. Warnungen können helfen, ein fehlendes break zu finden, aber bei absichtlich gruppierten Marken muss die Aktion nicht wiederholt werden.
Gib falllokalen Variablen einen Block
Unter C17 kann eine Deklaration nicht direkt auf eine case-Marke folgen. Setze die Anweisungen des Falls in geschweifte Klammern:
case 3: {
int delay = 5;
printf("%d\n", delay);
break;
}
Der Block hält delay außerdem lokal für diesen Fall. C23 lockert die Einschränkung für Marken vor Deklarationen, aber geschweifte Klammern machen den Gültigkeitsbereich weiterhin deutlich.
Zwischen switch und if wählen
Verwende switch für exakte ganzzahlige Alternativen. Verwende if–else if für Ungleichungen, Bedingungen mit mehreren Variablen oder Gleitkommamesswerte. Gleich breite Punktebereiche lassen sich manchmal durch Ganzzahldivision in exakte Fälle umwandeln. Bei Punktzahlen von 0 bis 100 ergibt score / 10 für 85 den Wert 8; die Fälle 9 und 10 können den höchsten Bereich gemeinsam nutzen. Prüfe diesen Wertebereich, damit negative oder zu hohe Punktzahlen nicht ohne Meldung einem Bereich zugeordnet werden.
Ein switch innerhalb einer Schleife
Ein break verlässt das nächstgelegene umschließende switch oder die nächstgelegene umschließende Schleife. Innerhalb eines switch, das selbst in einer Schleife liegt, beendet das break nur das switch. Um die Menüschleife zu beenden, setze eine Variable, die von ihrer Bedingung geprüft wird, oder kehre aus main zurück, wenn das Programm enden soll. Ein continue in diesem switch bezieht sich auf die umschließende Schleife und führt zu deren nächster Iteration. Verfolge, auf welches Konstrukt sich jede Ablaufübertragung richtet, bevor du entscheidest, ob das Menü stoppt.
Wichtige Erkenntnisse
- Ein switch wählt anhand eines ganzzahligen Werts eine Einstiegsmarke aus.
- Die Ausführung läuft bis zu
break, einer anderen Ablaufübertragung oder dem Ende weiter. - case-Werte müssen unterschiedliche ganzzahlige konstante Ausdrücke sein.
- Verwende in C17 einen Block für eine falllokale Deklaration.
- Das
breakeines switch beendet nicht zugleich eine umschließende Schleife.
5.3 Wiederholen, bis die Daten das Ende bestimmen
Eine while-Schleife prüft vor jedem Durchlauf eine Bedingung. Eine do–while-Schleife prüft nach dem Rumpf und läuft daher mindestens einmal. Wähle die Form, bei der die Prüfung dort steht, wo das Programm sie benötigt.
Eine bedingungsgesteuerte Berechnung nachvollziehen
int n = 4096, sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
n % 10 ermittelt eine Ziffer und n /= 10 entfernt sie. Die Werte nach den Durchläufen sind:
| Durchlauf | sum | n |
|---|---|---|
| 1 | 6 | 409 |
| 2 | 15 | 40 |
| 3 | 15 | 4 |
| 4 | 19 | 0 |
Die nächste Bedingung ist falsch, sodass die Schleife mit einer Ziffernsumme von 19 endet. Beginnt n bei null, wird der Rumpf kein einziges Mal ausgeführt und die Summe bleibt null. Auch negative Eingaben überspringen diese Schleife, sodass ein Programm, das sie akzeptiert, eine festgelegte Behandlung benötigt.
Eine Zählschleife setzt einen Zähler, prüft und aktualisiert ihn. Andere Schleifen machen Fortschritt, indem sie Daten lesen oder break erreichen. Beim wiederholten ganzzahligen Halbieren von 100 ergeben sich die Werte 50, 25, 12, 6, 3 und 1: Es sind sechs Durchläufe nötig, um eins zu erreichen.
Die Eingabe steuert die Wiederholung
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
sum += x;
count++;
}
if (count != 0) {
printf("%.2f\n", (double)sum / count);
}
Bei der Eingabe „5 8 3 0“ beendet der Wächterwert null die Schleife und wird nicht addiert. Die Summe ist 16, die Anzahl ist 3 und das angezeigte arithmetische Mittel ist 5.33. Die Prüfung der zurückgegebenen Anzahl sichert die Verwendung von x ab; die Prüfung der Anzahl schützt die Division, falls der erste Wert null ist. Diese Berechnung setzt außerdem voraus, dass Eingaben und Summen in int darstellbar sind.
Ohne die Prüfung auf den Wächterwert stoppt while (scanf("%d", &x) == 1) bei einer fehlgeschlagenen numerischen Konvertierung oder am Eingabeende. In einem Terminal signalisiert unter Linux und macOS gewöhnlich Strg+D das Eingabeende; unter Windows verwende Strg+Z und anschließend Enter.
Vor der Prüfung einmal ausführen
int height;
do {
printf("Height (1-8): ");
fflush(stdout);
if (scanf("%d", &height) != 1) {
return 1;
}
} while (height < 1 || height > 8);
Die Aufforderung und das Lesen erfolgen vor der Bereichsprüfung. Bei den Werten null und neun wird wiederholt; die Werte eins und acht werden akzeptiert. Das Semikolon nach while (...) ist in dieser Form erforderlich. Die Konvertierungsprüfung beendet dieses Beispiel bei nicht numerischer Eingabe; die Bereichsprüfung allein kann eine fehlgeschlagene Konvertierung nicht behandeln.
Auf jedem fortgesetzten Pfad Fortschritt machen
break verlässt sofort die innerste Schleife. continue überspringt den Rest des Rumpfs und kehrt in einer while-Schleife zur Bedingung zurück. Steht eine Aktualisierung unter einem continue, wird auch sie übersprungen. Eine Schleife kann dann endlos denselben Wert prüfen.
Stelle vor der Ausführung einer Schleife fest, was sich ändert und warum die Abbruchbedingung schließlich erreicht wird. Ein bewusst gesetztes while (1) kann durch break enden; eine fehlende Zähleraktualisierung ist eine andere Situation. Strg+C stoppt ein Programm, das in einem Terminal festhängt. Teste die kleinste Eingabe und den Wert an der Abbruchgrenze, um Fehler durch null oder einen zusätzlichen Durchlauf zu erkennen.
Wichtige Erkenntnisse
whileprüft zuerst;do–whileprüft nach mindestens einem Durchlauf.- Ein Wächterwert beendet die Eingabe, ohne Teil der Daten zu werden.
- Initialisiere Akkumulatoren außerhalb der Schleife.
- Auf jedem fortgesetzten Pfad muss die Schleife der Abbruchbedingung näherkommen.
- Ein continue in
whilekann eine spätere Aktualisierung überspringen. - Prüfe vor der Berechnung eines arithmetischen Mittels, ob keine Datenwerte vorliegen.
5.4 Durchläufe zählen und Schleifen kombinieren
Eine for-Schleife hält ein Zählmuster in einem Kopf zusammen. Verschachtelte Schleifen wiederholen für jeden äußeren Durchlauf eine innere Aufgabe. Beides lässt sich leichter prüfen, wenn du die tatsächlichen Zählerwerte auflistest und jeden Ausstieg bestimmst.
Den Kopf der Reihe nach verfolgen
for (int i = 0; i < 3; i++) {
printf("%d ", i);
}
Die Initialisierung läuft einmal. Dann wird die Bedingung geprüft, der Rumpf bei wahrer Bedingung ausgeführt und vor der nächsten Prüfung die Aktualisierung ausgeführt. Die Ausgabe ist 0 1 2 . Die im Kopf deklarierte Variable ist lokal für die Schleife; deklariere sie vor der Schleife, wenn du ihren Endwert danach benötigst.
Für ein nicht negatives n läuft i = 0; i < n; i++ n-mal und durchläuft null bis n − 1. Mit i <= n läuft die Schleife einmal zusätzlich. Für eine positive Schrittweite s von a bis einschließlich b beträgt die Anzahl bei Ganzzahldivision (b - a) / s + 1, sofern die arithmetischen Ergebnisse und Zähleraktualisierungen darstellbar sind. Von 2 bis 20 in Schritten von 3 lauten die Werte 2, 5, 8, 11, 14, 17, 20: sieben Durchläufe.
Den Akkumulator außerhalb halten
int total = 0;
for (int i = 1; i <= 100; i++) {
total += i;
}
Die endgültige Summe ist 5050. Die Initialisierung muss einmal vor der Schleife erfolgen. Verschiebst du int total = 0; in den Rumpf, entsteht bei jedem Durchlauf eine neue Summe und die bisherige Arbeit geht verloren.
Zähler wählen, die das Ende erreichen können
Ein Semikolon direkt nach dem for-Kopf macht den Rumpf leer. Ein folgender Block wird dann nach der Schleife einmal ausgeführt. Verwende direkt an den Kopf anschließende geschweifte Klammern, um den Rumpf sichtbar zu machen.
Ein vorzeichenloser rückwärts zählender Zähler mit i >= 0 kann so nicht stoppen: Vorzeichenlose Werte sind immer nicht negativ, und das Dekrementieren von null erzeugt einen Wraparound zum Maximum. Ein Gleitkommazähler, der wiederholt 0.1 addiert, kann den exakten Wert 1.0 überspringen, sodass x != 1.0 als Abbruchregel unzuverlässig ist. Zähle mit einer Ganzzahl und berechne im Rumpf einen Messwert wie x = i / 10.0.
Mit verschachtelten Schleifen Zeilen bilden
Für jeden äußeren Durchlauf startet die innere Schleife und durchläuft ihre eigene Folge. Drei äußere und vier feste innere Durchläufe führen den inneren Rumpf zwölfmal aus. Hängt die innere Schranke von der Zeile ab, zähle jede Zeile gesondert.
int height = 3;
for (int row = 1; row <= height; row++) {
for (int s = 0; s < height - row; s++) printf(" ");
for (int h = 0; h < row; h++) printf("#");
printf("\n");
}
Die Zeilen enthalten zwei Leerzeichen und ein Rautenzeichen, ein Leerzeichen und zwei Rautenzeichen, dann drei Rautenzeichen. Der Zeilenumbruch gehört hinter die inneren Schleifen, sodass jeder äußere Durchlauf eine Zeile erzeugt.
Arbeit überspringen oder vorzeitig abbrechen
continue in einer for-Schleife führt zur Aktualisierung und dann zur nächsten Prüfung. Eine Schleife, die nur ungerade Werte von 1 bis 9 addiert, kann gerade Werte überspringen und mit 25 enden. break verlässt sofort die innerste Schleife. Bei verschachtelten Schleifen stoppt ein inneres break die äußere Schleife nicht.
Eine for-Schleife ist bei einem bekannten Zählerbereich oft die klarste Wahl; while eignet sich für datengesteuerte Wiederholungen und do–while für Aufgaben, die vor der Prüfung erfolgen müssen. Beim Umschreiben einer for-Schleife in eine while-Schleife ist bei continue besondere Sorgfalt nötig, weil die Aktualisierung auch auf diesem Pfad erfolgen muss.
Wichtige Erkenntnisse
- Eine
for-Schleife führt die Initialisierung einmal und die Aktualisierung nach jedem fortgesetzten Durchlauf aus. - Prüfe die durchlaufenen Werte, um Off-by-one-Fehler zu erkennen.
- Verwende Ganzzahlzähler mit ausreichendem Wertebereich.
- Verschachtelte Schleifen starten die innere Folge für jeden äußeren Durchlauf erneut.
- Ein inneres
breakverlässt nur die innere Schleife. - Ein continue in
forführt die Aktualisierung im Kopf weiterhin aus. - Initialisiere eine laufende Summe außerhalb des wiederholten Rumpfs.
6Ganzzahlüberläufe erkennen und verhindern
Endliche Ganzzahlbereiche beeinflussen Berechnungen auch dann, wenn das Endergebnis klein genug erscheint. Unterscheide Wraparound bei vorzeichenlosen Ganzzahlen, Überlauf bei vorzeichenbehafteten Ganzzahlen und bei der Konvertierung verlorene Werte. Untersuche dann die Typen der Zwischenoperationen. Vorbeugung beginnt vor einer riskanten Berechnung: Erweitere einen Operanden, weise Schranken nach oder verwende eine überlaufgeprüfte Operation. Wertebereiche von Zählern und Grenzwerttests helfen dir, Fehler zu finden, die bei gewöhnlichen kleinen Eingaben und kurzen Testläufen unentdeckt bleiben können.
6.1 Was geschieht, wenn eine Ganzzahl nicht darstellbar ist
Der Wertebereich einer Ganzzahl ist endlich. Ein mathematisches Ergebnis kann korrekt und dennoch für den zur Berechnung verwendeten C-Typ zu groß sein. Die Folgen hängen davon ab, ob die Operation vorzeichenlos, vorzeichenbehaftet oder eine Konvertierung ist.
Vorzeichenlose Arithmetik erzeugt einen Wraparound
Ein vorzeichenloses N-Bit-Ergebnis wird modulo 2ᴺ reduziert. Bei einem 32-Bit-unsigned int ergibt UINT_MAX + 1u null, 0u - 1u ergibt 4.294.967.295 und 3u - 5u ergibt 4.294.967.294. Das Ergebnis ist definiert, kann aber für die Aufgabe falsch sein: Fünf Artikel von einem Bestand von drei abzuziehen, sollte nicht Milliarden Artikel ergeben.
Für uint8_t b = 255; speichert b = b + 1; den Wert null. Auf einem typischen System mit 32-Bit-int erfolgt die Addition zunächst als int und ergibt 256; die Rückkonvertierung dieses Werts in einen vorzeichenlosen 8-Bit-Typ ergibt null. Eine kleine vorzeichenlose Speichervariable bedeutet nicht, dass jede Zwischenoperation diesen kleinen Typ hat.
Arithmetischer Überlauf bei vorzeichenbehafteten Ganzzahlen ist undefiniert
Bei einem 32-Bit-int benötigt die Addition von eins zu INT_MAX den Wert 2.147.483.648. C definiert das vorzeichenbehaftete arithmetische Ergebnis nicht. Die Hardware scheint möglicherweise einen Wraparound zu INT_MIN durchzuführen, aber der Compiler kann unter der Annahme optimieren, dass ein Überlauf bei vorzeichenbehafteten Ganzzahlen nie auftritt. Eine Prüfung wie if (x + 1 < x) kann bei vorzeichenbehafteten Ganzzahlen entfernt werden, weil jede definierte vorzeichenbehaftete Addition von eins einen größeren Wert ergibt.
Berechne nicht zuerst und untersuche dann den Schaden. Eine undefinierte Operation ist bereits geschehen, bevor eine spätere Prüfung ausgeführt wird.
Ein breiterer Zieltyp kann zu spät kommen
int a = 50000, b = 50000;
long long product = a * b;
Die Multiplikation wird als int ausgeführt und läuft daher auf einem System mit 32-Bit-int über. Die Breite des Zieltyps kann das nicht beheben. (long long)a * b ändert den Typ der Operation vor der Multiplikation und ergibt 2.500.000.000.
Fakultäten wachsen schnell: 12! ist 479.001.600 und in einer vorzeichenbehafteten 32-Bit-Ganzzahl darstellbar; 13! ist 6.227.020.800 und nicht darstellbar. Mit vorzeichenloser 32-Bit-Arithmetik ergibt 13! durch Wraparound 1.932.053.504. Ein vorzeichenbehafteter 64-Bit-long long fasst 20! = 2.432.902.008.176.640.000, aber nicht 21!.
Für Konvertierungen gelten eigene Regeln
Die Umwandlung einer Ganzzahl in einen vorzeichenlosen Typ verwendet eine Reduktion modulo. Unter C17 ergibt die Umwandlung einer Ganzzahl außerhalb des Wertebereichs in einen vorzeichenbehafteten Typ ein implementierungsdefiniertes Ergebnis oder Signal. Die Umwandlung eines Gleitkommawerts, dessen abgeschnittener Ganzzahlanteil im ganzzahligen Zieltyp nicht darstellbar ist, führt zu undefiniertem Verhalten. Auch das Einlesen einer Zahl außerhalb des Wertebereichs durch eine numerische scanf-Konvertierung ist undefiniert.
Fehler beim Wertebereich können große Folgen haben
Der erste Flug der Ariane 5 am 4. Juni 1996 ging verloren, nachdem ein Gleitkommawert in der Trägheitsreferenzsoftware nicht in einer vorzeichenbehafteten 16-Bit-Ganzzahl darstellbar war. Das Programm war in Ada geschrieben; die Lehre, Wertebereiche bei Konvertierungen zu prüfen, gilt auch für C.
Ein vorzeichenbehafteter 32-Bit-Sekundenzähler, der zur Unix-Epoche beginnt, erreicht am 19. Januar 2038 um 03:14:07 Uhr UTC den Wert 2.147.483.647. Systeme, die diese Zeitdarstellung verwenden, benötigen eine breitere. 2015 befasste sich die FAA außerdem mit der Generatorsteuerungssoftware der Boeing 787, die nach 248 Tagen ununterbrochener Stromversorgung überlaufen konnte. Diese Fehlfunktionen zeigen, warum sowohl lange Laufzeiten als auch Zwischenwerte in eine Wertebereichsschätzung gehören.
Für Gleitkommawerte gelten andere Grenzen: Gängige IEEE-Systeme können Unendlich erzeugen, und ein float kann die Genauigkeit für ganze Zahlen verlieren, bevor er sein Maximum erreicht. Ein Wraparound bei vorzeichenlosen Ganzzahlen erzeugt einen gewöhnlichen Zahlenwert; ein Überlauf bei vorzeichenbehafteten Ganzzahlen bietet überhaupt kein verlässliches Ergebnis.
Wichtige Erkenntnisse
- Vorzeichenlose Arithmetik verwendet eine Reduktion modulo.
- Ein arithmetischer Überlauf bei vorzeichenbehafteten Ganzzahlen ist undefiniertes Verhalten.
- Ein breiterer Zieltyp kann eine Zwischenoperation in einem schmaleren Typ nicht korrigieren.
- Für Ganzzahlkonvertierung, Gleitkommakonvertierung und arithmetischen Überlauf gelten unterschiedliche Regeln.
- Schätze Werte über die gesamte Betriebsdauer ab.
6.2 Überläufe verhindern, bevor sie auftreten
Beginne mit dem größten Wert, den jede Berechnung erzeugen kann. Berücksichtige Zwischenprodukte und Zähleraktualisierungen, nicht nur das Endergebnis. Wähle dann einen Typ oder eine Vorabprüfung, die jede Operation im Wertebereich hält.
Den Typ für die Berechnung früh erweitern
Eine vorzeichenbehaftete 32-Bit-Ganzzahl fasst etwa 2,1 Milliarden, während ein vorzeichenbehafteter 64-Bit-long long bis zu 9.223.372.036.854.775.807 fasst. Ein Millisekundenzähler erreicht die vorzeichenbehaftete 32-Bit-Grenze nach etwa 24,9 Tagen. Verwende einen 64-Bit-Typ, wenn eine Zeitdauer länger sein kann.
Schreibe (long long)a * b, um den Typ eines Ganzzahlprodukts vor dessen Auswertung zu erweitern. Auch Konstanten sind wichtig: 86400LL * days beginnt die Multiplikation in long long. (long long)(a * b) wandelt erst nach dem Produkt im schmaleren Typ um und kann dessen Überlauf daher nicht verhindern. Auch breitere Typen haben Grenzen; prüfe die größtmöglichen Werte dagegen.
Addition durch Subtraktion prüfen
Binde für int a und int b die Headerdatei <limits.h> ein und prüfe:
if ((b > 0 && a > INT_MAX - b) ||
(b < 0 && a < INT_MIN - b)) {
printf("Sum out of range\n");
} else {
int sum = a + b;
printf("%d\n", sum);
}
Jede Subtraktion wird durch das Vorzeichen von b abgesichert, sodass die Prüfung selbst darstellbar bleibt. Für 2.000.000.000 plus 200.000.000 ist INT_MAX - b gleich 1.947.483.647 und damit kleiner als a, sodass die Addition abgelehnt wird. Der Fall der Gleichheit ist darstellbar: Wenn a gleich INT_MAX - b ist, ist die Summe genau INT_MAX.
Positive Multiplikation durch Division prüfen
Für positive int-Operanden bedeutet a > INT_MAX / b, dass das Produkt das Maximum überschreiten würde. Die Bedingung vermeidet die Berechnung des Produkts und einen Divisor von null, weil b positiv ist. Bei einer Fakultätsberechnung erkennt die Prüfung vor der Multiplikation von 12! mit 13, dass 479.001.600 größer als 165.191.049 ist. Stoppe vor dieser Multiplikation.
Prüfe bei einem vorzeichenlosen Bestand vor der Subtraktion stock >= requested. Der alleinige Wechsel zu einem vorzeichenlosen Typ verhindert kein unsinniges Ergebnis durch Wraparound.
Riskante Zwischenausdrücke umschreiben
Der Mittelpunkt (low + high) / 2 kann überlaufen, selbst wenn sein Ergebnis darstellbar ist. Wenn 0 <= low <= high gilt und beide Werte int sind, verwende low + (high - low) / 2. Die Differenz ist nicht negativ und liegt im Wertebereich, und das Ergebnis überschreitet high nie. Diese Bedingungen sind wichtig; die Formel ist kein allgemeiner Nachweis für beliebige vorzeichenbehaftete Endpunkte.
Auch Schleifenzähler benötigen einen ausreichenden Wertebereich. Ein 16-Bit-short kann 40000 nicht erreichen, sodass eine Schleife, die damit so weit zählt, nicht wie beabsichtigt enden kann. Verwende einen Zählertyp, der die Schranke und jede abschließende Aktualisierung darstellen kann.
Überlaufgeprüfte Arithmetik und Tests verwenden
C23 stellt <stdckdint.h> mit ckd_add, ckd_sub und ckd_mul bereit. Zum Beispiel speichert ckd_add(&r, a, b) ein Ergebnis und gibt wahr zurück, wenn das mathematische Ergebnis nicht in r darstellbar ist. GCC und clang stellen außerdem __builtin_add_overflow, __builtin_sub_overflow und __builtin_mul_overflow als Compilererweiterungen bereit.
Warnungen erkennen manche konstanten Überläufe und Fehler mit vorzeichenbehafteten und vorzeichenlosen Werten. In GCC oder clang kann -fsanitize=undefined -g einen bei einem Test erreichten Überlauf bei vorzeichenbehafteten Ganzzahlen melden; -ftrapv fordert das Anhalten bei Überläufen vorzeichenbehafteter Addition, Subtraktion und Multiplikation an. Teste null, eins, minus eins, die größte erwartete Eingabe und Werte an und knapp unter den Grenzen. Ein Testwerkzeug prüft die Pfade, die du ausführst. Halte daher die Überlegungen zum Wertebereich im Code fest.
Wichtige Erkenntnisse
- Erweitere einen Operanden vor einer riskanten Operation.
- Verwende bei vorzeichenbehafteter Addition Vorabprüfungen, die das Vorzeichen berücksichtigen.
- Die Divisionsprüfung vor einer Multiplikation setzt positive Operanden voraus.
- Auch die vorzeichenlose Subtraktion benötigt eine Wertebereichsprüfung.
- Die sicherere Mittelpunktformel setzt geordnete nicht negative Endpunkte voraus.
- Überlaufgeprüfte Arithmetik meldet, ob das Ergebnis im Zieltyp darstellbar ist.
- Grenzwerttests und Sanitizer ergänzen die Überlegungen zum Wertebereich.
Datentypen und Operatoren in C: Beispiele
1Eine Begrüßung erstellen und eine fehlgeschlagene Änderung untersuchen
9 Schritte
- 1EinführungDie Programmerstellung verfolgen
Ein gespeichertes C-Programm ist Text. Für seine Ausführung wird eine aus diesem Text erzeugte ausführbare Datei benötigt. Halte die Rollen der beiden Dateien auseinander: Eine Bearbeitung ändert den Quellcode, eine erfolgreiche Erstellung ersetzt die ausführbare Datei.
Du wirst eine kurze Begrüßung erstellen, untersuchen, was in jeder Phase geschieht, und eine fehlgeschlagene Änderung analysieren. Entscheidend ist, das Ergebnis der Erstellung zu prüfen, bevor du einen Programmlauf als Beleg für den neuen Quellcode heranziehst.
- 2ErklärungDas Grundgerüst des Programms lesen
Das Programm beginnt bei
main:#include <stdio.h> int main(void) { printf("Ready\n"); return 0; }Die Headerdatei stellt die Deklaration von
printfbereit. Der Rumpf gibt eine Zeile aus und meldet dann Erfolg zurück.voidbedeutet hier, dass main keine Parameter hat;intbezeichnet den Typ seines Rückgabewerts. Geschweifte Klammern umschließen den Rumpf, und einfache Anweisungen enden mit Semikolons. - 3BeispielErstellen und ausführen
Speichere den Quellcode als
ready.c. Gib in seinem Ordner ein:gcc -std=c17 -Wall -Wextra ready.c -o ready ./readyDie Erstellung wählt C17 und nützliche Warnungen aus und gibt der ausführbaren Datei den Namen
ready. Der Programmlauf gibtReadygefolgt von einem Zeilenumbruch aus. Unter Linux oder macOS wählt./den aktuellen Ordner aus. Du kannst an derselben Stelle im Befehl clang verwenden; ausführbare Dateien unter Windows erhalten die Endung.exe. - 4VerbindungDie Rolle des Objektcodes erkennen
Die Vorverarbeitung behandelt die include-Direktive. Die Kompilierung erzeugt anschließend Assemblersprache, und die Assemblierung erzeugt Objektcode. Das Linken löst Verweise auf und stellt die ausführbare Datei fertig; Bibliothekscode kann zur Laufzeit von einer gemeinsam genutzten Bibliothek bereitgestellt werden.
Die Optionen
-E,-Sund-cstoppen in jeweils späteren Phasen. Insbesondere stoppt-cbei einer Objektdatei: Der für die ausführbare Datei nötige Linkschritt ist noch nicht abgeschlossen. - 5HerausforderungDen nächsten Programmlauf vorhersagen
Du erstellst das Begrüßungsprogramm und speicherst dann eine Änderung, die „Ready“ in „Begin“ ändert. Du führst die vorhandene ausführbare Datei aus, ohne zu kompilieren. Welcher Text erscheint und wie änderst du ihn?
---reveal---
Sie gibt weiterhin
Readyaus. Die ausführbare Datei entspricht der früheren erfolgreichen Erstellung. Kompiliere die gespeicherte Änderung und führe die neue ausführbare Datei aus, umBeginauszugeben. Das bloße Speichern des Quellcodes aktualisiert den Maschinencode nicht. - 6BeispielDen ersten Fehler beheben
Nach einer Änderung fehlt dieser Zeile ein Semikolon:
printf("Begin\n") return 0;Der Compiler kann auf die erste Zeile oder auf
returnzeigen, wo er das fehlende Ende der Ausdrucksanweisung bemerkt. Ergänze das Semikolon nach dem Aufruf und erstelle das Programm erneut. Beginne mit dieser ersten Ursache, auch wenn spätere Meldungen andere Zeilen nennen; ein einzelner Syntaxfehler kann das Lesen des folgenden Codes durch den Compiler beeinträchtigen. - 7TippWarnungen als nützliche Hinweise behandeln
Eine Warnung lässt die Erstellung gewöhnlich weiterlaufen, kann aber auf einen schwerwiegenden Fehler hinweisen.
printf("%d\n", 2.5);übergibt einen double, wo das Format einen int verlangt, was zu undefiniertem Verhalten führt.Behebe die Typabweichung, statt eine beliebige Ausgabe eines Testlaufs zu akzeptieren. Warnungen von
-Wall -Wextrahelfen, solche Fehler aufzudecken;-Werrorlässt sie die Erstellung stoppen. Gib bei einer falschen Berechnung Zwischenwerte aus und vergleiche sie mit einem kleinen Ablaufprotokoll von Hand. - 8HerausforderungEine fehlgeschlagene Erstellung von einem falschen Programmlauf unterscheiden
Nach einer erfolgreichen Erstellung des Begrüßungsprogramms lässt sich eine neue Änderung nicht kompilieren. Die Ausführung von
./readygibt weiterhin eine Begrüßung aus. Zeigt das, dass die Änderung zulässig ist, und was solltest du als Nächstes untersuchen?---reveal---
Nein. Die alte ausführbare Datei kann nach einer fehlgeschlagenen Erstellung erhalten bleiben. Untersuche den ersten Compilerfehler einschließlich der vorherigen Quellcodezeile, behebe ihn und erstelle das Programm erfolgreich neu. Führe dann diese neue ausführbare Datei aus und vergleiche ihre Ausgabe mit deiner Vorhersage. Die vorhandene Begrüßung sagt nichts über die fehlgeschlagene Änderung aus.
- 9ZusammenfassungEinen klaren Korrekturzyklus verwenden
Beziehe jeden Test auf den Quellcode, aus dem tatsächlich eine ausführbare Datei erstellt wurde. Speichere die Änderung, kompiliere, lies die erste Meldung, behebe ihre Ursache und erstelle das Programm erneut, bevor du es ausführst.
Wenn das Programm erstellt wird, aber den falschen Wert berechnet, verwende ein kleines Ablaufprotokoll und Eingaben an den Grenzen. Führt eine Operation zu undefiniertem Verhalten, ändere den Code so, dass es nicht auftreten kann; eine normal aussehende Ausgabe ist für diese Operation kein verlässliches Ergebnis.
2Speicher für Anzahlen und Messwerte auswählen
13 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungFragen, was ein Wert benötigt
- 2BeispielEin kopierter Wert bleibt eine Kopie
- 3ErklärungNamen innerhalb ihrer Blöcke halten
- 4HerausforderungEinen verdeckten Namen verfolgen
- 5ErklärungVor der Auswahl Wertebereiche vergleichen
- 6BeispielEin Bitmuster und ein Literal lesen
- 7HerausforderungEinen portablen Wertebereich wählen
- 8ErklärungErkennen, warum Dezimalbrüche abweichen
- 9BeispielMit einem erlaubten Fehler vergleichen
- 10HerausforderungKonvertierung und Rundung unterscheiden
- 11KernaussageDie Genauigkeit kann vor dem Wertebereich an ihre Grenze stoßen
- 12HerausforderungDen Typ auf die Aufgabe abstimmen
- 13ZusammenfassungDie Speicherung auf zwei Arten prüfen
3Messwerte einlesen und Ergebnisse klar ausgeben
11 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungTypisierte Werte mit Text verbinden
- 2ErklärungDas Ausgabeformat lesen
- 3BeispielEine ausgerichtete Anzeige erstellen
- 4HerausforderungEine aufgefüllte Ganzzahl vorhersagen
- 5ErklärungIn Ziele des richtigen Typs einlesen
- 6BeispielEine teilweise Konvertierung erkennen
- 7TippEinen verbliebenen Zeilenumbruch nicht versehentlich lesen
- 8HerausforderungVerfolgen, was bei einem fehlgeschlagenen Lesen erhalten bleibt
- 9BeispielDen zulässigen Bereich der Berechnung prüfen
- 10HerausforderungEinlesen und Anzeige verbinden
- 11ZusammenfassungZwei Prüfungen bei der Eingabe verwenden
4Einen Ausdruck von den Typen bis zu den Auswirkungen lesen
17 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungWerte und Typen notieren
- 2BeispielEine Größe in ganze Teile zerlegen
- 3ErklärungDie beiden Inkremente unterscheiden
- 4HerausforderungEine zusammengesetzte Aktualisierung verfolgen
- 5BeispielDen Gleitkommaoperanden früh genug einsetzen
- 6KernaussageKleine vorzeichenlose Werte können als vorzeichenbehafteter int berechnet werden
- 7HerausforderungEinen vorzeichenbehafteten Wert in vorzeichenloser Arithmetik verfolgen
- 8ErklärungVollständige Vergleiche verknüpfen
- 9BeispielEine Schutzbedingung zuerst ausführen
- 10BeispielEin Flag ändern, ohne andere zu beeinflussen
- 11HerausforderungWahrheitswerte mit gemeinsam gesetzten Bits vergleichen
- 12ErklärungVor dem Nachvollziehen gruppieren
- 13TippDie Maske innerhalb des Vergleichs setzen
- 14VerbindungEinen Wert wählen, ohne beide auszuwerten
- 15HerausforderungKönnen Klammern zwei Aktualisierungen korrigieren?
- 16HerausforderungGruppierung mit einer Bereichsprüfung verbinden
- 17ZusammenfassungEinen Ausdruck in drei Durchgängen untersuchen
5Entscheidungen verfolgen und eine laufende Summe bilden
18 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungDen tatsächlich genommenen Pfad verfolgen
- 2ErklärungDen ersten wahren Zweig verwenden
- 3TippGeschweifte Klammern und Gleichheit sind beide wichtig
- 4HerausforderungDen ersten passenden Bereich finden
- 5ErklärungEinen Befehlseinstieg auswählen
- 6BeispielEine Aktion bewusst gemeinsam nutzen
- 7HerausforderungDas Ziel von break bestimmen
- 8ErklärungEingaben vor der Verwendung lesen
- 9BeispielDie Berechnung mit Wächterwert verfolgen
- 10HerausforderungEinen sofortigen Abbruch behandeln
- 11VerbindungMit do-while mindestens einmal fragen
- 12ErklärungGenau die beabsichtigten Durchläufe zählen
- 13BeispielEine Summe zwischen Durchläufen erhalten
- 14TippZähler vermeiden, die den Ausstieg verfehlen
- 15BeispielZeilen gesondert zählen
- 16HerausforderungEinen vorzeitigen Abbruch in einer Verschachtelung verfolgen
- 17HerausforderungEin Flag mit einer Menüschleife verbinden
- 18ZusammenfassungJeden Weg zum nächsten Durchlauf prüfen
6Ein Produkt vor seiner Berechnung absichern
12 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungDie Berechnung vor dem Speichern untersuchen
- 2BeispielDie Operation im schmalen Typ finden
- 3ErklärungVorzeichenlose Arithmetik liefert ein definiertes, aber möglicherweise falsches Ergebnis
- 4HerausforderungKorrigiert eine späte Typumwandlung das Produkt?
- 5BeispielDen Typ der Operation selbst erweitern
- 6ErklärungEine Addition prüfen, ohne zu addieren
- 7BeispielEin positives Produkt durch Division prüfen
- 8HerausforderungDie Grenze korrekt prüfen
- 9KernaussageEin darstellbares Ergebnis kann eine unsichere Summe verbergen
- 10TippWerkzeuge verwenden und Grenzen testen
- 11HerausforderungEinen vollständigen Entwurf des Wertebereichs prüfen
- 12ZusammenfassungDen Nachweis vor der Operation führen
Datentypen und Operatoren in C: Übungsaufgaben
Set 1: Programme erstellen und gespeicherte Werte verfolgen10 Übungsfragen
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Frage 2 von 10
Antwort zeigenAntwort ausblenden
Frage 3 von 10
Welche Werte gibt dieser Code aus?
int units = 6;
int total = units * 4;
units = 9;
printf("%d %d\n", units, total);
Antwort zeigenAntwort ausblenden
Frage 4 von 10
Antwort zeigenAntwort ausblenden
Frage 5 von 10
014 dar?Antwort zeigenAntwort ausblenden
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Frage 7 von 10
printf("|%04d|", 23); zwischen den senkrechten Strichen aus?Antwort zeigenAntwort ausblenden
Frage 8 von 10
int a = 2, b = 9;. Ein Aufruf von scanf("%d:%d", &a, &b) erhält „17 42“. Welcher Rückgabewert und welche gespeicherten Werte ergeben sich?Antwort zeigenAntwort ausblenden
Frage 9 von 10
Was gibt dies auf einem System mit 8-Bit-unsigned-char und 32-Bit-int aus?
unsigned char a = 230, b = 40;
int sum = a + b;
unsigned char small = sum;
printf("%d %u\n", sum, (unsigned)small);
Antwort zeigenAntwort ausblenden
Frage 10 von 10
gcc -std=c17 -c sensor.c ist erfolgreich. Was hat er erzeugt und welche Phase der Programmerstellung ist vor einer gewöhnlichen Ausführung noch nötig?Antwort zeigenAntwort ausblenden
Set 2: Eingaben lesen und mit C-Typen rechnen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
double distance_m; deklariert. Welche scanf-Konvertierung und welches Argument passen zu ihrem Typ?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
-23 / 6 und -23 % 6 in C17?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
int distance = 19, time = 4; eine Geschwindigkeit als double und erhält den Nachkommateil?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
%d gelesener int akzeptiert die Eingabe „46kg“. Welcher Wert wird gespeichert und welche Eingabe bleibt unmittelbar nach der Konvertierung ungelesen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
total und count, ob das ganzzahlige arithmetische Mittel über 5 liegt, ohne durch null zu dividieren?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
2 + 5 * 3 % 7?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
printf("|%6.1f|", 8.375); bei gewöhnlicher Rundung auf das nächste Zehntel aus?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Welche Werte werden ausgegeben?
int n = 8;
int a = n++;
int b = ++n;
a *= b % 4;
printf("%d %d %d\n", a, b, n);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Was gibt dies bei 32-Bit-int und unsigned int aus?
int delta = -3;
unsigned int limit = 2;
printf("%d %u\n", delta < limit, limit + delta);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
flags = 10u. Welcher Wert entsteht durch Setzen von Bit 2, wenn das niederwertigste Bit als Bit 0 gezählt wird?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 3: Zweige, switch-Anweisungen und Schleifen verfolgen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Eine Punktzahl beträgt 76. Welches Zeichen wird zugewiesen?
if (score >= 70) grade = 'C';
else if (score >= 85) grade = 'B';
else grade = 'F';
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Was wird ausgegeben, wenn mode gleich 2 ist?
switch (mode) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Welche Endwerte haben n und passes?
int n = 45, passes = 0;
while (n > 2) {
n /= 2;
passes++;
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
for (int i = 4; i < 19; i += 4)?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Was wird ausgegeben?
int level = 2;
if (level = 0) {
printf("Low");
} else {
printf("Other");
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Welche Summe wird ausgegeben?
int total = 0;
for (int r = 1; r <= 4; r++) {
for (int c = 1; c <= r; c++) {
if (c == 3) break;
total += c;
}
}
printf("%d\n", total);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Welche Summe ergibt sich?
int sum = 0;
for (int i = 2; i <= 8; i++) {
if (i % 3 == 0) continue;
sum += i;
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Wie oft wird „tick“ ausgegeben?
int running = 1, passes = 0;
while (running && passes < 3) {
switch (passes) {
case 1: break;
default: printf("tick\n");
}
passes++;
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Die Eingabe ist „9 4 0 7“. Welche Anzahl und welches arithmetische Mittel werden ausgegeben?
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
sum += x;
count++;
}
if (count) printf("%d %.1f\n", count, (double)sum / count);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 4: Konvertierungen und Wertebereichsfehler untersuchen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
0u - 1u, wenn unsigned int 32 Wertbits hat?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
a = 60000 und b = 40000 ihr exaktes Produkt sicher in einem typischen 64-Bit-long long?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
flags & 2u == 0u für das vorzeichenlose flags = 6u?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
a + b ausgewertet wird?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
f * i den Wert INT_MAX überschreiten würde?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
-fsanitize=undefined eine Meldung über einen Überlauf bei vorzeichenbehafteten Ganzzahlen aus. Welche Schlussfolgerung sollte die Korrektur leiten?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 5: Eingabe, Programmablauf und sichere Arithmetik verbinden10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Welcher Wert wird ausgegeben?
int total = 0;
for (int r = 2; r <= 5; r++) {
for (int c = 1; c < r; c++) {
if (c % 2 == 0) continue;
total += r;
}
}
printf("%d\n", total);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
days ist 30000. Welcher Ausdruck berechnet seine Dauer in Millisekunden korrekt in einem 64-Bit-long long?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Was garantiert dieser C17-Ausschnitt über seine Ausgabe?
int n = 4;
printf("%d %d\n", n++, n++);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Was wird ausgegeben, wenn a = -2 und b = 7 gilt?
if (a > 0)
if (b > 0) printf("Both");
else printf("A only");
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Warum macht diese Schleife keinen Fortschritt mehr, nachdem sie einmal null ausgegeben hat?
int i = 0;
while (i < 4) {
if (i == 1) continue;
printf("%d\n", i);
i++;
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Welchen Wert hinterlässt dieser Code bei einem 32-Bit-unsigned-int in flags?
unsigned int flags = 13u;
flags &= ~(1u << 2);
flags |= 1u << 1;
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Ein Menü beginnt mit running = 1 und actions = 0. Die Eingabe ist „s q s“. Welchen Wert hat actions nach dieser Schleife?
char cmd;
while (running && scanf(" %c", &cmd) == 1) {
switch (cmd) {
case 'q': running = 0; break;
case 's': actions++; break;
}
}
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Welcher Wert wird rate zugewiesen?
int completed = 14, planned = 20;
double rate = completed / planned * 100.0;
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe
Schlüsselbegriffe
A
Ablaufprotokoll
Abschneiden in Richtung null
Akkumulator
Anweisungsblock
Assoziativität
24 / 4 / 2 als (24 / 4) / 2 und ergibt drei.ausführbare Datei
./hello startet die ausführbare Datei im aktuellen Ordner, ohne den C-Compiler erneut aufzurufen.Ausgabepufferung
fflush(stdout) sie vor einem Eingabeaufruf sendet.Auswertungsreihenfolge
B
bedingter Operator
condition ? a : b, um nach der Prüfung der Bedingung einen Wertausdruck auszuwählen.condition ? a : b den Ausdruck b aus und überspringt a.Bedingung
if (items) ist für null falsch, während eine negative Anzahl ungleich null den Zweig ausführt.Bezeichner
distance_m ist gültig; 2distance kann kein Name sein, weil er mit einer Ziffer beginnt.Bitmaske
1u << 5 wählt Bit fünf aus, wobei das niederwertigste Bit als Position null gezählt wird.Bitverschiebung
bitweiser Operator
& behält nur die in zwölf und zehn gemeinsam gesetzten Bits und ergibt acht.Blockgültigkeitsbereich
boolescher Wert
<stdbool.h> in C17 speichert bool ready = 2; den booleschen Wert wahr.break-Anweisung
C
case-Marke
case 4: ist in C17 gültig; eine gewöhnliche const-int-Variable kann ihren Wert nicht liefern.Compiler
continue-Anweisung
D
default-Marke
Deklaration
int count = 0; gibt dem Namen einen Ganzzahltyp und liefert zugleich seinen Anfangswert.Divisionsrest
%-Operation in C liefert; ein Ergebnis ungleich null hat das Vorzeichen des Dividenden.-22 % 5 ist −2, weil der abgeschnittene Quotient −4 ist und die Divisionsgleichung gelten muss.do-while-Schleife
E
Eingabeende
Endlosschleife
Escape-Sequenz
\n fügt innerhalb einer C-Zeichenkette einen Zeilenumbruch ein; printf verwendet %% für ein Prozentzeichen.explizite Typumwandlung
(double)sum / count wirkt vor der Division und erhält einen gebrochenen Quotienten.F
Fall-through
Feldbreite
%6d fügt vor 42 vier Leerzeichen ein, während ein siebenstelliger Wert trotzdem vollständig ausgegeben wird.for-Schleife
i < n sowie i++ verwendet, führt für nicht negatives n genau n Durchläufe aus, wenn die Aktualisierungen darstellbar sind.Formatstring
printf("Mass: %.1f kg\n", mass) liefert eine Beschriftung, die Anzahl der Nachkommastellen und eine Einheit.G
Ganzzahldivision
22 / 5 ergibt vier, selbst wenn das fertige Ergebnis einem double zugewiesen wird.Ganzzahlerweiterung
Ganzzahlliteral
0x30 verwendet die hexadezimale Schreibweise, während 48u denselben Wert vorzeichenlos dezimal schreibt.Ganzzahltyp mit exakter Bitbreite
uint8_t enthält, sofern er bereitgestellt wird, Werte von null bis 255.Ganzzahlüberlauf
Genauigkeit
Gleitkommawert
Grenzwerttest
H
hängendes else
I
implementierungsdefiniertes Verhalten
implizite Typumwandlung
Initialisierung
Iteration
K
Kommaoperator
i++, j--; Kommas zwischen printf-Argumenten tun das nicht.Konvertierung außerhalb des Wertebereichs
Konvertierungsspezifikation
%lld passt zu einem long-long-Argument; %d kann es nicht automatisch umwandeln.Kurzschlussauswertung
count != 0 && sum / count > 4 überspringt die Division, wenn count null ist.L
Leerraumzeichen
%c liest einen verbliebenen Zeilenumbruch, sofern es nicht angewiesen wird, ihn zu überspringen.Linker
printf auf; der zugehörige Bibliothekscode kann von einer gemeinsam genutzten Bibliothek bereitgestellt werden.logischer Fehler
logischer Operator
&&, || oder ! null oder eins.ready && valid prüft, ob beide gespeicherten Werte ungleich null sind.M
Makro
#define LIMIT 8 wird das Token LIMIT überall dort durch 8 ersetzt, wo das Makro gilt.N
NaN
O
Off-by-one-Fehler
i < n durch i <= n in einer bei null beginnenden Zählschleife verursacht einen Off-by-one-Fehler.Operand
sum / count bestimmen die Typen der Operanden, ob die Division einen Nachkommateil im Ergebnis erhält.Operatorpriorität
3 + 4 * 2 den Ausdruck 4 * 2 vor der Addition.P
Postfix-Inkrement
n = 3 gibt das Postfix-Inkrement in old = n++; der Variablen old den Wert drei und lässt n bei vier.Präprozessor
#include und Makroersetzungen vor den eigentlichen Phasen der C-Übersetzung.Q
Quellcode
R
Rückgabestatus
Rundungsfehler
S
Sanitizer
Seiteneffekt
Standardeingabe
switch-Anweisung
T
Toleranz
fabs(measured - target) mit der Toleranz, statt exakte Gleichheit zu verlangen.Typerweiterung
a * b ändert den Typ des Produkts; eine explizite Typumwandlung nach dem Produkt kommt zu spät.Ü
Übereinstimmungsfehler
%d-Konvertierung verursachen einen Übereinstimmungsfehler und bleiben für einen erneuten Versuch ungelesen.überlaufgeprüfte Arithmetik
ckd_mul aus C23 kann ein Produkt melden, das im Zieltyp nicht darstellbar ist.Übersetzungsfehler
übliche arithmetische Umwandlungen
U
undefiniertes Verhalten
V
Variable
mass_kg speichert einen Messwert, während count festhält, wie viele Proben eingegeben wurden.Vergleichsoperator
<, <=, > oder >= und liefert den int-Wert null oder eins.height >= 1 prüft eine Schranke; ein weiterer Vergleich prüft die andere.verschachtelte Schleife
vorzeichenbehaftete Ganzzahl
vorzeichenlose Ganzzahl
W
Wächterwert
Warnung
%d mit einem double-Argument kombiniert wurde, was eine ungültige Kombination ist.Wertebereichsprüfung
while-Schleife
Wraparound bei vorzeichenlosen Ganzzahlen
Z
Zeichenkonstante
'A' kann mit %c als Buchstabe A oder mit %d als Zahl 65 ausgegeben werden.zusammengesetzte Zuweisung
total += item; aktualisiert die laufende Summe, ohne item zu ändern.Zuweisung
count = count + 1; liest den alten Wert und ersetzt ihn durch einen um eins größeren Wert.Zuweisungsanzahl
Zweierkomplement
10000000 den Wert −128 dar.Zweig
Zwischenergebnis
(low + high) / 2 ist ein Zwischenergebnis, das überlaufen kann, selbst wenn der Mittelpunkt darstellbar ist.Über diese Einheit
Erstelle deine eigenen Lernmaterialien
Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.