Inline (eingebettete) Funktionen. Funktionsüberladung

Funktionsüberladung

Überladen von Operationen (Operatoren, Funktionen, Prozeduren)- in der Programmierung - eine der Möglichkeiten, Polymorphismus zu implementieren, der in der Möglichkeit der gleichzeitigen Existenz in einem Bereich von mehreren besteht Verschiedene Optionen Operationen (Anweisung, Funktion oder Prozedur), die denselben Namen haben, sich aber in den Parametertypen unterscheiden, auf die sie sich beziehen.

Terminologie

Der Begriff "Overloading" ist ein Pauspapier des englischen "Overloading", das in der ersten Hälfte der 1990er Jahre in russischen Übersetzungen von Büchern über Programmiersprachen erschien. Vielleicht ist dies nicht die meisten die beste WeiseÜbersetzung, da das Wort "Überlastung" in der russischen Sprache eine etablierte Bedeutung hat, die sich radikal von der neu vorgeschlagenen unterscheidet, hat es sich jedoch etabliert und ist weit verbreitet. In den Veröffentlichungen der Sowjetzeit wurden ähnliche Mechanismen auf Russisch als „Neudefinition“ oder „Neudefinition“ von Operationen bezeichnet, aber diese Option ist nicht zu bestreiten: In den Übersetzungen der englischen „override“, „overload“ und „ neu definieren“.

Gründe für das Erscheinen

Die meisten frühen Programmiersprachen hatten eine Einschränkung, dass nicht mehr als eine Operation mit demselben Namen gleichzeitig in einem Programm verfügbar sein konnte. Dementsprechend müssen alle Funktionen und Prozeduren, die an einer bestimmten Stelle im Programm sichtbar sind, unterschiedliche Namen haben. Die Namen und Bezeichnungen von Funktionen, Prozeduren und Operatoren, die Teil der Programmiersprache sind, können vom Programmierer nicht verwendet werden, um seine eigenen Funktionen, Prozeduren und Operatoren zu benennen. In manchen Fällen kann ein Programmierer sein eigenes Programmobjekt mit dem Namen eines anderen, bereits existierenden erstellen, aber dann "überschneidet" sich das neu erstellte Objekt mit dem vorherigen, und es wird unmöglich, beide Optionen gleichzeitig zu verwenden.

Diese Situation ist in einigen ziemlich häufigen Fällen unbequem.

  • Manchmal besteht die Notwendigkeit, Operationen auf vom Programmierer erstellte Datentypen zu beschreiben und anzuwenden, die in ihrer Bedeutung denen entsprechen, die bereits in der Sprache verfügbar sind. Ein klassisches Beispiel ist eine Bibliothek zum Arbeiten mit komplexen Zahlen. Sie unterstützen, wie gewöhnliche numerische Typen, arithmetische Operationen, und es wäre natürlich, dafür zu sorgen dieser Art Operationen "plus", "minus", "multiplizieren", "dividieren", wobei sie mit den gleichen Operationszeichen wie bei anderen numerischen Typen bezeichnet werden. Das Verbot der Verwendung von in der Sprache definierten Elementen erzwingt die Erstellung vieler Funktionen mit Namen wie ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat und so weiter.
  • Wenn gleichbedeutende Operationen auf Operanden angewendet werden verschiedene Arten, müssen sie anders benannt werden. Beantragung nicht möglich verschiedene Typen Funktionen mit demselben Namen führt dazu, dass verschiedene Namen für dasselbe erfunden werden müssen, was Verwirrung stiftet und zu Fehlern führen kann. In der klassischen C-Sprache gibt es beispielsweise zwei Versionen der Standardbibliotheksfunktion zum Ermitteln des Moduls einer Zahl: abs() und fabs() - die erste ist für ein ganzzahliges Argument gedacht, die zweite für ein reelles. Diese Situation, kombiniert mit einer schwachen C-Typprüfung, kann zu einem schwer zu findenden Fehler führen: Wenn ein Programmierer abs(x) in die Berechnung schreibt, wobei x eine reelle Variable ist, dann generieren einige Compiler Code, ohne dass dies der Fall ist Wandeln Sie x in eine ganze Zahl um, indem Sie die Nachkommastellen verwerfen, und berechnen Sie den Modulus aus der resultierenden ganzen Zahl!

Teilweise wird das Problem durch Objektprogrammierung gelöst - wenn neue Datentypen als Klassen deklariert werden, können Operationen auf ihnen als Klassenmethoden formalisiert werden, einschließlich gleichnamiger Klassenmethoden (da Methoden verschiedener Klassen keine haben müssen verschiedene Namen), aber erstens ist eine solche Art der Operation von Operationen mit Werten unterschiedlichen Typs unbequem und zweitens löst sie nicht das Problem der Erstellung neuer Operatoren.

Das Überladen von Operatoren selbst ist nur syntaktischer Zucker, obwohl es auch als solches nützlich sein kann, da es dem Entwickler ermöglicht, auf natürlichere Weise zu programmieren, und dafür sorgt, dass sich benutzerdefinierte Typen eher wie integrierte Typen verhalten. Wenn wir uns dem Thema von einer allgemeineren Position nähern, können wir sehen, dass die Werkzeuge, die es Ihnen ermöglichen, die Sprache zu erweitern, sie durch neue Operationen und syntaktische Konstruktionen ergänzen (und das Überladen von Operationen ist eines dieser Werkzeuge, zusammen mit Objekten, Makros , Funktionale, Closures) wandeln sie bereits in die Metasprache um – ein Mittel zur Beschreibung von Sprachen, das sich an bestimmten Aufgaben orientiert. Mit seiner Hilfe ist es möglich, für jede spezifische Aufgabe eine Spracherweiterung zu erstellen, die für sie am besten geeignet ist und die es ermöglicht, ihre Lösung in der natürlichsten, verständlichsten und einfachsten Form zu beschreiben. Zum Beispiel in der Anwendung zum Überladen von Operationen: Das Erstellen einer Bibliothek komplexer mathematischer Typen (Vektoren, Matrizen) und das Beschreiben von Operationen damit in einer natürlichen, „mathematischen“ Form, schafft eine „Sprache für Vektoroperationen“, in der die Komplexität von Berechnungen sind verborgen, und es ist möglich, die Lösung von Problemen in Form von Vektor- und Matrixoperationen zu beschreiben, wobei der Schwerpunkt auf dem Kern des Problems liegt, nicht auf der Technik. Aus diesen Gründen wurden solche Mittel einst in die Algol-68-Sprache aufgenommen.

Überlastmechanismus

Implementierung

Das Überladen von Operatoren beinhaltet die Einführung von zwei miteinander verbundenen Merkmalen in die Sprache: die Möglichkeit, mehrere Prozeduren oder Funktionen mit demselben Namen im selben Gültigkeitsbereich zu deklarieren, und die Fähigkeit, Ihre eigenen Implementierungen von Operationen zu beschreiben (d. h. normalerweise die Vorzeichen von Operationen in Infixschreibweise geschrieben, zwischen Operanden). Im Grunde ist ihre Umsetzung ganz einfach:

  • Um die Existenz mehrerer gleichnamiger Operationen zuzulassen, genügt es, eine Regel in die Sprache einzuführen, nach der eine Operation (Prozedur, Funktion oder Operator) vom Compiler nicht nur am Namen (Notation) erkannt wird, sondern auch durch die Typen ihrer Parameter. Also sind abs(i), wobei i als ganze Zahl deklariert ist, und abs(x), wobei x als reell deklariert ist, zwei verschiedene Operationen. Eine solche Auslegung ist grundsätzlich ohne Schwierigkeiten möglich.
  • Um das Definieren und Redefinieren von Operationen zu ermöglichen, ist es notwendig, geeignete syntaktische Konstruktionen in die Sprache einzuführen. Es kann ziemlich viele Optionen geben, aber tatsächlich unterscheiden sie sich nicht voneinander. Es reicht aus, sich daran zu erinnern, dass die Eingabe des Formulars „<операнд1> <знакОперации> <операнд2>» ähnelt im Grunde dem Aufruf der Funktion «<знакОперации>(<операнд1>,<операнд2>)". Es genügt, dem Programmierer zu erlauben, das Verhalten von Operatoren in Form von Funktionen zu beschreiben – und das Beschreibungsproblem ist gelöst.

Optionen und Probleme

Überladen von Prozeduren und Funktionen auf der Ebene gemeinsame Idee ist in der Regel weder schwer umzusetzen noch zu verstehen. Aber auch darin gibt es einige „Fallstricke“, die es zu beachten gilt. Das Überladen von Operatoren zuzulassen, schafft viel mehr Probleme sowohl für den Sprachimplementierer als auch für den Programmierer, der in dieser Sprache arbeitet.

Identifizierungsproblem

Die erste Frage, die sich ein Entwickler eines Sprachübersetzers stellt, der das Überladen von Prozeduren und Funktionen erlaubt, ist, wie er unter den gleichnamigen Prozeduren diejenige auswählen soll, die in einem gegebenen Fall angewendet werden soll konkreten Fall? Alles ist gut, wenn es eine Variante der Prozedur gibt, deren Typen von Formalparametern exakt mit den Typen der eigentlichen Parameter übereinstimmen, die in diesem Aufruf verwendet werden. In fast allen Sprachen gibt es jedoch einen gewissen Freiheitsgrad bei der Verwendung von Typen, vorausgesetzt, der Compiler führt in bestimmten Situationen automatisch typsichere Konvertierungen durch. Beispielsweise wird bei arithmetischen Operationen mit reellen und ganzzahligen Argumenten die Ganzzahl normalerweise automatisch in den reellen Typ konvertiert, und das Ergebnis ist reell. Angenommen, es gibt zwei Varianten der add-Funktion:

int add(int a1, int a2); float add (float a1, float a2);

Wie soll der Compiler mit dem Ausdruck y = add(x, i) umgehen, wobei x ein Float und i ein Int ist? Offensichtlich gibt es keine exakte Übereinstimmung. Es gibt zwei Möglichkeiten: entweder y=add_int((int)x,i) oder als y=add_flt(x, (float)i) (hier bezeichnen die Namen add_int und add_float die erste bzw. zweite Version der Funktion) .

Es stellt sich die Frage: Sollte der Compiler eine solche Verwendung überladener Funktionen zulassen, und wenn ja, auf welcher Grundlage wird er die jeweils verwendete Variante auswählen? Sollte der Übersetzer im obigen Beispiel bei der Auswahl insbesondere den Typ der Variablen y berücksichtigen? Es sei darauf hingewiesen, dass die obige Situation die einfachste ist, es sind viel kompliziertere Fälle möglich, die noch dadurch erschwert werden, dass nicht nur eingebaute Typen nach den Regeln der Sprache konvertiert werden können, sondern auch vom Programmierer deklarierte Klassen , wenn sie Verwandtschaftsbeziehungen haben, können einander zugeordnet werden. Es gibt zwei Lösungen für dieses Problem:

  • Verbieten Sie überhaupt eine ungenaue Identifizierung. Fordern Sie, dass es für jedes bestimmte Typenpaar eine genau passende Variante der überladenen Prozedur oder Operation gibt. Wenn es keine solche Option gibt, sollte der Compiler einen Fehler ausgeben. Der Programmierer muss in diesem Fall eine explizite Konvertierung anwenden, um die tatsächlichen Parameter in den gewünschten Satz von Typen umzuwandeln. Dieser Ansatz ist in Sprachen wie C++, die ziemlich viel Freiheit im Umgang mit Typen zulassen, unpraktisch, da er zu einem signifikanten Unterschied im Verhalten von eingebauten und überladenen Operatoren führt (arithmetische Operationen können auf gewöhnliche Zahlen angewendet werden ohne nachzudenken, sondern zu anderen Typen - nur mit expliziter Konvertierung) oder zur Entstehung einer Vielzahl von Optionen für Operationen.
  • Legen Sie bestimmte Regeln für die Auswahl der „nächsten Übereinstimmung“ fest. Normalerweise wählt der Compiler in dieser Variante diejenigen der Varianten aus, deren Aufrufe nur durch sichere (nicht verlustbehaftete Informationen) Typkonvertierungen von der Quelle erhalten werden können, und wenn es mehrere davon gibt, kann er auswählen, welche Variante weniger erfordert solche Umbauten. Wenn das Ergebnis mehr als eine Möglichkeit zulässt, gibt der Compiler einen Fehler aus und fordert den Programmierer auf, die Variante explizit anzugeben.

Spezifische Probleme beim Überladen von Operationen

Im Gegensatz zu Prozeduren und Funktionen haben Infix-Operationen von Programmiersprachen zwei zusätzliche Eigenschaften, die ihre Funktionalität erheblich beeinflussen: Priorität und Assoziativität, deren Vorhandensein auf die Möglichkeit der "verketteten" Aufzeichnung von Operatoren zurückzuführen ist (wie man a + b versteht * c: als (a + b )*c oder wie a+(b*c) ?Der Ausdruck a-b+c ist (ab)+c oder a-(b+c) ?).

Die in die Sprache eingebauten Operationen haben immer einen vordefinierten traditionellen Vorrang und Assoziativität. Es stellt sich die Frage: Welche Prioritäten und Assoziativität werden die neu definierten Versionen dieser Operationen haben oder darüber hinaus die vom Programmierer neu erstellten Operationen? Es gibt noch andere Feinheiten, die einer Klärung bedürfen. Beispielsweise gibt es in C zwei Arten von Inkrement- und Dekrementoperatoren ++ und -- - Präfix und Postfix, die sich unterschiedlich verhalten. Wie sollten sich die überladenen Versionen solcher Operatoren verhalten?

Verschiedene Sprachen gehen auf unterschiedliche Weise mit diesen Themen um. Daher werden in C++ der Vorrang und die Assoziativität von überladenen Versionen von Operatoren mit denen beibehalten, die in der Sprache definiert sind; Es ist möglich, die Präfix- und Postfixformen der Inkrement- und Dekrementoperatoren mit speziellen Signaturen separat zu überladen:

Int wird also verwendet, um einen Unterschied in Signaturen zu machen

Ankündigung neuer Operationen

Noch komplizierter ist die Situation bei der Ankündigung neuer Operationen. Die Aufnahme der Möglichkeit einer solchen Erklärung in die Sprache ist nicht schwierig, aber ihre Umsetzung ist mit erheblichen Schwierigkeiten behaftet. Das Deklarieren einer neuen Operation ist tatsächlich das Erstellen einer neuen Stichwort Programmiersprache, erschwert dadurch, dass Operationen im Text in der Regel ohne Trennzeichen mit anderen Tokens folgen können. Wenn sie auftreten, ergeben sich zusätzliche Schwierigkeiten bei der Organisation des lexikalischen Analysators. Wenn die Sprache beispielsweise bereits die Operationen „+“ und das unäre „-“ (Vorzeichenwechsel) hat, dann kann der Ausdruck a+-b genau als ein + (-b) interpretiert werden, aber wenn eine neue Operation +- ist im Programm deklariert, entsteht sofort Mehrdeutigkeit, da derselbe Ausdruck bereits als a (+-) b geparst werden kann. Der Entwickler und Implementierer der Sprache muss sich irgendwie mit solchen Problemen befassen. Die Optionen können wiederum unterschiedlich sein: verlangen, dass alle neuen Operationen aus einem Zeichen bestehen, postulieren, dass für alle Diskrepanzen die „längste“ Version der Operation gewählt wird (d. h. bis der nächste vom Übersetzer gelesene Satz von Zeichen übereinstimmt). jede Operation, es wird weiterhin gelesen), versuchen Sie, Kollisionen während der Übersetzung zu erkennen und in kontroversen Fällen Fehler zu erzeugen ... Auf die eine oder andere Weise lösen Sprachen, die die Deklaration neuer Operationen ermöglichen, diese Probleme.

Es sollte nicht vergessen werden, dass es bei neuen Operationen auch die Frage der Bestimmung der Assoziativität und Priorität gibt. Eine fertige Lösung in Form einer Standard-Sprachbedienung gibt es nicht mehr, und in der Regel müssen Sie diese Parameter nur noch mit den Regeln der Sprache einstellen. Machen Sie beispielsweise alle neuen Operationen linksassoziativ und geben Sie ihnen die gleiche, feste Priorität, oder führen Sie in die Sprache die Möglichkeit ein, beide zu spezifizieren.

Überladen und polymorphe Variablen

Wenn überladene Operatoren, Funktionen und Prozeduren in stark typisierten Sprachen verwendet werden, in denen jede Variable einen vordeklarierten Typ hat, ist es dem Übersetzer überlassen, welche Variante des überladenen Operators er in jedem einzelnen Fall verwenden soll, egal wie komplex . Das bedeutet, dass bei kompilierten Sprachen die Verwendung von Operatorüberladungen nicht zu Leistungseinbußen führt – in jedem Fall gibt es eine wohldefinierte Operation oder einen Funktionsaufruf im Objektcode des Programms. Anders verhält es sich, wenn in der Sprache polymorphe Variablen verwendet werden können, also Variablen, die zu unterschiedlichen Zeiten Werte unterschiedlichen Typs enthalten können.

Da der Typ des Werts, auf den die überladene Operation angewendet wird, zum Zeitpunkt der Übersetzung des Codes nicht bekannt ist, kann der Compiler keine Auswahl treffen gewünschte Möglichkeit im Voraus. In diesem Fall muss ein Fragment in den Objektcode eingebettet werden, das unmittelbar vor der Ausführung dieser Operation die Typen der Werte in den Argumenten bestimmt und dynamisch eine Variante auswählt, die diesem Satz von Typen entspricht. Darüber hinaus muss eine solche Definition bei jeder Ausführung der Operation vorgenommen werden, da sogar derselbe Code, der ein zweites Mal aufgerufen wird, durchaus unterschiedlich ausgeführt werden kann.

Die Verwendung von Operatorüberladung in Kombination mit polymorphen Variablen macht es daher unumgänglich, den aufgerufenen Code dynamisch zu bestimmen.

Kritik

Der Einsatz von Overload wird nicht von allen Experten als Segen angesehen. Wenn das Überladen von Funktionen und Prozeduren im Allgemeinen keine ernsthaften Einwände findet (teilweise, weil es nicht zu einigen typischen "Operator" -Problemen führt, teilweise, weil es weniger verlockend ist, es zu missbrauchen), dann ist das Überladen von Operatoren im Prinzip und im Besonderen Sprachimplementierungen, wird von vielen Theoretikern und Praktikern der Programmierung ziemlich scharf kritisiert.

Kritiker weisen darauf hin, dass die oben genannten Probleme der Identifizierung, Präzedenz und Assoziativität das Arbeiten mit überladenen Operatoren oft unnötig erschweren oder unnatürlich machen:

  • Identifikation. Wenn die Sprache strenge Identifikationsregeln hat, dann ist der Programmierer gezwungen, sich daran zu erinnern, für welche Kombinationen von Typen es überladene Operationen gibt, und ihnen manuell Operanden zuzuweisen. Wenn die Sprache eine "ungefähre" Identifizierung zulässt, kann man nie sicher sein, dass in einer ziemlich komplizierten Situation genau die Version der Operation ausgeführt wird, die der Programmierer im Sinn hatte.
  • Priorität und Assoziativität. Wenn sie starr definiert sind, kann dies umständlich und für das Fachgebiet nicht relevant sein (z. B. bei Operationen mit Mengen unterscheiden sich Prioritäten von arithmetischen). Wenn sie vom Programmierer gesetzt werden können, wird dies zu einer zusätzlichen Fehlerquelle (schon deshalb, weil sich herausstellt, dass verschiedene Varianten einer Operation unterschiedliche Prioritäten oder sogar Assoziativität haben).

Wie sehr die Bequemlichkeit der Verwendung Ihrer eigenen Operationen die Unannehmlichkeiten der Verschlechterung der Steuerbarkeit des Programms aufwiegen kann, ist eine Frage, auf die es keine klare Antwort gibt.

