Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Um ponteiro é um tipo de variável. Ele armazena o endereço de um objeto na memória e é usado para acessar esse objeto. Um ponteiro bruto é um ponteiro cujo tempo de vida não é controlado por um objeto de encapsulamento, como um ponteiro inteligente. Um ponteiro bruto pode receber o endereço de outra variável não ponteiro ou pode receber um valor de nullptr
. Um ponteiro que não teve atribuído um valor contém dados aleatórios.
Um ponteiro também pode ser desreferenciado para recuperar o valor do objeto para o qual aponta. O operador de acesso a membro fornece acesso aos membros de um objeto.
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
Um ponteiro pode apontar para um objeto tipado ou para void
. Quando um programa aloca um objeto no heap na memória, ele recebe o endereço desse objeto na forma de um ponteiro. Esses ponteiros são chamados de ponteiros proprietários. Um ponteiro proprietário (ou uma cópia dele) deverá ser usado para liberar explicitamente o objeto alocado no heap quando não for mais necessário. A falha em liberar a memória resulta em uma perda de memória, e tornará esse local de memória indisponível para qualquer outro programa no computador. A memória alocada usando new
deverá ser liberada usando delete
(ou delete[]
). Para obter mais informações, consulte os operadores new
e delete
.
MyClass* mc = new MyClass(); // allocate object on the heap
mc->print(); // access class member
delete mc; // delete object (please don't forget!)
Um ponteiro (se não for declarado como const
) poderá ser incrementado ou decrementado para apontar para outro local na memória. Essa operação é chamada de aritmética de ponteiro. É usado na programação em estilo C para iterar sobre elementos em matrizes ou outras estruturas de dados. Um ponteiro const
não pode apontar para um local de memória diferente e, nesse sentido, é semelhante a uma referência. Para obter mais informações, consulte ponteiros const
e volatile
.
// 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.
Em sistemas operacionais de 64 bits, um ponteiro tem um tamanho de 64 bits. O tamanho do ponteiro de um sistema determina quanta memória endereçável poderá ter. Todas as cópias de um ponteiro apontam para o mesmo local de memória. Ponteiros (junto com referências) são usados extensivamente em C++ para passar objetos maiores de e para funções. Muitas vezes, é mais eficiente copiar o endereço de um objeto do que copiar o objeto inteiro. Ao definir uma função, especifique os parâmetros do ponteiro como const
, exceto se você quiser que a função modifique o objeto. Em geral, as referências const
são a maneira preferencial de passar objetos para funções, a menos que o valor do objeto possa ser nullptr
.
Ponteiros para funções permitem que funções sejam passadas para outras funções. Eles são usados para "retornos de chamada" na programação em estilo C. O C++ moderno usa expressões lambda para essa finalidade.
Inicialização e acesso a membros
O exemplo a seguir mostra como declarar, inicializar e usar um ponteiro bruto. Ele inicializa usando new
para apontar um objeto alocado no heap, que você deverá explicitamente delete
. O exemplo também mostra alguns dos perigos associados aos ponteiros brutos. (Lembre-se, este exemplo é programação no estilo C e não C++ moderno).
#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
}
Aritmética de ponteiros e matrizes
Ponteiros e matrizes estão estritamente relacionados. Quando uma matriz é passada por valor para uma função, ela é passada como um ponteiro para o primeiro elemento. O exemplo a seguir demonstra as seguintes propriedades importantes de ponteiros e matrizes:
- O operador
sizeof
retorna o tamanho total em bytes de uma matriz. - Para determinar o número de elementos, divida o total de bytes pelo tamanho de um elemento.
- Quando uma matriz é passada para uma função, ela decai para um tipo de ponteiro.
- Quando o operador
sizeof
é aplicado a um ponteiro, ele retorna o tamanho do ponteiro, por exemplo, 4 bytes em x86 ou 8 bytes em 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);
}
Certas operações aritméticas podem ser usadas em ponteiros não const
para fazê-los apontar para outro local de memória. Os ponteiros são incrementados e decrementados usando os operadores ++
, +=
, -=
e --
. Essa técnica pode ser usada em matrizes e é especialmente útil em buffers de dados não tipados. Um void*
é incrementado pelo tamanho de um char
(1 byte). Um ponteiro tipado é incrementado pelo tamanho do tipo para o qual aponta.
O exemplo a seguir demonstra como a aritmética de ponteiro pode ser usada para acessar pixels individuais em um bitmap no Windows. Observe o uso de new
e delete
, e o operador de desreferência.
#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();
}
ponteiros void*
Um ponteiro para void
simplesmente aponta para um local de memória bruta. Às vezes, é necessário usar ponteiros void*
, por exemplo, ao passar entre o código C++ e as funções C.
Quando um ponteiro tipado é convertido em um ponteiro void
, o conteúdo do local da memória fica inalterado. No entanto, as informações de tipo são perdidas, para que não seja possível fazer operações de incremento ou decremento. Um local de memória pode ser convertido, por exemplo, de MyClass*
para void*
e de volta para MyClass*
. Essas operações são inerentemente propensas a erros e exigem muito cuidado para evitá-los. O C++ moderno desencoraja o uso de void
ponteiros em quase todas as circunstâncias.
//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);
}
Ponteiros para funções
Na programação no estilo C, os ponteiros de função são usados principalmente para passar funções para outras funções. Essa técnica permite que o chamador personalize o comportamento de uma função sem modificá-la. No C++ moderno, as Expressões lambda fornecem a mesma capacidade com maior segurança de tipos e outras vantagens.
Uma declaração de ponteiro de função especifica a assinatura que a função apontada deverá ter:
// 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);
O exemplo a seguir mostra uma função combine
que usa como parâmetro qualquer função que aceita um std::string
e retorna um std::string
. Dependendo da função que é passada para combine
, ela precederá ou anexará uma cadeia de caracteres.
#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";
}
Confira também
Ponteiros inteligentesOperador de indireção: *
Operador address-of: &
Boas-vindas de volta ao C++