![]() |
|
|
Folgende besonderen Merkmale weist g_malloc() im Gegensatz zu malloc() in Standard-C auf:
Hierzu ein Listing, das Ihnen die g-Familie der malloc()-Fraktion demonstriert: /* glib3.c */ #include <gtk/gtk.h> #define MMAX 100 int main(void) { gpointer *mem, *dup; gchar *text = "Text zum Kopieren"; /* Speicher reservieren */ mem = g_malloc(MMAX); /* Etwas in Speicher schreiben */ g_snprintf((gchar *)mem, MMAX, "%s\n",text); /* Inhalt des Speichers ausgeben */ g_print("mem: %s",(gchar *)mem); /* Speicherplatz duplizieren */ dup = g_memdup(mem, MMAX); /* Inhalt des kopierten Speichers ausgeben */ g_print("dup: %s",(gchar *)dup); /* Kopierten Speicherplatz freigeben */ g_free(dup); /* Ein Fehler mit Absicht - Programmabbruch */ mem = g_malloc( -1 ); return 0; } Das Programm bei der Ausführung: $ gcc -o glib3 glib3.c `pkg-config --libs --cflags gtk+-2.0` $ ./glib3 mem: Text zum Kopieren dup: Text zum Kopieren GLib-ERROR **: gmem.c:140: failed to allocate 4294967295 bytes aborting... Abgebrochen Speicherblöcke kopierenDer einfachste Weg, um einen Speicherblock zu kopieren, ist (wie im Beispiel oben gesehen) die Verwendung der Funktion g_memdup(). Diese Funktion reserviert die Menge an Speicherplatz, die Sie als zweiten Parameter angegeben haben, und kopiert die Anzahl der Bytes von der Adresse des ersten Parameters in den neuen Block. Als Rückgabewert erhalten Sie die Anfangsadresse des neuen Blocks oder NULL. Makros zur SpeicherverwaltungNeben malloc()-ähnlichen Funktionen der Glib bietet Ihnen diese Bibliothek auch einige Makros zur Speicherbeschaffung an. Da ja die Speicherblockgröße beim Aufruf mit z. B. g_malloc() nicht nummerisch angegeben werden sollte, sondern als Größe des Datentyps oder der Struktur mit dem sizeof()-Operator, finden sich in der Glib Makros, die Ihnen die Arbeit des Castens abnehmen (was immer noch eine häufige Fehlerquelle ist, weshalb Sie es, wenn nicht nötig, sein lassen sollten). Ein Beispiel: Strukturzeiger = (structurtyp *)g_malloc(sizeof(structurtyp)*10); Hiermit werden für den Strukturzeiger zehn Elemente vom Typ structurtyp mit der entsprechenden Größe reserviert. Es ist jetzt im Prinzip egal, wie diese Struktur aussieht. Diese Casterei wird Ihnen jetzt mit dem Makro g_new() abgenommen: Strukturzeiger = g_new(strukturtyp, 10); Auch wenn das Konstrukt dem C++-Pendant recht ähnlich sieht, es hat nichts damit zu tun! Speicherplatz müssen Sie hierbei weiterhin mit g_free() freigeben und nicht etwa mit einem C++-ähnlichen delete(). Hier ein Überblick zu den Makros und den gleichwertigen Gegenstücken:
Weitere Funktionen zur SpeicherbeschaffungDie Glib bietet noch eine Menge mehr Funktionen und Makros zur Speicherbeschaffung und -verwaltung an. Alle hier zu diskutieren würde den Umfang des Buchs sprengen. Die gängigen kennen Sie zumindest jetzt, und sie sind in der Regel auch völlig ausreichend, um damit arbeiten zu können. Erwähnen muss ich allerdings noch einen speziellen Mechanismus der Glib – den Speicherklumpen (GmemChunk). Dieser wird für das Belegen immer gleichartiger Speicherblöcke verwendet, was bei einer GUI-Software recht häufig vorkommt. Der Klumpen setzt sich dabei aus Atomen (Speicherblöcken) zusammen. GmemChunk ist allerdings keine Datenstruktur, sondern ein Hilfsmittel zu Verwaltung der Speicherblöcke. Sinn hat es natürlich, dadurch die Geschwindigkeit von GUI-Anwendungen zu beschleunigen. Eine weitere Speichertechnik sind die Quarks mit GQuark, die erst einmal nichts mit Physik oder der Speicherverwaltung zu tun haben, aber intern irgendwie doch, da mit dieser Technik Speicherplatz gespart werden kann. Sofern Sie also umfangreiche und speicherintensive Anwendungen erstellen, sollten Sie sich GMemChunk und GQuark auf jeden Fall in der Dokumentation ansehen. Als Tipp kann ich Ihnen hierfür auch das Buch von M. Warkus, GNOME 2.0, empfehlen, das auf diese Themen genauer eingeht. 15.3.5 Stringbearbeitung
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hinweis Nicht behandelt wurden außerdem die Unmengen an Funktionen für Unicode- und Zeichencodierung. Bei Bedarf sei wieder die Dokumentation empfohlen oder eben entsprechende Literatur. |
In der Glib wurde ein neuer Datentyp mit dem Namen GString eingeführt, der zwar gleichwertig wie ein gewöhnlicher String ist, nur dass dieser sich automatisch um den Puffer kümmert. Damit gehören Buffer Overflows in Ihrem Programm der Vergangenheit an. Realisiert wird dieser Puffer mit einer einfachen Struktur namens GString:
struct GString {
/* Zeiger auf einen \0-terminierten String */
gchar *str;
/* Aktuelle Länge */
gint len;
};
Einen neuen Textpuffer können Sie mit den folgenden Funktionen erstellen:
GString *g_string_new(const gchar *init); Gstring *g_string_sized_new(guint size);
Die erste Funktion erstellt einen neuen Textpuffer, wobei der neue Puffer gleich mit einer Kopie von init initialisiert wird, worauf dieser verweist. Mit der zweiten Funktion (g_string_sized_new()) reservieren Sie einen Speicherplatz für einen neuen String mit size Bytes Länge. Der Puffer wird dabei automatisch mit '\0' Zeichen initialisiert. Den Speicherplatz für GString geben Sie mit der Funktion g_string_free() wieder frei.
void g_string_free(GString *string, gint free_segment);
Ist free_segment TRUE, werden auch die Daten von str der GString-Struktur freigegeben.
Jetzt folgen einige Funktionen, mit denen Sie den Textpuffer bearbeiten können. Hierzu erst einmal eine - natürlich nicht komplette - Liste einiger Funktionen, die mit dem Datentypen GString zusammenarbeiten.
GString *g_string_assign(GString *lval, const gchar *rval); GString *g_string_append(GString *string, gchar *val); GString *g_string_append_c(GString *string, gchar c); GString *g_string_prepend(GString *string, gchar *val); GString *g_string_prepend_c(GString *string, gchar c); GString *g_string_insert( GString *string, gint offset, gchar *val); GString *g_string_insert_c( GString *string, gint offset, gchar val); GString *g_string_erase( GString *string, gint pos, gint length);
Mit der Funktion g_string_assign() kopieren Sie den String rval in den GString lval. Wie bei der Standard-C-Funktion strcpy() wird dabei der Originaltext (falls einer vorhanden ist) in lval überschrieben. Um den Speicherplatz müssen Sie sich keine Gedanken machen, das erledigt Glib für Sie. Als Rückgabewert erhalten Sie bei Erfolg einen Zeiger auf lval.
Mit den Funktionen g_string_append() und g_string_append_c() hängen Sie an den Quellstring string (erster Parameter) einen String oder (bei g_string_append_c()) ein Zeichen (zweiter Parameter) an. Als Rückgabewert erhalten Sie einen Zeiger auf die Anfangsadresse von string.
Mit g_string_prepend() oder g_string_prepend_c() fügen Sie am Anfang des Zielstrings string einen String oder ein Zeichen hinzu. Auch hier wird als Rückgabewert ein Zeiger auf die Anfangsadresse von string zurückgegeben.
Mit den Funktionen g_string_insert() und g_string_insert_c() fügen Sie einen String oder ein Zeichen in den Zielstring string ein. Wo dieser String oder das Zeichen eingefügt wird, geben Sie mit offset an. Als Rückgabewert erhalten Sie einen Zeiger auf die Anfangsadresse von string.
Mit g_string_erase() können Sie im Zielstring string von der Position pos bis zu length Zeichen löschen. Bei falscher Angabe von pos oder length wird string unverändert zurückgegeben. Die Rückgabe ist auch hierbei ein Zeiger auf den Anfang von string.
Alle hier erwähnten Funktionen liefern im Fehlerfalle NULL zurück, wenn der Zielstring NULL ist. Ist allerdings der Quellstring NULL, wird der Zielstring nicht verändert und auch so zurückgegeben.
Eine besondere Funktion sollte noch erwähnt werden:
void g_string_sprintf(GString *string, const gchar *fmt ...);
Diese Funktion ist der sprintf()-Funktion von C sehr ähnlich und hat den Vorteil, dass der Puffer automatisch erweitert wird, falls nötig. Befinden sich im GString string noch Zeichen, werden diese unwiderruflich überschrieben.
Das folgende Listing soll Ihnen diese Funktionen und den neuen Glib-Datentyp GString in der Praxis zeigen:
/* glib6.c */ #include <gtk/gtk.h> #include <string.h> #define MMAX 255 int main(void) { GString *gstr; guint size; gint val = 1; gstr = g_string_new("GString initialisiert"); g_print("%s mit %d Bytes\n\n",gstr->str, gstr->len); g_string_assign(gstr, "g_string_assign() ueberschreibt den Originalstring"); g_print("%s\n\n", gstr->str); g_string_append(gstr, "\n-> hinzugefuegt mit g_string_append()"); g_print("%s\n\n", gstr->str); /* Einzelnes Zeichen hinzufügen am Ende */ g_string_append_c(gstr, '!'); g_print("%s\n\n", gstr->str); g_string_prepend(gstr, "Vorne angefuegt mit g_string_prepend()\n"); g_print("%s\n\n", gstr->str); /* Einzelnes Zeichen hinzufügen am Anfang */ g_string_prepend_c(gstr, '!'); g_print("%s\n\n", gstr->str); g_string_insert(gstr, 10, " xxxEINGEFUEGTxxx " ); g_print("%s\n\n", gstr->str); size = strlen(gstr->str); /* String löschen */ g_string_erase(gstr, 0, size); g_string_sprintf(gstr, "Puffer wird mit g_string_sprintf() automatisch" " verwaltet (%d)\n",val); g_print("%s\n\n", gstr->str); return 0; }
Das Programm bei der Ausführung:
$ gcc -o glib6 glib6.c `pkg-config --libs --cflags gtk+-2.0` $ ./glib6 GString initialisiert mit 21 Bytes g_string_assign() überschreibt den Originalstring g_string_assign() überschreibt den Originalstring -> hinzugefügt mit g_string_append() g_string_assign() überschreibt den Originalstring -> hinzugefügt mit g_string_append()! Vorne angefügt mit g_string_prepend() g_string_assign() überschreibt den Originalstring -> hinzugefügt mit g_string_append()! !Vorne angefügt mit g_string_prepend() g_string_assign() überschreibt den Originalstring -> hinzugefügt mit g_string_append()! !Vorne ang xxxEINGEFÜGTxxx efügt mit g_string_prepend() g_string_assign() überschreibt den Originalstring -> hinzugefügt mit g_string_append()! Puffer wird mit g_string_sprintf() automatisch verwaltet (1)
Im Großen und Ganzen ist ein Timer nichts anderes als eine Stoppuhr, die so genau wie die Systemuhr ist. Timer werden gerne verwendet, wenn z. B. für eine bestimmte Zeit auf ein Event gewartet werden soll – oder einfach als Verzögerung (wie Sie dies im Beispiel der X-Programmierung gesehen haben). Diese sind eigentlich recht einfach zu verwenden. Hier ein Überblick zu einigen Funktionen, die Ihnen dazu zur Verfügung stehen:
| Bedeutung | Funktion |
| Timer erzeugen | GTimer *g_timer_new(); |
| Timer starten | void g_timer_start( GTimer *timer ); |
| Zeit abfragen (Sekunden und Millisekunden), die seit dem Start des Timers vergangen ist | gdouble g_timer_elapsed( GTimer *timer, gulong *msecs ); |
| Die abgelaufene Zeit auf 0 zurücksetzen (und neustarten) | void g_timer_reset( GTimer *timer); |
| Timer stoppen | g_timer_stop( GTimer *stop ); |
| Timer zerstören (freigeben) | void g_timer_destroy(GTimer *timer); |
Ein einfaches Listing, das die Timer-Funktionen demonstrieren soll.
/* glib7.c */ #include <gtk/gtk.h> #include <stdio.h> int main (void) { GTimer *timer = NULL; gdouble time; gulong us; gint i=300000000; gint j=300000000; timer = g_timer_new (); g_timer_start (timer); g_print ("Timer laeuft\n"); while(i--); time = g_timer_elapsed (timer, &us); g_print ("Schleife dekrementiert %g sek == %ld usek\n", time, us); g_timer_reset( timer ); while(i++ <= j); time = g_timer_elapsed (timer, &us); g_print ("Schleife inkrementieren %g sek == %ld usek\n", time, us); g_timer_reset( timer ); g_print("Timer von Hand starten <ENTER>\n"); getchar(); g_timer_start (timer); g_print("Timer von Hand stoppen <ENTER>\n"); getchar(); g_timer_stop( timer ); time = g_timer_elapsed (timer, &us); g_print ("Vergangene Zeit zwischen Start und Stopp" " %g sek == %ld usek\n", time, us); g_timer_destroy (timer); return 0; }
Das Programm bei der Ausführung:
$ gcc -o glib7 glib7.c `pkg-config --libs --cflags gtk+-2.0` $ ./glib7 Timer läuft Schleife dekrementiert 0.769323 sek == 769323 usek Schleife inkrementieren 0.775894 sek == 775894 usek Timer von Hand starten <ENTER> ENTER Timer von Hand stoppen <ENTER> ENTER Vergangene Zeit zwischen Start und Stopp 2.26975 sek == 269751 usek
Die Glib bietet Ihnen mit GArray einen Datentyp für ein C-typisches Arrayfeld, nur mit dem Unterschied, dass dieses nicht von fester Größe sein muss. Mit dem flexibleren Array-Datentyp können Sie beliebig neue Elemente einfügen, und das sowohl am Anfang und am Ende wie auch mittendrin – natürlich ist auch das Löschen inbegriffen.
Dabei bietet Ihnen die Glib gleich drei Formen von dynamischen Arrays an:
| gewöhnliche Arrays |
| Zeiger-Arrays |
| Byte-Arrays |
Durchgenommen werden hier nur die gewöhnlichen Arrays, welche praktisch mit allen Elementen beliebigen Typs – inklusive Strukturen – verwendet werden können. Dazu eine kurze Zusammenstellung von einigen Funktionen für GArray, welche gewöhnlich alle mit dem Präfix g_array_ beginnen.
GArray *g_array_new( gboolean zero_terminated, gboolean clear, guint size ); void g_array_free( GArray *array, gboolean free_segment ); GArray *g_array_append_vals( GArray *array, gconstpointer *data, guint len ); GArray *g_array_prepend_vals( GArray *array, gconstpointer *data, guint len ); GArray *g_array_insert_vals( GArray *array, guint index, gconstpointer *data, guint len ); GArray *g_array_remove_index( GArray *array, guint index ); g_array_index( array, datentyp, index );
Zuerst muss mit der Funktion g_array_new() der Array alloziiert werden. Mit den beiden gboolean-Parametern geben Sie an, ob ein Nullwert als Terminierungszeichen angehängt wird und ob neue Elemente auf null gesetzt werden. Der letzte Parameter muss die Größe des Elements in Bytes (Datentyp) enthalten, wovon Sie einen dynamischen Array erzeugen wollen. Zurückgegeben wird bei Erfolg ein Zeiger auf den neuen GArray.
Die Speicherverwaltung wird von nun an mit den entsprechenden Funktionen von der Glib übernommen. Allerdings freigeben müssen Sie nicht verwendeten Speicherplatz mit der Funktion g_array_free() wieder selbst.
Wollen Sie ein neues Element am Anfang bzw. Ende des GArray hinzufügen, können Sie hierfür die Funktionen g_array_prepend_vals() bzw. g_array_append_vals() verwenden. Beide Funktionen erwarten als ersten Parameter einen Zeiger auf den Array, wo entsprechende Daten, die mit dem zweiten Parameter angegeben werden, hinzugefügt werden. Mit dem dritten Parameter geben Sie die Anzahl der Elemente an, die Sie hinzufügen wollen.
Die Funktion g_array_insert_vals() hat im Gegensatz zu den Funktionen g_array_prepend_vals() und g_array_append_vals() noch einen zusätzlichen Parameter (zweiter Parameter), womit Sie die Indexposition angeben und das neue Element einfügen können.
Ein Element aus der Liste des GArray (erster Parameter) können Sie unter Angabe der Indexnummer des zweiten Parameters mit der Funktion g_array_remove_index() entfernen.
Um anschließend wieder auf die einzelnen Elemente des GArray zurückzugreifen, benötigen Sie das Makro g_array_index(). Als Argument übergeben Sie dieser Funktion das Array, den Datentyp der Elemente und die Indexnummer. Zurückgegeben wird dann der Wert des entsprechenden Elementes.
Das folgende Listing soll Ihnen die Verwendung vom dynamischen GArray mit der Glib demonstrieren. Sie können gerne versuchen, das Beispiel in Standard-C in dieser kurzen Form und mit solch einfachem Code zu schreiben.
/* glib8.c */ #include <gtk/gtk.h> #include <stdio.h> #define MMAX 255 static struct daten { gchar ort[MMAX]; guint plz; }; static struct daten help; static guint count = 0; static void read_data (void) { g_print ("Ort eingeben : "); fgets (help.ort, MMAX, stdin); g_strchomp (help.ort); g_print ("Postleitzahl eingeben : "); scanf ("%d", &help.plz); } static void insert_beginn (GArray * arr) { read_data (); g_array_prepend_vals (arr, &help, 1); count++; } static void insert_end (GArray * arr) { read_data (); g_array_append_vals (arr, &help, 1); count++; } static void insert_middle (GArray * arr) { guint pos; read_data(); g_print ("Position angeben : "); scanf ("%d", &pos); if (pos < count) { g_array_insert_vals (arr, pos, &help, 1); count++; } else g_print ("Konnte Array nicht einfuegen (%d < %d)\n", pos, count); } static void remove_array (GArray * arr) { guint pos; g_print ("Position zum Loeschen angeben : "); scanf ("%d", &pos); if (pos < count) { g_array_remove_index (arr, pos); count--; } else g_print ("Konnte Array nicht entfernen (%d < %d)\n", pos, count); } static void show_arrays (GArray * arr) { struct daten test; guint pos = 0; while (pos < count) { test = g_array_index (arr, struct daten, pos); g_print ("Ort : %s\n", test.ort); g_print ("PLZ : %d\n", test.plz); pos++; } } int main (int argc, char **argv) { GArray *darray; guint auswahl; darray = g_array_new (FALSE, FALSE, sizeof (struct daten)); do { printf ("Demonstration: dynamische Arrays (Glib)\n\n"); printf ("1 - Neue Daten am Anfang einfuegen\n"); printf ("2 - Neue Daten am Ende einfuegen\n"); printf ("3 - Neue Daten an gewuenschte Pos. einfuegen\n"); printf ("4 - Daten entfernen\n"); printf ("5 - Daten ausgeben\n"); printf ("0 - Programmende\n"); printf ("\nIhre Auswahl : "); scanf ("%d", &auswahl); getchar (); switch (auswahl) { case 1: insert_beginn (darray); break; case 2: insert_end (darray); break; case 3: insert_middle (darray); break; case 4: remove_array (darray); break; case 5: show_arrays (darray); break; } } while (auswahl != 0); g_array_free (darray, TRUE); return 0; }
Das Programm bei der Ausführung:
$ gcc -o glib8 glib8.c `pkg-config --libs --cflags gtk+-2.0` $ ./glib8 Demonstration von dynamischen Arrays mit GLib 1 - Neue Daten am Anfang einfuegen 2 - Neue Daten am Ende einfuegen 3 - Neue Daten an gewuenschte Pos. einfügen 4 - Daten entfernen 5 - Daten ausgeben 0 - Programmende Ihre Auswahl : ...
|
Hinweis Wenn Sie das GArray auch noch sortieren wollen, können Sie sich die Funktionen g_array_sort() und g_array_sort_with_data() ansehen. |
Weiterhin bietet die Glib eine Unmenge an Funktionen für dynamische Datenstrukturen wie die einfachen und doppelt verketteten Listen. Für die einfach verketteten Listen werden der Datentyp GSList und die Funktionen mit dem Präfix g_slist_ verwendet und für die doppelt verketteten Listen der Typ GList und die Funktionen mit dem Präfix g_list_. Der Vorteil dieser Funktionen ist natürlich, dass man eben schnell auf abgepackte Routinen zugreifen kann und nicht eigene und somit auch wieder fehleranfällige Versionen der Listen schreiben muss.
Ebenfalls eine klassische, aber auch lästig zu schreibende Datenstruktur stellt die Glib Ihnen mit den Hashtabellen zur Verfügung. Hashtabellen sind Tabellen, die Schlüsseln einen sog. Hash zuordnen. Mit diesem Hash werden neue Werte eingefügt und vor allem auch schneller wieder gefunden. Der Glib-Datentyp für Hashtabellen ist GHashTable, und die Routinen haben das Präfix g_hash_. Bei dem GNOME-Desktop und dessen Software werden Hashtabellen zum Beispiel intensiv eingesetzt.
Der eingebaute Typ Gtree ist eine Implementierung binärer Bäume in der Glib mit allen möglichen Routinen dazu. Besser noch, bei den Bäumen der Glib handelt es sich zusätzlich noch um ausbalancierte Bäume. Wer schon einmal versucht hat, eine eigene Routine für ausbalancierte Bäume zu schreiben, weiß, wie schwierig und vor allem fehleranfällig das ist. Des Weiteren sind die einzelnen Elemente im binären Baum eben nicht nur einfache Elemente, die aus einem Datensatz bestehen, sondern außerdem auch einen Schlüssel besitzen, anhand dessen die Datensätze sortiert und gefunden werden – sprich, eine Hashtabelle von binären Bäumen, wenn Sie so wollen. Die Routinen, die sich auf den Typ Gtree beziehen, beginnen alle mit dem Präfix g_tree_.
Auf die beiden klassischen Datenstrukturen soll hierbei nicht mehr näher eingegangen werden, da sich die Einführung in die Glib ohnehin schon ziemlich umfangreich gestaltet und mehr Platz für das eigentliche Thema der GUI-Programmierung reserviert werden sollte. Für den weiteren Verlauf des Buchs benötigen Sie dieses Wissen ohnehin nicht.
|
Hinweis Sollten Sie mit Begriffen wie Listen, Hashtabellen oder binären Bäumen nichts anzufangen wissen, dann muss ich Sie auf ein Einsteigerbuch zu C verweisen. Auf der Buch-CD finden Sie diesbezüglich mit dem Buch »C von A bis Z« einen einfachen Einstieg in dieses mächtige Thema. |
Hier angekommen, lässt sich nur sagen, dass die Glib noch eine Menge mehr hilfreicher Routinen und Typen anbietet. Ich würde teilweise sogar so weit gehen und Ihnen empfehlen, die Glib zur Erstellung von Konsolenprogrammen zu verwenden. Zu den Vorteilen gehören Dinge wie die Sicherheit und ein Einsparen an Overhead bei der Programmierung. Natürlich sollte es nicht unerwähnt bleiben, dass Sie die Glib nicht verwenden müssen, um GTK+-Programme zu schreiben – Sie können natürlich weiterhin für diese Aufgaben die Standard-C-Funktionen verwenden oder im Falle von Algorithmen und Datenstrukturen selbst zum x-ten Mal etwas neu schreiben. Fakt ist, die Glib ist eine ausgereifte und sehr hilfreiche Bibliothek, die einem das Leben enorm erleichtern kann und vor allem bei dem Thema Portieren auf anderen Systemen erste Wahl sein sollte.
| << zurück |
|
||||||||||||
|
||||||||||||
|
||||||||||||
Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich
ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen,
wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist
urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der
Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und
Verarbeitung in elektronischen Systemen.