Aus Sicht der Sprachimplementierung führen die gleichen Probleme zur Komplexität der Übersetzer und zur Abnahme ihrer Effizienz und Zuverlässigkeit. Und die Verwendung des Überladens in Verbindung mit polymorphen Variablen ist auch offensichtlich langsamer als das Aufrufen einer fest codierten Operation während der Kompilierung und bietet weniger Möglichkeiten zur Optimierung des Objektcodes. Spezifische Merkmale der Implementierung von Überladungen in verschiedenen Sprachen werden gesondert kritisiert. So kann in C++ die fehlende Einigkeit über die interne Darstellung der Namen überladener Funktionen kritisiert werden, was zu Inkompatibilitäten auf der Ebene von Bibliotheken führt, die von verschiedenen C++-Compilern kompiliert wurden.

Einige Kritiker sprechen sich gegen Überlastungsoperationen aus, basierend auf allgemeine Grundsätze Entwicklungstheorie Software und reale industrielle Praxis.

  • Befürworter des "puritanischen" Ansatzes zum Sprachaufbau, wie Wirth oder Hoare, lehnen die Überladung von Operatoren einfach ab, weil auf sie leicht verzichtet werden kann. Ihrer Meinung nach verkomplizieren solche Tools nur die Sprache und den Übersetzer, ohne dieser Komplikation entsprechend zu sein Zusatzfunktionen. Ihrer Meinung nach sieht die Idee, eine aufgabenorientierte Erweiterung der Sprache zu schaffen, nur attraktiv aus. In Wirklichkeit macht die Verwendung von Spracherweiterungswerkzeugen das Programm nur für seinen Autor verständlich - derjenige, der diese Erweiterung entwickelt hat. Das Programm wird für andere Programmierer viel schwieriger zu verstehen und zu analysieren, was die Wartung, Modifikation und Teamentwicklung erschwert.
  • Es wird darauf hingewiesen, dass die bloße Möglichkeit der Verwendung von Overload oft eine provokative Rolle spielt: Programmierer beginnen damit, es wo immer möglich zu verwenden, als Ergebnis wird ein Werkzeug, das entwickelt wurde, um das Programm zu vereinfachen und zu rationalisieren, zur Ursache seiner Komplikation und Verwirrung.
  • Überladene Operatoren tun möglicherweise nicht genau das, was von ihnen erwartet wird, basierend auf ihrer Art. Zum Beispiel bedeutet a + b normalerweise (aber nicht immer) dasselbe wie b + a , aber "eins" + "zwei" unterscheidet sich von "zwei" + "eins" in Sprachen, in denen der Operator + überladen ist String-Verkettung.
  • Das Überladen von Operatoren macht Programmfragmente kontextsensitiver. Ohne die Typen der an einem Ausdruck beteiligten Operanden zu kennen, ist es unmöglich zu verstehen, was der Ausdruck tut, wenn er überladene Operatoren verwendet. Beispielsweise ist in einem C++-Programm die Anweisung<< может означать и побитовый сдвиг, и вывод в поток. Выражение a << 1 возвращает результат побитового сдвига значения a на один бит влево, если a - целая переменная, но если a является выходным потоком , то же выражение выведет в этот поток строку «1» .

Einstufung

Das Folgende ist eine Klassifizierung einiger Programmiersprachen danach, ob sie das Überladen von Operatoren zulassen und ob Operatoren auf einen vordefinierten Satz beschränkt sind:

Operationen Keine Überlastung Es liegt eine Überlastung vor
Begrenzte Anzahl von Operationen
  • Ziel c
  • Python
Es ist möglich, neue Operationen zu definieren
  • PostgreSQL
  • siehe auch

    Wikimedia-Stiftung. 2010 .

    Sehen Sie in anderen Wörterbüchern, was "Funktionsüberladung" ist:

      - (Operatoren, Funktionen, Prozeduren) in der Programmierung eine der Möglichkeiten zur Implementierung von Polymorphismus, die in der Möglichkeit der gleichzeitigen Existenz mehrerer verschiedener Varianten einer Operation (Operator, Funktion oder ... ... Wikipedia) in einem Geltungsbereich besteht

Das Überladen von Funktionen ist die Definition mehrerer Funktionen (zwei oder mehr) mit demselben Namen, aber unterschiedlichen Parametern. Die Parametersätze überladener Funktionen können sich in Reihenfolge, Anzahl und Typ unterscheiden. Daher ist das Überladen von Funktionen erforderlich, um zu vermeiden, dass die Namen von Funktionen dupliziert werden, die ähnliche Aktionen, aber mit unterschiedlicher Programmlogik ausführen. Betrachten Sie zum Beispiel die Funktion areaRectangle(), die die Fläche eines Rechtecks ​​berechnet.

Float areaRectangle(float, float) //Funktion, die die Fläche eines Rechtecks ​​mit zwei Parametern a(cm) und b(cm) berechnet (return a * b; // Seitenlänge des Rechtecks ​​multiplizieren und zurück das resultierende Produkt)

Dies ist also eine Funktion mit zwei Parametern vom Typ float , und die an die Funktion übergebenen Argumente müssen in Zentimetern angegeben werden, der zurückgegebene Wert vom Typ float ist ebenfalls in Zentimetern.

Angenommen, unsere Anfangsdaten (Rechteckseiten) sind in Metern und Zentimetern angegeben, zum Beispiel: a = 2 m 35 cm; b = 1m 86 cm In diesem Fall wäre es praktisch, eine Funktion mit vier Parametern zu verwenden. Das heißt, jede Länge der Seiten des Rechtecks ​​wird in zwei Parametern an die Funktion übergeben: Meter und Zentimeter.

Float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // Funktion, die die Fläche eines Rechtecks ​​mit 4 Parametern a(m) a(cm) berechnet; b(m) b(cm) (Rückgabe (a_m * 100 + a_sm) * (b_m * 100 + b_sm); )

Im Hauptteil der Funktion werden die in Metern übergebenen Werte (a_m und b_m) in Zentimeter umgewandelt und zu den Werten a_sm b_sm addiert, danach multiplizieren wir die Summen und erhalten die Fläche des Rechtecks in cm. Natürlich war es möglich, die ursprünglichen Daten in Zentimeter umzurechnen und die erste Funktion zu verwenden, aber darum geht es jetzt nicht.

Das Wichtigste ist nun, dass wir zwei Funktionen mit unterschiedlichen Signaturen, aber denselben Namen haben (überladene Funktionen). Eine Signatur ist eine Kombination aus einem Funktionsnamen mit seinen Parametern. Wie werden diese Funktionen aufgerufen? Und das Aufrufen überladener Funktionen unterscheidet sich nicht vom Aufrufen regulärer Funktionen, zum Beispiel:

AreaRectangle(32, 43); // es wird eine Funktion aufgerufen, die die Fläche eines Rechtecks ​​mit zwei Parametern a(cm) und b(cm) berechnet areaRectangle(4, 43, 2, 12); // es wird eine Funktion aufgerufen, die die Fläche eines Rechtecks ​​mit 4 Parametern a(m) a(cm) berechnet; b(m) b(cm)

Wie Sie sehen, wählt der Compiler die gewünschte Funktion selbstständig aus und analysiert nur die Signaturen überladener Funktionen. Um das Überladen von Funktionen zu umgehen, könnte man einfach eine Funktion mit einem anderen Namen deklarieren, und sie würde ihre Arbeit gut machen. Aber stellen Sie sich vor, was passiert, wenn Sie mehr als zwei solcher Funktionen benötigen, zum Beispiel 10. Und für jede müssen Sie sich einen aussagekräftigen Namen einfallen lassen, und am schwierigsten ist es, sich an sie zu erinnern. Genau aus diesem Grund ist es einfacher und besser, Funktionen zu überladen, es sei denn, es besteht natürlich ein Bedarf dafür. Der Quellcode des Programms ist unten dargestellt.

#einschließen "stdafx.h" #einschließen << "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

// Code Code::Blöcke

// Dev-C++-Code

#enthalten mit Namensraum std; // Prototypen überladener Funktionen float areaRectangle(float a, float b); float areaRectangle(float a_m, float a_sm, float b_m, float b_sm); int main() (cout<< "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

Das Ergebnis des Programms ist in Abbildung 1 dargestellt.



Wie erreicht man eine Funktionsüberladung in C? (10)

Gibt es eine Möglichkeit, eine Funktionsüberladung in C zu erreichen? Ich betrachte einfache Funktionen, die wie überladen werden können

foo (int a) foo (char b) foo (float c , int d)

Ich denke, es gibt keinen direkten Weg; Ich suche nach Workarounds, falls vorhanden.

Ich hoffe, der folgende Code wird Ihnen helfen, das Überladen von Funktionen zu verstehen

#enthalten #enthalten int fun(int a, ...); int main(int argc, char *argv)( fun(1,10); fun(2,"cquestionbank"); return 0; ) int fun(int a, ...)( va_list vl; va_start(vl,a ); if(a==1) printf("%d",va_arg(vl,int)); else printf("\n%s",va_arg(vl,char *)); )

Ich meine, du meinst - nein, das kannst du nicht.

Sie können die va_arg-Funktion als deklarieren

void my_func(char* format, ...);

Aber Sie müssen einige Informationen über die Anzahl der Variablen und ihre Typen im ersten Argument übergeben - wie printf() .

Ja wie.

Hier gibst du ein Beispiel:

void printA(int a)( printf("Hallo Welt von printA: %d\n",a); ) void printB(const char *buff)( printf("Hallo Welt von printB: %s\n",buff) ; ) #define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 #define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N #define _Num_ARGS_(... ) __VA_ARG_N(__VA_ARGS__) #define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) #define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t) #define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) #define print(x , args ...) \ CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \ CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout) ; \ (( \ if (__builtin_types_compatible_p (typeof (x), int)) \ printA(x, ##args); \ else \ printB (x, ##args); \ )) int main(int argc, char* * argv) ( int a=0; print(a); print("hallo"); return (EXIT_SUCCESS); )

Es wird 0 und hallo von printA und printB ausgeben.

Wenn Ihr Compiler gcc ist und es Ihnen nichts ausmacht, jedes Mal, wenn Sie eine neue Überladung hinzufügen, manuelle Aktualisierungen vorzunehmen, können Sie eine Makromasse erstellen und das gewünschte Ergebnis aus der Sicht des Aufrufers erhalten, nicht so schön zu schreiben ... aber es ist so möglich

Schauen Sie sich __builtin_types_compatible_p an und verwenden Sie es dann, um ein Makro zu definieren, das so etwas tut

#define foo(a) \ ((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

aber ja, böse, einfach nicht

BEARBEITEN: C1X wird Unterstützung für Typausdrücke erhalten, die wie folgt aussehen:

#define cbrt(X) _Generic((X), long double: cbrtl, \ default: cbrt, \ float: cbrtf)(X)

Wie bereits erwähnt, wird das Überladen in dem Sinne, den Sie meinen, von C nicht unterstützt. Die übliche Redewendung zur Lösung des Problems besteht darin, dass die Funktion eine getaggte Union akzeptiert. Dies wird mithilfe des struct -Parameters implementiert, wobei die Struktur selbst aus einer Art von Typindikator besteht, z. B. einem enum , und einer Vereinigung verschiedener Werttypen. Beispiel:

#enthalten typedef enum ( T_INT, T_FLOAT, T_CHAR, ) my_type; typedef struct ( my_type type; union ( int a; float b; char c; ) my_union; ) my_struct; void set_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: whatever->my_union.a = 1; break; case T_FLOAT: Whatever->my_union.b = 2.0; break; case T_CHAR: whatever-> my_union.c = "3"; ) ) void printf_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: printf("%d\n", Whatever->my_union.a); break; case T_FLOAT : printf("%f\n", was auch immer->meine_union.b); break; case T_CHAR: printf("%c\n", was auch immer->meine_union.c); break; ) ) int main (int argc, char* argv) ( my_struct s; s.type=T_INT; set_overload(&s); printf_overload(&s); s.type=T_FLOAT; set_overload(&s); printf_overload(&s); s.type=T_CHAR; set_overload(&s) ; printf_overload(&s); )

Können Sie nicht einfach C++ verwenden und alle anderen C++-Funktionen außer dieser nicht verwenden?

Wenn es bisher kein streng striktes C gab, würde ich stattdessen variadische Funktionen empfehlen.

Der folgende Ansatz ist ähnlich wie a2800276, aber mit einigen C99-Makros:

// wir brauchen `size_t` #include // Argumenttypen zum Akzeptieren von Enum sum_arg_types ( SUM_LONG, SUM_ULONG, SUM_DOUBLE ); // eine Struktur zum Halten eines Arguments struct sum_arg ( enum sum_arg_types type; union ( long as_long; unsigned long as_ulong; double as_double; ) value; ); // Größe eines Arrays bestimmen #define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY))) // so wird unsere Funktion aufgerufen #define sum(...) _sum( count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__)) // ein Array von `struct sum_arg` erstellen #define sum_args(...) ((struct sum_arg )( __VA_ARGS__ )) // Initialisierer für die Argumente erstellen #define sum_long (VALUE) ( SUM_LONG, ( .as_long = (VALUE) ) ) #define sum_ulong(VALUE) ( SUM_ULONG, ( .as_ulong = (VALUE) ) ) #define sum_double(VALUE) ( SUM_DOUBLE, ( .as_double = (VALUE) ) ) // unsere polymorphe Funktion long double _sum(size_t count, struct sum_arg * args) ( long double value = 0; for(size_t i = 0; i< count; ++i) { switch(args[i].type) { case SUM_LONG: value += args[i].value.as_long; break; case SUM_ULONG: value += args[i].value.as_ulong; break; case SUM_DOUBLE: value += args[i].value.as_double; break; } } return value; } // let"s see if it works #include int main() ( unsigned long foo = -1; long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10)); printf("%Le\n", value); return 0; )

Vorerst ist _Generic seit der Frage _Generic, Standard-C (keine Erweiterungen) effektiv habe Unterstützung für das Überladen von Funktionen (anstelle von Operatoren) dank der Hinzufügung des _Generic-Wortes _Generic in C11. (unterstützt in GCC seit Version 4.9)

(Das Überladen ist nicht wirklich auf die in der Frage gezeigte Weise "eingebaut", aber es ist einfach, etwas zu zerstören, das so funktioniert.)

Generic ist ein Kompilierungsoperator in derselben Familie wie sizeof und _Alignof . Es ist im Standardabschnitt 6.5.1.1 beschrieben. Es benötigt zwei Hauptparameter: einen Ausdruck (der zur Laufzeit nicht ausgewertet wird) und eine Liste von Typ-/Ausdruckszuordnungen, die ein bisschen wie ein Schalterblock ist. _Generic erhält den generischen Typ des Ausdrucks und "wechselt" dann zu ihm, um den endgültigen Ergebnisausdruck in der Liste für seinen Typ auszuwählen:

Generic(1, float: 2.0, char *: "2", int: 2, default: get_two_object());

Der obige Ausdruck ergibt 2 - der Typ des Steuerausdrucks ist int , also wählt er den mit int verknüpften Ausdruck als Wert aus. Nichts davon bleibt zur Laufzeit übrig. (Die Standardklausel ist obligatorisch: Wenn Sie sie nicht angeben und der Typ nicht übereinstimmt, führt dies zu einem Kompilierungsfehler.)

Eine für das Überladen von Funktionen nützliche Technik besteht darin, dass sie vom C-Präprozessor eingefügt werden kann und einen Ergebnisausdruck basierend auf dem Typ der Argumente auswählen kann, die an das steuernde Makro übergeben werden. Also (Beispiel aus dem C-Standard):

#define cbrt(X) _Generic((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \)(X)

Dieses Makro implementiert die überladene cbrt-Operation, indem es den Typ des Arguments an das Makro weitergibt, die entsprechende Implementierungsfunktion auswählt und dann das ursprüngliche Makro an diese Funktion übergibt.

Um Ihr ursprüngliches Beispiel zu implementieren, könnten wir Folgendes tun:

Foo_int (int a) foo_char (char b) foo_float_int (float c , int d) #define foo(_1, ...) _Generic((_1), \ int: foo_int, \ char: foo_char, \ float: _Generic(( FIRST(__VA_ARGS__,)), \int: foo_float_int))(_1, __VA_ARGS__) #define FIRST(A, ...) A

In diesem Fall könnten wir default: binding für den dritten Fall verwenden, aber das zeigt nicht, wie das Prinzip auf mehrere Argumente erweitert werden kann. Das Endergebnis ist, dass Sie foo(...) in Ihrem Code verwenden können, ohne sich (viel) Gedanken über den Typ Ihrer Argumente zu machen.

Für komplexere Situationen, z. B. Funktionen, die mehr Argumente oder unterschiedliche Zahlen überladen, können Sie Hilfsmakros verwenden, um automatisch statische Dispatch-Strukturen zu generieren:

void print_ii(int a, int b) ( printf("int, int\n"); ) void print_di(double a, int b) ( printf("double, int\n"); ) void print_iii(int a, int b, int c) ( printf("int, int, int\n"); ) void print_default(void) ( printf("unknown arguments\n"); ) #define print(...) OVERLOAD(print, (__VA_ARGS__), \ (print_ii, (int, int)), \ (print_di, (double, int)), \ (print_iii, (int, int, int)) \) #define OVERLOAD_ARG_TYPES (int, double) #define OVERLOAD_FUNCTIONS (print) #include "activate-overloads.h" int main(void) ( print(44, 47); // gibt "int, int" aus print(4.4, 47); // gibt "double, int" aus print (1, 2, 3); // gibt "int, int, int" aus print(""); // gibt "unbekannte Argumente" aus )

(Implementierung hier). Mit etwas Aufwand können Sie die Boilerplate so reduzieren, dass sie einer Sprache mit integrierter Überladungsunterstützung ziemlich ähnlich sieht.

Abgesehen davon war es bereits möglich, zu überladen Anzahl Argumente (statt Typ) in C99.

Beachten Sie, dass die Art und Weise, wie C ausgewertet wird, Sie bewegen kann. Dadurch wird foo_int ausgewählt, wenn Sie beispielsweise versuchen, ein Literalzeichen daran zu übergeben, und Sie benötigen etwas foo_int, wenn Ihre Überladungen Zeichenfolgenliterale unterstützen sollen. Insgesamt aber ziemlich cool.

Leushenkos Antwort ist wirklich cool: Nur das foo-Beispiel kompiliert nicht mit GCC, was bei foo(7) fehlschlägt, das FIRST-Makro und den eigentlichen Funktionsaufruf ((_1, __VA_ARGS__) trifft und mit einem zusätzlichen Komma bleibt. Auch we Probleme bekommen, wenn wir zusätzliche Überladungen wie foo(double) bereitstellen wollen.

Also beschloss ich, diese Frage ausführlicher zu beantworten, einschließlich der Erlaubnis der leeren Überladung (foo(void) - was einige Probleme verursachte ...).

Die Idee ist jetzt: Definieren Sie mehr als ein Generikum in verschiedenen Makros und lassen Sie das richtige anhand der Anzahl der Argumente auswählen!

Die Anzahl der Argumente ist ziemlich einfach, basierend auf dieser Antwort:

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) #define CONCAT(X, Y) CONCAT_(X, Y) # definiere CONCAT_(X, Y) X ## Y

Das ist gut, wir entscheiden uns entweder für SELECT_1 oder SELECT_2 (oder mehr Argumente, wenn Sie sie wollen/benötigen), also brauchen wir nur die entsprechenden Definitionen:

#define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char: foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double : _Generic((_2), \ int: foo_double_int \) \)

Erstens erstellt ein leerer Makroaufruf (foo()) immer noch ein Token, aber es ist leer. Das count-Makro gibt also tatsächlich 1 statt 0 zurück, selbst wenn das Makro leer aufgerufen wird. Wir können dieses Problem "einfach" beheben, wenn wir __VA_ARGS__ mit einem Komma nach __VA_ARGS__ setzen bedingt, je nachdem ob die Liste leer ist oder nicht:

#define NARG(...) ARG4_(__VA_ARGS__ KOMMA(__VA_ARGS__) 4, 3, 2, 1, 0)

Das sah einfach, aber das COMMA-Makro ist ziemlich schwer; Glücklicherweise wird dieses Thema bereits in Jens Gustedts Blog behandelt (danke, Jens). Der Haupttrick besteht darin, dass Funktionsmakros nicht erweitert werden, es sei denn, es folgen Klammern, siehe Jens-Blog für weitere Erklärungen ... Wir müssen die Makros nur ein wenig an unsere Bedürfnisse anpassen (ich werde der Kürze halber kürzere Namen und weniger Argumente verwenden) .

#define ARGN(...) ARGN_(__VA_ARGS__) #define ARGN_(_0, _1, _2, _3, N, ...) N #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0 ) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_COMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3 # define COMMA_0000 , #define COMMA_0001 #define COMMA_0010 , // ... (alle anderen mit Komma) #define COMMA_1111 ,

Und jetzt geht es uns gut...

Vollständiger Code in einem Block:

/* * demo.c * * Erstellt am: 14.09.2017 * Autor: sboehler */ #include void foo_void(void) ( puts("void"); ) void foo_int(int c) ( printf("int: %d\n", c); ) void foo_char(char c) ( printf("char: %c \n", c); ) void foo_double(double c) ( printf("double: %.2f\n", c); ) void foo_double_int(double c, int d) ( printf("double: %.2f, int: %d\n", c, d); ) #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) # define CONCAT(X, Y) CONCAT_(X, Y) #define CONCAT_(X, Y) X ## Y #define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char : foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double: _Generic((_2), \ int: foo_double_int \) \) #define ARGN(...) ARGN_( __VA_ARGS__) #define ARGN_(_0, _1, _2, N, ...) N #define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0) #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_C OMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3 COMMA_1001 , #define COMMA_1010 , #define COMMA_1011 , #define COMMA_1100 , #define COMMA_1101 , #define COMMA_1110 , #define COMMA_1111 , int main(int argc, char** argv) ( foo(); foo(7); foo(10.12); foo(12.10, 7); foo((char)"s"); 0 zurückgeben; )

Anmerkung: Die Vorlesung behandelt die Konzepte, Deklaration und Verwendung von Inline- und überladenen Funktionen in C++-Programmen, Mechanismen zur Durchführung von Substitution und Funktionsüberladung, Empfehlungen zur Verbesserung der Effizienz von Programmen durch Überladung oder Funktionssubstitution.

Zweck der Vorlesung: Inline (eingebettete) Funktionen und Funktionsüberladungen lernen, lernen, wie man Programme mit Funktionsüberladung in C++ entwickelt.

Inline-Funktionen

Eine Funktion aufrufen, ihr Werte übergeben, einen Wert zurückgeben - diese Operationen nehmen ziemlich viel CPU-Zeit in Anspruch. Normalerweise reserviert der Compiler beim Definieren einer Funktion nur einen Block von Zellen im Speicher, um seine Anweisungen zu speichern. Nachdem die Funktion aufgerufen wurde, wird die Steuerung des Programms an diese Operatoren übertragen, und nach der Rückkehr von der Funktion wird die Programmausführung ab der Zeile fortgesetzt, die dem Funktionsaufruf folgt.

Bei wiederholten Aufrufen verarbeitet das Programm jedes Mal denselben Befehlssatz, ohne Kopien für jeden Aufruf separat zu erstellen.

Jeder Sprung in den Speicherbereich mit den Funktionsanweisungen verlangsamt die Ausführung des Programms. Wenn die Funktion klein ist, können Sie bei mehreren Aufrufen Zeit sparen, indem Sie den Compiler anweisen, den Funktionscode direkt an der Aufrufstelle in das Programm einzubetten. Solche Funktionen werden aufgerufen ersetzt. Wenn man von Effizienz spricht, ist in diesem Fall zunächst die Geschwindigkeit der Programmausführung gemeint.

Inline oder Inline-Funktionen sind Funktionen, deren Code vom Compiler direkt an der Aufrufstelle eingefügt wird, anstatt die Steuerung an eine einzelne Instanz der Funktion zu übertragen.

Wenn die Funktion inline ist, erstellt der Compiler die angegebene Funktion nicht im Speicher, sondern kopiert ihre Zeichenfolgen direkt in den Programmcode an der Aufrufstelle. Dies entspricht dem Schreiben geeigneter Blöcke im Programm anstelle von Funktionsaufrufen. Also der Spezifizierer in der Reihe definiert für die Funktion die sog interne Bindung, was darin liegt, dass der Compiler die Befehle seines Codes ersetzt, anstatt die Funktion aufzurufen. Inline-Funktionen werden verwendet, wenn der Funktionskörper aus mehreren Anweisungen besteht.

Mit diesem Ansatz können Sie die Geschwindigkeit der Programmausführung erhöhen, da Befehle aus dem Programm ausgeschlossen werden. Mikroprozessor Das Erforderliche, um Argumente zu übergeben und die Funktion aufzurufen.

Zum Beispiel:

/*Funktion gibt den Abstand vom Punkt mit den Koordinaten (x1,y1) zum Punkt mit den Koordinaten (x2,y2) zurück*/ inline float Line(float x1,float y1,float x2, float y2) ( return sqrt(pow(x1-x2 ,2)+pow(y1-y2,2)); )

Allerdings ist zu beachten, dass die Verwendung von Inline-Funktionen nicht immer zu einem positiven Effekt führt. Wird eine solche Funktion mehrfach im Programmcode aufgerufen, dann in Kompilierungszeit es werden so viele Kopien dieser Funktion in das Programm eingefügt, wie es Aufrufe gibt. Es wird eine signifikante Vergrößerung des Programmcodes geben, wodurch die erwartete Steigerung der Effizienz der zeitlichen Programmausführung möglicherweise nicht eintritt.

Beispiel 1.

#einschließen "stdafx.h" #einschließen mit Namensraum std; Inline int Cube(int x); int _tmain(int argc, _TCHAR* argv)( int x=2; float y=3; double z=4; cout<

Wir listen die Gründe auf, warum eine Funktion mit dem Inline-Bezeichner als reguläre Nicht-Inline-Funktion behandelt wird:

  • die Inline-Funktion ist rekursiv;
  • Funktionen, deren Aufruf vor ihrer Definition steht;
  • Funktionen, die mehr als einmal in einem Ausdruck aufgerufen werden;
  • Funktionen, die Schleifen, Schalter und enthalten Sprungoperatoren;
  • Funktionen, die zu groß sind, um eine Substitution zu ermöglichen.

Einschränkungen bei der Durchführung von Substitutionen sind meistens implementierungsabhängig. Wenn für eine Funktion mit einem Bezeichner in der Reihe Der Compiler kann die Substitution aufgrund des Kontexts, in dem der Aufruf platziert wird, nicht ausführen, dann wird die Funktion als statisch betrachtet und ausgegeben Warnmeldung.

Ein weiteres Merkmal von Inline-Funktionen ist die Unmöglichkeit, sie zu ändern, ohne alle Teile des Programms, in denen diese Funktionen aufgerufen werden, neu zu kompilieren.

Funktionsüberlastung

Bei der Definition von Funktionen in Programmen müssen der Typ des von der Funktion zurückgegebenen Werts sowie die Anzahl der Parameter und deren Typ angegeben werden. Wenn die C++-Sprache eine Funktion namens add_values ​​hätte, die mit zwei ganzzahligen Werten arbeitet, und das Programm eine ähnliche Funktion verwenden müsste, um drei ganzzahlige Werte zu übergeben, müsste eine Funktion mit einem anderen Namen erstellt werden. Zum Beispiel add_two_values ​​​​und add_three_values ​​​​. Wenn Sie eine ähnliche Funktion zum Arbeiten mit Float-Werten verwenden möchten, benötigen Sie eine andere Funktion mit einem anderen Namen. Um die Duplizierung von Funktionen zu vermeiden, erlaubt Ihnen C++, mehrere Funktionen mit demselben Namen zu definieren. Während der Kompilierung berücksichtigt C++ die Anzahl der Argumente, die von jeder Funktion verwendet werden, und ruft dann genau die erforderliche Funktion auf. Dem Compiler die Wahl zwischen mehreren Funktionen zu geben, wird als Überladen bezeichnet.

Funktionsüberladung ist die Erstellung mehrerer Funktionen mit demselben Namen, aber unterschiedlichen Parametern. Unterschiedliche Parameter bedeuten, was anders sein sollte Anzahl Argumente Funktionen und/oder deren Art. Das heißt, das Überladen von Funktionen ermöglicht es Ihnen, mehrere Funktionen mit demselben Namen und Rückgabetyp zu definieren.

Das Überladen von Funktionen wird auch als Überladen von Funktionen bezeichnet Funktionspolymorphismus. "Poly" bedeutet viel, "Morph" - eine Form, dh eine polymorphe Funktion ist eine Funktion, die sich durch eine Vielzahl von Formen auszeichnet.

Unter Funktionspolymorphismus versteht man das Vorhandensein mehrerer überladener Versionen einer Funktion im Programm, die unterschiedliche Werte haben. Indem Sie die Anzahl oder Art der Parameter ändern, können Sie zwei oder mehr Funktionen denselben Namen geben. In diesem Fall gibt es keine Verwirrung, da die gewünschte Funktion durch die Übereinstimmung der verwendeten Parameter bestimmt wird. Auf diese Weise können Sie eine Funktion erstellen, die mit Ganzzahlen, Gleitkommazahlen oder anderen Arten von Werten arbeiten kann, ohne für jede Funktion separate Namen erstellen zu müssen.

So dank der Verwendung überladene Funktionen, sollten Sie sich keine Gedanken darüber machen, die richtige Funktion im Programm aufzurufen, die dem Typ der übergebenen Variablen entspricht. Wenn Sie eine überladene Funktion aufrufen, bestimmt der Compiler automatisch, welche Version der Funktion verwendet werden soll.

Das folgende Programm überlädt beispielsweise eine Funktion namens add_values ​​. Die erste Funktionsdefinition fügt zwei Werte vom Typ int hinzu. Die zweite Funktionsdefinition fügt drei Werte vom Typ int hinzu. Beim Kompilieren bestimmt C++ korrekt die zu verwendende Funktion:

#einschließen "stdafx.h" #einschließen mit Namensraum std; int add_values ​​(int a, int b); int add_values ​​​​(int a, int b, int c); int _tmain(int argc, _TCHAR* argv)( cout<< "200+801=" << add_values(200,801) << "\n"; cout << "100+201+700=" << add_values(100,201,700) << "\n"; system("pause"); return 0; } int add_values(int a,int b) { return(a + b); } int add_values (int a, int b, int c) { return(a + b + c); }

Somit definiert das Programm zwei Funktionen namens add_values ​​​​. Die erste Funktion fügt zwei Werte hinzu, während die zweite drei Werte desselben int-Typs hinzufügt. Der C++-Compiler bestimmt anhand der vom Programm bereitgestellten Optionen, welche Funktion verwendet werden soll.

Funktionsüberladung verwenden

Einer der häufigsten Anwendungsfälle für das Überladen ist die Verwendung einer Funktion, um bei verschiedenen Parametern ein bestimmtes Ergebnis zu erzeugen. Angenommen, Ihr Programm hat eine Funktion namens day_of_week, die den aktuellen Wochentag zurückgibt (0 für Sonntag, 1 für Montag, ... , 6 für Samstag). Ein Programm könnte diese Funktion so überladen, dass sie den Wochentag korrekt zurückgibt, wenn ihr ein julianischer Tag als Parameter gegeben wird oder wenn ihr ein Tag, ein Monat und ein Jahr gegeben werden.

int day_of_week(int julianischer_tag) ( // Operatoren ) int day_of_week(int Monat, int Tag, int Jahr) ( // Operatoren )

Verwenden überladene Funktionen da werden oft fehler gemacht. Wenn sich Funktionen beispielsweise nur im Rückgabetyp, aber nicht im Argumenttyp unterscheiden, können solche Funktionen nicht denselben Namen haben. Die folgende Überladungsoption ist ebenfalls ungültig:

int Funktionsname (int Argument_Name); int Funktionsname (int Argument_Name); /* ungültige Namensüberladung: Argumente haben dieselbe Nummer und denselben Typ */

Vorteile der Funktionsüberladung:

  • Funktionsüberlastung verbessert sich Lesbarkeit Programme;
  • Das Überladen von C++-Funktionen ermöglicht es Programmen, mehrere Funktionen mit demselben Namen zu definieren;
  • überladene Funktionen Rückgabewerte des gleichen Typs, können sich jedoch in Anzahl und Art der Parameter unterscheiden;
  • Das Überladen von Funktionen vereinfacht die Aufgabe von Programmierern, indem sie sich nur einen Funktionsnamen merken müssen, aber dann müssen sie wissen, welche Kombination von Parametern welcher Funktion entspricht.

Beispiel 2.

/*Überladene Funktionen haben denselben Namen, aber unterschiedliche Parameterlisten und Rückgabewerte*/ #include "stdafx.h" #include mit Namensraum std; int Durchschnitt (int erste_Zahl, int zweite_Zahl, int dritte_Zahl); int Durchschnitt (int first_number, int second_number); int _tmain(int argc, _TCHAR* argv)(// Hauptfunktion int number_A = 5, number_B = 3, number_C = 10; cout<< "Целочисленное среднее чисел " << number_A << " и "; cout << number_B << " равно "; cout << average(number_A, number_B) << ".\n\n"; cout << "Целочисленное среднее чисел " << number_A << ", "; cout << number_B << " и " << number_C << " равно "; cout << average(number_A, number_B, number_C) << ".\n"; system("PAUSE"); return 0; }// конец главной функции /*функция для вычисления целочисленного среднего значения 3-х целых чисел*/ int average(int first_number, int second_number, int third_number) { return((first_number + second_number + third_number)/3); } // конец функции /*функция для вычисления целочисленного среднего значения 2-х целых чисел*/ int average(int first_number, int second_number) { return((first_number + second_number)/2); } // конец функции



Wird geladen...
oben