C++ - Für was sind "const" funktionen?

OP #2816657
Lesenswert?

Hallo zusammen. Kann mir mal jemand sagen wofür dieses "const" ist vor 
der Funktion?
1
const vec3 operator+( vec3 &e )

Und was ist der unterschied zwischen dem und wenn ich es erst am schluss 
schreibe?
1
vec3 operator+( vec3 &e ) const

Kann man auch "const" weglassen?

Hier das Ganze:
1
// Overloading Operators
2
// by Forest DUMP
3

4
#include <iostream>
5
#include <iomanip>
6

7
using namespace std;
8

9
// ---------------------------------------------------------------------------------
10
// vector 3 class
11
class vec3
12
{
13
    public:
14

15
        // vector 3 elements
16
        float x;
17
        float y;
18
        float z;
19

20
        // overload operators
21
        const vec3 operator+( vec3 &e );
22

23
        // constructor, destructor
24
        vec3( void );
25
        vec3( float x, float y, float z );
26
        ~vec3();
27
};
28

29
// ---------------------------------------------------------------------------------
30
// ability to set values on creation in the constructor
31
vec3::vec3(float x, float y, float z)
32
{
33
    this->x = x;
34
    this->y = y;
35
    this->z = z;
36
}
37

38
// ...or not
39
vec3::vec3( void )
40
{
41
}
42

43
// ---------------------------------------------------------------------------------
44
// destructor
45
vec3::~vec3()
46
{
47
}
48

49
// ---------------------------------------------------------------------------------
50
// Overload plus operator
51
const vec3 vec3::operator+( vec3 &e )
52
{
53
    vec3 result;
54
    result.x = x + e.x;
55
    result.y = y + e.y;
56
    result.z = z + e.z;
57
    return result;
58
}
59

60
// ---------------------------------------------------------------------------------
61
// main entry point (I think DarkGDK has some other form of this)
62
int main( int argc , char* argv[] )
63
{
64

65
    // define 2 vector objects
66
    vec3 myVector1(4, 4, 4);
67
    vec3 myVector2(6, 7, 3);
68

69
    // add vectors together and store in result vector
70
    vec3 result;
71
    result = myVector1 + myVector2;
72

73
    // output vectors
74
    cout << "Vector 1 : " << myVector1.x << "," << myVector1.y << "," << myVector1.z << endl;
75
    cout << "Vector 2 : " << myVector2.x << "," << myVector2.y << "," << myVector2.z << endl;
76
    cout << "result : " << result.x << "," << result.y << "," << result.z << endl;
77

78
    // end program
79
    cin.ignore();
80
    return 0;
81
}
Gast #2816660
Lesenswert?

> const vec3 operator+( vec3 &e )

liefer ein vec3 zurück der aber const ist, er darf/kann also nicht 
veändert werden. Macht aber nicht so viel sinn, wird im zusammenhang mit 
referenz verwenden.

z.b.
1
const vec3& operator+( vec3 &e )
damit verhindert man, das jemand ein objekt was man nur als Referenz 
rausgibt ändert.

Und was ist der unterschied zwischen dem und wenn ich es erst am schluss
schreibe?

vec3 operator+( vec3 &e ) const

