Was ist der Sinn von const-Zeigern?

149

Ich spreche nicht von Zeigern auf const-Werte, sondern von const-Zeigern selbst.

Ich lerne C und C ++ über das Grundlegende hinaus und habe bis heute festgestellt, dass Zeiger wertmäßig an Funktionen übergeben werden, was Sinn macht. Dies bedeutet, dass ich innerhalb einer Funktion den kopierten Zeiger auf einen anderen Wert zeigen lassen kann, ohne den ursprünglichen Zeiger des Aufrufers zu beeinflussen.

Was bringt es also, einen Funktionsheader zu haben, der sagt:

void foo(int* const ptr);

In einer solchen Funktion können Sie ptr nicht auf etwas anderes verweisen lassen, da es const ist und Sie nicht möchten, dass es geändert wird, sondern eine Funktion wie diese:

void foo(int* ptr);

Funktioniert das genauso gut! da der Zeiger sowieso kopiert wird und der Zeiger im Aufrufer nicht betroffen ist, selbst wenn Sie die Kopie ändern. Was ist der Vorteil von const?

R. Ruiz.
quelle
29
Was ist, wenn Sie beim Kompilieren garantieren möchten, dass der Zeiger nicht geändert werden kann und sollte, um auf etwas anderes zu verweisen?
Platinum Azure
25
Genau der gleiche Punkt wie jeder constParameter.
David Heffernan
2
@PlatinumAzure, ich habe einige Jahre Erfahrung im Programmieren, aber meine Softwarepraktiken fehlen. Ich bin froh, dass ich diese Frage gestellt habe, weil ich nie das Bedürfnis hatte, den Compiler auf meine eigenen Logikfehler hinweisen zu lassen. Meine erste Reaktion zum Zeitpunkt der Übermittlung der Frage war: "Warum sollte es mich interessieren, wenn der Zeiger geändert wird? Der Anrufer wird davon nicht betroffen." Bis ich alle Antworten gelesen habe, habe ich verstanden, dass es mich interessieren sollte, denn wenn ich versuche, sie zu ändern, sind ich und meine Codierungslogik falsch (oder es gab einen Tippfehler wie das Fehlen des Sterns) und ich hätte möglicherweise nicht bemerkt, ob der Compiler dies nicht getan hat. ' Sag es mir nicht.
R. Ruiz.
3
@ R.Ruiz. Zweifellos könnten selbst die erfahrensten von uns zusätzliche Garantien für die Richtigkeit gebrauchen. Da Software-Engineering eine Form des Engineerings ist, bei der ein wenig mehr Spielraum akzeptabel sein kann (zufällige HTTP 502s, langsame Verbindungen, das gelegentliche Nichtladen von Bildern sind keine außergewöhnlichen Anlässe, aber ein Motorausfall in einem Flugzeug ist inakzeptabel und wahrscheinlich schwerwiegend), manchmal Menschen programmieren mit unangemessener Eile. Das gleiche Argument, das das Schreiben von Unit-Tests rechtfertigt, erklärt die Verwendung von const-korrektheitsgarantien. Wir fühlen uns nur sicherer, dass unser Code zweifellos korrekt ist.
Platinum Azure
3
Eine verwandte Frage: stackoverflow.com/questions/219914/…
Raedwald

Antworten:

207

const ist ein Tool, das Sie zur Verfolgung eines sehr wichtigen C ++ - Konzepts verwenden sollten:

Finden Sie Fehler zur Kompilierungszeit und nicht zur Laufzeit, indem Sie den Compiler dazu bringen, das zu erzwingen, was Sie meinen.

Auch wenn die Funktionalität dadurch nicht geändert wird, führt das Hinzufügen zu consteinem Compilerfehler, wenn Sie Dinge tun, die Sie nicht tun wollten. Stellen Sie sich folgenden Tippfehler vor:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

