Es stimmt, wie Du es gesagt hast, Zeiger und Referenzen sind völlig unterschiedliche Dinge und liegen doch von der Semantik so nah beieinander. Um es kurz zu fassen ist eine Referenz lediglich ein anderer Name für ein Objekt, z.B.:
jetzt kann man sowohl mit r, als auch mit x arbeiten. Daher ist eine Referenz vom Typ her auch kein

Zeiger, sondern hat den gleichen Typ wie das Objekt, für das sie steht (vgl. Josuttis, »Objektorientiertes Programmieren in C++«, Seite 190f.)
Verwendet man Referenzen in Funktionsaufrufen als Parameter, so arbeitet man tatsächlich mit einem
call-by-reference Mechanismus. Eine swpa Funktion würde dann so aussehen:
Code:
void swap(int& a, int& b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
Aufgerufen wird die Funktion dann einfach mit den Variablen einer anderen Funktion:
Code:
[...]
int x = 13;
int y = 42;
[...]
swap(x, y);
Josuttis nennt die Verwirklichung dieser Sprachtechnik entweder Traum oder Alptraum, denn Referenzen können das Leben einfacher machen: Man müsste die swap Funktion mit Zeigern implementieren und den Aufruf mit swap(&x, &y); durchführen. Der Nachteil ist, dass dem »Leser« nicht mehr sofort klar sein muss, ob die Werte, die an eine Funktion übergeben werden, nun tatsächlich geändert werden, oder ob nur Kopien von diesen Werten verändert werden -- man muss also die Funktionsdeklaration beachten.
Zeiger sind eigentlich total einfach zu verstehen, man muss sich nur daran gewöhnen. Es gibt drei verschiedene Operatoren, die da greifen, einmal der Sternoperator, der einmal bei der Deklaration eingesetzt wird (int
*a -> a ist ein Zeiger auf einen Integer) und dann auch als Dereferenzierung eines Zeigers (die Verwendung von
*a gibt den int-Wert zurück). Außerdem gibt es noch den &-Operator, auch Adressoperator genannt. Mit diesem kann man die Adresse eines Objekts an einen Zeiger übergeben. Folgendes Beispiel:
Code:
#include <stdio.h>
int main()
{
int x = 4711;
int y = 13;
int *xp = &x;
int *yp = &y;
*xp = 42;
printf("x: %d, y: %d\n", *xp, *yp);
return 0;
}
Wichtig ist bei Zeigern immer, dass Speicher bereitstehen muss, sonst kommt es zu undefiniertem Verhalten, i.d.R. Speicherzugriffsfehlern. Folgendes würde also nicht

funktionieren:
Code:
#include <stdio.h>
int main()
{
int *x;
int *y;
*x = 42;
*y = 13;
printf("x: %d, y: %d\n", *x, *y);
return 0;
}
weil einfach kein Speicher für *x und *y bereitgestellt wurde.
Zusätzlich gibt es noch den Pfeiloperator (
->) und den Punkt-Operator (.), die bei Strukturen zum Einsatz kommen. Folgendes Beispiel:
Code:
#include <stdio.h>
struct test {
int a;
};
int main()
{
struct test atest;
struct test *btest = &atest;
atest.a = 42;
printf("btest->a: %d\n", btest->a);
return 0;
}
Wir legen eine Struktur atest an (allozieren dafür automatisch Speicher) und legen einen Zeiger *btest darauf an. Wenn wir mit einer Struktur direkt arbeiten, kann man mittels des Punktoperators direkt auf die Werte zugreifen (atest.a). Haben wir nur einen Zeiger darauf bekommen, könnte man schreiben:
(*btest).a also zuerst den Strukturzeiger dereferenzieren und dann mit dem Punktoperator darauf zugreifen. Diese umständliche Schreibweise ist einem mit dem Pfeiloperator abgenommen worden.
Jetzt gibt es noch schließlich zu beachten, dass sich ein Array verhält wie ein Zeiger auf sein erstes Element. Und der Grund, warum man
const char* var = "text"; so schreiben muss liegt darin begründet, dass man unter C keinen fundamentalen String-Typ hat (unter C++ bietet sich
std::string an), sondern mit einem Array aus Zeichen arbeiten muss. Und da sich Arrays und Zeiger so verwandt sind, kann man einen konstanten String eben so definieren.