hier sagt das const, das das objekt von der methode nicht durch den 
methoden aufruf geändert wird. (gibt auch wieder ausnahmen (mutable)

Kann man auch "const" weglassen?
kann man, aber ist sollte man nicht.
#2817497
Lesenswert?

Beide gleich: konstante Zeiger. In zweiten Fall als Referenz darauf.

Die Syntax von Typdeklarationen in C und infolgedessen C++ ist 
hoffnungslos verhunzt, da ist nichts mehr zu retten. Daher diese 
Konfusion. Mit Googles Go bin ich erstmals einer in syntaktischer 
Tradition von C stehenden Sprache begegnet, die sich davon etwas ablöst.
Gast #2817591
Lesenswert?

A. K. schrieb:
> Beide gleich: konstante Zeiger. In zweiten Fall als Referenz darauf.
>
> Die Syntax von Typdeklarationen in C und infolgedessen C++ ist
> hoffnungslos verhunzt, da ist nichts mehr zu retten.

Na komm, so schwer ist das in diesem Fall auch nicht. const bezieht sich 
immer auf das, was direkt links davon steht, mit einer Ausnahme:  Wenn 
links nichts mehr ist, bezieht es sich auf das, was direkt rechts davon 
steht.
Richtig lustig wird's eigentlich erst, wenn man Funktionen deklariert, 
die mit Funktionszeigern arbeitet. Ein gerne genommenes Beispiel ist die 
Standard-Funktion signal(), deren Deklaration so aussieht:
1
void (*signal(int signum, void (*handler)(int)))(int);

Aber auch das läßt sich mit einem Typedef entschärfen:
1
typedef void (*sighandler_t)(int);
2

3
sighandler_t signal(int signum, sighandler_t handler);

> Mit Googles Go bin ich erstmals einer in syntaktischer Tradition von C
> stehenden Sprache begegnet, die sich davon etwas ablöst.

Bei Java hat man das gelöst, indem man einfach die Hälfte weggelassen 
hat.
Moderator #2817605
Lesenswert?

Rolf Magnus schrieb:
> Na komm, so schwer ist das in diesem Fall auch nicht. const bezieht sich
> immer auf das, was direkt links davon steht, mit einer Ausnahme:  Wenn
> links nichts mehr ist, bezieht es sich auf das, was direkt rechts davon
> steht.

Ich hätte eher gesagt, const bezieht sich in C immer auf das, was direkt
rechts daneben steht, und das ohne Ausnahme, also noch einfacher.

In C++ gibt es noch das const rechts neben einer Funktionsdeklaration.
Da kann diese Regel natürlich nicht gelten, aber hier hat const auch
eine ganz andere Bedeutung, weswegen man es sich getrennt merken muss.
Da wurde einfach nur Schlüsselwortrecycling gemacht, ähnlich wie bei
static, wenn es außerhalb eines Funktionsrumpfs benutzt wird.

Eigentlich ist die Typdeklaration in C und C++ ziemlich logisch und
konsistent, wenn man das Prinzip erst mal kapiert hat. Am besten ist es,
man arbeitet sich von innen, beginnend mit dem deklarierten Variablen-
namen, nach außen.
Gast #2817615
Lesenswert?

Yalu X. schrieb:
> Rolf Magnus schrieb:
>> Na komm, so schwer ist das in diesem Fall auch nicht. const bezieht sich
>> immer auf das, was direkt links davon steht, mit einer Ausnahme:  Wenn
>> links nichts mehr ist, bezieht es sich auf das, was direkt rechts davon
>> steht.
>
> Ich hätte eher gesagt, const bezieht sich in C immer auf das, was direkt
> rechts daneben steht, und das ohne Ausnahme, also noch einfacher.

Das ist aber falsch.
1
// zweimal ein nichtkonstanter Zeiger auf einen konstanten int
2
int const * p1;
3
const int * p2;
4

5
// ein nichtkonstanter Zeiger auf einen konstanten Zeiger
6
// auf einen konstanten int
7
int const * const * p3;

> In C++ gibt es noch das const rechts neben einer Funktionsdeklaration.
> Da kann diese Regel natürlich nicht gelten, aber hier hat const auch
> eine ganz andere Bedeutung.

Dennoch gilt aber meine Regel auch da: const bezieht sich auf die 
Funktion, also das Mehod3(...), was links davon steht.
#2817639
Lesenswert?

Klar, man kann damit umgehen, aber es bleibt Murks, der sich ergab, weil 
in Deklarationen abgeleitete Typen teils durch ein Symbol links vom 
Namen (pointer) und teils rechts vom Namen (array, function) 
gekennzeichnet werden.

Und sich folglich abgeleitete Typen ineinander schachteln, statt sich 
einfach linear aufzureihen.

Weiter verkompliziert wurde es dadurch, dass Klammern darin zweierlei 
verschiedene Bedeutungen haben, nämlich Funktionen und Prioritäten. Was 
in C++ zur Folge hat, dass
  int(a);
eigentlich gleichermassen Typumwandlung und Deklaration mit unnötiger 
Klammer sein könne.

Für die natürliche Platzierung von const und volatile entsprechend der 
Umgangssprache war schlussendlich kein Platz mehr, so dass die an für 
Anfänger irritierende Stellen wanderten. Ausser anfangs beim strukturell 
ähnlichen _far - das landete da, wo es der Anfänger vermutet aber es von 
der syntaktischen Logik her nicht hingehört.

Das ist schlicht ein Konstruktionsfehler von C.
Moderator #2817694
Lesenswert?

Rolf Magnus schrieb:
> Das ist aber falsch.

Ich glaube eher, wie haben eine Unterschiedliche Leseweise für
Typdeklarationen.

> // zweimal ein nichtkonstanter Zeiger auf einen konstanten int
> int const * p1;

Mit meiner oben vorgeschlagenen "Von-Innen-Nach-Außen-Methode" (die IMHO
auch Ritchie bei der Konzeptioen der Deklarationssystax im Kopf gehabt
hat) lese ich das so:

p1 dereferenziert ist konstant und vom Typ int.

> const int * p2;

p2 dereferenziert ist vom Typ int und dieser int-Wert ist konstant.

> // ein nichtkonstanter Zeiger auf einen konstanten Zeiger
> // auf einen konstanten int
> int const  const  p3;

p3 dereferenziert ist konstant,
nochmals dereferenziert ist er wieder konstant und vom Typ int.

Diese Leseweise mag nach holprigem Deutsch aussehen, ist aber bei der
systematischen Analyse und Synthese stark verschachtelter Deklarationen
mit durchmischten Pointern, Arrays und Funktionspointern von Vorteil.

>> In C++ gibt es noch das const rechts neben einer Funktionsdeklaration.
>> Da kann diese Regel natürlich nicht gelten, aber hier hat const auch
>> eine ganz andere Bedeutung.
>
> Dennoch gilt aber meine Regel auch da: const bezieht sich auf die
> Funktion, also das Mehod3(...), was links davon steht.

Man könnte auch sagen, das const bezieht sich auf die Membervariablen,
die von der Methode nicht verändert werden. Dadurch, dass rechts vom
const nichts steht, wird angezeigt, dass die konstanten Dinge woanders
stehen, nämlich dort, wor die Membervariablen deklariert sind. Insofern
gilt meine Regel ebenfalls :)
Gast #2817729
Lesenswert?

Yalu X. schrieb:
>> int const * p1;
>
> Mit meiner oben vorgeschlagenen "Von-Innen-Nach-Außen-Methode" (die IMHO
> auch Ritchie bei der Konzeptioen der Deklarationssystax im Kopf gehabt
> hat) lese ich das so:
>
> p1 dereferenziert ist konstant und vom Typ int.

Das ist falsch, const bezieht sich hier auf int.

>> const int * p2;
>
> p2 dereferenziert ist vom Typ int und dieser int-Wert ist konstant.

Das ist richtig.  Bei

int const * p1;
const int * p2;

haben p1 und p2 den gleichen Typ!

Für einen konstanten Zeiger mußt du

int * const p3;

schreiben.

Manche Leute schreiben die Qualifier auch einheitlich rechts neben den 
Typ, auf den sie sich beziehen.

Jürgen
#2817733
Lesenswert?

Juergen schrieb:
>> p1 dereferenziert ist konstant und vom Typ int.
>
> Das ist falsch, const bezieht sich hier auf int.

Passt schon. Er wird ja erst deferenziert und ist dann erst konstant. 
Sprachlich holpert das eben etwas, man muss da geistige "sequence 
points" bei der Zerlegung einfügen.

Intention von Ritche war es, die Deklaration übersichtlich zu machen, 
damit der Programmierer es einfacher hat. Aber bekanntlich ist "gut 
gemeint" das Gegenteil von "gut gemacht" und das heutige Ergebnis ist, 
dass der Mensch wie ein Compiler denken muss, um diesen Mist überhaupt 
verstehen zu können.
#2817735
Lesenswert?