Wenn Sie verwenden int* const, würde dies einen Compilerfehler erzeugen, da Sie den Wert in ändern ptr. Das Hinzufügen von Einschränkungen über die Syntax ist im Allgemeinen eine gute Sache. Gehen Sie einfach nicht zu weit - das Beispiel, das Sie gegeben haben, ist ein Fall, in dem sich die meisten Menschen nicht darum kümmern const.

zehn vier
quelle
8
Danke, das ist eine Antwort, die mich überzeugt. Sie setzen const so, dass der Compiler Sie vor Ihren eigenen Zuweisungsfehlern warnt. Ihr Beispiel ist perfekt, um dieses Konzept zu veranschaulichen, da dies ein häufiger Fehler bei Zeigern ist. Als du!
R. Ruiz.
25
"Hilf dem Compiler, dir zu helfen" ist das Mantra, das ich normalerweise dafür singe.
Flexo
1
+1, aber hier ist ein interessanter Fall, in dem es umstritten ist: stackoverflow.com/questions/6305906/…
Raedwald
3
Es ist also dieselbe Antwort, die Sie auf "Warum Variablen privat machen, wenn Sie sie nur nicht außerhalb der Klasse verwenden können" geben würden.
Lee Louviere
Dies mag ein wenig vom Thema abweichen, aber wo befindet sich dieser Konstantenzeiger im Speicher? Ich weiß, dass alle const im globalen Teil des Speichers sitzen, aber ich bin mir nicht 100% sicher, ob ein const als func var übergeben wird. Danke und Entschuldigung, wenn dies nicht zum Thema gehört
Tomer
77

Ich lege Wert darauf, nur const Argumente zu verwenden, da dies mehr Compilerprüfungen ermöglicht: Wenn ich versehentlich einen Argumentwert innerhalb der Funktion neu zuweise, beißt mich der Compiler.

Ich verwende Variablen selten wieder. Es ist sauberer, neue Variablen zu erstellen, um neue Werte aufzunehmen. Daher sind im Wesentlichen alle meine Variablendeklarationen const(mit Ausnahme einiger Fälle wie Schleifenvariablen, in denen constder Code nicht funktioniert).

Beachten Sie, dass dies nur bei der Definition einer Funktion sinnvoll ist . Es gehört nicht in die Deklaration , was der Benutzer sieht. Und dem Benutzer ist es egal, ob ich constfür Parameter innerhalb der Funktion verwende.

Beispiel:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

Beachten Sie, wie sowohl das Argument als auch die lokale Variable sind const. Weder ist notwendig, aber mit Funktionen, die noch etwas größer sind, hat mich dies wiederholt vor Fehlern bewahrt.

Konrad Rudolph
quelle
17
+1von einem anderen constbesessenen Fanatiker. Ich ziehe es jedoch vor, dass meine Compiler mich ankläffen. Ich mache zu viele Fehler und würde zu sehr leiden, wenn sie beißen würden.
sbi
2
+1, ich empfehle die constStrategie "es sei denn, es gibt einen guten Grund". Es gibt jedoch einige gute Ausnahmen, z. B. Kopieren und Tauschen
Flexo
2
Wenn ich eine neue Sprache entwerfen würde, wären deklarierte Objekte standardmäßig schreibgeschützt ("const"). Sie benötigen eine spezielle Syntax, möglicherweise ein Schlüsselwort "var", um ein Objekt beschreibbar zu machen.
Keith Thompson
@ Keith Ich bin versucht, "natürlich" zu sagen. Alles andere ist an dieser Stelle dumm. Leider scheinen die meisten Sprachdesigner nicht mit uns übereinzustimmen. Tatsächlich habe ich bereits so viel gepostet (in einer jetzt gelöschten Frage: „Was ist Ihre umstrittenste Programmiermeinung?“).
Konrad Rudolph
1
@ KubaOber Ich sehe nicht, wie es überhaupt eine Pessimisierung ist. Wenn Sie später feststellen, dass Sie ändern müssen, was im Hauptteil der Funktion übergeben wurde, löschen Sie einfach constdas Argument und kommentieren Sie vorzugsweise, warum. Diese winzige zusätzliche Arbeit rechtfertigt nicht, alle Argumente als nicht conststandardmäßig zu markieren und sich allen potenziellen Fehlern zu öffnen, die dadurch entstehen.
underscore_d
20

Ihre Frage berührt etwas Allgemeineres: Sollten Funktionsargumente const sein?

Die Konstanz von Wertargumenten (wie Ihr Zeiger) ist ein Implementierungsdetail und nicht Teil der Funktionsdeklaration. Dies bedeutet, dass Ihre Funktion immer folgende ist:

void foo(T);

Es liegt ganz bei der Implementiererin der Funktion, ob sie die Argumentvariable functions-scope auf veränderliche oder konstante Weise verwenden möchte:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

Befolgen Sie also die einfache Regel, constdie Deklaration (Header) niemals einzufügen, und fügen Sie sie in die Definition (Implementierung) ein, wenn Sie die Variable nicht ändern möchten oder müssen.

Kerrek SB
quelle
5
Ich stimme dem zu - aber ich bin etwas unzufrieden mit der Idee, die Erklärung und Definition anders zu machen. Für andere Dinge als constdie Deklaration und den (Prototyp-Teil von) der Definition werden sie im Allgemeinen identisch sein.
Keith Thompson
@ KeithThompson: Nun, du musst es nicht tun, wenn du nicht willst. Geben Sie constdie Erklärung einfach nicht ein . Es liegt ganz bei Ihnen, ob Sie das Qualifikationsmerkmal zur Implementierung hinzufügen möchten.
Kerrek SB
1
Wie gesagt, ich stimme zu, dass es constSinn macht , die Erklärung, aber nicht die Definition abzugeben. Es fühlt sich einfach wie ein Fehler in der Sprache an, dass dies der einzige Fall ist, in dem es Sinn macht, die Deklaration und Definition nicht identisch zu machen. (Es ist natürlich kaum Cs einzige Panne.)
Keith Thompson
16

Das const-Qualifikationsmerkmal der obersten Ebene wird in Deklarationen verworfen, sodass die Deklarationen in der Frage genau dieselbe Funktion deklarieren. Andererseits überprüft der Compiler in der Definition (Implementierung), ob der Zeiger, wenn Sie ihn als const markieren, nicht im Hauptteil der Funktion geändert wird.

David Rodríguez - Dribeas
quelle
Das wusste ich nicht. Wenn ich also versuche, void foo (int * const ptr) mit void foo (int * t ptr) zu überladen, wird ein Compilerfehler angezeigt. Vielen Dank!
R. Ruiz.
@ R.Ruiz. Wenn Ihre Verzögerung void foo (int * t ptr) und Ihre Definition void foo (int * const ptr) ist, wird KEIN Compilerfehler angezeigt.
Trevor Hickey
1
@ TrevorHickey Sie werden ... aber nicht aus dem Grund, den sie denken. int* t ptrist ein Syntaxfehler. Ohne das sind die beiden für Überlastungszwecke identisch.
underscore_d
14

Sie haben Recht, für den Anrufer macht es absolut keinen Unterschied. Aber für den Verfasser der Funktion kann es ein Sicherheitsnetz sein "okay, ich muss sicherstellen, dass ich diesen Punkt nicht auf das Falsche hinweise". Nicht sehr nützlich, aber auch nicht nutzlos.

Es ist im Grunde das gleiche wie int const the_answer = 42in Ihrem Programm.

cnicutar
quelle
1
Was macht es aus, wohin sie zeigen, es ist ein Zeiger, der auf dem Stapel zugewiesen ist? Die Funktion kann das nicht vermasseln. Es ist weitaus sinnvoller, schreibgeschützte Zeiger zu verwenden, wenn es sich um Zeiger auf Zeiger handelt. Es ist nicht dasselbe wie int const! Ich werde dies nur für diese irreführende Aussage ablehnen. const intund int constsind gleichwertig, während const int*und int* consthaben zwei verschiedene Bedeutungen!
Lundin
1
@lundin Nehmen wir an, die Funktion ist groß und enthält andere Inhalte. Aus Versehen kann man auf etwas anderes verweisen (auf dem Stapel der Funktion). Dies ist für den Anrufer nicht problematisch, könnte aber sicherlich für den Angerufenen sein. Ich sehe in meinen Aussagen nichts Irreführendes: " Für den Verfasser der Funktion kann es ein Sicherheitsnetz sein".
Cnicutar
1
@Lundin Über int constTeil; Ich habe den Typ seit einiger Zeit vor const gestellt (es klingt unnatürlich) und bin mir der Unterschiede bewusst. Dieser Artikel könnte sich als nützlich erweisen. Ich selbst hatte jedoch etwas andere Gründe, zu diesem Stil zu wechseln.
Cnicutar
1
In Ordnung. Ich habe gerade eine langatmige Antwort für den Fall geschrieben, dass jemand anderes als Sie und ich diesen Beitrag lesen. Ich habe auch eine Begründung aufgenommen, warum int const ein schlechter Stil und const int ein guter Stil ist.
Lundin
Lundin, ich lese auch! Ich bin damit einverstanden, dass const int der bessere Stil ist. @cnicutar, Ihre erste Antwort auf Lundin ist das, wonach ich gesucht habe, als ich diese Frage eingereicht habe. Das Problem liegt also nicht beim Anrufer (wie ich gedankenlos dachte), aber const ist eine Zusicherung für den Angerufenen. Ich mag diese Idee, danke.
R. Ruiz.
14

Es gibt viel zu const Schlüsselwort hat bieten, es ist ziemlich komplex. Im Allgemeinen wird das Hinzufügen von viel const zu Ihrem Programm als gute Programmierpraxis angesehen. Durchsuchen Sie das Web nach "const-Korrektheit" und Sie werden viele Informationen dazu finden.

Das Schlüsselwort const ist ein sogenanntes "Typqualifikationsmerkmal", andere sind volatileund restrict. Zumindest flüchtig folgt den gleichen (verwirrenden) Regeln wie const.


Erstens dient das Schlüsselwort const zwei Zwecken. Am naheliegendsten ist es, Daten (und Zeiger) vor absichtlichem oder versehentlichem Missbrauch zu schützen, indem sie schreibgeschützt werden. Jeder Versuch, eine const-Variable zu ändern, wird vom Compiler zur Kompilierungszeit erkannt.

Es gibt aber auch einen anderen Zweck in jedem System mit Nur-Lese-Speicher, nämlich sicherzustellen, dass eine bestimmte Variable in einem solchen Speicher zugeordnet ist - beispielsweise EEPROM oder Flash. Diese werden als nichtflüchtige Speicher (NVM) bezeichnet. Eine in NVM zugewiesene Variable folgt natürlich weiterhin allen Regeln einer const-Variablen.

Es gibt verschiedene Möglichkeiten, das constSchlüsselwort zu verwenden:

Deklarieren Sie eine konstante Variable.

Dies kann entweder als erfolgen

const int X=1; or
int const X=1;

Diese beiden Formen sind völlig gleichwertig . Der letztere Stil wird als schlechter Stil angesehen und sollte nicht verwendet werden.

Der Grund, warum die zweite Zeile als schlechter Stil angesehen wird, liegt wahrscheinlich darin, dass "Speicherklassenspezifizierer" wie statisch und extern auch nach dem tatsächlichen Typ deklariert werden können.int static usw. . Dies wird jedoch für Speicherklassenspezifizierer als veraltete Funktion gekennzeichnet vom C-Ausschuss (Entwurf ISO 9899 N1539, 6.11.5). Aus Gründen der Konsistenz sollte man daher auch keine Typqualifizierer auf diese Weise schreiben. Es dient keinem anderen Zweck, als den Leser irgendwie zu verwirren.

Deklarieren Sie einen Zeiger auf eine konstante Variable.

const int* ptr = &X;

Dies bedeutet, dass der Inhalt von 'X' nicht geändert werden kann. Dies ist die normale Art und Weise, wie Sie solche Zeiger deklarieren, hauptsächlich als Teil von Funktionsparametern für "const-Korrektheit". Da 'X' nicht unbedingt als const deklariert werden muss, kann es sich um eine beliebige Variable handeln. Mit anderen Worten, Sie können eine Variable jederzeit auf const "upgraden". Technisch gesehen erlaubt C auch das Herabstufen von const auf eine einfache Variable durch explizite Typumwandlungen, dies wird jedoch als schlechte Programmierung angesehen, und Compiler geben normalerweise Warnungen davor aus.

Deklarieren Sie einen konstanten Zeiger

int* const ptr = &X;

Dies bedeutet, dass der Zeiger selbst konstant ist. Sie können ändern, auf was es zeigt, aber Sie können den Zeiger selbst nicht ändern. Dies hat nicht viele Verwendungszwecke, es gibt einige, wie zum Beispiel sicherzustellen, dass die Adresse eines Zeigers (Zeiger auf Zeiger) nicht geändert wird, während er als Parameter an eine Funktion übergeben wird. Sie müssen etwas schreiben, das nicht zu lesbar ist:

void func (int*const* ptrptr)

Ich bezweifle, dass viele C-Programmierer die const und * genau dort rein bekommen können. Ich weiß, ich kann nicht - ich musste mich bei GCC erkundigen. Ich denke, deshalb sieht man diese Syntax für Zeiger zu Zeiger selten, obwohl sie als gute Programmierpraxis angesehen wird.

Konstante Zeiger können auch verwendet werden, um sicherzustellen, dass die Zeigervariable selbst im schreibgeschützten Speicher deklariert ist. Sie können beispielsweise eine zeigerbasierte Nachschlagetabelle deklarieren und in NVM zuweisen.

Und natürlich können, wie durch andere Antworten angezeigt, konstante Zeiger auch verwendet werden, um "konstante Korrektheit" zu erzwingen.

Deklarieren Sie einen konstanten Zeiger auf konstante Daten

const int* const ptr=&X;

Dies sind die beiden oben beschriebenen Zeigertypen, kombiniert mit allen Attributen von beiden.

Deklarieren Sie eine schreibgeschützte Elementfunktion (C ++).

Da dies mit C ++ gekennzeichnet ist, sollte ich auch erwähnen, dass Sie Elementfunktionen einer Klasse als const deklarieren können. Dies bedeutet, dass die Funktion beim Aufrufen kein anderes Mitglied der Klasse ändern darf, was sowohl den Programmierer der Klasse vor versehentlichen Fehlern schützt als auch den Aufrufer der Mitgliedsfunktion darüber informiert, dass sie nichts durcheinander bringen bis es anruft. Die Syntax lautet:

void MyClass::func (void) const;
Lundin
quelle
8

... heute habe ich festgestellt, dass Zeiger wertmäßig an Funktionen übergeben werden, was Sinn macht.

(imo) es macht wirklich keinen Sinn als Standard. Die sinnvollere Standardeinstellung ist die Übergabe als nicht neu zuweisbarer Zeiger ( int* const arg). Das heißt, ich hätte es vorgezogen, wenn Zeiger, die als Argumente übergeben wurden, implizit als const deklariert wurden.

Was ist der Vorteil von const?

Der Vorteil ist, dass es einfach und manchmal unklar ist, wenn Sie die Adresse ändern, auf die das Argument verweist, sodass Sie einen Fehler einführen können, wenn es nicht einfach ist. Das Ändern der Adresse ist untypisch. Es ist klarer, eine lokale Variable zu erstellen, wenn Sie die Adresse ändern möchten. Außerdem ist die Manipulation von Rohzeigern eine einfache Möglichkeit, Fehler einzuführen.

Daher ist es klarer, eine unveränderliche Adresse zu übergeben und eine Kopie (in diesen atypischen Fällen) zu erstellen, wenn Sie die Adresse ändern möchten, auf die das Argument verweist:

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

Das Hinzufügen dieses lokalen Elements ist praktisch kostenlos und verringert die Wahrscheinlichkeit von Fehlern, während die Lesbarkeit verbessert wird.

Auf einer höheren Ebene: Wenn Sie das Argument als Array bearbeiten, ist es für den Client in der Regel klarer und weniger fehleranfällig, das Argument als Container / Sammlung zu deklarieren.

Im Allgemeinen ist das Hinzufügen von const zu Werten, Argumenten und Adressen eine gute Idee, da Sie die Nebenwirkungen, die der Compiler gerne erzwingt, nicht immer erkennen. Daher ist es genauso nützlich wie const wie in anderen Fällen (z. B. ähnelt die Frage 'Warum sollte ich Werte const deklarieren?'). Zum Glück haben wir auch Referenzen, die nicht neu zugeordnet werden können.

Justin
quelle
4
+1 dafür ist die Standardeinstellung. C ++ hat es, wie die meisten Sprachen, falsch herum. Anstatt ein constSchlüsselwort zu haben, sollte es ein mutableSchlüsselwort haben (nun ja, aber mit der falschen Semantik).
Konrad Rudolph
2
Interessante Idee mit const als Standard. Danke für deine Antwort!
R. Ruiz.
6

Wenn Sie eingebettete Systeme oder Gerätetreiber programmieren, bei denen Geräte mit Speicherzuordnung vorhanden sind, werden häufig beide Formen von 'const' verwendet, um zu verhindern, dass der Zeiger neu zugewiesen wird (da er auf eine feste Hardwareadresse verweist), und wenn das Peripheriegerät Das Register, auf das es zeigt, ist ein schreibgeschütztes Hardwareregister. Dann erkennt eine andere Konstante viele Fehler zur Kompilierungszeit und nicht zur Laufzeit.

Ein schreibgeschütztes 16-Bit-Peripherie-Chipregister könnte ungefähr so ​​aussehen:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

Dann können Sie das Hardwareregister einfach lesen, ohne auf die Assemblersprache zurückgreifen zu müssen:

input_word = *peripheral;

Dan Haynes
quelle
5

int iVal = 10; int * const ipPtr = & iVal;

Genau wie bei einer normalen const-Variablen muss ein const-Zeiger bei der Deklaration auf einen Wert initialisiert werden, und sein Wert kann nicht geändert werden.

Dies bedeutet, dass ein const-Zeiger immer auf denselben Wert zeigt. Im obigen Fall zeigt ipPtr immer auf die Adresse von iVal. Da der Wert, auf den gezeigt wird, immer noch nicht konstant ist, ist es möglich, den Wert, auf den gezeigt wird, durch Dereferenzieren des Zeigers zu ändern:

* ipPtr = 6; // erlaubt, da pnPtr auf eine nicht konstante int zeigt

jusathr
quelle
5

Dieselbe Frage kann für jeden anderen Typ gestellt werden (nicht nur für Zeiger):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}
pmg
quelle
LOL, du hast recht. Der Fall von Zeigern kam mir zuerst in den Sinn, weil ich dachte, sie wären etwas Besonderes, aber sie sind genau wie jede andere Variable, die als Wert übergeben wird.
R. Ruiz.
5

Ihre Frage ist wirklich mehr darüber, warum Sie eine Variable als const definieren und nicht nur als const-Zeigerparameter für eine Funktion. Hier gelten die gleichen Regeln wie beim Definieren einer Variablen als Konstante, wenn es sich um einen Funktionsparameter, eine Mitgliedsvariable oder eine lokale Variable handelt.

In Ihrem speziellen Fall macht es funktional keinen Unterschied wie in vielen anderen Fällen, wenn Sie eine lokale Variable als const deklarieren, aber es gibt eine Einschränkung, dass Sie diese Variable nicht ändern können.

zar
quelle
Ihr Kommentar macht deutlich, wie sinnlos meine Frage ist, weil Sie Recht haben: Es ist dasselbe wie mit jedem anderen Parameter als const. Es mag nutzlos erscheinen, aber es soll dem Programmierer helfen, diese Einschränkung zu haben und Fehler zu vermeiden
R. Ruiz.
4

Das Übergeben eines const-Zeigers an eine Funktion ist wenig sinnvoll, da er sowieso als Wert übergeben wird. Dies ist nur eines der Dinge, die das allgemeine Sprachdesign zulässt. Wenn Sie es nur verbieten, weil es keinen Sinn ergibt, wird nur die Sprachspezifikation festgelegt. größer.

Wenn Sie sich in einer Funktion befinden, ist dies natürlich ein anderer Fall. Ein Zeiger, der nicht ändern kann, worauf er zeigt, ist eine Behauptung, die den Code klarer macht.

Anders Abel
quelle
4

Ich denke, ein Vorteil wäre, dass der Compiler aggressivere Optimierungen innerhalb der Funktion durchführen kann, da er weiß, dass sich dieser Zeiger nicht ändern kann.

Es vermeidet auch zB. Übergeben dieses Zeigers an eine Unterfunktion, die eine nicht konstante Zeigerreferenz akzeptiert (und daher den Zeiger wie ändern könnte void f(int *&p)), aber ich stimme zu, dass die Nützlichkeit in diesem Fall etwas eingeschränkt ist.

MartinStettner
quelle
+1 für die Optimierungen. Zumindest sagen die Bücher, dass es wahr ist. Ich möchte genau verstehen, was diese Optimierungen sind
R. Ruiz.
4

Ein Beispiel dafür, wo ein const-Zeiger hoch anwendbar ist, kann auf diese Weise demonstriert werden. Angenommen, Sie haben eine Klasse mit einem dynamischen Array darin und möchten dem Benutzer den Zugriff auf das Array übergeben, ohne ihm jedoch die Rechte zum Ändern des Zeigers zu erteilen. Erwägen:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

Welches produziert:

Eingabedaten
setzen Daten

Aber wenn wir das versuchen:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

Wir bekommen:

Fehler: lWert als linker Operand der Zuordnung erforderlich // Drat erneut vereitelt!

So klar können wir den Inhalt des Arrays ändern, aber nicht den Zeiger des Arrays. Gut, wenn Sie sicherstellen möchten, dass der Zeiger einen konsistenten Status hat, wenn Sie ihn an den Benutzer zurückgeben. Es gibt jedoch einen Haken:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

Wir können die Speicherreferenz des Zeigers weiterhin löschen, auch wenn wir den Zeiger selbst nicht ändern können.

Wenn Sie also möchten, dass die Speicherreferenz immer auf etwas verweist (IE wird niemals geändert, ähnlich wie eine Referenz derzeit funktioniert), ist sie in hohem Maße anwendbar. Wenn Sie möchten, dass der Benutzer vollen Zugriff hat und ihn ändert, ist non-const genau das Richtige für Sie.

Bearbeiten:

Nachdem er den Kommentar okorz001 bemerkt hat, dass er nicht zugewiesen werden kann, weil GetArray () ein Operand mit dem richtigen Wert ist, ist sein Kommentar völlig korrekt, aber das oben Gesagte gilt immer noch, wenn Sie einen Verweis auf den Zeiger zurückgeben (ich nehme an, ich habe GetArray angenommen) unter Bezugnahme auf eine Referenz), zum Beispiel:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

Wird im ersten zurückkehren, was zu einem Fehler führt:

Fehler: Zuweisung des schreibgeschützten Speicherorts 'Temp.TestA :: GetArray ()'

Aber die zweite wird trotz möglicher Konsequenzen für die Unterseite fröhlich auftreten.

Offensichtlich wird die Frage aufgeworfen, warum Sie einen Verweis auf einen Zeiger zurückgeben möchten. Es gibt seltene Fälle, in denen Sie dem betreffenden Originalzeiger Speicher (oder Daten) direkt zuweisen müssen (z. B. Erstellen eines eigenen malloc / free- oder new / free-Frontends). In diesen Fällen handelt es sich jedoch um eine nicht konstante Referenz . Ein Verweis auf einen const-Zeiger Ich bin nicht auf eine Situation gestoßen, die dies rechtfertigen würde (es sei denn, es handelt sich möglicherweise um deklarierte const-Referenzvariablen anstatt um Rückgabetypen?).

