Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Ein Zeiger ist ein Typ von Variable. Er speichert die Adresse eines Objekts im Arbeitsspeicher und wird verwendet, um auf dieses Objekt zuzugreifen. Ein unformatierter Zeiger ist ein Zeiger, dessen Lebensdauer nicht durch ein Kapselobjekt gesteuert wird, z. B. einen intelligenten Zeiger. Einem unformatierten Zeiger kann die Adresse einer anderen Nichtzeigervariablen zugewiesen werden, oder ihm kann ein Wert nullptr
zugewiesen werden. Ein Zeiger, dem kein Wert zugewiesen wurde, enthält zufällige Daten.
Ein Zeiger kann auch abgeleitet werden, um den Wert des Objekts abzurufen, auf das es zeigt. Der Memberzugriffsoperator bietet Zugriff auf die Elemente eines Objekts.
int* p = nullptr; // declare pointer and initialize it
// so that it doesn't store a random address
int i = 5;
p = &i; // assign pointer to address of object
int j = *p; // dereference p to retrieve the value at its address
Ein Zeiger kann auf ein typisiertes Objekt oder auf void
zeigen. Wenn ein Programm ein Objekt im Heap zuweist, empfängt es die Adresse dieses Objekts in Form eines Zeigers. Solche Zeiger werden als besitzende Zeiger bezeichnet. Ein besitzender Zeiger (oder eine Kopie davon) muss verwendet werden, um das vom Heap zugewiesene Objekt explizit freizuweisen, wenn es nicht mehr benötigt wird. Wenn der Speicher nicht freigegeben wird, tritt ein Speicherverlust auf und rendert diesen Speicherort, der für ein anderes Programm auf dem Computer nicht verfügbar ist. Der verwendete new
Speicher muss mit delete
(oder delete[]
) freigegeben werden. Weitere Informationen finden Sie unter new
und delete
Operatoren.
MyClass* mc = new MyClass(); // allocate object on the heap
mc->print(); // access class member
delete mc; // delete object (please don't forget!)
Ein Zeiger (wenn er nicht als const
deklariert ist) kann erhöht oder erniedrigt werden, um auf eine andere Stelle im Speicher zu zeigen. Dieser Vorgang wird als Zeigerarithmetik bezeichnet. Es wird in der Programmierung im C-Stil verwendet, um Elemente in Arrays oder anderen Datenstrukturen zu durchlaufen. Ein const
Zeiger kann nicht auf einen anderen Speicherort verweisen, und in diesem Sinne ähnelt es einem Verweis. Weitere Informationen finden Sie unter const
und volatile
Zeiger.
// declare a C-style string. Compiler adds terminating '\0'.
const char* str = "Hello world";
const int c = 1;
const int* pconst = &c; // declare a non-const pointer to const int
const int c2 = 2;
pconst = &c2; // OK pconst itself isn't const
const int* const pconst2 = &c;
// pconst2 = &c2; // Error! pconst2 is const.
Auf 64-Bit-Betriebssystemen hat ein Zeiger eine Größe von 64 Bit. Die Zeigergröße eines Systems bestimmt, wie viel adressierbarer Arbeitsspeicher vorhanden sein kann. Alle Kopien eines Zeigers zeigen auf denselben Speicherort. Zeiger (zusammen mit Verweisen) werden in C++ umfassend verwendet, um größere Objekte an und von Funktionen zu übergeben. Häufig ist es effizienter, die Adresse eines Objekts zu kopieren, als das gesamte Objekt zu kopieren. Geben Sie beim Definieren einer Funktion Zeigerparameter so an, als const
, es sei denn, Sie möchten, dass die Funktion das Objekt ändern soll. Im Allgemeinen sind const
Verweise die bevorzugte Methode zum Übergeben von Objekten an Funktionen, es sei denn, der Wert des Objekts kann nullptr
sein .
Zeiger auf Funktionen ermöglichen das Übergeben von Funktionen an andere Funktionen. Sie werden für „Rückrufe” in der Programmierung im C-Stil verwendet. Modern C++ verwendet Lambda-Ausdrücke für diesen Zweck.
Initialisierung und Memberzugriff
Das folgende Beispiel zeigt, wie man einen unformatierten Zeiger deklariert, initialisiert und verwendet. Es wird mithilfe der Verwendung von new
eines Objekts initialisiert, das auf den Heap zugeordnet ist, das Sie explizit delete
verwenden müssen. Das Beispiel zeigt auch einige der Gefahren, die mit unformatierten Zeigern verbunden sind. (Denken Sie daran, dass es sich bei diesem Beispiel um die Programmierung im C-Stil handelt und nicht um moderne C++!)
#include <iostream>
#include <string>
class MyClass
{
public:
int num;
std::string name;
void print() { std::cout << name << ":" << num << std::endl; }
};
// Accepts a MyClass pointer
void func_A(MyClass* mc)
{
// Modify the object that mc points to.
// All copies of the pointer will point to
// the same modified object.
mc->num = 3;
}
// Accepts a MyClass object
void func_B(MyClass mc)
{
// mc here is a regular object, not a pointer.
// Use the "." operator to access members.
// This statement modifies only the local copy of mc.
mc.num = 21;
std::cout << "Local copy of mc:";
mc.print(); // "Erika, 21"
}
int main()
{
// Use the * operator to declare a pointer type
// Use new to allocate and initialize memory
MyClass* pmc = new MyClass{ 108, "Nick" };
// Prints the memory address. Usually not what you want.
std:: cout << pmc << std::endl;
// Copy the pointed-to object by dereferencing the pointer
// to access the contents of the memory location.
// mc is a separate object, allocated here on the stack
MyClass mc = *pmc;
// Declare a pointer that points to mc using the addressof operator
MyClass* pcopy = &mc;
// Use the -> operator to access the object's public members
pmc->print(); // "Nick, 108"
// Copy the pointer. Now pmc and pmc2 point to same object!
MyClass* pmc2 = pmc;
// Use copied pointer to modify the original object
pmc2->name = "Erika";
pmc->print(); // "Erika, 108"
pmc2->print(); // "Erika, 108"
// Pass the pointer to a function.
func_A(pmc);
pmc->print(); // "Erika, 3"
pmc2->print(); // "Erika, 3"
// Dereference the pointer and pass a copy
// of the pointed-to object to a function
func_B(*pmc);
pmc->print(); // "Erika, 3" (original not modified by function)
delete(pmc); // don't forget to give memory back to operating system!
// delete(pmc2); //crash! memory location was already deleted
}
Zeigerarithmetik und Arrays
Zeiger und Arrays sind eng miteinander verknüpft. Wenn ein Array an eine Funktion übergeben wird, wird es als Zeiger auf das erste Element übergeben. Im folgenden Beispiel werden die folgenden wichtigen Eigenschaften von Zeigern und Arrays veranschaulicht:
- Der
sizeof
Operator gibt die Gesamtgröße in Byte eines Arrays zurück. - Um die Anzahl der Elemente zu ermitteln, dividieren Sie die Gesamtbyteanzahl durch die Größe eines Elements.
- Wenn ein Array an eine Funktion übergeben wird, wird es in einen Zeigertyp umgewandelt.
- Wenn der
sizeof
Operator auf einen Zeiger angewendet wird, gibt er die Zeigergröße zurück, z. B. 4 Bytes auf x86 oder 8 Bytes auf x64
#include <iostream>
void func(int arr[], int length)
{
// returns pointer size. not useful here.
size_t test = sizeof(arr);
for(int i = 0; i < length; ++i)
{
std::cout << arr[i] << " ";
}
}
int main()
{
int i[5]{ 1,2,3,4,5 };
// sizeof(i) = total bytes
int j = sizeof(i) / sizeof(i[0]);
func(i,j);
}
Bestimmte arithmetische Vorgänge können auf Nichtzeigernconst
verwendet werden, um sie auf einen anderen Speicherspeicherort zu verweisen. Zeiger werden mithilfe der ++
, +=
, -=
und --
Operatoren erhöht. Diese Technik kann in Arrays verwendet werden und ist besonders nützlich bei Puffern von nicht typisierten Daten. Ein void*
wird um die Größe eines char
(1 Byte) erhöht. Ein typisierter Zeiger wird um die Größe des Typs erhöht, auf den er verweist.
Im folgenden Beispiel wird veranschaulicht, wie Zeigerarithmetik verwendet werden kann, um auf einzelne Pixel in einer Bitmap unter Windows zuzugreifen. Beachten Sie die Verwendung von new
und und delete
, und den Dereference-Operator.
#include <Windows.h>
#include <fstream>
using namespace std;
int main()
{
BITMAPINFOHEADER header;
header.biHeight = 100; // Multiple of 4 for simplicity.
header.biWidth = 100;
header.biBitCount = 24;
header.biPlanes = 1;
header.biCompression = BI_RGB;
header.biSize = sizeof(BITMAPINFOHEADER);
constexpr int bufferSize = 30000;
unsigned char* buffer = new unsigned char[bufferSize];
BITMAPFILEHEADER bf;
bf.bfType = 0x4D42;
bf.bfSize = header.biSize + 14 + bufferSize;
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //54
// Create a gray square with a 2-pixel wide outline.
unsigned char* begin = &buffer[0];
unsigned char* end = &buffer[0] + bufferSize;
unsigned char* p = begin;
constexpr int pixelWidth = 3;
constexpr int borderWidth = 2;
while (p < end)
{
// Is top or bottom edge?
if ((p < begin + header.biWidth * pixelWidth * borderWidth)
|| (p > end - header.biWidth * pixelWidth * borderWidth)
// Is left or right edge?
|| (p - begin) % (header.biWidth * pixelWidth) < (borderWidth * pixelWidth)
|| (p - begin) % (header.biWidth * pixelWidth) > ((header.biWidth - borderWidth) * pixelWidth))
{
*p = 0x0; // Black
}
else
{
*p = 0xC3; // Gray
}
p++; // Increment one byte sizeof(unsigned char).
}
ofstream wf(R"(box.bmp)", ios::out | ios::binary);
wf.write(reinterpret_cast<char*>(&bf), sizeof(bf));
wf.write(reinterpret_cast<char*>(&header), sizeof(header));
wf.write(reinterpret_cast<char*>(begin), bufferSize);
delete[] buffer; // Return memory to the OS.
wf.close();
}
void*
Zeiger
Ein Zeiger, der void
einfach auf einen unformatierten Speicherspeicherort zeigt. Manchmal ist es erforderlich, void*
Zeiger zu verwenden, z. B. beim Übergeben von C++-Code- und C-Funktionen.
Wenn ein typisierter Zeiger in einen void
Zeiger umgebrochen wird, bleiben die Inhalte des Speicherspeicherorts unverändert. Die Typinformationen gehen jedoch verloren, sodass Sie keine Inkrement- oder Dekrementierungsvorgänge ausführen können. Ein Speicherort kann z. B. von MyClass*
zu void*
und wieder in MyClass*
umgewandelt werden. Solche Vorgänge sind inhärent fehleranfällig und erfordern eine große Sorgfalt, um Fehler zu vermeiden. Modern C++ entmutigt die Verwendung von void
Zeigern unter fast allen Umständen.
//func.c
void func(void* data, int length)
{
char* c = (char*)(data);
// fill in the buffer with data
for (int i = 0; i < length; ++i)
{
*c = 0x41;
++c;
}
}
// main.cpp
#include <iostream>
extern "C"
{
void func(void* data, int length);
}
class MyClass
{
public:
int num;
std::string name;
void print() { std::cout << name << ":" << num << std::endl; }
};
int main()
{
MyClass* mc = new MyClass{10, "Marian"};
void* p = static_cast<void*>(mc);
MyClass* mc2 = static_cast<MyClass*>(p);
std::cout << mc2->name << std::endl; // "Marian"
delete(mc);
// use operator new to allocate untyped memory block
void* pvoid = operator new(1000);
char* pchar = static_cast<char*>(pvoid);
for(char* c = pchar; c < pchar + 1000; ++c)
{
*c = 0x00;
}
func(pvoid, 1000);
char ch = static_cast<char*>(pvoid)[0];
std::cout << ch << std::endl; // 'A'
operator delete(pvoid);
}
Zeiger auf Funktionen
Bei der Programmierung im C-Stil werden Funktionszeiger hauptsächlich verwendet, um Funktionen an andere Funktionen zu übergeben. Mit dieser Technik kann der Aufrufer das Verhalten einer Funktion anpassen, ohne sie zu ändern. In modernen C++ bieten Lambda-Ausdrücke die gleiche Funktion mit größerer Typsicherheit und anderen Vorteilen.
Eine Funktionszeigerdeklaration gibt die Signatur an, die die Zeigerfunktion aufweisen muss:
// Declare pointer to any function that...
// ...accepts a string and returns a string
string (*g)(string a);
// has no return value and no parameters
void (*x)();
// ...returns an int and takes three parameters
// of the specified types
int (*i)(int i, string s, double d);
Das folgende Beispiel zeigt eine Funktion combine
, die als Parameter eine beliebige Funktion, die eine std::string
akzeptiert und eine std::string
zurückgibt. Abhängig von der Funktion, an die combine
übergeben wird, wird entweder eine Zeichenfolge vorangestellt oder angefügt.
#include <iostream>
#include <string>
using namespace std;
string base {"hello world"};
string append(string s)
{
return base.append(" ").append(s);
}
string prepend(string s)
{
return s.append(" ").append(base);
}
string combine(string s, string(*g)(string a))
{
return (*g)(s);
}
int main()
{
cout << combine("from MSVC", append) << "\n";
cout << combine("Good morning and", prepend) << "\n";
}
Siehe auch
Intelligente Zeigerdereferenzierungsoperator: *
Adresse des Operators: &
Willkommen bei C++