Hi
Ich bin gerade dabei, C++ zu lernen, und bin beim Thema Arrays. Die
Startaddresse eines Arrays ist der Name des Arrays selbst. So kenne ich
es aus C. In C++ bin ich auf etwas gestoßen, das ich mir nicht selbst
erklären kann.
MinGW, VSCODE, W10 64-bit:
1
#include<iostream>
2
3
usingnamespacestd;
4
5
intmain()
6
{
7
intmyArr[]{10,25,4};
8
charmyName[]{'A','l','e','x'};
9
10
cout<<"Address of myArr: "<<myArr<<endl;// Address of where the array myArr starts
11
cout<<"Address of myName: "<<myName<<endl;// Address of where the array myName starts
12
13
return0;
14
}
Output:
> Address of myArr: 0x61ff04> Address of myName: Alex
Mag mich jemand aufklären?
Danke.
Alex schrieb:> Mag mich jemand aufklären?
Wenn du einen char* per Operator << ausgibst, wird davon ausgegangen,
dass du einen String ausgeben willst und nicht die Adresse seines ersten
Zeichens. Oder was würdest du bei einem std::cout << "Hello world\n";
erwarten? Die Adresse bekommst du z.B. mit einem Cast nach void*.
Es ist schon etwas schräg, dass bei der Pointer-Überladung von <<
speziell für char-Pointer eine Ausnahme gemacht wird. Den Grund hat ja
Rolf schon genannt:
Rolf M. schrieb:> Oder was würdest du bei einem std::cout << "Hello world\n";> erwarten?
Das ist halt – wie noch ein paar andere Dinge – historisch gewachsen. Da
man aber nur ganz selten Pointer-Werte ausgeben muss, ist das kein allzu
großes Problem.
Unnötigerweise erstreckt sich die Überladung dabei gleich über sämtliche
char-Pointer-Typen, nämlich
1
char*
2
signedchar*
3
unsignedchar*
4
constchar*
5
constsignedchar*
6
constunsignedchar*
Um C-Strings komfortabel ausgeben zu können, hätten eigentlich char *
und const char * genügt.
Man beachte auch, dass für die Ausgabe mit dem <<-Operator der
char-Pointer auf einen nullterminierten String zeigen muss. Eine
fehlende Nullterminierung, kann zur unerwünschten Ergebnissen führen:
Yalu X. schrieb:> Unnötigerweise erstreckt sich die Überladung dabei gleich über sämtliche> char-Pointer-Typen, nämlich>> char *> signed char *> unsigned char *> const char *> const signed char *> const unsigned char *
Nicht nur über die Zeiger, sondern auch die char-Typen selber. Das führt
dazu, dass z.B. bei
1
uint8_tval=100;
2
std::cout<<val<<'\n';
nicht etwa "100", sondern (bei ASCII-basierten Zeichensätzen) "d"
ausgegeben wird.
Falls das bis jetzt alles verwirrend klingt und weil der TO von C kommt:
In C++ sollte man die richtigen Typen für seine Aufgaben verwenden. Das
heisst in diesem Fall std::string anstatt char[]. Das Arbeiten mit
einfachen Pointern sollte man sich abgewöhnen, damit fällt man in C++
genauso oft auf die Nase, wie in C.
Thomas F. schrieb:> Das Arbeiten mit einfachen Pointern sollte man sich abgewöhnen, damit> fällt man in C++ genauso oft auf die Nase, wie in C.
Es ist aber trotzdem kein Fehler, sie verstanden zu haben, und das schon
möglichst frühzeitig. Früher oder später kommt mal mit den Pointern
sowieso in Berührung, evtl. sogar in einem wesentlich komplexeren Umfeld
als in dem Beispiel des TE. Dann ist es von Vorteil, wenn man bereits
sicher mit ihnen umgehen kann, um eben gerade nicht auf die Nase hzu
fallen.
Vielen Dank euch allen. :)
Das muss ich erstmal sacken lassen, und mich weiter tiefgründig mit C++
beschäftigen. Irgendwann fällt der Groschen sicherlich :)
Gruß,
g457 schrieb:> Kuckst Du Operatoren. Hier:
Das ist zwar rein technisch ein Operator, aber "+" ist auch ein
Operator; präziser sollte man hier "Cast" sagen.
Solche functional casts sollte man in C++ nicht benutzen, weil sie die
gleichen Regeln wie klassische C-Casts haben, welche "zu mächtig" sind
und mit welchen man schnell versehentlich etwas falsch macht. Außerdem
ist dem Leser nicht leicht ersichtlich, was da passiert. Besser sind
daher Named Casts:
1
std::cout<<"Address of myArr: "<<reinterpret_cast<std::uintptr_t>(myName)<<std::endl;// Address of where the array myArr starts
Dem Leser ist hier durch das reinterpret_cast sofort ersichtlich, dass
hier etwas "böses" passiert, nämlich ein cast von Pointer nach int. Das
ist selten nötig und fehleranfällig
Außerdem wird der Pointer dann als Dezimalzahl Ausgegeben. Sinnvoller
ist der cast nach "void*":
1
std::cout<<"Address of myArr: "<<static_cast<void*>(myName)<<std::endl;// Address of where the array myArr starts
Das ist dann auch ein static_cast, welcher "harmloser" ist.
Alex schrieb:> using namespace std;
Sollte man nicht machen: https://stackoverflow.com/q/1452721Yalu X. schrieb:> Eine> fehlende Nullterminierung, kann zur unerwünschten Ergebnissen führen:
Hier sollte man klarer sagen, dass es nicht einfach nur ein
"unerwünschtes Ergebnis" ist, sondern ein buffer overflow, was undefined
behaviour ist, d.h. ein Fehler, bei dem "irgendetwas" passieren kann,
vom Programmabsturz bis zu gelöschten Daten, und daher unbedingt zu
vermeiden ist.
Niklas G. schrieb:> Alex schrieb:>> using namespace std;>> Sollte man nicht machen: https://stackoverflow.com/q/1452721
Allerdings finde ich das andere Extrem, das gerne als beste Variante
verkauft wird, nämlich überall explizit std:: davor zu schreiben,
inzwischen auch nicht mehr so arg prickelnd.
Rolf M. schrieb:> Allerdings finde ich das andere Extrem, das gerne als beste Variante> verkauft wird, nämlich überall explizit std:: davor zu schreiben,> inzwischen auch nicht mehr so arg prickelnd.
Finde ich gar nicht so schlimm. So sieht man beim Lesen sofort, wo der
Bezeichner herkommt. Die Zeit die man zum Tippen des "std::" braucht ist
eh praktisch 0 im Vergleich zur gesamten Entwicklungszeit. Wenn man das
wirklich gar nicht aushält kann man ja problemlos "using std::cout;"
o.ä. in die Funktion/Klasse/Namespace schreiben.
Seit C++17 kann man wie oben gezeigt mehrere Symbole in eine Zeile
packen (vor C++17 musste man ganz umständlich für jedes Symbol eine
eigene using-Deklaration schreiben, weswegen von dieser Möglichkeit kaum
jemand Gebrauch gemacht hat).
Durch diese selektiven using-Deklarationen umgeht man zum einen das
Problem mit zukünftigen Name-Clashes bei der Verwendung von using
namespace, zum anderen muss man den Quellcode nicht mit unzähligen std::
verunstalten.
Noch angenehmer wäre die obige using-Deklaration zu schreiben, wenn man
auch dort den Namespace-Namen nicht ständig wiederholen müsste. Andere
Programmiersprachen wie bspw. Python (from Modulname import ...) oder
Haskell (import Modulname (...)) zeigen, wie es geht. In C++ könnte die
entsprechende Syntax folgendermaßen aussehen: