PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Unklarheiten bei C/C++


Binaermensch
2006-01-31, 18:18:57
moin!

Ich versuche mir C++ anhand eines Einsteigertutorials selbst beizubringen.
Derzeit bereitet mir folgendes Probleme:


1.)
Im Tutorial wird erwähnt, das Strings eigentlich nichts weiter als char-Arrays sind.

Trotzdem gibt sizeof die Größe unterschiedlich an. Auch wird beim Array bei einer Ausgabe durch cout noch ein "=" angehangen.
Beispiel:#include<iostream>
using namespace std;

int main() {
char zeichenarray[] = {'H','a','l','l','o',' ','W','e','l','t'};
string zeichenkette = "Hallo Welt";

cout << sizeof zeichenarray << endl; // gibt 10 aus
cout << sizeof zeichenkette << endl; // gibt 4 aus

cout << zeichenarray << endl; // Gibt Hallo Welt= aus
cout << zeichenkette << endl; // Gibt Hallo Welt aus

cin.get();
}


2.)
Im Kapitel über Pointer habe ich erfahren, dass mit dem &-Zeichen die Adresse einer Variable übergeben wird.
Beispiel: char* zeiger = &zeichenkette[0] // zeiger hat nicht den Wert von zeichenkette[0], sondern die Speicheradresse davon.

Meine Schlußfolgerung:

int variable = 7;
cout << &variable << endl;

bzw.

int variable = 7;
int* zeiger = &variable;
cout << zeiger << endl;

sollte also die Speicheradressen von variable ausgeben.
Tut es aber nicht.#include<iostream>
using namespace std;

int main() {
string zeichenkette = "Hallo Welt";

for(int c = 0; zeichenkette[c]; c++)
cout << &zeichenkette[c] << endl;

for(char* zeiger = &zeichenkette[0]; *zeiger; zeiger++) {
cout << zeiger << endl;
}

cin.get();
}Ausgabe:Hallo Welt
allo Welt
llo Welt
lo Welt
o Welt
Welt
Welt
elt
lt
t
Hallo Welt
allo Welt
llo Welt
lo Welt
o Welt
Welt
Welt
elt
lt
tKann mir jemand erklären wieso?
Ich habe zwar bereits erfahren, dass ich die Speicheradressen mithilfe von reinterpret_cast erhalte, meinem Verständnis nach sollte es jedoch auch ohne dem gehn.

#include<iostream>
using namespace std;

int main() {
string zeichenkette = "Hallo Welt";

for(char* zeiger = &zeichenkette[0]; *zeiger; zeiger++) {
int i = reinterpret_cast<int>(zeiger);
cout << i << endl;
}

cin.get();
}



Danke für die Hilfe!

Coda
2006-01-31, 18:29:21
Zu 1:
Du must das zeichenarray mit 0 terminieren! Oder du verwendest char zeichenarray[] = "Hallo, Welt!", dann wirds automatisch terminiert

std::string ist eine Klasse die selber einen String verwaltet, sizeof gibt nicht die Länge des verwalteten Arrays zurück sondern die Größe des Objektes auf dem Heap. Wenn du die Länge brauchst nimm std::string::size()

Zu 2:
std::cout::operator<<() ist so überladen, dass es bei char* annimmt dass du einen C-String ausgeben willst und nicht die Speicheraddresse desselben. Wenn du die Speicheraddresse willst must du wohl auf void* casten.

Du solltest dir angewöhnen bei den for-schleifen preincrements statt postincrements zu verwenden, das macht bei Iteratoren einen Unterschied.

Trap
2006-01-31, 18:33:46
Ein std::string ist ein Handle auf ein char[], sieht also intern irgendwie so aus:
class string {
char* x;
}

Deshalb gibt sizeof() bei std::string nichts zurück was irgendwas mit der Länge des Strings zu tun hat.

Zu 2)
In C-code werden char* (also Speicheradressen von char-arrays) als Strings benutzt und deshalb unterstützen auch die iostreams das. Das & macht genau das gleiche wie bei dem Beispiel mit int obendrüber. Das << hat aber eine Spezialbehandlung für Speicheradressen von chars.

Coda
2006-01-31, 18:39:17
Mir ist unerklärlich wie std::string nur 4 bytes groß sein soll. Da gibts z.B. npos das der Standard vorschreibt.

Bei mir hats auch 28 Bytes (VC++ 2005).

gentoo
2006-01-31, 18:40:22
1.)
Mit sizeof erhälst du die Größe einer Klasse retour.
Da ein Char 1 Byte reserviert hat und dein char-Array 10 Zeichen hat, bekommst
du als Antwort 10.
Ein String hat bei deinem System einen reservierten Speicher von 4.
Um die Länge eines String auszulesen rufst du auf deinen String die Methode length() auf
(zeichenkette.length())

2.)
Deine FOR-Schleifen haben keine Abbruchbedingungen,
korrekt wäre es um die einzelnen Speicheraddressen des Strings zu erhalten:

for(int c = 0; c < zeichenkette.length(); c++)
cout << &zeichenkette.c_str()[c] << endl;

EDIT:
Oops, anscheinend sind deine Fragen in der Zwischenzeit schon beantwortet worden. ;)

Coda
2006-01-31, 18:42:10
Deine FOR-Schleifen haben keine Abbruchbedingungen,Doch haben sie. Und sie stimmen auch.

Trap
2006-01-31, 18:47:16
Mir ist unerklärlich wie std::string nur 4 bytes groß sein soll. Da gibts z.B. npos das der Standard vorschreibt.
In GCC waren sie mal so implementiert (ich bin mir nicht sicher ob es immernoch so ist), dass std::string nur 1 Pointer enthält und alle anderen Daten direkt vor dem eigentlichen String stehen.

gentoo
2006-01-31, 18:50:53
Doch haben sie. Und sie stimmen auch.

Stimmt ist anscheinend eine Eigenheit von der STL-Implementierung von Strin
( man lernt doch nie aus :D )
Schön ist es trotzdem nicht, man sollte besser at() verwenden, da sie
dir bei einem ungünstigen Index eine Exception wirft.

Coda
2006-01-31, 18:51:55
In GCC waren sie mal so implementiert (ich bin mir nicht sicher ob es immernoch so ist), dass std::string nur 1 Pointer enthält und alle anderen Daten direkt vor dem eigentlichen String stehen.std::string::npos ist aber im Standard enthalten, d.h. es ist also auf keinen Fall standardkonform.

Schön ist es trotzdem nicht, man sollte besser at() verwenden, da sie
dir bei einem ungünstigen Index eine Exception wirft.Dann sollte man gleich besser Java verwenden.

Trap
2006-01-31, 18:56:38
Was ist mit std::string::npos? Das muss doch nicht in jedem Objekt enthalten sein, es reicht doch wenn es static ist, oder?

Java hat keine generischen Container die int oder char enthalten können, was ziemlich blöd ist (Container die Integer enthalten sind nicht das gleiche...).

Coda
2006-01-31, 19:06:15
Ok, das static habe ich übersehen...

Binaermensch
2006-01-31, 22:22:25
Probleme gelöst, danke an alle für die nette Hilfe! :)

P.S.: Das Konzept von CPP zu verstehen ist verdammt heavy. :/

Binaermensch
2006-02-04, 01:18:00
3.)
Ich übergebe die Variable „i” an eine Funktion.
Wird hier der rvalue oder der lvalue von „i” verwendet?

Xmas
2006-02-04, 01:46:01
3.)
Ich übergebe die Variable „i” an eine Funktion.
Wird hier der rvalue oder der lvalue von „i” verwendet?
Kommt drauf an. Normalerweise hast du Call-by-Value ("rvalue" wird übergeben, Variable kann nicht verändert werden). Erwartet die Funktion aber eine Referenz, wird Call-by-Reference gemacht ("lvalue" wird übergeben, der Inhalt kann verändert werden).


void f1(int i)
{
i = 2;
}

void f2(int &i)
{
i = 2;
}

int main()
{
int i = 0;
f1(i); // i immer noch 0
f2(i); // hiernach ist i gleich 2
}

zeckensack
2006-02-04, 02:13:25
Ein lvalue ist ein Ausdruck der in einer Zuweisung links vom Zuweisungsoperator stehen kann, und ein rvalue sinngemäß rechts. Natürlich gibt's da Überschneidung. Die Konstante 5 ist als lvalue nicht zulässig, aber sehr wohl als rvalue. Funktionsaufrufe und Variablen die im aktuellen Kontext const sind können ebenfalls keine lvalues sein etc. Wenn ich jetzt nichts wichtiges vergessen habe, ist jeder zulässige lvalue auch immer ein zulässiger rvalue (als Übermenge).

Die Frage ist merkwürdig. Die Antwort ist "Es reicht wenn sich i als rvalue qualifiziert".

Das Beispiel von XMas ist IMO geschummelt und die Aussage dass bei Referenzübergabe ein lvalue genommen wird ist genau genommen falsch, weil eine Referenz in C++ nur syntaktischer Zucker für einen Zeiger ist. Der Zeiger wird "by value" übergeben, auch wenn man ihn nicht direkt zu fassen bekommt.

Die eigentliche Logik dahinter:
=> Wenn die aufgerufene Funktion die Variable ändert, kann der Parameter nicht als const-Referenz deklariert sein.
=> Damit man eine nicht-const-Referenz erzeugen kann, muss sich der Parameter im aufrufenden Code auch als lvalue qualifizieren können.

Tatsächlich übergeben wird ein impliziter Zeiger, und da quasi alles sich automatisch als rvalue eignet, ist das kein Problem.
Die Erklärung ist allerdings dann unzutreffend, wenn der Parameter als const int& deklariert ist. Dann ist nämlich die Qualifikation zum lvalue wieder unnötig.

Xmas
2006-02-04, 03:09:29
Zecki, ich fand die Formulierung der Frage zunächst auch etwas seltsam, aber es gibt wohl verschiedene Interpretationen von rvalue und lvalue, die letztlich normalerweise aufs gleiche hinauslaufen:
http://cplus.about.com/od/cprogrammin1/l/bldef_rvalue.htm
Mit location value und read value ergibt die Frage durchaus Sinn.

Referenzen sind immer nur gekapselte Zeiger. Betrachtet man das als Schummeln, so gibt es Call-by-Reference überhaupt nicht.

Für die Frage ob Call-by-Value oder Call-by-Reference spielt nur eines eine Rolle: Wenn ich "f(x)" aufrufe, wird der Wert von x übergeben oder die Adresse von x? Entsprechend gibt es in Java beispielsweise nur Call-by-Value.

zeckensack
2006-02-04, 03:25:44
Das wichtige war der letzte Satz.void f(const int& franz) { }

int i=5;
f(i);Die Sache mit "location value" habe ich mir jetzt noch nicht angesehen, muss auch gleich ins Bettchen springen und verschiebe das dann mal auf morgen ... aber nach meiner "klassischen" Interpretation führt die Referenz per se noch nicht zu einem lvalue.

Binaermensch
2006-02-04, 13:33:26
Erstmal danke für eure Antworten!

Ich denke, es wäre vielleicht nicht schlecht wenn ich mein (Verständnis-)Problem noch etwas näher ausführen würde.

Um sicherzugehen, dass ich nicht von völlig falschen Vorraussetzungen ausgehe, poste ich mal die Grundlagen so wie ich sie verstanden habe:

Zu jeder Variable gibt es einen lvalue (left-value) und einen rvalue (right-value)
lvalue: Die Adresse jenes Speicherbereichs, an dem der Wert (bzw. der rvalue) der Variable hinterlegt ist.
rvalue: Der Wert der Variablen.

Je nachdem ob die Variable links oder rechts vom Zuweisungsoperator steht, wird deren l- oder rvalue verwendet.

int i;
int k;
i = k;
// In Deutsch: Schreibe den rvalue von k in jenen Speicher, dessen Adresse in der lvalue von i hinterlegt ist.

Will ich nun davon abweichendes Verhalten erzwingen, muss ich den &- oder den *-Operator verwenden.
&-Operator: Verwende den lvalue der Variable, obwohl Variable rechts vom Zuweisungsoperator steht.
*-Operator: Verwende den rvalue der Variable, obwohl Variable links vom Zuweisungsoperator steht.

int i;
int k;
i = k;
i = &k;
// In Deutsch: Schreibe den rvalue lvalue von k in jenen Speicher, dessen Adresse in der lvalue von i hinterlegt ist.

int* i;
int k;
i = k;
*i = k;
// In Deutsch: Schreibe den rvalue von k in jenen Speicher, dessen Adresse in der lvalue rvalue von i hinterlegt ist.

Soweit ist für mich alles klar. Ich hoffe, bis hierhin ist auch alles richtig.


Probleme bekomme ich jedoch bei Zeile B des folgendem Codeschnippsel, genauergesagt bei rot markiertem Code:
/* Program 2.1 from PTRTUT10.HTM 6/13/97 */

#include <stdio.h>

int my_array[] = {1,23,17,4,-5,100};
int *ptr;

int main(void)
{
int i;
ptr = &my_array[0]; /* point our pointer to the first
element of the array */
printf("\n\n");
for (i = 0; i < 6; i++)
{
printf("my_array[%d] = %d ",i,my_array[ i ]); /*<-- A */
printf("ptr + %d = %d\n",i, *(ptr + i)); /*<-- B */
}
return 0;
}Die Tatsache, dass hier ausdrücklich ein rvalue übergeben wird (denn genau das ist doch die Bedeutung des *), impliziert für mich, dass hier normalerweise eine lvalue Verwendung finden würde.

Ergo: Der Klammerausdruck steht für einen lvalue. (Ich dachte, nur Variablen/Objekte haben r- und lvalues?)
(Oder mit anderen Worten: Ohne den *-Operator würde auf einen lvalue zugegriffen werden.)

Bei den Variablen innerhalb der Klammern wird jedoch auf deren rvalues zugegriffen. Für sich alleine gesehen macht das auch durchaus Sinn, was ich dabei nicht verstehe ist folgendes:


Warum wird, sobald ich eine Klammer „drumherum” lege, plötzlich der lvalue verwendet, obwohl doch ohne die Klammer (bzw. innerhalb dieser) auf die rvalues zugegriffen wird?



Würde Zeile B folgendermaßen aussehen, hätte ich absolut kein Problem das alles nach-zu-vollziehen:

printf("ptr + %d = %d\n",i, *(ptr = ptr + i)); /*<-- B */
// Drittes Argument von printf in Deutsch: Addiere den rvalue von ptr zu dem rvalue von i, und schreibe das Ergebnis in jene Adresse, welche im lvalue von ptr steht.

Leider sieht Zeile B jedoch nicht so aus. ptr = fehlt.

Coda
2006-02-04, 13:37:25
Ha? Wieso sollte bei B ptr etwas zugewiesen werden bevor er dereferenziert wird?

Ehrlich gesagt weiß ich nicht was ein rvalue oder lvalue genau ist und verstehe den Code so wahrscheinlich besser als ohne den ganzen Hirnfick darum X-D

*(ptr + i) bedeutet einfach: Nimm die Speicheraddresse in ptr, addiere i*sizeof(int) dazu und ließ den Wert aus der entstandenen Addresse. ptr+i ergibt eben einen neuen int* (lvalue nehm ich an).

Binaermensch
2006-02-04, 16:17:14
Vergesst Problem Nr. 3...

Bei den ganzen Tutorials die ich mir gerade durchlese hatte ich den Kopf so voll mit Pointern, rvalues und lvalues, sodass ich garnicht auf die Idee gekommen bin, dass dieses Verhalten auch nicht-Pointer-bedingt sein könnte.


Über was ich mir die ganze Zeit den Kopf zerbrochen habe, und was ich nicht verstehen konnte, war schlicht das, dass
int x = a + b; printf("%d", x);
und
printf("%d", a + b); völlig äquivalent sind.

Folglich sind auch
int* a = ptr + i
printf("%d", *a);
und
printf("%d", *(ptr + i));
äquivalent.


Entschuldigung, dass ich euch mit dem Schmarrn eure Zeit gestohlen habe. Ich geh mich jetzt eingraben.