Überlegen Sie, ob wir eine Funktion haben, die einen konstanten Zeiger verwendet (im Gegensatz zu einer, die dies nicht tut):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

Der Fehler in der const erzeugt also folgende Meldung:

Fehler: Dekrement des schreibgeschützten Parameters 'Daten'

Was gut ist, da wir das wahrscheinlich nicht wollen, es sei denn, wir wollen die in den Kommentaren angegebenen Probleme verursachen. Wenn wir das Dekrement in der const-Funktion herausarbeiten, geschieht Folgendes:

NonConst: A
Const: B.

Obwohl A 'Daten [1]' ist, wird es eindeutig als 'Daten [0]' behandelt, da der NonConst-Zeiger die Dekrementierungsoperation zuließ. Wenn die const implementiert ist, wie eine andere Person schreibt, erkennen wir den potenziellen Fehler, bevor er auftritt.

Eine andere wichtige Überlegung ist, dass ein const-Zeiger als Pseudoreferenz verwendet werden kann, indem das Objekt, auf das die Referenz zeigt, nicht geändert werden kann (man fragt sich, ob es vielleicht so implementiert wurde). Erwägen:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

Beim Kompilieren wird der folgende Fehler ausgegeben:

Fehler: Zuweisung der schreibgeschützten Variablen 'B'

Was wahrscheinlich eine schlechte Sache ist, wenn ein ständiger Verweis auf A gewünscht wurde. Wenn dies auskommentiert B = NULList, lässt der Compiler uns gerne ändern *Bund daher A. Dies scheint bei Ints möglicherweise nicht nützlich zu sein. Überlegen Sie jedoch, ob Sie eine einzelne Haltung einer grafischen Anwendung hatten, in der Sie einen nicht modifizierbaren Zeiger wollten, der darauf verweist und den Sie übergeben könnten um.

Die Verwendung ist variabel (entschuldigen Sie das unbeabsichtigte Wortspiel), aber bei richtiger Verwendung ist es ein weiteres Werkzeug in der Box, das Sie bei der Programmierung unterstützt.

SSight3
quelle
2
Temp.GetArray() = NULLschlägt fehl, weil Temp.GetArray()es sich um einen r-Wert handelt, nicht weil er const-qualifiziert ist. Außerdem glaube ich, dass das const-Qualifikationsmerkmal von allen Rückgabetypen entfernt ist.
Oscar Korz
@ okorz001: Nach dem Testen sind Sie in der Tat richtig. Dies gilt jedoch, wenn Sie einen Verweis auf den Zeiger selbst zurückgeben. Ich werde meinen Beitrag entsprechend bearbeiten.
SSight3
3

Es gibt nichts Besonderes an Zeigern, bei denen man niemals möchte, dass sie konstant sind. So wie Sie konstante intWerte für Klassenmitglieder haben können, können Sie aus ähnlichen Gründen auch konstante Zeiger haben: Sie möchten sicherstellen, dass niemand jemals ändert, worauf verwiesen wird. C ++ - Referenzen adressieren dies etwas, aber das Zeigerverhalten wird von C geerbt.

Mark B.
quelle
3

Ich glaube, dies würde verhindern, dass Code den Zeiger innerhalb des Funktionskörpers inkrementiert oder dekrementiert.

Mike Christensen
quelle
3

Arten der Deklaration von Variablen wie:
(1) Deklarieren einer konstanten Variablen.
DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2) Deklarieren Sie einen Zeiger auf eine konstante Variable
const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified

Pyadav
quelle
Wir stellen eine Zeigervariable als Konstante in eine Funktion als Argument bereit, wenn wir ihren tatsächlichen Wert nicht ändern wollen
Pyadav