Поделиться через


Необработанные указатели (C++)

Указатель — это тип переменной. Он хранит адрес объекта в памяти и используется для доступа к нему. Необработанный указатель — это указатель, время существования которого не контролируется инкапсулирующим объектом, например умным указателем. Необработанный указатель можно назначить адрес другой переменной, отличной от указателя, или его можно назначить значение nullptr. Указатель, который не был назначен значению, содержит случайные данные.

Указатель также можно разыменовать , чтобы получить значение объекта, на который он указывает. Оператор доступа к членам объекта предоставляет доступ к членам объекта.

    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

Указатель может указывать на типизированный объект или на void. Когда программа выделяет объект в куче в памяти, он получает адрес этого объекта в виде указателя. Такие указатели называются указателями владения. Указатель на владение (или копию) должен использоваться для явного освобождения выделенного кучи объекта, если он больше не нужен. Сбой освобождения памяти приводит к утечке памяти и отрисовывает, что расположение памяти недоступно для любой другой программы на компьютере. Выделение new памяти должно быть освобождено с помощью delete (или delete[]). Дополнительные сведения см. в разделе new и delete операторах.

    MyClass* mc = new MyClass(); // allocate object on the heap
    mc->print(); // access class member
    delete mc; // delete object (please don't forget!)

Указатель (если он не объявлен как const) может быть добавочным или уменьшаться, чтобы указать другое расположение в памяти. Эта операция называется арифметикой указателя. Он используется в программировании в стиле C для итерации элементов в массивах или других структурах данных. const Указатель не может указывать на другое расположение памяти, и в этом смысле аналогичен ссылке. Дополнительные сведения см. в разделе const и 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.

В 64-разрядных операционных системах указатель имеет размер 64 бита. Размер указателя системы определяет, сколько адресной памяти она может иметь. Все копии указателя указывают на одно расположение памяти. Указатели (а также ссылки) широко используются в C++ для передачи больших объектов в функции и из них. Зачастую это более эффективно для копирования адреса объекта, чем для копирования всего объекта. При определении функции укажите параметры указателя, const если не планируется изменять объект. Как правило, ссылки являются предпочтительным способом передачи объектов в функции, const если не может быть nullptrзначение объекта.

Указатели на функции позволяют передавать функции другим функциям. Они используются для "обратных вызовов" в программировании в стиле C. Современный C++ использует лямбда-выражения для этой цели.

Инициализация и доступ к члену

В следующем примере показано, как объявить, инициализировать и использовать необработанный указатель. Он инициализирован с помощью new указания объекта, выделенного в куче, который необходимо явно delete. В примере также показано несколько опасностей, связанных с необработанными указателями. (Помните, что этот пример — программирование в стиле C, а не современное 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
}

Арифметические указатели и массивы

Указатели и массивы тесно связаны. Когда массив передается по значению функции, он передается в качестве указателя на первый элемент. В следующем примере показаны следующие важные свойства указателей и массивов:

  • Оператор sizeof возвращает общий размер в байтах массива
  • Чтобы определить количество элементов, разделите общее число байтов по размеру одного элемента
  • Когда массив передается функции, он распадается на тип указателя.
  • sizeof Если оператор применяется к указателю, он возвращает размер указателя, например 4 байта в x86 или 8 байтах на 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);
}

Некоторые арифметические операции можно использовать на не-указателяхconst , чтобы они указывали на другое расположение памяти. Указатели увеличивается и уменьшается с помощью ++операторов , +=-= а также -- операторов. Этот метод можно использовать в массивах и особенно полезен в буферах нетипизированных данных. Значение void* увеличивается по размеру char (1 байта). Типизированный указатель увеличивается по размеру типа, на который он указывает.

В следующем примере показано, как арифметический указатель можно использовать для доступа к отдельным пикселям в растровом рисунке в Windows. Обратите внимание на использование new и deleteоператор разыменования.

#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* Указатели

Указатель на void простое расположение необработанной памяти. Иногда необходимо использовать void* указатели, например при передаче между кодом C++ и функциями C.

Если типизированный указатель приведение к void указателю, содержимое расположения памяти не изменяется. Однако сведения о типе теряются, поэтому нельзя выполнять операции добавочного или уменьшения. Расположение памяти может быть приведение, например, из MyClass* и void* обратно в MyClass*. Такие операции по сути подвержены ошибкам и требуют большогоvoid ухода за ошибками. Современный C++ не рекомендует использовать указатели void практически во всех обстоятельствах.

//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);
}

Указатели на функции

В программировании в стиле C указатели функций используются в основном для передачи функций другим функциям. Этот метод позволяет вызывающему объекту настраивать поведение функции, не изменяя ее. В современных выражениях C++лямбда-выражения обеспечивают ту же возможность с большей безопасностью типов и другими преимуществами.

Объявление указателя функции указывает сигнатуру, которую должна иметь функция, указывающая на следующую:

// 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);

В следующем примере показана функция, которая принимает в качестве параметра любую функцию combine , которая принимает и std::string возвращает значение std::string. В зависимости от передаваемой combineфункции он либо добавляет строку, либо добавляет строку.

#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";
}

См. также

Оператор косвенного обращения смарт-указателей: *
Оператор address-of: &
Welcome back to C++