K&R The C Programming Language: Word Lengths Histogram Aufgabe
-
Hallo,
danke für die Links etc. ist mir jetzt einleuchtend. MAX_LEN ist in der Tat sehr generisch. An sowas hatte ich bisher garnicht gedacht (C++ Hintergrund).
Ist das mit dem Modul allgemein dann z.B. so gemeint (nur zum Verständnis)?
// main.c #include "histogram.h" // use output_histogram
// histogram.h void output_histogram(unsigned arr[], unsigned len); static void output_header(unsigned numLengths, const char* title); static void output_graph(unsigned arr[], unsigned len); static void output_y_axis_segment(unsigned y, unsigned step); static void output_graph_segment(unsigned arr[], unsigned len, unsigned static currentY); static void output_x_axis(unsigned arr[], unsigned len);
// histogram.c // implementierung
-
Also, man könnte natürlich auch mal überlegen, wie man denn sowas reusable machen könnte.
Dann würde es sich anbieten, einen Histogram-Typen zu machen, in welchem du die Dinge wir die Bin-Inhalte, wie viele Bins etc. speicherst (ggf. auch noch Dinge wie Overflow-Bins, Mittelwerte etc.).
// Header-Datei: Histogram *historgram_create(const char *title, int nBins, double from, double to); void histogram_fill(Histogram* h, double value, double weight = 1.); void histogram_draw(Histogram* h); void histogram_delete(Histogram* h);
oder so ähnlich.
Dann könntest du in deinem Programm ein Histogramm-Objekt erzeugen, die Datei einlesen und für jedes Wort einmal histogram_fill() aufrufen. Am Ende dann ein histogram_draw() und fertig.
Wenn du nämlich, wie jetzt, deiner Funktion
(unsigned arr[], unsigned len);
übergeben musst, dann musst du dich z.B. um das Füllen von arr im Hauptprogramm kümmern - was, wenn du sehr viele und lange Worte hast und vielleicht Wortlängen [0..10) in das erste Bin, [10..20) ins zweite Bin usw. füllen willst? D.h. Binbreiten sind bei dir nicht variabel und die entsprechende Berechnung dazu müsstest du im main machen, was schlecht wäre.Andererseits natürlich die Frage, wie weit du mit dieser Aufgabe gehen willst.
-
Die klassische Vorgehensweise für dein Beispiel sieht so aus:
main.c
#include "histogram.h" int main(){...}
histogram.h
void count_word_lengths(FILE *fp, unsigned numWordsOfLen[], unsigned maxLen); void output_histogram(unsigned arr[], unsigned len);
histogram.c
#include "histogram.h" /* Prototypen */ void count_length(unsigned numWordsOfLen[], unsigned len, unsigned maxLen); void output_header(unsigned numLengths, const char* title); void output_graph(unsigned arr[], unsigned len); void output_y_axis_segment(unsigned y, unsigned step); void output_graph_segment(unsigned arr[], unsigned len, unsigned currentY); void output_x_axis(unsigned arr[], unsigned len); unsigned find_max(unsigned arr[], unsigned len); void char_line(int ch, unsigned length); /* Implementierungen */ void count_word_lengths(FILE *fp, unsigned numWordsOfLen[], unsigned maxLen){...} void output_histogram(unsigned arr[], unsigned len){...} static void count_length(unsigned numWordsOfLen[], unsigned len, unsigned maxLen){...} static void output_header(unsigned numLengths, const char* title){...} static void output_graph(unsigned arr[], unsigned len){...} static void output_y_axis_segment(unsigned y, unsigned step){...} static void output_graph_segment(unsigned arr[], unsigned len, unsigned currentY){...} static void output_x_axis(unsigned arr[], unsigned len){...} static unsigned find_max(unsigned arr[], unsigned len){...} static void char_line(int ch, unsigned length){...}
Integer-basierte Konstanten (MAX_LEN) verwendet man professionell auch nicht als #define
sondern als enum.
Das hat den Vorteil, dass der Compiler bei Doppelverwendung (in anderen Modulen) immer einen Fehler wirft, während bei #define der Präprozessor eine Warnung werfen kann.
Obendrein haben #defines immer global Scope und bei Doppelverwendung ist es zufällig, welcher der beiden Doppelbelegungen aus unterschiedlichen Modulen "gewinnt", da die Abarbeitungsreihenfolge der einzelnen Header/Module durch den Präprozessor nicht vom Standard vorgegeben ist.
-
Hallo,
ersteinmal vielen Dank, dass weiterhin so viel Feedback kommt.
@wob
Ich habe nicht vor, eine komplette Histogramm-Lib zu entwickeln, mit der man jedes mögliche Histogramm in jedem möglichen Szenario handhaben kann. Es wäre aber schon nett, wenn das Modul so viel Flexibilität hat, dass ich z.B. die zweite Aufgabe ebenfalls damit lösen könnte, ohne großen Mehraufwand:Write a program to print a histogram of the frequencies of different characters in its input
Da könnte ich ja ähnlich die char-codes in Array-indices übersetzten, und müsste dann halt irgendwie bei der Ausgabe die chars ausgeben können. Mein spontaner Gedanke wäre gewesen, irgendwie einen Format String als Parameter anzunehmen, welcher dann zur Ausgabe verwendet wird (also praktisch die Bin Beschriftung, z.B. "%2d" vs "%c"). Hier stellt sich natürlich die Frage, was passiert wenn man printf mit mehr Argumenten als Formats aufruft (wenn ich einen Bereich in Bins zulasse, kann ich ja immer low und high an printf übergeben und dann entweder "%d..%d" oder "%d" als format verwenden, wenn low == high).
Ich möchte hier natürlich nicht, dass mir jemand das vorprogrammiert, da darf ich bitte erstmal selbst Knobeln. Wenn es sich hier anbietet die "Objektorientierung" in C anzuwenden, werde ich das natürlich gerne ausprobieren.
@wutz
Das Codebeispiel ist jetzt mehr als ich erwartet hatte, erklärt aber auch super was ich nicht ganz verstanden hatte (was nun in den Header kommt und wo das static hingehört). Ist ja schon etwas anders als bei C++ (da komme ich her). Das mit dem enum für die Konstanten finde ich eine gute Idee, werde ich auf jeden Fall machen.Ich meld mich wieder, wenn ich neuen Code haben sollte.
LG
P.S.: Ich wünschte ich hätte mich mehr für Deutsch interessiert, dann könnte ich mich hier sauberer ausdrücken
-
HarteWare schrieb:
Wenn es sich hier anbietet die "Objektorientierung" in C anzuwenden, werde ich das natürlich gerne ausprobieren.
c ist eine prozedurale programmiersprache, das heißt start -> mach was -> ende.
wenn du objektorientiert programmieren willst, solltest du vielleicht lieber c++ oder java verwenden.
-
Quark. Jedes bekannte C-Programm ist objektorientiert geschrieben.
start -> mach was -> ende.
Das hat so was von überhaupt nichts mit prozedural vs. objektorientiert zu tun.
-
also das, was ich so über oop gelernt habe, kann man unter c (im gegensatz zu c++, java usw) wenn überhaupt nur mit großem aufwand umsetzen, dafür kann man aber sehr schön den ablauf innerhalb der hardware nachbilden und die hardware läuft doch prozedural, oder nicht?
-
Vielleicht hast du falsches gelernt? Deine Vorstellungen sind jedenfalls total wirr. OOP und Prozdural haben nichts Hardware zu tun, das sind philosophische Arten und Weisen, wie man über Daten und Abläufe nachdenkt.
-
dass man prinzipiell mit jeder programmiersprache alles programmieren kann, ist mir schon klar. soll ja auch jeder machen, wie er will.
trotzdem bleibe ich dabei, dass - zumindest im rahmen meines beschränkten horizonts - der funktionsumfang von c besser für die prozedurale programmierung geeignet ist und dass es daher unsinn ist, mit c irgendwelche objekte zusammen zu basteln, wenn es andere sprachen gibt, die das alles viel besser und bequemer können.
-
Aha. Sind für dich die wichtigsten Merkmale von OOP also die Existenz der "class"- und "virtual"-Keywords?
Warum ist Code wie folgender für dich nicht OOP
GRand *rand = g_rand_new_with_seed(0); int value = g_rand_int_range(rand, 0, 42); g_rand_free(rand);
Während es folgender aber wäre?
GRand *rand = new GRand(0); int value = rand.int_range(rand, 0, 42); delete rand;
-
Oder ein Beispiel aus der stdlib:
File* f = fopen('foo'); fwrite(f, ...); fclose(f)
Das ist OO pur!
-
g_rand_int_range(rand, 0, 42);
die obige funktion ist global und bekommt eine (beliebige) instanz einer datenstruktur übergeben.
rand.int_range(rand, 0, 42);
die obige funktion ist fest an die instanz eines (bestimmten) objektes gebunden und damit eigentlich eine methode. warum das objekt sich selbst übergeben bekommt, weiß ich allerdings nicht. tippfehler?
SG1 schrieb:
Oder ein Beispiel aus der stdlib:
File* f = fopen('foo'); fwrite(f, ...); fclose(f)
Das ist OO pur!
oo pur wäre f.write();
-
Dann hast du das Konzept wirklich nicht verstanden. Die gezeigten Codes sind jedenfalls tatsächlich OO pur. OO hat nichts mit Punkten in der Syntax zu tun. Ein anderer Unterschied besteht zwischen dem C und dem C++-Code aber nicht.
Oder anders gesagt, in C++ (doer jeder anderen Sprache, die du als oo bezeichnen würdest) ist
f.write("Bla")
nur Syntaxzucker fürwrite(f, "Bla")
.
-
ne mit punkten nicht, aber damit, dass funktionen bei oop in ein objekt gekapselt werden sollen, damit das alles schön übersichtlich ist und man weiß, dass f.write() eine methode der durch f instanziierten klasse File ist und daher nicht zig dateien durchsucht werden müssen, wenn man da irgendwas dran ändern will.
den vorzug hast du bei write(f,...) nicht.
-
So? Was ist denn anders? Der erste Parameter von fwrite nimmt schließlich einen FILE* entgegen, weil fwrite eine Methode der Klasse FILE ist. Erinnert dich das vielleicht an etwas? *hust*this*hust*
-
Am Ende des Tages wird doch eh alles (völlig Hunz ob C, C++, Python, Haskell, oder Brainfuck) in die Maschinensprache des Computers übersetzt, denn das ist das einzige was er versteht, alles andere ist nur für den Menschen (ausgenommen letzteres). Jedes Programm ist start -> tu was -> stop.
Ein Objekt bestimmt meiner Ansicht nach eine Menge von Daten (Also irgendwelche Bytes im Speicher) und auf diese Daten ist eine Menge möglicher Operationen definiert. Wie das jetzt konkret aussieht, ist doch wurscht.
-
HarteWare schrieb:
...
"Objektorientierung" in C anzuwenden,
...
bei C++ (da komme ich her).Lange vor Stroustrup und dem Managerhype OOP wurde von den Praktikern schon das "Objekt" benutzt, natürlich auch von Ritchie.
In C++ hätte o.g. Beispiel dann als Klasse so ausgesehen:
class Histogram { public: void count_word_lengths(FILE *fp, unsigned numWordsOfLen[], unsigned maxLen); void output_histogram(unsigned arr[], unsigned len); private: static constexpr int MAX_LEN = 100; // hier hat Stroustrup mal wieder gepfuscht, das hätte so schon in C++98 funktionieren müssen void count_length(unsigned numWordsOfLen[], unsigned len, unsigned maxLen); void output_header(unsigned numLengths, const char* title); void output_graph(unsigned arr[], unsigned len); void output_y_axis_segment(unsigned y, unsigned step); void output_graph_segment(unsigned arr[], unsigned len, unsigned currentY); void output_x_axis(unsigned arr[], unsigned len); unsigned find_max(unsigned arr[], unsigned len); void char_line(int ch, unsigned length); };
Wie du siehst, kann man mit C-Modulen und static die Accesslevel implementieren.
Bleibt nur noch die Frage: Wo wäre MAX_LEN (als enum) denn am besten aufgehoben?
Antwort: Im C-Modul:
histogram.c:enum {MAX_LEN=100}; /* das ist keine Objekt- sondern nur eine Typdefinition, d.h. ein Zugriffsschutz via static ist nicht nötig (und nicht möglich) */
-
Bleibt nur noch die Frage: Wo wäre MAX_LEN (als enum) denn am besten aufgehoben?
Antwort: Im C-Modul:Stimmt, das war mir auch nicht 100% klar. Kannst ja Gedanken lesen :o
-
Wobei die enum-Variante im Rahmen einer anonymen enum-Typdefinition im C-Modul prinzipiell noch weitere Vorteile bietet:
- erstmal natürlich: du kommst von außerhalb des C-Moduls niemals an den Wert heran, da kannst du via extern versuchen was du willst; weil MAX_LEN gar kein Objekt ist
- ein Debugger zeigt dir den enum-Wert sehr viel wahrscheinlicher separat an als ein #define
- gegenüber einer const int-Definition hast du hier den Vorteil, dass du auf Fehler immer schon zur Compilezeit hingewiesen wirst, wenn du (verbotenerweise doch) versuchst den Wert zu ändern, und nicht erst vielleicht zur Laufzeit
#include <stdio.h> const int x = 4711; enum {y = 4711}; int main() { #if 0 *(int*)&x = 4712; /* nicht lachen, ich habe schon viel solchen Code gesehen (auch voll selbsternannten Profis), der const auf diese Weise umgehen will */ printf("%d",x); #else y=4712; #endif return 0; }
- const-Variante: du kannst froh sein, wenn du überhaupt zur Laufzeit einen Fehler bekommst (hängt von der Güte des Compilers ab)
const int x = 4711; enum {y = 4711}; int main() { #if 1 *(int*)&x = 4712; printf("%d",x); #else y=4712; #endif return 0; } Runtime error
- enum-Variante: du erhältst immer schon zur Compilezeit einen Fehler (ein unschätzbarer Vorteil bei professioneller Programmierung: man lässt so viel wie möglich durch den Compiler prüfen); der Code reift nicht beim Kunden sondern möglichst beim Entwickler
const int x = 4711; enum {y = 4711}; int main() { #if 0 *(int*)&x = 4712; printf("%d",x); #else y=4712; #endif return 0; } prog.c:11:6: error: lvalue required as left operand of assignment y=4712; ^
-
SeppJ schrieb:
Oder anders gesagt, in C++ (doer jeder anderen Sprache, die du als oo bezeichnen würdest) ist
f.write("Bla")
nur Syntaxzucker fürwrite(f, "Bla")
.Interessant. Wie kommt write() denn an die privates von f heran? Datenkapselung ist ja ein wesentliches Merkmal von OOP.