Wir haben es so gelernt, dass man immer von rechts nach links lesen 
soll.

int const * p1;
p1 ist ein Pointer auf ein konstanter Int.

const int * p1;
p1 ist ein Pointer auf eine Int-Konstante.

int * const p1;
p1 ist ein konstanter Pointer auf ein Int.

const int * const p1;
p1 ist ein konstanter Pointer auf eine Int-Konstante.

mfg
#2817744
Lesenswert?

A. K. schrieb:
> Urban B. schrieb:
>> Wir haben es so gelernt, dass man immer von rechts nach links lesen
>> soll.
>
> Was ist demzufolge
>   int (*p)[];

Diese Eselsbrücke ist nur für const-Sachen gedacht, also um rauszufinden 
ob nun der Pointer oder die Variable selbst konstant ist (oder beides) 
;-) Darum ging es ja in den letzten paar Beiträgen, deshalb habe ich 
diese Eselsbrücke mal erwähnen wollen.
#2818004
Lesenswert?

Juergen schrieb:
> Manche Leute schreiben die Qualifier auch einheitlich rechts neben den
> Typ, auf den sie sich beziehen.

Ich bin inzwischen einer davon. Und leide noch heute darunter, dass 
Teile der Code-Basis noch nicht konvertiert wurden.

Richtig toll ist das nicht, aber immerhin kann man dann die "qualifier" 
konsistent schreiben:

Zitat von 
http://stackoverflow.com/questions/2478151/why-is-volatile-parasitic-in-c

Always write the const / volatile qualifier after what you wanted to 
qualify. It is the only way to write qualifiers consistently, because 
you can write both volatile int and int volatile when you want a 
volatile integer, but only int * volatile will give you a volatile 
pointer.
Gast #2818153
Lesenswert?

Yalu X. schrieb:
> Rolf Magnus schrieb:
>> Das ist aber falsch.
>
> Ich glaube eher, wie haben eine Unterschiedliche Leseweise für
> Typdeklarationen.

Ja, du versuchst, die vermutlich ursprünglich gedachte Leseweise 
anzuwenden, ich versuche, die durch was zu ersetzen, das für mich mehr 
Sinn ergibt. Keine Ahnung, welche Variante nun die bessere ist. ;-)

> Mit meiner oben vorgeschlagenen "Von-Innen-Nach-Außen-Methode" (die IMHO
> auch Ritchie bei der Konzeptioen der Deklarationssystax im Kopf gehabt
> hat) lese ich das so:
>
> p1 dereferenziert ist konstant und vom Typ int.

Damit hab ich insofern schon meine Schwierigkeit, als daß für mich das * 
hier in der Deklaration ausschließlich die Bedeutung "Zeiger auf das, 
was davor steht" hat, während das dereferenzierungs-Sternchen damit 
erstmal direkt nichts zu tun hat.
Daß die Dereferenzierung und die Zeigerdeklaration das selbe Zeichen 
nutzen ist natürlich Absicht und resultiert wohl aus der von dir 
angegebenen Leseweise, aber ich finde das unlogisch, weil es zwei ganz 
verschiedene Dinge sind.

> Man könnte auch sagen, das const bezieht sich auf die Membervariablen,
> die von der Methode nicht verändert werden.

Genauer gesagt beziehen sie sich auf den this-Pointer innerhalb der 
Funktion. Dieser ist dann nämlich ein Zeiger auf const. Alle Zugriffe 
auf die Membervariablen werden (entweder implizit oder explizit) über 
diesen durchgeführt, also sind sie speziell in dieser Funktion auch alle 
const.

> Dadurch, dass rechts vom const nichts steht, wird angezeigt, dass die
> konstanten Dinge woanders stehen, nämlich dort, wor die Membervariablen
> deklariert sind.

Aber dennoch bezieht sich das const nicht ganz allegemin auf die 
Membervariablen, sondern nur auf Zugriffe auf diese aus der Funktion 
heraus.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren